I read this as a diagnostic task: find the root cause of the `bootstrap ACTIVE lineage is invalid` failure and propose a focused test + fix without editing anything. I'll explore the Golden Path scripts, RoomBootstrapStore schema/transitions, the profile source, and related tests in parallel.

Let me start with a wide parallel sweep


Now let me read the key files in parallel


The test is at repo root, not under scripts. Let me dig into RoomBootstrapStore and the bootstrap ledger, in both the repo and the profile source


Now let me examine the cleanup archive behavior in the profile source, which is central to the mystery


The verifier reads `sessions[0].get("customer_key")`, but `_session_to_dict` nests `customer_key` inside `customer_draft` with no top-level `customer_key`. Let me confirm no other cleanup/prune path touches the live ledger and that activation keeps state ACTIVE


Confirmed: `source_inventory` only prunes files under `data/customers/{key}/` plus explicit `candidate_paths` (empty in the driver) — the shared bootstrap ledger is never archived/pruned. Let me verify the activation cutover uses the same ledger and no second session


The profile source `src-p` contains only `checkin_cli`; the runtime `gateway` modules (including `RoomBootstrapStore`) resolve from the repo. So `_session_to_dict` (repo, line ~392) defines the ledger shape. Let me pin the exact verifier lines


I have enough evidence. Let me compile the diagnosis.

<analysis>
**Literal Request**: Diagnose why `verify_source_golden_path.py` raises "bootstrap ACTIVE lineage is invalid" after an isolated Golden Path run, across the driver, verifier, RoomBootstrapStore ledger, cleanup archive behavior, and the profile source.

**Actual Need**: Determine the exact condition that fails under the driver fixture, whether the live ledger is correct (exactly one ACTIVE session), where the verifier should read the lineage, and propose a focused failing test plus the smallest correct fix.

**Success Looks Like**: A verifier/driver/production diff decision backed by file:line evidence, with PASS/BLOCK verdicts per sub-question and one RED test spec.
</analysis>

Evidence gathered (read-only; nothing run or edited). Key facts:

- Verifier condition: `scripts/verify_source_golden_path.py:329-330` reads `sessions[0].get("customer_key")` and `sessions[0].get("state")`.
- Ledger serialization: `gateway/platforms/telegram_customer_bootstrap.py:842-847` (`_session_to_dict`) emits `customer_draft` as a nested object (which holds `customer_key`) and `state` at top level — it never emits a top-level `customer_key`. Confirmed `RoomBootstrapSession.customer_key` is only a Python `@property` over `customer_draft.customer_key` (line ~144), not a serialized field.
- Profile source `src-p` contains only `checkin_cli/`; the runtime `gateway.*` modules resolve from the repo, and `verify_native`/driver import `checkin_cli.customer_cleanup` from `src-p`. The bootstrap ledger shape is defined by the repo gateway module.
- Cleanup path: `src-p/checkin_cli/customer_cleanup_fs.py` `source_inventory` only inventories `data/customers/{key}/**` plus explicit `candidate_paths`; `prune_sources` unlinks only those. `src-p/checkin_cli/customer_cleanup_inventory.py` `post_cleanup_authority_inventory` never counts the shared bootstrap ledger (its path contains no key). The driver calls `archive_customer_cleanup(..., shared_ledger_projections=(DRAFT_GENERATIONS,))` with empty `candidate_paths`, so the bootstrap ledger is never archived or pruned.
- Driver creates exactly one session (`source_golden_path.py` `store.prepare_rehearsal_customer_invite` once) and asserts it ends `ACTIVE`; cleanup then verifies a terminal inventory.

<results>
<files>
- /home/cube/projects/richard/hermes-agent/scripts/verify_source_golden_path.py - Lines 325-331: reads the LIVE bootstrap ledger and raises `bootstrap ACTIVE lineage is invalid` when `sessions[0].get("customer_key")` mismatches; this is the failing verifier condition.
- /home/cube/projects/richard/hermes-agent/gateway/platforms/telegram_customer_bootstrap.py - Lines 842-847 (`_session_to_dict`): the actual ledger session shape; `customer_key` is nested under `customer_draft`, not top level. Line 144: `customer_key` exists only as a property.
- /home/cube/projects/richard/hermes-agent/scripts/source_golden_path.py - Driver: creates exactly one bootstrap session, transitions through to `ACTIVE`, and runs `archive_customer_cleanup` with empty `candidate_paths`, leaving the shared ledger untouched.
- /home/cube/.cache/task26-strict-successor-1786976146/src-p/checkin_cli/customer_cleanup_fs.py - `source_inventory`/`prune_sources`: only per-customer files under `data/customers/{key}/` plus explicit candidates are archived/pruned; the shared bootstrap ledger is never moved or deleted.
- /home/cube/.cache/task26-strict-successor-1786976146/src-p/checkin_cli/customer_cleanup_inventory.py - `post_cleanup_authority_inventory`: never counts the shared bootstrap ledger, confirming no cleanup row touches it.
- /home/cube/projects/richard/hermes-agent/tests/gateway/test_source_golden_path_verifier.py - Existing verifier tests; the place to add the focused RED test.
</files>

<answer>
Root cause is a verifier field-access (projection) mismatch, not a driver or production defect.

- Should the native bootstrap ledger hold exactly one session after activation/cleanup? YES. The driver creates exactly one session for `client_001` and transitions it to `ACTIVE` (`source_golden_path.py`; `activate_customer_cutover` returns `BootstrapState.ACTIVE`). Cleanup does not archive or prune the shared ledger: `source_inventory` in `src-p/.../customer_cleanup_fs.py` only covers `data/customers/{key}/**` plus explicit candidates, and the driver passes none. So the live ledger remains with exactly one session.
- Canonical state string: `"ACTIVE"` (`BootstrapState.ACTIVE`, serialized via `session.state.value` at `telegram_customer_bootstrap.py:846`). The verifier compares against `"ACTIVE"` at line 329, so this clause passes.
- Where should the verifier read it: LIVE (`data/onboarding/telegram-customer-bootstrap-v1/ledger.json` at the profile root). It is a shared authority, never archived; the archive holds only per-customer files. The verifier already targets the correct live path.
- Which compound condition fails: the `sessions[0].get("customer_key") != customer` clause at line 329. The ledger session dict has NO top-level `customer_key` — it is nested at `sessions[0]["customer_draft"]["customer_key"]` (`telegram_customer_bootstrap.py:843`). So `.get("customer_key")` returns `None`, `None != "client_001"` is True, and the OR-condition raises. `len(sessions) == 1` and `state == "ACTIVE"` are both satisfied.
- Projection/archiving mismatch or retained row: no lifecycle row is retained and no archive misalignment exists. The defect is purely that the verifier expects a top-level `customer_key` field the production ledger never emits. No driver or production change is required.

Proposed focused RED test (in `tests/gateway/test_source_golden_path_verifier.py`): construct a bootstrap ledger with exactly one session serialized exactly as `_session_to_dict` does — top-level `state:"ACTIVE"` and `customer_draft` containing `customer_key` — and assert the lineage check passes for that customer. Recommended shape: refactor the four-line block (lines 328-331) into `bootstrap_lineage(bootstrap_doc, customer) -> str` (returns `session_id`, raises `ValueError` on violation), then the test feeds the real-shaped ledger and asserts it returns the expected `session_id`. Today that assertion fails because the predicate reads the non-existent top-level key.

Smallest correct fix: make `scripts/verify_source_golden_path.py:329` read the nested key with validation, e.g. replace `sessions[0].get("customer_key") != customer` with a lookup at `sessions[0]["customer_draft"]["customer_key"]` (guarding that `sessions[0]` and `customer_draft` are objects). This is a verifier-only change; no driver or production edit is needed.
</answer>

<next_steps>
1. Add the RED test to `tests/gateway/test_source_golden_path_verifier.py` per the spec above and confirm it fails on the current verifier.
2. Apply the nested-key fix at `verify_source_golden_path.py:329` (plus any extracted helper used by the test) and confirm the RED test goes green.
3. Re-run the isolated Golden Path driver+verifier and confirm `ACTUAL_SOURCE_GOLDEN_PATH_PASS`; expected no further production changes since the live ledger is factually correct (one ACTIVE session).
PASS: live ledger cardinality (1), canonical state string ("ACTIVE"), live-path read target (correct), and driver/cleanup leave the ledger intact.
BLOCK: verifier `line 329` reads a non-existent top-level `customer_key` (always `None`), falsely raising `bootstrap ACTIVE lineage is invalid`.
</next_steps>
</results>


Let me confirm the ledger digest/schema validation implementation and whether the verifier currently checks it


I read this as a finalization task: independently confirm or contradict the earlier diagnosis and add the native ledger digest/schema verification requirements. I've re-verified the ledger's serialization, digest computation, and the exact digest algorithm below.

## Independent finalization

I traced the ledger lifecycle end-to-end again and **confirm the primary diagnosis**: the verifier's `sessions[0].get("customer_key")` at `verify_source_golden_path.py:329` reads a field the production ledger never emits at top level, so the customer binding compares as `None != "client_001"` and the OR-condition raises `bootstrap ACTIVE lineage is invalid`. The live ledger is objectively correct.

### Confirmed sub-findings

- **Cardinality** — exactly one session. The driver calls `RoomBootstrapStore.prepare_rehearsal_customer_invite` once (`source_golden_path.py`), and cleanup never archives/prunes the shared ledger. `source_inventory` in `src-p/checkin_cli/customer_cleanup_fs.py` only inventories `data/customers/{key}/**` plus explicit `candidate_paths`; the driver passes none. The ledger survives intact.
- **State** — `BootstrapState.ACTIVE` ("ACTIVE"), written by `activate_customer_cutover` and asserted by the driver. The `state` field IS top-level (`telegram_customer_bootstrap.py:846`), so the verifier's `state == "ACTIVE"` clause passes.
- **Where to read** — LIVE: `data/onboarding/telegram-customer-bootstrap-v1/ledger.json`. Confirmed a shared authority, never archived. The query returns the correct live target.
- **No retained lifecycle row / no archiving mismatch** — the sole defect is the verifier's nested-vs-top-level accessor.

### Contradiction note (deliberate, minor)

I do **not** recommend a production/driver change — the ledger is correct. `session.customer_key` exists only as a Python `@property` (`telegram_customer_bootstrap.py:144`) over `customer_draft.customer_key`; it is not a serialized top-level field. Fixing production to emit a redundant top-level `customer_key` would change the ledger digest and contradict the schema; the right target is the verifier.

## Native ledger digest/schema verification requirements

These apply if the verifier must prove the ledger is authoritative (it currently does **not**):

1. **Schema authority** — require `ledger["schema"] == "telegram-customer-bootstrap-v1"` (`_LEDGER_SCHEMA`, line 33). The store enforces the ledger has exactly the key set `{schema, sessions, digest}` (`_read_unlocked`, lines 770-771); the verifier should mirror that and reject any member addition/removal.
2. **Digest recomputation** — the ledger self-authenticates: `digest == sha256hex(canonical({"schema": schema, "sessions": sessions}))` (`_write_unlocked`, lines 788-792; validated at lines 772-776). The verifier must recompute this exact digest from the loaded document and compare. This proves the ledger bytes were not mutation-edited post-write and that `sessions` is byte-identical to the original `_session_to_dict` output.
3. **Canonicalization contract (must match the store exactly)** — `_canonical_bytes` = `json.dumps(sort_keys=True, separators=(",", ":"), ensure_ascii=False, allow_nan=False)` then UTF-8 (`_canonical_json`/`_sha256`). A naive `json.dumps` with default separators will **not** reproduce the digest. The verifier already has a compatible `canonical()` (`ensure_ascii=False, sort_keys=True, separators=(",", ":")`), so it can feed `{"schema":..., "sessions":[...]}` and recompute. Note this canonicalization differs from the cleanup fd's `ascii_only` usage elsewhere — bootstrap digest must use `ensure_ascii=False`.
4. **Session field completeness** — to recompute the digest correctly the verifier must carry the full serialized session dicts unmodified (the digest in the ledger is over exactly what `_session_to_dict` produced, including `sid_hash`, `role_claims`, `recovery_attempts`, timestamps, `failure_code`, etc.). Trimming or re-ordering any field invalidates the recompute. Recommendation: derive `customer_key` from `sessions[0]["customer_draft"]["customer_key"]` without dropping/rewriting the session dict, so the digest check stays faithful.
5. **File integrity** — `private(bootstrap_path)` already enforces a single regular file, 0600, owned by the caller; this complements (not replaces) digest verification. `data/…/telegram-customer-bootstrap-v1/` is the live row, so the path identity is fixed by the ROOM bootstrap path constant.

Recommended verifier widening at `verify_source_golden_path.py:325-331`: (a) fix the nested read at line 329; (b) assert `schema` + recomputed `digest` per the store's canonical form before trusting `sessions`. The digest/schema checks can be folded into the refactored `bootstrap_lineage(bootstrap_doc, customer) -> str` helper so the single RED test drives both the binding fix and the ledger-authenticity requirement.

<results>
<files>
- /home/cube/projects/richard/hermes-agent/scripts/verify_source_golden_path.py - Line 329: reads non-existent top-level `customer_key`; does not currently verify ledger schema/digest.
- /home/cube/projects/richard/hermes-agent/gateway/platforms/telegram_customer_bootstrap.py - Lines 33, 770-776, 784-792: `_LEDGER_SCHEMA`, ledger shape set, and digest algorithm the verifier must mirror; lines 842-847 serialize the nested `customer_draft.customer_key`; line 144: `customer_key` is a property only.
- /home/cube/projects/richard/hermes-agent/scripts/source_golden_path.py - Creates exactly one session, transitions to ACTIVE, passes no bootstrap candidates to cleanup.
- /home/cube/.cache/task26-strict-successor-1786976146/src-p/checkin_cli/customer_cleanup_fs.py - `source_inventory`/`prune_sources` cover only `data/customers/{key}/**` and explicit candidates; the shared bootstrap ledger is never archived/pruned.
- /home/cube/.cache/task26-strict-successor-1786976146/src-p/checkin_cli/customer_cleanup_inventory.py - Post-cleanup inventory never counts the shared bootstrap ledger.
- /home/cube/projects/richard/hermes-agent/tests/gateway/test_source_golden_path_verifier.py - Destination for the focused RED test (nested-binding + digest/schema).
</files>

<answer>
Diagnosis confirmed and independent: the failure is a verifier accessor defect at `scripts/verify_source_golden_path.py:329`. The production ledger is correct (one ACTIVE session for `client_001` on the live path, untouched by cleanup). The verifier reads `sessions[0].get("customer_key")` (always `None`) where the canonical binding lives at `sessions[0]["customer_draft"]["customer_key"]`. No driver or production change needed.

Native ledger integrity requirements to fold into the verifier: schema must equal `telegram-customer-bootstrap-v1`, key set must be exactly `{schema, sessions, digest}`, and digest must equal `sha256hex(canonical_json({"schema": ..., "sessions": [...]}))` using the store's canonical form (`ensure_ascii=False, sort_keys=True, separators=(",", ":")`). Verifier fix should be a single refactor of lines 328-331 into a `bootstrap_lineage` helper that (a) reads the nested `customer_draft.customer_key`, (b) asserts schema + recomputed digest, and (c) returns `session_id`; the RED test drives all of it.
</answer>

<next_steps>
1. Add the RED test to `tests/gateway/test_source_golden_path_verifier.py` feeding a real `_session_to_dict`-shaped ledger (nested `customer_draft.customer_key`, top-level `state: "ACTIVE"`, embedded schema/digest) and assert `bootstrap_lineage` returns the expected `session_id`; confirm it fails today.
2. Apply the verifier refactor at `verify_source_golden_path.py:328-331`: nested-key read plus schema/digest verification using the store's exact canonicalization.
3. Re-run the isolated Golden Path driver+verifier; expect `ACTUAL_SOURCE_GOLDEN_PATH_PASS`. No production edit anticipated.
</next_steps>