# Stage 43 — sealed, crash-completable synthetic history for dualcoachtest/virtual_customer

## Status and boundary

Planning only; this supersedes stage 41. Execution is separately gated. The only later mutable target is `/home/cube/.hermes/profiles/dualcoachtest/data/customers/virtual_customer`, reached through registered `CustomerRuntime`, `CanonicalEventTransaction`, `EventStore.for_registered`, and `AdaptiveEventStore.for_registered` APIs. Never write JSONL directly; never call `recover()`, migration, truncate, delete, restore, service/gateway startup, Telegram, provider, approval, activation, reservation, delivery, or deployment. Product/source changes belong only in `/home/cube/.hermes/profiles/dualcoachtest/workspace/checkin_cli`; fixture copies are local test data, never the target profile.

Fixed scenario: profile `dualcoachtest`, customer `virtual_customer`, version `synthetic_30_day_v2`, seed `synthetic-30day-v2-dualcoachtest-2026-07-27`, KST days 2026-06-28 through 2026-07-27 (offsets 0..29), and deterministic recorded timestamp `2026-07-27T23:59:00+09:00`. Preserve incumbent ownership, offset 11 omission, 18 synthetic roots, and the offset-23 synthetic correction. The all-skipped classifier fixture is a zero-write no-op.

## RALPLAN-DR

**Principles.** (P1) existing bytes and non-target customers win over convenience; (P2) a durable intent, not a heuristic reader, is the sole authority to complete an interrupted write; (P3) canonical evidence precedes derived evidence under one lock order; (P4) deterministic fixtures are independent oracles, never runtime baselines; (P5) a local test may prove behavior but may never construct a live transport.

**Drivers.** Preserve the known 44/44 canonical baseline and all non-history profile artifacts; obtain meaningful adaptive evidence while retaining provenance; make every crash boundary append-only/retryable; make stale or foreign state stop before mutation; retain correction/retraction auditability.

**Options considered.**

| Option | Decision | Reason |
|---|---|---|
| Runtime digest capture as the seal | Rejected | It blesses a replacement that predates import. |
| Existing `CanonicalEventTransaction._append_pair_locked` plus `recover()` | Rejected | It can expose an unequal pair and recovery truncates. |
| Generic repair in snapshot readers | Rejected | Reads must not mutate or guess intent. |
| Canonical-only import and a later implicit derived repair | Rejected | A crash leaves visible evidence without required coverage. |
| One generation-keyed intent/state journal with an explicit mutating resume | Chosen | It gives a finite, auditable completion authority while readers fail closed. |
| Delete/rewrite rows for retraction | Rejected | It destroys audit evidence and violates append-only retention. |

ADR-ready decisions: **ADR-SH-01** immutable source fixture is a required precondition; **ADR-SH-02** only `resume_synthetic_history_transaction` repairs an accepted intent; **ADR-SH-03** coverage/tombstones are append-only materialization, not mutations of history; **ADR-SH-04** all public read APIs reject incomplete state; **ADR-SH-05** proposal testing is pure/local with construction-level transport tripwires.

## Immutable baseline oracle (not runtime capture)

Add these checked-in, read-only test/production-preflight resources under `workspace/checkin_cli/checkin_cli/fixtures/synthetic_history_v2/`:

* `expected-baseline-manifest.json` (schema `synthetic_expected_baseline_v1`), `baseline-wizard-events.jsonl`, `baseline-canonical-sequence.jsonl`, `baseline-source-days.jsonl`, and `baseline-source-day-intents.jsonl`. The latter four are the literal 2026-07-28 bytes inspected from the target and live outside customer data. Preflight compares target bytes/rows to these resources; it never creates or updates them.
* Required literal fixture file SHA-256s: events `6eb973a06db55df9d4ee1614fb98c2c32f676b240bb03f1b8cc6c706bea989eb` (44 lines/36,631 bytes); sequence `238bddc00904d1177aff8e1279871d11e8996b5d9b224f3af96e6dfda6b131f3` (44/11,518); source-days `bb3fd649945c56d0e43fdcf3542fbc5321365a7f3f268317e1d22072e647aed3` (44/21,425); source-day-intents `525edd99e6d1b5e464bfaa7a05c6482684a1efc3b9570c78a58bed0e6295c20b` (88/57,986).
* `expected-baseline-manifest.json` contains, for every ordinal 1..44, the exact newline-terminated event bytes and sequence bytes as base64, each row SHA-256, event ID, sequence number, event digest, sequence row digest, and `supersedes` edge; it also contains all 44 source-day and 88 source-day-intent raw rows/digests, their full-file digests above, and the immutable profile artifact path/byte/digest table. Tests reconstruct bytes from the manifest and assert the four fixture file digests and every row digest before use. This is the complete 44/44/44/88 pin; no datum is read from the target to become expected state.
* Required runtime immutable artifact entries include `profile.yaml` `4b413e043991824fa87b4cb91e8a1154a653bbdc05d8176747e21a22cafe8a14`, `customers/registry.json` `f68c6950f61014722e69fac8375477350a414fa520f839e0eedad308c8a33689`, feature epoch `caebe470e2f3bcd06c643315ba89fff24f80746895d9b04a78f01c6196b1d7b9`, overlay `20da8db4ab75c33056f6c4ead7b5962e1d9b6736e3ac14fa7dacf00162fb3038`, policy `78374d0d818b79de63fcd65b9a4c46858ae2d25dfbec58cfa4b92adc180419d9`, and every binding, registration, policy/audit/revision, authority, lifecycle, reservation, delivery, activation, credential, and destination artifact discovered by the sealed registered runtime. Bind the current ordinary-v1 registered binding digest `16f32bdeab4bd0c12ed8f73312efcc0f9d6ba06e9919cc32bc25cf21675546d7` (registry digest above, activation digest `dc01bba9bea725aa272645b9d4a25a1b8e55faf1fe5c23f2f70d910b82e7a35c`).

The terminal/reachability portion of that manifest is fixed now, using SHA-256 over each raw newline-terminated event record:

| Offset/day | terminal ID | terminal digest | complete predecessor path |
|---|---|---|---|
|16/2026-07-14|`gate_d_weight_only_20260714_00`|`71ae1a7edc8f8c09ae68d8d0318875b7300f98974f597a84be1ddef32a2fddde`|`gate_d_weight_20260714_00`|
|17/2026-07-15|`gate_d_weight_only_20260715_01`|`d71688ef27ad68ac52d56a750bd598fe1246b61a5bbc8afa3550d662b5f03332`|`gate_d_weight_20260715_01`|
|18/2026-07-16|`gate_d_weight_only_20260716_02`|`97e097d8ecb7fdfc46a47ae2cf56e5a42dae3408d56bc7b645cc9f91158ade45`|`gate_d_weight_20260716_02`|
|19/2026-07-17|`gate_d_weight_only_20260717_03`|`d84b0f082339859e5c8081ddd62b5e06129aba4e307c2516635b2b60b158362d`|`gate_d_weight_20260717_03`|
|20/2026-07-18|`gate_d_weight_only_20260718_04`|`25398dab5f88421396e229a1875e0f31adbc3a1bf988bbf0de9cf09b02a04ba1`|`gate_d_weight_20260718_04`|
|21/2026-07-19|`gate_d_weight_only_20260719_05`|`be5d405daf5964d97da346940e4f535592eed7dbcbe3bdd579fb713108cfb3fb`|`gate_d_weight_20260719_05`|
|22/2026-07-20|`gate_d_weight_only_20260720_06`|`bdadc62c936aea9a0b57f773d5a648a9dd11c134fe7d11a0ca9e6166420ee5c1`|`gate_d_weight_20260720_06`|
|24/2026-07-22|`gate_d_weight_only_20260722_07`|`ba25d41b42334c3fc91b6f42253f661724fced7207d58874e3a58687d1e2ef88`|`gate_d_weight_20260722_08 → wizard_503942f5a6d88af2edae18db → wizard_22ef723bac6b7bb13f8d2c39`|
|25/2026-07-23|`gate_d_weight_only_20260723_08`|`ff821279ef16a6a288ee5b41790f9649d4d560b85c78eccd5e6cbbfc915c3df2`|`gate_d_weight_20260723_09 → wizard_1e112bd4f7be389a23c9eee0 → wizard_c9419085bd202afef91ad239 → wizard_9de006395a30e33028b219a0 → wizard_a83b020c556586df5064a012 → wizard_503bc731cca60aa88ad673ae → wizard_404f88f875a7538feacb1c79`|
|26/2026-07-24|`gate_d_weight_only_20260724_09`|`e94294db33694e159bd5098eda627176288a5a734653a899793a9e85953bf758`|`gate_d_weight_20260724_07`|
|29/2026-07-27|`gate_d_weight_only_20260727_10`|`78f2d6b9bbd0dd4e59ba0224385aabbe07f3019acdcfc818d7c2a09439d46d34`|`gate_d_weight_20260727_10 → wizard_db1e6ef1769837e6cb37e76e → wizard_5a5dd3db90d8aec8303d5a9d`|

`SealedBaselineReader` opens all paths `O_NOFOLLOW`, rejects symlinks/replacement/permissions/partial lines, compares literal expected bytes and graph, captures `(dev, ino, size, sha256)`, and checks again after each lock and before every append. It does not use `_registered_customer`, `load_runtime_customer_registry`, initialization, or any recovery routine. Any mismatch, extra/fork/orphan edge, or terminal divergence is `stale_precondition`, zero writes.

## Deterministic vector oracle

Add literal newline-delimited canonical-JSON fixture `fixtures/synthetic_history_v2/golden-canonical-events.jsonl` with SHA-256 `56a49db5f96e44bb48bb5dd1f6d7f93e974cf72ac1a282409b7cdc66be47b54a` (29,775 UTF-8 bytes): 18 root records in ascending offset followed by the offset-23 correction. It is generated once by an independent fixture builder committed beside it; tests only compare implementation output to its bytes/hash, never regenerate expectations.

All JSON preimages use `json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode("utf-8")`; row/event bytes add exactly one LF (`0x0a`). SHA-256 is lowercase hex. `seed_digest=H("synthetic-seed:v2|"+seed)` = `2f319fff9a0d89189f8c943605a048972fc7a90013b04ca303551b65c471b936`; `scenario_digest=H("synthetic-scenario:v2|synthetic_30_day_v2|2026-06-28|2026-07-27|"+seed_digest)` = `34119a44c4a505433bbc192f1bd9a0a72fb7d2df9dc845ae21a5ab308ed332cd`. The serialized owner vector is the sorted array of `{offset,day,owner_kind,incumbent_terminal_event_id,incumbent_terminal_digest}` and has digest `3daa1af451f593b7bd0832ac6c04d5e93fa2c2cd04d0a1960ed7bd810697ad18`; therefore generation digest is `63d284286ed10e3a480b6480eea47b3662904caeb502ee5570fc9f65343b1520`.

Owner rules: incumbent offsets `{16..22,24..26,29}` with the table above; omitted `{11}`; synthetic `{0..10,12..15,23,27,28}`. The training offset set is exactly `{1,3,5,8,10,12,15,22,26,28}`. `U(o,field)=int.from_bytes(SHA256((seed_digest+"|"+format(o,"02d")+"|"+field).encode("utf-8")).digest()[:4],"big")/2**32`; field labels are exactly `calories`, `carbohydrate`, `protein`, `fat`, `weight`, `sleep`, `training`, `digestion`, `pain`. `N` is Python round of `lo+U*(hi-lo)` to stated precision; macros/calories are `int(round(target*N(...,3)))` because `ContractCheckin` accepts integers. Training choice is `[strength_upper,strength_lower,zone2,intervals]`, otherwise `rest`; digestion `[normal,bloated,reflux]`; pain `[none,knee_mild,shoulder_mild]`.

Targets are `(2530,282,150,80)` when `o % 7 in {1,3,5}`, otherwise `(2300,245,150,80)`. Actual macro factors are `.95..1.05`; calorie factor `.92..1.08`, except offsets 14..17 use `1.12..1.18`; weight is `77.6..78.4`, except 14..17 `78.4..78.8`, 18..22 `77.9..78.2`; sleep is `7.0..8.0`, except 14..17 `5.5..6.0`. Offset 7 omits weight/sleep. Offset 19 omits calories and makes adherence `contradictory/target_actual_pair_required`; offsets 14..17 are complete, `adherent=false`, `inadequate`; other complete roots call `derive_canonical_adherence_signal` with the exact target/actual maps and tolerance `10`. Root time is `dayT00:00:00+09:00`; correction time is `2026-07-21T00:01:00+09:00`.

For each root, `payload_digest=H(canonical_json({offset,day,check_in,manifest_without_root_payload_digest,adherence_signal}))`; `event_id="synthetic_"+H("synthetic-event-id:v2|"+generation_digest+"|"+offset2+"|root|"+payload_digest)[:24]`; `dedupe_key=H("synthetic-dedupe:v2|"+generation_digest+"|"+offset2+"|root|"+payload_digest)`. Correction preimage is canonical JSON of `{root_event_id,root_payload_digest,check_in_with_body_weight_kg_78_2}` and uses role `correction`. Exact member IDs are offsets `0,1,2,3,4,5,6,7,8,9,10,12,13,14,15,23,27,28` = `synthetic_3a563e94607799232df76d77, synthetic_daa52ef84ad5e345f486aa65, synthetic_b13b6dc8003b7af36703f256, synthetic_52dee330b5235c28d79e7bf5, synthetic_6aaa5a27f27fe2d2136aac86, synthetic_a2c17d1e3ec0f6e0ffda3980, synthetic_0223d1b1416de8d6d72e914b, synthetic_28030f91dce6479704f40d4b, synthetic_b5399b824b49da77510b30f6, synthetic_9d122158ec435d4ba624aae6, synthetic_b16500c2579ad03d8bd95e47, synthetic_80a4113ca5a43b812c8fe401, synthetic_4d7d5feed81b33ff0ff699a3, synthetic_847a4d0b7e2053cdb59efe74, synthetic_e7fbd8de9094357635d2dde6, synthetic_a5d5104b012a39359bbbeeab, synthetic_aee275a866bddfb852608a53, synthetic_2a0dfc1304b6e723ebd22ebe`; correction is `synthetic_6a2a26ed2bc60126dc0a040c`.

## Contracts, paths, and transaction state machine

Extend `checkin_cli/models.py`, shared JSON schema, and strict profile models together: add frozen `SyntheticHistoryManifest`, `SyntheticHistoryRetraction`, `SyntheticCoverageRecord`, and `SyntheticCoverageTombstone`; add `EventType.SYNTHETIC_HISTORY_RETRACTED`, `Event.history_manifest`, `Event.adherence_signal`, and `Event.history_retraction`. Use `extra="forbid"` and discriminator validation: only root `history_imported` events have manifest/adherence and `provenance.source_type="derived"`; corrections have full check-in only; retractions carry only `history_retraction`; legacy records remain valid/non-projectable. The correction must use the golden ID above (the displayed stage-41 typo `...040c` is superseded).

Add `history_synthetic.py` for vector construction, seal loading, owner classification, and three narrow public methods: `import_synthetic_history(runtime)`, `resume_synthetic_history_transaction(runtime)`, and `retract_synthetic_history(runtime, reason)`. `validate_synthetic_history_state(runtime)` is read-only. There is no generic customer/destination argument. Receipts/report fields are allowlisted to schema/scenario/generation/owner-vector digests, state/reason, dates, counts, dispositions, and file/prefix digests; reject IDs, text, credentials, destinations, tokens.

Transaction-owned append-only journals, all private `0600` files under `nutrition-plans/`, are:

* `synthetic-history-pair-intents.jsonl`: `synthetic_pair_intent_v1` `{generation_digest,ordinal,kind,event_bytes_b64,sequence_bytes_b64,event_sha256,sequence_sha256,canonical_event_prefix_sha256,canonical_sequence_prefix_sha256,recorded_at_kst,intent_digest}`.
* `synthetic-history-pair-completes.jsonl`: `synthetic_pair_complete_v1` `{intent_digest,event_sha256,sequence_sha256,complete_digest}`.
* `synthetic-history-transactions.jsonl`: `synthetic_history_tx_v1` records `{generation_digest,owner_vector_digest,operation(import|retract),phase,canonical_member_digest,derived_plan_digest,tx_digest}`; phases are `CANONICAL_PREPARED`, `CANONICAL_COMPLETE`, `DERIVED_PREPARED`, `DERIVED_COMPLETE`, `RETRACTION_CANONICAL_COMPLETE`, `RETRACTION_DERIVED_COMPLETE`.
* `synthetic-coverage.jsonl`, schema `synthetic_coverage_v1`, one record per canonical event ID with `{canonical_event_id,root_event_id,observation_day,terminal_event_id,generation_digest,state(active|retraction),row_digest}`.
* `synthetic-coverage-intents.jsonl`, schema `synthetic_coverage_intent_v1`, prepare then commit rows `{intent_id,generation_digest,operation,coverage_row_digest,tombstone_digest,state(prepare|commit),row_digest}`.
* `synthetic-coverage-tombstones.jsonl`, schema `synthetic_coverage_tombstone_v1`, `{generation_digest,retraction_event_id,reason,root_event_ids(sorted unique),root_payload_digests(sorted unique),tombstone_digest}`.

All journal records use canonical JSON+LF and include their digest calculated without the digest field. Add these five paths to the allowed-delta section of the immutable manifest; they must start absent/empty and are otherwise append-only. Existing canonical event/sequence/source-day/source-day-intent journals are allowed only with baseline prefix identity plus this transaction’s exact expected suffix.

Under `wizard/.events.lock`, `SyntheticCanonicalTransaction` first performs **intent-aware recovery admission**: it checks immutable prefix plus the final transaction-owned suffix, accepts only one uncompleted v2 pair intent whose deterministic bytes/hash/prefix/ordinal/generation match the golden candidate, and suffix-completes intent → event → sequence → complete marker by append-only `write_all`, flush, fsync. Empty/exact-prefix/exact-complete candidate states are legal; all other fragments are `torn_pair_stop` unchanged. Only then does strict `CanonicalEventTransaction.read_snapshot_readonly` run. Ordinary loaders, proposals, gateway, projection, and `validate_synthetic_history_state` reject any partial pair, uncompleted transaction, unequal row count, unknown intent, malformed suffix, or incomplete derived intent; none invokes recovery.

After every append revalidate (a) immutable baseline prefix `(dev,ino,size,digest)` and (b) each owned journal as immutable prefix plus the exact byte suffix represented by the durable intent/complete records. This is the only exception to whole-file equality; a foreign append/replacement fails closed. Lock order is exactly canonical lock → `nutrition-plans/.adaptive.lock` → transaction-owned journal locks in lexical pathname order; never acquire canonical while adaptive is held.

Legal durable crash states are: `ABSENT`; `PAIR_INTENT`; `EVENT_SUFFIX`; `SEQUENCE_SUFFIX`; `PAIR_COMPLETE`; `CANONICAL_COMPLETE`; `DERIVED_PREPARED`; `DERIVED_COMMITTING`; `DERIVED_COMPLETE`; and the equivalent `RETRACTION_*` states. `resume_synthetic_history_transaction` is the only mutator admitted from any nonterminal legal state and is idempotent by byte equality. It completes canonical pairs first, then, while retaining canonical then adaptive locks, appends transaction `DERIVED_PREPARED`, coverage prepare rows, coverage/tombstone rows, coverage commit rows, and transaction terminal record—each suffix-completed from its durable deterministic intent. Canonical COMPLETE with absent/mismatched derived plan is incomplete, not visible to readers. Any different payload/timestamp/owner vector/dedupe/foreign child is `CONFLICT` zero-write. `import` calls resume after sealed preflight; replay terminal import is byte-identical. Retraction validates COMPLETE member set/digests, canonically appends exactly one typed retraction through the same pair protocol, then resumes the derived tombstone transaction; it never removes rows. All-skipped appends nothing and retraction returns `nothing_to_retract`.

Replace raw source-day ID equality with `resolve_canonical_coverage` in `adaptive_nutrition.py`, projection, and `AdaptiveOperatorService._validate_journal_consistency`. Coverage ID set must equal canonical sequence IDs. Resolver exposes active evidence only for a complete generation, terminal resolved root/correction, and no valid tombstone; it preserves legacy normalized rows. Rename the mutating method to `reconcile_synthetic_history_transaction`; reserve `validate_synthetic_history_state` for read-only validation. The mutator creates coverage for every new canonical member, including correction and retraction, without changing the sealed 44/88 prefixes.

## Execution slices and acceptance criteria

1. **Fixture/contracts:** add the sealed baseline/golden resources, models/schema unions, and pure vector builder. Acceptance: literal fixture hashes above, all model/schema discriminator negatives fail, 18 roots plus one correction exactly match golden bytes, deterministic decision fixture produces `Decision.MAINTAIN` with `calorie_changing=false`.
2. **Canonical protocol:** extend `store.py` with private synthetic pair-intent completion without changing legacy append semantics or exposing `recover()`. Acceptance: every injected partial-write/flush/fsync boundary converges append-only to the golden 19/19 rows; malformed/ambiguous tails do zero writes.
3. **Derived protocol/resolver:** implement the transaction journal, mutating reconciler, read-only validator, coverage/tombstone fold, and gateway use of the shared resolver. Acceptance: no reader admits incomplete state; after import coverage equals sequence; after retraction no synthetic root or offset-23 correction is active; second reconciliation/retraction is zero delta.
4. **Command/report:** add a fixed-profile/customer CLI command that invokes only the sealed APIs, produces allowlisted receipt/report, and contains construction-level live-path tripwires. Acceptance: copied fixture only; no transport/provider/gateway/deployment object is constructed.

## Verification matrix

**Unit:** exact vector/hash/preimages and training offsets; owner table and all-skipped case; every illegal model discriminator; duplicate byte equality versus conflict; baseline fixture reconstruction, all row/graph/prefix/artifact pins; every canonical and derived suffix state; incomplete-state reader rejection; coverage/tombstone membership/folding; receipt/report redaction.

**Integration on copied registered fixtures:** real 11 terminal chains select 18 roots + correction; every terminal/row/edge/artifact replacement fails zero-write; each canonical and derived crash boundary resumes to exact bytes; concurrent stale/foreign writer and lock inversion fail; allowed suffix revalidation accepts only owned suffix; import/replay/retract/replay; real gateway consistency checks before/after tombstone.

**Local E2E only:** monkeypatch sealed paths to copied fixture; import → `reconcile_synthetic_history_transaction` → read-only validation → projection → typed safety → approved-artifact load → pure adaptive proposal and assert `maintain`; retract → resume/validate → no active synthetic evidence. Tripwire all Telegram/provider/gateway startup/coordinator production proposal/approval/activation/delivery/reservation/deployment constructors and calls. No endpoint is contacted.

After every success, no-op, conflict, injected failure, replay, reconciliation failure, and retraction assert byte equality of every immutable artifact and baseline prefix; allowed journals assert prefix identity, exact expected suffix/count, valid schema, and no truncation. Run focused `pytest` files for models, store, adaptive nutrition, history synthetic, and customer-admin gateway integration, then the local E2E subset; record actual commands/results in the final report.

## Pre-mortem

1. **Baseline changes before execution.** Sign: expected-byte/edge mismatch. Mitigation: immutable fixture equality under lock; stop zero-write.
2. **Power loss makes a pair or coverage half-visible.** Sign: uncompleted durable intent. Mitigation: sole explicit resume state machine; readers reject it.
3. **An owned append masks a foreign mutation.** Sign: suffix differs from durable intent. Mitigation: immutable-prefix plus exact-owned-suffix revalidation after every append.
4. **A correction remains active after retraction.** Sign: resolver active set contains tombstoned root. Mitigation: root-keyed tombstone folds correction coverage and real validator test.
5. **Dynamic time changes bytes.** Sign: golden mismatch. Mitigation: fixed recorded timestamp and canonical UTF-8 serialization.
6. **A local test reaches a live component.** Sign: tripwire invocation. Mitigation: copied roots plus construction/call tripwires.

## Execution gate

Do not execute against the target until this revision is approved, fixture hashes and source review match the plan, all listed local tests pass, and final command scope proves exactly `dualcoachtest/virtual_customer`. Any seal, vector, lock, or transport mismatch is a stop condition, never an in-place repair.
