Existing-Repository Adoption and Migration (FND-070)¶
This page describes how an existing project adopts the Skill Family infrastructure, and the gate for judging migration completion. It serves two reader types: a human owner uses it to judge the adoption path and risks; an agent uses it to run a read-only inventory via adopt-plan, without duplicating write actions on this page.
Classification Method¶
Before an existing repository connects to Foundation, classify it into one of the four categories below. The classification determines which path to take, whether files are written, and how the legacy implementation is handled.
| Category | Meaning | Handling path | Writes files? |
|---|---|---|---|
direct-adoption |
The repository already has a capability that can be replaced by Foundation as-is, with no business-semantic coupling | Direct adoption: adopt-plan inventory → scaffold/projection writes to disk |
Yes (contained) |
compatibility-layer |
The repository's existing implementation differs from the Foundation interface and needs a compatibility adaptation layer | Write a compatibility layer, then adopt; the legacy implementation exits via the compatibility layer | Yes (compatibility layer contained) |
keep-business |
The capability contains domain semantics or a business state machine, belongs to the caller, and Foundation does not take it | Keep with the consumer; connect only business-neutral mechanisms | No (on the Foundation side) |
foundation-gap |
A needed capability is not yet provided by Foundation (e.g., host apply, Qoder driver) | Keep with the consumer or a later version; do not forcibly expand Foundation | No |
Classification is a prerequisite for the adoption decision. Capabilities that fall into
keep-businessorfoundation-gapshould not be stuffed into Foundation just to "complete adoption"; this is exactly the concrete instantiation of architecture-routing steps 1 and 8.
Principles¶
- Plan first: any existing repository first runs the read-only
adopt-plan, obtaining the current state, target Profile, exact write set, conflicts, legacy-implementation exit list, and acceptance commands, before deciding whether to perform a write. - Read-only plan, zero change:
adopt-planwrites no files (including temporary files), runs no git write commands, renames nothing, and touches no remotes; zero change to the input repository's bytes (provable by before/after hash walk-through). - Adoption and rename are separate: infrastructure adoption never drags along a repository rename or remote-coordinate change; the two are independent decisions, and the tool provides no rename capability.
- Dual-track does not count as complete: the migration-completion judgment requires the legacy duplicate implementation to have been deleted; connecting the new infrastructure while the legacy implementation still remains is permanently judged false.
adopt-plan Output Structure¶
| Section | Content |
|---|---|
target |
Current state: entry-point count, target's own managed declarations |
project / traceability |
Target Profile and the source of truth for each write (Contracts version, skeleton generation function, license Profile and variant) |
git |
Read-only Git pre-state (repository, HEAD, clean) |
writeSet |
Exact write set: per-path action (only create/unchanged), fileClass, sha256 |
conflicts |
Handwritten conflicts, managed drift, symlink placeholders, temporary exceptions for missing fields |
risks |
dirty, nested repository, tracked-but-ignored, expired exceptions, etc. |
migration |
Migration state machine: binding, on-disk proof (adoptionProof), pending-write count, check gate, four-class verification evidence, state and completion judgment |
verificationPlan |
Acceptance command sequence (check → projection → re-check) |
Migration Manifest (skill-family.migration.json)¶
The target repository declares its own migration state in the root directory:
{
"kind": "skill-family.migration-manifest",
"legacyInfra": [
{ "path": "scripts/old-validator.mjs", "replacedBy": "sf-kit check" }
],
"exceptions": [
{
"owner": "owner",
"reason": "specific reason",
"deadline": "2026-12-31",
"migrationTarget": "wave-m1"
}
],
"targetProfile": "generic",
"foundationPackages": [
{ "name": "skill-family-contracts", "version": "0.3.0", "digest": "sha256:<64 hex digits>" },
{ "name": "skill-family-harness-node", "version": "0.3.0", "digest": "sha256:<64 hex digits>" },
{ "name": "skill-family-engineering-kit", "version": "0.3.0", "digest": "sha256:<64 hex digits>" }
],
"verification": {
"unit": "docs/evidence/unit.json",
"integration": "docs/evidence/integration.json",
"consumer": "docs/evidence/consumer.json",
"independentAudit": "docs/evidence/independent-audit.json"
}
}
The structure above shows the core fields of the migration manifest; each legacyInfra entry gives the legacy-implementation path and its replacement; the tool only read-only evaluates existence and never deletes on its behalf.
The Quickstart Profile candidate moves to v2 in 0.3.0. A migration manifest that binds candidate capabilities must pin all three Foundation packages to exactly 0.3.0; an existing integration that still requires v1 must keep all three packages pinned to exactly 0.2.1 and must not mix the two sets.
legacyInfra: each entry gives the legacy-implementation path and its replacement; the tool only read-only evaluates existence and never deletes on its behalf.exceptionsare temporary exceptions: must simultaneously contain all four fields — owner, reason, deadline, and migrationTarget — and missing any field counts as a conflict (plan failure); expired exceptions are not auto-renewed and persistently block the completion judgment.targetProfile/foundationPackages: adoption binding. The target Profile must be consistent with the plan Profile; the three Foundation packages (contracts, harness-node, engineering-kit) must each bind an exact version number (floating ranges not accepted) and asha256:digest.verification: paths to the four classes of verification-evidence documents (unit, integration, consumer, independentAudit). Each piece of evidence must be parseable JSON whoseprojectIdmatches the target project's identity to count as proven; out-of-bounds paths, missing, unparseable, or identity-mismatched evidence are all unproven, fail-closed.
Migration State Machine¶
States advance monotonically and are derived only from read-only evidence; no tool advances state by writing.
not-declared → declared → adopted → verified → complete
| State | Meaning |
|---|---|
not-declared |
No contract-valid migration manifest exists |
declared |
The manifest contract is valid, but the adoption-proof facts are incomplete |
adopted |
Foundation bytes are on disk and the legacy implementation has exited, but the check gate is not green |
verified |
The check gate is green, but not all verification evidence is proven |
complete |
All eight completion conditions are satisfied; complete is true if and only if the state is complete |
Completion Gate¶
migration.completion.complete is true if and only if all eight hold:
- A contract-valid migration manifest has been declared (kind/fields validated by the Contracts schema);
- The adoption binding is proven: the target Profile is consistent with the plan, and the three Foundation packages each bind an exact version and a sha256 digest;
- The Project Manifest, managed lock, identity, and all managed skeleton files are on disk and their digests are consistent with the plan (
adoptionProof.missing/mismatchedare empty); - The write set has no pending create/replace/project actions (
pendingWritesis 0); - The Foundation check gate is green;
- Each legacy implementation has exited (absent) — containment refusal or malformed entries fail-closed identically to present;
- No missing fields, no unparseable deadline, no expired temporary exceptions; no unresolved adoption conflicts;
- All four classes of verification evidence (unit/integration/consumer/independentAudit) are proven and identity (projectId) matches.
An unevaluated input is equivalent to unproven: the gate advances only on presented evidence. The blocking reason (blockers) is a stable, readable string that can go directly into the migration review record.
Mandatory Negative Cases (completion must be false in any scenario)¶
- Empty repository + empty manifest (no contract-valid manifest →
not-declared); - Manifest with wrong kind (schema-invalid → treated as undeclared);
- 9 skeleton files still pending create (
declared, all missing paths listed); - Managed lock drift (
mismatchednon-empty); - Only the new implementation connected, legacy still present (dual-track →
declared); - Consumer verification evidence missing or identity mismatched (
verified, must not complete); - Temporary exception expired (the tool never renews).
Legacy-Removal and Rollback Contract¶
- Removal is the caller's responsibility:
adopt-planonly evaluates legacy-implementation existence and lists the exit manifest, never deleting on its behalf;foundation-gapandkeep-businesscapabilities do not require removal. - Rollback relies on evidence: before adoption writes to disk, a dual-digest binding of
adoptionProofandfoundationPlanDigestmust exist first; on rollback, the plan write set'sexpect.sha256pre-state is used to restore; an overwrite action that does not declare its pre-state is refused at theprojectionstage. - fail-closed default: any unparseable, out-of-bounds, identity-mismatched, or evidence-missing input makes the corresponding judgment unproven; success is not assumed.
Profile Draft¶
adopt-plan output carries a profileDraft sub-block (kind=skill-family.profile-draft, schemaVersion=1): a pre-filled profile.json descriptor draft (descriptorRelPath=profile.json). The adoption-lock concept and its artifact form are deprecated (remediation handoff D-8): the lightweight adoption proof is the descriptor's own adoption declaration, machine-verified by verifyProfile (Profile SPI v2) — field completeness and pin format are schema-checked at the descriptor step (SPE1001), pin digests are compared against the real artifact bytes inside the write set per GK-4 discipline (SPE1006), and the tightening-only overrides policy is adjudicated separately (SPE1007).
The draft pre-fills everything mechanically derivable from read-only facts:
- Descriptor identity and base:
profile.id/profile.namecome from the plan inputs,base.contractsVersionis the loaded frozen contracts version;differences/spistart as empty arrays. - The adoption declaration (D-8 minimal field set, authoritative definition in
adoptionDeclarationofprofiles/spi/extension-spi.json): foundation_profile.idis the adopted foundation profile id from the plan inputs;version/stabilityare human decisions and stay null.foundation_pin.algorithmis alwayssha256; the three foundation packages'versionvalues are pre-filled manifest-declaration-first, then from loaded constants;path/sha256stay null until real artifacts are placed in the profile write set (a digest comes only from real bytes, never guessed).adopted_atis fixed at adoption time, never at planning time, and stays null.overridesis an empty array (the empty example): overrides are opt-in self-tightening declarations, and the kit never enumerates rule examples (the core never imports the profile layer); the draft carriesoverridesGuidanceexplaining the tightening-only semantics.
Incompleteness Blocking Rules¶
- Any non-derivable field stays null and is listed in
incompleteFields; as long asincompleteFieldsis non-empty,ready=false: a draft with gaps must not be written as an adoption declaration as-is. - The tool never guesses: a human completes
foundation_profile.version/stability, each package artifact'spath/sha256,adopted_at, andprofile.version.
Binding¶
The draft carries a dual sha256 digest (the binding block): targetSetDigest summarizes the target repository's file set, and foundationPlanDigest summarizes the Foundation plan write set. Two plans for the same target yield the same digest, precisely binding the draft to the target state at plan time.
Boundary¶
adopt-planis strictly read-only: the draft is only printed with the plan to stdout, never written into the consumer repository;- No fifth Kit top-level command is added: Kit top-level commands are permanently the four — scaffold, adopt-plan, projection, check — and
profileDraftis only a sub-block ofadopt-planoutput.
Hazard-Class Fixtures¶
fixtures/adoption/cases/ covers seven hazard classes: dirty repository, nested repository, tracked-but-ignored, symlink placeholder, generated-artifact drift, legacy-implementation exit gate, and temporary-exception fields. Each case proves the plan is read-only via byte-level before/after snapshots; see the in-repo fixtures/adoption/README.md for case descriptions.