跳转至

简体中文

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-business or foundation-gap should not be stuffed into Foundation just to "complete adoption"; this is exactly the concrete instantiation of architecture-routing steps 1 and 8.

Principles

  1. 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.
  2. Read-only plan, zero change: adopt-plan writes 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).
  3. 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.
  4. 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.
  • exceptions are 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 a sha256: digest.
  • verification: paths to the four classes of verification-evidence documents (unit, integration, consumer, independentAudit). Each piece of evidence must be parseable JSON whose projectId matches 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:

  1. A contract-valid migration manifest has been declared (kind/fields validated by the Contracts schema);
  2. 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;
  3. The Project Manifest, managed lock, identity, and all managed skeleton files are on disk and their digests are consistent with the plan (adoptionProof.missing/mismatched are empty);
  4. The write set has no pending create/replace/project actions (pendingWrites is 0);
  5. The Foundation check gate is green;
  6. Each legacy implementation has exited (absent) — containment refusal or malformed entries fail-closed identically to present;
  7. No missing fields, no unparseable deadline, no expired temporary exceptions; no unresolved adoption conflicts;
  8. 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 (mismatched non-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-plan only evaluates legacy-implementation existence and lists the exit manifest, never deleting on its behalf; foundation-gap and keep-business capabilities do not require removal.
  • Rollback relies on evidence: before adoption writes to disk, a dual-digest binding of adoptionProof and foundationPlanDigest must exist first; on rollback, the plan write set's expect.sha256 pre-state is used to restore; an overwrite action that does not declare its pre-state is refused at the projection stage.
  • 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.name come from the plan inputs, base.contractsVersion is the loaded frozen contracts version; differences/spi start as empty arrays.
  • The adoption declaration (D-8 minimal field set, authoritative definition in adoptionDeclaration of profiles/spi/extension-spi.json):
  • foundation_profile.id is the adopted foundation profile id from the plan inputs; version/stability are human decisions and stay null.
  • foundation_pin.algorithm is always sha256; the three foundation packages' version values are pre-filled manifest-declaration-first, then from loaded constants; path/sha256 stay null until real artifacts are placed in the profile write set (a digest comes only from real bytes, never guessed).
  • adopted_at is fixed at adoption time, never at planning time, and stays null.
  • overrides is 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 carries overridesGuidance explaining the tightening-only semantics.

Incompleteness Blocking Rules

  • Any non-derivable field stays null and is listed in incompleteFields; as long as incompleteFields is 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's path/sha256, adopted_at, and profile.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-plan is 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 profileDraft is only a sub-block of adopt-plan output.

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.