Repository: buildpacks/lifecycle
Pull request #1206 (2023-09-22 23:16:50)

Bump github.com/GoogleContainerTools/kaniko from 1.15.0 to 1.16.0
Bumps [github.com/GoogleContainerTools/kaniko](https://github.com/GoogleContainerTools/kaniko) from 1.15.0 to 1.16.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/GoogleContainerTools/kaniko/releases">github.com/GoogleContainerTools/kaniko's releases</a>.</em></p>
<blockquote>
<h1>v1.16.0 Release 2023-09-22</h1>
<p>The executor images in this release are:</p>
<pre><code>gcr.io/kaniko-project/executor:v1.16.0
gcr.io/kaniko-project/executor:latest
</code></pre>
<p>The debug images are available at:</p>
<pre><code>gcr.io/kaniko-project/executor:debug
gcr.io/kaniko-project/executor:v1.16.0-debug
</code></pre>
<p>The slim executor images which don't contain any authentication binaries are available at:</p>
<pre><code>gcr.io/kaniko-project/executor:slim
gcr.io/kaniko-project/executor:v1.16.0-slim
</code></pre>
<p>Highlights:</p>
<ul>
<li>OCI support w/ Kaniko has been improved fixing a previous issue seen related to warming usage + OCI images</li>
<li>Fixed issue in which kaniko would at times incorrectly resolve GIDs as UIDs resulting previously in issues where it could not find the UID in the group list because indeed it could be it is not a GID.</li>
</ul>
<p>Features:</p>
<ul>
<li>fix: avoid returning the UID when resolving the GIDs. <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2689">#2689</a></li>
<li>fix: correct function name on comment <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2707">#2707</a></li>
<li>fix: ensure images layers correspond with the image media type <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2719">#2719</a></li>
<li>fix: make it so release.sh script doesn't output duplicate change PRs <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2735">#2735</a></li>
</ul>
<p>Updates and Refactors:</p>
<ul>
<li>chore(deps): bump cloud.google.com/go/storage from 1.32.0 to 1.33.0 <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2740">#2740</a></li>
<li>chore(deps): bump docker/build-push-action from 4.1.1 to 4.2.1 <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2739">#2739</a></li>
<li>chore(deps): bump docker/build-push-action from 4.2.1 to 5.0.0 <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2742">#2742</a></li>
<li>chore(deps): bump docker/setup-buildx-action from 2.10.0 to 3.0.0 <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2743">#2743</a></li>
<li>chore(deps): bump docker/setup-buildx-action from 2.9.1 to 2.10.0 <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2726">#2726</a></li>
<li>chore(deps): bump docker/setup-qemu-action from 2.2.0 to 3.0.0 <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2744">#2744</a></li>
<li>chore(deps): bump github.com/aws/aws-sdk-go-v2/config from 1.18.37 to 1.18.38 <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2724">#2724</a></li>
<li>chore(deps): bump github.com/aws/aws-sdk-go-v2/feature/s3/manager from 1.11.81 to 1.11.83 <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2733">#2733</a></li>
<li>chore(deps): bump github.com/containerd/containerd from 1.7.3 to 1.7.5 <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2723">#2723</a></li>
<li>chore(deps): bump github.com/containerd/containerd from 1.7.5 to 1.7.6 <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2750">#2750</a></li>
<li>chore(deps): bump github.com/go-git/go-billy/v5 from 5.4.1 to 5.5.0 <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2746">#2746</a></li>
<li>chore(deps): bump github.com/go-git/go-git/v5 from 5.8.1 to 5.9.0 <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2749">#2749</a></li>
<li>chore(deps): bump github.com/google/slowjam from 1.0.1 to 1.1.0 <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2745">#2745</a></li>
<li>chore(deps): bump golang.org/x/net from 0.14.0 to 0.15.0 <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2734">#2734</a></li>
<li>chore(deps): bump golang.org/x/oauth2 from 0.11.0 to 0.12.0 <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2732">#2732</a></li>
<li>chore(deps): bump golang.org/x/sys from 0.11.0 to 0.12.0 <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2722">#2722</a></li>
<li>chore(deps): bump google.golang.org/api from 0.138.0 to 0.139.0 <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2741">#2741</a></li>
<li>chore(deps): bump google.golang.org/api from 0.138.0 to 0.140.0 <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2747">#2747</a></li>
<li>chore(deps): bump google.golang.org/api from 0.140.0 to 0.141.0 <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2748">#2748</a></li>
<li>chore(deps): bump sigstore/cosign-installer from 3.1.1 to 3.1.2 <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2727">#2727</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/GoogleContainerTools/kaniko/blob/main/CHANGELOG.md">github.com/GoogleContainerTools/kaniko's changelog</a>.</em></p>
<blockquote>
<h1>v1.16.0 Release 2023-09-22</h1>
<p>The executor images in this release are:</p>
<pre><code>gcr.io/kaniko-project/executor:v1.16.0
gcr.io/kaniko-project/executor:latest
</code></pre>
<p>The debug images are available at:</p>
<pre><code>gcr.io/kaniko-project/executor:debug
gcr.io/kaniko-project/executor:v1.16.0-debug
</code></pre>
<p>The slim executor images which don't contain any authentication binaries are available at:</p>
<pre><code>gcr.io/kaniko-project/executor:slim
gcr.io/kaniko-project/executor:v1.16.0-slim
</code></pre>
<ul>
<li>fix: make it so release.sh script doesn't output duplicate change PRs <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2735">#2735</a></li>
<li>chore: update function names to be correct and representative of functionality <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2720">#2720</a></li>
<li>chore(deps): bump github.com/go-git/go-git/v5 from 5.8.1 to 5.9.0 <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2749">#2749</a></li>
<li>chore(deps): bump google.golang.org/api from 0.140.0 to 0.141.0 <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2748">#2748</a></li>
<li>chore(deps): bump github.com/containerd/containerd from 1.7.5 to 1.7.6 <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2750">#2750</a></li>
<li>fix: ensure images layers correspond with the image media type <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2719">#2719</a></li>
<li>chore(deps): bump github.com/google/slowjam from 1.0.1 to 1.1.0 <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2745">#2745</a></li>
<li>chore(deps): bump docker/setup-buildx-action from 2.10.0 to 3.0.0 <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2743">#2743</a></li>
<li>chore(deps): bump github.com/go-git/go-billy/v5 from 5.4.1 to 5.5.0 <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2746">#2746</a></li>
<li>chore(deps): bump google.golang.org/api from 0.138.0 to 0.140.0 <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2747">#2747</a></li>
<li>chore(deps): bump docker/setup-qemu-action from 2.2.0 to 3.0.0 <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2744">#2744</a></li>
<li>chore(deps): bump docker/build-push-action from 4.2.1 to 5.0.0 <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2742">#2742</a></li>
<li>chore(deps): bump google.golang.org/api from 0.138.0 to 0.139.0 <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2741">#2741</a></li>
<li>chore(deps): bump cloud.google.com/go/storage from 1.32.0 to 1.33.0 <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2740">#2740</a></li>
<li>chore(deps): bump docker/build-push-action from 4.1.1 to 4.2.1 <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2739">#2739</a></li>
<li>chore(deps): bump golang.org/x/oauth2 from 0.11.0 to 0.12.0 <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2732">#2732</a></li>
<li>chore(deps): bump github.com/aws/aws-sdk-go-v2/feature/s3/manager from 1.11.81 to 1.11.83 <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2733">#2733</a></li>
<li>chore(deps): bump golang.org/x/net from 0.14.0 to 0.15.0 <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2734">#2734</a></li>
<li>chore(deps): bump github.com/containerd/containerd from 1.7.3 to 1.7.5 <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2723">#2723</a></li>
<li>chore(deps): bump sigstore/cosign-installer from 3.1.1 to 3.1.2 <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2727">#2727</a></li>
<li>chore(deps): bump docker/setup-buildx-action from 2.9.1 to 2.10.0 <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2726">#2726</a></li>
<li>chore(deps): bump github.com/aws/aws-sdk-go-v2/config from 1.18.37 to 1.18.38 <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2724">#2724</a></li>
<li>chore(deps): bump golang.org/x/sys from 0.11.0 to 0.12.0 <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2722">#2722</a></li>
<li>chore: unnecessary use of fmt.Sprintf <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2717">#2717</a></li>
<li>fix function name on comment <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2707">#2707</a></li>
<li>Avoid returning the UID when resolving the GIDs. <a href="https://redirect.github.com/GoogleContainerTools/kaniko/pull/2689">#2689</a></li>
</ul>
<p>Huge thank you for this release towards our contributors:</p>
<ul>
<li>Aaron Prindle</li>
<li>dependabot[bot]</li>
<li>Diego Gonzalez</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/GoogleContainerTools/kaniko/commit/a144a7872a90cc696da694d6502a8e3494adc025"><code>a144a78</code></a> chore(release): release v1.16.0 (<a href="https://redirect.github.com/GoogleContainerTools/kaniko/issues/2755">#2755</a>)</li>
<li><a href="https://github.com/GoogleContainerTools/kaniko/commit/8c09efff4c45d4a7edeb4960b03eb28da3c0a213"><code>8c09eff</code></a> fix: make it so release.sh script doesn't output duplicate change PRs (<a href="https://redirect.github.com/GoogleContainerTools/kaniko/issues/2735">#2735</a>)</li>
<li><a href="https://github.com/GoogleContainerTools/kaniko/commit/3d1ad4c64a91f59dc65b8a06896b3807297018c0"><code>3d1ad4c</code></a> chore: update function names to be correct and representative of functionalit...</li>
<li><a href="https://github.com/GoogleContainerTools/kaniko/commit/735499f49517ab4b1a965730beadd28bcc70707a"><code>735499f</code></a> chore(deps): bump github.com/go-git/go-git/v5 from 5.8.1 to 5.9.0 (<a href="https://redirect.github.com/GoogleContainerTools/kaniko/issues/2749">#2749</a>)</li>
<li><a href="https://github.com/GoogleContainerTools/kaniko/commit/ce093cb8ad2ad99955966a7ffb3a07040e86c187"><code>ce093cb</code></a> chore(deps): bump google.golang.org/api from 0.140.0 to 0.141.0 (<a href="https://redirect.github.com/GoogleContainerTools/kaniko/issues/2748">#2748</a>)</li>
<li><a href="https://github.com/GoogleContainerTools/kaniko/commit/a861fadeba3045a4f4ec11c6ec2f9b17cbd91ecc"><code>a861fad</code></a> chore(deps): bump github.com/containerd/containerd from 1.7.5 to 1.7.6 (<a href="https://redirect.github.com/GoogleContainerTools/kaniko/issues/2750">#2750</a>)</li>
<li><a href="https://github.com/GoogleContainerTools/kaniko/commit/14b2ea5528ce1da07af47a4207c2efad61357d5e"><code>14b2ea5</code></a> feat: ensure images layers correspond with the image media type (<a href="https://redirect.github.com/GoogleContainerTools/kaniko/issues/2719">#2719</a>)</li>
<li><a href="https://github.com/GoogleContainerTools/kaniko/commit/572904c1c4ee2d5b2032a789dbfabbd48ccca4c6"><code>572904c</code></a> chore(deps): bump github.com/google/slowjam from 1.0.1 to 1.1.0 (<a href="https://redirect.github.com/GoogleContainerTools/kaniko/issues/2745">#2745</a>)</li>
<li><a href="https://github.com/GoogleContainerTools/kaniko/commit/f43985e1516809025c4769b6a7ac335a757888f4"><code>f43985e</code></a> chore(deps): bump docker/setup-buildx-action from 2.10.0 to 3.0.0 (<a href="https://redirect.github.com/GoogleContainerTools/kaniko/issues/2743">#2743</a>)</li>
<li><a href="https://github.com/GoogleContainerTools/kaniko/commit/29dc5ba23f6fa0cb1a9f731e7729c475df6e1738"><code>29dc5ba</code></a> chore(deps): bump github.com/go-git/go-billy/v5 from 5.4.1 to 5.5.0 (<a href="https://redirect.github.com/GoogleContainerTools/kaniko/issues/2746">#2746</a>)</li>
<li>Additional commits viewable in <a href="https://github.com/GoogleContainerTools/kaniko/compare/v1.15.0...v1.16.0">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/GoogleContainerTools/kaniko&package-manager=go_modules&previous-version=1.15.0&new-version=1.16.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/buildpack/build.go b/buildpack/build.go
index f5e3258c..0e757e33 100644
--- a/buildpack/build.go
+++ b/buildpack/build.go
@@ -56,7 +56,11 @@ type BuildOutputs struct {
 	Slices      []layers.Slice
 }
 
-//go:generate mockgen -package testmock -destination ../testmock/build_executor.go github.com/buildpacks/lifecycle/buildpack BuildExecutor
+// BuildExecutor executes a single buildpack's `./bin/build` binary,
+// providing inputs as defined in the Buildpack Interface Specification,
+// and processing outputs for the platform.
+//
+//go:generate mockgen -package testmock -destination ../phase/testmock/build_executor.go github.com/buildpacks/lifecycle/buildpack BuildExecutor
 type BuildExecutor interface {
 	Build(d BpDescriptor, inputs BuildInputs, logger log.Logger) (BuildOutputs, error)
 }
diff --git a/buildpack/descriptor.go b/buildpack/descriptor.go
index fbba8bff..0ea29701 100644
--- a/buildpack/descriptor.go
+++ b/buildpack/descriptor.go
@@ -5,7 +5,10 @@ const (
 	KindExtension = "Extension"
 )
 
-//go:generate mockgen -package testmock -destination ../testmock/component_descriptor.go github.com/buildpacks/lifecycle/buildpack Descriptor
+// Descriptor exposes information contained in a buildpack.toml or extension.toml
+// that is generic to buildpacks and/or image extensions.
+//
+//go:generate mockgen -package testmock -destination ../lifecycle/testmock/component_descriptor.go github.com/buildpacks/lifecycle/buildpack Descriptor
 type Descriptor interface {
 	API() string
 	Homepage() string
diff --git a/buildpack/detect.go b/buildpack/detect.go
index f23c5ef6..80716d5d 100644
--- a/buildpack/detect.go
+++ b/buildpack/detect.go
@@ -39,7 +39,12 @@ type DetectOutputs struct {
 	Err    error  `toml:"-"`
 }
 
-//go:generate mockgen -package testmock -destination ../testmock/detect_executor.go github.com/buildpacks/lifecycle/buildpack DetectExecutor
+// DetectExecutor executes a single buildpack or image extension's `./bin/detect` binary,
+// providing inputs as defined in the Buildpack Interface Specification,
+// and processing outputs for the platform.
+// For image extensions (where `./bin/detect` is optional), pre-populated outputs are processed here.
+//
+//go:generate mockgen -package testmock -destination ../phase/testmock/detect_executor.go github.com/buildpacks/lifecycle/buildpack DetectExecutor
 type DetectExecutor interface {
 	Detect(d Descriptor, inputs DetectInputs, logger log.Logger) DetectOutputs
 }
diff --git a/buildpack/generate.go b/buildpack/generate.go
index acfeaf89..ba32596d 100644
--- a/buildpack/generate.go
+++ b/buildpack/generate.go
@@ -34,7 +34,12 @@ type GenerateOutputs struct {
 	MetRequires []string
 }
 
-//go:generate mockgen -package testmock -destination ../testmock/generate_executor.go github.com/buildpacks/lifecycle/buildpack GenerateExecutor
+// GenerateExecutor executes a single image extension's `./bin/generate` binary,
+// providing inputs as defined in the Buildpack Interface Specification,
+// and processing outputs for the platform.
+// Pre-populated outputs for image extensions that are missing `./bin/generate` are processed here.
+//
+//go:generate mockgen -package testmock -destination ../phase/testmock/generate_executor.go github.com/buildpacks/lifecycle/buildpack GenerateExecutor
 type GenerateExecutor interface {
 	Generate(d ExtDescriptor, inputs GenerateInputs, logger log.Logger) (GenerateOutputs, error)
 }
diff --git a/cache/image_comparer.go b/cache/image_comparer.go
index cbac626c..5faa2a33 100644
--- a/cache/image_comparer.go
+++ b/cache/image_comparer.go
@@ -5,7 +5,7 @@ import (
 	"github.com/pkg/errors"
 )
 
-//go:generate mockgen -package testmockcache -destination ../testmock/cache/image_comparer.go github.com/buildpacks/lifecycle/cache ImageComparer
+//go:generate mockgen -package testmockcache -destination ../lifecycle/testmock/cache/image_comparer.go github.com/buildpacks/lifecycle/cache ImageComparer
 
 // ImageComparer provides a way to compare images
 type ImageComparer interface {
diff --git a/cache/image_deleter.go b/cache/image_deleter.go
index afde27f8..8807715d 100644
--- a/cache/image_deleter.go
+++ b/cache/image_deleter.go
@@ -7,7 +7,7 @@ import (
 	"github.com/buildpacks/lifecycle/log"
 )
 
-//go:generate mockgen -package testmockcache -destination ../testmock/cache/image_deleter.go github.com/buildpacks/lifecycle/cache ImageDeleter
+//go:generate mockgen -package testmockcache -destination ../phase/testmock/cache/image_deleter.go github.com/buildpacks/lifecycle/cache ImageDeleter
 
 // ImageDeleter defines the methods available to delete and compare cached images
 type ImageDeleter interface {
diff --git a/cmd/apis.go b/cmd/apis.go
index f4a99f9f..1a583861 100644
--- a/cmd/apis.go
+++ b/cmd/apis.go
@@ -24,37 +24,45 @@ var DeprecationMode = EnvOrDefault(EnvDeprecationMode, DefaultDeprecationMode)
 
 type BuildpackAPIVerifier struct{}
 
-func (v *BuildpackAPIVerifier) VerifyBuildpackAPI(kind, name, requested string, logger log.Logger) error {
-	return VerifyBuildpackAPI(kind, name, requested, logger)
+// VerifyBuildpackAPI given a Buildpack API version and relevant information for logging
+// will error if the requested version is unsupported,
+// and will log a warning, error, or do nothing if the requested version is deprecated,
+// depending on if the configured deprecation mode is "warn", "error", or "silent", respectively.
+func (v *BuildpackAPIVerifier) VerifyBuildpackAPI(kind, name, requestedVersion string, logger log.Logger) error {
+	return VerifyBuildpackAPI(kind, name, requestedVersion, logger)
 }
 
-func VerifyBuildpackAPI(kind, name, requested string, logger log.Logger) error {
-	requestedAPI, err := api.NewVersion(requested)
+// VerifyBuildpackAPI given a Buildpack API version and relevant information for logging
+// will error if the requested version is unsupported,
+// and will log a warning, error, or do nothing if the requested version is deprecated,
+// depending on if the configured deprecation mode is "warn", "error", or "silent", respectively.
+func VerifyBuildpackAPI(kind, name, requestedVersion string, logger log.Logger) error {
+	requested, err := api.NewVersion(requestedVersion)
 	if err != nil {
 		return FailErrCode(
 			nil,
 			CodeForIncompatibleBuildpackAPI,
-			fmt.Sprintf("parse buildpack API '%s' for %s '%s'", requestedAPI, strings.ToLower(kind), name),
+			fmt.Sprintf("parse buildpack API '%s' for %s '%s'", requested, strings.ToLower(kind), name),
 		)
 	}
-	if api.Buildpack.IsSupported(requestedAPI) {
-		if api.Buildpack.IsDeprecated(requestedAPI) {
+	if api.Buildpack.IsSupported(requested) {
+		if api.Buildpack.IsDeprecated(requested) {
 			switch DeprecationMode {
 			case ModeQuiet:
 				break
 			case ModeError:
-				logger.Errorf("%s '%s' requests deprecated API '%s'", kind, name, requested)
+				logger.Errorf("%s '%s' requests deprecated API '%s'", kind, name, requestedVersion)
 				logger.Errorf("Deprecated APIs are disabled by %s=%s", EnvDeprecationMode, ModeError)
-				return buildpackAPIError(kind, name, requested)
+				return buildpackAPIError(kind, name, requestedVersion)
 			case ModeWarn:
-				logger.Warnf("%s '%s' requests deprecated API '%s'", kind, name, requested)
+				logger.Warnf("%s '%s' requests deprecated API '%s'", kind, name, requestedVersion)
 			default:
-				logger.Warnf("%s '%s' requests deprecated API '%s'", kind, name, requested)
+				logger.Warnf("%s '%s' requests deprecated API '%s'", kind, name, requestedVersion)
 			}
 		}
 		return nil
 	}
-	return buildpackAPIError(kind, name, requested)
+	return buildpackAPIError(kind, name, requestedVersion)
 }
 
 func buildpackAPIError(moduleKind string, name string, requested string) error {
@@ -65,40 +73,44 @@ func buildpackAPIError(moduleKind string, name string, requested string) error {
 	)
 }
 
-func VerifyPlatformAPI(requested string, logger log.Logger) error {
-	if strings.TrimSpace(requested) == "" {
+// VerifyPlatformAPI given a Platform API version and relevant information for logging
+// will error if the requested version is unsupported,
+// and will log a warning, error, or do nothing if the requested version is deprecated,
+// depending on if the configured deprecation mode is "warn", "error", or "silent", respectively.
+func VerifyPlatformAPI(requestedVersion string, logger log.Logger) error {
+	if strings.TrimSpace(requestedVersion) == "" {
 		return FailErrCode(
 			nil,
 			CodeForIncompatiblePlatformAPI,
 			fmt.Sprintf("get platform API version; please set '%s' to specify the desired platform API version", EnvPlatformAPI),
 		)
 	}
-	requestedAPI, err := api.NewVersion(requested)
+	requested, err := api.NewVersion(requestedVersion)
 	if err != nil {
 		return FailErrCode(
 			nil,
 			CodeForIncompatiblePlatformAPI,
-			fmt.Sprintf("parse platform API '%s'", requested),
+			fmt.Sprintf("parse platform API '%s'", requestedVersion),
 		)
 	}
-	if api.Platform.IsSupported(requestedAPI) {
-		if api.Platform.IsDeprecated(requestedAPI) {
+	if api.Platform.IsSupported(requested) {
+		if api.Platform.IsDeprecated(requested) {
 			switch DeprecationMode {
 			case ModeQuiet:
 				break
 			case ModeError:
-				logger.Errorf("Platform requested deprecated API '%s'", requested)
+				logger.Errorf("Platform requested deprecated API '%s'", requestedVersion)
 				logger.Errorf("Deprecated APIs are disabled by %s=%s", EnvDeprecationMode, ModeError)
-				return platformAPIError(requested)
+				return platformAPIError(requestedVersion)
 			case ModeWarn:
-				logger.Warnf("Platform requested deprecated API '%s'", requested)
+				logger.Warnf("Platform requested deprecated API '%s'", requestedVersion)
 			default:
-				logger.Warnf("Platform requested deprecated API '%s'", requested)
+				logger.Warnf("Platform requested deprecated API '%s'", requestedVersion)
 			}
 		}
 		return nil
 	}
-	return platformAPIError(requested)
+	return platformAPIError(requestedVersion)
 }
 
 func platformAPIError(requested string) error {
diff --git a/cmd/lifecycle/analyzer.go b/cmd/lifecycle/analyzer.go
index 070a1d16..0bbdbec2 100644
--- a/cmd/lifecycle/analyzer.go
+++ b/cmd/lifecycle/analyzer.go
@@ -3,16 +3,14 @@ package main
 import (
 	"fmt"
 
-	"github.com/buildpacks/lifecycle/image"
-	"github.com/buildpacks/lifecycle/internal/encoding"
-
 	"github.com/docker/docker/client"
 	"github.com/google/go-containerregistry/pkg/authn"
 
-	"github.com/buildpacks/lifecycle"
 	"github.com/buildpacks/lifecycle/auth"
 	"github.com/buildpacks/lifecycle/cmd"
 	"github.com/buildpacks/lifecycle/cmd/lifecycle/cli"
+	"github.com/buildpacks/lifecycle/image"
+	"github.com/buildpacks/lifecycle/phase"
 	"github.com/buildpacks/lifecycle/platform"
 	"github.com/buildpacks/lifecycle/priv"
 )
@@ -97,15 +95,15 @@ func (a *analyzeCmd) Privileges() error {
 
 // Exec executes the command.
 func (a *analyzeCmd) Exec() error {
-	factory := lifecycle.NewAnalyzerFactory(
+	factory := phase.NewConnectedFactory(
 		a.PlatformAPI,
 		&cmd.BuildpackAPIVerifier{},
 		NewCacheHandler(a.keychain),
-		lifecycle.NewConfigHandler(),
+		phase.Config,
 		image.NewHandler(a.docker, a.keychain, a.LayoutDir, a.UseLayout, a.InsecureRegistries),
 		image.NewRegistryHandler(a.keychain, a.InsecureRegistries),
 	)
-	analyzer, err := factory.NewAnalyzer(a.AdditionalTags, a.CacheImageRef, a.LaunchCacheDir, a.LayersDir, a.OutputImageRef, a.PreviousImageRef, a.RunImageRef, a.SkipLayers, cmd.DefaultLogger)
+	analyzer, err := factory.NewAnalyzer(a.Inputs(), cmd.DefaultLogger)
 	if err != nil {
 		return unwrapErrorFailWithMessage(err, "initialize analyzer")
 	}
@@ -113,10 +111,5 @@ func (a *analyzeCmd) Exec() error {
 	if err != nil {
 		return cmd.FailErrCode(err, a.CodeFor(platform.AnalyzeError), "analyze")
 	}
-	cmd.DefaultLogger.Debugf("Run image info in analyzed metadata is: ")
-	cmd.DefaultLogger.Debugf(encoding.ToJSONMaybe(analyzedMD.RunImage))
-	if err = encoding.WriteTOML(a.AnalyzedPath, analyzedMD); err != nil {
-		return cmd.FailErr(err, "write analyzed")
-	}
-	return nil
+	return phase.Config.WriteAnalyzed(a.AnalyzedPath, &analyzedMD, cmd.DefaultLogger)
 }
diff --git a/cmd/lifecycle/builder.go b/cmd/lifecycle/builder.go
index 06d56fc5..50b78eaa 100644
--- a/cmd/lifecycle/builder.go
+++ b/cmd/lifecycle/builder.go
@@ -5,12 +5,12 @@ import (
 
 	"github.com/BurntSushi/toml"
 
-	"github.com/buildpacks/lifecycle"
 	"github.com/buildpacks/lifecycle/buildpack"
 	"github.com/buildpacks/lifecycle/cmd"
 	"github.com/buildpacks/lifecycle/cmd/lifecycle/cli"
 	"github.com/buildpacks/lifecycle/internal/encoding"
 	"github.com/buildpacks/lifecycle/launch"
+	"github.com/buildpacks/lifecycle/phase"
 	"github.com/buildpacks/lifecycle/platform"
 	"github.com/buildpacks/lifecycle/platform/files"
 	"github.com/buildpacks/lifecycle/priv"
@@ -74,7 +74,7 @@ func (b *buildCmd) Exec() error {
 }
 
 func (b *buildCmd) build(group buildpack.Group, plan files.Plan, analyzedMD files.Analyzed) error {
-	builder := &lifecycle.Builder{
+	builder := &phase.Builder{
 		AppDir:         b.AppDir,
 		BuildConfigDir: b.BuildConfigDir,
 		LayersDir:      b.LayersDir,
@@ -109,7 +109,7 @@ func (b *buildCmd) unwrapBuildFail(err error) error {
 }
 
 func (b *buildCmd) readData() (buildpack.Group, files.Plan, error) {
-	group, err := lifecycle.ReadGroup(b.GroupPath)
+	group, err := phase.ReadGroup(b.GroupPath)
 	if err != nil {
 		return buildpack.Group{}, files.Plan{}, cmd.FailErr(err, "read buildpack group")
 	}
diff --git a/cmd/lifecycle/creator.go b/cmd/lifecycle/creator.go
index 531da1d3..c7e16a31 100644
--- a/cmd/lifecycle/creator.go
+++ b/cmd/lifecycle/creator.go
@@ -5,19 +5,18 @@ import (
 	"fmt"
 	"time"
 
-	"github.com/buildpacks/lifecycle/image"
-	"github.com/buildpacks/lifecycle/platform/files"
-
 	"github.com/docker/docker/client"
 	"github.com/google/go-containerregistry/pkg/authn"
 	"github.com/pkg/errors"
 
-	"github.com/buildpacks/lifecycle"
 	"github.com/buildpacks/lifecycle/auth"
 	"github.com/buildpacks/lifecycle/buildpack"
 	"github.com/buildpacks/lifecycle/cmd"
 	"github.com/buildpacks/lifecycle/cmd/lifecycle/cli"
+	"github.com/buildpacks/lifecycle/image"
+	"github.com/buildpacks/lifecycle/phase"
 	"github.com/buildpacks/lifecycle/platform"
+	"github.com/buildpacks/lifecycle/platform/files"
 	"github.com/buildpacks/lifecycle/priv"
 )
 
@@ -124,15 +123,15 @@ func (c *createCmd) Exec() error {
 		plan       files.Plan
 	)
 	cmd.DefaultLogger.Phase("ANALYZING")
-	analyzerFactory := lifecycle.NewAnalyzerFactory(
+	analyzerFactory := phase.NewConnectedFactory(
 		c.PlatformAPI,
 		&cmd.BuildpackAPIVerifier{},
 		NewCacheHandler(c.keychain),
-		lifecycle.NewConfigHandler(),
+		phase.NewConfigHandler(),
 		image.NewHandler(c.docker, c.keychain, c.LayoutDir, c.UseLayout, c.InsecureRegistries),
 		image.NewRegistryHandler(c.keychain, c.InsecureRegistries),
 	)
-	analyzer, err := analyzerFactory.NewAnalyzer(c.AdditionalTags, c.CacheImageRef, c.LaunchCacheDir, c.LayersDir, c.OutputImageRef, c.PreviousImageRef, c.RunImageRef, c.SkipLayers, cmd.DefaultLogger)
+	analyzer, err := analyzerFactory.NewAnalyzer(c.Inputs(), cmd.DefaultLogger)
 	if err != nil {
 		return unwrapErrorFailWithMessage(err, "initialize analyzer")
 	}
@@ -140,16 +139,19 @@ func (c *createCmd) Exec() error {
 	if err != nil {
 		return err
 	}
+	if err := phase.Config.WriteAnalyzed(c.AnalyzedPath, &analyzedMD, cmd.DefaultLogger); err != nil {
+		return err
+	}
 
 	// Detect
 	cmd.DefaultLogger.Phase("DETECTING")
-	detectorFactory := lifecycle.NewDetectorFactory(
+	detectorFactory := phase.NewHermeticFactory(
 		c.PlatformAPI,
 		&cmd.BuildpackAPIVerifier{},
-		lifecycle.NewConfigHandler(),
+		phase.NewConfigHandler(),
 		dirStore,
 	)
-	detector, err := detectorFactory.NewDetector(analyzedMD, c.AppDir, c.BuildConfigDir, c.OrderPath, c.PlatformDir, cmd.DefaultLogger)
+	detector, err := detectorFactory.NewDetector(c.Inputs(), cmd.DefaultLogger)
 	if err != nil {
 		return unwrapErrorFailWithMessage(err, "initialize detector")
 	}
@@ -165,8 +167,7 @@ func (c *createCmd) Exec() error {
 			Platform: c.Platform,
 			keychain: c.keychain,
 		}
-		err := restoreCmd.restore(analyzedMD.LayersMetadata, group, cacheStore)
-		if err != nil {
+		if err := restoreCmd.restore(analyzedMD.LayersMetadata, group, cacheStore); err != nil {
 			return err
 		}
 	}
diff --git a/cmd/lifecycle/detector.go b/cmd/lifecycle/detector.go
index 2fdff652..de6c2dde 100644
--- a/cmd/lifecycle/detector.go
+++ b/cmd/lifecycle/detector.go
@@ -3,11 +3,10 @@ package main
 import (
 	"errors"
 
-	"github.com/buildpacks/lifecycle"
 	"github.com/buildpacks/lifecycle/buildpack"
 	"github.com/buildpacks/lifecycle/cmd"
 	"github.com/buildpacks/lifecycle/cmd/lifecycle/cli"
-	"github.com/buildpacks/lifecycle/internal/encoding"
+	"github.com/buildpacks/lifecycle/phase"
 	"github.com/buildpacks/lifecycle/platform"
 	"github.com/buildpacks/lifecycle/platform/files"
 	"github.com/buildpacks/lifecycle/priv"
@@ -60,22 +59,14 @@ func (d *detectCmd) Privileges() error {
 
 func (d *detectCmd) Exec() error {
 	dirStore := platform.NewDirStore(d.BuildpacksDir, d.ExtensionsDir)
-	detectorFactory := lifecycle.NewDetectorFactory(
+	detectorFactory := phase.NewHermeticFactory(
 		d.PlatformAPI,
 		&cmd.BuildpackAPIVerifier{},
-		lifecycle.NewConfigHandler(),
+		phase.NewConfigHandler(),
 		dirStore,
 	)
-	amd, err := files.ReadAnalyzed(d.AnalyzedPath, cmd.DefaultLogger)
-	if err != nil {
-		return unwrapErrorFailWithMessage(err, "reading analyzed.toml")
-	}
 	detector, err := detectorFactory.NewDetector(
-		amd,
-		d.AppDir,
-		d.BuildConfigDir,
-		d.OrderPath,
-		d.PlatformDir,
+		d.Inputs(),
 		cmd.DefaultLogger,
 	)
 	if err != nil {
@@ -86,48 +77,39 @@ func (d *detectCmd) Exec() error {
 			return err
 		}
 	}
-	group, plan, err := doDetect(detector, d.Platform)
+	group, _, err := doDetect(detector, d.Platform)
 	if err != nil {
 		return err // pass through error
 	}
 	if group.HasExtensions() {
-		generatorFactory := lifecycle.NewGeneratorFactory(
+		generatorFactory := phase.NewHermeticFactory(
+			d.PlatformAPI,
 			&cmd.BuildpackAPIVerifier{},
-			lifecycle.Config,
+			phase.Config,
 			dirStore,
 		)
-		var generator *lifecycle.Generator
+		var generator *phase.Generator
 		generator, err = generatorFactory.NewGenerator(
-			d.AnalyzedPath,
-			d.AppDir,
-			d.BuildConfigDir,
-			group.GroupExtensions,
-			d.GeneratedDir,
-			plan,
-			d.PlatformAPI,
-			d.PlatformDir,
-			d.RunPath,
+			d.Inputs(),
 			cmd.Stdout, cmd.Stderr,
 			cmd.DefaultLogger,
 		)
 		if err != nil {
 			return unwrapErrorFailWithMessage(err, "initialize generator")
 		}
-		var result lifecycle.GenerateResult
+		var result phase.GenerateResult
 		result, err = generator.Generate()
 		if err != nil {
 			return d.unwrapGenerateFail(err)
 		}
-
-		if err = d.writeGenerateData(result.AnalyzedMD); err != nil {
+		if err := phase.Config.WriteAnalyzed(d.AnalyzedPath, &result.AnalyzedMD, cmd.DefaultLogger); err != nil {
 			return err
 		}
-		// was the build plan updated?
-		if result.UsePlan {
-			plan = result.Plan
+		if err := phase.Config.WritePlan(d.PlanPath, &result.Plan); err != nil {
+			return err
 		}
 	}
-	return d.writeDetectData(group, plan)
+	return nil
 }
 
 func unwrapErrorFailWithMessage(err error, msg string) error {
@@ -147,7 +129,7 @@ func (d *detectCmd) unwrapGenerateFail(err error) error {
 	return cmd.FailErrCode(err, d.CodeFor(platform.GenerateError), "build")
 }
 
-func doDetect(detector *lifecycle.Detector, p *platform.Platform) (buildpack.Group, files.Plan, error) {
+func doDetect(detector *phase.Detector, p *platform.Platform) (buildpack.Group, files.Plan, error) {
 	group, plan, err := detector.Detect()
 	if err != nil {
 		switch err := err.(type) {
@@ -167,25 +149,11 @@ func doDetect(detector *lifecycle.Detector, p *platform.Platform) (buildpack.Gro
 			return buildpack.Group{}, files.Plan{}, cmd.FailErrCode(err, p.CodeFor(platform.DetectError), "detect")
 		}
 	}
-	return group, plan, nil
-}
-
-func (d *detectCmd) writeDetectData(group buildpack.Group, plan files.Plan) error {
-	if err := encoding.WriteTOML(d.GroupPath, group); err != nil {
-		return cmd.FailErr(err, "write buildpack group")
-	}
-	if err := encoding.WriteTOML(d.PlanPath, plan); err != nil {
-		return cmd.FailErr(err, "write detect plan")
+	if err := phase.Config.WriteGroup(p.GroupPath, &group); err != nil {
+		return buildpack.Group{}, files.Plan{}, err
 	}
-	return nil
-}
-
-// writeGenerateData re-outputs the analyzedMD that we read previously, but now we've added the RunImage, if a custom runImage was configured
-func (d *detectCmd) writeGenerateData(analyzedMD files.Analyzed) error {
-	cmd.DefaultLogger.Debugf("Run image info in analyzed metadata is: ")
-	cmd.DefaultLogger.Debugf(encoding.ToJSONMaybe(analyzedMD.RunImage))
-	if err := encoding.WriteTOML(d.AnalyzedPath, analyzedMD); err != nil {
-		return cmd.FailErr(err, "write analyzed metadata")
+	if err := phase.Config.WritePlan(p.PlanPath, &plan); err != nil {
+		return buildpack.Group{}, files.Plan{}, err
 	}
-	return nil
+	return group, plan, nil
 }
diff --git a/cmd/lifecycle/exporter.go b/cmd/lifecycle/exporter.go
index bc9006e9..dd1b73aa 100644
--- a/cmd/lifecycle/exporter.go
+++ b/cmd/lifecycle/exporter.go
@@ -20,7 +20,6 @@ import (
 	v1 "github.com/google/go-containerregistry/pkg/v1"
 	"github.com/pkg/errors"
 
-	"github.com/buildpacks/lifecycle"
 	"github.com/buildpacks/lifecycle/auth"
 	"github.com/buildpacks/lifecycle/buildpack"
 	"github.com/buildpacks/lifecycle/cache"
@@ -29,6 +28,7 @@ import (
 	"github.com/buildpacks/lifecycle/image"
 	"github.com/buildpacks/lifecycle/internal/encoding"
 	"github.com/buildpacks/lifecycle/layers"
+	"github.com/buildpacks/lifecycle/phase"
 	"github.com/buildpacks/lifecycle/platform"
 	"github.com/buildpacks/lifecycle/platform/files"
 	"github.com/buildpacks/lifecycle/priv"
@@ -131,7 +131,7 @@ func (e *exportCmd) Privileges() error {
 }
 
 func (e *exportCmd) Exec() error {
-	group, err := lifecycle.ReadGroup(e.GroupPath)
+	group, err := phase.ReadGroup(e.GroupPath)
 	if err != nil {
 		return cmd.FailErr(err, "read buildpack group")
 	}
@@ -160,7 +160,7 @@ func (e *exportCmd) registryImages() []string {
 	return registryImages
 }
 
-func (e *exportCmd) export(group buildpack.Group, cacheStore lifecycle.Cache, analyzedMD files.Analyzed) error {
+func (e *exportCmd) export(group buildpack.Group, cacheStore phase.Cache, analyzedMD files.Analyzed) error {
 	artifactsDir, err := os.MkdirTemp("", "lifecycle.exporter.layer")
 
 	if err != nil {
@@ -177,7 +177,7 @@ func (e *exportCmd) export(group buildpack.Group, cacheStore lifecycle.Cache, an
 		cmd.DefaultLogger.Debugf("no project metadata found at path '%s', project metadata will not be exported\n", e.ProjectMetadataPath)
 	}
 
-	exporter := &lifecycle.Exporter{
+	exporter := &phase.Exporter{
 		Buildpacks: group.Group,
 		LayerFactory: &layers.Factory{
 			ArtifactsDir: artifactsDir,
@@ -205,12 +205,12 @@ func (e *exportCmd) export(group buildpack.Group, cacheStore lifecycle.Cache, an
 		return err
 	}
 
-	runImageForExport, err := platform.GetRunImageForExport(*e.LifecycleInputs)
+	runImageForExport, err := platform.GetRunImageForExport(e.Inputs())
 	if err != nil {
 		return err
 	}
 
-	report, err := exporter.Export(lifecycle.ExportOptions{
+	report, err := exporter.Export(phase.ExportOptions{
 		AdditionalNames:    e.AdditionalTags,
 		AppDir:             e.AppDir,
 		DefaultProcessType: e.DefaultProcessType,
@@ -451,8 +451,8 @@ func (e *exportCmd) initLayoutAppImage(analyzedMD files.Analyzed) (imgutil.Image
 	return appImage, runImageID.String(), nil
 }
 
-func launcherConfig(launcherPath, launcherSBOMDir string) lifecycle.LauncherConfig {
-	return lifecycle.LauncherConfig{
+func launcherConfig(launcherPath, launcherSBOMDir string) phase.LauncherConfig {
+	return phase.LauncherConfig{
 		Path:    launcherPath,
 		SBOMDir: launcherSBOMDir,
 		Metadata: files.LauncherMetadata{
diff --git a/cmd/lifecycle/extender.go b/cmd/lifecycle/extender.go
index fc8ba47b..595ae1f8 100644
--- a/cmd/lifecycle/extender.go
+++ b/cmd/lifecycle/extender.go
@@ -4,11 +4,11 @@ import (
 	"errors"
 	"fmt"
 
-	"github.com/buildpacks/lifecycle"
 	"github.com/buildpacks/lifecycle/buildpack"
 	"github.com/buildpacks/lifecycle/cmd"
 	"github.com/buildpacks/lifecycle/cmd/lifecycle/cli"
 	"github.com/buildpacks/lifecycle/internal/extend/kaniko"
+	"github.com/buildpacks/lifecycle/phase"
 	"github.com/buildpacks/lifecycle/platform"
 	"github.com/buildpacks/lifecycle/priv"
 )
@@ -52,22 +52,14 @@ func (e *extendCmd) Privileges() error {
 }
 
 func (e *extendCmd) Exec() error {
-	extenderFactory := lifecycle.NewExtenderFactory(&cmd.BuildpackAPIVerifier{}, lifecycle.NewConfigHandler())
+	extenderFactory := phase.NewHermeticFactory(e.PlatformAPI, &cmd.BuildpackAPIVerifier{}, phase.NewConfigHandler(), platform.NewDirStore("", ""))
 	applier, err := kaniko.NewDockerfileApplier()
 	if err != nil {
 		return err
 	}
 	extender, err := extenderFactory.NewExtender(
-		e.AnalyzedPath,
-		e.AppDir,
-		e.ExtendedDir,
-		e.GeneratedDir,
-		e.GroupPath,
-		e.LayersDir,
-		e.PlatformDir,
-		e.KanikoCacheTTL,
+		e.Inputs(),
 		applier,
-		e.ExtendKind,
 		cmd.DefaultLogger,
 	)
 	if err != nil {
diff --git a/cmd/lifecycle/main.go b/cmd/lifecycle/main.go
index fdcfdc33..74b51a01 100644
--- a/cmd/lifecycle/main.go
+++ b/cmd/lifecycle/main.go
@@ -8,11 +8,11 @@ import (
 	"github.com/google/go-containerregistry/pkg/authn"
 	"github.com/pkg/errors"
 
-	"github.com/buildpacks/lifecycle"
 	"github.com/buildpacks/lifecycle/buildpack"
 	"github.com/buildpacks/lifecycle/cache"
 	"github.com/buildpacks/lifecycle/cmd"
 	"github.com/buildpacks/lifecycle/cmd/lifecycle/cli"
+	"github.com/buildpacks/lifecycle/phase"
 	"github.com/buildpacks/lifecycle/platform"
 )
 
@@ -91,9 +91,9 @@ func NewCacheHandler(keychain authn.Keychain) *DefaultCacheHandler {
 }
 
 // InitCache is a factory used to create either a NewImageCache or a NewVolumeCache
-func (ch *DefaultCacheHandler) InitCache(cacheImageRef string, cacheDir string, deletionEnabled bool) (lifecycle.Cache, error) {
+func (ch *DefaultCacheHandler) InitCache(cacheImageRef string, cacheDir string, deletionEnabled bool) (phase.Cache, error) {
 	var (
-		cacheStore lifecycle.Cache
+		cacheStore phase.Cache
 		err        error
 	)
 	if cacheImageRef != "" {
@@ -113,9 +113,9 @@ func (ch *DefaultCacheHandler) InitCache(cacheImageRef string, cacheDir string,
 
 // helpers
 
-func initCache(cacheImageTag, cacheDir string, keychain authn.Keychain, deletionEnabled bool) (lifecycle.Cache, error) {
+func initCache(cacheImageTag, cacheDir string, keychain authn.Keychain, deletionEnabled bool) (phase.Cache, error) {
 	var (
-		cacheStore lifecycle.Cache
+		cacheStore phase.Cache
 		err        error
 	)
 	if cacheImageTag != "" {
diff --git a/cmd/lifecycle/rebaser.go b/cmd/lifecycle/rebaser.go
index 448beb92..c687be4e 100644
--- a/cmd/lifecycle/rebaser.go
+++ b/cmd/lifecycle/rebaser.go
@@ -11,12 +11,12 @@ import (
 	"github.com/google/go-containerregistry/pkg/name"
 	"github.com/pkg/errors"
 
-	"github.com/buildpacks/lifecycle"
 	"github.com/buildpacks/lifecycle/auth"
 	"github.com/buildpacks/lifecycle/cmd"
 	"github.com/buildpacks/lifecycle/cmd/lifecycle/cli"
 	"github.com/buildpacks/lifecycle/image"
 	"github.com/buildpacks/lifecycle/internal/encoding"
+	"github.com/buildpacks/lifecycle/phase"
 	"github.com/buildpacks/lifecycle/platform"
 	"github.com/buildpacks/lifecycle/platform/files"
 	"github.com/buildpacks/lifecycle/priv"
@@ -128,7 +128,7 @@ func (r *rebaseCmd) Exec() error {
 		return cmd.FailErr(err, "access run image")
 	}
 
-	rebaser := &lifecycle.Rebaser{
+	rebaser := &phase.Rebaser{
 		Logger:      cmd.DefaultLogger,
 		PlatformAPI: r.PlatformAPI,
 		Force:       r.ForceRebase,
diff --git a/cmd/lifecycle/restorer.go b/cmd/lifecycle/restorer.go
index 536d61cd..82dc4d48 100644
--- a/cmd/lifecycle/restorer.go
+++ b/cmd/lifecycle/restorer.go
@@ -13,7 +13,6 @@ import (
 	"github.com/docker/docker/client"
 	"github.com/google/go-containerregistry/pkg/authn"
 
-	"github.com/buildpacks/lifecycle"
 	"github.com/buildpacks/lifecycle/auth"
 	"github.com/buildpacks/lifecycle/buildpack"
 	"github.com/buildpacks/lifecycle/cmd"
@@ -21,6 +20,7 @@ import (
 	"github.com/buildpacks/lifecycle/image"
 	"github.com/buildpacks/lifecycle/internal/encoding"
 	"github.com/buildpacks/lifecycle/internal/layer"
+	"github.com/buildpacks/lifecycle/phase"
 	"github.com/buildpacks/lifecycle/platform"
 	"github.com/buildpacks/lifecycle/platform/files"
 	"github.com/buildpacks/lifecycle/priv"
@@ -149,7 +149,7 @@ func (r *restoreCmd) Exec() error {
 		cmd.DefaultLogger.Warnf("Not using analyzed data, usable file not found: %s", err)
 	}
 
-	group, err := lifecycle.ReadGroup(r.GroupPath)
+	group, err := phase.ReadGroup(r.GroupPath)
 	if err != nil {
 		return cmd.FailErr(err, "read buildpack group")
 	}
@@ -263,8 +263,8 @@ func (r *restoreCmd) pullSparse(imageRef string) (imgutil.Image, error) {
 	return remoteImage, nil
 }
 
-func (r *restoreCmd) restore(layerMetadata files.LayersMetadata, group buildpack.Group, cacheStore lifecycle.Cache) error {
-	restorer := &lifecycle.Restorer{
+func (r *restoreCmd) restore(layerMetadata files.LayersMetadata, group buildpack.Group, cacheStore phase.Cache) error {
+	restorer := &phase.Restorer{
 		LayersDir:             r.LayersDir,
 		Buildpacks:            group.Group,
 		Logger:                cmd.DefaultLogger,
diff --git a/go.mod b/go.mod
index 29ccc754..34432bb8 100644
--- a/go.mod
+++ b/go.mod
@@ -2,7 +2,7 @@ module github.com/buildpacks/lifecycle
 
 require (
 	github.com/BurntSushi/toml v1.3.2
-	github.com/GoogleContainerTools/kaniko v1.15.0
+	github.com/GoogleContainerTools/kaniko v1.16.0
 	github.com/apex/log v1.9.0
 	github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20230906235100-ae4cac8b496c
 	github.com/buildpacks/imgutil v0.0.0-20230919143643-4ec9360d5f02
diff --git a/go.sum b/go.sum
index 1389c198..3576df4f 100644
--- a/go.sum
+++ b/go.sum
@@ -71,8 +71,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
 github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
 github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
-github.com/GoogleContainerTools/kaniko v1.15.0 h1:W3fbiRJ1S4dNXZo7IXT6Q47Fde+4shnJ+mTCZpTQNBo=
-github.com/GoogleContainerTools/kaniko v1.15.0/go.mod h1:zsbhrd4mL8qdZsO1RRWpNEwBEC0GGSVe2OehQDBK7d0=
+github.com/GoogleContainerTools/kaniko v1.16.0 h1:2Z1kPdbWkgRTfiGv3OE1Tn6BtFwfmh/eI3NMrdpZ58s=
+github.com/GoogleContainerTools/kaniko v1.16.0/go.mod h1:IUtehN2dxFOvcQ1eXpsTOhqd2LE5SrIQOKQt3wJs+CE=
 github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
 github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
 github.com/Microsoft/hcsshim v0.11.0 h1:7EFNIY4igHEXUdj1zXgAyU3fLc7QfOKHbkldRVTBdiM=
diff --git a/image/handler.go b/image/handler.go
index f579f5e7..2705102e 100644
--- a/image/handler.go
+++ b/image/handler.go
@@ -7,6 +7,11 @@ import (
 	"github.com/google/go-containerregistry/pkg/authn"
 )
 
+// Handler wraps initialization of an [imgutil] image.
+//
+// [imgutil]: github.com/buildpacks/imgutil
+//
+//go:generate mockgen -package testmock -destination ../phase/testmock/image_handler.go github.com/buildpacks/lifecycle/image Handler
 type Handler interface {
 	InitImage(imageRef string) (imgutil.Image, error)
 	Kind() string
diff --git a/image/registry_handler.go b/image/registry_handler.go
index 83ef8578..076cfcba 100644
--- a/image/registry_handler.go
+++ b/image/registry_handler.go
@@ -10,7 +10,7 @@ import (
 
 // RegistryHandler takes care of the registry settings and checks
 //
-//go:generate mockgen -package testmock -destination testmock/registry_handler.go github.com/buildpacks/lifecycle RegistryHandler
+//go:generate mockgen -package testmock -destination ../phase/testmock/registry_handler.go github.com/buildpacks/lifecycle/image RegistryHandler
 type RegistryHandler interface {
 	EnsureReadAccess(imageRefs ...string) error
 	EnsureWriteAccess(imageRefs ...string) error
diff --git a/internal/layer/metadata_restorer.go b/internal/layer/metadata_restorer.go
index 831ff1e8..f33e0e17 100644
--- a/internal/layer/metadata_restorer.go
+++ b/internal/layer/metadata_restorer.go
@@ -14,7 +14,12 @@ import (
 	"github.com/buildpacks/lifecycle/platform/files"
 )
 
-//go:generate mockgen -package testmock -destination ../../testmock/metadata_restorer.go github.com/buildpacks/lifecycle/internal/layer MetadataRestorer
+// MetadataRestorer given a group of buildpacks and metadata from the previous image and cache,
+// will create `<layers>/<buildpack-id>/<layer>.toml` files containing `metadata` that the buildpack previously wrote.
+// Note that layer `types` information is not persisted, as the buildpack must opt in to layer re-use
+// by editing the provided `<layer>.toml` to configure the desired layer type.
+//
+//go:generate mockgen -package testmock -destination ../../phase/testmock/metadata_restorer.go github.com/buildpacks/lifecycle/internal/layer MetadataRestorer
 type MetadataRestorer interface {
 	Restore(buildpacks []buildpack.GroupElement, appMeta files.LayersMetadata, cacheMeta platform.CacheMetadata, layerSHAStore SHAStore) error
 }
diff --git a/internal/layer/sbom_restorer.go b/internal/layer/sbom_restorer.go
index 5db324ec..0af736c7 100644
--- a/internal/layer/sbom_restorer.go
+++ b/internal/layer/sbom_restorer.go
@@ -20,7 +20,12 @@ import (
 	"github.com/buildpacks/lifecycle/log"
 )
 
-//go:generate mockgen -package testmock -destination ../../testmock/sbom_restorer.go github.com/buildpacks/lifecycle/internal/layer SBOMRestorer
+// SBOMRestorer handles the restoration of SBOM layer data.
+// Given a previous image or cache, it will extract the SBOM layer to the local filesystem.
+// Given a group of buildpacks and a `<layers>/sbom/<cache|launch>` directory,
+// it will copy the relevant SBOM files to `<layers>/<buildpack-id>/ so that they can be used in the current build.
+//
+//go:generate mockgen -package testmock -destination ../../phase/testmock/sbom_restorer.go github.com/buildpacks/lifecycle/internal/layer SBOMRestorer
 type SBOMRestorer interface {
 	RestoreFromPrevious(image imgutil.Image, layerDigest string) error
 	RestoreFromCache(cache Cache, layerDigest string) error
diff --git a/analyzer.go b/phase/analyzer.go
similarity index 55%
rename from analyzer.go
rename to phase/analyzer.go
index 1321c85b..0217411b 100644
--- a/analyzer.go
+++ b/phase/analyzer.go
@@ -1,11 +1,10 @@
-package lifecycle
+package phase
 
 import (
 	"github.com/buildpacks/imgutil"
 	"github.com/pkg/errors"
 
 	"github.com/buildpacks/lifecycle/api"
-	"github.com/buildpacks/lifecycle/cache"
 	"github.com/buildpacks/lifecycle/image"
 	"github.com/buildpacks/lifecycle/internal/fsutil"
 	"github.com/buildpacks/lifecycle/internal/layer"
@@ -14,33 +13,8 @@ import (
 	"github.com/buildpacks/lifecycle/platform/files"
 )
 
-type AnalyzerFactory struct {
-	platformAPI     *api.Version
-	apiVerifier     BuildpackAPIVerifier
-	cacheHandler    CacheHandler
-	configHandler   ConfigHandler
-	imageHandler    image.Handler
-	registryHandler image.RegistryHandler
-}
-
-func NewAnalyzerFactory(
-	platformAPI *api.Version,
-	apiVerifier BuildpackAPIVerifier,
-	cacheHandler CacheHandler,
-	configHandler ConfigHandler,
-	imageHandler image.Handler,
-	registryHandler image.RegistryHandler,
-) *AnalyzerFactory {
-	return &AnalyzerFactory{
-		platformAPI:     platformAPI,
-		apiVerifier:     apiVerifier,
-		cacheHandler:    cacheHandler,
-		configHandler:   configHandler,
-		imageHandler:    imageHandler,
-		registryHandler: registryHandler,
-	}
-}
-
+// Analyzer reads metadata from the previous image (if it exists) and the run image,
+// and additionally restores the SBOM layer from the previous image for use later in the build.
 type Analyzer struct {
 	PreviousImage imgutil.Image
 	RunImage      imgutil.Image
@@ -50,90 +24,34 @@ type Analyzer struct {
 }
 
 // NewAnalyzer configures a new Analyzer according to the provided Platform API version.
-func (f *AnalyzerFactory) NewAnalyzer(additionalTags []string, cacheImageRef string, launchCacheDir string, layersDir string, outputImageRef string, previousImageRef string, runImageRef string, skipLayers bool, logger log.Logger) (*Analyzer, error) {
+func (f *ConnectedFactory) NewAnalyzer(inputs platform.LifecycleInputs, logger log.Logger) (*Analyzer, error) {
 	analyzer := &Analyzer{
 		Logger:       logger,
 		SBOMRestorer: &layer.NopSBOMRestorer{},
 		PlatformAPI:  f.platformAPI,
 	}
 
-	if err := f.ensureRegistryAccess(additionalTags, cacheImageRef, outputImageRef, runImageRef, previousImageRef); err != nil {
+	if err := f.ensureRegistryAccess(inputs); err != nil {
 		return nil, err
 	}
 
-	if f.platformAPI.AtLeast("0.8") && !skipLayers {
-		analyzer.SBOMRestorer = &layer.DefaultSBOMRestorer{ // FIXME: eventually layer.NewSBOMRestorer should always return the default one, and then we can use the constructor
-			LayersDir: layersDir,
+	if f.platformAPI.AtLeast("0.8") && !inputs.SkipLayers {
+		analyzer.SBOMRestorer = &layer.DefaultSBOMRestorer{
+			LayersDir: inputs.LayersDir,
 			Logger:    logger,
 		}
 	}
 
-	if err := f.setPrevious(analyzer, previousImageRef, launchCacheDir); err != nil {
+	var err error
+	if analyzer.PreviousImage, err = f.getPreviousImage(inputs.PreviousImageRef, inputs.LaunchCacheDir); err != nil {
 		return nil, err
 	}
-	if err := f.setRun(analyzer, runImageRef); err != nil {
+	if analyzer.RunImage, err = f.getRunImage(inputs.RunImageRef); err != nil {
 		return nil, err
 	}
 	return analyzer, nil
 }
 
-func (f *AnalyzerFactory) ensureRegistryAccess(
-	additionalTags []string,
-	cacheImageRef string,
-	outputImageRef string,
-	runImageRef string,
-	previousImageRef string,
-) error {
-	var readImages, writeImages []string
-	writeImages = append(writeImages, cacheImageRef)
-	if f.imageHandler.Kind() == image.RemoteKind {
-		readImages = append(readImages, previousImageRef, runImageRef)
-		writeImages = append(writeImages, outputImageRef)
-		writeImages = append(writeImages, additionalTags...)
-	}
-
-	if err := f.registryHandler.EnsureReadAccess(readImages...); err != nil {
-		return errors.Wrap(err, "validating registry read access")
-	}
-	if err := f.registryHandler.EnsureWriteAccess(writeImages...); err != nil {
-		return errors.Wrap(err, "validating registry write access")
-	}
-	return nil
-}
-
-func (f *AnalyzerFactory) setPrevious(analyzer *Analyzer, imageRef string, launchCacheDir string) error {
-	if imageRef == "" {
-		return nil
-	}
-	var err error
-	analyzer.PreviousImage, err = f.imageHandler.InitImage(imageRef)
-	if err != nil {
-		return errors.Wrap(err, "getting previous image")
-	}
-	if launchCacheDir == "" || f.imageHandler.Kind() != image.LocalKind {
-		return nil
-	}
-
-	volumeCache, err := cache.NewVolumeCache(launchCacheDir)
-	if err != nil {
-		return errors.Wrap(err, "creating launch cache")
-	}
-	analyzer.PreviousImage = cache.NewCachingImage(analyzer.PreviousImage, volumeCache)
-	return nil
-}
-
-func (f *AnalyzerFactory) setRun(analyzer *Analyzer, imageRef string) error {
-	if imageRef == "" {
-		return nil
-	}
-	var err error
-	analyzer.RunImage, err = f.imageHandler.InitImage(imageRef)
-	if err != nil {
-		return errors.Wrap(err, "getting run image")
-	}
-	return nil
-}
-
 // Analyze fetches the layers metadata from the previous image and writes analyzed.toml.
 func (a *Analyzer) Analyze() (files.Analyzed, error) {
 	defer log.NewMeasurement("Analyzer", a.Logger)()
diff --git a/builder.go b/phase/builder.go
similarity index 97%
rename from builder.go
rename to phase/builder.go
index c4872d42..6f9ae736 100644
--- a/builder.go
+++ b/phase/builder.go
@@ -1,4 +1,4 @@
-package lifecycle
+package phase
 
 import (
 	"fmt"
@@ -25,7 +25,9 @@ type Platform interface {
 	API() *api.Version
 }
 
-//go:generate mockgen -package testmock -destination testmock/build_env.go github.com/buildpacks/lifecycle BuildEnv
+// BuildEnv encapsulates modifications that the lifecycle can make to buildpacks' build environment.
+//
+//go:generate mockgen -package testmock -destination testmock/build_env.go github.com/buildpacks/lifecycle/phase BuildEnv
 type BuildEnv interface {
 	AddRootDir(baseDir string) error
 	AddEnvDir(envDir string, defaultAction env.ActionType) error
diff --git a/cache.go b/phase/cache.go
similarity index 99%
rename from cache.go
rename to phase/cache.go
index 88059440..16685d97 100644
--- a/cache.go
+++ b/phase/cache.go
@@ -1,4 +1,4 @@
-package lifecycle
+package phase
 
 import (
 	"fmt"
diff --git a/phase/connected_factory.go b/phase/connected_factory.go
new file mode 100644
index 00000000..3514938a
--- /dev/null
+++ b/phase/connected_factory.go
@@ -0,0 +1,88 @@
+package phase
+
+import (
+	"fmt"
+
+	"github.com/buildpacks/imgutil"
+
+	"github.com/buildpacks/lifecycle/api"
+	"github.com/buildpacks/lifecycle/cache"
+	"github.com/buildpacks/lifecycle/image"
+	"github.com/buildpacks/lifecycle/platform"
+)
+
+// ConnectedFactory is used to construct lifecycle phases that require access to an image repository
+// (registry, layout directory, or docker daemon) and/or a cache.
+type ConnectedFactory struct {
+	platformAPI     *api.Version
+	apiVerifier     BuildpackAPIVerifier
+	cacheHandler    CacheHandler
+	configHandler   ConfigHandler
+	imageHandler    image.Handler
+	registryHandler image.RegistryHandler
+}
+
+// NewConnectedFactory constructs a new ConnectedFactory.
+func NewConnectedFactory(
+	platformAPI *api.Version,
+	apiVerifier BuildpackAPIVerifier,
+	cacheHandler CacheHandler,
+	configHandler ConfigHandler,
+	imageHandler image.Handler,
+	registryHandler image.RegistryHandler,
+) *ConnectedFactory {
+	return &ConnectedFactory{
+		platformAPI:     platformAPI,
+		apiVerifier:     apiVerifier,
+		cacheHandler:    cacheHandler,
+		configHandler:   configHandler,
+		imageHandler:    imageHandler,
+		registryHandler: registryHandler,
+	}
+}
+
+func (f *ConnectedFactory) ensureRegistryAccess(inputs platform.LifecycleInputs) error {
+	var readImages, writeImages []string
+	writeImages = append(writeImages, inputs.CacheImageRef)
+	if f.imageHandler.Kind() == image.RemoteKind {
+		readImages = append(readImages, inputs.PreviousImageRef, inputs.RunImageRef)
+		writeImages = append(writeImages, inputs.OutputImageRef)
+		writeImages = append(writeImages, inputs.AdditionalTags...)
+	}
+	if err := f.registryHandler.EnsureReadAccess(readImages...); err != nil {
+		return fmt.Errorf("validating registry read access: %w", err)
+	}
+	if err := f.registryHandler.EnsureWriteAccess(writeImages...); err != nil {
+		return fmt.Errorf("validating registry write access: %w", err)
+	}
+	return nil
+}
+
+func (f *ConnectedFactory) getPreviousImage(imageRef string, launchCacheDir string) (imgutil.Image, error) {
+	if imageRef == "" {
+		return nil, nil
+	}
+	previousImage, err := f.imageHandler.InitImage(imageRef)
+	if err != nil {
+		return nil, fmt.Errorf("getting previous image: %w", err)
+	}
+	if launchCacheDir == "" || f.imageHandler.Kind() != image.LocalKind {
+		return previousImage, nil
+	}
+	volumeCache, err := cache.NewVolumeCache(launchCacheDir)
+	if err != nil {
+		return nil, fmt.Errorf("creating launch cache: %w", err)
+	}
+	return cache.NewCachingImage(previousImage, volumeCache), nil
+}
+
+func (f *ConnectedFactory) getRunImage(imageRef string) (imgutil.Image, error) {
+	if imageRef == "" {
+		return nil, nil
+	}
+	runImage, err := f.imageHandler.InitImage(imageRef)
+	if err != nil {
+		return nil, fmt.Errorf("getting run image: %w", err)
+	}
+	return runImage, nil
+}
diff --git a/detector.go b/phase/detector.go
similarity index 89%
rename from detector.go
rename to phase/detector.go
index f140bb6b..f230a878 100644
--- a/detector.go
+++ b/phase/detector.go
@@ -1,4 +1,4 @@
-package lifecycle
+package phase
 
 import (
 	"fmt"
@@ -28,32 +28,16 @@ var (
 	ErrBuildpack       = errors.New("buildpack(s) failed with err")
 )
 
-//go:generate mockgen -package testmock -destination testmock/detect_resolver.go github.com/buildpacks/lifecycle DetectResolver
+// DetectResolver given a group of buildpacks (and optional image extensions)
+// processes the outputs of each `./bin/detect` to determine if the group is viable -
+// that is, whether all non-optional buildpacks passed detection and a valid Build Plan can be resolved
+// from each "requires" and "provides" expressed by each group element.
+//
+//go:generate mockgen -package testmock -destination testmock/detect_resolver.go github.com/buildpacks/lifecycle/phase DetectResolver
 type DetectResolver interface {
 	Resolve(done []buildpack.GroupElement, detectRuns *sync.Map) ([]buildpack.GroupElement, []files.BuildPlanEntry, error)
 }
 
-type DetectorFactory struct {
-	platformAPI   *api.Version
-	apiVerifier   BuildpackAPIVerifier
-	configHandler ConfigHandler
-	dirStore      DirStore
-}
-
-func NewDetectorFactory(
-	platformAPI *api.Version,
-	apiVerifier BuildpackAPIVerifier,
-	configHandler ConfigHandler,
-	dirStore DirStore,
-) *DetectorFactory {
-	return &DetectorFactory{
-		platformAPI:   platformAPI,
-		apiVerifier:   apiVerifier,
-		configHandler: configHandler,
-		dirStore:      dirStore,
-	}
-}
-
 type Detector struct {
 	AppDir         string
 	BuildConfigDir string
@@ -74,55 +58,29 @@ type Detector struct {
 	memHandler *memory.Handler
 }
 
-func (f *DetectorFactory) NewDetector(analyzedMD files.Analyzed, appDir, buildConfigDir, orderPath, platformDir string, logger log.LoggerHandlerWithLevel) (*Detector, error) {
+// NewDetector constructs a new Detector by initializing services and reading the provided analyzed and order files.
+func (f *HermeticFactory) NewDetector(inputs platform.LifecycleInputs, logger log.LoggerHandlerWithLevel) (*Detector, error) {
 	memHandler := memory.New()
 	detector := &Detector{
-		AnalyzeMD:      analyzedMD,
-		AppDir:         appDir,
-		BuildConfigDir: buildConfigDir,
+		AppDir:         inputs.AppDir,
+		BuildConfigDir: inputs.BuildConfigDir,
 		DirStore:       f.dirStore,
 		Executor:       &buildpack.DefaultDetectExecutor{},
 		Logger:         logger,
-		PlatformDir:    platformDir,
+		PlatformDir:    inputs.PlatformDir,
 		Resolver:       NewDefaultDetectResolver(&apexlog.Logger{Handler: memHandler}),
 		Runs:           &sync.Map{},
 		memHandler:     memHandler,
 		PlatformAPI:    f.platformAPI,
 	}
-	if err := f.setOrder(detector, orderPath, logger); err != nil {
+	var err error
+	if detector.AnalyzeMD, err = f.configHandler.ReadAnalyzed(inputs.AnalyzedPath, logger); err != nil {
 		return nil, err
 	}
-	return detector, nil
-}
-
-func (f *DetectorFactory) setOrder(detector *Detector, path string, logger log.Logger) error {
-	orderBp, orderExt, err := f.configHandler.ReadOrder(path)
-	if err != nil {
-		return errors.Wrap(err, "reading order")
-	}
-	if len(orderExt) > 0 {
-		detector.HasExtensions = true
-	}
-	if err = f.verifyAPIs(orderBp, orderExt, logger); err != nil {
-		return err
-	}
-	detector.Order = PrependExtensions(orderBp, orderExt)
-	return nil
-}
-
-func (f *DetectorFactory) verifyAPIs(orderBp buildpack.Order, orderExt buildpack.Order, logger log.Logger) error {
-	for _, group := range append(orderBp, orderExt...) {
-		for _, groupEl := range group.Group {
-			module, err := f.dirStore.Lookup(groupEl.Kind(), groupEl.ID, groupEl.Version)
-			if err != nil {
-				return err
-			}
-			if err = f.apiVerifier.VerifyBuildpackAPI(groupEl.Kind(), groupEl.String(), module.API(), logger); err != nil {
-				return err
-			}
-		}
+	if detector.Order, detector.HasExtensions, err = f.getOrder(inputs.OrderPath, logger); err != nil {
+		return nil, err
 	}
-	return nil
+	return detector, nil
 }
 
 func (d *Detector) Detect() (buildpack.Group, files.Plan, error) {
diff --git a/exporter.go b/phase/exporter.go
similarity index 99%
rename from exporter.go
rename to phase/exporter.go
index 992907b1..004a041a 100644
--- a/exporter.go
+++ b/phase/exporter.go
@@ -1,4 +1,4 @@
-package lifecycle
+package phase
 
 import (
 	"compress/gzip"
@@ -47,7 +47,10 @@ type Exporter struct {
 	PlatformAPI  *api.Version
 }
 
-//go:generate mockgen -package testmock -destination testmock/layer_factory.go github.com/buildpacks/lifecycle LayerFactory
+// LayerFactory given a directory on the local filesystem will return a `layers.Layer`
+// that can be used to construct an OCI image.
+//
+//go:generate mockgen -package testmock -destination testmock/layer_factory.go github.com/buildpacks/lifecycle/phase LayerFactory
 type LayerFactory interface {
 	DirLayer(id string, dir string, createdBy string) (layers.Layer, error)
 	LauncherLayer(path string) (layers.Layer, error)
diff --git a/extender.go b/phase/extender.go
similarity index 83%
rename from extender.go
rename to phase/extender.go
index f97f2682..dab1dd56 100644
--- a/extender.go
+++ b/phase/extender.go
@@ -1,4 +1,4 @@
-package lifecycle
+package phase
 
 import (
 	"context"
@@ -24,6 +24,7 @@ import (
 	"github.com/buildpacks/lifecycle/launch"
 	"github.com/buildpacks/lifecycle/layers"
 	"github.com/buildpacks/lifecycle/log"
+	"github.com/buildpacks/lifecycle/platform"
 )
 
 type Extender struct {
@@ -39,96 +40,50 @@ type Extender struct {
 	Extensions        []buildpack.GroupElement // extensions are ordered from group.toml
 }
 
-//go:generate mockgen -package testmock -destination testmock/dockerfile_applier.go github.com/buildpacks/lifecycle DockerfileApplier
+// DockerfileApplier given a base image and a `build.Dockerfile` or `run.Dockerfile` will apply it to the base image
+// and return a new image, or an error if encountered.
+//
+//go:generate mockgen -package testmock -destination testmock/dockerfile_applier.go github.com/buildpacks/lifecycle/phase DockerfileApplier
 type DockerfileApplier interface {
 	ImageFor(reference string) (v1.Image, error)
 	Apply(dockerfile extend.Dockerfile, toBaseImage v1.Image, withBuildOptions extend.Options, logger log.Logger) (v1.Image, error)
 	Cleanup() error
 }
 
-type ExtenderFactory struct {
-	apiVerifier   BuildpackAPIVerifier
-	configHandler ConfigHandler
-}
-
-func NewExtenderFactory(apiVerifier BuildpackAPIVerifier, configHandler ConfigHandler) *ExtenderFactory {
-	return &ExtenderFactory{
-		apiVerifier:   apiVerifier,
-		configHandler: configHandler,
-	}
-}
-
-func (f *ExtenderFactory) NewExtender(
-	analyzedPath string,
-	appDir string,
-	extendedDir string,
-	generatedDir string,
-	groupPath string,
-	layersDir string,
-	platformDir string,
-	cacheTTL time.Duration,
-	dockerfileApplier DockerfileApplier,
-	kind string,
-	logger log.Logger,
-) (*Extender, error) {
+// NewExtender constructs a new Extender by initializing services and reading the provided analyzed and group files
+// to determine the image to extend and the extensions to use.
+func (f *HermeticFactory) NewExtender(inputs platform.LifecycleInputs, dockerfileApplier DockerfileApplier, logger log.Logger) (*Extender, error) {
 	extender := &Extender{
-		AppDir:            appDir,
-		ExtendedDir:       extendedDir,
-		GeneratedDir:      generatedDir,
-		LayersDir:         layersDir,
-		PlatformDir:       platformDir,
-		CacheTTL:          cacheTTL,
+		AppDir:            inputs.AppDir,
+		ExtendedDir:       inputs.ExtendedDir,
+		GeneratedDir:      inputs.GeneratedDir,
+		LayersDir:         inputs.LayersDir,
+		PlatformDir:       inputs.PlatformDir,
+		CacheTTL:          inputs.KanikoCacheTTL,
 		DockerfileApplier: dockerfileApplier,
 	}
-	if err := f.setImageRef(extender, kind, analyzedPath, logger); err != nil {
+	var err error
+	if extender.ImageRef, err = f.getExtendImageRef(inputs, logger); err != nil {
 		return nil, err
 	}
-	if err := f.setExtensions(extender, groupPath, logger); err != nil {
+	if extender.Extensions, err = f.getExtensions(inputs.GroupPath, logger); err != nil {
 		return nil, err
 	}
 	return extender, nil
 }
 
-func (f *ExtenderFactory) setImageRef(extender *Extender, kind, path string, logr log.Logger) error {
-	analyzedMD, err := f.configHandler.ReadAnalyzed(path, logr)
-	if err != nil {
-		return err
-	}
-	if kind == "build" {
-		if analyzedMD.BuildImage != nil {
-			extender.ImageRef = analyzedMD.BuildImage.Reference
-		}
-	} else if kind == "run" {
-		if analyzedMD.RunImage != nil {
-			extender.ImageRef = analyzedMD.RunImage.Reference
-		}
-	}
-
-	return nil
-}
-
-func (f *ExtenderFactory) setExtensions(extender *Extender, path string, logger log.Logger) error {
-	_, groupExt, err := f.configHandler.ReadGroup(path)
+func (f *HermeticFactory) getExtendImageRef(inputs platform.LifecycleInputs, logger log.Logger) (string, error) {
+	analyzedMD, err := f.configHandler.ReadAnalyzed(inputs.AnalyzedPath, logger)
 	if err != nil {
-		return fmt.Errorf("reading group: %w", err)
+		return "", err
 	}
-	for i := range groupExt {
-		groupExt[i].Extension = true
+	if inputs.ExtendKind == "build" && analyzedMD.BuildImage != nil {
+		return analyzedMD.BuildImage.Reference, nil
 	}
-	if err = f.verifyAPIs(groupExt, logger); err != nil {
-		return err
+	if inputs.ExtendKind == "run" && analyzedMD.RunImage != nil {
+		return analyzedMD.RunImage.Reference, nil
 	}
-	extender.Extensions = groupExt
-	return nil
-}
-
-func (f *ExtenderFactory) verifyAPIs(groupExt []buildpack.GroupElement, logger log.Logger) error {
-	for _, groupEl := range groupExt {
-		if err := f.apiVerifier.VerifyBuildpackAPI(groupEl.Kind(), groupEl.String(), groupEl.API, logger); err != nil {
-			return err
-		}
-	}
-	return nil
+	return "", nil
 }
 
 func (e *Extender) Extend(kind string, logger log.Logger) error {
diff --git a/generator.go b/phase/generator.go
similarity index 77%
rename from generator.go
rename to phase/generator.go
index 8cd09171..a5f35d53 100644
--- a/generator.go
+++ b/phase/generator.go
@@ -1,4 +1,4 @@
-package lifecycle
+package phase
 
 import (
 	"fmt"
@@ -33,88 +33,39 @@ type Generator struct {
 	RunMetadata    files.Run
 }
 
-type GeneratorFactory struct {
-	apiVerifier   BuildpackAPIVerifier
-	configHandler ConfigHandler
-	dirStore      DirStore
-}
-
-func NewGeneratorFactory(
-	apiVerifier BuildpackAPIVerifier,
-	configHandler ConfigHandler,
-	dirStore DirStore,
-) *GeneratorFactory {
-	return &GeneratorFactory{
-		apiVerifier:   apiVerifier,
-		configHandler: configHandler,
-		dirStore:      dirStore,
-	}
-}
-
-func (f *GeneratorFactory) NewGenerator(
-	analyzedPath string,
-	appDir string,
-	buildConfigDir string,
-	extensions []buildpack.GroupElement,
-	generatedDir string,
-	plan files.Plan,
-	platformAPI *api.Version,
-	platformDir string,
-	runPath string,
-	stdout, stderr io.Writer,
-	logger log.Logger,
-) (*Generator, error) {
+// NewGenerator constructs a new Generator by initializing services and reading the provided analyzed, group, plan, and run files.
+func (f *HermeticFactory) NewGenerator(inputs platform.LifecycleInputs, stdout, stderr io.Writer, logger log.Logger) (*Generator, error) {
 	generator := &Generator{
-		AppDir:         appDir,
-		BuildConfigDir: buildConfigDir,
-		GeneratedDir:   generatedDir,
-		PlatformAPI:    platformAPI,
-		PlatformDir:    platformDir,
+		AppDir:         inputs.AppDir,
+		BuildConfigDir: inputs.BuildConfigDir,
+		GeneratedDir:   inputs.GeneratedDir,
+		PlatformAPI:    inputs.PlatformAPI,
+		PlatformDir:    inputs.PlatformDir,
 		DirStore:       f.dirStore,
 		Executor:       &buildpack.DefaultGenerateExecutor{},
 		Logger:         logger,
-		Plan:           plan,
 		Out:            stdout,
 		Err:            stderr,
 	}
-	if err := f.setExtensions(generator, extensions, logger); err != nil {
+	var err error
+	if generator.Extensions, err = f.getExtensions(inputs.GroupPath, logger); err != nil {
 		return nil, err
 	}
-	if err := f.setAnalyzedMD(generator, analyzedPath, logger); err != nil {
+	if generator.Plan, err = f.configHandler.ReadPlan(inputs.PlanPath); err != nil {
 		return nil, err
 	}
-	if err := f.setRunMD(generator, runPath, logger); err != nil {
+	if generator.RunMetadata, err = f.configHandler.ReadRun(inputs.RunPath, logger); err != nil {
 		return nil, err
 	}
-	return generator, nil
-}
-
-func (f *GeneratorFactory) setExtensions(generator *Generator, extensions []buildpack.GroupElement, logger log.Logger) error {
-	generator.Extensions = extensions
-	for _, el := range generator.Extensions {
-		if err := f.apiVerifier.VerifyBuildpackAPI(buildpack.KindExtension, el.String(), el.API, logger); err != nil {
-			return err
-		}
+	if generator.AnalyzedMD, err = f.configHandler.ReadAnalyzed(inputs.AnalyzedPath, logger); err != nil {
+		return nil, err
 	}
-	return nil
-}
-
-func (f *GeneratorFactory) setAnalyzedMD(generator *Generator, analyzedPath string, logger log.Logger) error {
-	var err error
-	generator.AnalyzedMD, err = f.configHandler.ReadAnalyzed(analyzedPath, logger)
-	return err
-}
-
-func (f *GeneratorFactory) setRunMD(generator *Generator, runPath string, logger log.Logger) error {
-	var err error
-	generator.RunMetadata, err = f.configHandler.ReadRun(runPath, logger)
-	return err
+	return generator, nil
 }
 
 type GenerateResult struct {
 	AnalyzedMD files.Analyzed
 	Plan       files.Plan
-	UsePlan    bool
 }
 
 func (g *Generator) Generate() (GenerateResult, error) {
@@ -185,7 +136,6 @@ func (g *Generator) Generate() (GenerateResult, error) {
 	return GenerateResult{
 		AnalyzedMD: finalAnalyzedMD,
 		Plan:       filteredPlan,
-		UsePlan:    true,
 	}, nil
 }
 
diff --git a/handlers.go b/phase/handlers.go
similarity index 55%
rename from handlers.go
rename to phase/handlers.go
index 2c4b3c3e..033edf50 100644
--- a/handlers.go
+++ b/phase/handlers.go
@@ -1,4 +1,4 @@
-package lifecycle
+package phase
 
 import (
 	"fmt"
@@ -6,37 +6,47 @@ import (
 	"github.com/BurntSushi/toml"
 
 	"github.com/buildpacks/lifecycle/buildpack"
+	"github.com/buildpacks/lifecycle/internal/encoding"
 	"github.com/buildpacks/lifecycle/log"
 	"github.com/buildpacks/lifecycle/platform/files"
 )
 
 var Config = &DefaultConfigHandler{}
 
-//go:generate mockgen -package testmock -destination testmock/cache_handler.go github.com/buildpacks/lifecycle CacheHandler
+// CacheHandler wraps initialization of a cache image or cache volume.
+//
+//go:generate mockgen -package testmock -destination testmock/cache_handler.go github.com/buildpacks/lifecycle/phase CacheHandler
 type CacheHandler interface {
 	InitCache(imageRef, dir string, deletionEnabled bool) (Cache, error)
 }
 
-//go:generate mockgen -package testmock -destination testmock/dir_store.go github.com/buildpacks/lifecycle DirStore
+// DirStore is a repository of buildpacks and/or image extensions.
+// Each element should be present on disk according to the format outlined in the Platform Interface Specification,
+// namely: `/cnb/<buildpacks|extensions>/<id>/<version>/<root directory>`.
+//
+//go:generate mockgen -package testmock -destination testmock/dir_store.go github.com/buildpacks/lifecycle/phase DirStore
 type DirStore interface {
 	Lookup(kind, id, version string) (buildpack.Descriptor, error)
 	LookupBp(id, version string) (*buildpack.BpDescriptor, error)
 	LookupExt(id, version string) (*buildpack.ExtDescriptor, error)
 }
 
-//go:generate mockgen -package testmock -destination testmock/image_handler.go github.com/buildpacks/lifecycle/image Handler
-
-//go:generate mockgen -package testmock -destination testmock/buildpack_api_verifier.go github.com/buildpacks/lifecycle BuildpackAPIVerifier
+// BuildpackAPIVerifier verifies a requested Buildpack API version.
+//
+//go:generate mockgen -package testmock -destination testmock/buildpack_api_verifier.go github.com/buildpacks/lifecycle/phase BuildpackAPIVerifier
 type BuildpackAPIVerifier interface {
-	VerifyBuildpackAPI(kind, name, requested string, logger log.Logger) error
+	VerifyBuildpackAPI(kind, name, requestedVersion string, logger log.Logger) error
 }
 
-//go:generate mockgen -package testmock -destination testmock/config_handler.go github.com/buildpacks/lifecycle ConfigHandler
+// ConfigHandler reads configuration files for the lifecycle.
+//
+//go:generate mockgen -package testmock -destination testmock/config_handler.go github.com/buildpacks/lifecycle/phase ConfigHandler
 type ConfigHandler interface {
 	ReadAnalyzed(path string, logger log.Logger) (files.Analyzed, error)
 	ReadGroup(path string) (buildpackGroup []buildpack.GroupElement, extensionsGroup []buildpack.GroupElement, err error)
 	ReadOrder(path string) (buildpack.Order, buildpack.Order, error)
 	ReadRun(runPath string, logger log.Logger) (files.Run, error)
+	ReadPlan(path string) (files.Plan, error)
 }
 
 type DefaultConfigHandler struct{}
@@ -45,8 +55,19 @@ func NewConfigHandler() *DefaultConfigHandler {
 	return &DefaultConfigHandler{}
 }
 
-func (h *DefaultConfigHandler) ReadAnalyzed(path string, logr log.Logger) (files.Analyzed, error) {
-	return files.ReadAnalyzed(path, logr)
+// ReadAnalyzed reads the provided analyzed.toml file.
+func (h *DefaultConfigHandler) ReadAnalyzed(path string, logger log.Logger) (files.Analyzed, error) {
+	return files.ReadAnalyzed(path, logger)
+}
+
+// WriteAnalyzed writes the provided analyzed metadata to analyzed.toml.
+func (h *DefaultConfigHandler) WriteAnalyzed(path string, analyzedMD *files.Analyzed, logger log.Logger) error {
+	logger.Debugf("Run image info in analyzed metadata is: ")
+	logger.Debugf(encoding.ToJSONMaybe(analyzedMD.RunImage))
+	if err := encoding.WriteTOML(path, analyzedMD); err != nil {
+		return fmt.Errorf("failed to write analyzed: %w", err)
+	}
+	return nil
 }
 
 func (h *DefaultConfigHandler) ReadGroup(path string) (buildpackGroup []buildpack.GroupElement, extensionsGroup []buildpack.GroupElement, err error) {
@@ -68,6 +89,31 @@ func ReadGroup(path string) (buildpack.Group, error) {
 	return group, err
 }
 
+// WriteGroup writes the provided group information to group.toml.
+func (h *DefaultConfigHandler) WriteGroup(path string, group *buildpack.Group) error {
+	if err := encoding.WriteTOML(path, group); err != nil {
+		return fmt.Errorf("failed to write group: %w", err)
+	}
+	return nil
+}
+
+// ReadPlan reads the provided plan.toml file.
+func (h *DefaultConfigHandler) ReadPlan(path string) (files.Plan, error) {
+	var plan files.Plan
+	if _, err := toml.DecodeFile(path, &plan); err != nil {
+		return files.Plan{}, err
+	}
+	return plan, nil
+}
+
+// WritePlan writes the provided plan information to plan.toml.
+func (h *DefaultConfigHandler) WritePlan(path string, plan *files.Plan) error {
+	if err := encoding.WriteTOML(path, plan); err != nil {
+		return fmt.Errorf("failed to write plan: %w", err)
+	}
+	return nil
+}
+
 func (h *DefaultConfigHandler) ReadOrder(path string) (buildpack.Order, buildpack.Order, error) {
 	orderBp, orderExt, err := ReadOrder(path)
 	if err != nil {
diff --git a/phase/hermetic_factory.go b/phase/hermetic_factory.go
new file mode 100644
index 00000000..f3b1516b
--- /dev/null
+++ b/phase/hermetic_factory.go
@@ -0,0 +1,88 @@
+package phase
+
+import (
+	"fmt"
+
+	"github.com/pkg/errors"
+
+	"github.com/buildpacks/lifecycle/api"
+	"github.com/buildpacks/lifecycle/buildpack"
+	"github.com/buildpacks/lifecycle/log"
+)
+
+// HermeticFactory is used to construct lifecycle phases that do NOT require access to an image repository.
+type HermeticFactory struct {
+	platformAPI   *api.Version
+	apiVerifier   BuildpackAPIVerifier
+	configHandler ConfigHandler
+	dirStore      DirStore
+}
+
+// NewHermeticFactory is used to construct a new HermeticFactory.
+func NewHermeticFactory(
+	platformAPI *api.Version,
+	apiVerifier BuildpackAPIVerifier,
+	configHandler ConfigHandler,
+	dirStore DirStore,
+) *HermeticFactory {
+	return &HermeticFactory{
+		platformAPI:   platformAPI,
+		apiVerifier:   apiVerifier,
+		configHandler: configHandler,
+		dirStore:      dirStore,
+	}
+}
+
+func (f *HermeticFactory) getExtensions(groupPath string, logger log.Logger) ([]buildpack.GroupElement, error) {
+	_, groupExt, err := f.configHandler.ReadGroup(groupPath)
+	if err != nil {
+		return nil, fmt.Errorf("reading group: %w", err)
+	}
+	for i := range groupExt {
+		groupExt[i].Extension = true
+	}
+	if err = f.verifyGroup(groupExt, logger); err != nil {
+		return nil, err
+	}
+	return groupExt, nil
+}
+
+func (f *HermeticFactory) getOrder(path string, logger log.Logger) (order buildpack.Order, hasExtensions bool, err error) {
+	orderBp, orderExt, orderErr := f.configHandler.ReadOrder(path)
+	if orderErr != nil {
+		err = errors.Wrap(orderErr, "reading order")
+		return
+	}
+	if len(orderExt) > 0 {
+		hasExtensions = true
+	}
+	if err = f.verifyOrder(orderBp, orderExt, logger); err != nil {
+		return
+	}
+	order = PrependExtensions(orderBp, orderExt)
+	return
+}
+
+func (f *HermeticFactory) verifyGroup(group []buildpack.GroupElement, logger log.Logger) error {
+	for _, groupEl := range group {
+		if err := f.apiVerifier.VerifyBuildpackAPI(groupEl.Kind(), groupEl.String(), groupEl.API, logger); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (f *HermeticFactory) verifyOrder(orderBp buildpack.Order, orderExt buildpack.Order, logger log.Logger) error {
+	for _, group := range append(orderBp, orderExt...) {
+		for _, groupEl := range group.Group {
+			module, err := f.dirStore.Lookup(groupEl.Kind(), groupEl.ID, groupEl.Version)
+			if err != nil {
+				return err
+			}
+			if err = f.apiVerifier.VerifyBuildpackAPI(groupEl.Kind(), groupEl.String(), module.API(), logger); err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}
diff --git a/phase/phase.go b/phase/phase.go
new file mode 100644
index 00000000..8d9880ce
--- /dev/null
+++ b/phase/phase.go
@@ -0,0 +1,6 @@
+// Package phase handles logic internal to the CNB lifecycle,
+// and roughly corresponds to the actions denoted by "the lifecycle MUST" and "the lifecycle SHALL"
+// in the [Platform Interface Specification].
+//
+// [Platform Interface Specification]: https://github.com/buildpacks/spec/blob/main/platform.md
+package phase
diff --git a/rebaser.go b/phase/rebaser.go
similarity index 99%
rename from rebaser.go
rename to phase/rebaser.go
index 76a70f18..7887332b 100644
--- a/rebaser.go
+++ b/phase/rebaser.go
@@ -1,4 +1,4 @@
-package lifecycle
+package phase
 
 import (
 	"encoding/json"
diff --git a/restorer.go b/phase/restorer.go
similarity index 99%
rename from restorer.go
rename to phase/restorer.go
index 4bf29c60..30357c52 100644
--- a/restorer.go
+++ b/phase/restorer.go
@@ -1,4 +1,4 @@
-package lifecycle
+package phase
 
 import (
 	"path/filepath"
diff --git a/save.go b/phase/save.go
similarity index 99%
rename from save.go
rename to phase/save.go
index 4505c8e4..b2e4a891 100644
--- a/save.go
+++ b/phase/save.go
@@ -1,4 +1,4 @@
-package lifecycle
+package phase
 
 import (
 	"fmt"
diff --git a/testmock/sbom_restorer.go b/phase/testmock/sbom_restorer.go
similarity index 100%
rename from testmock/sbom_restorer.go
rename to phase/testmock/sbom_restorer.go
diff --git a/utils.go b/phase/utils.go
similarity index 95%
rename from utils.go
rename to phase/utils.go
index fb39803a..3e6af365 100644
--- a/utils.go
+++ b/phase/utils.go
@@ -1,4 +1,4 @@
-package lifecycle
+package phase
 
 import (
 	"strings"
diff --git a/platform/platform.go b/platform/platform.go
index 8820ab18..908ea7c5 100644
--- a/platform/platform.go
+++ b/platform/platform.go
@@ -34,3 +34,8 @@ func NewPlatformFor(platformAPI string) *Platform {
 func (p *Platform) API() *api.Version {
 	return p.PlatformAPI
 }
+
+// Inputs exposes the platform's inputs to the lifecycle.
+func (p *Platform) Inputs() LifecycleInputs {
+	return *p.LifecycleInputs
+}


Test patch:
diff --git a/acceptance/restorer_test.go b/acceptance/restorer_test.go
index 8ec3ec5a..60e06eb2 100644
--- a/acceptance/restorer_test.go
+++ b/acceptance/restorer_test.go
@@ -14,9 +14,9 @@ import (
 	"github.com/sclevine/spec"
 	"github.com/sclevine/spec/report"
 
-	"github.com/buildpacks/lifecycle"
 	"github.com/buildpacks/lifecycle/api"
 	"github.com/buildpacks/lifecycle/cmd"
+	"github.com/buildpacks/lifecycle/phase"
 	h "github.com/buildpacks/lifecycle/testhelpers"
 )
 
@@ -198,7 +198,7 @@ func testRestorerFunc(platformAPI string) func(t *testing.T, when spec.G, it spe
 					h.WithArgs("-build-image", restoreRegFixtures.SomeCacheImage), // some-cache-image simulates a builder image in a registry
 				)
 				t.Log("records builder image digest in analyzed.toml")
-				analyzedMD, err := lifecycle.Config.ReadAnalyzed(filepath.Join(copyDir, "layers", "analyzed.toml"), cmd.DefaultLogger)
+				analyzedMD, err := phase.Config.ReadAnalyzed(filepath.Join(copyDir, "layers", "analyzed.toml"), cmd.DefaultLogger)
 				h.AssertNil(t, err)
 				h.AssertStringContains(t, analyzedMD.BuildImage.Reference, restoreRegFixtures.SomeCacheImage+"@sha256:")
 				t.Log("writes builder manifest and config to the kaniko cache")
@@ -230,7 +230,7 @@ func testRestorerFunc(platformAPI string) func(t *testing.T, when spec.G, it spe
 					),
 				)
 				t.Log("updates run image reference in analyzed.toml to include digest and target data")
-				analyzedMD, err := lifecycle.Config.ReadAnalyzed(filepath.Join(copyDir, "layers", "some-extend-true-analyzed.toml"), cmd.DefaultLogger)
+				analyzedMD, err := phase.Config.ReadAnalyzed(filepath.Join(copyDir, "layers", "some-extend-true-analyzed.toml"), cmd.DefaultLogger)
 				h.AssertNil(t, err)
 				h.AssertStringContains(t, analyzedMD.RunImage.Reference, restoreRegFixtures.ReadOnlyRunImage+"@sha256:")
 				h.AssertEq(t, analyzedMD.RunImage.Image, restoreRegFixtures.ReadOnlyRunImage)
@@ -266,7 +266,7 @@ func testRestorerFunc(platformAPI string) func(t *testing.T, when spec.G, it spe
 				)
 				if api.MustParse(platformAPI).AtLeast("0.12") {
 					t.Log("updates run image reference in analyzed.toml to include digest and target data")
-					analyzedMD, err := lifecycle.Config.ReadAnalyzed(filepath.Join(copyDir, "layers", "some-extend-false-analyzed.toml"), cmd.DefaultLogger)
+					analyzedMD, err := phase.Config.ReadAnalyzed(filepath.Join(copyDir, "layers", "some-extend-false-analyzed.toml"), cmd.DefaultLogger)
 					h.AssertNil(t, err)
 					h.AssertStringContains(t, analyzedMD.RunImage.Reference, restoreRegFixtures.ReadOnlyRunImage+"@sha256:")
 					h.AssertEq(t, analyzedMD.RunImage.Image, restoreRegFixtures.ReadOnlyRunImage)
@@ -279,7 +279,7 @@ func testRestorerFunc(platformAPI string) func(t *testing.T, when spec.G, it spe
 					h.AssertEq(t, len(fis), 1) // .gitkeep
 				} else {
 					t.Log("doesn't update analyzed.toml")
-					analyzedMD, err := lifecycle.Config.ReadAnalyzed(filepath.Join(copyDir, "layers", "some-extend-false-analyzed.toml"), cmd.DefaultLogger)
+					analyzedMD, err := phase.Config.ReadAnalyzed(filepath.Join(copyDir, "layers", "some-extend-false-analyzed.toml"), cmd.DefaultLogger)
 					h.AssertNil(t, err)
 					h.AssertNil(t, analyzedMD.RunImage.TargetMetadata)
 				}
@@ -306,7 +306,7 @@ func testRestorerFunc(platformAPI string) func(t *testing.T, when spec.G, it spe
 						),
 					)
 					t.Log("updates run image reference in analyzed.toml to include digest and target data")
-					analyzedMD, err := lifecycle.Config.ReadAnalyzed(filepath.Join(copyDir, "layers", "some-extend-false-analyzed.toml"), cmd.DefaultLogger)
+					analyzedMD, err := phase.Config.ReadAnalyzed(filepath.Join(copyDir, "layers", "some-extend-false-analyzed.toml"), cmd.DefaultLogger)
 					h.AssertNil(t, err)
 					h.AssertStringDoesNotContain(t, analyzedMD.RunImage.Reference, "@sha256:") // daemon image ID
 					h.AssertEq(t, analyzedMD.RunImage.Image, restoreRegFixtures.ReadOnlyRunImage)
diff --git a/buildpack/build_test.go b/buildpack/build_test.go
index e6ce3420..a28b3101 100644
--- a/buildpack/build_test.go
+++ b/buildpack/build_test.go
@@ -24,11 +24,11 @@ import (
 	"github.com/buildpacks/lifecycle/launch"
 	"github.com/buildpacks/lifecycle/layers"
 	llog "github.com/buildpacks/lifecycle/log"
+	"github.com/buildpacks/lifecycle/phase/testmock"
 	h "github.com/buildpacks/lifecycle/testhelpers"
-	"github.com/buildpacks/lifecycle/testmock"
 )
 
-//go:generate mockgen -package testmock -destination testmock/env.go github.com/buildpacks/lifecycle BuildEnv
+//go:generate mockgen -package testmock -destination testmock/env.go github.com/buildpacks/lifecycle/phase BuildEnv
 
 func TestBuild(t *testing.T) {
 	spec.Run(t, "unit-build", testBuild, spec.Report(report.Terminal{}))
diff --git a/buildpack/generate_test.go b/buildpack/generate_test.go
index f71f78e6..b8539ccd 100644
--- a/buildpack/generate_test.go
+++ b/buildpack/generate_test.go
@@ -19,8 +19,8 @@ import (
 	"github.com/buildpacks/lifecycle/api"
 	"github.com/buildpacks/lifecycle/buildpack"
 	llog "github.com/buildpacks/lifecycle/log"
+	"github.com/buildpacks/lifecycle/phase/testmock"
 	h "github.com/buildpacks/lifecycle/testhelpers"
-	"github.com/buildpacks/lifecycle/testmock"
 )
 
 func TestGenerate(t *testing.T) {
diff --git a/buildpack/testmock/env.go b/buildpack/testmock/env.go
index 6fc83b10..59526c8d 100644
--- a/buildpack/testmock/env.go
+++ b/buildpack/testmock/env.go
@@ -1,5 +1,5 @@
 // Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/buildpacks/lifecycle (interfaces: BuildEnv)
+// Source: github.com/buildpacks/lifecycle/phase (interfaces: BuildEnv)
 
 // Package testmock is a generated GoMock package.
 package testmock
diff --git a/cache/image_cache_test.go b/cache/image_cache_test.go
index 4d1d7d76..d3fcb2d1 100644
--- a/cache/image_cache_test.go
+++ b/cache/image_cache_test.go
@@ -7,8 +7,6 @@ import (
 	"path/filepath"
 	"testing"
 
-	cacheMock "github.com/buildpacks/lifecycle/testmock/cache"
-
 	"github.com/golang/mock/gomock"
 
 	"github.com/buildpacks/imgutil/fakes"
@@ -20,6 +18,7 @@ import (
 	"github.com/buildpacks/lifecycle/cache"
 	"github.com/buildpacks/lifecycle/cmd"
 	"github.com/buildpacks/lifecycle/log"
+	testmockcache "github.com/buildpacks/lifecycle/phase/testmock/cache"
 	"github.com/buildpacks/lifecycle/platform"
 	h "github.com/buildpacks/lifecycle/testhelpers"
 )
@@ -48,8 +47,8 @@ func testImageCache(t *testing.T, when spec.G, it spec.S) {
 		fakeOriginalImage = fakes.NewImage("fake-image", "", local.IDIdentifier{ImageID: "fakeOriginalImage"})
 		fakeNewImage = fakes.NewImage("fake-image", "", local.IDIdentifier{ImageID: "fakeImage"})
 		mockController := gomock.NewController(t)
-		fakeImageDeleter := cacheMock.NewMockImageDeleter(mockController)
-		fakeImageComparer := cacheMock.NewMockImageComparer(mockController)
+		fakeImageDeleter := testmockcache.NewMockImageDeleter(mockController)
+		fakeImageComparer := testmockcache.NewMockImageComparer(mockController)
 		fakeImageComparer.EXPECT().ImagesEq(gomock.Any(), gomock.Any()).AnyTimes().Return(false, nil)
 		fakeImageDeleter.EXPECT().DeleteOrigImageIfDifferentFromNewImage(gomock.Any(), gomock.Any()).AnyTimes()
 		testLogger = cmd.DefaultLogger
diff --git a/cache/image_deleter_test.go b/cache/image_deleter_test.go
index 1bc9009f..515314ed 100644
--- a/cache/image_deleter_test.go
+++ b/cache/image_deleter_test.go
@@ -13,8 +13,8 @@ import (
 
 	"github.com/buildpacks/lifecycle/cmd"
 	"github.com/buildpacks/lifecycle/log"
+	cacheMock "github.com/buildpacks/lifecycle/phase/testmock/cache"
 	h "github.com/buildpacks/lifecycle/testhelpers"
-	cacheMock "github.com/buildpacks/lifecycle/testmock/cache"
 )
 
 func TestImageDeleter(t *testing.T) {
diff --git a/image/handler_test.go b/image/handler_test.go
index f97ff4f6..d3d1f960 100644
--- a/image/handler_test.go
+++ b/image/handler_test.go
@@ -9,11 +9,11 @@ import (
 	"github.com/sclevine/spec"
 	"github.com/sclevine/spec/report"
 
+	testmockauth "github.com/buildpacks/lifecycle/phase/testmock/auth"
 	h "github.com/buildpacks/lifecycle/testhelpers"
-	testmockauth "github.com/buildpacks/lifecycle/testmock/auth"
 )
 
-//go:generate mockgen -package testmockauth -destination ../testmock/auth/mock_keychain.go github.com/google/go-containerregistry/pkg/authn Keychain
+//go:generate mockgen -package testmockauth -destination ../phase/testmock/auth/mock_keychain.go github.com/google/go-containerregistry/pkg/authn Keychain
 
 func TestHandler(t *testing.T) {
 	spec.Run(t, "ImageHandler", testHandler, spec.Sequential(), spec.Report(report.Terminal{}))
diff --git a/internal/layer/sbom_restorer_test.go b/internal/layer/sbom_restorer_test.go
index 6d50653a..544b3f29 100644
--- a/internal/layer/sbom_restorer_test.go
+++ b/internal/layer/sbom_restorer_test.go
@@ -13,13 +13,13 @@ import (
 	"github.com/sclevine/spec"
 	"github.com/sclevine/spec/report"
 
-	"github.com/buildpacks/lifecycle"
 	"github.com/buildpacks/lifecycle/api"
 	"github.com/buildpacks/lifecycle/buildpack"
 	"github.com/buildpacks/lifecycle/cache"
 	"github.com/buildpacks/lifecycle/internal/layer"
 	"github.com/buildpacks/lifecycle/launch"
 	"github.com/buildpacks/lifecycle/layers"
+	"github.com/buildpacks/lifecycle/phase"
 	"github.com/buildpacks/lifecycle/platform"
 	"github.com/buildpacks/lifecycle/platform/files"
 	h "github.com/buildpacks/lifecycle/testhelpers"
@@ -149,7 +149,7 @@ func testSBOMRestorer(t *testing.T, when spec.G, it spec.S) {
 			artifactsDir string
 			cacheDir     string
 			layerDigest  string
-			testCache    lifecycle.Cache
+			testCache    phase.Cache
 		)
 
 		it.Before(func() {
diff --git a/analyzer_test.go b/phase/analyzer_test.go
similarity index 74%
rename from analyzer_test.go
rename to phase/analyzer_test.go
index 0c6ba9aa..cfa165f3 100644
--- a/analyzer_test.go
+++ b/phase/analyzer_test.go
@@ -1,4 +1,4 @@
-package lifecycle_test
+package phase_test
 
 import (
 	"encoding/json"
@@ -15,30 +15,29 @@ import (
 	"github.com/sclevine/spec"
 	"github.com/sclevine/spec/report"
 
-	"github.com/buildpacks/lifecycle/image"
-	"github.com/buildpacks/lifecycle/platform/files"
-
-	"github.com/buildpacks/lifecycle"
 	"github.com/buildpacks/lifecycle/api"
 	"github.com/buildpacks/lifecycle/cache"
 	"github.com/buildpacks/lifecycle/cmd"
+	"github.com/buildpacks/lifecycle/image"
 	"github.com/buildpacks/lifecycle/internal/layer"
+	"github.com/buildpacks/lifecycle/phase"
+	"github.com/buildpacks/lifecycle/phase/testmock"
 	"github.com/buildpacks/lifecycle/platform"
+	"github.com/buildpacks/lifecycle/platform/files"
 	h "github.com/buildpacks/lifecycle/testhelpers"
-	"github.com/buildpacks/lifecycle/testmock"
 )
 
 func TestAnalyzer(t *testing.T) {
 	spec.Run(t, "unit-new-analyzer/", testAnalyzerFactory, spec.Parallel(), spec.Report(report.Terminal{}))
-	for _, api := range api.Platform.Supported {
-		spec.Run(t, "unit-analyzer/"+api.String(), testAnalyzer(api.String()), spec.Parallel(), spec.Report(report.Terminal{}))
+	for _, platformAPI := range api.Platform.Supported {
+		spec.Run(t, "unit-analyzer/"+platformAPI.String(), testAnalyzer(platformAPI.String()), spec.Parallel(), spec.Report(report.Terminal{}))
 	}
 }
 
 func testAnalyzerFactory(t *testing.T, when spec.G, it spec.S) {
 	when("#NewAnalyzer", func() {
 		var (
-			analyzerFactory     *lifecycle.AnalyzerFactory
+			analyzerFactory     *phase.ConnectedFactory
 			fakeAPIVerifier     *testmock.MockBuildpackAPIVerifier
 			fakeCacheHandler    *testmock.MockCacheHandler
 			fakeConfigHandler   *testmock.MockConfigHandler
@@ -64,12 +63,12 @@ func testAnalyzerFactory(t *testing.T, when spec.G, it spec.S) {
 
 		it.After(func() {
 			mockController.Finish()
-			os.RemoveAll(tempDir)
+			_ = os.RemoveAll(tempDir)
 		})
 
 		when("platform api >= 0.8", func() {
 			it.Before(func() {
-				analyzerFactory = lifecycle.NewAnalyzerFactory(
+				analyzerFactory = phase.NewConnectedFactory(
 					api.Platform.Latest(),
 					fakeAPIVerifier,
 					fakeCacheHandler,
@@ -99,7 +98,16 @@ func testAnalyzerFactory(t *testing.T, when spec.G, it spec.S) {
 					t.Log("processes run image")
 					fakeImageHandler.EXPECT().InitImage("some-run-image-ref").Return(runImage, nil)
 
-					analyzer, err := analyzerFactory.NewAnalyzer([]string{"some-additional-tag"}, "some-cache-image-ref", "some-launch-cache-dir", "some-layers-dir", "some-output-image-ref", "some-previous-image-ref", "some-run-image-ref", false, logger)
+					analyzer, err := analyzerFactory.NewAnalyzer(platform.LifecycleInputs{
+						AdditionalTags:   []string{"some-additional-tag"},
+						CacheImageRef:    "some-cache-image-ref",
+						LaunchCacheDir:   "some-launch-cache-dir",
+						LayersDir:        "some-layers-dir",
+						OutputImageRef:   "some-output-image-ref",
+						PreviousImageRef: "some-previous-image-ref",
+						RunImageRef:      "some-run-image-ref",
+						SkipLayers:       false,
+					}, logger)
 					h.AssertNil(t, err)
 					h.AssertEq(t, analyzer.PreviousImage.Name(), previousImage.Name())
 					h.AssertEq(t, analyzer.RunImage.Name(), runImage.Name())
@@ -132,7 +140,16 @@ func testAnalyzerFactory(t *testing.T, when spec.G, it spec.S) {
 				t.Log("processes run image")
 				fakeImageHandler.EXPECT().InitImage("some-run-image-ref").Return(runImage, nil)
 
-				analyzer, err := analyzerFactory.NewAnalyzer([]string{"some-additional-tag"}, "some-cache-image-ref", "some-launch-cache-dir", "some-layers-dir", "some-output-image-ref", "some-previous-image-ref", "some-run-image-ref", false, logger)
+				analyzer, err := analyzerFactory.NewAnalyzer(platform.LifecycleInputs{
+					AdditionalTags:   []string{"some-additional-tag"},
+					CacheImageRef:    "some-cache-image-ref",
+					LaunchCacheDir:   "some-launch-cache-dir",
+					LayersDir:        "some-layers-dir",
+					OutputImageRef:   "some-output-image-ref",
+					PreviousImageRef: "some-previous-image-ref",
+					RunImageRef:      "some-run-image-ref",
+					SkipLayers:       false,
+				}, logger)
 				h.AssertNil(t, err)
 				h.AssertEq(t, analyzer.PreviousImage.Name(), previousImage.Name())
 				h.AssertEq(t, analyzer.RunImage.Name(), runImage.Name())
@@ -166,7 +183,16 @@ func testAnalyzerFactory(t *testing.T, when spec.G, it spec.S) {
 
 					launchCacheDir := filepath.Join(tempDir, "some-launch-cache-dir")
 					h.AssertNil(t, os.MkdirAll(launchCacheDir, 0777))
-					analyzer, err := analyzerFactory.NewAnalyzer([]string{"some-additional-tag"}, "some-cache-image-ref", launchCacheDir, "some-layers-dir", "some-output-image-ref", "some-previous-image-ref", "some-run-image-ref", false, logger)
+					analyzer, err := analyzerFactory.NewAnalyzer(platform.LifecycleInputs{
+						AdditionalTags:   []string{"some-additional-tag"},
+						CacheImageRef:    "some-cache-image-ref",
+						LaunchCacheDir:   launchCacheDir,
+						LayersDir:        "some-layers-dir",
+						OutputImageRef:   "some-output-image-ref",
+						PreviousImageRef: "some-previous-image-ref",
+						RunImageRef:      "some-run-image-ref",
+						SkipLayers:       false,
+					}, logger)
 					h.AssertNil(t, err)
 					h.AssertEq(t, analyzer.PreviousImage.Name(), previousImage.Name())
 					h.AssertEq(t, analyzer.RunImage.Name(), runImage.Name())
@@ -187,7 +213,16 @@ func testAnalyzerFactory(t *testing.T, when spec.G, it spec.S) {
 					fakeImageHandler.EXPECT().InitImage(gomock.Any())
 					fakeImageHandler.EXPECT().InitImage(gomock.Any())
 
-					analyzer, err := analyzerFactory.NewAnalyzer([]string{"some-additional-tag"}, "some-cache-image-ref", "some-launch-cache-dir", "some-layers-dir", "some-output-image-ref", "some-previous-image-ref", "some-run-image-ref", true, logger)
+					analyzer, err := analyzerFactory.NewAnalyzer(platform.LifecycleInputs{
+						AdditionalTags:   []string{"some-additional-tag"},
+						CacheImageRef:    "some-cache-image-ref",
+						LaunchCacheDir:   "some-launch-cache-dir",
+						LayersDir:        "some-layers-dir",
+						OutputImageRef:   "some-output-image-ref",
+						PreviousImageRef: "some-previous-image-ref",
+						RunImageRef:      "some-run-image-ref",
+						SkipLayers:       true,
+					}, logger)
 					h.AssertNil(t, err)
 
 					_, ok := analyzer.SBOMRestorer.(*layer.NopSBOMRestorer)
@@ -198,7 +233,7 @@ func testAnalyzerFactory(t *testing.T, when spec.G, it spec.S) {
 
 		when("platform api = 0.7", func() {
 			it.Before(func() {
-				analyzerFactory = lifecycle.NewAnalyzerFactory(
+				analyzerFactory = phase.NewConnectedFactory(
 					api.MustParse("0.7"),
 					fakeAPIVerifier,
 					fakeCacheHandler,
@@ -223,7 +258,16 @@ func testAnalyzerFactory(t *testing.T, when spec.G, it spec.S) {
 				t.Log("processes run image")
 				fakeImageHandler.EXPECT().InitImage("some-run-image-ref").Return(runImage, nil)
 
-				analyzer, err := analyzerFactory.NewAnalyzer([]string{"some-additional-tag"}, "some-cache-image-ref", "some-launch-cache-dir", "some-layers-dir", "some-output-image-ref", "some-previous-image-ref", "some-run-image-ref", false, logger)
+				analyzer, err := analyzerFactory.NewAnalyzer(platform.LifecycleInputs{
+					AdditionalTags:   []string{"some-additional-tag"},
+					CacheImageRef:    "some-cache-image-ref",
+					LaunchCacheDir:   "some-launch-cache-dir",
+					LayersDir:        "some-layers-dir",
+					OutputImageRef:   "some-output-image-ref",
+					PreviousImageRef: "some-previous-image-ref",
+					RunImageRef:      "some-run-image-ref",
+					SkipLayers:       true,
+				}, logger)
 				h.AssertNil(t, err)
 				h.AssertEq(t, analyzer.PreviousImage.Name(), previousImage.Name())
 				h.AssertEq(t, analyzer.RunImage.Name(), runImage.Name())
@@ -254,7 +298,16 @@ func testAnalyzerFactory(t *testing.T, when spec.G, it spec.S) {
 
 					launchCacheDir := filepath.Join(tempDir, "some-launch-cache-dir")
 					h.AssertNil(t, os.MkdirAll(launchCacheDir, 0777))
-					analyzer, err := analyzerFactory.NewAnalyzer([]string{"some-additional-tag"}, "some-cache-image-ref", launchCacheDir, "some-layers-dir", "some-output-image-ref", "some-previous-image-ref", "some-run-image-ref", false, logger)
+					analyzer, err := analyzerFactory.NewAnalyzer(platform.LifecycleInputs{
+						AdditionalTags:   []string{"some-additional-tag"},
+						CacheImageRef:    "some-cache-image-ref",
+						LaunchCacheDir:   launchCacheDir,
+						LayersDir:        "some-layers-dir",
+						OutputImageRef:   "some-output-image-ref",
+						PreviousImageRef: "some-previous-image-ref",
+						RunImageRef:      "some-run-image-ref",
+						SkipLayers:       true,
+					}, logger)
 					h.AssertNil(t, err)
 					h.AssertEq(t, analyzer.PreviousImage.Name(), previousImage.Name())
 					h.AssertEq(t, analyzer.RunImage.Name(), runImage.Name())
@@ -267,14 +320,14 @@ func testAnalyzerFactory(t *testing.T, when spec.G, it spec.S) {
 func testAnalyzer(platformAPI string) func(t *testing.T, when spec.G, it spec.S) {
 	return func(t *testing.T, when spec.G, it spec.S) {
 		var (
-			cacheDir     string
-			layersDir    string
-			tmpDir       string
-			analyzer     *lifecycle.Analyzer
-			image        *fakes.Image
-			mockCtrl     *gomock.Controller
-			sbomRestorer *testmock.MockSBOMRestorer
-			testCache    lifecycle.Cache
+			cacheDir      string
+			layersDir     string
+			tmpDir        string
+			analyzer      *phase.Analyzer
+			previousImage *fakes.Image
+			mockCtrl      *gomock.Controller
+			sbomRestorer  *testmock.MockSBOMRestorer
+			testCache     phase.Cache
 		)
 
 		it.Before(func() {
@@ -292,7 +345,7 @@ func testAnalyzer(platformAPI string) func(t *testing.T, when spec.G, it spec.S)
 			testCache, err = cache.NewVolumeCache(cacheDir)
 			h.AssertNil(t, err)
 
-			image = fakes.NewImage("image-repo-name", "", local.IDIdentifier{
+			previousImage = fakes.NewImage("image-repo-name", "", local.IDIdentifier{
 				ImageID: "s0m3D1g3sT",
 			})
 
@@ -303,8 +356,8 @@ func testAnalyzer(platformAPI string) func(t *testing.T, when spec.G, it spec.S)
 			sbomRestorer = testmock.NewMockSBOMRestorer(mockCtrl)
 
 			h.AssertNil(t, err)
-			analyzer = &lifecycle.Analyzer{
-				PreviousImage: image,
+			analyzer = &phase.Analyzer{
+				PreviousImage: previousImage,
 				Logger:        &discardLogger,
 				SBOMRestorer:  sbomRestorer,
 				PlatformAPI:   api.MustParse(platformAPI),
@@ -320,7 +373,7 @@ func testAnalyzer(platformAPI string) func(t *testing.T, when spec.G, it spec.S)
 			h.AssertNil(t, os.RemoveAll(tmpDir))
 			h.AssertNil(t, os.RemoveAll(layersDir))
 			h.AssertNil(t, os.RemoveAll(cacheDir))
-			h.AssertNil(t, image.Cleanup())
+			h.AssertNil(t, previousImage.Cleanup())
 			mockCtrl.Finish()
 		})
 
@@ -339,7 +392,7 @@ func testAnalyzer(platformAPI string) func(t *testing.T, when spec.G, it spec.S)
 			when("previous image exists", func() {
 				it.Before(func() {
 					metadata := h.MustReadFile(t, filepath.Join("testdata", "analyzer", "app_metadata.json"))
-					h.AssertNil(t, image.SetLabel("io.buildpacks.lifecycle.metadata", string(metadata)))
+					h.AssertNil(t, previousImage.SetLabel("io.buildpacks.lifecycle.metadata", string(metadata)))
 					h.AssertNil(t, json.Unmarshal(metadata, &expectedAppMetadata))
 				})
 
@@ -370,7 +423,7 @@ func testAnalyzer(platformAPI string) func(t *testing.T, when spec.G, it spec.S)
 
 			when("previous image not found", func() {
 				it.Before(func() {
-					h.AssertNil(t, image.Delete())
+					h.AssertNil(t, previousImage.Delete())
 				})
 
 				it("returns a nil image in the analyzed metadata", func() {
@@ -384,7 +437,7 @@ func testAnalyzer(platformAPI string) func(t *testing.T, when spec.G, it spec.S)
 
 			when("previous image does not have metadata label", func() {
 				it.Before(func() {
-					h.AssertNil(t, image.SetLabel("io.buildpacks.lifecycle.metadata", ""))
+					h.AssertNil(t, previousImage.SetLabel("io.buildpacks.lifecycle.metadata", ""))
 				})
 
 				it("returns empty analyzed metadata", func() {
@@ -396,7 +449,7 @@ func testAnalyzer(platformAPI string) func(t *testing.T, when spec.G, it spec.S)
 
 			when("previous image has incompatible metadata", func() {
 				it.Before(func() {
-					h.AssertNil(t, image.SetLabel("io.buildpacks.lifecycle.metadata", `{["bad", "metadata"]}`))
+					h.AssertNil(t, previousImage.SetLabel("io.buildpacks.lifecycle.metadata", `{["bad", "metadata"]}`))
 				})
 
 				it("returns empty analyzed metadata", func() {
@@ -409,12 +462,12 @@ func testAnalyzer(platformAPI string) func(t *testing.T, when spec.G, it spec.S)
 			when("previous image has an SBOM layer digest in the analyzed metadata", func() {
 				it.Before(func() {
 					metadata := fmt.Sprintf(`{"sbom": {"sha":"%s"}}`, "some-digest")
-					h.AssertNil(t, image.SetLabel("io.buildpacks.lifecycle.metadata", metadata))
+					h.AssertNil(t, previousImage.SetLabel("io.buildpacks.lifecycle.metadata", metadata))
 					h.AssertNil(t, json.Unmarshal([]byte(metadata), &expectedAppMetadata))
 				})
 
 				it("calls the SBOM restorer with the SBOM layer digest", func() {
-					sbomRestorer.EXPECT().RestoreFromPrevious(image, "some-digest")
+					sbomRestorer.EXPECT().RestoreFromPrevious(previousImage, "some-digest")
 					_, err := analyzer.Analyze()
 					h.AssertNil(t, err)
 				})
@@ -422,7 +475,7 @@ func testAnalyzer(platformAPI string) func(t *testing.T, when spec.G, it spec.S)
 
 			when("run image is provided", func() {
 				it.Before(func() {
-					analyzer.RunImage = image
+					analyzer.RunImage = previousImage
 				})
 
 				it("returns the run image digest in the analyzed metadata", func() {
@@ -432,13 +485,13 @@ func testAnalyzer(platformAPI string) func(t *testing.T, when spec.G, it spec.S)
 					h.AssertEq(t, md.RunImage.Reference, "s0m3D1g3sT")
 				})
 				it("populates target metadata from the run image", func() {
-					h.AssertNil(t, image.SetLabel("io.buildpacks.base.id", "id software"))
-					h.AssertNil(t, image.SetOS("windows"))
-					h.AssertNil(t, image.SetOSVersion("95"))
-					h.AssertNil(t, image.SetArchitecture("Pentium"))
-					h.AssertNil(t, image.SetVariant("MMX"))
-					h.AssertNil(t, image.SetLabel("io.buildpacks.distro.name", "moobuntu"))
-					h.AssertNil(t, image.SetLabel("io.buildpacks.distro.version", "Helpful Holstein"))
+					h.AssertNil(t, previousImage.SetLabel("io.buildpacks.base.id", "id software"))
+					h.AssertNil(t, previousImage.SetOS("windows"))
+					h.AssertNil(t, previousImage.SetOSVersion("95"))
+					h.AssertNil(t, previousImage.SetArchitecture("Pentium"))
+					h.AssertNil(t, previousImage.SetVariant("MMX"))
+					h.AssertNil(t, previousImage.SetLabel("io.buildpacks.distro.name", "moobuntu"))
+					h.AssertNil(t, previousImage.SetLabel("io.buildpacks.distro.version", "Helpful Holstein"))
 
 					md, err := analyzer.Analyze()
 					h.AssertNil(t, err)
diff --git a/builder_test.go b/phase/builder_test.go
similarity index 99%
rename from builder_test.go
rename to phase/builder_test.go
index 00971bc3..f4778761 100644
--- a/builder_test.go
+++ b/phase/builder_test.go
@@ -1,4 +1,4 @@
-package lifecycle_test
+package phase_test
 
 import (
 	"bytes"
@@ -17,16 +17,16 @@ import (
 	"github.com/sclevine/spec"
 	"github.com/sclevine/spec/report"
 
-	"github.com/buildpacks/lifecycle"
 	"github.com/buildpacks/lifecycle/api"
 	"github.com/buildpacks/lifecycle/buildpack"
 	"github.com/buildpacks/lifecycle/env"
 	"github.com/buildpacks/lifecycle/launch"
 	"github.com/buildpacks/lifecycle/layers"
 	llog "github.com/buildpacks/lifecycle/log"
+	"github.com/buildpacks/lifecycle/phase"
+	"github.com/buildpacks/lifecycle/phase/testmock"
 	"github.com/buildpacks/lifecycle/platform/files"
 	h "github.com/buildpacks/lifecycle/testhelpers"
-	"github.com/buildpacks/lifecycle/testmock"
 )
 
 func TestBuilder(t *testing.T) {
@@ -36,7 +36,7 @@ func TestBuilder(t *testing.T) {
 func testBuilder(t *testing.T, when spec.G, it spec.S) {
 	var (
 		mockCtrl       *gomock.Controller
-		builder        *lifecycle.Builder
+		builder        *phase.Builder
 		tmpDir         string
 		appDir         string
 		layersDir      string
@@ -61,7 +61,7 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) {
 		h.Mkdir(t, layersDir, appDir, filepath.Join(platformDir, "env"))
 		stdout, stderr = &bytes.Buffer{}, &bytes.Buffer{}
 
-		builder = &lifecycle.Builder{
+		builder = &phase.Builder{
 			AppDir:        appDir,
 			LayersDir:     layersDir,
 			PlatformDir:   platformDir,
diff --git a/cache_test.go b/phase/cache_test.go
similarity index 96%
rename from cache_test.go
rename to phase/cache_test.go
index dce8b66b..b97ccf8a 100644
--- a/cache_test.go
+++ b/phase/cache_test.go
@@ -1,4 +1,4 @@
-package lifecycle_test
+package phase_test
 
 import (
 	"errors"
@@ -15,13 +15,13 @@ import (
 	"github.com/sclevine/spec"
 	"github.com/sclevine/spec/report"
 
-	"github.com/buildpacks/lifecycle"
 	"github.com/buildpacks/lifecycle/api"
 	"github.com/buildpacks/lifecycle/buildpack"
 	"github.com/buildpacks/lifecycle/cache"
 	"github.com/buildpacks/lifecycle/layers"
+	"github.com/buildpacks/lifecycle/phase"
+	"github.com/buildpacks/lifecycle/phase/testmock"
 	h "github.com/buildpacks/lifecycle/testhelpers"
-	"github.com/buildpacks/lifecycle/testmock"
 )
 
 func TestCache(t *testing.T) {
@@ -32,12 +32,12 @@ func testCache(t *testing.T, when spec.G, it spec.S) {
 	when("#Cache", func() {
 		var (
 			cacheDir     string
-			exporter     *lifecycle.Exporter
+			exporter     *phase.Exporter
 			layerFactory *testmock.MockLayerFactory
 			layersDir    string
 			logHandler   *memory.Handler
 			mockCtrl     *gomock.Controller
-			testCache    lifecycle.Cache
+			testCache    phase.Cache
 			tmpDir       string
 		)
 
@@ -60,7 +60,7 @@ func testCache(t *testing.T, when spec.G, it spec.S) {
 			level, err := log.ParseLevel("info")
 			h.AssertNil(t, err)
 
-			exporter = &lifecycle.Exporter{
+			exporter = &phase.Exporter{
 				PlatformAPI: api.Platform.Latest(),
 				Buildpacks: []buildpack.GroupElement{
 					{ID: "buildpack.id", API: api.Buildpack.Latest().String()},
@@ -288,7 +288,7 @@ func testCache(t *testing.T, when spec.G, it spec.S) {
 	})
 }
 
-func assertCacheHasLayer(t *testing.T, cache lifecycle.Cache, id string) {
+func assertCacheHasLayer(t *testing.T, cache phase.Cache, id string) {
 	t.Helper()
 
 	rc, err := cache.RetrieveLayer(testLayerDigest(id))
@@ -299,7 +299,7 @@ func assertCacheHasLayer(t *testing.T, cache lifecycle.Cache, id string) {
 	h.AssertEq(t, string(contents), testLayerContents(id))
 }
 
-func initializeCache(t *testing.T, exporter *lifecycle.Exporter, testCache *lifecycle.Cache, cacheDir, layersDir, metadataTemplate string) {
+func initializeCache(t *testing.T, exporter *phase.Exporter, testCache *phase.Cache, cacheDir, layersDir, metadataTemplate string) {
 	previousCache, err := cache.NewVolumeCache(cacheDir)
 	h.AssertNil(t, err)
 
diff --git a/detector_test.go b/phase/detector_test.go
similarity index 95%
rename from detector_test.go
rename to phase/detector_test.go
index 9026c91f..3663c2d1 100644
--- a/detector_test.go
+++ b/phase/detector_test.go
@@ -1,4 +1,4 @@
-package lifecycle_test
+package phase_test
 
 import (
 	"errors"
@@ -15,13 +15,14 @@ import (
 	"github.com/sclevine/spec"
 	"github.com/sclevine/spec/report"
 
-	"github.com/buildpacks/lifecycle"
 	"github.com/buildpacks/lifecycle/api"
 	"github.com/buildpacks/lifecycle/buildpack"
 	"github.com/buildpacks/lifecycle/log"
+	"github.com/buildpacks/lifecycle/phase"
+	"github.com/buildpacks/lifecycle/phase/testmock"
+	"github.com/buildpacks/lifecycle/platform"
 	"github.com/buildpacks/lifecycle/platform/files"
 	h "github.com/buildpacks/lifecycle/testhelpers"
-	"github.com/buildpacks/lifecycle/testmock"
 )
 
 func TestDetector(t *testing.T) {
@@ -37,7 +38,7 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
 		dirStore      *testmock.MockDirStore
 		logger        log.LoggerHandlerWithLevel
 
-		detectorFactory *lifecycle.DetectorFactory
+		detectorFactory *phase.HermeticFactory
 	)
 
 	it.Before(func() {
@@ -48,7 +49,7 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
 		dirStore = testmock.NewMockDirStore(mockController)
 		logger = log.NewDefaultLogger(io.Discard)
 
-		detectorFactory = lifecycle.NewDetectorFactory(
+		detectorFactory = phase.NewHermeticFactory(
 			api.Platform.Latest(),
 			apiVerifier,
 			configHandler,
@@ -61,6 +62,10 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
 	})
 
 	when("#NewDetector", func() {
+		it.Before(func() {
+			configHandler.EXPECT().ReadAnalyzed("some-analyzed-path", gomock.Any()).Return(files.Analyzed{}, nil).AnyTimes()
+		})
+
 		it("configures the detector", func() {
 			order := buildpack.Order{
 				buildpack.Group{Group: []buildpack.GroupElement{{ID: "A", Version: "v1"}}},
@@ -71,9 +76,14 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
 			bpA1 := &buildpack.BpDescriptor{WithAPI: "0.2"}
 			dirStore.EXPECT().Lookup(buildpack.KindBuildpack, "A", "v1").Return(bpA1, nil)
 			apiVerifier.EXPECT().VerifyBuildpackAPI(buildpack.KindBuildpack, "A@v1", "0.2", logger)
-			amd := files.Analyzed{}
 
-			detector, err := detectorFactory.NewDetector(amd, "some-app-dir", "some-build-config-dir", "some-order-path", "some-platform-dir", logger)
+			detector, err := detectorFactory.NewDetector(platform.LifecycleInputs{
+				AnalyzedPath:   "some-analyzed-path",
+				AppDir:         "some-app-dir",
+				BuildConfigDir: "some-build-config-dir",
+				OrderPath:      "some-order-path",
+				PlatformDir:    "some-platform-dir",
+			}, logger)
 			h.AssertNil(t, err)
 
 			h.AssertEq(t, detector.AppDir, "some-app-dir")
@@ -82,7 +92,7 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
 			h.AssertEq(t, detector.HasExtensions, false)
 			h.AssertEq(t, detector.Order, order)
 			h.AssertEq(t, detector.PlatformDir, "some-platform-dir")
-			_, ok := detector.Resolver.(*lifecycle.DefaultDetectResolver)
+			_, ok := detector.Resolver.(*phase.DefaultDetectResolver)
 			h.AssertEq(t, ok, true)
 			h.AssertNotNil(t, detector.Runs)
 		})
@@ -133,8 +143,13 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
 				dirStore.EXPECT().Lookup(buildpack.KindExtension, "D", "v1").Return(extD1, nil)
 				apiVerifier.EXPECT().VerifyBuildpackAPI(buildpack.KindExtension, "D@v1", "some-other-api-version", logger)
 
-				amd := files.Analyzed{}
-				detector, err := detectorFactory.NewDetector(amd, "some-app-dir", "some-build-config-dir", "some-order-path", "some-platform-dir", logger)
+				detector, err := detectorFactory.NewDetector(platform.LifecycleInputs{
+					AnalyzedPath:   "some-analyzed-path",
+					AppDir:         "some-app-dir",
+					BuildConfigDir: "some-build-config-dir",
+					OrderPath:      "some-order-path",
+					PlatformDir:    "some-platform-dir",
+				}, logger)
 				h.AssertNil(t, err)
 
 				h.AssertEq(t, detector.AppDir, "some-app-dir")
@@ -142,7 +157,7 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
 				h.AssertEq(t, detector.HasExtensions, true)
 				h.AssertEq(t, detector.Order, expectedOrder)
 				h.AssertEq(t, detector.PlatformDir, "some-platform-dir")
-				_, ok := detector.Resolver.(*lifecycle.DefaultDetectResolver)
+				_, ok := detector.Resolver.(*phase.DefaultDetectResolver)
 				h.AssertEq(t, ok, true)
 				h.AssertNotNil(t, detector.Runs)
 			})
@@ -151,23 +166,22 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
 
 	when(".Detect", func() {
 		var (
-			detector *lifecycle.Detector
+			detector *phase.Detector
 			executor *testmock.MockDetectExecutor
 			resolver *testmock.MockDetectResolver
 		)
 
 		it.Before(func() {
-			configHandler.EXPECT().ReadOrder(gomock.Any()).Return(buildpack.Order{}, buildpack.Order{}, nil)
-			amd := files.Analyzed{}
+			configHandler.EXPECT().ReadAnalyzed("some-analyzed-path", gomock.Any()).Return(files.Analyzed{}, nil).AnyTimes()
+			configHandler.EXPECT().ReadOrder("some-order-path").Return(buildpack.Order{}, buildpack.Order{}, nil)
 			var err error
-			detector, err = detectorFactory.NewDetector(
-				amd,
-				"some-app-dir",
-				"some-build-config-dir",
-				"some-order-path",
-				"some-platform-dir",
-				logger,
-			)
+			detector, err = detectorFactory.NewDetector(platform.LifecycleInputs{
+				AnalyzedPath:   "some-analyzed-path",
+				AppDir:         "some-app-dir",
+				BuildConfigDir: "some-build-config-dir",
+				OrderPath:      "some-order-path",
+				PlatformDir:    "some-platform-dir",
+			}, logger)
 			h.AssertNil(t, err)
 			// override factory-provided services
 			executor = testmock.NewMockDetectExecutor(mockController)
@@ -317,7 +331,7 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
 			).Return(
 				[]buildpack.GroupElement{},
 				[]files.BuildPlanEntry{},
-				lifecycle.ErrFailedDetection,
+				phase.ErrFailedDetection,
 			)
 
 			// bpA1 already done
@@ -333,7 +347,7 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
 			).Return(
 				[]buildpack.GroupElement{},
 				[]files.BuildPlanEntry{},
-				lifecycle.ErrFailedDetection,
+				phase.ErrFailedDetection,
 			).After(firstResolve)
 
 			// bpA1 already done
@@ -353,7 +367,7 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
 			).Return(
 				[]buildpack.GroupElement{},
 				[]files.BuildPlanEntry{},
-				lifecycle.ErrFailedDetection,
+				phase.ErrFailedDetection,
 			).After(secondResolve)
 
 			// bpA1 already done
@@ -369,7 +383,7 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
 			).Return(
 				[]buildpack.GroupElement{},
 				[]files.BuildPlanEntry{},
-				lifecycle.ErrFailedDetection,
+				phase.ErrFailedDetection,
 			).After(thirdResolve)
 
 			// bpA1 already done
@@ -387,7 +401,7 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
 			).Return(
 				[]buildpack.GroupElement{},
 				[]files.BuildPlanEntry{},
-				lifecycle.ErrFailedDetection,
+				phase.ErrFailedDetection,
 			).After(fourthResolve)
 
 			order := buildpack.Order{
@@ -490,7 +504,7 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
 			).Return(
 				[]buildpack.GroupElement{},
 				[]files.BuildPlanEntry{},
-				lifecycle.ErrFailedDetection,
+				phase.ErrFailedDetection,
 			)
 
 			// bpA1 already done
@@ -506,7 +520,7 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
 			).Return(
 				[]buildpack.GroupElement{},
 				[]files.BuildPlanEntry{},
-				lifecycle.ErrFailedDetection,
+				phase.ErrFailedDetection,
 			).After(firstResolve)
 
 			// bpA1 already done
@@ -526,7 +540,7 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
 			).Return(
 				[]buildpack.GroupElement{},
 				[]files.BuildPlanEntry{},
-				lifecycle.ErrFailedDetection,
+				phase.ErrFailedDetection,
 			).After(secondResolve)
 
 			// bpA1 already done
@@ -681,7 +695,7 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
 					resolver.EXPECT().Resolve(group, detector.Runs).Return(
 						[]buildpack.GroupElement{},
 						[]files.BuildPlanEntry{},
-						lifecycle.ErrBuildpack,
+						phase.ErrBuildpack,
 					)
 
 					detector.Order = buildpack.Order{{Group: group}}
@@ -706,7 +720,7 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
 					resolver.EXPECT().Resolve(group, detector.Runs).Return(
 						[]buildpack.GroupElement{},
 						[]files.BuildPlanEntry{},
-						lifecycle.ErrFailedDetection,
+						phase.ErrFailedDetection,
 					)
 
 					detector.Order = buildpack.Order{{Group: group}}
@@ -911,7 +925,7 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
 				).Return(
 					[]buildpack.GroupElement{},
 					[]files.BuildPlanEntry{},
-					lifecycle.ErrFailedDetection,
+					phase.ErrFailedDetection,
 				)
 
 				executor.EXPECT().Detect(extB1, gomock.Any(), gomock.Any())
@@ -927,7 +941,7 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
 				).Return(
 					[]buildpack.GroupElement{},
 					[]files.BuildPlanEntry{},
-					lifecycle.ErrFailedDetection,
+					phase.ErrFailedDetection,
 				).After(firstResolve)
 
 				// bpA1 already done
@@ -941,7 +955,7 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
 				).Return(
 					[]buildpack.GroupElement{},
 					[]files.BuildPlanEntry{},
-					lifecycle.ErrFailedDetection,
+					phase.ErrFailedDetection,
 				).After(secondResolve)
 
 				// extA1 already done
@@ -957,7 +971,7 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
 				).Return(
 					[]buildpack.GroupElement{},
 					[]files.BuildPlanEntry{},
-					lifecycle.ErrFailedDetection,
+					phase.ErrFailedDetection,
 				).After(thirdResolve)
 
 				// extB1 already done
@@ -988,7 +1002,7 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
 					{Group: []buildpack.GroupElement{{ID: "B", Version: "v1"}}},
 				}
 
-				detector.Order = lifecycle.PrependExtensions(orderBp, orderExt)
+				detector.Order = phase.PrependExtensions(orderBp, orderExt)
 				group, _, err := detector.Detect()
 				h.AssertNil(t, err)
 
@@ -1000,7 +1014,7 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
 
 	when(".Resolve", func() {
 		var (
-			resolver   *lifecycle.DefaultDetectResolver
+			resolver   *phase.DefaultDetectResolver
 			logHandler *memory.Handler
 			logger     *log.DefaultLogger
 		)
@@ -1008,12 +1022,12 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
 		it.Before(func() {
 			logHandler = memory.New()
 			logger = &log.DefaultLogger{Logger: &apexlog.Logger{Handler: logHandler}}
-			resolver = lifecycle.NewDefaultDetectResolver(logger)
+			resolver = phase.NewDefaultDetectResolver(logger)
 		})
 
 		it("fails if the group is empty", func() {
 			_, _, err := resolver.Resolve([]buildpack.GroupElement{}, &sync.Map{})
-			if err != lifecycle.ErrFailedDetection {
+			if err != phase.ErrFailedDetection {
 				t.Fatalf("Unexpected error:\n%s\n", err)
 			}
 
@@ -1040,7 +1054,7 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
 			})
 
 			_, _, err := resolver.Resolve(group, detectRuns)
-			if err != lifecycle.ErrFailedDetection {
+			if err != phase.ErrFailedDetection {
 				t.Fatalf("Unexpected error:\n%s\n", err)
 			}
 
@@ -1070,7 +1084,7 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
 				})
 
 				_, _, err := resolver.Resolve(group, detectRuns)
-				if err != lifecycle.ErrFailedDetection {
+				if err != phase.ErrFailedDetection {
 					t.Fatalf("Unexpected error:\n%s\n", err)
 				}
 
@@ -1100,7 +1114,7 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
 			})
 
 			_, _, err := resolver.Resolve(group, detectRuns)
-			if err != lifecycle.ErrBuildpack {
+			if err != phase.ErrBuildpack {
 				t.Fatalf("Unexpected error:\n%s\n", err)
 			}
 
@@ -1133,7 +1147,7 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
 				})
 
 				_, _, err := resolver.Resolve(group, detectRuns)
-				if err != lifecycle.ErrFailedDetection {
+				if err != phase.ErrFailedDetection {
 					t.Fatalf("Unexpected error:\n%s\n", err)
 				}
 
@@ -1158,7 +1172,7 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
 				})
 
 				_, _, err := resolver.Resolve(group, detectRuns)
-				if err != lifecycle.ErrBuildpack {
+				if err != phase.ErrBuildpack {
 					t.Fatalf("Unexpected error:\n%s\n", err)
 				}
 
@@ -1315,7 +1329,7 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
 			})
 
 			_, _, err := resolver.Resolve(group, detectRuns)
-			if err != lifecycle.ErrFailedDetection {
+			if err != phase.ErrFailedDetection {
 				t.Fatalf("Unexpected error:\n%s\n", err)
 			}
 
@@ -1372,7 +1386,7 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
 			})
 
 			_, _, err := resolver.Resolve(group, detectRuns)
-			if err != lifecycle.ErrFailedDetection {
+			if err != phase.ErrFailedDetection {
 				t.Fatalf("Unexpected error:\n%s\n", err)
 			}
 
@@ -1603,7 +1617,7 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
 				buildpack.Group{Group: []buildpack.GroupElement{{ID: "D", Version: "v1", Extension: true, Optional: true}}},
 			}
 
-			newOrder := lifecycle.PrependExtensions(orderBp, orderExt)
+			newOrder := phase.PrependExtensions(orderBp, orderExt)
 
 			t.Log("returns the modified order")
 			if s := cmp.Diff(newOrder, buildpack.Order{
@@ -1639,7 +1653,7 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
 					buildpack.Group{Group: []buildpack.GroupElement{{ID: "B", Version: "v1"}}},
 				}
 
-				newOrder := lifecycle.PrependExtensions(orderBp, nil)
+				newOrder := phase.PrependExtensions(orderBp, nil)
 
 				if s := cmp.Diff(newOrder, buildpack.Order{
 					buildpack.Group{Group: []buildpack.GroupElement{{ID: "A", Version: "v1"}}},
diff --git a/exporter_test.go b/phase/exporter_test.go
similarity index 99%
rename from exporter_test.go
rename to phase/exporter_test.go
index f1e3ec23..96ee824d 100644
--- a/exporter_test.go
+++ b/phase/exporter_test.go
@@ -1,4 +1,4 @@
-package lifecycle_test
+package phase_test
 
 import (
 	"encoding/json"
@@ -20,15 +20,15 @@ import (
 	"github.com/sclevine/spec"
 	specreport "github.com/sclevine/spec/report"
 
-	"github.com/buildpacks/lifecycle"
 	"github.com/buildpacks/lifecycle/api"
 	"github.com/buildpacks/lifecycle/buildpack"
 	"github.com/buildpacks/lifecycle/internal/path"
 	"github.com/buildpacks/lifecycle/launch"
 	"github.com/buildpacks/lifecycle/layers"
+	"github.com/buildpacks/lifecycle/phase"
+	"github.com/buildpacks/lifecycle/phase/testmock"
 	"github.com/buildpacks/lifecycle/platform/files"
 	h "github.com/buildpacks/lifecycle/testhelpers"
-	"github.com/buildpacks/lifecycle/testmock"
 )
 
 func TestExporter(t *testing.T) {
@@ -37,13 +37,13 @@ func TestExporter(t *testing.T) {
 
 func testExporter(t *testing.T, when spec.G, it spec.S) {
 	var (
-		exporter     *lifecycle.Exporter
+		exporter     *phase.Exporter
 		tmpDir       string
 		mockCtrl     *gomock.Controller
 		layerFactory *testmock.MockLayerFactory
 		fakeAppImage *fakes.Image
 		logHandler   = memory.New()
-		opts         = lifecycle.ExportOptions{
+		opts         = phase.ExportOptions{
 			RunImageRef:     "run-image-reference",
 			AdditionalNames: []string{},
 		}
@@ -61,7 +61,7 @@ func testExporter(t *testing.T, when spec.G, it spec.S) {
 		launcherPath, err := filepath.Abs(filepath.Join("testdata", "exporter", "launcher"))
 		h.AssertNil(t, err)
 
-		opts.LauncherConfig = lifecycle.LauncherConfig{
+		opts.LauncherConfig = phase.LauncherConfig{
 			Path: launcherPath,
 			Metadata: files.LauncherMetadata{
 				Version: "1.2.3",
@@ -135,7 +135,7 @@ func testExporter(t *testing.T, when spec.G, it spec.S) {
 				return []layers.Layer{layer}, nil
 			}).AnyTimes()
 
-		exporter = &lifecycle.Exporter{
+		exporter = &phase.Exporter{
 			Buildpacks: []buildpack.GroupElement{
 				{ID: "buildpack.id", Version: "1.2.3", API: api.Buildpack.Latest().String()},
 				{ID: "other.buildpack.id", Version: "4.5.6", API: api.Buildpack.Latest().String(), Optional: false},
@@ -1409,7 +1409,7 @@ version = "4.5.6"
 					_, err := exporter.Export(opts)
 					h.AssertNil(t, err)
 
-					extensions := lifecycle.SBOMExtensions()
+					extensions := phase.SBOMExtensions()
 					for _, component := range []string{"lifecycle", "launcher"} {
 						for _, extension := range extensions {
 							assertLogEntry(t, logHandler, fmt.Sprintf("Did not find SBOM %s.%s", component, extension))
diff --git a/extender_test.go b/phase/extender_test.go
similarity index 93%
rename from extender_test.go
rename to phase/extender_test.go
index 7f6b5461..50e13ff7 100644
--- a/extender_test.go
+++ b/phase/extender_test.go
@@ -1,4 +1,4 @@
-package lifecycle_test
+package phase_test
 
 import (
 	"bytes"
@@ -19,14 +19,15 @@ import (
 	"github.com/sclevine/spec"
 	"github.com/sclevine/spec/report"
 
-	"github.com/buildpacks/lifecycle"
 	"github.com/buildpacks/lifecycle/api"
 	"github.com/buildpacks/lifecycle/buildpack"
 	"github.com/buildpacks/lifecycle/internal/extend"
 	llog "github.com/buildpacks/lifecycle/log"
+	"github.com/buildpacks/lifecycle/phase"
+	"github.com/buildpacks/lifecycle/phase/testmock"
+	"github.com/buildpacks/lifecycle/platform"
 	"github.com/buildpacks/lifecycle/platform/files"
 	h "github.com/buildpacks/lifecycle/testhelpers"
-	"github.com/buildpacks/lifecycle/testmock"
 )
 
 func TestExtender(t *testing.T) {
@@ -40,7 +41,7 @@ func testExtenderFactory(t *testing.T, when spec.G, it spec.S) {
 	when("#NewExtender", func() {
 		var (
 			mockController    *gomock.Controller
-			extenderFactory   *lifecycle.ExtenderFactory
+			extenderFactory   *phase.HermeticFactory
 			fakeAPIVerifier   *testmock.MockBuildpackAPIVerifier
 			fakeConfigHandler *testmock.MockConfigHandler
 			fakeDirStore      *testmock.MockDirStore
@@ -49,17 +50,17 @@ func testExtenderFactory(t *testing.T, when spec.G, it spec.S) {
 				BuildImage: &files.ImageIdentifier{Reference: "some-build-image-ref"},
 				RunImage:   &files.RunImage{Reference: "some-run-image-ref"},
 			}
-			extender *lifecycle.Extender
+			extender *phase.Extender
 			kind     = "build"
 		)
 
 		createExtender := func() {
 			fakeConfigHandler.EXPECT().ReadAnalyzed("some-analyzed-path", logger).Return(
 				analyzedMD, nil,
-			)
+			).AnyTimes()
 			fakeConfigHandler.EXPECT().ReadGroup("some-group-path").Return(
 				[]buildpack.GroupElement{}, []buildpack.GroupElement{{ID: "A", Version: "v1", API: "0.9"}}, nil,
-			)
+			).AnyTimes()
 			fakeDirStore.EXPECT().LookupExt("A", "v1").Return(&buildpack.ExtDescriptor{
 				WithAPI: "0.9",
 				Extension: buildpack.ExtInfo{
@@ -69,23 +70,21 @@ func testExtenderFactory(t *testing.T, when spec.G, it spec.S) {
 					},
 				},
 			}, nil).AnyTimes()
-			fakeAPIVerifier.EXPECT().VerifyBuildpackAPI(buildpack.KindExtension, "A@v1", "0.9", logger)
-
+			fakeAPIVerifier.EXPECT().VerifyBuildpackAPI(buildpack.KindExtension, "A@v1", "0.9", logger).AnyTimes()
 			fakeDockerfileApplier := testmock.NewMockDockerfileApplier(mockController)
+
 			var err error
-			extender, err = extenderFactory.NewExtender(
-				"some-analyzed-path",
-				"some-app-dir",
-				"some-extended-dir",
-				"some-generated-dir",
-				"some-group-path",
-				"some-layers-dir",
-				"some-platform-dir",
-				7*(24*time.Hour),
-				fakeDockerfileApplier,
-				kind,
-				logger,
-			)
+			extender, err = extenderFactory.NewExtender(platform.LifecycleInputs{
+				AnalyzedPath:   "some-analyzed-path",
+				AppDir:         "some-app-dir",
+				ExtendedDir:    "some-extended-dir",
+				GeneratedDir:   "some-generated-dir",
+				GroupPath:      "some-group-path",
+				LayersDir:      "some-layers-dir",
+				PlatformDir:    "some-platform-dir",
+				KanikoCacheTTL: 7 * (24 * time.Hour),
+				ExtendKind:     kind,
+			}, fakeDockerfileApplier, logger)
 			h.AssertNil(t, err)
 		}
 
@@ -94,7 +93,7 @@ func testExtenderFactory(t *testing.T, when spec.G, it spec.S) {
 			fakeAPIVerifier = testmock.NewMockBuildpackAPIVerifier(mockController)
 			fakeConfigHandler = testmock.NewMockConfigHandler(mockController)
 			fakeDirStore = testmock.NewMockDirStore(mockController)
-			extenderFactory = lifecycle.NewExtenderFactory(fakeAPIVerifier, fakeConfigHandler)
+			extenderFactory = phase.NewHermeticFactory(api.Platform.Latest(), fakeAPIVerifier, fakeConfigHandler, nil)
 
 			logger = &log.Logger{Handler: &discard.Handler{}}
 		})
@@ -150,7 +149,7 @@ func testExtenderFactory(t *testing.T, when spec.G, it spec.S) {
 func testExtender(t *testing.T, when spec.G, it spec.S) {
 	when("Extend", func() {
 		var (
-			extender              *lifecycle.Extender
+			extender              *phase.Extender
 			fakeDockerfileApplier *testmock.MockDockerfileApplier
 			mockCtrl              *gomock.Controller
 			extendedDir           string
@@ -176,7 +175,7 @@ func testExtender(t *testing.T, when spec.G, it spec.S) {
 				{ID: "B", Version: "v2", API: api.Buildpack.Latest().String()},
 			}
 			fakeDockerfileApplier = testmock.NewMockDockerfileApplier(mockCtrl)
-			extender = &lifecycle.Extender{
+			extender = &phase.Extender{
 				AppDir:            "some-app-dir",
 				ImageRef:          "some-image-tag@sha256:9412cff392ca11c0d7b9df015808c4e40aff218fbe324df6490b9552ba82be38",
 				ExtendedDir:       extendedDir,
@@ -395,7 +394,7 @@ func testExtender(t *testing.T, when spec.G, it spec.S) {
 							})
 						firstConfig := &v1.ConfigFile{Config: v1.Config{
 							User:   "1234:5678",
-							Labels: map[string]string{lifecycle.RebasableLabel: fmt.Sprintf("%t", tc.firstDockerfileRebasable)},
+							Labels: map[string]string{phase.RebasableLabel: fmt.Sprintf("%t", tc.firstDockerfileRebasable)},
 						}}
 						someFakeImage.ConfigFileReturnsOnCall(1, firstConfig, nil)
 
@@ -427,7 +426,7 @@ func testExtender(t *testing.T, when spec.G, it spec.S) {
 							})
 						secondConfig := &v1.ConfigFile{Config: v1.Config{
 							User:   "1234:5678",
-							Labels: map[string]string{lifecycle.RebasableLabel: fmt.Sprintf("%t", tc.secondDockerfileRebasable)},
+							Labels: map[string]string{phase.RebasableLabel: fmt.Sprintf("%t", tc.secondDockerfileRebasable)},
 						}}
 						someFakeImage.ConfigFileReturnsOnCall(2, secondConfig, nil)
 
diff --git a/generator_test.go b/phase/generator_test.go
similarity index 95%
rename from generator_test.go
rename to phase/generator_test.go
index d096e310..d6b91abe 100644
--- a/generator_test.go
+++ b/phase/generator_test.go
@@ -1,4 +1,4 @@
-package lifecycle_test
+package phase_test
 
 import (
 	"bytes"
@@ -16,13 +16,14 @@ import (
 	"github.com/sclevine/spec"
 	"github.com/sclevine/spec/report"
 
-	"github.com/buildpacks/lifecycle"
 	"github.com/buildpacks/lifecycle/api"
 	"github.com/buildpacks/lifecycle/buildpack"
 	llog "github.com/buildpacks/lifecycle/log"
+	"github.com/buildpacks/lifecycle/phase"
+	"github.com/buildpacks/lifecycle/phase/testmock"
+	"github.com/buildpacks/lifecycle/platform"
 	"github.com/buildpacks/lifecycle/platform/files"
 	h "github.com/buildpacks/lifecycle/testhelpers"
-	"github.com/buildpacks/lifecycle/testmock"
 )
 
 func TestGenerator(t *testing.T) {
@@ -35,7 +36,7 @@ func TestGenerator(t *testing.T) {
 func testGeneratorFactory(t *testing.T, when spec.G, it spec.S) {
 	when("#NewGenerator", func() {
 		var (
-			generatorFactory  *lifecycle.GeneratorFactory
+			generatorFactory  *phase.HermeticFactory
 			fakeAPIVerifier   *testmock.MockBuildpackAPIVerifier
 			fakeConfigHandler *testmock.MockConfigHandler
 			fakeDirStore      *testmock.MockDirStore
@@ -51,7 +52,8 @@ func testGeneratorFactory(t *testing.T, when spec.G, it spec.S) {
 			fakeDirStore = testmock.NewMockDirStore(mockController)
 			logger = &log.Logger{Handler: &discard.Handler{}}
 
-			generatorFactory = lifecycle.NewGeneratorFactory(
+			generatorFactory = phase.NewHermeticFactory(
+				api.Platform.Latest(),
 				fakeAPIVerifier,
 				fakeConfigHandler,
 				fakeDirStore,
@@ -66,7 +68,10 @@ func testGeneratorFactory(t *testing.T, when spec.G, it spec.S) {
 			fakeAPIVerifier.EXPECT().VerifyBuildpackAPI(buildpack.KindExtension, "A@v1", "0.9", logger)
 			fakeConfigHandler.EXPECT().ReadAnalyzed("some-analyzed-path", logger).Return(files.Analyzed{RunImage: &files.RunImage{Reference: "some-run-image-ref"}}, nil)
 			fakeConfigHandler.EXPECT().ReadRun("some-run-path", logger).Return(files.Run{Images: []files.RunImageForExport{{Image: "some-run-image"}}}, nil)
-
+			providedExtensions := []buildpack.GroupElement{
+				{ID: "A", Version: "v1", API: "0.9"},
+			}
+			fakeConfigHandler.EXPECT().ReadGroup("some-group-path").Return([]buildpack.GroupElement{}, providedExtensions, nil)
 			providedPlan := files.Plan{Entries: []files.BuildPlanEntry{
 				{
 					Providers: []buildpack.GroupElement{
@@ -77,20 +82,19 @@ func testGeneratorFactory(t *testing.T, when spec.G, it spec.S) {
 					},
 				},
 			}}
-			generator, err := generatorFactory.NewGenerator(
-				"some-analyzed-path",
-				"some-app-dir",
-				"some-build-config-dir",
-				[]buildpack.GroupElement{
-					{ID: "A", Version: "v1", API: "0.9"},
-				},
-				"some-output-dir",
-				providedPlan,
-				api.Platform.Latest(),
-				"some-platform-dir",
-				"some-run-path",
-				stdout, stderr,
-				logger,
+			fakeConfigHandler.EXPECT().ReadPlan("some-plan-path").Return(providedPlan, nil)
+
+			generator, err := generatorFactory.NewGenerator(platform.LifecycleInputs{
+				AnalyzedPath:   "some-analyzed-path",
+				AppDir:         "some-app-dir",
+				BuildConfigDir: "some-build-config-dir",
+				GroupPath:      "some-group-path",
+				GeneratedDir:   "some-output-dir",
+				PlanPath:       "some-plan-path",
+				PlatformAPI:    api.Platform.Latest(),
+				PlatformDir:    "some-platform-dir",
+				RunPath:        "some-run-path",
+			}, stdout, stderr, logger,
 			)
 			h.AssertNil(t, err)
 
@@ -98,7 +102,7 @@ func testGeneratorFactory(t *testing.T, when spec.G, it spec.S) {
 			h.AssertEq(t, generator.AppDir, "some-app-dir")
 			h.AssertNotNil(t, generator.DirStore)
 			h.AssertEq(t, generator.Extensions, []buildpack.GroupElement{
-				{ID: "A", Version: "v1", API: "0.9"},
+				{ID: "A", Version: "v1", API: "0.9", Extension: true},
 			})
 			h.AssertEq(t, generator.GeneratedDir, "some-output-dir")
 			h.AssertEq(t, generator.Logger, logger)
@@ -114,7 +118,7 @@ func testGeneratorFactory(t *testing.T, when spec.G, it spec.S) {
 func testGenerator(t *testing.T, when spec.G, it spec.S) {
 	var (
 		mockCtrl       *gomock.Controller
-		generator      *lifecycle.Generator
+		generator      *phase.Generator
 		tmpDir         string
 		appDir         string
 		generatedDir   string
@@ -143,7 +147,7 @@ func testGenerator(t *testing.T, when spec.G, it spec.S) {
 		h.Mkdir(t, generatedDir, appDir, filepath.Join(platformDir, "env"))
 		stdout, stderr = &bytes.Buffer{}, &bytes.Buffer{}
 
-		generator = &lifecycle.Generator{
+		generator = &phase.Generator{
 			AnalyzedMD: files.Analyzed{},
 			AppDir:     appDir,
 			DirStore:   dirStore,
diff --git a/handlers_test.go b/phase/handlers_test.go
similarity index 91%
rename from handlers_test.go
rename to phase/handlers_test.go
index 9241aa07..6518f67d 100644
--- a/handlers_test.go
+++ b/phase/handlers_test.go
@@ -1,4 +1,4 @@
-package lifecycle_test
+package phase_test
 
 import (
 	"os"
@@ -8,8 +8,8 @@ import (
 	"github.com/sclevine/spec"
 	"github.com/sclevine/spec/report"
 
-	"github.com/buildpacks/lifecycle"
 	"github.com/buildpacks/lifecycle/buildpack"
+	"github.com/buildpacks/lifecycle/phase"
 	h "github.com/buildpacks/lifecycle/testhelpers"
 )
 
@@ -80,7 +80,7 @@ group = [{id = "D", version = "v1"}]
 	when("#ReadGroup", func() {
 		it("returns a single group object with a buildpack group and an extensions group", func() {
 			h.Mkfile(t, groupTOMLContents, filepath.Join(tmpDir, "group.toml"))
-			foundGroup, err := lifecycle.ReadGroup(filepath.Join(tmpDir, "group.toml"))
+			foundGroup, err := phase.ReadGroup(filepath.Join(tmpDir, "group.toml"))
 			h.AssertNil(t, err)
 			h.AssertEq(t, foundGroup, buildpack.Group{
 				Group:           expectedGroupBp,
@@ -92,7 +92,7 @@ group = [{id = "D", version = "v1"}]
 	when("#ReadOrder", func() {
 		it("returns an ordering of buildpacks and an ordering of extensions", func() {
 			h.Mkfile(t, orderTOMLContents, filepath.Join(tmpDir, "order.toml"))
-			foundOrder, foundOrderExt, err := lifecycle.ReadOrder(filepath.Join(tmpDir, "order.toml"))
+			foundOrder, foundOrderExt, err := phase.ReadOrder(filepath.Join(tmpDir, "order.toml"))
 			h.AssertNil(t, err)
 			h.AssertEq(t, foundOrder, expectedOrderBp)
 			h.AssertEq(t, foundOrderExt, expectedOrderExt)
@@ -101,11 +101,11 @@ group = [{id = "D", version = "v1"}]
 
 	when("DefaultConfigHandler", func() {
 		var (
-			configHandler *lifecycle.DefaultConfigHandler
+			configHandler *phase.DefaultConfigHandler
 		)
 
 		it.Before(func() {
-			configHandler = lifecycle.NewConfigHandler()
+			configHandler = phase.NewConfigHandler()
 		})
 
 		when(".ReadGroup", func() {
diff --git a/rebaser_test.go b/phase/rebaser_test.go
similarity index 99%
rename from rebaser_test.go
rename to phase/rebaser_test.go
index 3337dce9..8791f56d 100644
--- a/rebaser_test.go
+++ b/phase/rebaser_test.go
@@ -1,4 +1,4 @@
-package lifecycle_test
+package phase_test
 
 import (
 	"encoding/json"
@@ -13,9 +13,9 @@ import (
 	"github.com/sclevine/spec"
 	"github.com/sclevine/spec/report"
 
-	"github.com/buildpacks/lifecycle"
 	"github.com/buildpacks/lifecycle/api"
 	"github.com/buildpacks/lifecycle/image"
+	"github.com/buildpacks/lifecycle/phase"
 	"github.com/buildpacks/lifecycle/platform"
 	"github.com/buildpacks/lifecycle/platform/files"
 	h "github.com/buildpacks/lifecycle/testhelpers"
@@ -27,7 +27,7 @@ func TestRebaser(t *testing.T) {
 
 func testRebaser(t *testing.T, when spec.G, it spec.S) {
 	var (
-		rebaser           *lifecycle.Rebaser
+		rebaser           *phase.Rebaser
 		fakeAppImage      *fakes.Image
 		fakeNewBaseImage  *fakes.Image
 		fakePreviousImage *fakes.Image
@@ -78,7 +78,7 @@ func testRebaser(t *testing.T, when spec.G, it spec.S) {
 
 		logHandler = memory.New()
 
-		rebaser = &lifecycle.Rebaser{
+		rebaser = &phase.Rebaser{
 			Logger:      &log.Logger{Handler: logHandler},
 			PlatformAPI: api.Platform.Latest(),
 		}
diff --git a/restorer_test.go b/phase/restorer_test.go
similarity index 98%
rename from restorer_test.go
rename to phase/restorer_test.go
index 0cef4b59..61066c88 100644
--- a/restorer_test.go
+++ b/phase/restorer_test.go
@@ -1,4 +1,4 @@
-package lifecycle_test
+package phase_test
 
 import (
 	"encoding/json"
@@ -14,16 +14,16 @@ import (
 	"github.com/sclevine/spec"
 	"github.com/sclevine/spec/report"
 
-	"github.com/buildpacks/lifecycle"
 	"github.com/buildpacks/lifecycle/api"
 	"github.com/buildpacks/lifecycle/buildpack"
 	"github.com/buildpacks/lifecycle/cache"
 	"github.com/buildpacks/lifecycle/internal/layer"
 	"github.com/buildpacks/lifecycle/layers"
+	"github.com/buildpacks/lifecycle/phase"
+	"github.com/buildpacks/lifecycle/phase/testmock"
 	"github.com/buildpacks/lifecycle/platform"
 	"github.com/buildpacks/lifecycle/platform/files"
 	h "github.com/buildpacks/lifecycle/testhelpers"
-	"github.com/buildpacks/lifecycle/testmock"
 )
 
 func TestRestorer(t *testing.T) {
@@ -46,8 +46,8 @@ func testRestorer(buildpackAPI, platformAPI string) func(t *testing.T, when spec
 				layersDir    string
 				logHandler   *memory.Handler
 				skipLayers   bool
-				testCache    lifecycle.Cache
-				restorer     *lifecycle.Restorer
+				testCache    phase.Cache
+				restorer     *phase.Restorer
 				mockCtrl     *gomock.Controller
 				sbomRestorer *testmock.MockSBOMRestorer
 			)
@@ -74,7 +74,7 @@ func testRestorer(buildpackAPI, platformAPI string) func(t *testing.T, when spec
 					sbomRestorer.EXPECT().RestoreToBuildpackLayers(gomock.Any()).AnyTimes()
 				}
 
-				restorer = &lifecycle.Restorer{
+				restorer = &phase.Restorer{
 					LayersDir: layersDir,
 					Logger:    &logger,
 					Buildpacks: []buildpack.GroupElement{
diff --git a/testdata/analyzer/app_metadata.json b/phase/testdata/analyzer/app_metadata.json
similarity index 100%
rename from testdata/analyzer/app_metadata.json
rename to phase/testdata/analyzer/app_metadata.json
diff --git a/testdata/analyzer/cache_inconsistent_metadata.json b/phase/testdata/analyzer/cache_inconsistent_metadata.json
similarity index 100%
rename from testdata/analyzer/cache_inconsistent_metadata.json
rename to phase/testdata/analyzer/cache_inconsistent_metadata.json
diff --git a/testdata/analyzer/cache_metadata.json b/phase/testdata/analyzer/cache_metadata.json
similarity index 100%
rename from testdata/analyzer/cache_metadata.json
rename to phase/testdata/analyzer/cache_metadata.json
diff --git a/testdata/cacher/invalid-layers/buildpack.id/cache-true-no-contents.toml b/phase/testdata/cacher/invalid-layers/buildpack.id/cache-true-no-contents.toml
similarity index 100%
rename from testdata/cacher/invalid-layers/buildpack.id/cache-true-no-contents.toml
rename to phase/testdata/cacher/invalid-layers/buildpack.id/cache-true-no-contents.toml
diff --git a/testdata/cacher/invalid-layers/buildpack.id/layer-1.toml b/phase/testdata/cacher/invalid-layers/buildpack.id/layer-1.toml
similarity index 100%
rename from testdata/cacher/invalid-layers/buildpack.id/layer-1.toml
rename to phase/testdata/cacher/invalid-layers/buildpack.id/layer-1.toml
diff --git a/testdata/cacher/invalid-layers/buildpack.id/layer-1/layer-1-file b/phase/testdata/cacher/invalid-layers/buildpack.id/layer-1/layer-1-file
similarity index 100%
rename from testdata/cacher/invalid-layers/buildpack.id/layer-1/layer-1-file
rename to phase/testdata/cacher/invalid-layers/buildpack.id/layer-1/layer-1-file
diff --git a/testdata/cacher/invalid-layers/buildpack.id/layer-2.toml b/phase/testdata/cacher/invalid-layers/buildpack.id/layer-2.toml
similarity index 100%
rename from testdata/cacher/invalid-layers/buildpack.id/layer-2.toml
rename to phase/testdata/cacher/invalid-layers/buildpack.id/layer-2.toml
diff --git a/testdata/cacher/invalid-layers/buildpack.id/layer-2/layer-2-file b/phase/testdata/cacher/invalid-layers/buildpack.id/layer-2/layer-2-file
similarity index 100%
rename from testdata/cacher/invalid-layers/buildpack.id/layer-2/layer-2-file
rename to phase/testdata/cacher/invalid-layers/buildpack.id/layer-2/layer-2-file
diff --git a/testdata/cacher/layers/buildpack.id/cache-false-layer.toml b/phase/testdata/cacher/layers/buildpack.id/cache-false-layer.toml
similarity index 100%
rename from testdata/cacher/layers/buildpack.id/cache-false-layer.toml
rename to phase/testdata/cacher/layers/buildpack.id/cache-false-layer.toml
diff --git a/testdata/cacher/layers/buildpack.id/cache-false-layer/file-from-cache-false-layer b/phase/testdata/cacher/layers/buildpack.id/cache-false-layer/file-from-cache-false-layer
similarity index 100%
rename from testdata/cacher/layers/buildpack.id/cache-false-layer/file-from-cache-false-layer
rename to phase/testdata/cacher/layers/buildpack.id/cache-false-layer/file-from-cache-false-layer
diff --git a/testdata/cacher/layers/buildpack.id/cache-true-layer.sha b/phase/testdata/cacher/layers/buildpack.id/cache-true-layer.sha
similarity index 100%
rename from testdata/cacher/layers/buildpack.id/cache-true-layer.sha
rename to phase/testdata/cacher/layers/buildpack.id/cache-true-layer.sha
diff --git a/testdata/cacher/layers/buildpack.id/cache-true-layer.toml b/phase/testdata/cacher/layers/buildpack.id/cache-true-layer.toml
similarity index 100%
rename from testdata/cacher/layers/buildpack.id/cache-true-layer.toml
rename to phase/testdata/cacher/layers/buildpack.id/cache-true-layer.toml
diff --git a/testdata/cacher/layers/buildpack.id/cache-true-layer/file-from-cache-true-layer b/phase/testdata/cacher/layers/buildpack.id/cache-true-layer/file-from-cache-true-layer
similarity index 100%
rename from testdata/cacher/layers/buildpack.id/cache-true-layer/file-from-cache-true-layer
rename to phase/testdata/cacher/layers/buildpack.id/cache-true-layer/file-from-cache-true-layer
diff --git a/testdata/cacher/layers/buildpack.id/cache-true-no-sha-layer.toml b/phase/testdata/cacher/layers/buildpack.id/cache-true-no-sha-layer.toml
similarity index 100%
rename from testdata/cacher/layers/buildpack.id/cache-true-no-sha-layer.toml
rename to phase/testdata/cacher/layers/buildpack.id/cache-true-no-sha-layer.toml
diff --git a/testdata/cacher/layers/buildpack.id/cache-true-no-sha-layer/file-from-cache-true-no-sha-layer b/phase/testdata/cacher/layers/buildpack.id/cache-true-no-sha-layer/file-from-cache-true-no-sha-layer
similarity index 100%
rename from testdata/cacher/layers/buildpack.id/cache-true-no-sha-layer/file-from-cache-true-no-sha-layer
rename to phase/testdata/cacher/layers/buildpack.id/cache-true-no-sha-layer/file-from-cache-true-no-sha-layer
diff --git a/testdata/cacher/layers/old.buildpack.id/cache-true-layer.sha b/phase/testdata/cacher/layers/old.buildpack.id/cache-true-layer.sha
similarity index 100%
rename from testdata/cacher/layers/old.buildpack.id/cache-true-layer.sha
rename to phase/testdata/cacher/layers/old.buildpack.id/cache-true-layer.sha
diff --git a/testdata/cacher/layers/old.buildpack.id/cache-true-layer.toml b/phase/testdata/cacher/layers/old.buildpack.id/cache-true-layer.toml
similarity index 100%
rename from testdata/cacher/layers/old.buildpack.id/cache-true-layer.toml
rename to phase/testdata/cacher/layers/old.buildpack.id/cache-true-layer.toml
diff --git a/testdata/cacher/layers/old.buildpack.id/cache-true-layer/file-from-cache-true-layer b/phase/testdata/cacher/layers/old.buildpack.id/cache-true-layer/file-from-cache-true-layer
similarity index 100%
rename from testdata/cacher/layers/old.buildpack.id/cache-true-layer/file-from-cache-true-layer
rename to phase/testdata/cacher/layers/old.buildpack.id/cache-true-layer/file-from-cache-true-layer
diff --git a/testdata/cacher/layers/other.buildpack.id/other-buildpack-layer.toml b/phase/testdata/cacher/layers/other.buildpack.id/other-buildpack-layer.toml
similarity index 100%
rename from testdata/cacher/layers/other.buildpack.id/other-buildpack-layer.toml
rename to phase/testdata/cacher/layers/other.buildpack.id/other-buildpack-layer.toml
diff --git a/testdata/cacher/layers/other.buildpack.id/other-buildpack-layer/other-buildpack-layer-file b/phase/testdata/cacher/layers/other.buildpack.id/other-buildpack-layer/other-buildpack-layer-file
similarity index 100%
rename from testdata/cacher/layers/other.buildpack.id/other-buildpack-layer/other-buildpack-layer-file
rename to phase/testdata/cacher/layers/other.buildpack.id/other-buildpack-layer/other-buildpack-layer-file
diff --git a/testdata/cacher/layers/sbom/cache/buildpack.id/cache-true-layer/bom.cdx.json b/phase/testdata/cacher/layers/sbom/cache/buildpack.id/cache-true-layer/bom.cdx.json
similarity index 100%
rename from testdata/cacher/layers/sbom/cache/buildpack.id/cache-true-layer/bom.cdx.json
rename to phase/testdata/cacher/layers/sbom/cache/buildpack.id/cache-true-layer/bom.cdx.json
diff --git a/testdata/cred_helpers/config1.json b/phase/testdata/cred_helpers/config1.json
similarity index 100%
rename from testdata/cred_helpers/config1.json
rename to phase/testdata/cred_helpers/config1.json
diff --git a/testdata/cred_helpers/config2.json b/phase/testdata/cred_helpers/config2.json
similarity index 100%
rename from testdata/cred_helpers/config2.json
rename to phase/testdata/cred_helpers/config2.json
diff --git a/testdata/exporter/app-slices/layers/app/.hidden.txt b/phase/testdata/exporter/app-slices/layers/app/.hidden.txt
similarity index 100%
rename from testdata/exporter/app-slices/layers/app/.hidden.txt
rename to phase/testdata/exporter/app-slices/layers/app/.hidden.txt
diff --git a/testdata/exporter/app-slices/layers/app/static/assets/config.txt b/phase/testdata/exporter/app-slices/layers/app/static/assets/config.txt
similarity index 100%
rename from testdata/exporter/app-slices/layers/app/static/assets/config.txt
rename to phase/testdata/exporter/app-slices/layers/app/static/assets/config.txt
diff --git a/testdata/exporter/app-slices/layers/app/static/assets/logo.svg b/phase/testdata/exporter/app-slices/layers/app/static/assets/logo.svg
similarity index 100%
rename from testdata/exporter/app-slices/layers/app/static/assets/logo.svg
rename to phase/testdata/exporter/app-slices/layers/app/static/assets/logo.svg
diff --git a/testdata/exporter/app-slices/layers/app/static/misc/resources/reports/numbers.csv b/phase/testdata/exporter/app-slices/layers/app/static/misc/resources/reports/numbers.csv
similarity index 100%
rename from testdata/exporter/app-slices/layers/app/static/misc/resources/reports/numbers.csv
rename to phase/testdata/exporter/app-slices/layers/app/static/misc/resources/reports/numbers.csv
diff --git a/testdata/exporter/app-slices/layers/app/static/misc/resources/reports/report.tps b/phase/testdata/exporter/app-slices/layers/app/static/misc/resources/reports/report.tps
similarity index 100%
rename from testdata/exporter/app-slices/layers/app/static/misc/resources/reports/report.tps
rename to phase/testdata/exporter/app-slices/layers/app/static/misc/resources/reports/report.tps
diff --git a/testdata/exporter/app-slices/layers/app/test_app.sh b/phase/testdata/exporter/app-slices/layers/app/test_app.sh
similarity index 100%
rename from testdata/exporter/app-slices/layers/app/test_app.sh
rename to phase/testdata/exporter/app-slices/layers/app/test_app.sh
diff --git a/testdata/exporter/app-slices/layers/buildpack.id/layer1.toml b/phase/testdata/exporter/app-slices/layers/buildpack.id/layer1.toml
similarity index 100%
rename from testdata/exporter/app-slices/layers/buildpack.id/layer1.toml
rename to phase/testdata/exporter/app-slices/layers/buildpack.id/layer1.toml
diff --git a/testdata/exporter/app-slices/layers/buildpack.id/layer1/file-from-layer-1 b/phase/testdata/exporter/app-slices/layers/buildpack.id/layer1/file-from-layer-1
similarity index 100%
rename from testdata/exporter/app-slices/layers/buildpack.id/layer1/file-from-layer-1
rename to phase/testdata/exporter/app-slices/layers/buildpack.id/layer1/file-from-layer-1
diff --git a/testdata/exporter/app-slices/layers/buildpack.id/layer2.toml b/phase/testdata/exporter/app-slices/layers/buildpack.id/layer2.toml
similarity index 100%
rename from testdata/exporter/app-slices/layers/buildpack.id/layer2.toml
rename to phase/testdata/exporter/app-slices/layers/buildpack.id/layer2.toml
diff --git a/testdata/exporter/app-slices/layers/buildpack.id/layer2/file-from-layer-2 b/phase/testdata/exporter/app-slices/layers/buildpack.id/layer2/file-from-layer-2
similarity index 100%
rename from testdata/exporter/app-slices/layers/buildpack.id/layer2/file-from-layer-2
rename to phase/testdata/exporter/app-slices/layers/buildpack.id/layer2/file-from-layer-2
diff --git a/testdata/exporter/app-slices/layers/config/metadata.toml b/phase/testdata/exporter/app-slices/layers/config/metadata.toml
similarity index 100%
rename from testdata/exporter/app-slices/layers/config/metadata.toml
rename to phase/testdata/exporter/app-slices/layers/config/metadata.toml
diff --git a/testdata/exporter/app-slices/layers/other.buildpack.id/local-reusable-layer.toml b/phase/testdata/exporter/app-slices/layers/other.buildpack.id/local-reusable-layer.toml
similarity index 100%
rename from testdata/exporter/app-slices/layers/other.buildpack.id/local-reusable-layer.toml
rename to phase/testdata/exporter/app-slices/layers/other.buildpack.id/local-reusable-layer.toml
diff --git a/testdata/exporter/app-slices/layers/other.buildpack.id/local-reusable-layer/layer5contents b/phase/testdata/exporter/app-slices/layers/other.buildpack.id/local-reusable-layer/layer5contents
similarity index 100%
rename from testdata/exporter/app-slices/layers/other.buildpack.id/local-reusable-layer/layer5contents
rename to phase/testdata/exporter/app-slices/layers/other.buildpack.id/local-reusable-layer/layer5contents
diff --git a/testdata/exporter/app-slices/layers/other.buildpack.id/new-launch-layer.toml b/phase/testdata/exporter/app-slices/layers/other.buildpack.id/new-launch-layer.toml
similarity index 100%
rename from testdata/exporter/app-slices/layers/other.buildpack.id/new-launch-layer.toml
rename to phase/testdata/exporter/app-slices/layers/other.buildpack.id/new-launch-layer.toml
diff --git a/testdata/exporter/app-slices/layers/other.buildpack.id/new-launch-layer/new-launch-layer-file b/phase/testdata/exporter/app-slices/layers/other.buildpack.id/new-launch-layer/new-launch-layer-file
similarity index 100%
rename from testdata/exporter/app-slices/layers/other.buildpack.id/new-launch-layer/new-launch-layer-file
rename to phase/testdata/exporter/app-slices/layers/other.buildpack.id/new-launch-layer/new-launch-layer-file
diff --git a/testdata/exporter/bad-layer/layers/app/metadata.toml b/phase/testdata/exporter/bad-layer/layers/app/metadata.toml
similarity index 100%
rename from testdata/exporter/bad-layer/layers/app/metadata.toml
rename to phase/testdata/exporter/bad-layer/layers/app/metadata.toml
diff --git a/testdata/exporter/bad-layer/layers/app/test_app.sh b/phase/testdata/exporter/bad-layer/layers/app/test_app.sh
similarity index 100%
rename from testdata/exporter/bad-layer/layers/app/test_app.sh
rename to phase/testdata/exporter/bad-layer/layers/app/test_app.sh
diff --git a/testdata/exporter/bad-layer/layers/buildpack.id/bad-layer.toml b/phase/testdata/exporter/bad-layer/layers/buildpack.id/bad-layer.toml
similarity index 100%
rename from testdata/exporter/bad-layer/layers/buildpack.id/bad-layer.toml
rename to phase/testdata/exporter/bad-layer/layers/buildpack.id/bad-layer.toml
diff --git a/testdata/exporter/bad-layer/layers/config/metadata.toml b/phase/testdata/exporter/bad-layer/layers/config/metadata.toml
similarity index 100%
rename from testdata/exporter/bad-layer/layers/config/metadata.toml
rename to phase/testdata/exporter/bad-layer/layers/config/metadata.toml
diff --git a/testdata/exporter/build-metadata/bad-layers/buildpack.id/build.toml b/phase/testdata/exporter/build-metadata/bad-layers/buildpack.id/build.toml
similarity index 100%
rename from testdata/exporter/build-metadata/bad-layers/buildpack.id/build.toml
rename to phase/testdata/exporter/build-metadata/bad-layers/buildpack.id/build.toml
diff --git a/testdata/exporter/build-metadata/bad-layers/config/metadata.toml b/phase/testdata/exporter/build-metadata/bad-layers/config/metadata.toml
similarity index 100%
rename from testdata/exporter/build-metadata/bad-layers/config/metadata.toml
rename to phase/testdata/exporter/build-metadata/bad-layers/config/metadata.toml
diff --git a/testdata/exporter/build-metadata/bad-layers/other.buildpack.id/build.toml b/phase/testdata/exporter/build-metadata/bad-layers/other.buildpack.id/build.toml
similarity index 100%
rename from testdata/exporter/build-metadata/bad-layers/other.buildpack.id/build.toml
rename to phase/testdata/exporter/build-metadata/bad-layers/other.buildpack.id/build.toml
diff --git a/testdata/exporter/build-metadata/layers/buildpack.id/build.toml b/phase/testdata/exporter/build-metadata/layers/buildpack.id/build.toml
similarity index 100%
rename from testdata/exporter/build-metadata/layers/buildpack.id/build.toml
rename to phase/testdata/exporter/build-metadata/layers/buildpack.id/build.toml
diff --git a/testdata/exporter/build-metadata/layers/config/metadata.toml b/phase/testdata/exporter/build-metadata/layers/config/metadata.toml
similarity index 100%
rename from testdata/exporter/build-metadata/layers/config/metadata.toml
rename to phase/testdata/exporter/build-metadata/layers/config/metadata.toml
diff --git a/testdata/exporter/build-metadata/layers/other.buildpack.id/build.toml b/phase/testdata/exporter/build-metadata/layers/other.buildpack.id/build.toml
similarity index 100%
rename from testdata/exporter/build-metadata/layers/other.buildpack.id/build.toml
rename to phase/testdata/exporter/build-metadata/layers/other.buildpack.id/build.toml
diff --git a/testdata/exporter/cache-layer-no-contents/layers/app/metadata.toml b/phase/testdata/exporter/cache-layer-no-contents/layers/app/metadata.toml
similarity index 100%
rename from testdata/exporter/cache-layer-no-contents/layers/app/metadata.toml
rename to phase/testdata/exporter/cache-layer-no-contents/layers/app/metadata.toml
diff --git a/testdata/exporter/cache-layer-no-contents/layers/app/test_app.sh b/phase/testdata/exporter/cache-layer-no-contents/layers/app/test_app.sh
similarity index 100%
rename from testdata/exporter/cache-layer-no-contents/layers/app/test_app.sh
rename to phase/testdata/exporter/cache-layer-no-contents/layers/app/test_app.sh
diff --git a/testdata/exporter/cache-layer-no-contents/layers/buildpack.id/cache-layer-no-contents.toml b/phase/testdata/exporter/cache-layer-no-contents/layers/buildpack.id/cache-layer-no-contents.toml
similarity index 100%
rename from testdata/exporter/cache-layer-no-contents/layers/buildpack.id/cache-layer-no-contents.toml
rename to phase/testdata/exporter/cache-layer-no-contents/layers/buildpack.id/cache-layer-no-contents.toml
diff --git a/testdata/exporter/cache-layer-no-contents/layers/config/metadata.toml b/phase/testdata/exporter/cache-layer-no-contents/layers/config/metadata.toml
similarity index 100%
rename from testdata/exporter/cache-layer-no-contents/layers/config/metadata.toml
rename to phase/testdata/exporter/cache-layer-no-contents/layers/config/metadata.toml
diff --git a/testdata/exporter/cleanup-cache/layers/buildpack.id/no-toml-layer/file-for-invalid-layer b/phase/testdata/exporter/cleanup-cache/layers/buildpack.id/no-toml-layer/file-for-invalid-layer
similarity index 100%
rename from testdata/exporter/cleanup-cache/layers/buildpack.id/no-toml-layer/file-for-invalid-layer
rename to phase/testdata/exporter/cleanup-cache/layers/buildpack.id/no-toml-layer/file-for-invalid-layer
diff --git a/testdata/exporter/default-process/metadata-with-default/layers/config/metadata.toml b/phase/testdata/exporter/default-process/metadata-with-default/layers/config/metadata.toml
similarity index 100%
rename from testdata/exporter/default-process/metadata-with-default/layers/config/metadata.toml
rename to phase/testdata/exporter/default-process/metadata-with-default/layers/config/metadata.toml
diff --git a/testdata/exporter/default-process/metadata-with-no-default/layers/config/metadata.toml b/phase/testdata/exporter/default-process/metadata-with-no-default/layers/config/metadata.toml
similarity index 100%
rename from testdata/exporter/default-process/metadata-with-no-default/layers/config/metadata.toml
rename to phase/testdata/exporter/default-process/metadata-with-no-default/layers/config/metadata.toml
diff --git a/testdata/exporter/empty-metadata/layers/config/metadata.toml b/phase/testdata/exporter/empty-metadata/layers/config/metadata.toml
similarity index 100%
rename from testdata/exporter/empty-metadata/layers/config/metadata.toml
rename to phase/testdata/exporter/empty-metadata/layers/config/metadata.toml
diff --git a/testdata/exporter/escaped-bpid/layers/app/app b/phase/testdata/exporter/escaped-bpid/layers/app/app
similarity index 100%
rename from testdata/exporter/escaped-bpid/layers/app/app
rename to phase/testdata/exporter/escaped-bpid/layers/app/app
diff --git a/testdata/exporter/escaped-bpid/layers/config/metadata.toml b/phase/testdata/exporter/escaped-bpid/layers/config/metadata.toml
similarity index 100%
rename from testdata/exporter/escaped-bpid/layers/config/metadata.toml
rename to phase/testdata/exporter/escaped-bpid/layers/config/metadata.toml
diff --git a/testdata/exporter/escaped-bpid/layers/some_escaped_bp_id/some-layer.toml b/phase/testdata/exporter/escaped-bpid/layers/some_escaped_bp_id/some-layer.toml
similarity index 100%
rename from testdata/exporter/escaped-bpid/layers/some_escaped_bp_id/some-layer.toml
rename to phase/testdata/exporter/escaped-bpid/layers/some_escaped_bp_id/some-layer.toml
diff --git a/testdata/exporter/escaped-bpid/layers/some_escaped_bp_id/some-layer/some-file b/phase/testdata/exporter/escaped-bpid/layers/some_escaped_bp_id/some-layer/some-file
similarity index 100%
rename from testdata/exporter/escaped-bpid/layers/some_escaped_bp_id/some-layer/some-file
rename to phase/testdata/exporter/escaped-bpid/layers/some_escaped_bp_id/some-layer/some-file
diff --git a/testdata/exporter/launcher b/phase/testdata/exporter/launcher
similarity index 100%
rename from testdata/exporter/launcher
rename to phase/testdata/exporter/launcher
diff --git a/testdata/exporter/launcher-sbom/layers/config/metadata.toml b/phase/testdata/exporter/launcher-sbom/layers/config/metadata.toml
similarity index 100%
rename from testdata/exporter/launcher-sbom/layers/config/metadata.toml
rename to phase/testdata/exporter/launcher-sbom/layers/config/metadata.toml
diff --git a/testdata/exporter/launcher-sbom/layers/some-launcher-sbom-dir/a-regular-file.txt b/phase/testdata/exporter/launcher-sbom/layers/some-launcher-sbom-dir/a-regular-file.txt
similarity index 100%
rename from testdata/exporter/launcher-sbom/layers/some-launcher-sbom-dir/a-regular-file.txt
rename to phase/testdata/exporter/launcher-sbom/layers/some-launcher-sbom-dir/a-regular-file.txt
diff --git a/testdata/exporter/launcher-sbom/layers/some-launcher-sbom-dir/some-sbom-file.sbom.spdx.json b/phase/testdata/exporter/launcher-sbom/layers/some-launcher-sbom-dir/some-sbom-file.sbom.spdx.json
similarity index 100%
rename from testdata/exporter/launcher-sbom/layers/some-launcher-sbom-dir/some-sbom-file.sbom.spdx.json
rename to phase/testdata/exporter/launcher-sbom/layers/some-launcher-sbom-dir/some-sbom-file.sbom.spdx.json
diff --git a/testdata/exporter/previous-image-exists/layers/app/.hidden.txt b/phase/testdata/exporter/previous-image-exists/layers/app/.hidden.txt
similarity index 100%
rename from testdata/exporter/previous-image-exists/layers/app/.hidden.txt
rename to phase/testdata/exporter/previous-image-exists/layers/app/.hidden.txt
diff --git a/testdata/exporter/previous-image-exists/layers/app/subdir/myfile.txt b/phase/testdata/exporter/previous-image-exists/layers/app/subdir/myfile.txt
similarity index 100%
rename from testdata/exporter/previous-image-exists/layers/app/subdir/myfile.txt
rename to phase/testdata/exporter/previous-image-exists/layers/app/subdir/myfile.txt
diff --git a/testdata/exporter/previous-image-exists/layers/app/test_app.sh b/phase/testdata/exporter/previous-image-exists/layers/app/test_app.sh
similarity index 100%
rename from testdata/exporter/previous-image-exists/layers/app/test_app.sh
rename to phase/testdata/exporter/previous-image-exists/layers/app/test_app.sh
diff --git a/testdata/exporter/previous-image-exists/layers/app/test_app_real.sh b/phase/testdata/exporter/previous-image-exists/layers/app/test_app_real.sh
similarity index 100%
rename from testdata/exporter/previous-image-exists/layers/app/test_app_real.sh
rename to phase/testdata/exporter/previous-image-exists/layers/app/test_app_real.sh
diff --git a/testdata/exporter/previous-image-exists/layers/bad.buildpack.id/bad-layer.toml b/phase/testdata/exporter/previous-image-exists/layers/bad.buildpack.id/bad-layer.toml
similarity index 100%
rename from testdata/exporter/previous-image-exists/layers/bad.buildpack.id/bad-layer.toml
rename to phase/testdata/exporter/previous-image-exists/layers/bad.buildpack.id/bad-layer.toml
diff --git a/testdata/exporter/previous-image-exists/layers/bad.buildpack.id/bad-layer/layer5contents b/phase/testdata/exporter/previous-image-exists/layers/bad.buildpack.id/bad-layer/layer5contents
similarity index 100%
rename from testdata/exporter/previous-image-exists/layers/bad.buildpack.id/bad-layer/layer5contents
rename to phase/testdata/exporter/previous-image-exists/layers/bad.buildpack.id/bad-layer/layer5contents
diff --git a/testdata/exporter/previous-image-exists/layers/buildpack.id/launch-layer-no-local-dir.toml b/phase/testdata/exporter/previous-image-exists/layers/buildpack.id/launch-layer-no-local-dir.toml
similarity index 100%
rename from testdata/exporter/previous-image-exists/layers/buildpack.id/launch-layer-no-local-dir.toml
rename to phase/testdata/exporter/previous-image-exists/layers/buildpack.id/launch-layer-no-local-dir.toml
diff --git a/testdata/exporter/previous-image-exists/layers/buildpack.id/launch.toml b/phase/testdata/exporter/previous-image-exists/layers/buildpack.id/launch.toml
similarity index 100%
rename from testdata/exporter/previous-image-exists/layers/buildpack.id/launch.toml
rename to phase/testdata/exporter/previous-image-exists/layers/buildpack.id/launch.toml
diff --git a/testdata/exporter/previous-image-exists/layers/buildpack.id/new-launch-layer.toml b/phase/testdata/exporter/previous-image-exists/layers/buildpack.id/new-launch-layer.toml
similarity index 100%
rename from testdata/exporter/previous-image-exists/layers/buildpack.id/new-launch-layer.toml
rename to phase/testdata/exporter/previous-image-exists/layers/buildpack.id/new-launch-layer.toml
diff --git a/testdata/exporter/previous-image-exists/layers/buildpack.id/new-launch-layer/file-from-new-launch-layer b/phase/testdata/exporter/previous-image-exists/layers/buildpack.id/new-launch-layer/file-from-new-launch-layer
similarity index 100%
rename from testdata/exporter/previous-image-exists/layers/buildpack.id/new-launch-layer/file-from-new-launch-layer
rename to phase/testdata/exporter/previous-image-exists/layers/buildpack.id/new-launch-layer/file-from-new-launch-layer
diff --git a/testdata/exporter/previous-image-exists/layers/config/metadata.toml b/phase/testdata/exporter/previous-image-exists/layers/config/metadata.toml
similarity index 100%
rename from testdata/exporter/previous-image-exists/layers/config/metadata.toml
rename to phase/testdata/exporter/previous-image-exists/layers/config/metadata.toml
diff --git a/testdata/exporter/previous-image-exists/layers/old.buildpack.id/bad-layer.toml b/phase/testdata/exporter/previous-image-exists/layers/old.buildpack.id/bad-layer.toml
similarity index 100%
rename from testdata/exporter/previous-image-exists/layers/old.buildpack.id/bad-layer.toml
rename to phase/testdata/exporter/previous-image-exists/layers/old.buildpack.id/bad-layer.toml
diff --git a/testdata/exporter/previous-image-exists/layers/old.buildpack.id/bad-layer/layer5contents b/phase/testdata/exporter/previous-image-exists/layers/old.buildpack.id/bad-layer/layer5contents
similarity index 100%
rename from testdata/exporter/previous-image-exists/layers/old.buildpack.id/bad-layer/layer5contents
rename to phase/testdata/exporter/previous-image-exists/layers/old.buildpack.id/bad-layer/layer5contents
diff --git a/testdata/exporter/previous-image-exists/layers/other.buildpack.id/local-reusable-layer.toml b/phase/testdata/exporter/previous-image-exists/layers/other.buildpack.id/local-reusable-layer.toml
similarity index 100%
rename from testdata/exporter/previous-image-exists/layers/other.buildpack.id/local-reusable-layer.toml
rename to phase/testdata/exporter/previous-image-exists/layers/other.buildpack.id/local-reusable-layer.toml
diff --git a/testdata/exporter/previous-image-exists/layers/other.buildpack.id/local-reusable-layer/layer5contents b/phase/testdata/exporter/previous-image-exists/layers/other.buildpack.id/local-reusable-layer/layer5contents
similarity index 100%
rename from testdata/exporter/previous-image-exists/layers/other.buildpack.id/local-reusable-layer/layer5contents
rename to phase/testdata/exporter/previous-image-exists/layers/other.buildpack.id/local-reusable-layer/layer5contents
diff --git a/testdata/exporter/previous-image-exists/layers/other.buildpack.id/new-launch-layer.toml b/phase/testdata/exporter/previous-image-exists/layers/other.buildpack.id/new-launch-layer.toml
similarity index 100%
rename from testdata/exporter/previous-image-exists/layers/other.buildpack.id/new-launch-layer.toml
rename to phase/testdata/exporter/previous-image-exists/layers/other.buildpack.id/new-launch-layer.toml
diff --git a/testdata/exporter/previous-image-exists/layers/other.buildpack.id/new-launch-layer/new-launch-layer-file b/phase/testdata/exporter/previous-image-exists/layers/other.buildpack.id/new-launch-layer/new-launch-layer-file
similarity index 100%
rename from testdata/exporter/previous-image-exists/layers/other.buildpack.id/new-launch-layer/new-launch-layer-file
rename to phase/testdata/exporter/previous-image-exists/layers/other.buildpack.id/new-launch-layer/new-launch-layer-file
diff --git a/testdata/exporter/previous-image-exists/layers/sbom/launch/buildpack.id/bom.cdx.json b/phase/testdata/exporter/previous-image-exists/layers/sbom/launch/buildpack.id/bom.cdx.json
similarity index 100%
rename from testdata/exporter/previous-image-exists/layers/sbom/launch/buildpack.id/bom.cdx.json
rename to phase/testdata/exporter/previous-image-exists/layers/sbom/launch/buildpack.id/bom.cdx.json
diff --git a/testdata/exporter/previous-image-not-exist/layers/app/.hidden.txt b/phase/testdata/exporter/previous-image-not-exist/layers/app/.hidden.txt
similarity index 100%
rename from testdata/exporter/previous-image-not-exist/layers/app/.hidden.txt
rename to phase/testdata/exporter/previous-image-not-exist/layers/app/.hidden.txt
diff --git a/testdata/exporter/previous-image-not-exist/layers/app/metadata.toml b/phase/testdata/exporter/previous-image-not-exist/layers/app/metadata.toml
similarity index 100%
rename from testdata/exporter/previous-image-not-exist/layers/app/metadata.toml
rename to phase/testdata/exporter/previous-image-not-exist/layers/app/metadata.toml
diff --git a/testdata/exporter/previous-image-not-exist/layers/app/test_app.sh b/phase/testdata/exporter/previous-image-not-exist/layers/app/test_app.sh
similarity index 100%
rename from testdata/exporter/previous-image-not-exist/layers/app/test_app.sh
rename to phase/testdata/exporter/previous-image-not-exist/layers/app/test_app.sh
diff --git a/testdata/exporter/previous-image-not-exist/layers/buildpack.id/layer1.toml b/phase/testdata/exporter/previous-image-not-exist/layers/buildpack.id/layer1.toml
similarity index 100%
rename from testdata/exporter/previous-image-not-exist/layers/buildpack.id/layer1.toml
rename to phase/testdata/exporter/previous-image-not-exist/layers/buildpack.id/layer1.toml
diff --git a/testdata/exporter/previous-image-not-exist/layers/buildpack.id/layer1/file-from-layer-1 b/phase/testdata/exporter/previous-image-not-exist/layers/buildpack.id/layer1/file-from-layer-1
similarity index 100%
rename from testdata/exporter/previous-image-not-exist/layers/buildpack.id/layer1/file-from-layer-1
rename to phase/testdata/exporter/previous-image-not-exist/layers/buildpack.id/layer1/file-from-layer-1
diff --git a/testdata/exporter/previous-image-not-exist/layers/buildpack.id/layer2.toml b/phase/testdata/exporter/previous-image-not-exist/layers/buildpack.id/layer2.toml
similarity index 100%
rename from testdata/exporter/previous-image-not-exist/layers/buildpack.id/layer2.toml
rename to phase/testdata/exporter/previous-image-not-exist/layers/buildpack.id/layer2.toml
diff --git a/testdata/exporter/previous-image-not-exist/layers/buildpack.id/layer2/file-from-layer-2 b/phase/testdata/exporter/previous-image-not-exist/layers/buildpack.id/layer2/file-from-layer-2
similarity index 100%
rename from testdata/exporter/previous-image-not-exist/layers/buildpack.id/layer2/file-from-layer-2
rename to phase/testdata/exporter/previous-image-not-exist/layers/buildpack.id/layer2/file-from-layer-2
diff --git a/testdata/exporter/previous-image-not-exist/layers/config/metadata.toml b/phase/testdata/exporter/previous-image-not-exist/layers/config/metadata.toml
similarity index 100%
rename from testdata/exporter/previous-image-not-exist/layers/config/metadata.toml
rename to phase/testdata/exporter/previous-image-not-exist/layers/config/metadata.toml
diff --git a/testdata/exporter/previous-image-not-exist/layers/sbom/launch/buildpack.id/launch-true-layer/bom.cdx.json b/phase/testdata/exporter/previous-image-not-exist/layers/sbom/launch/buildpack.id/launch-true-layer/bom.cdx.json
similarity index 100%
rename from testdata/exporter/previous-image-not-exist/layers/sbom/launch/buildpack.id/launch-true-layer/bom.cdx.json
rename to phase/testdata/exporter/previous-image-not-exist/layers/sbom/launch/buildpack.id/launch-true-layer/bom.cdx.json
diff --git a/testdata/restorer/buildpack.id/cache-false/file-from-cache-false-layer b/phase/testdata/restorer/buildpack.id/cache-false/file-from-cache-false-layer
similarity index 100%
rename from testdata/restorer/buildpack.id/cache-false/file-from-cache-false-layer
rename to phase/testdata/restorer/buildpack.id/cache-false/file-from-cache-false-layer
diff --git a/testdata/restorer/buildpack.id/cache-launch/file-from-cache-launch-layer b/phase/testdata/restorer/buildpack.id/cache-launch/file-from-cache-launch-layer
similarity index 100%
rename from testdata/restorer/buildpack.id/cache-launch/file-from-cache-launch-layer
rename to phase/testdata/restorer/buildpack.id/cache-launch/file-from-cache-launch-layer
diff --git a/testdata/restorer/buildpack.id/cache-only/file-from-cache-only-layer b/phase/testdata/restorer/buildpack.id/cache-only/file-from-cache-only-layer
similarity index 100%
rename from testdata/restorer/buildpack.id/cache-only/file-from-cache-only-layer
rename to phase/testdata/restorer/buildpack.id/cache-only/file-from-cache-only-layer
diff --git a/testdata/restorer/escaped_buildpack_id/escaped-bp-layer/file-from-escaped-bp b/phase/testdata/restorer/escaped_buildpack_id/escaped-bp-layer/file-from-escaped-bp
similarity index 100%
rename from testdata/restorer/escaped_buildpack_id/escaped-bp-layer/file-from-escaped-bp
rename to phase/testdata/restorer/escaped_buildpack_id/escaped-bp-layer/file-from-escaped-bp
diff --git a/testdata/restorer/nogroup.buildpack.id/some-layer/file-from-some-layer b/phase/testdata/restorer/nogroup.buildpack.id/some-layer/file-from-some-layer
similarity index 100%
rename from testdata/restorer/nogroup.buildpack.id/some-layer/file-from-some-layer
rename to phase/testdata/restorer/nogroup.buildpack.id/some-layer/file-from-some-layer
diff --git a/testmock/auth/mock_keychain.go b/phase/testmock/auth/mock_keychain.go
similarity index 100%
rename from testmock/auth/mock_keychain.go
rename to phase/testmock/auth/mock_keychain.go
diff --git a/testmock/build_env.go b/phase/testmock/build_env.go
similarity index 97%
rename from testmock/build_env.go
rename to phase/testmock/build_env.go
index 6fc83b10..59526c8d 100644
--- a/testmock/build_env.go
+++ b/phase/testmock/build_env.go
@@ -1,5 +1,5 @@
 // Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/buildpacks/lifecycle (interfaces: BuildEnv)
+// Source: github.com/buildpacks/lifecycle/phase (interfaces: BuildEnv)
 
 // Package testmock is a generated GoMock package.
 package testmock
diff --git a/testmock/build_executor.go b/phase/testmock/build_executor.go
similarity index 100%
rename from testmock/build_executor.go
rename to phase/testmock/build_executor.go
diff --git a/testmock/buildpack_api_verifier.go b/phase/testmock/buildpack_api_verifier.go
similarity index 95%
rename from testmock/buildpack_api_verifier.go
rename to phase/testmock/buildpack_api_verifier.go
index aaefe073..86ca9b4f 100644
--- a/testmock/buildpack_api_verifier.go
+++ b/phase/testmock/buildpack_api_verifier.go
@@ -1,5 +1,5 @@
 // Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/buildpacks/lifecycle (interfaces: BuildpackAPIVerifier)
+// Source: github.com/buildpacks/lifecycle/phase (interfaces: BuildpackAPIVerifier)
 
 // Package testmock is a generated GoMock package.
 package testmock
diff --git a/testmock/cache/image_comparer.go b/phase/testmock/cache/image_comparer.go
similarity index 100%
rename from testmock/cache/image_comparer.go
rename to phase/testmock/cache/image_comparer.go
diff --git a/testmock/cache/image_deleter.go b/phase/testmock/cache/image_deleter.go
similarity index 100%
rename from testmock/cache/image_deleter.go
rename to phase/testmock/cache/image_deleter.go
diff --git a/testmock/cache_handler.go b/phase/testmock/cache_handler.go
similarity index 88%
rename from testmock/cache_handler.go
rename to phase/testmock/cache_handler.go
index c5281f85..c62468fb 100644
--- a/testmock/cache_handler.go
+++ b/phase/testmock/cache_handler.go
@@ -1,5 +1,5 @@
 // Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/buildpacks/lifecycle (interfaces: CacheHandler)
+// Source: github.com/buildpacks/lifecycle/phase (interfaces: CacheHandler)
 
 // Package testmock is a generated GoMock package.
 package testmock
@@ -9,7 +9,7 @@ import (
 
 	gomock "github.com/golang/mock/gomock"
 
-	lifecycle "github.com/buildpacks/lifecycle"
+	phase "github.com/buildpacks/lifecycle/phase"
 )
 
 // MockCacheHandler is a mock of CacheHandler interface.
@@ -36,10 +36,10 @@ func (m *MockCacheHandler) EXPECT() *MockCacheHandlerMockRecorder {
 }
 
 // InitCache mocks base method.
-func (m *MockCacheHandler) InitCache(arg0, arg1 string, arg2 bool) (lifecycle.Cache, error) {
+func (m *MockCacheHandler) InitCache(arg0, arg1 string, arg2 bool) (phase.Cache, error) {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "InitCache", arg0, arg1, arg2)
-	ret0, _ := ret[0].(lifecycle.Cache)
+	ret0, _ := ret[0].(phase.Cache)
 	ret1, _ := ret[1].(error)
 	return ret0, ret1
 }
diff --git a/testmock/component_descriptor.go b/phase/testmock/component_descriptor.go
similarity index 76%
rename from testmock/component_descriptor.go
rename to phase/testmock/component_descriptor.go
index 2fbbeaf9..c2259de8 100644
--- a/testmock/component_descriptor.go
+++ b/phase/testmock/component_descriptor.go
@@ -8,6 +8,8 @@ import (
 	reflect "reflect"
 
 	gomock "github.com/golang/mock/gomock"
+
+	buildpack "github.com/buildpacks/lifecycle/buildpack"
 )
 
 // MockDescriptor is a mock of Descriptor interface.
@@ -60,3 +62,17 @@ func (mr *MockDescriptorMockRecorder) Homepage() *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Homepage", reflect.TypeOf((*MockDescriptor)(nil).Homepage))
 }
+
+// TargetsList mocks base method.
+func (m *MockDescriptor) TargetsList() []buildpack.TargetMetadata {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "TargetsList")
+	ret0, _ := ret[0].([]buildpack.TargetMetadata)
+	return ret0
+}
+
+// TargetsList indicates an expected call of TargetsList.
+func (mr *MockDescriptorMockRecorder) TargetsList() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TargetsList", reflect.TypeOf((*MockDescriptor)(nil).TargetsList))
+}
diff --git a/testmock/config_handler.go b/phase/testmock/config_handler.go
similarity index 85%
rename from testmock/config_handler.go
rename to phase/testmock/config_handler.go
index 68cbb3b2..a0dc6b1b 100644
--- a/testmock/config_handler.go
+++ b/phase/testmock/config_handler.go
@@ -1,5 +1,5 @@
 // Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/buildpacks/lifecycle (interfaces: ConfigHandler)
+// Source: github.com/buildpacks/lifecycle/phase (interfaces: ConfigHandler)
 
 // Package testmock is a generated GoMock package.
 package testmock
@@ -84,6 +84,21 @@ func (mr *MockConfigHandlerMockRecorder) ReadOrder(arg0 interface{}) *gomock.Cal
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadOrder", reflect.TypeOf((*MockConfigHandler)(nil).ReadOrder), arg0)
 }
 
+// ReadPlan mocks base method.
+func (m *MockConfigHandler) ReadPlan(arg0 string) (files.Plan, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "ReadPlan", arg0)
+	ret0, _ := ret[0].(files.Plan)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// ReadPlan indicates an expected call of ReadPlan.
+func (mr *MockConfigHandlerMockRecorder) ReadPlan(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadPlan", reflect.TypeOf((*MockConfigHandler)(nil).ReadPlan), arg0)
+}
+
 // ReadRun mocks base method.
 func (m *MockConfigHandler) ReadRun(arg0 string, arg1 log.Logger) (files.Run, error) {
 	m.ctrl.T.Helper()
diff --git a/testmock/detect_executor.go b/phase/testmock/detect_executor.go
similarity index 100%
rename from testmock/detect_executor.go
rename to phase/testmock/detect_executor.go
diff --git a/testmock/detect_resolver.go b/phase/testmock/detect_resolver.go
similarity index 92%
rename from testmock/detect_resolver.go
rename to phase/testmock/detect_resolver.go
index d62d81ad..90ecd851 100644
--- a/testmock/detect_resolver.go
+++ b/phase/testmock/detect_resolver.go
@@ -1,5 +1,5 @@
 // Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/buildpacks/lifecycle (interfaces: DetectResolver)
+// Source: github.com/buildpacks/lifecycle/phase (interfaces: DetectResolver)
 
 // Package testmock is a generated GoMock package.
 package testmock
@@ -11,7 +11,7 @@ import (
 	gomock "github.com/golang/mock/gomock"
 
 	buildpack "github.com/buildpacks/lifecycle/buildpack"
-	"github.com/buildpacks/lifecycle/platform/files"
+	files "github.com/buildpacks/lifecycle/platform/files"
 )
 
 // MockDetectResolver is a mock of DetectResolver interface.
diff --git a/testmock/dir_store.go b/phase/testmock/dir_store.go
similarity index 97%
rename from testmock/dir_store.go
rename to phase/testmock/dir_store.go
index 9db25b8c..d47b4765 100644
--- a/testmock/dir_store.go
+++ b/phase/testmock/dir_store.go
@@ -1,5 +1,5 @@
 // Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/buildpacks/lifecycle (interfaces: DirStore)
+// Source: github.com/buildpacks/lifecycle/phase (interfaces: DirStore)
 
 // Package testmock is a generated GoMock package.
 package testmock
diff --git a/testmock/dockerfile_applier.go b/phase/testmock/dockerfile_applier.go
similarity index 97%
rename from testmock/dockerfile_applier.go
rename to phase/testmock/dockerfile_applier.go
index 06b0bd18..e922a46f 100644
--- a/testmock/dockerfile_applier.go
+++ b/phase/testmock/dockerfile_applier.go
@@ -1,5 +1,5 @@
 // Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/buildpacks/lifecycle (interfaces: DockerfileApplier)
+// Source: github.com/buildpacks/lifecycle/phase (interfaces: DockerfileApplier)
 
 // Package testmock is a generated GoMock package.
 package testmock
diff --git a/testmock/generate_executor.go b/phase/testmock/generate_executor.go
similarity index 100%
rename from testmock/generate_executor.go
rename to phase/testmock/generate_executor.go
diff --git a/testmock/image_handler.go b/phase/testmock/image_handler.go
similarity index 100%
rename from testmock/image_handler.go
rename to phase/testmock/image_handler.go
diff --git a/testmock/layer_factory.go b/phase/testmock/layer_factory.go
similarity index 97%
rename from testmock/layer_factory.go
rename to phase/testmock/layer_factory.go
index 885c8898..1e0aede7 100644
--- a/testmock/layer_factory.go
+++ b/phase/testmock/layer_factory.go
@@ -1,5 +1,5 @@
 // Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/buildpacks/lifecycle (interfaces: LayerFactory)
+// Source: github.com/buildpacks/lifecycle/phase (interfaces: LayerFactory)
 
 // Package testmock is a generated GoMock package.
 package testmock
diff --git a/testmock/metadata_restorer.go b/phase/testmock/metadata_restorer.go
similarity index 97%
rename from testmock/metadata_restorer.go
rename to phase/testmock/metadata_restorer.go
index aab08aa3..6f3f52a6 100644
--- a/testmock/metadata_restorer.go
+++ b/phase/testmock/metadata_restorer.go
@@ -12,7 +12,7 @@ import (
 	buildpack "github.com/buildpacks/lifecycle/buildpack"
 	layer "github.com/buildpacks/lifecycle/internal/layer"
 	platform "github.com/buildpacks/lifecycle/platform"
-	"github.com/buildpacks/lifecycle/platform/files"
+	files "github.com/buildpacks/lifecycle/platform/files"
 )
 
 // MockMetadataRestorer is a mock of MetadataRestorer interface.
diff --git a/testmock/ref.go b/phase/testmock/ref.go
similarity index 100%
rename from testmock/ref.go
rename to phase/testmock/ref.go
diff --git a/testmock/registry_handler.go b/phase/testmock/registry_handler.go
similarity index 96%
rename from testmock/registry_handler.go
rename to phase/testmock/registry_handler.go
index 737a15aa..258ccf41 100644
--- a/testmock/registry_handler.go
+++ b/phase/testmock/registry_handler.go
@@ -1,5 +1,5 @@
 // Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/buildpacks/lifecycle (interfaces: RegistryHandler)
+// Source: github.com/buildpacks/lifecycle/image (interfaces: RegistryHandler)
 
 // Package testmock is a generated GoMock package.
 package testmock
diff --git a/utils_test.go b/phase/utils_test.go
similarity index 78%
rename from utils_test.go
rename to phase/utils_test.go
index b5d396ac..18d5c484 100644
--- a/utils_test.go
+++ b/phase/utils_test.go
@@ -1,4 +1,4 @@
-package lifecycle_test
+package phase_test
 
 import (
 	"testing"
@@ -7,7 +7,7 @@ import (
 	"github.com/sclevine/spec"
 	"github.com/sclevine/spec/report"
 
-	"github.com/buildpacks/lifecycle"
+	"github.com/buildpacks/lifecycle/phase"
 )
 
 func TestUtils(t *testing.T) {
@@ -17,7 +17,7 @@ func TestUtils(t *testing.T) {
 func testUtils(t *testing.T, when spec.G, it spec.S) {
 	when(".TruncateSha", func() {
 		it("should truncate the sha", func() {
-			actual := lifecycle.TruncateSha("ed649d0a36b218c476b64d61f85027477ef5742045799f45c8c353562279065a")
+			actual := phase.TruncateSha("ed649d0a36b218c476b64d61f85027477ef5742045799f45c8c353562279065a")
 			if s := cmp.Diff(actual, "ed649d0a36b2"); s != "" {
 				t.Fatalf("Unexpected sha:\n%s\n", s)
 			}
@@ -25,7 +25,7 @@ func testUtils(t *testing.T, when spec.G, it spec.S) {
 
 		it("should not truncate the sha with it's short", func() {
 			sha := "not-a-sha"
-			actual := lifecycle.TruncateSha(sha)
+			actual := phase.TruncateSha(sha)
 			if s := cmp.Diff(actual, sha); s != "" {
 				t.Fatalf("Unexpected sha:\n%s\n", s)
 			}
@@ -33,7 +33,7 @@ func testUtils(t *testing.T, when spec.G, it spec.S) {
 
 		it("should remove the prefix", func() {
 			sha := "sha256:ed649d0a36b218c476b64d61f85027477ef5742045799f45c8c353562279065a"
-			actual := lifecycle.TruncateSha(sha)
+			actual := phase.TruncateSha(sha)
 			if s := cmp.Diff(actual, "ed649d0a36b2"); s != "" {
 				t.Fatalf("Unexpected sha:\n%s\n", s)
 			}