State and ownership
Architecture reconciliation (2026-10-25): the
delivery, discovery and activation contract
controls intended production routes and reader intent. Existing Codex
AGENTS.md contribution APIs described here are experimental library
boundaries, not production independent-rule delivery. Independent Codex/OpenCode
rules remain beside unchanged team guidance through a selected native or hook
route. The native-first amendment permits explicit OpenCode paths after Q5 PASS
and selects the trusted Codex hook with its declared recorded-history
limitation. Reusable ownership, preflight and recovery mechanisms do not select
or qualify a runtime route, and schema-2 production activation remains blocked
(AD-0040 amendment).
This concern owns the requirements and implemented architecture below. Moving these records does not change their implementation or approval status. Library-only capabilities remain separate from production CLI activation; each section records its own limits. Accepted decisions remain in ADRs.
Requirements
Pure legacy-state projection
The internal library boundary legacy::project_legacy(&Lockfile) describes
typed schema-1 state without I/O or mutation. It retains an owned snapshot and
indexed item and bundle rows, including order, duplicates, installed aliases,
source names, recorded refs and SSOT hashes, dependency/group links, and raw
client strings. Explicitly tagged plugin and MCP rows preserve their original
targets, scopes, statuses and bundle references. Typed preservation does not
recover unknown JSON fields or the original serialization bytes.
Persisted source labels are decoded with the existing compatibility parser. A parsed source or recorded ref/hash does not prove an immutable source revision or ownership of generated bytes. Requested selection remains explicitly unresolved for every item, including singleton effective client lists. Empty client lists expand to the fixed historical Claude, Copilot and OpenCode clients; unknown spellings remain in the snapshot and produce structured diagnostics.
Candidates contain only a historical client, a relative entrypoint and a relative resource search base. The frozen nine kind/client layouts are shared with the current pipeline and use the installed alias. They do not inspect roots, files or resources, establish ownership, or authorize adoption or deletion. Bundle names and ancillary files do not produce candidates. Invalid installed names suppress candidates: empty names, dot, dot-dot, slash, backslash, colon and control characters are invalid; spaces and Unicode remain representable. Invalid sources retain otherwise known layout facts. Duplicate identities and identical entrypoints identify all affected item indices without selecting a winner. Diagnostics retain malformed rows for inspection. Unsupported schema values retain all typed records but produce no interpreted sources, clients or destinations.
This prerequisite leaves commands and schema-1 persistence unchanged. Verified sources, reproducible historical rendering, resource enumeration, adoption, requested-selection reconciliation, schema-2 lifecycle preservation, exact backup, containment, preflight, writer locking and recovery remain later work.
See the projection design and AD-0015 for implementation boundaries and rationale.
Combined lifecycle state foundation
The opt-in lifecycle::state library validates a schema-2 installation and
reference graph without I/O. Direct references root the graph; bundle members
and dependencies must resolve, be reachable and contain no dependency cycle.
Installations retain requested surfaces even when no artifact is generated.
Duplicate identities and inconsistent source revision evidence are rejected.
Artifact owners and roots must match authoritative installations. Explicit
per-owner consumer associations must match every artifact selector exactly;
their union must equal the artifact consumers. The strict schema-2 codec
validates before parsing results are returned and before serialization. It emits
deterministic pretty JSON with one trailing newline without mutating input.
Typed schema-1 projection creates no owners and preserves historical rows,
labels and statuses in their original order; exact JSON bytes and unknown legacy
fields require the later migration backup.
This library does not change lockfile::CURRENT_SCHEMA, command dispatch or
production lockfiles. It does not establish filesystem ownership or native
support. See
combined lifecycle state.
Per-owner recorded contract evidence
The opt-in combined state accepts optional explicit rule-owner contracts. Missing historical evidence stays unknown, is omitted when empty and gains no inferred revision; schema-1 projection produces no contracts. Each row binds exact scope, raw root, path, optional contribution selector and installation ID to captured source evidence, normalized artifact content and complete consumer/revision pairs. File content includes hash and optional mode; instruction content includes its body hash without aggregate whitespace bookkeeping.
Every present row must match an authoritative owner-consumer association, installation source evidence and owned artifact. Duplicate/orphan identities, inexact bindings or consumer coverage, duplicate/empty profiles and zero revisions reject. Unknown serialized fields reject. Deterministic encoding sorts contracts and profiles without changing the supplied state.
Agreement validates the whole state and selects an exact artifact identity. Unknown identities error. It reports missing evidence and source/profile conflicts in artifact-owner order. Matching requires all owners’ full source manifest digests and complete surface/revision maps to agree; profile vector order is immaterial. Equal bytes and differing consumer subsets do not suffice.
Upsert receives the complete explicit contract subset for changed installations; omitted changed evidence becomes unknown. Untouched rows survive exactly, replacement identities preserve relative order among surviving rows and new rows append in identity order. Evidence for unchanged installations rejects. Complete resulting state validation precedes successful planning. Empty desired outputs and reference graph retirement remove only associated retired evidence; surviving shared owners retain their contracts.
This inactive supplied-value boundary grants no shared-mutation, native, filesystem or source-authenticity authority. The original upsert and public mixed planner retain affected multi-owner rejection; explicit shared-output reconciliation consumes agreement through a separate entrypoint. Production commands and schema 1 are unchanged. Combined lifecycle delivery evidence is recorded in the Gate 5 acceptance note. See the owner-contract design and AD-0034.
Architecture
Combined lifecycle state
The opt-in lifecycle::state library describes schema-2 state for the future
owned-content lifecycle. Current commands still use schema 1. These records do
not migrate state, fetch sources, claim ownership or write output files. The
combined lifecycle state foundation
owns the requirements;
lifecycle::state
exposes the records and public functions.
Public records and validation
CombinedState holds the schema number, installations, bundles, references,
owned artifacts, per-owner consumers, optional owner contracts, optional origin
evidence and an optional typed legacy snapshot. An Installation reuses
ArtifactOwner and InstallationRoot, and records its complete requested
target surfaces and SourceEvidence. A BundleNode records its ID, source,
name and optional source revision. Reference distinguishes direct
installations, direct bundles, bundle membership and bundle dependencies.
OwnerConsumers binds an installation ID to a root, relative path, optional
contribution selector and explicit consumers. Existing ownership, target and
schema-1 types retain their wire representations. No new dependency is required.
validate_state
checks the complete envelope and reuses ownership::validate_artifacts for
artifact shape.
associations
supplies the internal owner/selector checks within that same public validation
boundary. Parsing and serialization both call this validator. Inputs remain
unchanged on rejection.
Reference authority
Installations bind an explicit ID to source, item kind, source name, installed alias, root, requested surfaces and captured source-manifest identity. Git records require their resolved revision to match the owner binding; local records have no revision. These recorded strings do not prove source authenticity. Manifest digests must contain exactly 64 lowercase hexadecimal characters; installation sources and names are nonempty. Roots must be absolute, and requested surfaces must be nonempty and unique. Git evidence requires a nonempty revision; an optional bundle revision cannot be empty.
Node IDs are caller supplied, nonempty ASCII alphanumeric, hyphen or underscore strings, unique within each node kind. Bundle and installation namespaces are typed, so the same string may occur in both. Identity allocation for lifecycle operations remains later work.
Direct installation and bundle references are roots of a directed graph. Bundle membership and dependencies retain existing installations without duplicating artifacts. Validation rejects duplicate IDs and edges, missing endpoints, bundle cycles and unreachable nodes. An iterative topological traversal rejects bundle cycles without recursive graph traversal, and traversal from direct references establishes reachability. Installations with no generated output remain valid when referenced, for example after audience exclusion.
Distinct IDs cannot describe the same source/kind/name/alias installation under the same exact root. Requested surfaces and resolved revisions may change on update without changing that identity. Root comparisons are lexical and retain raw spellings; they establish no filesystem equivalence or containment.
The combined graph is the single authority for new installation bindings and references. Reusing existing ownership and legacy types preserves their reviewed contracts. Independent writable sidecars and automatic legacy adoption were rejected because they would separate authority or invent ownership evidence.
Delivery boundary
This foundation is separate from command activation, source resolution, compiler reconciliation, migration backup, application and recovery. Gate 5 requires all lifecycle slices; Gate 6 supplies target qualification and activation.
Owners and consumers
Every artifact owner must exactly match its authoritative installation, including revision and alias. Artifact roots must match the installation scope and raw root spelling. Existing artifact validation still rejects ambiguous whole-file/contribution locations and malformed content metadata.
Each artifact selector and owner has exactly one explicit consumer association. Whole files use no contribution selector; Markdown regions use their recorded contribution ID. Associations must name existing owners, contain unique nonempty surfaces within that owner’s requested selection, and have no surplus entries. The union of these associations equals the artifact consumer set exactly. Validation never infers individual consumers from that union. Optional owner contracts bind explicit source, normalized content and revision evidence to these associations; absent rows stay unknown.
Codec and retained legacy data
parse_state and serialize_state both validate the complete envelope.
Schema-2 records reject unknown fields. Encoding sorts owned graph records,
selectors, owners and consumer lists in a cloned value and emits pretty JSON
with one trailing newline. It does not change the supplied state.
project_legacy requires schema 1 and retains its typed Lockfile snapshot in
legacy, with an empty owned graph. Legacy arrays, duplicate rows, labels,
recorded refs, optional fields and integration statuses keep their original
values and order. They are unresolved historical data, never ownership evidence.
Unknown original JSON fields and serialization bytes are outside this typed
projection. The applying lifecycle must preserve them in an exact first-write
backup; this pure slice does not create that backup. The text codec accepts
UTF-8 &str; future byte-loading callers must reject invalid UTF-8 explicitly.
An empty envelope has schema: 2, empty installations, bundles,
references, artifacts and owner_consumers arrays, and legacy: null. New
references are tagged as direct-installation, direct-bundle,
bundle-installation or bundle-dependency; source evidence is tagged git or
local. Legacy fields keep their existing schema-1 names rather than being
normalized to new tags.
Verification boundary
tests/lifecycle_state.rs
exercises graph sharing, every reference kind, reachability and cycles, identity
collisions, source revision/hash bindings, audience-excluded installations,
exact owner/consumer associations, lexical roots and selectors, strict decoding,
deterministic owned ordering and typed legacy retention. Independent controls
omit validation or sorting checks and require assertion failures after each
omission, followed by exact restoration. The
validation record
records the checked revision and scope of the evidence.
This completes the pure state slice. Separate captured desired-set production, reference reconciliation and shared-owner removal planning are implemented. Complete single-owner upsert planning is also implemented. A separate explicit shared upsert now reconciles supplied owner contracts and observations. The opt-in guarded application boundary connects complete artifact plans and combined state to checkpointed execution, including the exact original-state backup and recovery path. Observational lifecycle adapters, native and complete lifecycle qualification remain required; this record does not declare Gate 5 or M1 complete.
Per-owner recorded contract evidence
The inactive lifecycle::contracts boundary records supplied contract evidence
inside authoritative CombinedState. It supports rule installations and exposes
pure validation and agreement checking. Production commands and schema-1
lockfiles do not consume it. It adds no dependencies or native effects.
Representation and validation
CombinedState.owner_contracts is an optional vector, defaulting to empty and
omitted from serialization when empty. Historical absence stays unknown;
schema-1 projection creates no contracts. OwnerConsumers continues to describe
consumer associations without implying adapter revisions or contract evidence.
An OwnerContract binds exact scope, raw root spelling, path, optional
instruction contribution selector and installation ID to a captured
source-manifest digest, normalized content and explicit ConsumerContract
surface/revision pairs. Families follow stable target surfaces. File content
includes its recorded hash and optional mode. Instruction content includes the
body hash and excludes aggregate whitespace bookkeeping, allowing preserved
manual bytes or removal of neighboring contributions without invalidating an
untouched body’s evidence.
Whole-state validation checks every present row against an authoritative owner-consumer association, installation source evidence and owned artifact. It rejects duplicate identities, orphan evidence, changed roots/selectors, inexact source/content bindings, empty or duplicate profiles, inexact consumer coverage and zero revisions. Rows need not cover every owner. Strict unknown-field rejection remains. Serialization clones the state, sorts rows by scope/raw root/path/selector/installation identity and profiles by surface, and leaves the input unchanged.
Agreement boundary
check_contract_agreement validates the complete state, then locates an
artifact by exact supplied root, path and contribution selector. An unknown
identity is an error. It walks artifact owners in their recorded order, reports
every owner missing evidence, and compares evidenced owners with the first
evidenced owner. Source-manifest and target-profile conflicts are separate
diagnostics and can both occur for an owner. Profile vector order is immaterial.
Matching requires each owner to have evidence, identical full
captured-manifest digests and identical complete surface/revision maps.
Normalized content already matches the artifact through validation. Different
consumer subsets block even when output bytes match. This supplied agreement
establishes no native behavior, source authenticity, physical identity, new
ownership or execution authority.
Lifecycle propagation
UpsertRequest.contracts supplies the complete explicit evidence subset for
changed installations. Omitted changed rows become unknown. Untouched rows
retain their values and relative order; replacements occupy corresponding
surviving positions after removals. New rows append in identity order. Supplied
evidence for unchanged installations and duplicate rows reject. The complete
resulting state is validated before a plan is returned, so stale or invalid
proof exposes no partial plan.
Empty desired sets remove output evidence while retaining referenced installation history. Graph retirement removes retired installations’ contracts. Reference removal preserves surviving shared owners’ contracts and removes retired owners’ contracts, including instruction contributions and final empty aggregates.
The original plan_upsert and public mixed-planner sharing rejections remain
intact. The separate
shared upsert
consumes agreement for affected desired final multi-owner artifacts. Complete
selected recompilation can replace unknown history; unknown unselected proof is
never inferred. Agreement alone grants no shared-mutation or execution
authority. This supplied-value path does not integrate qualified
source/destination observations, complete Gate 5 or qualify native clients.
Components
- Contract model and agreement
- Combined-state validation and codec
- Upsert propagation
- Reference retirement
- Acceptance examples
- Evidence policy
- Validation limits
Pure owner release with no desired value at an identity preserves untouched survivors and their unknown evidence without requiring agreement. Supplied physical observations and all physical checks still apply.
Pure legacy-state projection
The public library boundary
legacy::project_legacy
accepts a borrowed typed Lockfile and returns an owned LegacyProjection. It
has no scope-root input, I/O, state writer or CLI dispatch.
AD-0015
records the choice of this intermediate boundary; the
specification owns its requirements.
Snapshot and interpreted rows
The snapshot is an exact typed clone, including original vector order and every current field. It does not preserve unknown JSON fields or serialization bytes. Items and bundles retain their original index and cloned record. Plugins precede MCP entries in a tagged integration vector; each integration index addresses its original plugin or MCP vector. Native targets, scopes and statuses remain facts, without runtime verification or permission to operate on the target.
Unsupported schema values retain all these records, produce a global diagnostic,
and return without interpreting sources, clients or destinations. For schema 1,
source decoding uses parse_install_source_label; parse errors retain the raw
record and actionable parser message. Successful parsing does not verify source
availability, a revision or generated bytes.
Every item has RequestedSelection::Unresolved. Effective generation clients
cannot recover requested families lost through author audience filtering, and a
Copilot client does not distinguish CLI from VS Code Local. Integration targets
remain separate from requested content selection.
Historical clients and layout
legacy::layout
fixes the historical client set to Claude, Copilot and OpenCode. An empty
recorded list expands to those three in that order. Explicit known clients
retain first-occurrence order and produce one candidate each; unknown spellings
produce diagnostics and remain in the cloned record. An unknown-only list
produces no candidates.
The nine kind/client path mappings use the installed alias. Current
pipeline::output
wrappers delegate to the same native PathBuf helpers through explicit
current-client conversion. Those helpers retain raw-name behavior for existing
pipeline callers. Only the projector validates names before converting paths to
forward-slash strings: empty names, dot, dot-dot, slash, backslash, colon and
control characters suppress candidates; spaces and Unicode remain representable.
Each candidate carries a client, relative entrypoint and relative resource search base. Skills and OpenCode rules are directory-backed; flat entrypoints use a sibling name directory for resources. These paths are relative to a later caller’s established legacy scope root. They do not establish existence, containment or ownership, enumerate resources or permit recursive adoption. Malformed sources do not erase otherwise known layout facts. Bundles and ancillary files have no candidates.
Diagnostics and later consumers
Diagnostics are structured data located at a global, item or bundle position.
The projector continues across malformed rows. Separate borrowed-key groups
collect duplicate (kind, name) identities and identical entrypoint candidates
across distinct rows, retaining every affected index. Collision locations anchor
at the first affected row; no record wins or disappears. Disjoint client lists
can duplicate an identity without sharing an entrypoint. Resource-tree overlap
analysis requires later verified resource enumeration.
The projection has no serialization contract, ownership record, artifact hash, mode, source-verification result or generic operation-blocking flag. Later planners determine which unresolved facts an operation requires. Source verification, historical rendering, resource enumeration, adoption, requested selection reconciliation, schema-2 lifecycle preservation, exact backup and transactional execution remain outside this boundary.
tests/legacy_projection.rs
pins typed preservation, all nine literal layouts, client order and candidate
identity, source compatibility, malformed and duplicate rows, unsupported
schemas and root-independent paths. Existing pipeline output and resource tests
cover the unchanged wrappers. These checks do not establish native Windows
execution, native discovery or actual artifact ownership.
Durable lifecycle origin
Requirements
Later writes must preserve initial-state origin and retained typed legacy history. Fresh initialization requires absent state and backup and creates private state without inventing historical bytes. Migration binds the exact original schema-1 bytes; reopened writes reject changed backup evidence. Missing historical provenance remains unknown and blocks bound application.
Architecture
Combined state optionally carries a strict origin record. fresh identifies
an originally absent lockfile and requires no legacy snapshot. migrated
requires a retained schema-1 snapshot and binds the original exact backup bytes
with backup_sha256 and backup_length. The constructor
StateOrigin::from_schema1_bytes hashes original bytes, including whitespace
and unknown schema-1 fields. Existing unbound internal schema-2 records remain
valid for inspection; an applying adapter cannot infer original provenance from
them.
Origin survives complete upsert and reference-retirement planning. The bound application boundary preserves it across writes, verifies the canonical backup against that durable evidence and refuses changed origin or legacy history. Fresh initialization has no fabricated schema-1 backup. See AD-0038 and the origin validation record.