I read this as a read-only root-cause investigation of the F2 outbox residue - I’ll trace runtime ownership and cleanup coverage against the exact live profile, then return a decisive verdict and an atomic fail-closed transaction/test design without changing state.


The live ledger is not unrelated: every one of its 26 records is bound to the target session and route. I’m now narrowing the missed ownership edge by reading the installed cleanup/inventory and outbox implementations, plus enumerating the outbox’s lock/ledger siblings and archive manifests.


The defect boundary is now clear: cleanup supports only customer-directory files, key-in-path candidates, three explicitly parsed activation/schedule files, and one draft-generations projection. The outbox is session-keyed rather than customer-keyed, so both discovery paths report zero even though bootstrap evidence binds that session to this customer. I’m doing the final integrity/read-only validation and shaping the failing-first regression and rollback contract.


I read this as a read-only root-cause report for F2 - I traced the live residue through the installed runtime and cleanup implementation, validated it cryptographically, and designed the smallest safe remediation and regression test.

## Verdict

Assuming:

- **H1:** the outbox represents active target workflow authority.
- **H2:** it is historical or unrelated residue.

**Decisive result:** it is **terminal historical evidence belonging exclusively to the cleaned target**, not an active/pending workflow and not unrelated.

- H1 is false operationally: all records are `COMMITTED`; none are dispatchable or awaiting reconciliation.
- H2 is only partly true: historical yes, unrelated no.
- F2 is a real cleanup coverage defect. The target’s committed publication evidence survived because it is keyed by `session_id` and route, while cleanup discovers shared data by `customer_key`.

## Exact live observations

Profile:

`/home/cube/.hermes/profiles/dualcoachtest`

Target binding from the archived bootstrap ledger:

- customer: `task26_claim_20260818145508_1b96b23d`
- session: `cb_mYUoMIsk_CRzSDKYpPE9dg`
- user/chat/topic: `8527916639 / 8527916639 / 0`
- state at cleanup: `AWAITING_ACTIVATION`
- expiry: `2026-08-18T15:25:08.385071+00:00`

Live outbox `ledger.json`:

- schema: `telegram-nutrition-onboarding-publication-outbox-v2`
- size: 17,668 bytes
- SHA-256: `0d79c3e461a0401265d3033b364eb8ec4323b5614386693231d003c7b312a947`
- records: 26
- sessions: exactly `{cb_mYUoMIsk_CRzSDKYpPE9dg: 26}`
- routes: exactly `{("8527916639", "0"): 26}`
- roles: exactly `{customer: 26}`
- states: exactly `{COMMITTED: 26}`
- payload states: 22 `collecting`, 4 `customer_attestation`
- generations: `0..21, 23, 24, 25, 26`
- message IDs: `304, 306, ..., 346, 348, 350, 352, 354`
- literal customer key is absent from the ledger.

Using the installed `GatewayOnboardingPublicationOutbox(..., initialize=False)` validated all 26 records and their HMAC receipt integrity:

- primary records: 26
- emergency records: 0
- target owner callback: absent

The runtime service is currently inactive/dead with `MainPID=0`; no real profile process was found. The customer data directory and bootstrap source directory are absent. Registry state is disabled with `ai_processing_consent.granted=false`.

## Why this is terminal, not active

Installed runtime:

`gateway/platforms/telegram_nutrition_onboarding_publication_outbox.py`

- Lines 69-82 define the outbox as the durable publication receipt authority and all seven paths.
- Lines 217-239 transition a receipted publication to `COMMITTED` only after profile commit.
- Lines 241-247 expose only `RECEIPTED` records for recovery.
- Lines 642-655 initialize the complete outbox suite.
- Lines 657-684 inspect and integrity-check the suite.
- Lines 897-931 parse and reject malformed or ambiguous ledgers.

Therefore these records cannot initiate another send. They are nevertheless customer-related durable publication evidence and violate a zero-residue cleanup assertion.

## Why cleanup missed it

Installed cleanup code:

### `archive_customer_cleanup`

`checkin_cli/customer_cleanup.py`

- Lines 127-148 call `source_inventory()` using only the customer directory and explicit `candidate_paths`.
- The live manifest confirms:
  - `candidate: []`
  - `shared_ledger_projections: []`
  - inventory contains only three customer-directory files.
- Lines 204-218 trust `post_cleanup_authority_inventory()` before and after pruning.

### Source inventory

`checkin_cli/customer_cleanup_fs.py`, lines 62-101:

- Automatically walks only `data/customers/<customer_key>`.
- An external candidate is accepted only when its path is customer-scoped or its filename contains the customer key.
- `data/onboarding/telegram-publication-outbox-v1/ledger.json` satisfies neither rule.

### Post-cleanup inventory

`checkin_cli/customer_cleanup_inventory.py`, lines 162-261:

- Shared authorities are limited to `draft-generations.json`.
- The generic scan at lines 218-235 only recognizes files whose **relative pathname contains the customer key**.
- It does not inspect session-keyed outbox records.
- Consequently it returned `active_count=0`, `orphan_count=0`, and Task27 recorded a false PASS.

### Shared-ledger support gap

`checkin_cli/customer_cleanup_shared.py`, lines 16-20:

- The closed `SharedLedgerProjection` enum supports only:
  `data/owner-actions/draft-generations.json`.
- There is no publication-outbox projection or customer-to-session resolver.

## Files that must move together

The current directory contains exactly seven entries:

1. `.lock`
2. `.emergency.lock`
3. `.owner-callbacks.lock`
4. `.receipt-key`
5. `ledger.json`
6. `emergency.json`
7. `owner-callbacks.json`

They form one integrity/concurrency domain and must be archived as a directory, not as an isolated ledger:

- `.receipt-key` authenticates publication and callback receipts.
- The primary and emergency ledgers reconcile against each other.
- Owner callbacks bind to committed primary publications.
- The three lock files protect those stores and their inode identity.
- Runtime initialization and inspection expect the suite coherently.

If `ledger-v1-untrusted.json` exists in another profile, it must move with the suite too. Unexpected temporary or unknown entries should block cleanup rather than be silently included.

The staff-membership journal containing route `8527916639` is not an outbox sibling and cannot safely be deleted based on route alone; it is a profile-wide subscription journal.

## Smallest safe archive/prune transaction

Because every live outbox record belongs to this one target and both adjunct ledgers are empty, the smallest live remediation is an **atomic directory rename into a new archive operation**, not record-by-record rewriting.

### Fail-closed preconditions

Under the profile authority lock and all three outbox locks:

1. Service is fenced/inactive and no matching profile process exists.
2. Registry row exactly matches the expected customer, route, disabled state, and non-consent state.
3. Canonical cleanup journal is committed and its archive verifies.
4. Archived bootstrap evidence uniquely binds target customer → target session → route.
5. Customer and bootstrap source directories remain absent.
6. Runtime inspection validates all HMAC-backed outbox records.
7. Every primary record:
   - has the expected session and route,
   - has role `customer`,
   - is `COMMITTED`.
8. Emergency and callback ledgers are empty.
9. Entry set is exactly the expected seven files; no symlinks, hard links, foreign owners, permissive modes, temporary files, or foreign records.
10. Re-stat every entry after reading and require unchanged device/inode/size/mtime/hash.
11. New archive destination is absent and on device `66306`, matching the source.
12. No existing committed Task27 archive is modified.

### Transaction

1. Write and fsync a new `PREPARED` manifest containing every entry’s identity, size, mode, and SHA-256 plus the exact customer/session/route binding.
2. Atomically rename the entire directory to:
   `data/task27-final-cleanup/archives/<new-op>/payload/data/onboarding/telegram-publication-outbox-v1`
3. Fsync source and archive parents.
4. Revalidate the archived suite with `initialize=False`, verify all manifest hashes, and confirm the live source path is absent.
5. Append/fsync `ARCHIVED_VERIFIED`, then `COMMITTED`; freeze the archive read-only.

A later runtime start may create a fresh empty suite and a new receipt key through `_prepare_paths`; it cannot recover these archived records.

### Rollback

Before commit, any verification or journal failure must rename the unchanged archived directory back to the original path and fsync both parents. Rollback is permitted only if:

- the original path is still absent,
- archive identities still match the prepared manifest,
- service remains fenced,
- no fresh outbox was created.

Otherwise stop fail-closed and require operator recovery. After `COMMITTED`, rollback must be a separate authenticated operation.

## Failing-first test design

1. **Inventory regression**
   - Create a disabled/nonconsenting customer.
   - Create bootstrap/session binding and one valid committed publication record.
   - Assert `post_cleanup_authority_inventory()` reports `outbox > 0` and, after a committed cleanup journal, `orphan_count > 0`.
   - Current code fails by returning zero.

2. **Cleanup integration**
   - Run `archive_customer_cleanup()` against a customer whose only remaining authority is the session-keyed outbox.
   - Assert cleanup cannot commit while that authority is unarchived.
   - After outbox projection/archive support, assert the complete seven-file suite is archived and the live target authority is absent.
   - Current code commits while leaving the ledger live.

3. **Foreign-record fail-closed test**
   - Add one committed record for a second session/customer.
   - Assert whole-directory archival is rejected and no bytes move.
   - This prevents cross-customer deletion.

4. **Nonterminal and malformed cases**
   - Parameterize `DISPATCHING`, `RECEIPTED`, non-empty emergency ledger, callback records, unknown entries, changed inode/hash, and missing receipt key.
   - Each must block without mutation.

5. **Deterministic rollback**
   - Inject a fault immediately after atomic rename.
   - Assert exact source tree identity is restored, archive payload is absent, and no commit phase exists.
   - Use fault hooks/events, not sleeps or polling.

No profile, service, repository file, or external state was modified.