| 1 | { |
| 2 | "status": "Revision2, addressing actual claude-fable-5-1 CHANGES REQUIRED on9ece6a7; independent re-review required. No runtime authority enabled.", |
| 3 | "task": "Home61: bind historical-empty schema recognition to independently enrolled, signed and verified native launches, and repeat it in readonly result verification.", |
| 4 | "source_baseline": "549a277773e03c42166c3ebde84589b4a2156359 on feat-legacy-schema-profile. This design branch starts from main7d68e87 as AGENTS requires; baseline implementation is a dependency, not implicitly approved for merge.", |
| 5 | "observed": { |
| 6 | "at": "2026-09-11T19:49:10Z", |
| 7 | "profile": "legacy-directory-v1", |
| 8 | "match": true, |
| 9 | "legacy_credentials_present": false, |
| 10 | "limits": "One diagnostic read snapshot only. Six legacy-only tables were empty; shared sessions were not part of the occupancy test. Diagnostic results confer no authority and are never read by the operator to select a profile." |
| 11 | }, |
| 12 | "decision": "Use explicit versioned schema commitments. Do not autodetect, accept extra ledger entries generically, or use an environment flag by itself to select historical compatibility.", |
| 13 | "catalog": { |
| 14 | "profiles": [ |
| 15 | "credential-current-v1", |
| 16 | "directory-current-v1", |
| 17 | "directory-historical-empty-v1" |
| 18 | ], |
| 19 | "meaning": "Each immutable profile binds its role, full exact migration ID/checksum set, full exact SQLite schema objects (including ledger and replication objects), explicitly empty table set, and validation-policy version. A changed schema/policy requires a new profile identity and digest; names never silently acquire new meanings.", |
| 20 | "digest": "SHA256 lower-case hex over the exact embedded UTF-8 manifest file bytes, including its single trailing LF. Encoding is encoding/json.MarshalIndent(manifest, \"\", \" \") plus LF, with standard HTML escaping enabled, not RFC8785 or go-json-experiment Deterministic. Fixed struct field order: version, id, role, validation_policy, migrations, objects, empty_tables. Migration fields: id, checksum; object fields: Type, Name, Table, SQL (matching current frozen schema objects). Sort migrations by bytewise ID, objects by Type then Name, and empty_tables bytewise; use non-null arrays, version integer1, no omitted fields, duplicate entries or arbitrary maps. Strict decoder rejects unknown/duplicate fields and compares exact re-encoded bytes to the embedded source. Golden bytes/digests freeze this convention; do not normalize whitespace before hashing.", |
| 21 | "historical_empty_tables": [ |
| 22 | "people", |
| 23 | "signin_links", |
| 24 | "passkey_credentials", |
| 25 | "passkey_challenges", |
| 26 | "passkey_pending", |
| 27 | "passkey_recovery_codes" |
| 28 | ], |
| 29 | "sessions": "Preserve shared sessions and data exactly. Existing readonly checker does not scan session rows: checked immutable database bytes provide non-mutation evidence, while enrolled key fingerprints prove only key continuity. Rows minted by the earlier combined Home can remain valid directory sessions until their normal expiry; this change does not revoke them or establish their historical custody. Any session cleanup/custody transition needs separate review.", |
| 30 | "separation": "Keep diagnostic_profiles and its accepted-embed guard unchanged. New admission manifests are a separate catalog; do not import diagnostic result types, log readers or startup state into admission. A bounded pure schema comparator can be shared after review, but observations cannot authorize profile selection.", |
| 31 | "fresh_consistency": "Existing compatibility_profiles/credential.json and directory.json remain the mounted fresh schema source of truth and keep their current exact embed guard. New catalog fresh manifests coexist as commitments to those exact object arrays, plus the same compiled migration/checksum sets. Mandatory equality tests compare each fresh manifest to its existing frozen file and role migration replay; any drift fails CI. Stage A does not replace or edit the existing files. Historical manifest independently freezes the proven diagnostic objects and provenance, with mandatory equality and replay tests; diagnostic output is never consumed.", |
| 32 | "comparator_location": "Private admission-only schema checker lives in internal/home/schema_commitment_linux.go and is reachable only from tests during stage A. It uses the selected catalog manifest and bounded exact comparison/occupancy logic. No imports from operator into legacy_schema_diagnostic.go and no calls into admission from diagnostic startup. Existing mounted operatorSchemaCheck and compatibility checker stay unchanged in stage A. Stage B may make both real adapters call the same admission checker only after its own design/source review and canary gate; no silent comparator replacement in stage A." |
| 33 | }, |
| 34 | "enrollment": { |
| 35 | "new_version": "InstallationPlan version2 adds schema_profile and schema_manifest_digest to each role target. Closed JSON and exact role/profile/digest matching. Version1 retains its existing fresh-only semantics; it cannot authorize historical compatibility.", |
| 36 | "authority": "The existing EnrollPlan is local preparation only. Its encrypted signed journal commits the profile, release artifact and protected environment digest, but does not assert owner approval, held fence, worker eligibility or correct key custody.", |
| 37 | "immutability": "Prepared operations must agree with the enrolled target. Never rewrite an existing plan, fill missing fields from an inspected database, or overwrite an in-flight/ambiguous operation to change its profile. A profile transition requires a separately reviewed reenrollment/lifecycle operation; none is introduced here.", |
| 38 | "manifest_availability": "The full known manifest must be included in the reviewed release. Unknown IDs, digest mismatch, wrong role, partial profiles, empty fields and attempts to select historical on a credential role refuse.", |
| 39 | "release_changes": "A new additive migration requires a new profile identity/digest and reviewed release enrollment. Existing v2 plans remain pinned and refuse the new schema; this matches their existing release/artifact immutability. No automatic plan upgrade or reenrollment verb in this task." |
| 40 | }, |
| 41 | "signed_request": { |
| 42 | "version2": "Add the same schema_profile and schema_manifest_digest pair to version2 operator Authorization and version2 InspectChallenge. Verify equality with independently pinned expectations before database open. Include them in signature input, authorization head and journal replay checks; inspect uses its existing distinct signature context.", |
| 43 | "version1": "Use explicit version-specific wire structs/decoders and version agreement matrix; retain exact v1 canonical bytes, signature contexts and recovery behavior. Golden captured v1 fixtures verify unchanged decoding/signature input; do not depend on fresh ECDSA signatures reproducing bytes. No downgrade/fallback after any v2 refusal.", |
| 44 | "pins": "Extend verified configuration with explicit profile commitments. A v2 request must match v2 protected command configuration and the enrolled plan. Do not trust a signed field alone: a trusted provisioner key may sign for several targets, so role/origin/installation/registry/device/action binding and exact enrolled profile all still apply.", |
| 45 | "recovery": "Retain existing authentic expired request recovery semantics. Repeat the selected exact schema and occupancy check before returning an idempotent result; unexpected drift or new legacy rows remains refused even on replay. No resigning or profile substitution for ambiguous operations.", |
| 46 | "version_matrix": { |
| 47 | "operator_input_v1": "InstallationPlan1 -> commandConfig1 -> wrapper.version1 -> envelope.v1 -> Authorization.version1. No schema commitment fields; existing fresh-only behavior. Exact existing wire bytes unchanged.", |
| 48 | "operator_input_v2": "InstallationPlan2 -> commandConfig2 -> wrapper.version2 -> envelope.v1 -> Authorization.version2. The generic signature envelope stays v1 and existing signature context stays operator-input/v1; payload schema and wrapper become v2. Schema commitments mandatory and equal to enrolled/protected pins.", |
| 49 | "operator_inspect_v1": "InstallationPlan1 -> commandConfig1 -> envelope.v1 -> InspectChallenge.version1; existing distinct operator-inspect/v1 signature context.", |
| 50 | "operator_inspect_v2": "InstallationPlan2 -> commandConfig2 -> envelope.v1 -> InspectChallenge.version2; same distinct operator-inspect/v1 context, mandatory equal profile commitments. No wrapper around inspection.", |
| 51 | "mismatches": "Refuse v1 request under v2 config, v2 request under v1 config, v2 plan signing v1 authorization/challenge, and v1 plan signing v2 authorization/challenge; also refuse envelope.v2, mismatched wrapper/payload versions and commitment fields on any v1 wire.", |
| 52 | "operation_plan": "OperationPlan stays version1 with existing input formats. The enrolled installation plan alone selects authorization protocol version. Operation input cannot choose a protocol/profile or override the enrolled target.", |
| 53 | "launchctx": "These application protocol versions are unrelated to launchctx mutable-context v1 and readonly-context v2. Keep platform launch wire unchanged." |
| 54 | } |
| 55 | }, |
| 56 | "launch_binding": { |
| 57 | "operator": "The platform launch already binds fd0 input SHA256, artifact digest and protected environment envelope digest. Keep its shared transport wire unchanged. The independently verified platform enrollment/owner/fence/lease path must bind those exact values to the plan, including the environment containing protected command profile pins. Home verifies signed input and matching protected pins before constructing the database connector.", |
| 58 | "missing_authority": "Current descriptor checks and a non-nil callback do not establish that platform authority. Until the runtime integration verifies the plan/environment/artifact/attempt binding plus isolation/IAM eligibility, no production code path may select the historical profile. Do not add a HOME_ALLOW_LEGACY flag or relax the currently mounted operatorSchemaCheck.", |
| 59 | "readonly": "Readonly expectation gains the exact immutable schema commitment inside its verified launch/enrollment evidence, not an unsigned optional argument. It must agree with the operation commitment and enrolled target on both pre-open and final verification. Also retain pinned database bytes, inode/device, artifact, key fingerprints, operator state/head, witness, generation and attempt identity checks.", |
| 60 | "unavailable": "The readonly production verifier is not implemented today. Keep the checker unmounted and historical operator selection unavailable until the verified adapter exists. Synthetic tests may exercise a private implementation, but must not be described as runtime authorization.", |
| 61 | "readonly_uninitialized": "Readonly compatibility continues to require initialized operator state and an enrolled witness. It is a post-operation/resume check, not initial adoption preflight. Uninitialized historical state is refused by this checker; initial adoption uses the separate approved signed inspection/operator transaction with fresh owner/fence/lease authority. No relaxation of state/witness checks." |
| 62 | }, |
| 63 | "database_checks": { |
| 64 | "before_mutation": "After verified launch selection, run exact ledger/schema checks and all six empty-table EXISTS checks on the same connection inside the existing BEGIN IMMEDIATE transaction before replay or mutation. Keep foreign-key checks, replication shape, row and byte budgets, state/sequence/key checks. No schema migration or data cleanup from operator/checker.", |
| 65 | "readonly": "Use the same selected immutable manifest and occupancy conditions in the same readonly transaction. Run all existing semantic and key-continuity checks as well. The two paths must not select profiles independently from observed database state.", |
| 66 | "bounds": "Use diagnostic-grade SQL CASE type/byte limits before materializing schema/ledger strings: IDs/names/table names256 bytes, type16, checksum64, SQL65536, at most1000 rows using LIMIT1001. Coalesce SQLite autoindex NULL SQL to empty before checking. Reject unknown/missing/duplicate/oversized/partial/changed objects. Occupancy queries select only EXISTS(SELECT1) over constant table names after exact schema equality.", |
| 67 | "cleanup": "Honor bounded cancellation; check all row and transaction closure errors. Any uncertain BEGIN failure requires connection eviction before returning it to a pool; failed rollback also evicts. Do not claim a successful unchanged-byte result until readonly driver and descriptor closure plus final byte comparison and authority recheck succeed.", |
| 68 | "preservation": "Serving migrations stay role-specific and additive. New directory databases retain their fresh profile; never install credential tables to make a new directory match the historical profile. No deletion, rekey, session mutation, table drop, ledger rewrite or historical credential copy." |
| 69 | }, |
| 70 | "stages": [ |
| 71 | { |
| 72 | "stage": "A: offline commitments and schema checker", |
| 73 | "deliverable": "A: unmounted primitives only. Private v2 manifest, plan, authorization/inspection decoders and constructors plus private schema checker, exercised only by synthetic tests. No production CLI, journal, command, serving or readonly call-site changes. Existing parseCommandConfig remains version1-only; public Verifier.Verify refuses wrapper/envelope/authorization v2 and cannot call private v2 decoders; EnrollPlan/prepareRequest keep version1-only. No v2 fresh acceptance is shipped here. Future stage B connects the private machinery under its own reviewed integration. This does not complete task61.", |
| 74 | "guard": "Source-level AST dependency guard roots at cmd/home main, RunOperatorCommand, RunCommand, parseCommandConfig, Verifier.Verify, SignRequest, EnrollPlan, prepareRequest and inspectCompatibilityDatabase; fail if any reachable production path references private v2 constructors/decoders or historical admission catalog/checker. Explicitly freeze existing version1-only dispatch checks. Tests may call private constructors. Do not treat a single historical allowlist as isolation." |
| 75 | }, |
| 76 | { |
| 77 | "stage": "B: verified adapter integration", |
| 78 | "deliverable": "After platform126/132 prerequisites, connect protected environment and exact signed request/enrolled target to opaque verified profile selection. Bind readonly pre/post expectations and make the historical profile reachable only through this path. Independent design/source review and exact CI before deployment." |
| 79 | }, |
| 80 | { |
| 81 | "stage": "C: observed execution and recovery", |
| 82 | "deliverable": "Verify isolation including console, enroll authority through reviewed lifecycle, execute a bounded approved operation, repeat readonly checks and prove restore/recovery before public gateway cutover. Report only actual verified deployments." |
| 83 | } |
| 84 | ], |
| 85 | "tests": [ |
| 86 | "Exact frozen fresh credential, fresh directory and historical-empty profile replay; prevent manifest/provenance/digest drift.", |
| 87 | "Every wrong/unknown/missing profile or digest; wrong role; v1 with legacy field; v2 missing commitment; duplicate/unknown JSON members; noncanonical representations; unchanged v1 signature bytes.", |
| 88 | "Tampered signature/profile/digest; signed request for another enrolled target; inspect commitment mismatch; plan journal replay substitution; same key across multiple profile targets.", |
| 89 | "Individually occupy each legacy-only table; preserve nonempty shared sessions and unrelated logical data; reject any occupancy on mutation, replay, inspect and readonly verification.", |
| 90 | "Exact ledger mismatch, modified checksum/comment, missing/extra/partial objects, triggers/views/indexes, malformed/oversized metadata, foreign-key/replication drift and resource limits.", |
| 91 | "Post-diagnostic occupancy and post-operation drift still refuse; no diagnostic output or request flag can reach admission.", |
| 92 | "Failure before transaction writes or cached result return; BEGIN uncertainty and rollback failure evict; cancellation and closure errors refuse; readonly final bytes unchanged.", |
| 93 | "Guard that production dispatch remains fresh-only until the real verified adapter is connected. Tests for adapter must cover missing/stale/incorrect plan, owner approval, held fence, lease, environment, artifact, attempt identity, profile and readonly expectation.", |
| 94 | "Required full Home tests, race, formatting, vet, build, exact source CI and Fable source review for each implemented stage; no runtime or public-gateway claim from codec tests.", |
| 95 | "AST reachability/source guards prove v2 constructors, historical manifests and admission checker are unreachable from all mounted command, CLI, journal and readonly roots in stage A; signed v2 input and v2 config refuse at current public entrypoints.", |
| 96 | "Golden manifest exact-byte SHA256, re-encoding equality, fresh frozen-profile equality and historical source-provenance equality; ordering/escaping/trailing newline mutations fail.", |
| 97 | "Explicit version matrix including envelope staying1, mixed wrappers/payloads, cross-plan/config request versions, both mutation and signed inspection, and OperationPlan unable to select protocol version." |
| 98 | ], |
| 99 | "platform_work_to_check": { |
| 100 | "126": "isolated-exec build readiness, dev-fleet rollout, explicit migration of existing instances, observed nested/ready layout and peer data/process isolation including the console. External routes need supervisor isolation evidence. Revert/downgrade must refuse with active worker authority.", |
| 101 | "132": "Worker enrollment/authentication and head lifecycle CAS, independent IAM writer boundary, eligibility checks before private input/token use, accepted result reconciliation and revocation/closure/reclaim. Merged codec/store7fdcb6d is inert and does not supply this runtime." |
| 102 | } |
| 103 | } |
| 104 | |