All docs · North OS2026-09-15

Private work ledger: implementation plan

Status: in-progress · Author: North OS product engineering · Date: 2026-09-15 · Repo: north-os · Related: design spec, dependency design.

Current implementation scope: §61 governs PR #444 and supersedes stale completion/limitation claims below. Foundation only; not the whole feature, not merge-ready.

In progress. Plan review CLOSED at §58. Read §59 and §60 before anything else: §59 removes every personal-Clio-enrollment requirement (one admin-level organization connection only), and §60 cuts packet F entirely and supersedes all 41 references to /tasks/chats/new?review=work — that route is going away and is not a destination. §15.5's file and line totals are withdrawn, not updated. Preserve §26 staging-refuse (bg_init_config side-effect-free; marker≠DEPLOY_SHA refuse in bg_stage_release AFTER green-lit reuse :604–609 and after existing [ -e "$BG_RELEASE_DIR" ] refuse :610–612, before set_phase :615). Introduce no new discriminator (no BG_RELEASE_REUSED); leave existing BG_RELEASE_PROMOTED at :55/:607/:723 untouched. J1 edit at bluegreen.sh:727–738: source="${BG_RELEASE_DIR}/deploy/preprod/bin/${name}.sh" (do not phrase $BG_RELEASE_DIR as current fact; price inside J1's 190). The hook refresh runs at :1511, before staging at :1515, so it cannot read $BG_RELEASE_DIR; skip is the only safe form. bg_refresh_post_receive_hook marker skip stays load-bearing, untouched (return 0, no copy) when the marker ≠ DEPLOY_SHA, or when the marker is absent and the active floor minimumVersion ≥ 1; do not abort; bg_stage_release owns the only refusal. Sole live J contract: hook skip only; J1 helper-source edit priced inside J1's 190. Preserve skip-not-abort (hook only), one-statement digest, query-param /tasks/chats/new?review=work, assistant marker, S/A, MCP IDs-only, no firm who_am_i, HITL reject, personal-account+activity dismiss. Admission still accepts matter-filed threads. Ingest-connector set is type='clio' AND scope='organization' AND status='authorized'; revoked leftovers out; still no decrypt/refresh/who_am_i on firm ingest. Clio durable anchor is verified account (canonical regional origin + data.account.id) + activity ID; connector_id is a selection predicate and non-key provenance snapshot, not part of the unique/dismiss key. J stays three paths /610. With active floor minimumVersion ≥ 1, marker REQUIRED on the staging path: absence → bg_stage_release refuse, refresh skip; green-lit reuse unaffected; J3 absent-marker green-lit-reuse succeeds. No active floor → absence dormant. J3(e): run_deploy writes the marker for the SHA it materializes, simulating J2 (fixture-only; J2 stays the only production writer); (a)/(b)/(d)/(g) materialize a different SHA first; (e)'s absence arm removes or omits the seed. Complete map: 118 paths / 20,100 prospective lines. Recorded-time projection, canonical/dismiss keys, and exact GET require source_origin IS NULL OR source_origin = $personalVerifiedOrigin; NULL-origin rows stay in the projection and are keyed under the actor’s own verified origin — (11) refuses the named figure only, it never withholds a row (§58 Opus 1). Differing-origin rows are (6) coverage EXISTS, not keyed as personal, not in LIMIT 200; matching-origin and NULL-origin rows are not withheld. (11) NULL-origin complete-figure-unavailable do not withhold. Named key provider_metadata.personal_user_id. Enum stays 11 members. Do not add member (12). Prior privacy/consent/#432 and handoffs remain; no product/schema/deploy.sh/commit by this docs seat.

Deliverable: personal saved work episodes and evidence, explicit bounded reconstruction, my Clio time reconciliation, durable corrections and atomic combine/split, one structured review opened from Home and /matters, and copyable time/involvement/matter-update drafts. UI, product tools and MCP use the same owner-scoped service. No organizational dashboard, billing or provider write.

1. Authority, base and readiness

Remaining start barriers: fresh current-main source/caller review, independent plan-review pair and RLS re-review; effective synthetic grants under P1; a later factory schema slot; and deployment code handoff before activation. NOS-384 owns ONLY matters-page/table scope: F5 and any matters-page wiring await orchestrator handoff. NOS-384 handoff condition: restore marker-EXISTS exclusion in listChatsForMatter (no userId) plus its helper test; this lane does not keep that helper or add a cross-workspace import. F4's /tasks/chats/new Home/query entry belongs to this lane and has no NOS-384 hold; other implementation gates still apply. Cross-workspace messaging was blocked and the factory notified. Support/MCP policy S+A and adopted 73 / 8,700 baseline stay settled. Existing test accounts are chosen without identifiers. No product, account-call or git action by this documentation seat.

The 73 /8,700 map is adopted history. §§14–17 reached81 /10,750; §18 reached86 /11,150; §19 reached97 /12,750; §20 added3/1,650 for100 /14,400. §§21–22 add one EXISTING audit-boundary path and1,200 net prospective lines for101 /15,600. No unallocated provenance/SDK/owner/enrollment work, provider-version switch or global framework. These are planning allocations, not measured implementation or product-edit permission.

The provider report proves documented contracts, not effective grants. Source reads through §20.1 cover the pinned provider's actual hook arguments, raw adapter calls, consent/code/refresh timing and transaction behavior. Public documentation is corroboration only where it matches1.6.23. No provider, SQL race, deployment or browser behavior was executed by this docs seat.

2. Adopted baseline and newly required closure delta

Historical adopted baseline:73 hand-authored files /8,700 lines. Deltas: §14 +1/500; §15 +6/850; §16 +1/500; §17 +0/200; §18 +5/400; §19 +11/1,600; §20 +3/1,650; §§21–22 +1/1,200; §23 +0/100; §24 +0/300; §25 +1/80; §26 +0/0; §27 +2/480; §28 +1/540; §29 +1/340; §30 +0/80; §31 +0/180; §32 +0/240; §33 +0/200; §34 +0/200; §35 +0/200; §36 +1/200; §37 +1/200; §38 +0/200; §39 +0/200; §40 +0/280; §41 +0/0 (reconciliation: G 5,310→5,510 already in packet-table 19,620); §42 +3/200; §43 +0/0; §44 +2/80; §45 +0/0; §46 +1/40; §47 +4/160; §48 +2/80; §49 +0/0; §50 −2/80; §51 +0/0 (I 22→21); §52 +0/0 (I 21→22 restore); §53 +0/0. Current unique/line/output caps live only in §15.5 (118 unique with I=22). Generated migration/index outputs are separately counted.

Historical adopted allocation — superseded by §15.5, not current packet caps
Historical packet / ownerHistorical filesHistorical line allocation
A. Schema/epoch — Fable high8850
B. Identity/source transport — Fable high, sequential slices201,800
C. Ledger service/range reconciliation — Fable high31,400
D. Admission/private sharing/visibility — Fable high9800
F. Review UI/API — Fable high5800
G. Tests — configured writer @write, Astra high101,400
H. Human prose — Fable high normally; degraded prose uses Astra writer, never Sol/Luna3150
I. Direct authority/callers — Fable high; tests — configured writer @write151,500
Historical adopted baseline only738,700

Historical allocation decision: the adopted authority expansion redistributed work from the removed global credential/crypto/refresh framework to bounded enrollment, reconciliation, visibility and behavioral proof. It did not authorize later deltas or measure implementation fit. Range computation remains one shared service; exclusion remains the ordinary mutation union.

Implementation slices: use only §15.5's current B enrollment/source/refresh allocation. Stable B labels still skip removed B19. Stop on a measured complete-slice overrun and report the complete delta; never omit behavior or tests to fit an estimate.

G author is the configured writer seat: @write resolves to Astra high, with the full writer profile read,grep,glob,edit,write,bash. No hardcoded Sol-medium hybrid; @gates remains read/run-only. At pane creation AND harness launch set FACTORY_SEAT=writer and FACTORY_LANE=private-work-ledger. Launch omp --approval-mode yolo --model @write --thinking high --tools read,grep,glob,edit,write,bash --append-system-prompt <writer.md body>; append the full bounded behavior profile, never replace system instructions. Await readiness, successfully register the exact session with bun scripts/ledger/lib.ts --pane <exact-pane> --seat writer --lane private-work-ledger --worktree <canonical-worktree> --branch feat/private-work-ledger, THEN deliver its substantive brief. Missing descriptors stop work. Product code and prose workers are omp Astra seats per the 2026-09-08 standing rule, not new Claude panes. This documents launches; this seat launches nobody.

Historical file arithmetic: original 58 − worker-chassis/src/fence/refresh-credentials.ts + chat-runtime/src/message-visibility.ts =58; original I added fifteen =73. B18/unchanged generic persistCredentials and worker refresh remain; removed B22–B26/global crypto work stays removed. G1→G6, G2/G3→G5 and G7→G9 consolidation stays. Later additions are enumerated separately and summed only in §15.5.

Scope correction, not user-scope reduction: delete changes to packages/secrets/src/runtime-cipher.ts, kms.ts, aws-kms.ts, apps/connectors-api/src/routes/deps.ts and main.ts. They were required only by our invented 20-second all-in promise. Delete scoped Redis and injected global refresh/cipher factories too. Keep dedicated strict enrollment, Outlook-local bound state, canonical refresh safety, source-free generic tools and narrow signal forwarding at real source HTTP callers. Existing external credential/KMS latency is an explicit limitation, not unfinished implementation work.

3. Exact file/function map

Pre-transplant inventory, requiring fresh source verification. These 58 unique files plus I's original 15 retain the adopted baseline; prior source claims remain stale except the narrow refreshes in §§14–15. F4 Home/query wiring belongs to this lane and is NOT held by NOS-384. F5 and matters-page integration alone require its orchestrator handoff. Generated SQL/snapshot/journal remain A-only after a later factory slot. Stable G numbers identify behavior groups. Prepare ALL credentials before C's single absolute 20-second source deadline across both providers; no overall SLA/cache.

A. Schema: 12 required files

  1. packages/db/src/schema/work_episode.ts new: complete null-safe matter/duration/observation/dismissal CHECKs per spec §4; user/org ON DELETE CASCADE, matter-pair-only SET NULL; restrictive owner plus live membership/active-org RLS. Human/proposed/retained fields keep immutable enrollment_member_id snapshots. Version/receipt/lineage and staged_restructure remain; coordinator belongs to inputs. work_date is NOT NULL. Named index work_episode_org_owner_date_active_idx on (organization_id, owner_user_id, work_date) partial lifecycle = 'active', in this same later A unit, not a new path. Coverage labels episodes excluded solely by timezone difference between stored episode timezone and the request; do not change the stored-work_date predicate. No reconstruction_key or membership/source-connector FK.
  2. packages/db/src/schema/work_episode_evidence.ts new: composite owner/org/episode FK ON DELETE CASCADE; same owner/live-membership/active-org RLS. Source keys stable; immutable source/member/credential IDs are snapshots, not FKs. Evidence refresh cannot relabel retained episode dependencies. Named owner-scoped index work_episode_evidence_org_owner_kind_key_idx on (organization_id, owner_user_id, source_kind, source_key) for C's bounded VALUES join, beyond the existing unique, in this same later A unit, not a new path.
  3. packages/db/src/schema/clio_activity.ts: extend clioActivity with nullable lossless source_authorization_epoch bigint (or text compared via ::bigint) and nullable source_origin text (ingest-side normalized origin). Named index clio_activity_org_connector_user_date_idx on (organization_id, connector_id, user_source_id, date) in this same later A unit, not a new path. B7's eligible ingest set is type='clio' AND scope='organization' AND status='authorized' (revoked leftovers out). At most one authorized org Clio connector (connectors_org_scope_unique); isolation fixture is authorized vs revoked leftover. State that invariant. Drop markActivitiesUnseen cleared_reason NULL change. Drop enum/default-arm/partition-uses-cleared_reason residue. cleared_reason is unused by this lane; no feeder edit. Do not decrypt or refresh firm ingest credentials, and do not call who_am_i on firm ingest for the personal ledger. P3 A7 stamps NULL only. Enable the helper stamp in P4 after B3 preserves stored origin, same release. Origin helper returns string | null and catches URL/TypeError and ClioApiError; A7 stamps NULL on any throw. Never raw api_host. Never ClioClient.baseUrl/env default. A6 does not call normalizeHost. B7 compares that same helper output to personal verified origin in the same REPEATABLE READ snapshot. Unstamped EXISTS is source_authorization_epoch IS NULL only. source_origin IS NULL is (11) only (complete figure unavailable; do not withhold rows). When non-null, source_origin <> $personalVerifiedOrigin is (6) identity-unverified. Delete source_origin IS NULL OR source_origin <> … as a single arm. Recorded-time projection, canonical/dismiss keys, and exact GET require source_origin IS NULL OR source_origin = $personalVerifiedOrigin; NULL-origin rows stay in the projection and are keyed under the actor’s own verified origin — (11) refuses the named figure only, it never withholds a row (§58 Opus 1). Existence of non-null differing origin is (6) coverage EXISTS that refuses the named figure; those rows are not keyed as personal and do not occupy the personal LIMIT 200. Matching-origin and NULL-origin rows are not withheld. No who_am_i. No broad app-role reader. No db:generate now.
  4. packages/db/src/schema/index.ts: export the new tables. Public row types derive from Drizzle, not parallel hand-written interfaces.
  5. packages/db/scripts/provision-roles.ts: A5 app_role-owned SECURITY DEFINER with qualified relations, fixed pg_catalog, pg_temp search path, both actor GUCs and existing auth DML lock privileges; PUBLIC revoked, app/sync EXECUTE only. Preserve full NOWAIT root/member/session/client/refresh/access/exact-consent locks through commit. Add §20.1's issuer-mode root/live-direct checks without requiring an as-yet-unissued access row; refresh never requires browser liveness. Ordinary ledger validation requires access.consentId/generation = exact current consent, not tuple replacement; issuer token transactions lock existing refresh FOR UPDATE and consent SHARE, consent mutations UPDATE, with no lock upgrades. No source/secrets/ledger rows returned, broad sync grants or global authority framework. Name GRANT ON work_episode and work_episode_evidence TO app_role in TABLE_GRANTS. G5/G6 and I real SQL prove modes, GUCs and revocation ordering.
  6. packages/clio-sync/src/activity-events.ts: applyActivityPage stamps the passed locked authorization epoch and the ingest-side origin string already normalized by A7. A6 does not call normalizeHost. Stamp the helper return value, not .origin. Absent or throw → explicit NULL. Never stamp raw api_host, ClioClient.baseUrl or env default. P3 A7 stamps NULL only. Enable the helper stamp in P4 after B3 preserves stored origin, same release. Existing eligibility and absence behavior unchanged. Keep this A6 clear-writer: it NULLs date/user/matter. Drop markActivitiesUnseen cleared_reason NULL change. Drop enum/default-arm/partition-uses-cleared_reason residue. cleared_reason is unused by this lane; no feeder edit. Name source_authorization_epoch and source_origin on applyActivityPage's ON CONFLICT DO UPDATE SET list (activity-events.ts:145–166).
  7. apps/worker-clio/src/loops/activity-sync.ts: commitPage passes its locked epoch. A7 (already depends on connector-clio) stamps origin: P3 A7 stamps NULL only. Enable the helper stamp in P4 after B3 preserves stored origin, same release. Passes raw provider_metadata.api_host (NULL if not a string) to the connector-clio origin helper returning string | null, exported from packages/connector-clio/src/clio-client.ts (export-only; wrap/catch URL/TypeError and ClioApiError); A7/helper normalizes; stamps the helper return value, not .origin. A7 stamps NULL on any throw. Never raw api_host. Never ClioClient.baseUrl/env default. Import CLIO_AUTHORIZATION_EPOCH_SQL from A11 packages/clio-sync/src/index.ts; do not keep a second definition here. No new loop; existing page/checkpoint atomicity and bounds remain.
  8. packages/db/src/schema/connectors.ts: A8. Keep nullable versioned outlook_account_binding on sync-only connectorUserTokens, with exact connector/account/credential/member IDs, lossless grant/revision, null-safe shape CHECK and partial selected-row uniqueness. Add nullable connectors.enrollment_generation timestamptz, mode:string, no default/backfill, for §22.2's START/commit CAS; it is NOT authorized_at, credential revision or binding JSON. Existing safe connector metadata access remains; no credential grant or connector ACL rewrite. Preserve global provider/account uniqueness pending separate platform remediation. One scheduled schema unit after #432.
  9. packages/db/src/schema/oauth.ts: A9. Five nullable exact-generation fields and immutable non-FK token snapshots per §20.1. Retain both token pair CHECKs and consent-generation-implies-ledger CHECK; add §22.5's THREE explicitly named ledger-scope user/reference CHECKs, one on each OAuth consent/access/refresh table, so partial tuple uniqueness has no nullable ledger identity loophole. No ordinary-scope nullability change or provenance backfill. Same later A generation unit only after #432 releases the slot.
  10. packages/clio-sync/src/activity-sync-state.ts existing A10: host named CLIO_AUTHORIZATION_EPOCH_SQL and parseActivitySyncState. Shared SQL returns text; B7 casts. Unstamped comparison numeric. B7 uses that named expression with numeric/bigint comparison in the same REPEATABLE READ snapshot. Worker A7 imports the expression from A11; do not keep a second definition in activity-sync.ts.
  11. packages/clio-sync/src/index.ts existing A11: public package surface. Re-export CLIO_AUTHORIZATION_EPOCH_SQL, parseActivitySyncState, applyActivityPage and markActivitiesUnseen. B7/A7 import from this surface, not a deep path.
  12. packages/connector-clio/src/clio-client.ts existing A12: export-only origin helper returning string | null; wrap/catch URL/TypeError and ClioApiError. A7 stamps the helper return value, not .origin; B7 compares that same helper output to personal verified origin. Do not leave this file unmapped.

Both ledger policies require owner GUC, live matching member and active organization in USING/WITH CHECK. Session active-org and direct origin are independently checked by C. Matter pair CHECK explicitly requires both NULL OR both nonnull with matter org = episode org. Unknown duration clears all numbers, estimated requires both bounds/no confirmed, confirmed requires positive confirmed/no bounds; observation/dismissal branches are complete. Explicit user/org and evidence-parent CASCADE follows working_profile lifecycle; matter alone SET NULLs its pair. No source/member FK or accidental NO ACTION. G6 actual invalid SQL and lifecycle tests own proof.

B. Identity/source transport: 21 required files, stable labels skip B19

  1. packages/provider/src/adapters/msgraph/email.ts: listPersonalSentWorkPage/exact reads implement spec §3.1's documented delegated sentitems projection and grant-tested field handling: select sender/uniqueBody explicitly, compare exact GET when preflighting, parse itemBody.contentType, never equate missing text with empty work. ImmutableId on each request, full nextLink, case-sensitive mailbox-local IDs, opaque changeKey; no snapshot/exhaustive-human claim. Separate attachment collection selects only id,name,contentType,size,isInline,lastModifiedDateTime; hasAttachments excludes inline. Aggregate 100 records/5 HTTP attempts/pages including retries, ≤ 4 concurrent requests, same deadline; oversized pages locally truncate/label partial, not a claimed byte/page-size guarantee. No contentBytes/$value/expanded bodies or generic enrich fanout. Thread supported signal before scheduling/after await; ordinary mail coverage unchanged.
  2. packages/provider/src/adapters/msgraph/index.ts: export the narrow reader through the existing public /msgraph subpath; no provider deep imports.
  3. packages/connector-clio/src/auth.ts: personal signed start-bound state and readCurrentUserIdentity via unchanged ClioClient.json/public wildcard export. Extensionless who_am_i?fields=id,account{id}, URL-encoded braces. Validate decoded data.id/account.id BEFORE coercion: positive safe-integer numbers or canonical positive decimal strings within signed int64; otherwise unverified (§16.2). Export the narrow ID validator here for B7 through the existing public surface, not a new module. Preserve the producing connector's verified stored regional api_host using the existing HTTPS allowlist, never cfg/env/default as identity. Fix bundleFromToken/refreshAuth to retain established origin and use it for refresh; an injected different baseUrl refuses before credentials leave. Fresh enrollment binds actual validated producing origin and verified identity. Persist who_am_i data.id as encrypted provider_metadata.personal_user_id (canonical decimal string) at enrollment; B18 commits that key inside existing encrypted metadata. No new path, no new column. Missing origin stays ledger-unverified; ordinary legacy refresh fallback must not manufacture verified provenance. Personal who_am_i remains for personal identity. Do not call who_am_i on firm ingest credentials or decrypt/refresh firm ingest. Origin comparison is B7 against the A6/A7 stamped ingest origin, not a firm who_am_i. Export a string-returning origin helper from already-mapped packages/connector-clio/src/clio-client.ts (export-only; wrap/catch ClioApiError); not a new path. A7 and B7 import it from the existing public connector-clio surface. Do not call normalizeHost from A6. No separate Account endpoint or allowlist expansion. G5 and G15 prove origin and ID behavior.
  4. apps/web/lib/connections/clio-oauth.ts: personal start/complete binds original generation and session; sends verified identity and credentials to the dedicated atomic personal-enrollment route. Carry who_am_i data.id so B18 can persist provider_metadata.personal_user_id. No separate authorize PATCH or personal ingest queue. Organization flow remains unchanged.
  5. apps/web/app/api/connectors/clio/callback/route.ts: handleGET verifies signed mode before error dispatch; personal completion requires matching current session. Forward who_am_i data.id for B18 provider_metadata.personal_user_id. Import personal helpers directly from clio-oauth.ts. Forged/malformed personal state and personal provider errors never call markConnectorErrorBestEffort on the org row; invalid state mutates neither mode.
  6. apps/connectors-api/src/routes/clio-activities.ts: internal prepare/scan phases on the existing route family. Prepare returns request-local credential snapshot over the authenticated internal channel; scan requires C's existing absolute deadline and prepared snapshot, never starts another timer or reacquires credentials. Validate live actor/member/org/binding after awaits; c.req.raw.signal aborts supported HTTP. Expired/changed snapshots yield partial/reprepare-required. No stored capability/cipher factory; existing activity POST refusal remains.
  7. apps/connectors-api/src/lib/clio-activities.ts: B7 runs as sync_role. readPersonalClioTime returns a bounded row projection, not keys+totals only: canonical key (verified account: canonical regional origin + data.account.id + activity ID; connector_id is a selection predicate and non-key provenance snapshot, not part of the unique/dismiss key; dismiss key is personal verified account + activity ID; no stored ingest account, no who_am_i; cross-account isolation rests on unproved global uniqueness of Clio user ids; drop ingest-same-account-as-personal comparison; dismissals survive disconnect+reconnect of that personal account), date/matter, visible-or-unknown minutes, lossless hours as the numeric string, redacted flag, authorized note/URL, row epoch, plus source population fingerprint (projection, countEligibleRecordedTimeOverflow, named coverage EXISTS countEligibleNullDateCoverage, named coverage EXISTS countEligibleNullUserCoverage, named coverage EXISTS countEligibleNonVisibleCoverage, named coverage EXISTS countEligibleUnstampedCoverage, named coverage EXISTS countEligibleBelowEpochSameOriginCoverage, NULL-origin arm (own enum member, same class as unstamped — awaiting feeder; user sentence is spec §10.1 null-origin): shared population ∧ source_origin IS NULL → null-origin refuse. Mismatch stays member (6): shared population ∧ source_origin <> $personalVerifiedOrigin → identity-unverified refuse. Recorded-time projection, canonical/dismiss keys, and exact GET require source_origin IS NULL OR source_origin = $personalVerifiedOrigin; NULL-origin rows stay in the projection and are keyed under the actor’s own verified origin — (11) refuses the named figure only, it never withholds a row (§58 Opus 1). Differing-origin rows are not keyed as personal and do not occupy the personal LIMIT 200; matching-origin and NULL-origin rows are not withheld. Not unscoped. Not on cleared rows, parseActivitySyncState of the authorized ingest connector's activity_sync_state in that same REPEATABLE READ snapshot) and a separate round-once aggregate minutes from the same REPEATABLE READ snapshot. It does not join ledger tables. Named overflow query countEligibleRecordedTimeOverflow is a separate COUNT/EXISTS over the eligible population, not a 201st materialized key and not “5 full pages”. Overflow/200-vs-201 measured on the same set as the projection. Delete the pre-epoch-filter overflow refinement. Coverage reasons before overflow: combined >200 AND null-date emits the coverage reason, never “narrow dates”. Projection uses LIMIT 200; 200 complete and 201 too-many must both be expressible. Local fetch may still keyset-page at default size 40 to fill the 200, but overflow is that named query. Separate provider budget: 200 distinct exact validations, ≤ 4 concurrent, same 20-second post-acquisition phase. Local selection consumes no provider slot; retries of the same key use existing HTTP-attempt/time limits, not another distinct-entry slot or local page. Identity calls do not consume the entry counter; no outer retries, HTTP/page limit increase or five-GET interpretation. Actor/date/matter/eligibility remain source facts; old/null observation epochs cannot be repaired by ledger refresh. Eligible ingest set is type='clio' AND scope='organization' AND status='authorized' (revoked leftovers out). Three org-Clio branches: (1) authorized exists → parse that row’s scan-state; (2) authorized empty AND any org Clio row EXISTS (no status filter) → ingest-not-authorized; status source: partial-unique live org Clio row when it exists, else any leftover; extend partial-reason enum to cover revoked/archived; (3) zero org Clio rows → missing-feeder. Do not parse scan-state of a non-authorized row. G5/G10 pending case. Do not decrypt or refresh firm ingest credentials, and do not call who_am_i on firm ingest for the personal ledger. Feeder stamps ingest-side normalized origin (A7 (apps/worker-clio, already depends on connector-clio) stamps origin: passes raw provider_metadata.api_host (NULL if not a string) to the connector-clio string-returning origin helper exported from packages/connector-clio/src/clio-client.ts (export-only; wrap/catch ClioApiError); A7/helper normalizes; stamps the helper return value, not .origin. A6 does not call normalizeHost. Absent or throw → explicit NULL. Never raw api_host. Never ClioClient.baseUrl/env default. Pre-B3 rows stamp NULL. A origin stamp ships only after B3 preserves verified origin; until then source_origin is NULL and B7 (11) null-origin refuses (complete figure unavailable; do not withhold rows)) onto clio_activity in the same A6/A7 delta as source_authorization_epoch. When that stamp is absent, (11) null-origin (complete figure unavailable; do not withhold rows). When the stamp is non-null and differs from the personal verified origin, (6) identity-unverified refuse. B7 compares that same helper output to personal verified origin in the same REPEATABLE READ snapshot. No who_am_i. DROP last_seen_at as a work-window predicate. Shared population (stated once; quoted only by unstamped / below-epoch / origin EXISTS): ingest-connector ∧ observation_state='visible' ∧ type='TimeEntry' ∧ (matter only when selected) ∧ date in window ∧ (user_source_id = $actor). $actor is this request's resolved who_am_i data.id, never the enrollment-stored personal user id. Order: single identity resolution → RR snapshot → commit → exact validations. Before the snapshot: if live who_am_i data.id ≠ enrollment-stored provider_metadata.personal_user_id, refuse identity-unverified (reuse (6) H string), zero rows, no snapshot. Request-level admission, not a row predicate. Do not add member (12). Named coverage EXISTS for noncanonical stored user_source_id in the selected window (visible TimeEntry, ingest-connector, date/matter) that is not equal to $actor fires member (10). No complete-empty / silent omit. Identity GETs stay in the 20-second source phase, not C prepare. Non-visible, null-date and null-user keep their own literals. Unstamped NULL-user does not refuse (labelled via null-user). Cleared rows ignored everywhere. Named coverage EXISTS countEligibleNullDateCoverage: any currently eligible visible TimeEntry with date IS NULL in the actor + ingest-connector set (and selected matter only when the request selected a matter) is hard incomplete. Narrowing matter can clear it; narrowing dates cannot. Out-of-connector/actor rows do not count. Matter predicate only when selected; week-wide uses the unfiltered eligible set. Named coverage EXISTS countEligibleNullUserCoverage: any currently eligible visible TimeEntry with user_source_id IS NULL in the ingest-connector set (and selected matter only when the request selected a matter) is labelled incomplete, not org-wide refuse. Literal predicate: ingest-connector set ∧ observation_state='visible' ∧ TimeEntry ∧ user_source_id IS NULL ∧ (matter only when selected) ∧ (date BETWEEN bounds OR date IS NULL). No actor predicate. Window date BETWEEN bounds OR date IS NULL. NULL-user is labelled. Both-NULL row is labelled, not refuse. G5 and G6 assert visible redacted-user is labelled incomplete: countEligibleNullUserCoverage > 0 does not refuse the named figure. Do not change mapActivity in this pass; nested visibility still P1. Drop “awaiting feeder” as the clear path for redaction. G5: an out-of-window null-user row must not refuse. Partial reasons cite the B7 enumerated list. Named coverage EXISTS countEligibleNonVisibleCoverage (one literal, no cross-reference, no OR true; labelled, never refuse): ingest-connector ∧ observation_state='unseen' ∧ type='TimeEntry' ∧ (matter only when selected) ∧ (date BETWEEN bounds OR date IS NULL) ∧ (user_source_id = $actor OR user_source_id IS NULL) ∧ matter_id IS NOT NULL. Other users’ unseen must not set the flag. G5 one labelled-flag assertion that bites, not refuse: an in-window actor-or-null unseen TimeEntry with matter_id IS NOT NULL sets the flag; another user’s unseen does not. G5: clear a row, run markActivitiesUnseen, flag stays off for a different actor. Keep it in RR/fingerprint. Keep A6 NULLs. Drop the cleared_reason CHECK from this lane. cleared_reason is unused by this lane; no feeder edit. No generated CHECK, no hand-edited SQL, no deploy-path backfill. §8: redacted-matter entries leave the named figure with no signal. Named coverage EXISTS countEligibleUnstampedCoverage: shared population ∧ source_authorization_epoch IS NULL → refuse. Nothing else inside that EXISTS. Precedence: origin mismatch/NULL wins over unstamped when both true. Named coverage EXISTS countEligibleBelowEpochSameOriginCoverage: shared population ∧ source_authorization_epoch IS NOT NULLsource_authorization_epoch < (authorized ingest connector’s CLIO_AUTHORIZATION_EPOCH_SQL from connectors.authorized_at) ∧ source_origin = $personalVerifiedOrigin. Labelled, sealed, not G5(b) refuse. source_origin IS NULL is (11) only (complete figure unavailable; do not withhold rows). When non-null, source_origin <> $personalVerifiedOrigin is (6) identity-unverified. Delete source_origin IS NULL OR source_origin <> … as a single arm. Recorded-time projection, canonical/dismiss keys, and exact GET require source_origin IS NULL OR source_origin = $personalVerifiedOrigin; NULL-origin rows stay in the projection and are keyed under the actor’s own verified origin — (11) refuses the named figure only, it never withholds a row (§58 Opus 1). Existence of non-null differing origin is (6) coverage EXISTS that refuses the named figure; those rows are not keyed as personal and do not occupy the personal LIMIT 200. Matching-origin and NULL-origin rows are not withheld. Not unscoped. Not on cleared rows. Drop “state epoch equals current” until a full scan completes under the new epoch. Seal unstamped/below-epoch/origin with the fingerprint. G5/G6/G10. G10 matter-filter affordance for null-date refuse. Null-user labelled flag/fingerprint only. Enumerated partial-reason list (implementation notes, sole technical authority on this B7 card for source-eligibility reasons; operational reasons (budget exhaustion, 20s deadline, cancellation, mid-request authorization loss) are owned by H with strings in spec §10.1; lawyer-facing source-eligibility sentences live in spec §10 H register; every other technical mention cites this list): (1) null-date — refuse; implementation note: matter-filter only; never narrow dates; (2) null-user — labelled flag/fingerprint only; implementation note: labelled incomplete for visible redacted-user; drop “awaiting feeder”; (3) non-visible — labelled, never refuse; implementation note: unseen labelled only; other users’ unseen must not set the flag; requires matter_id IS NOT NULL; (4) unstamped — a complete figure is unavailable; do not withhold rows; implementation note: awaiting feeder reobservation; ledger refresh cannot repair; origin mismatch/NULL wins over unstamped when both true; unstamped NULL-user does not refuse (labelled via null-user); (5) below-epoch-same-origin — labelled, sealed, not G5(b) refuse; implementation note: awaiting full scan under the current authorization epoch; (6) identity-unverified origin — refuse; not unscoped; not on cleared rows; implementation note: origin differs from personal verified origin; those rows are not keyed as personal and do not occupy the personal LIMIT 200; matching-origin and NULL-origin rows are not withheld; NULL-origin is its own member; mismatch wins over unstamped when both true; (7) ingest-not-authorized including revoked/archived — refuse; branch (2); implementation note: organization Clio ingest is not authorized; (8) missing-feeder — refuse; branch (3); implementation note: no organization Clio connector; (9) validation-denial/hours-redaction — refuse named figure; implementation note: validation-time whole-entry denial or hours redaction; already-known quantity_redacted does not refuse; (10) unsafe-ID identity-unverified — withhold entry, named figure unavailable; implementation note: unsafe or non-canonical identity; never equal-by-rounding. Coverage EXISTS for noncanonical stored user_source_id in the selected window (visible TimeEntry, ingest-connector, date/matter) that is not equal to $actor fires (10). No complete-empty / silent omit. (11) null-origin — a complete figure is unavailable; do not withhold rows; same class as unstamped — awaiting feeder; user sentence is spec §10.1 null-origin; implementation note: source_origin IS NULL. Never authorization or full zero totals. Visible NULL user_source_id is labelled incomplete coverage, never evidence of “not mine”. B7 compares clio_activity.date to inclusive local date bounds; derived UTC range is for sentDateTime only. Overflow over dated-in-range rows does not see NULLs. Validation-time whole-entry denial or hours redaction refuses the named figure (partial reason); an already-known quantity_redacted ingestion row contributes zero minutes and one unknown-duration count and does not refuse. Do not publish the B7 snapshot aggregate as complete when validations withheld rows/hours. Return redacted hours as unknown and explicit coverage, never claimed weekly completeness. No app_role SELECT on clio_activity.
  8. apps/connectors-api/src/routes/connectors.ts: keep release-N Outlook legacy body/response and strict capability/enrollment receipt. For generic :type/credentials, load the ACTUAL stored connector identity; reject personal Clio regardless of supplied :type before calling unchanged persistCredentials. Dedicated strict personal endpoint calls B18; no unsynchronized legacy invalidation or org lock rewrite. Scope/owner/type are not mutable PATCH fields. Enable strict personal writes only after all API instances have this guard. Bind signals and bound owned enrollment waits; reuse boot dependencies.
  9. apps/connectors-api/src/lib/user-tokens.ts: prepare ciphertext before locks; strict member-bound START-generation CAS and selected connectorUserTokens binding/credential/authorized epoch commit together. Use spec §3.4 authority/root/member locks before connector and sorted token rows. Clear prior selections transactionally; forbid owner transfer. Same-write bearer/revision and strictly monotonic generation/epoch remain. Legacy Microsoft writes clear selection; disconnect invalidates. No broader offboarding/account-transfer claim.
  10. packages/agent-runtime/src/tools/email-access.ts: resolvePersonalWorkSource participates in C's prepare-ALL barrier and requests strict exact-row/member snapshot through B21. After C starts the one source deadline, use only the prepared bearer; changed/expired snapshot returns partial/reprepare-required, never reacquires or resets. No shared-mailbox fallback; ordinary token callers retain their contract.
  11. packages/connector-auth/src/tokens/encrypt.ts: strict selected credential/account/revision check; capture bearer and revision from the same write/locked transaction. Extend hook return context only here if needed for RETURNING; never postcommit reread pairing. Same-account refresh advances bound revision atomically but not selection/grant. Legacy/mismatch fails closed. Keep existing crypto/backfill lifecycle and NetDocs behavior; no global timing retrofit.
  12. packages/connector-auth/src/tokens/refresh.ts: preserve canonical lease/waiter/refresh; strict cache/refresh returns use the atomic bearer/revision pair from B11 and recheck current selected binding. No A token tagged with B revision after concurrent commit. Existing maintenance may settle after cancellation safely; caller discards aborted result. No request-owned Redis or all-in SLA.
  13. apps/web/lib/connections/outlook-oauth.ts: strict begin/complete requires the server session reference/current member ID passed from real actions/callbacks (§13), not user/org alone. Probe protocol 2, bind original membership/generation before OAuth, recheck direct authority after exchange and at commit. Require receipt before success/subscription enqueue. Legacy in-flight state stays unverified only during release N; no strict fallback.
  14. apps/connectors-api/src/lib/types.ts: preserve legacy wire; add strict member/session-bound enrollment request/receipt, exact-row sourceSnapshot and internal prepare/scan union with required existing absolute deadline for scan. Actor references are authenticated server context, never public user selectors or trusted origin booleans. No implicit upgrade or preparation deadline posing as source time.
  15. apps/web/lib/connections/connectors-api-client.ts: dedicated capability/enrollment HTTP methods plus personal Clio atomic enrollment transport; require exact versioned acknowledgement. Missing strict route never falls back to legacy credential write. Preserve old methods for the one-release overlap and remove at N+1 cutoff. No new transport module.
  16. packages/connector-outlook/src/auth.ts: Outlook-local optional bound context on beginAuth/completeAuth and exported signed-context decoder; encode actor/org/connector/original generation in versioned signed returnTo using existing nonce/PKCE storage. Enforce current context before consume/exchange. Legacy branch is explicitly unverified during overlap; connector.ts's generic calls and existing legacy auth tests retain their contract, never yield strict receipts.
  17. apps/web/app/api/connectors/outlook/callback/route.ts: pass current session owner/active org to strict completion; stale/changed-session/forged bound-state error must not mark a newly resolved connector. Remove unverified fresh lookup for bound callbacks; only original-generation-checked error handling may write. Preserve managed-mailbox branch and permitted legacy overlap behavior; G14 updates affected tests.
  18. apps/connectors-api/src/lib/connectors.ts: commitPersonalClioEnrollment remains separate from unchanged generic persistCredentials. Prepare encrypted metadata first; assert direct session/current original member with spec §3.4 root locks, then EXACT existing hashtextextended(connector_id,0) advisory before connector/credential writes. Compare original generation/member; commit reserved member-bound metadata, including provider_metadata.personal_user_id (who_am_i data.id at enrollment), and authorized epoch atomically. No new path, no new column. No nested generic transaction, remote/KMS call under local locks, or global provider refresh rewrite.
  19. apps/connectors-api/src/lib/bundle.ts: canonical acquisition only during preparation; preserve cancellation settlement and unchanged worker refresh. Return a strict member/identity/epoch-bound request-local preparation result for the existing internal source path. Scan must use its prepared bearer and revalidate rows, not call getRefreshedBundle again. No general bundle widening, stored capability or source timer spanning crypto.
  20. packages/agent-runtime/src/tools/connectors-api-internal.ts: optional caller signal and typed internal prepare/scan requests at real source callers. Forward trusted actor references/prepared snapshots only server-to-server; never log or persist secrets. Scan carries C's ONE absolute deadline unchanged across Outlook/Clio; missing deadline is invalid, not a fresh phase. No detached race or general resource factory.
  21. packages/db/src/sync-tx.ts existing B22: keep withSyncTenantTransaction. Add optional isolationLevel forwarded to drizzle db.transaction so BEGIN ISOLATION LEVEL REPEATABLE READ precedes the current_user check. Default unchanged; existing callers stay READ COMMITTED. Do not drop the wrapper. When isolationLevel is requested, SELECT current_setting('transaction_isolation') must equal repeatable read; throw otherwise. G5 (a) nested call throws. B7 uses this path for its snapshot.

B1 returns per-message attachment coverage through the existing coverage result: unscheduled is attachments_unchecked, a cut page/unfinished collection is partial, and verified empty requires a complete successful listing. C derives episode coverage without upgrading unchecked messages; F3 labels it on the web. §24.2 owns combine/split, saved-read defaults and G5/G10 proof. No new detail-fetch feature, persistence field or request budget.

Named enrollment fence: every B9/B13/B16/B17/B18 reference to original/new connector generation means A8's lossless enrollment_generation, never authorized_at/updated_at. B3/B4/B5 personal Clio and B8/B14/B15 internal requests/receipts carry the same nullable signed START snapshot; §22.2 defines exact compare/increment, callbacks/error paths and legacy/disconnect invalidation. No caller may substitute a callback-time generation.

Credential revision, not a connectors column: credential_revision remains the named lossless field inside the selected connector_user_tokens.outlook_account_binding JSON. Writers that bump it under the selected-row lock are B9 (user-tokens.ts, including legacy Microsoft write), B11 (encrypt.ts persistTokens) and B12 (refresh.ts). Strict reads compare that exact revision. It is distinct from A8 connectors.enrollment_generation. No extra A column.

B7's exact-response boundary validates activity/user/matter IDs before mapActivity/String coercion, reusing B3's narrow public validator. Existing ingested source/user/matter strings have lost their original JSON type: require canonical positive safe-range keys for exact correspondence, otherwise withhold the entry and mark coverage identity-unverified. Validate the personal actor and selected local matter correspondence before SQL narrowing, not after an empty result. No rounded-key GET, equality join, disclosure or full-range figure from ambiguous keys; §16.2 defines the conservative legacy boundary. Existing ingestion/mappers stay unchanged.

B7 snapshot, hours and ingest-connector set: projection, countEligibleRecordedTimeOverflow, named coverage EXISTS countEligibleNullDateCoverage, named coverage EXISTS countEligibleNullUserCoverage, named coverage EXISTS countEligibleNonVisibleCoverage, named coverage EXISTS countEligibleUnstampedCoverage, named coverage EXISTS countEligibleBelowEpochSameOriginCoverage, NULL-origin arm (own enum member, same class as unstamped — awaiting feeder; user sentence is spec §10.1 null-origin): shared population ∧ source_origin IS NULL → null-origin refuse. Mismatch stays member (6): shared population ∧ source_origin <> $personalVerifiedOrigin → identity-unverified refuse. Recorded-time projection, canonical/dismiss keys, and exact GET require source_origin IS NULL OR source_origin = $personalVerifiedOrigin; NULL-origin rows stay in the projection and are keyed under the actor’s own verified origin — (11) refuses the named figure only, it never withholds a row (§58 Opus 1). Differing-origin rows are not keyed as personal and do not occupy the personal LIMIT 200; matching-origin and NULL-origin rows are not withheld. Not unscoped. Not on cleared rows, parseActivitySyncState of the authorized ingest connector's activity_sync_state in that same REPEATABLE READ snapshot. Three org-Clio branches: (1) authorized exists → parse that row’s scan-state; (2) authorized empty AND any org Clio row EXISTS (no status filter) → ingest-not-authorized; status source: partial-unique live org Clio row when it exists, else any leftover; extend partial-reason enum to cover revoked/archived; (3) zero org Clio rows → missing-feeder. Do not parse scan-state of a non-authorized row. Unstamped: shared population ∧ source_authorization_epoch IS NULL → refuse; nothing else inside that EXISTS. Below-epoch and origin predicates as on the B7 card (shared population includes type='TimeEntry' stated once; quoted only by unstamped / below-epoch / origin; user clause on those three is the literal (user_source_id = $actor); unstamped NULL-user does not refuse, labelled via null-user). Precedence: origin mismatch/NULL wins over unstamped when both true. Partial reasons cite the B7 enumerated list. Unseen is labelled only; delete the hard-incomplete arm of countEligibleNonVisibleCoverage. The population fingerprint run in one REPEATABLE READ transaction on the B7 connection, opened through B22 packages/db/src/sync-tx.ts with optional isolationLevel. That REPEATABLE READ transaction commits after projection/overflow/null-date/null-user/non-visible/unstamped/below-epoch/unverified-origin/fingerprint and before any exact-entry validation call. Seal countEligibleNullDateCoverage, countEligibleNullUserCoverage, countEligibleNonVisibleCoverage, countEligibleUnstampedCoverage, countEligibleBelowEpochSameOriginCoverage, the (11) NULL-origin and (6) mismatch origin arms and the fifth fingerprint-sealed precondition parseActivitySyncState (or a coverage digest) with the fingerprint. Refuse the named figure unless completed_updated_since IS NOT NULL and last_full_scan_completed_at is present. Drop “state epoch equals current” until a full scan completes under the new epoch. A zero-row fresh connector refuses, not 0 minutes. Cursor reuse refuses the full figure if the fingerprint OR those flags change. Sealed fingerprint, not an open transaction, revalidates later. Canonical key is verified account (canonical regional origin + data.account.id) + activity ID; connector_id is a selection predicate and non-key provenance snapshot. Dismiss key is personal verified account + activity ID. Hours are lossless from the stored numeric onward. B7 returns lossless hours (numeric string) per row AND a separate round-once aggregate minutes from the same snapshot. C uses that aggregate for the named figure.

Caller closure and overlap: web start/helper/callback, Outlook local auth, HTTP client/body types and privileged writers change as a unit. Generic connector-outlook/src/connector.ts calls remain legacy-shaped and cannot opt into strict identity; its existing auth tests remain valid. G11 drives real bound start/complete in addition to web behavior; G14 owns actual callback dispatch/error changes. G12 covers both old/new API wire pairings. SourceSnapshot refuses unverified legacy data. One-release compatibility is explicit and temporary, not a clean-cutover exception hidden in optional fields.

Why B19 remains removed — limited historical lock proof: refresh-credentials.ts:311–321 takes hashtextextended(connector_id,0); :328–333 rereads after locking; :368/:375–418 settles provider success/error and writes under it. That pre-transplant worker audit still requires current-head runtime verification. Fresh Opus inspection of Clio auth.ts:116–123/:189–193 finds a separate defect: metadata is spread but api_host is overwritten from cfg/env/default. B3 fixes that adapter in its already mapped file; do not claim the present refresh preserves regional identity. No worker lock rewrite is needed by this finding. G5 must prove both advisory orders/rollback and stable region across refresh/config changes; ordinary legacy fallback cannot manufacture ledger provenance.

Legacy closure: source search found one production server caller of generic persistCredentials, routes/connectors.ts:340, which currently trusts arbitrary :type (comment :306–309). B8 must refuse stored personal Clio for EVERY path label; G5 calls legacy routes with both clio and spoofed labels and proves no credential/binding/status write. Generic helper and org behavior remain unchanged. Old API binaries cannot enforce that refusal: strict personal rollout waits for all API writers, and rollback disables the capability first. No claim of safe mixed-version personal writes. This enrollment rule does not waive §8's permanent D7/D9 floor for any share/history-serving application after ledger admission.

Completed documentary preflight: the unchanged provider capability report at repo path docs/research/2026-09-15-private-work-ledger-provider-preflight.md (H4; not a docs-site URL — docs/superpowers/serve.ts roots at docs/superpowers and has no .md type) documents extensionless who_am_i?fields=id,account{id}, nested User.account and int64 IDs. This is NOT proof that native JSON numbers preserve int64. Apply §16.2's fail-closed ID boundary before comparing verified stored regional origin + account ID across successive personal-grant enrollments (not ingest comparison), and exact activity user.id to the personal user. Missing origin/unsafe numeric identity stays unverified. No separate Account endpoint or allowlist widening; no proved global/lifetime/migration ID guarantee. Users — Read + Activities — Read remains the narrow portal configuration to verify, fixed at grant. Actual synthetic exact-entry and Graph grant observations remain P1.

C. Ledger service: 3 required files

  1. packages/agent-runtime/src/work-ledger/service.ts new: owns saved operations, source hydration/copy, transactional structure and web-only readWorkRangeReconciliation per spec §8.1. C runs as app_role. Saved-work is a separate query over work_episode by stored work_date/matter, independent of Clio keys; mail-only episodes count. Compare stored work_date to the request's local date bounds; episode timezone is display metadata. Bounded VALUES join only decorates B7's recorded-time row projection against work_episode_evidence for association/exclusion; unlinked TimeEntries still appear with empty relation. Range dates/timezone/matter/cursor yield named populations, cutoff/version, authorized rows, known/unknown figures and coverage. Full recorded-range figure only for whole populations ≤ 200 with all current-request validations and observed coverage complete, carving out labelled coverage (2) null-user, (3) non-visible and (5) below-epoch-same-origin; larger scopes return null/too-many-entries, narrower filters can succeed. Even ≤ 200 may be operationally partial. Local selection and distinct provider validation are separate 200-entry budgets; never sum hydration pages or cache authorization proof. C uses B7's round-once aggregate minutes for the named recorded-time figure; it does not sum per-row display minutes. C may combine that aggregate with its saved-work aggregate in process. No sync_role ledger grants. updateWorkEpisode distinguishes associate_clio (exact source authorization) from exclude_clio_relationship (owned existing relation, membership/version/UUID only, zero provider calls). Preserve tombstone through refresh and safe receipts; ordinary latest-mutation retries differ from structural lineage retries. No extra module/table/cache.
  2. packages/agent-runtime/src/tools/work-ledger.ts new: five strict source-free adapters; owned IDs/references/decision enums and safe receipts only. Admission atomically creates the exact marker from spec §7 before use. First ledger tool use admits the thread. Admission ACCEPTS a matter-filed thread (matter-table “Review my work” is the product). Do not refuse, do not unfile. Prompt/tool-help explain permanence. Do not add a HITL interrupt. Explain permanent private-chat restriction in tool help; no provider access for saved reads or existing-relationship exclusion. Association needs exact authority; receipt-backed structure remains. Audit projects IDs/enums/counts only, excluding even human-owned text.
  3. packages/agent-runtime/package.json: declared server-only ./work-ledger export to the service, plus only necessary dependency metadata if existing exports require it. Do not hand-edit lockfile; an actual dependency change must fit the approved cap and use Bun.

The service owns ledger SQL; UI and MCP remain callers. Validate every field against its immutable account/credential/grant/source-epoch/revision/visibility snapshot, not a mutable evidence-row pointer. R1-edited text remains stored but withheld after R2 redaction; only a new proposal or deliberate new edit can bind R2. Keep human text/confirmed scalars independent. The 409/UUID retry, combine/split, nullable matter-FK and shared-entry dedup contracts are unchanged.

C owns prepare-ALL → single absolute source deadline, with no nested refresh/reset. Retain every boundary authority check; local mutations/admission/strict enrollment use spec §3.4's complete locked authority graph: roots/member, referenced sessions, client, refresh, exact access and consent, all NOWAIT and deterministic, then owner/provider advisory and sorted ledger/credential children. Discovery changes/contention abort the entire local transaction; no SKIP LOCKED or silent retry. Hold authority through commit, recheck wall-clock expiry/cancellation, preserve actual-commit outcomes. No provider/KMS/model work under locks or public actor override. G5/G6 distinguish revoke-first refusal from ledger-first commit followed by revoke.

Safe handoff: coordinator MUST be an input. Stage/replace/commit first take authority locks and owner advisory, then ALL input/coordinator rows sorted and deduplicated, exactly once. A nonlocking receipt lookup may discover inputs; locked UUID/payload/input-set CAS must still match, otherwise refuse without adding out-of-order locks. Stage creation expects no prior receipt; replacement expects its exact UUID. Thirty-minute expiry, unchanged content versions, atomic consumption/lineage and source-free receipts remain. Crossed coordinators may hold separate stages, but competing replacement of one receipt has one winner and overlapping structural commits cannot both consume the same input versions. No correction capability is dropped.

D. Nine base admission/sharing paths, five existing memory/chassis paths in §19.1, existing D15 SDK boundary in §21.2, existing D16 worker-context loader in §27.3, existing D17 digest writer in §28.3, existing D18/D19 MCP import-boundary in §42, existing D20 marked-thread matter-move in §44, and existing D21 updateThreadMatter on threads.ts in §45/§46

  1. packages/agent-runtime/src/agents/legal-assistant/index.ts: preserve existing capability wrappers, supply the async ledger resolver from C to buildLegalAssistantGraph. Do not append ledger tools to global allDefaultTools or other agents lacking this admission.
  2. packages/agent-runtime/src/agents/legal-assistant/prompt.ts: explain source-free saved fields and authenticated review/action links; no claim of source inspection. First ledger tool use admits the thread. Admission ACCEPTS a matter-filed thread (matter-table “Review my work” is the product). Do not refuse, do not unfile. Prompt/tool-help explain permanence. Unfiling an admitted thread is permanent and re-filing is refused. Do not add a HITL interrupt. Cite spec §10.1 share-first admission refuse for an already-shared thread. Explain permanent private-chat restriction before ledger use, including failed/interrupted turns; independent web review is available without marking an unrelated chat. Keep full source copy/summary web-only, no taint framework.
  3. apps/mcp-server/src/tools/work-ledger.ts new: five existing-name-style definitions with authenticated principal and spec §7's mcp_ledger_transport_projection for every direct/chassis success/failure. Do not reuse product-chat human-text output. Structured correction/combine/split and offline exclusion remain; new association keeps provider checks. redactArgs allows IDs/enums/counts only, never human text. Tool help explains permanence of first-use admission; first ledger tool use admits the thread; do not add a HITL interrupt. Existing context carries abort info; no tool-def shape expansion. G8/G23 own real combined-scope response and ordinary-memory positive proof.
  4. apps/mcp-server/src/access.ts: ledger read families require live direct grant plus mcp:private-work-ledger, NOT mcp:read; ledger mutations also require mcp:write. Non-ledger families retain effective mcp:read and mutations additionally mcp:write. Explicit ledger-only scope arrays cannot use the legacy missing-scopes read-only contract; real OAuth resolution always supplies an explicit intersection. No cached membership/support-role substitution for ledger authority. Recheck exact token-row/live consent at every ledger boundary.
  5. apps/mcp-server/src/tools.ts: filter BOTH base and write catalogues by the same family/scope decision used at invocation, including family-null definitions; ledger-only principals see only qualified ledger tools. Unknown provenance never advertises ledger. Feeder synchronization cannot re-enable a non-ledger tool outside the read scope. Preserve all five ledger operations for qualified grants and ordinary read/read-write catalogues; G8 and G23 test lists and calls.
  6. apps/mcp-server/src/mcp-app.ts: D6. Preserve server-resolved grant context, fresh admission, ledger-only discovery and separate ordinary read/read+write guards including prompts. §21.3 adds a domain-separated, length-prefixed authoritative access-row ID to ledger-capable principalKey; compare against BOTH stored live/recovery keys before reuse, including scope loss. Same-scope replacement uses the existing404/reinitialize handshake, never stale init-principal closures. Ordinary-only keys remain byte-identical. Retain existing audit-boundary installation, RS/AS activation metadata parity and no bearer/binding logging.
  7. packages/chat-runtime/src/shares.ts: grantThreadShare keeps its existing chat_thread FOR UPDATE lock and rejects the private-ledger parts marker with existing not_shareable refusal. Serialize late sharing with C's admission transaction, including human-only reads. Shared-first refuses admission; ledger-first refuses sharing. Refusal is independent of ledger capability switches and remains in every rollback-compatible build after admission. Cite D10's exported predicate. No new thread schema.
  8. packages/agent-runtime/src/agents/legal-assistant/graph.ts: extend local LegalAssistantDeps with async ledger resolver. llmCall awaits actor-scoped admission immediately before every bindTools/tool-list exposure, then records the exact local advertised snapshot. Resumed tool dispatch without snapshot awaits fresh admission; invocation wrappers recheck current authority. Missing/unknown feeder hides only reconstruction; no global mutable list, taint, serializers, source rehydration or enrichment changes.
  9. packages/chat-runtime/src/message-visibility.ts: D9 changes ONLY the internal-only assistant marker predicate used by filterInternalMessages, recognizing exact ledger markers alongside legacy markers while preserving mixed/text replies. Leave lastVisibleAssistantText unchanged: its sole production caller buildPriorTurnSummary receives listMessages' already-filtered transcript (threads.ts:883–884/:993–998). G9 exercises interruption→persisted reload→earlier meaningful summary through that real chain, not an isolated helper scenario that bypasses filtering. No threads.ts visibility edit; D21 owns updateThreadMatter discriminated refuse (null unfile allowed; non-null target refused).
  10. apps/mcp-server/src/tool-import-boundary.ts existing D18: allowlist of workspace packages a file under src/tools/ may import. Admitting the server-only ledger subpath (@workspace/agent-runtime/work-ledger) means IDs-only is adapter discipline from that point — D3's mcp_ledger_transport_projection and redactArgs IDs/enums/counts only, never human text, from the moment the subpath is allowed. Do not treat the allowlist as a write-door substitute.
  11. apps/mcp-server/test/tool-import-boundary.test.ts existing D19: keep the allowlist test as a biting D-packet suite. Prove ledger-tool source cannot import a disallowed writer; prove admitting the server-only ledger subpath still projects IDs-only. G25 and D19 are closed-inventory guards that must be updated, never removed. Do not delete it as plumbing.
  12. packages/agent-runtime/src/tools/move-chat-to-matter.ts existing D20: same discriminated rule as D21 under the same chat_thread lock C/grantThreadShare already take: allow targetMatterId === null (unfile); refuse a non-null target on a marked thread. Admission ACCEPTS a matter-filed thread (matter-table “Review my work” is the product). Do not refuse admission; do not unfile as a side effect of admission. Unfiling an admitted thread is permanent and re-filing is refused. Cite D10's exported predicate.
  13. packages/chat-runtime/src/threads.ts existing D21: same chat_thread FOR UPDATE as C/grantThreadShare, then marker check, then mutate. updateThreadMatter returns a discriminated result; router maps a non-null target on a marked thread to 422 not_shareable-class like grantThreadShare. Non-null + marker → refuse; updateThreadMatter(..., null) unfile still allowed. Unfile sets matter_id null only; marker stays. ChatRow !res.ok uses one reason-keyed string for marked-thread 422 (I22). Keep D20. Cite D10's exported predicate. Do not put listChatsForMatter on this file.

Activation ownership: C/D/I admission stays OFF until all relevant serving instances enforce D7/D9, §19's permanent memory exclusion, worker-context/digest (D16/D17) and direct issuer/session gates, old memory consumers are replaced/drained, and §14's preactivation application streams/runs are retired. Deployment owner installs the current floor-aware controller/hook, verifies compatible active/fallback releases and sets the durable minimum before admission. AS/RS metadata uses that same activation flag. J mechanically enforces trusted static candidate declarations, not attestation or per-SHA approval. Unknown fleet/drain state leaves ledger OFF; unrelated vendor-token erasure remains excluded.

Packet E taint rewrite stays removed. checkpointer/resumable/digest serializers remain unchanged. Missing actor provenance requires the narrow run.ts/host plumbing in §13, now refined to per-invocation context by §15.4. D7/D9 permanent sharing/visibility protection remains independently of capability switches. Keeping support impersonation available would need a separate complete marked-thread caller map, not just this tool gate.

F. Review UI/API: five base files plus three existing admission files in §18.1

  1. apps/web/lib/api/app.ts: mount the new Hono router alongside mattersRouter; no native Next API duplication.
  2. apps/web/lib/work-ledger/router.ts new: owner-authenticated no-store Hono routes, including dedicated POST reconciliation→readWorkRangeReconciliation with dates/timezone/matter/cursor, never caller totals. Saved reads remain source-free; hydrate/prepare-copy/summary remain web-only. Existing mutation endpoint dispatches offline exclusion without source admission and online association with exact checks. Propagate cancellation; source-phase timing follows acquisition; reject changed population/cursor scope without stale figures.
  3. apps/web/components/work-ledger/review.tsx new: WorkLedgerReview owns the PERSONAL Clio Connect/Reconnect control in its unverified/disconnected source state, calling I's direct-session action in settings/connections/actions.ts. Pass the selected review/window/timezone/matter return target through B4/B5's signed state; restore that selection after success, cancel or failure, with server reauthorization of the matter. Organization Settings remains a separate enrollment flow. Consume server reconciliation separately from hydration; never sum pages. Above 200 show null/too-many-entries and conditional narrowing guidance. Keep redacted/partial/unsafe-ID/old-observation coverage explicit, including feeder delays that reconnect or ledger refresh cannot repair. Offline exclusion stays enabled; linking needs verified source access. Preserve all copy/structure/correction modes, safe handoff, permanent sharing explanation, preparation/phase-only timing and cancellation/late-preview clearing. Unfiling an admitted thread is permanent and re-filing is refused. Cite spec §10.1 share-first admission refuse for an already-shared thread.
  4. apps/web/app/(app)/tasks/chats/new/page.tsx: F4 is lane-owned, no NOS-384 hold. Keep Today/This week/Review my work above LiveChat and canonical query-mode WorkLedgerReview. Branch on §18's SAME trusted server classification used by the parent mailbox gate, not independently parsed searchParams.review. Review still requires live direct session/current member/active org through I, with the unchanged parent password gate. Recheck enforceOutlookConnected before ordinary LiveChat, including query-only Back to chat when a layout is reused; never fall back from failed review validation to ungated chat. Review filters/owned episode IDs remain server-validated, including deep links outside the default range. No chat-ui change or alternate route.
  5. apps/web/components/matters/matter-table.tsx: F5 PAUSED; NOS-384 owns matters-page scope. Do not edit matter-table.tsx in this lane. Fresh merged-base reads confirm header :138 and cell :168 use showActions, while RowActions :243 returns null without onAction. All three still need personal Review independent of optional admin actions, with aligned columns and the same filtered link, no tracking mutation. This is refreshed c767 evidence, not assumed drift. Orchestrator handoff and rechecking NOS-384's eventual delivered version remain mandatory; no competing edit or dropped capability.

F3's existing per-source coverage display distinguishes Attachments not checked, Attachment check incomplete and a scoped complete-empty listing. Never render unchecked as an empty attachment list proving absence. Saved rows still open without source calls; existing coverage defaults unknown when current proof is absent. The same per-source panel labels v1's absence of meetings and North document drafts, and labels episodes excluded solely by timezone difference between stored episode timezone and the request, without changing the stored-work_date predicate. This is not a complete personal-work claim. MCP receives only allowed enums/counts/owned IDs, never these labels or provider attachment metadata (§24.2).

Follow DESIGN.md and the impeccable product register: existing themes, semantic tokens, inline progressive detail, keyboard/no-drag structure editing, narrow-screen stacking, labelled error states, visible unknown effort and per-source coverage. Read shadcn and React skills when implementing those components.

G. Configured writer @write Astra high: ten base files plus existing G15–G23 in §§16.1/18.1/19.1, existing G24 in §27.1, existing G25 transport-policy in §16.1, existing G26 packages/agent-runtime/src/tools/move-chat-to-matter.test.ts (bg_gate_cross_matter must stay green), existing G27 packages/chat-runtime/src/threads.test.ts, and existing G29 packages/connector-clio/src/read-only.test.ts, stable labels skip dropped G28

Consolidated groups retain stable labels: G1 executes in G6's SQL fixture; G2/G3 execute in G5's source/credential HTTP fixture; G7 executes in G9's transport fixture. G15 adds the existing Clio auth suite because B3 changes its real contract; do not keep its fake-client/config-origin echo to preserve the old count.

  1. apps/worker-clio/test/loops/activity-sync.test.ts existing: extend the real loop's SQLite fixture CREATE TABLE clio_activity with source_authorization_epoch and source_origin and inspect stamped rows across old/null/new epochs and origins. Keep existing page/cursor/watermark/eligibility tests. Its FOR UPDATE/SHARE stripping means it cannot prove PostgreSQL locking or rollback; that proof is G6. G4 asserts restamping of an existing row after a second observation, not only a new insert.
  2. apps/connectors-api/src/lib/work-ledger-sources.integration.test.ts new (G5, G2/G3): real PostgreSQL plus synthetic Graph/Clio. Drive the UNCHANGED worker refresh fence and corrected B3 Clio refreshAuth against B18 in both advisory orders, delayed success/error and rollback; reserved identity metadata and newer enrollment survive. Change cfg/env between enrollment and refresh: established origin/account/source anchor remains stable, missing origin cannot become ledger-verified, no credential goes to the new configured region. Stored personal Clio refuses legacy/spoofed :type with no write; org path remains compatible. Outlook same/backward clock and paused commits prove strictly newer epoch and same-transaction bearer/revision. Old/null Clio epochs stay unavailable after ledger refresh; reobserved ones become readable. Keep source/attachment caps, cancellation, redacted hours and prior attribution/compatibility cases. REQUIRE_DB_INTEGRATION=1 && !RUN throw so the suite cannot skip under the gate.
  3. packages/agent-runtime/src/work-ledger/service.integration.test.ts new (G6/G1): real SQL owns actual role-denial proof on BOTH ledger tables, including peer-owner/same-org-admin/foreign/missing-GUC denial and forbidden ordinary sync/scheduler access, with intended owner access as positive control; retain FK/grant/CHECK, CAS, anchor/dedup and real rollback/worker-epoch cases. check-access Phase B is not this proof. Range cases retain cross-batch shared keys, dismissed-only/unlinked entries, episode versus provider date/matter, redacted hours and changed version/permission. Fast accessible one-hour entries with observed coverage: 199→11,940 minutes, 200→12,000 minutes, both non-null; 201→null/too-many-entries, then narrow the SAME fixture to 200→12,000 minutes. Those 199/200/201 numbers are composed C figures; G5 owns overflow query and 200-vs-201 under sync_role. G6/G10 combined fixture: >200 AND null-date emits the coverage reason, never “narrow dates”. Coverage reasons before overflow. Split the G5/G6 fixture: validation-time denial/redaction inside an otherwise complete 200 refuses the named figure (partial reason); an already-known quantity_redacted ingestion row contributes zero minutes and one unknown-duration count and does not refuse. G5 and G6 assert visible redacted-user is labelled incomplete: countEligibleNullUserCoverage > 0 does not refuse the named figure. Do not change mapActivity in this pass; nested visibility still P1. Drop “awaiting feeder” as the clear path for redaction. G5/G6 boundary-day non-UTC fixture: B7 compares clio_activity.date to inclusive local date bounds. Local pages never consume the provider counter; also prove operational exhaustion below 200 yields partial, not a full figure. No always-null passing fixture or page double-count. Offline exclusion makes ZERO token/provider calls and survives reconnect/reconstruct; association refuses. Ordinary stale UUID after another edit is stale; structural retry after child edit returns original IDs. Cancellation before commit settles rollback, not late writes.
  4. apps/mcp-server/test/work-ledger.test.ts new: actual catalogue/chassis/direct calls, per-actor admission and mcp:write. Enforce spec §7's named MCP projection across reads, mutations, errors and receipts; stored private human/source sentinels must never appear in the real response, including links. Under one combined read/write/ledger grant, follow the actual ledger call with ordinary memory_save using unrelated input and observe normal persistence. Do not claim detection of arbitrary copied text or deny unrelated memory. Offline exclusion, authorized association, audit sentinel refusal, positive web source control and structured handoff remain. REQUIRE_DB_INTEGRATION=1 && !RUN throw so the suite cannot skip under the gate.
  5. packages/chat-runtime/src/work-ledger-transport.test.ts new (G9/G7): real legal-assistant graph/dispatch, run/resumable/isolated Redis/history. Commit admission then interrupt before reply; reload hides exact marker row, prior-turn summary still uses earlier meaningful answer, real mixed/text replies remain, sharing stays permanently refused. D20 same discriminated rule under the same chat_thread lock C/grantThreadShare already take: allow targetMatterId === null (unfile); refuse a non-null target on a marked thread. Admission ACCEPTS a matter-filed thread; do not refuse admission; do not unfile as a side effect of admission. G9 asserts D20 still blocks subsequent non-null moves in the same fixture. After unfile, share still returns not_shareable. Existing G26 packages/agent-runtime/src/tools/move-chat-to-matter.test.ts; bg_gate_cross_matter must stay green. Extend this fixture: admit independently human-authored private text, disable ledger capabilities, then attempt sharing through the rollback-compatible build; expect not_shareable and preserved history/prior-turn visibility. Mixed-version activation guard refuses admission until EVERY share-serving instance is compatible, then permits normal admission. Two actors/feeders and subsequent rounds remain locally admitted; stale invocation refuses. Preserve positive web/source-free generic sentinel, alternate-tool arguments, both share race orders and transport failures; audit human text also stays excluded. REQUIRE_DB_INTEGRATION=1 && !RUN throw so the suite cannot skip under the gate.
  6. packages/e2e/tests/private-work-ledger.spec.ts new: Home review (this lane; still gates G/P6/P8), every correction/copy/structure/handoff persists. NOS-384-owned (does not gate this lane's completion): /matters matter-row (showActions false / onAction absent / header/cell/menu Review). F5 stays paused. Do not edit matter-table.tsx in this lane. With fast accessible one-hour entries and observed coverage, 199/200 show correct non-null 11,940/12,000-minute range figures; 201 shows unavailable/too-many-entries and narrow-dates/matter guidance, never eventual-total wording on continuation. Narrow that SAME 201 fixture to 200 and show 12,000 minutes. Those 199/200/201 numbers are composed UI figures; G5 owns overflow query and 200-vs-201 under sync_role, measured on the same set as the projection. G6/G10 combined fixture: >200 AND null-date emits the coverage reason, never “narrow dates”. G10 cites the B7 enumerated partial-reason list. G10: on a null-date refusal the “narrow dates” string is absent. G10 exposes the matter-filter affordance for null-date refuse. Null-user labelled flag/fingerprint only. Exercise sub-200 operational partial, cross-batch shared/dismissed-only records, redacted hours and old observation labels. Offline exclusion persists across reconnect; add-link refuses. Permanent sharing explanation and interrupted admission/reload without a blank bubble remain. Keep cancellation/late-preview and source-free generic controls.
  7. apps/web/lib/connections/outlook-oauth.test.ts existing: exercise bound START state and actual auth helpers with synthetic token HTTP/PKCE storage; delayed A after B, changed session, replay/expiry and strict receipt versus missing capability. Legacy overlap remains ordinary/unverified. Remove obsolete separate-PATCH/plumbing assertions; G5 supplies actual SQL interleaving.
  8. apps/connectors-api/test/user-tokens-routes.test.ts existing: old body remains accepted without new required fields; strict endpoint validates protocol/start context and returns exact receipt. Real HTTP auth/malformed/stale contracts, old-web/new-API and new-web/old-API pairing, no downgrade write, and explicit N+1 cutoff fixture. G5 proves binding clear/revision behavior.
  9. apps/web/app/api/connectors/clio/callback/route.test.ts existing: signed personal-state/session/provider-error/forged-state isolation; no firm credential/status or ingest mutation. Preserve valid org flow; G5 owns auth/transaction proof.
  10. apps/web/app/api/connectors/outlook/callback/route.test.ts existing (G14): changed session/stale A/forged bound state cannot flag B via fresh lookup, actual callback passes verified current identity; preserve managed-mailbox and release-N legacy dispatch. Fix affected behavior assertions, not mock field forwarding.
  11. packages/agent-runtime/src/tools/move-chat-to-matter.test.ts existing G26: counted G path. Name bg_gate_cross_matter as a gate that must stay green. Same discriminated rule as D21: allow targetMatterId === null on a marked thread; refuse non-null. Admission ACCEPTS a matter-filed thread. Assert unfiling an admitted thread is permanent and re-filing is refused. Host the D20 arm here (this file already imports the tool): D20 refuses the same seeded marker row. Do not add a ./tools/move-chat-to-matter subpath.
  12. packages/chat-runtime/src/threads.test.ts existing G27: affected existing G path. Prove discriminated updateThreadMatter result on a marked thread: null succeeds (unfile sets matter_id null only; marker stays); a non-null target refuses. After unfile, a subsequent non-null still refuses. Assert unfiling an admitted thread is permanent and re-filing is refused. Do not import listChatsForMatter.
  13. packages/connector-clio/src/read-only.test.ts existing G29: affected existing G path with lines. Keep GET-only / no-write closed. Classify the origin helper in the closed inventory if this suite pins it; destination proof stays G15/G5. Do not delete it as plumbing.

G5 owns B7's named overflow COUNT/EXISTS and 200-vs-201 under real sync_role, measured on the same set as the projection. 200 complete versus 201 too-many must both be expressible without materializing a 201st key or treating five full pages as overflow. G6/G10 own composed C/UI figures. One owner per case. Saved-work counts mail-only episodes independently of Clio keys; unlinked TimeEntries still appear with empty relation.

G5 adds literal synthetic HTTP JSON with adjacent unsafe numeric identity IDs 9007199254740992 / 9007199254740993, asserting both are unverified, never equal authorization. Exercise unsafe exact-entry/user/matter numbers BEFORE mapping and a real mapActivity-produced ambiguous stored key; withhold source/association and return explicit incomplete coverage, not a repaired key or full zero. Positive safe IDs still reconcile. Genuine canonical decimal strings validate through the int64 endpoint, but an unproven large ingested key still refuses correspondence. No new fixture file or provider call.

G5 owns B7's named overflow COUNT/EXISTS and 200-vs-201 under sync_role, measured on the same set as the projection, the REPEATABLE READ snapshot, ingest-connector isolation and hour rounding. G6/G10 own composed C/UI figures. One owner per case. Split: (a) same-request feeder interleave → projection/overflow/null-date/null-user/non-visible/unstamped/countEligibleBelowEpochSameOriginCoverage/unverified-origin/fingerprint all agree (REPEATABLE READ property; READ COMMITTED must fail). G5 (a) nested call throws. Drop “never a complete 200” from same-request. G5/G6/G10: parseActivitySyncState in the same RR snapshot; refuse the named figure unless completed_updated_since IS NOT NULL and last_full_scan_completed_at is present. Drop “state epoch equals current” until a full scan completes under the new epoch. A zero-row fresh connector refuses, not 0 minutes. (b) dated overflow count unchanged; a null-date insert and a separate unstamped (source_authorization_epoch IS NULL) insert → figure refused. G5(b) does not refuse labelled unseen. G5 one labelled-flag assertion that bites: an in-window actor-or-null unseen TimeEntry with matter_id IS NOT NULL sets countEligibleNonVisibleCoverage and does not refuse; another user’s unseen does not set the flag. G5: clear a row, run markActivitiesUnseen, flag stays off for a different actor. Precedence: origin mismatch/NULL wins over unstamped when both true. countEligibleBelowEpochSameOriginCoverage labelled, sealed in fingerprint, not G5(b) refuse. Unseen is labelled only. Delete the hard-incomplete arm of countEligibleNonVisibleCoverage and its G5/G6/G10 assertions. Do not keep a predicate no writer can produce. G5 two cases, not one combined: stamped-epoch/NULL-origin is (11) — that fixture asserts the row IS in the projection, IS keyed under the actor’s verified origin, AND that the named figure is refused with (11); non-null origin differs is (6) — that fixture asserts the row is NOT keyed, NOT in the LIMIT 200, and coverage EXISTS. G5: an AU-origin row is not in the keyed set; no collision with a US source_id; no exact GET on US origin for the AU id. Matching-origin and NULL-origin rows are not withheld. Not unscoped. Not on cleared rows. Cursor reuse refuses the full figure if the fingerprint OR the sealed null-date/null-user/non-visible/unstamped/below-epoch/unverified-origin flags change. The REPEATABLE READ transaction commits after those reads, including countEligibleBelowEpochSameOriginCoverage, and before any exact-entry validation call; sealed fingerprint, not an open transaction, revalidates later. Unstamped: shared population ∧ source_authorization_epoch IS NULL → refuse; nothing else inside that EXISTS. Below-epoch and origin predicates as on the B7 card (shared population includes type='TimeEntry' stated once; quoted only by unstamped / below-epoch / origin; user clause on those three is the literal (user_source_id = $actor); unstamped NULL-user does not refuse, labelled via null-user). Precedence: origin mismatch/NULL wins over unstamped when both true. Partial reasons cite the B7 enumerated list. G5/G10 three org-Clio branches: (1) authorized exists → parse that row’s scan-state; (2) authorized empty AND any org Clio row EXISTS (no status filter) → ingest-not-authorized; status source: partial-unique live org Clio row when it exists, else any leftover; extend partial-reason enum to cover revoked/archived; (3) zero org Clio rows → missing-feeder. Null-date fixture is disjoint from 199/200/201/narrowed controls. Visible NULL user_source_id is labelled incomplete: countEligibleNullUserCoverage > 0 does not refuse. G5: an out-of-window null-user row must not refuse. G5/G6/G10 cite the B7 enumerated partial-reason list. Split the G5/G6 fixture: validation-time denial/redaction inside an otherwise complete 200 refuses the named figure; already-known quantity_redacted does not refuse. G5/G6 boundary-day non-UTC fixture. Include a non-integral hours fixture; G5 asserts both lossless per-row hours and the round-once aggregate. G6 seeds the disjoint null-date, null-user, split denial/redaction and non-integral rows in its C-composition fixture. Week-wide G5 uses the unfiltered eligible set; matter predicate only when selected. G5: live who_am_i data.id ≠ enrollment-stored provider_metadata.personal_user_id → identity-unverified refuse, zero rows disclosed. G5: stored user_source_id = '9007199254740992' with live data.id = '9007199254740993' must not publish a complete figure.

Home is this lane and still gates G/P6/P8. /matters matter-row (showActions false / onAction absent / header/cell/menu Review) is NOS-384-owned and does not gate this lane. F5 stays paused. Do not edit matter-table.tsx in this lane. G10 starts in WorkLedgerReview with personal Clio unverified, activates its Connect control, completes the real direct-session action/helper/callback against synthetic HTTP, returns to the selected review/window/matter and obtains usable reconciliation with current feeder observations. Repeat the Reconnect transition on the same personal connector; ordinary organization Settings remains separate. Cancellation/denial returns to the selection without marking personal identity usable or mutating the org connector. Reconnect alone cannot make stale ingestion current. This is an observable authorization-to-review flow, not a mocked button callback.

G10 also owns §18's full-document admission proof: with Outlook absent, revoked or requires_reauth, reload the canonical review, edit saved human fields/offline exclusion, reload and observe persistence. With BOTH sources lost, saved corrections remain usable and personal Clio Connect/Reconnect returns to the selected review/window/matter despite Outlook remaining unavailable. Current Clio observations may reconcile after authorization; stale observations remain explicit. Ordinary /tasks/chats/new without review, existing chat and /matters full reloads still redirect to /connect-outlook; Back to chat is rechecked on query-only navigation. Duplicate review parameters in both orders and forged request headers cannot render ungated ordinary chat. Unauthenticated, password-reset, impersonated and lost-membership controls remain refused by their own gates. No actual runtime run by this docs seat.

G10 adds explicit organization recovery without losing §18 mailbox admission: stale non-null active org or null active org with a sole current membership yields select-organization-required, no ledger/source read or silent session write. Use F3's real organization.setActive control, observe authoritative session/cookie update, reload the same canonical selection and then read/correct the owned ledger. An invalid selection is refused; a later org/member change aborts before disclosure/write. Ordinary resolver sole-member fallback still works outside ledger. G8 adds ledger-only read and ledger+write mutation positives versus non-ledger list/direct/chassis refusals and existing read/read-write controls.

G9 owns actual proxy/mint admission, retirement of preactivation in-flight support streams/runs and request-context checkpoint/resume controls. J3 owns the existing shell fixture's narrow compatibility-floor proof. No vendor-credential revocation or lifetime matrix. G9 exercises real requireSession-protected routes; existing clicky-mint.test.ts covers helper/caller behavior. No extra proxy test harness/package dependency.

Sol proof additions in mapped G fixtures: G5 proves impersonated strict START/callback/internal commit rejection, remove/rejoin refusing old enrollment despite surviving tokens, atomic selected-row uniqueness and app/scheduler SELECT denial on binding custody. G6 proves fresh membership/org status after remote/model pauses, root/member lock races with removal/user/org deletion, authority-function forged-origin/role denial, every invalid null/state permutation, all lifecycle cascades/SET NULL behavior and crossed stage/replace/commit UUID CAS. G5/G6/G10 prepare Outlook and Clio before the shared deadline; delayed second preparation never spends a first-source phase, and expired snapshots after phase start do not refresh/reset. G8/G9/I tests prove new direct grant survives session deletion, old/impersonated grant cannot launder through refresh/reconsent, and normal non-ledger MCP still works. None of these runtime tests has run in this docs-only task.

Merged-base proof extensions: G5/G6 pause after the complete locked final check, then run direct-session revoke, client disable, consent narrow/delete and access/family revoke in BOTH orders. Authority-revoke-first refuses mutation/enrollment; ledger-first may commit while revoke waits, then revoke completes. MCP originating-session deletion is a positive control, not grant revocation: preserve otherwise valid direct grants after FK re-discovery. Include session SET NULL, client/user/refresh cascades, changed links and NOWAIT whole-transaction rollback; no post-authority-revoke commit or lock-order fallback. G9 adds actual pre-opened impersonated realtime SSE, resumable SSE, WS and a paused run across two test processes: admission refuses while any old context remains; drain/terminate and replace, reconnect direct controls, activate, emit private activity/result and prove old transports closed/no late continuation. Rollback retains denial. No TTL-only/mock-registry proof. Existing G5/G6/G9 files own these cases, not a new transport framework.

New regression files earn their cost on concrete privacy/concurrency/identity failures. Never keep mock echoes, nonempty assertions, exact incidental wording or source-text checks. Do not silently remove a touched existing assertion. The worker’s final report enumerates every removed assertion; reviewers approve the list. G25 and D19 are closed-inventory guards that must be updated, never removed. No broad sweep through untouched dependency tests.

H. Human prose: 4 required files

  1. docs/superpowers/specs/2026-09-15-private-work-ledger-design.html: approved decisions and eventual implementation/evidence notes, with precise limitations. H owns spec §10.1 source-eligibility strings, operational-reason strings (budget exhaustion, 20s deadline, cancellation, mid-request authorization loss, share-first admission refuse), organization_inactive_or_unverified, and already_shared_thread. B7 remains sole technical authority only for source-eligibility reasons. If main advances between post-receive's ref check and deploy.sh's SHA derivation, that deploy refuses with no staging; a later push deploys.
  2. docs/superpowers/plans/2026-09-15-private-work-ledger.html: only check items backed by fresh proof, retain original scope and record measured cap.
  3. apps/web/content/changelog.json: user-facing personal review/copy-only entry once the feature is real; no claim of billing, full work coverage or automatic Clio logging.
  4. docs/research/2026-09-15-private-work-ledger-provider-preflight.md existing H4: keep this completed provider capability report at its current path. Canonical pages cite that repo path in prose, never as a live docs-site href. Do not relocate it under docs/superpowers or expand serve.ts.

Product code and prose workers are omp Astra seats per the 2026-09-08 standing rule, not new Claude panes. Degraded human-readable prose uses writer @write Astra high; never Sol/Luna for human prose. Dual reviewers stay two models (Astra + Opus). Fable R2 exhaustion and the failed Devin Fusion registration are not a prose-author change. Do not describe Devin as reviewing.

Generated STATUS.md/index.html are regenerated with status.ts, never hand-edited. H estimates later implementation-status deltas to the canonical pages, not a rewrite of the plan.

Restore obligation: missing compatibility json is unimplementable as post-vs-pre by the json itself. J1: if the live controller's static declaration is ≥ 1, missing compatibility json refuses; if the live controller has no declaration or declaration 0, missing json remains dormant. The operator must restore the durable minimum/state file under the existing lock before supported deploys proceed when a declaring controller is live. J3 deletes only the json after a declaring controller is live and still refuses. No second file. Stay in the three J files.

4. Execution order and objective packet gates

  1. P0: fresh post-transplant source/review pass. All pre-transplant reviews and repo source claims are stale, including retained eight Sol/eight Opus2 dispositions. Re-read current-main contracts/callers and obtain a fresh independent pair plus RLS re-review of the restored ledger delta. Policy S + A, the adopted 73 / 8,700 baseline (historical; §15.5 is the current map) and permanent privacy floors stay chosen. Primary provider research is not authenticated grant proof. Preflight organization.status = 'active' on target orgs; the operator restores active/verified status before ledger proof. No product author starts on old evidence.
  2. P1: effective source-grant preflight. Documentary facts are established by the provider report at repo path docs/research/2026-09-15-private-work-ledger-provider-preflight.md; this gate is NOT passed. kwiss chose existing test accounts but supplied no identifiers. The future authorized runner needs the designated Clio regional origin, personal/ingest grants, observed uniqueness-scope recording on the two designated test accounts, synthetic TimeEntry/matter/user IDs, actual registered Users/Activities Read settings and grant timing; plus a synthetic Microsoft mailbox/directory/user manifest, known sent/reply and attachment/continuation fixtures, actual delegated User.Read + Mail.Read, and secure credential delivery. Do not infer identities, create accounts, dump secrets or run the mutating simulator preflight. Observe Clio equal-account/different-user success, record the observed uniqueness scope of Clio user ids on the two designated test accounts, exact TimeEntry actor/matter access, denied entry versus hours-only redaction; use who_am_i?fields=id,account{id} and exact activities fields=id,type,date,note,quantity_in_hours,quantity_redacted,user{id},matter{id}. Observe Graph /me binding, combined Sent list versus exact uniqueBody/field agreement, full nextLink/ImmutableId continuity and metadata-only attachment responses including oversized-page handling. Record redacted assertions/status/request IDs, not bodies/tokens. No sanctioned ready runner was identified in the report; no account token/API calls occur in this documentation task. Missing actual permissions/visibility keeps reconciliation blocked; no mocked proof or silent budget/grant expansion.
  3. P2: contract freeze and schema token. Settle Outlook atomic enrollment binding, immutable per-field source dependencies, nullable Clio hours, source keys, duration/version/retry/lineage and nullable matter pair. The factory owns the exclusive schema-generation slot; no explicit subsequent grant and approved isolated target means no generation/application.
  4. P3: A, then isolated schema proof. Schema TS first; bun db:generate, inspect additive generated SQL. P3 A7 stamps NULL only. Drop markActivitiesUnseen cleared_reason NULL change. Drop enum/default-arm/partition-uses-cleared_reason residue. No feeder edit; cleared_reason is unused by this lane. Then clone/migrate/provision, real-role assertions. No separate schema merge without kwiss's explicit authorization: keep the additive schema slice in this feature lane unless he authorizes another delivery boundary. The lane's exact dependency and serialized schema-token rules remain prerequisites.
  5. P4: I and B, then C. I's direct-origin caller/issuer/resolver plumbing and B's identity/epoch-bound source reader precede C's service integration; no privileged guard is bypassed while slices land. Enable the origin helper stamp in P4 after B3 preserves stored origin, same release. G and I's configured writer author regressions in separate sequential turns. Verify saved operations and every correction/structure operation on isolated synthetic data before UI wiring. No impersonation suspension is activated by merely landing these files.
  6. P5: D with C/I admission. Register product/MCP safe-projection tools, new explicit ledger scope and private-thread share guard as one gated release unit. Suspend global support impersonation with ledger activation, denying old sessions across all mapped entrypoints; retain the floor on rollback. Preserve existing non-ledger grants and require explicit direct ledger-scope consent for new ledger authorization. Prove real issuer/deleted-session/refresh behavior and no source ingress to graph/model/checkpoints/history/Redis/SSE or alternate-tool arguments using a positively hydrated web fixture; prove both share races and executable receipt-backed corrections. Packet E is deleted, not deferred.
  7. P6: F and completed G, with a matters-only ownership gate. F4 Home/query wiring is this lane's surface and may proceed through ordinary implementation gates independently. F5 matter-table and any matters-page wiring await NOS-384 orchestrator handoff. Preserve both entrypoints and all correction acceptance cases. Verify reusable UI/Hono integration on a permitted existing synthetic runtime, root lint/typecheck and changed-export suites on the current head. Preflight organization.status = 'active' on target orgs; the operator restores active/verified status before ledger proof. Require an observed completed full Clio scan under the new epoch and origin on the target org before the reconciliation surface is enabled. No worker starts a dev server.
  8. P7: adversarial convergence. Dual reviewers stay two models (Astra + Opus). Grok xhigh deletion audit and Sol max RLS/grants audit, against ledger delta and full acceptance criteria. Findings verified by orchestrator, fixed in narrower owner files, rerun tests and fresh reviews until clean.
  9. P8: gates and release packet. Sol low @gates runs exact checks; orchestrator owns git/PR/CI decisions. H updates docs only from evidence. After any dependency transplant or new main migration, invalidate P3/P6/P7/P8 evidence and repeat. Require an observed completed full Clio scan under the new epoch and origin on the target org before the reconciliation surface is enabled. Stop at MERGE-READY, not merge/deploy.

Required J dependency before activation: the deployment orchestrator takes code handoff for the three existing §15.1 files and owns installation, compatibility-state initialization, activation and execution. While ledger stays OFF, deploy the floor-aware controller/hook, declare compatible current/fallback releases and set the durable minimum; then complete S's session/stream/run retirement and enable ledger. P5/P8 require J3's synthetic shell proof and G9's actual privacy proof. This documentation seat neither writes deployment code nor executes the procedure.

Worker discipline: one active author per worktree; no worker git/PR/merge/deploy. G uses writer @write Astra high with yolo/full writer tools/profile. Product code and prose workers are omp Astra seats per the 2026-09-08 standing rule, not new Claude panes. Every seat gets exact FACTORY_SEAT/LANE at pane creation and harness launch, readiness then successful shared-ledger registration BEFORE substantive brief. @review Astra high, deletions Grok xhigh, independent RLS Sol max, gates Sol low, finding-specific fix Luna max remain separate roles. No bare default launch or hardcoded test hybrid; no agent launch by this planning seat.

5. Database isolation: every DSN consumer

Schema-slot constraint — post-transplant: P2/P3 and every generation/regeneration command below are blocked until the factory explicitly grants this lane a later slot. #431 is squash merged; the ledger-only replay is complete; #432 NOW holds the exclusive slot. Do not run bun db:generate until that later release. Transplant or successful review is not a grant. No behavior gate is completed by updating these docs.

Isolation rationale: independent APP_DATABASE_URL, OWNER_DATABASE_URL, WORKER_SYNC_DATABASE_URL and WORKER_SCHEDULER_DATABASE_URL consumers can still reach a shared database when only DATABASE_URL is overridden. DATABASE_URL alone is unsafe; every selected consumer must resolve to the isolated target.

  1. Orchestrator names an isolated database, e.g. app_loop_private_work_ledger_20260915, cloned or fresh plus synthetic fixtures. Never write to app, app_realdata, shared preprod or a real user's account. No drop/reset of an existing target, container restart or unattended cleanup.
  2. Before importing any DB package, establish an explicit environment map overriding DATABASE_URL, APP_DATABASE_URL, OWNER_DATABASE_URL, WORKER_SYNC_DATABASE_URL, WORKER_SCHEDULER_DATABASE_URL to the isolated database with the correct role login/assumption. Override every additional DSN used by selected test/eval fixtures, discovered via source search before running. Unused role DSNs are blocked or isolated, never inherited.
  3. Inspect dotenv wrappers and command-specific environment precedence. A worker shell's env file may still contain shared URLs; never print it. Direct bunx vitest run for focused integration tests avoids package wrappers when necessary, but still requires the explicit all-DSN map. No source module is imported before this map is set.
  4. Use a process-local preflight for each distinct pool/consumer that parses and reports only variable name, host, port and database, then queries current_database()/current_user. Refuse every write if the database name is not exactly the isolated target. No credentials, URLs or query bodies in output. This is verification plumbing, not new production configuration code.
  5. Relevant observed consumers: packages/db/src/index.ts and packages/agent-runtime/src/checkpointer.ts choose APP_DATABASE_URL before DATABASE_URL; packages/db/scripts/provision-roles.ts, buildProvisioningPool, chooses OWNER_DATABASE_URL first; packages/worker-chassis/src/config.ts, loadWorkerConfig, resolves independent sync/scheduler/owner pools; packages/db/drizzle.config.ts uses DATABASE_URL.
  6. Seed under createSyncDbClient for connectors/matters and withRlsTransaction(..., { userId }) for ledger records. Synthetic fixture identities only. Roll back test transactions where possible; do not clean up another lane's objects.
  7. Clone/migrate/provision exercise database-scoped changes. provision-roles also provisions shared cluster roles, so serialize it with the schema/DB coordinator and do not rotate shared login passwords. The isolated database name does not isolate cluster-global role DDL.

Generation/application commands are future implementation gates. Pass the all-DSN environment explicitly to each process and set DB_APP_LOGIN_USER to the approved isolated-test login. Do not paste real DSNs into this document.

# Root, only after schema token and TS edits
bun db:generate
# packages/db, every command gets the explicit isolated all-DSN map
bunx drizzle-kit migrate
bun run scripts/provision-roles.ts
bun run scripts/check-access.ts
# Still packages/db; eventual ledger delta against current main, after factory slot
bun scripts/lint-migrations.ts --changed origin/main
bun scripts/check-journal-monotonic.ts
bun scripts/check-migration-immutability.ts

Gate record must include expected tables/columns/policies/grants, actual role denial, migration ledger entries, generated chain integrity and an unchanged-schema generation check under the serialized token. A green migrate alone is not proof. After rebase, regenerate this lane's artifacts and use a fresh isolated target rather than hoping an already-applied migration reruns.

Ledger role-proof owner: G6 executes real SQL against both ledger tables under the required application identities and forbidden ordinary sync/scheduler roles, proving actual denial and intended owner access. check-access remains a general provisioning/access gate; its Phase B hardcoded hot-table list does NOT prove ledger-table role denial. No check-access file expansion is required or allocated.

6. Biting behavioral verification matrix

RiskExercise / observable contractOwning tests
Owner and organizationSeed two owners in one org (one admin), another org, same-looking source IDs. G6 real app_role SQL cannot read/update the other's episode/evidence or attach it by FK; missing user GUC fails closed. G6 also proves forbidden ordinary sync/scheduler access to both ledger tables. check-access Phase B is not ledger denial proof. API and direct MCP invocation return indistinguishable not-found/denial, not leaked versions/titles.G6/G1, G5, G8
Actor attributionPersonal sent substantive message qualifies; incoming/quoted-only/draft/delegate mismatch/shared mailbox/colleague action does not. Cross-account isolation rests on unproved global uniqueness of Clio user ids; P1 records the observed uniqueness scope on the two designated test accounts. User email/display-name coincidence has no effect. No firm who_am_i.G2, G3, G5
Durable correctionReconstruct overlapping date windows; change matter, description, unknown/confirmed duration and dismiss one episode; refresh changed provider content. User fields and dismissal remain; source proposal changes only; no duplicate anchor, no auto-confirm.G6, G10
Server-owned range reconciliationOne shared 1.25-hour Clio key counts once (75 known minutes). Dismissed-only/unlinked keys use provider date/matter; redacted hours stay unknown, exclusion does not erase time. Version/epoch changes invalidate cursors; no client page sums. Overflow is B7's named countEligibleRecordedTimeOverflow COUNT/EXISTS, not a 201st key or five full pages; projection LIMIT 200. Fast accessible one-hour populations 199 and 200 produce non-null 11,940/12,000-minute figures; 201 produces null/too-many-entries and narrow-filter guidance. Narrow that same fixture to 200 and obtain 12,000 minutes. Operational exhaustion below 200 remains partial; continuation never promises a full oversized total. Saved-work is independent of Clio keys and includes mail-only episodes.G5, G6, G10
Offline exclusion and retry distinctionAfter disconnect exclude an existing owned candidate/association by ID/version/UUID; zero provider/token calls, safe receipt, persists after reconstruct/reconnect. Foreign/version-conflict refuses; new association requires exact current access. Ordinary stale UUID after another edit cannot reapply; structural UUID/hash still resolves original result IDs after child edits.G6, G8, G10
Unknown effort / coverageNo duration from mail timestamps/count. Redacted-hours entries remain discoverable/associable with null duration. Coverage excludes untracked/unassigned/ineligible time; default no-match is scoped, not completeness. Verify once-per-connector identity, concurrency/item/page caps and the 20-second source phase after acquisition. Slow preparation is not mislabelled a source timeout.G2, G5, G6, G10
ConcurrencyTwo writes with the same expected version: one persists, the other conflicts. Identical latest retry returns its saved result; changed payload under the UUID conflicts. Concurrent overlapping reconstruction creates one anchor. No mock-only assertion of a version parameter.G6
Merge/split atomicity and handoffReal failure after child/evidence mutation rolls back; identical retry returns result IDs. Refresh resolves unique anchors to active survivors before insertion, including Clio-only anchors and manual children. Owned link references and staged partitions commit via MCP to the same service; replaced/expired/foreign/stale receipts refuse without partial edits. Deep-link handoff remains usable.G6, G8, G10
Revocation and immutable fieldsEdit R1 description, then redact/change R2 and refresh the evidence row. R1 text remains stored with unchanged dependencies but withheld from read/copy/model/history; fresh proposal may bind R2. Reconnect identical source IDs/new epochs cannot relabel retained edits. Independent human statement and confirmed scalar survive.G4, G6, G7, G8, G9, G10
Dynamic tool hidingBoth surfaces: mail only, Clio identity+firm only, both, neither, unknown lookup, lost permission, stale session. Source reconstruction hidden when no source is admitted; stale/direct call refuses. Saved human reads/corrections stay available. mcp:read cannot reconstruct/update/restructure.G7, G8
No generic source ingressThe same fixture first yields provider sentinel text in authorized web hydration. Generic product and MCP results, graph/checkpointer/model input and real registered web_research HTTP query/focus never contain it, including same-round parallel calls. No artificial source-less fixture or mocked safe graph as proof. After revoke, web hides it; owner-authored text remains usable.G7, G8, G9, G10
Selected-account atomicityPause A credential work; commit B reconnect; resume A. No B bearer stamped with A identity/epoch or A bearer stamped with B binding. A stale callback/refresh cannot overwrite B. Inspect the real transaction before/after commit and rollback; legacy multiple account rows without binding refuse ledger admission.G5, G11, G12
Marker/transport/private sharingInsert the exact internal assistant marker, interrupt before reply, reload actual history and prior-turn summary: marker is invisible and earlier meaningful reply remains. Real text/mixed replies remain. Admission permanently blocks sharing even on failure; both thread-lock race orders hold. Admit independently human-authored private text, disable capabilities and verify the rollback-compatible build still refuses sharing and preserves visibility. Mixed-version activation prevents admission until EVERY share-serving instance enforces D7. Per-bind local admission and positive-source transport/Redis/web_research controls remain unchanged.G7, G9, G10
Read effectsGeneric list/read make no provider/token calls or ledger/credential/queue writes; product thread admission may write only its content-free privacy marker. Dedicated web hydration may rotate credentials/request canonical recovery, never mutate ledger/versions or enqueue reconstruction. Inspect credential, ledger, marker and queue effects separately and recheck selected binding/epochs.G5, G6, G9
Phase timing and cancellationPrepare ALL Outlook/Clio credential snapshots before one absolute source deadline. Delay second preparation past 20 seconds, then give both sources the same full phase; no per-provider reset. Expiry/change during scanning yields partial/reprepare-required, never refresh or a new timer. Cancellation settles existing maintenance with no new disclosure/ledger commit. Recheck live actor/member/org after every remote/model boundary. Supported HTTP abort and real rollback remain; no global KMS cancellation claim.G5, G6, G10
Worker SQL fidelityKeep existing SQLite page/cursor cases with the added epoch column; do not claim locks from translated SQL. In PostgreSQL, race epoch change/page commit and inject failure after row mutation: actual locks/fencing and rollback protect rows plus checkpoint together.G4, G6
Coverage and attachment limitsOld/null Clio epochs remain unknown until feeder reobservation, with no schedule change. Attachment metadata stays within100 records/5 HTTP attempts/pages/4 concurrency and the shared deadline. G5 distinguishes a completely listed empty message, an oversized/cut page and messages never scheduled after exhaustion; G10 shows per-episode unchecked/partial labels. hasAttachments=false is not complete-empty evidence. No bytes or new detail-fetch path.G4, G5, G10
Usable UIHome (this lane; still gates G/P6/P8) enters the review/filter, persist each correction, combine/split without drag, show stale conflict and unknown, prepare copy with no stale evidence. Return later through Home and see saved state. Screenreader labels, focus, narrow viewport and both light/dark themes. NOS-384-owned (does not gate this lane's completion): /matters matter-row (showActions false / onAction absent / header/cell/menu Review). F5 stays paused. Do not edit matter-table.tsx in this lane.G10 plus actual ui-test
Callback isolation and exact CHECKsPersonal OAuth success/error/forged state cannot mutate the firm's connector or ingest queue. Database rejects incomplete estimate pairs, min < 0, max < min, max ≤ 0 and confirmed ≤ 0; explicit Unknown persists without numerical invention.G1, G3, G6, G13

Use real registered tool names for graph/MCP coverage, real isolated DB transactions for owner/concurrency/rollback, and a deterministic synthetic provider/model for source boundaries. A mocked database echoing caller fields cannot prove isolation, dedup or concurrency. Do not use full real-user mail/Clio data for fixtures.

7. Gate commands and actual-surface checks

Run package commands from their package directory with the explicit isolated environment. Do not use bun --filter … test for Vitest packages; it selects Bun's runner. New test paths below are planned paths from G.

Every command below, including root lint/typecheck/build and every child eval process, receives the same explicit isolated all-DSN environment. Inspect eval subprocess/dotenv precedence; no inherited shared target. Paid synthetic eval calls remain bounded under the standing allowance; no redundant per-call approval or unattended paid loop.

# Focused integration examples, appropriate package cwd and all DSNs isolated
# packages/agent-runtime
RUN_DB_INTEGRATION=1 REQUIRE_DB_INTEGRATION=1 bunx vitest run src/work-ledger/service.integration.test.ts
RUN_DB_INTEGRATION=1 REQUIRE_DB_INTEGRATION=1 bunx vitest run src/memory/access.integration.test.ts
# apps/mcp-server
RUN_DB_INTEGRATION=1 REQUIRE_DB_INTEGRATION=1 bunx vitest run test/work-ledger.test.ts
RUN_DB_INTEGRATION=1 REQUIRE_DB_INTEGRATION=1 bunx vitest run test/oauth-auth.integration.test.ts
# apps/connectors-api
RUN_DB_INTEGRATION=1 REQUIRE_DB_INTEGRATION=1 bunx vitest run src/lib/work-ledger-sources.integration.test.ts test/user-tokens-routes.test.ts
# packages/chat-runtime, isolated Redis namespace as well as all DSNs
RUN_DB_INTEGRATION=1 REQUIRE_DB_INTEGRATION=1 bunx vitest run src/work-ledger-transport.test.ts
# apps/worker-clio, existing SQLite fixture suite
bunx vitest run test/loops/activity-sync.test.ts
# apps/web: changed start/completion and both actual callbacks
bunx vitest run lib/connections/outlook-oauth.test.ts app/api/connectors/outlook/callback/route.test.ts app/api/connectors/clio/callback/route.test.ts
# apps/worker-context
RUN_DB_INTEGRATION=1 REQUIRE_DB_INTEGRATION=1 bunx vitest run src/digest.integration.test.ts src/thread-loader.test.ts
# apps/worker-memory
RUN_DB_INTEGRATION=1 REQUIRE_DB_INTEGRATION=1 bunx vitest run src/backfill.test.ts src/mine.test.ts src/synthesize.integration.test.ts
# Full suites, each from its own directory
bun run test
# Root
bun run lint
bun run typecheck
bun run eval:gate:cheap
# apps/web: browser/server import-boundary proof
bun run build
# packages/e2e: existing permitted runtime only
E2E_REUSE_SERVER=1 bunx playwright test tests/private-work-ledger.spec.ts

Full suites for changed exports: db, provider, connector-auth, connector-outlook, connector-clio, clio-sync, worker-chassis, agent-runtime, chat-runtime, worker-context and worker-memory. Also connectors-api, worker-clio, MCP and touched web tests, including Outlook OAuth and both callbacks. Secrets is unchanged and no new crypto cancellation suite is required. Every process/child receives the isolated all-DSN map; G9 isolates only its own Redis fixture keys, not a production resource framework. G1 runs in G6, G2/G3 in G5, G7 in G9. Cheap eval still covers actor-scoped admission; root gates remain required.

Actual UI proof: use skill ui-test against an already running authorized runtime displaying this branch and connected to the approved isolated fixture target. If the shared runtime runs another checkout or shared DB, it cannot prove this implementation. Coordinate with the runtime owner, never start/restart a server or shared Postgres/Redis here.

browser-use open http://localhost:5140/tasks/chats/new
browser-use state
# Use the indexed controls with the synthetic owner on Home (this lane; still gates G/P6/P8). /matters matter-row (showActions false / onAction absent / header/cell/menu Review) is NOS-384-owned and does not gate this lane.
# Inspect saved rows, corrections, structure preview, conflict and copy output.
browser-use close

Run actual UI checks with a synthetic authenticated account and controlled provider fixture. Do not send a real sign-in email or mutate a real user's session as setup. E2E_REUSE_SERVER=1 is mandatory: the inspected packages/e2e/playwright.config.ts starts a dev server when it is absent. If no permitted runtime exists, report the exact unavailable prerequisite and keep the UI acceptance gate unchecked; unit/component mounting is not full-page proof.

Earlier Astra/Opus2 findings and Sol-max's eight RLS dispositions remain recorded, not clean. Fable exhausted before substantive R2 review and Devin Fusion registration failed; neither supplied a clean result. This newly frozen policy/provider-contract revision requires a fresh independent review pair and RLS re-review. Authority/lock/runtime and effective registered synthetic grant proofs remain pending.

8. Rollout, rollback and completion

9. Recorded approval and review status

Approval recorded 2026-09-15: kwiss chose Proceed to plan review and Web-only source evidence. The subsequent explicit MCP decision is IDs and review links only, superseding the prior human-owned free-text MCP output allowance. Spec §7 names the transport projection distinction: ledger MCP excludes all free-text output; product-chat human text and authenticated web descriptions/copy remain. Structured corrections/combine/split and authenticated review/action links stay available. External host retention cannot be revoked; no host certification or taint/declassification framework.

Additional explicit decisions: ledger_support_policy = Suspend impersonation; ledger_mcp_consent = New explicit ledger scope; ledger_synthetic_preflight = Designate existing test accounts. Adopt S + A with the complete 73-file / 8,700-line map presented with that support option. Do not re-ask support/MCP or split out a cap question. Suspension begins with ledger activation and becomes a permanent rollback floor; old impersonated sessions are denied. Existing non-ledger MCP grants remain unchanged; legacy clients obtain ledger access only through explicit new direct ledger-scope consent.

Both pages remain planned, all implementation gates unchecked. Existing test accounts were chosen without supplied identifiers; effective registered grant permissions remain unproved despite completed primary research. A fresh independent review pair and RLS re-review are required after this freeze. Existing Clio advisory/metadata simplification and source-free transport remain; no global crypto/offboarding rewrite, product work, schema generation/slot or merge permission follows.

10. Combined round-one dispositions — 2026-09-15

The orchestrator accepted all eight Astra findings and the following Fable dispositions. This is a concise decision record, not an agent transcript. Removed implementation paths are deleted from the map, not deferred. Re-review remains pending.

Accepted finding / source evidenceDisposition and proof gate
1. encrypt.ts:400–429 selects LIMIT 1; user-tokens.ts:47–58 only upserts; outlook-oauth.ts:322–337 separately authorizes.A8/B8–B15 map atomic selected account/credential/grant epoch, strict refresh snapshots and every changed wire caller. G5 pauses A/B interleaving; G11/G12 update broken caller/route tests.
Astra 2: Redis resumable stream stores source-bearing chunks; mocked run tests miss replay.Accepted risk, superseded sanitizer solution by F1 source-free product tools. No source-serialization/resumable rewrite; I now includes run.ts for trusted actor provenance only. G9 positively hydrates the same fixture on web and tests actual generic origin/late/resume/fallback transport without source ingress.
Astra 3: read-only web_research sends model query/focus externally.Accepted risk, solved by source-free results rather than taint allowlist. G7 runs inside G9 and inspects actual single/parallel outgoing arguments. R2 graph changes are solely async actor-scoped admission, not enrichment or source rehydration.
4. A mutable evidence pointer cannot preserve the authorization of retained source-edited R1 text after an R2 update.A1/C1 immutable per-field identity/epochs/revision/visibility snapshots, separate retained edit and fresh proposal. G6/G7 prove stored-but-withheld R1, never relabelled R2.
5. Clio primary permissions/authorization docs fix app permissions at grant; clio-client.ts:251 already permits who_am_i.B3 uses unchanged transport/wildcard export. Completed primary report establishes fields=id,account{id} with regional account comparison; P1 still must prove actual registered grant permissions, nested visibility and exact-entry access. No dynamic-scope or mock-echo claim.
6. getValidGraphToken refreshes credentials; access-token route:583–589 requests canonical recovery.No ledger mutation/reconstruction on hydration, narrowly permitted credential maintenance; list remains local. G5/G6 independently inspect credential, ledger and queue effects.
7. Clio permissions docs retain Activity records while quantity fields become null and quantity_redacted=true.B7/C1 preserve authorized entry/link with nullable duration; no cached hours or false absence. G5/G6/G10 exercise visibility loss and shared-entry arithmetic.
8. Existing worker activity-sync.test.ts:181–188 lacks epoch column; :67–68 strips FOR UPDATE/SHARE.G4 updates that fixture and preserves page/cursor tests; G6 drives real worker commit against PostgreSQL for lock/rollback proof. No proposed clio-sync activity-epoch test.
Fable findingOrchestrator disposition and mapped amendment
F1: generic chat transport/taint complexityACCEPT source-free product/MCP, web-only hydration/synthesis. Delete E/privacy helpers; retain private-human-field marker/share guard. R2 supersedes the blanket no-graph-edit statement only for async feeder admission. G7/G9/G10 retain actual positive-source counterexamples.
F2: replace local Clio reader with live-onlyREJECT replacement: the brief requires a narrow personal reader over locally ingested clio_activity. ACCEPT coverage limitation: eligible tracked matters only, explicit exclusions, no full-week or untracked absence claim. B7 retains epoch-fenced local corpus plus bounded exact personal authorization, not provider-list completeness.
F3: blind MCP corrections/split/linkACCEPT discoverability concern; REJECT dropping required corrections. C/F offer owned opaque link references and explicit web-staged receipt for source partitions; MCP resolves to the same transaction union. Missing reference returns Review required/action link; G6/G8/G10 prove eventual execution and stale/foreign receipt refusal.
F4: Home/matter wiringACCEPT: page.tsx links above LiveChat; matter navigation independent of optional onAction/showActions. Direct personal-helper import, no assumed barrel or chat-ui refactor. F4/F5/G10 map the surfaces.
F5: migration command pathsACCEPT: migration discipline scripts run from packages/db, alongside migrate/provision/check-access, not nonexistent root scripts. Commands in §5 corrected; schema slot unchanged.
F6: repeated identity requestsACCEPT: B7 resolves identity once per distinct connector/request; exact reads share bounded concurrency/pages/deadline. F batches visible hydration, not N separate identity calls. G5 proves cadence and partial coverage.
F7: personal OAuth error/state isolationACCEPT: distinct signed prefix/current-session binding; malformed/forged personal state cannot mark org row; no personal sync enqueue. Use in-process bundle refresh only after owner check, never broaden general bundle route. B4/B5/B7 and G3/G13.
F8: Clio scopes versus transportSame accepted correction as Astra 5: fixed developer-app grant, enforced GET-only transport, already allowed who_am_i. P1 proves actual permission/account contract; no per-mode dynamic scopes or mock scope proof.
F9: positive source controlsACCEPT: G7/G9/G10 use the same authorized web source sentinel, then revoke it; generic graph/API/MCP/history stay source-free throughout. Existing human fields survive. No vacuous all-empty/source-less fixture.
F10: test/review seat precisionAccepted concern, superseded hybrid assignment: configured writer @write Astra high authors G under full yolo/tools/profile and factory registration-before-brief. Fable high product workers require FABLE_ALLOW_DIRECT_EXECUTION=1. (Superseded by §44 Opus 7: product code and prose workers are omp Astra seats per the 2026-09-08 standing rule, not new Claude panes.) @gates never writes; independent Sol-max RLS is not the test author.
F11: document metadataACCEPT: design-spec title suffix and Status/Author/Date/Repo/Related added to canonical pages, without operational pane IDs or context-file edits.
F12: eval isolationACCEPT: every eval child process gets the same isolated all-DSN map. Standing allowance covers bounded synthetic calls, not production billing or unattended paid loops.
F13: superseded reconstruction and duration/link boundariesACCEPT anchor-only idempotence; lookup anchor before episode insertion, including Clio-only/manual cases. Exact duration CHECKs and owned episode query links are in A/C/F and G1/G6/G10. No blind upsert of superseded reconstruction keys.

Rejected dismissal: Fable treated Outlook binding risk as merely additive. Retain Astra 1's atomic selected account/credential/new-epoch binding: an account-ID field existing is not proof that the currently consumed account was selected in that grant. Both reviewers agree personal connectors stay outside org ingest, identity GET is already allowed, composite nullable matter FK is feasible and no product deletions are needed.

Updated technical prerequisites: primary research establishes Clio's regional-origin + data.account.id discriminator; effective registered app/grant permission sufficiency remains unproved. Existing test accounts are chosen without supplied identifiers. No portal/grant mutation is authorized here. The adopted S + A map is 73 files / 8,700 lines, superseding the former 58 / 6,500 ceiling. Personal/Web-only evidence, nullable matter FK, owner RLS, shared-entry dedup, no broad Clio grant, no personal sync feeder and factory queue restrictions remain in force.

11. Accepted Astra R2 dispositions — 2026-09-15

Finding / inspected sourceDecision, exact implementation seams and proof
R2.1: connector-outlook/auth.ts:159–179 signs org only; web outlook-oauth.ts:282 selects current connector at callback.ACCEPT START-bound actor/org/connector/generation in one-use state. B13/B16/B17 map web start, local auth and actual callback/error path; B9 atomic commit compares original, not callback generation. G5/G11/G14 prove delayed A after completed B, session change, replay and no B error mutation. Generic connector.ts calls remain ordinary/unverified; existing auth wildcard export exposes the local decoder without another file.
R2.2: legal-assistant/index.ts:16–33 assembles tools; graph.ts:1353–1355 binds a synchronous snapshot.ACCEPT async actor-scoped resolver supplied by index, awaited at every graph binding/exposure and restored dispatch, plus invocation rechecks. D1/D8 and C own it; global tools/index.ts leaves the map. G9 proves actors/feeders across rounds without global mutable tools. No return of taint.
R2.3: generic credentials plain-upsert; worker refresh already has per-connector advisory.ACCEPT atomic personal enrollment/race safety; chosen simplification: B18 acquires the EXISTING hashtextextended(connector_id,0) advisory, and B8 rejects legacy personal writes regardless of path label. Existing fence rereads and preserves Clio metadata under that lock. Remove B19/global org lock rewrite, not race tests. G5 proves actual unchanged refresh success/error interleavings and rollback.
R2.4: missing source transport signal; all-in credential/KMS SLA was our expansion.ACCEPT B21/B10/B6/B7 and C/F phase-bound source timing and cancellation; REJECT global crypto/Redis expansion. B18 plus unchanged canonical fence preserve personal enrollment safety. G5/G6/G10 test no new ledger work after cancellation, not global KMS cancellation. No 20-second overall request claim.
R2.5: adding required legacy user-token body fields violates one-release overlap.ACCEPT dedicated strict capability/enrollment endpoint and versioned acknowledgement. B9/B11 clear/check verified binding and revision; B13/B15 never downgrade strict requests. Old-web/new-API enrollment works unverified; new-web/old-API refuses new strict enrollment before writes while existing mailbox use works. G5/G11/G12 prove pairings. N+1 removes the old enrollment endpoint/client and web's temporary legacy-state handling after rollback-floor migration; generic connector primitives are not ledger enrollment aliases.

Historical R2 checkpoint: Fable exhausted, Devin Fusion registration failed, and Sol-max had not yet returned. The later Sol result is recorded in §13. Earlier R2 safety decisions survive the narrower B18 transaction; source-free handoff and per-bind admission remain. No clean pair, schema slot or product work is authorized.

12. Fresh verified review dispositions

FindingDisposition and proof ownership
Astra 1 — private marker lacked representation/visibility.ACCEPT exact assistant marker in spec §7; C writes, D7 refuses sharing, D9 filters marker-only history and skips it in last meaningful assistant lookup. threads.ts already consumes both helpers at :884/:998. G9 interrupts admission before reply/reloads/prior-turn summary; F/D2 explain permanent restriction. No taint framework.
Astra 2 — no explicit range reconciliation owner.ACCEPT readWorkRangeReconciliation in C and dedicated F reconciliation route; B7 canonical source paging. Separate episode/provider date/matter/dismissal populations, dedup before joins, fixed cutoff/version, fresh bounded authorization, null incomplete totals/redacted unknown. G5/G6/G10 cover cross-batch shared and dismissed-only entries. Never sum visible hydration pages.
Astra 3 — exclusion wrongly required provider availability.ACCEPT associate_clio requires exact provider access; exclude_clio_relationship on existing owned relation requires membership/owner/version/UUID only. C stores exclusion tombstone, F/MCP remain usable offline with safe reference/receipt; G6/G8/G10 prove zero provider calls and persistence.
Astra 4 — unconfigured Sol-medium test hybrid.ACCEPT configured writer @write Astra high, full yolo/tools/profile, FACTORY_SEAT/LANE at creation/launch, registration before brief. Fable workers require direct-execution flag. (Superseded by §44 Opus 7: product code and prose workers are omp Astra seats per the 2026-09-08 standing rule, not new Claude panes.) No generic default agent, no gates writer; independent Sol-max RLS remains distinct.
Latest Astra full pass — one P1: rollback/mixed share-serving fleet loses permanent marker refusal.ACCEPT: verified existing shares.ts:192–208 checks owner/origin only, not the marker. Correct §8 and spec rollout: every share-serving instance enforces D7 before admission activation; after any admission, only rollback-compatible builds retaining D7 refusal and D9 visibility may serve these threads. Capability disablement never waives the floor. Extend existing G9 with independently human-authored private text, disabled capabilities, continued share refusal/visibility and mixed-version activation refusal. No new file, table or taint redesign.
Chosen simplification and coverage/precision clarifications.B19 removed only after source proof of existing lock/metadata preservation; B18 strict personal commit uses same key, B8 blocks every legacy personal path. Old/null observation epochs cannot be repaired by ledger refresh and may wait seven days/indefinitely; dependency schedule unchanged. Attachment aggregate caps, monotonic Outlook epoch/same-transaction bearer-revision, ordinary versus structural retry and IDs/enums/counts-only audit (human text excluded) are explicit in spec and G tests.
Opus claims/options.Global KMS expansion claim is obsolete: that work is already deleted. Optional generic-title disclosure and receipt replacement are rejected; retain source-free handoff and per-bind local admission absent demonstrated bugs. Settings navigation presents no verified security issue. These dispositions are not a clean review.
Opus2 / 1 — local selection and provider-validation budgets.CLARIFY the existing bound: local 200 canonical entries / up to 5 keyset pages, default 40; independent provider cap 200 distinct exact validations, ≤ 4 concurrency, same 20-second post-acquisition phase. Selection spends no provider slot. Same-entry retries spend existing HTTP attempts/time, not a new distinct slot or local page; exact GETs are not limited to five. No spend increase or proof cache.
Opus2 / 2 — full figures for oversized or operationally partial scopes.ACCEPT explicit disclosure: a full eligible-range figure requires the WHOLE population ≤ 200 and all required validations/coverage complete in the request. Above 200 return null/too-many-entries with narrow-dates/matter guidance; continuation only browses. Even ≤ 200 can exhaust operational limits and remain partial. Preserve known/redacted-hour semantics and the existing bounded/partial capability, not an eventual-full-total promise.
Opus2 / 3 — positive boundary controls.G6/G10 use fast accessible one-hour entries with current observation coverage: 199→11,940 minutes, 200→12,000 minutes, both non-null; 201→null plus guidance; narrow that SAME fixture to 200→12,000 minutes. Local selection cannot drain the provider counter. Retain sub-200 budget-exhaustion and redaction cases. An always-null implementation fails; no new test file.
Opus2 / 4 — outer MatterTable action guards.ACCEPT verified header :138 and cell :168 showActions guards as well as RowActions :243. F5 explicitly edits all three in matter-table.tsx so personal Review stays available with aligned columns and without admin callbacks. G10 exercises that path; no extra file.
Opus2 / 5 — unnecessary older generic schema-merge claim.DELETE the override claim from P3. Positive rule only: no separate schema merge without kwiss; preserve dependency and schema-slot prerequisites. Do not claim a reviewer search proved any prescription absent globally.
Opus2 / 6 — ledger role-denial proof ownership.ACCEPT G6 real SQL as the actual proof for both ledger tables and required/forbidden roles. check-access Phase B uses a hardcoded hot-table list and is only a general gate here, not ledger denial evidence. Keep check-access outside the edit map; no new file.
Opus2 / 7 — current-user path and provider preflight.CLARIFY extensionless /api/v4/users/who_am_i?fields=id,account{id}, never .json; keep the allowlist unchanged. Completed primary report documents data.account.id with canonical regional origin as the account discriminator. Actual registered synthetic grant visibility/permissions and exact-entry access remain unproved; fixtures cannot establish them.
Opus2 / 8 — optional removal of credential_id.RETAIN explicit exact credential-row snapshot identity with revision/grant binding. Removal is optional, not a demonstrated defect. Primary identity contracts are documented; actual selected-grant binding and visibility remain technical proof gates. No unnecessary schema/interface churn.

Current review state: §§21–22 record fresh Astra3 and completed Opus5 separately; Opus confirmed the provider-hook/patch design and prior cap arithmetic, not cleanliness. Earlier history and Sol4 remain. Approved MCP output choice and exact consent algorithm are unchanged except the explicitly accepted closures. NOT CLEAN: implementation, actual provider/SDK/protocol/owner/RLS proof and fresh review remain pending. #432/NOS-384/deployment handoffs remain.

13. Sol-max RLS review: eight dispositions and adopted authority expansion

FindingSource, disposition and proof owner
Sol 1 — offboarding, retained Outlook grants and cross-org account uniqueness.PARTLY PREEXISTING: auth.ts:81–98 only deletes sessions; worker-mail renewal :281–301 has no membership predicate; connectorUserTokens schema :669–671 is globally provider/account unique. Name separate platform removal/leave/notification/renewal/account-release remediation; do not claim this lane fixes it. Ledger B/C binds strict enrollment and immutable source snapshots to member.id; new membership after remove/rejoin cannot reuse old enrollment. G5/G6 deny stale START/source access, preserve owned human rows and fail closed on cross-org account conflicts. No gratuitous worker/webhook rewrite.
Sol 2 — support impersonation and durable MCP provenance.ACCEPT: admin() is enabled at auth.ts:598; web session resolver drops impersonation; OAuth access/refresh session FKs SET NULL (oauth.ts:59–60,87–88), and oauth-auth.ts:63–129 intentionally accepts durable grants without browser-session liveness. A live session join or mutable consent-only direct flag is not a fix. kwiss chose option A's explicitly/directly authorized ledger scope and policy S's global impersonation suspension with ledger activation, including old-session denial and permanent rollback floor. The complete 73 / 8,700 map is adopted. No legacy/refresh laundering, support exemption or withdrawal of ledger MCP operations; existing non-ledger grants unchanged. I/G8/G9 own real issuance/deleted-session/impersonation tests.
Sol 3 — live membership/active org and lock races.ACCEPT spec §3.4 entry/postremote/pre-model/post-model/precommit/preresponse checks and current member ID. House TS policy supports EXISTS member (managed_mailbox.ts:18–20), auth schema supplies organization.status; new ledger RLS includes both. User SHARE → org SHARE → member KEY SHARE precedes owner/provider advisory and sorted children. A5 narrowly asserts/locks privileged authority without broad auth grants. G5/G6 must prove remove/rejoin/deletion/refresh orders; no globally-safe-lock claim from static inspection alone.
Sol 4 — selected binding leaked through connectors grants.ACCEPT: provision-roles.ts:579 grants app SELECT connectors and :714 grants scheduler SELECT; scheduler bypasses RLS. Move authoritative JSON binding to sync-only connectorUserTokens in the SAME schema/connectors.ts, with row checks and one-selected-row partial uniqueness. Existing :732–742 excludes app/scheduler credential access. B9/B11/B12 atomically select/clear/refresh it; ordinary callers stay compatible. G5/G6 prove denied direct SELECT and no A-bearer/B-revision pair. No third table or connector-column ACL churn.
Sol 5 — nullable CHECK loopholes and inconsistent states.ACCEPT complete null-safe matter pair, duration branches, observation pair/order and dismissal-state constraints in A1/spec §4. Both nullable matter fields must be null OR both present with matching org. G6 uses real SQL invalid INSERT/UPDATE permutations, including partial-null pairs, stale estimate/confirmed combinations and dismissal reasons on active rows.
Sol 6 — deletion lifecycle.ACCEPT working_profile.ts:175–180's explicit user/org CASCADE precedent. Member removal retains inaccessible ledger rows; user/org deletion cascades episode→evidence; evidence parent CASCADE; matter only SET NULLs its nullable pair. Immutable connector/credential/member source references are scalar snapshots, no FK. A1/A2 and generated SQL must have no accidental NO ACTION; G6 verifies lifecycle, not only migration success.
Sol 7 — source phase starts before all preparation.ACCEPT prepare ALL selected credential snapshots first, then one absolute 20-second source deadline across Outlook/Clio internal hops. Prepare/scan stay in mapped B/C files; no stored capability cache or KMS framework. A source needing refresh after phase start becomes partial/reprepare-required; no nested acquisition/reset. G5/G6/G10 delay second preparation and expire snapshots during scanning.
Sol 8 — coordinator and crossed stage/commit locks.ACCEPT coordinator ∈ inputs. All stage/replace/commit operations take authority locks, owner advisory, then sorted/deduplicated input/coordinator locks once. Locked receipt UUID/payload/input-set CAS must match any discovery read; creation expects NULL, replacement exact old UUID. G6 PostgreSQL crosses coordinators and replacement/commit: overlapping stages may coexist, but one matching replacement winner and no two commits against the same input versions. No new table/file.

13.1 Chosen policies and retained technical proof

Chosen MCP option A — new explicit direct-authorization scope: use mcp:private-work-ledger beside existing read/write scopes. apps/web/lib/auth.ts guards resolved authorize/consent scopes with a fresh direct session/member/org check at consentReferenceId, and checks original stored authorization-code context before token issuance. Cached pinned provider 1.6.23 shows hooks before consent/code issuance and immutable refresh scope ceilings at dist/index.mjs:52–56,512–518,617–679,781–805,3934–3975. Existing consent receiving the scope never upgrades an old token lacking it. Legacy clients require explicit direct ledger-scope consent creating a new authorization; no refresh/rotation/deleted-session inference. Require the whole issuer fleet guard before advertising the scope and on rollback. Ordinary scopes/defaults and existing non-ledger grants/functions stay unchanged. Every ledger operation remains available, including offline exclusion/staged structure. Real pinned-provider HTTP/SQL proof must show a newly directly authorized grant survives originating-session deletion and refresh while old/impersonated grants stay ledger-denied.

Alternatives not selected: immutable per-code/access/refresh provenance storage (B) and selective actor-aware support history/copy access (T) are outside this adopted map, not pending choices. No provider fork/provenance backfill, consent-only boolean, permanent browser-session requirement, grandfathered unknown grant, support exemption, taint framework or removal of ledger MCP capability.

Chosen support policy S — bounded activation sequence: dormant rollout preserves existing support behavior. During activation keep ledger OFF, prohibit new impersonation and reject old-session entry, then retire all preactivation SSE/WS connections and outstanding runs across the fleet before enabling ledger. The per-request resolver alone does not close established transports. §14 specifies process quiescence/drain/replacement and proof; no transport framework. Ordinary direct users reconnect, existing non-ledger MCP grants stay unchanged. Post-admission rollback retains suspension, old-session denial, D7/D9, issuer guards and retired-context exclusion.

Custody/authority decisions: selected Outlook binding stays on connectorUserTokens; B9/B11/B12 and strict wire types change together. A5 uses the full root/session/client/refresh/access/consent NOWAIT/SHARE graph in spec §3.4 and §14, not roots alone. Keep fixed search path/GUC validation, no secrets/ledger-row output, required app/sync EXECUTE only and no blanket auth UPDATE grants. Source enrollment keeps original member ID and direct session. Global former-member cleanup/cross-org ownership remains separate; no global offboarding fix in this count.

13.2 I. Required additional caller/test map — chosen A + S

These fifteen existing files are the original I map, not the current count. I now has twenty-two paths including registration, proxy/mint, §20.1's provider patch/Bun lock and I22 ChatRow. The earlier no-schema/no-patch sufficiency claim is superseded by the selected exact-generation design. Existing auth.ts owns its bounded transaction callback; no new auth module or app-owned grant table.

  1. apps/web/lib/auth.ts: preserve S, all client mutation ceilings, explicit request/continuation normalization and gated AS metadata. Implement §20.1's new PATCH-DEFINED consentGeneration.run callback with real db.transaction, actor GUCs, NOWAIT root/consent locks, tuple advisory, SQL consent changes and a transaction-bound drizzleAdapter passed explicitly to the token-write continuation. Do not enable transactions globally or use custom response fields as storage. Never expose bindings in public query/body/response. Keep existing database adapter and non-ledger admission rules; all consent mutations pass the locked invalidation check so an ordinary-scope request cannot leave a ledger generation alive after narrowing.
  2. apps/web/lib/session-resolver.ts: preserve ordinary resolver/resolveActiveMembership fallback and non-refreshing render behavior. Keep authoritative session/member provenance and activated S checks; omitted impersonation field is not direct proof. Ledger-only admission returns select-organization-required if authoritative activeOrganizationId is stale, differs from requested/resolved org, or is null/absent even with one current membership. Expose only direct user's eligible organization choices to recovery, no ledger data. F3 uses existing organization.setActive; C/A5 independently enforce exact active org after selection and after awaits.
  3. apps/web/lib/chat/router.ts: pass trusted session/member reference into runChat, never public JSON. Recheck new starts/resumes; S's resolver protects newly admitted history/list/stream requests, NOT already-open SSE. §14's fleet retirement barrier closes preactivation realtime/resumable streams and runs before ledger activation; no general transport rewrite. Map updateThreadMatter discriminated result to 422 not_shareable-class like grantThreadShare for a non-null target on a marked thread; updateThreadMatter(..., null) is allowed (unfile). ChatRow !res.ok uses one reason-keyed string for marked-thread 422 (I22). Keep D20.
  4. apps/clicky-gateway/src/session-resolver.ts: bearer-native parity, reading authoritative session impersonation state despite the gateway's bearer-only Better Auth model. Under S refuse legacy impersonated sessions before dispatch/list/job paths.
  5. packages/chat-runtime/src/run.ts: pass optional server-resolved authority as context.work_ledger_actor on each LangGraph invocation. D8/C read only fresh context; absent context hides/refuses ledger, including resume. Never use configurable.ledger_block or ledger_coverage (existing coverage content), graph defaults/state/metadata/tool arguments or checkpoint-restored IDs as authority. Existing non-ledger configurable callers stay unchanged. §15.4/G9 prove actual persistence/resume behavior; no serializer rewrite.
  6. apps/mcp-server/src/auth.ts: extend principal with optional server-resolved OAuth access-row reference for ledger authority. Missing/dev-only/unverified provenance fails ledger admission; preserve other principal consumers and the preexisting non-OAuth legacy read-only contract. Real OAuth resolution always supplies explicit effective scopes; never infer read permission from an explicit empty or ledger-only array.
  7. apps/mcp-server/src/oauth-auth.ts: retain ordinary token ∩ live-consent scope behavior; add ledger only when the stored access.consentId/consentGeneration exactly matches the live ledger consent's ID/generation AND client/user/org tuple, with exactly one qualifying consent. Null/mismatch never ledger; do not substitute another tuple match or infer provenance from refresh/session/client metadata. Return server access-row reference, no public binding or browser-liveness requirement. Ledger-only/read intersection, catalogue/chassis and ordinary read/write guards remain.
  8. apps/web/app/(settings)/connections/actions.ts: actual Outlook connect/reconnect callers at :469–470/:499–513 pass direct session/current member context BEFORE reopen or provider work. Own the personal Clio Connect/Reconnect action called by F3 WorkLedgerReview, with the same direct-session rule and validated selected review/window/timezone/matter return target through B4/B5 signed state. Success/cancel/failure returns to that review; matter access is rechecked. Organization Settings enrollment remains distinct; no org connector substitution or Settings UI file added.
  9. apps/web/app/oauth/consent/page.tsx: explicit private-ledger scope description and direct reauthorization guidance. Do not silently broaden ordinary read/write consent or promise unsupported old-client ledger authorization.
  10. apps/web/lib/auth-oauth.test.ts: real pinned provider and isolated SQL for §20.1 consent acceptance/continuation, code exchange, rotation, narrowing/delete/regrant and transaction failures; no mock DB adapter proof. Existing client-surface/explicit-scope/default/metadata/S/request_uri tests remain. Prove code burns on failed exchange, refresh rollback leaves the original usable, and no external token response precedes commit. Ordinary OAuth and supported-session-deletion controls stay.
  11. apps/mcp-server/test/oauth-auth.integration.test.ts: real opaque access resolution for exact/missing/stale binding, same-ID regrant/new-ID recreation, descendant rotation and no tuple relinking; retain ordinary read/read-write, ledger-only, current membership/client/org and browser-session-deletion controls. Run fresh-install pinned patch provider fixture with I auth-oauth tests; G8/G23 own transport projection/catalogue behavior.
  12. apps/web/lib/api-auth.test.ts: web cookie/bearer direct controls, omitted-versus-authoritative impersonation state and current member reference. Preserve existing support behavior before first activation; activation refuses old impersonated sessions and rollback stays denied. Do not just re-pin object fields.
  13. apps/clicky-gateway/src/auth.test.ts: native parity for before-first-activation support behavior, old-impersonation refusal at activation and continued denial after rollback, plus direct-session positive control through the real resolver, including the bearer-only plugin's missing projection field.
  14. apps/web/app/(settings)/connections/actions.test.ts: genuine connect/reconnect blocked before reopen/token exchange when impersonated or membership changed; direct enrollment reaches strict completion. Update existing affected caller fixtures.
  15. apps/web/lib/chat/router.test.ts: direct start/resume passes authority to real ledger admission; missing/impersonated/stale origins cannot invoke it. Retain ordinary chat behavior; G9 covers full transport/history and permanent sharing refusal. Prove 422 not_shareable-class from discriminated updateThreadMatter for a non-null target on a marked thread; null unfile succeeds. ChatRow !res.ok uses one reason-keyed string for marked-thread 422 (I22).
  16. apps/web/components/app-shell/chat-row.tsx existing I22: one reason-keyed string in the !res.ok branch for marked-thread 422. Do not keep a generic move failure for that status. Unfiling an admitted thread is permanent and re-filing is refused.

O1 runtime proof stays in I's existing auth-oauth.test.ts and oauth-auth.integration.test.ts, using the real pinned provider, not captured options/mock echoes: an upgraded ledger-capable client omits scope with and without a session, including original-request login/consent continuation, prior consent and skip-consent paths; issued code/access/refresh remain non-ledger. Explicit ledger request plus direct authorization succeeds; consent cannot add ledger to the normalized non-ledger request. Reentered ctx.query, not the login request URL, is authoritative. Verify original non-ledger defaults, trusted-cache refresh, old-grant refresh ceilings and direct-session checks remain intact.

Historical adopted arithmetic only: original 58 + original I fifteen =73; authority expansion was +2,200 lines to 8,700. This records the map adopted with S+A, not today's executable cap. All current packet and slice allocations live in §15.5; later review deltas are not measured code or a claim of exact implementation fit.

Remaining gates: implementation of the selected §20.1 mechanism, fresh independent/RLS review and actual authority/memory/provider/activation/J/transport proofs; deployment and NOS-384 matters-only handoffs; synthetic manifest/effective grants; later schema release after #432. OAuth design is no longer an unresolved/user-owned choice. No product/git/schema/DB/account call or agent launch by this docs seat.

14. Fresh merged-base Astra — three dispositions

Fresh merged-base reads cover the named source paths and installed patched oauth-provider 1.6.23. They supersede only corresponding stale source citations. Opus has since returned eight findings in §15; no clean pair or executed concurrency/activation/grant proof.

FindingDisposition, exact source closure and proof
M1 — revoke after final authority check.ACCEPT. schema/auth.ts:38–55 and schema/oauth.ts:12–127 expose mutable session/client/access/consent authority and cascade/SET NULL edges. oauth-auth.ts:43–129 reads token/client/user/member/org/consent but takes no locks. A5 provision-roles.ts:512–569 is the existing privileged-function section, not an existing ledger routine. Spec §3.4 now locks the full provenance graph through commit; SHARE, not KEY SHARE, conflicts with non-key disabled/scope/expiry updates. G5/G6 prove both revoke/ledger orders and cascade contention.
M2 — already-open impersonated transports/runs.ACCEPT. web chat/router.ts:177–232 captures user/org once for realtime SSE; :479–491 checks once before resumable SSE. gateway ws.ts:68–95 caches authorization for 30 seconds and returns true on revalidation error; onOpen :127–162 captures the session. closeSockets :177–189 and gateway main.ts:117–134 provide shutdown closure. No TTL or new-resolver deployment proves old streams retired. Choose fleet ingress quiescence/drain/process replacement before ledger admission; G9 exercises pre-opened real network connections and an in-flight run, not only a new request.
M3 — registered-client scope ceiling blocks consent.ACCEPT. register-claude-oauth-client.ts:8–35 fixes old scopes, :155–169 refuses drift, :174–192 inserts that same ceiling. auth.ts:429–458 configures allowed/default scopes and cachedTrustedClients. Pinned provider index.mjs:3873–3883 rejects outside client.scopes before consent; :1543–1589 rejects trusted updates and requires ownership for ordinary clients. utils-B3Myyglw.mjs:145–156 caches trusted rows before DB lookup. Reconsent alone is inadequate. Add one explicit opt-in script path, all-issuer cache replacement and subsequent direct consent; test the actual script/provider in existing I integration coverage.

14.1 Authority ordering audit and conflict behavior

Order: user SHARE → organization SHARE → member KEY SHARE → sorted referenced sessions SHARE → client SHARE → referenced refresh SHARE → exact access SHARE → exact authorizing consent SHARE, all NOWAIT, then existing advisory/sorted children. Exact access.consentId/consentGeneration must equal the current consent's ID/generation and tuple (§20.1); null/multiple/mismatched provenance refuses without substitution. Web requires live direct session; MCP session/refresh ancestors coordinate FKs, not browser liveness. Changed discovery aborts. Issuer modes omit unissued rows and take refresh UPDATE for rotation or consent UPDATE for mutation from the outset, never upgrade SHARE locks.

Fresh FK audit: user deletion cascades sessions/client/grants; client deletion cascades refresh/access/consent; refresh deletion cascades access; session deletion SET NULLs both refresh/access. Pinned Better Auth internal-adapter.mjs:354–417 deletes sessions; provider index.mjs:2169–2214 deletes/updates consent, :2314–2326 deletes access, :2352–2377 updates refresh then deletes access. These writers do not acquire ledger children. Their native cascade child order is not assumed equal to ours: NOWAIT on every authority acquisition prevents holding one authority row while waiting cyclically for another. Roll back the whole local transaction on 55P03/discovery mismatch; never catch and proceed with an incomplete lock set. SHARE locks remain compatible with FK KEY SHARE checks but block DELETE/NO KEY UPDATE. Normal contention can cause explicit retry-required; this availability tradeoff avoids a global revocation/cascade rewrite. Preserve existing resource-server semantics: active access/consent/client are authority; refresh ancestors are coordination, not a new revocation policy. Tests cover full family revocation completion as well as intermediate provider steps. No instantaneous cancellation at revoke invocation or already-delivered-byte erasure claim.

14.2 Fleet activation barrier, not a transport framework

Deployment choreography in existing H docs and C/D/I gates: (1) keep ledger OFF and roll out direct-origin/issuer/D7/D9 guards everywhere; (2) enter activation maintenance, prohibit new impersonation and reject old-session entry on every issuer/web/native instance; (3) quiesce ingress and drain or terminate all relevant open connections, runs and continuations, replacing every process that could retain preactivation authority; (4) verify old processes/sockets gone, paused/queued/resumable work settled or fenced from restart, guarded replacements healthy and direct-user reconnection works; (5) only then enable ledger. If selective retirement cannot be demonstrated, drain all such process-local work; a bounded drain timeout stops activation, not the privacy gate. Include out-of-process run owners, ingress streams and pending reconnects; restarting only web is insufficient. Existing gateway shutdown closes sockets; use process exit/replacement proof where framework shutdown does not settle work. No new WS/SSE revocation bus, transport registry service or global framework/file. The authorized operator owns this potentially disruptive procedure; it is not run here.

§15 maps executable compatibility checks in existing deployment code plus proxy/mint entry denial. The orchestrator rejected artifact attestation, independent root-owned installation, hostile-code/installer defenses and vendor-credential erasure. Keep preactivation application session/stream/run retirement, including in-flight proxy streams; previously minted vendor tokens do not automatically grant ledger/history access and need no revocation proof here.

After any admission, rollback stays on guarded binaries and never reopens impersonation or preactivation contexts. G9 uses two real test processes with pre-opened impersonated realtime/resumable SSE and gateway WS, including one within its cached-authorization window and one paused run. Deliberately leave one old process/context alive and prove ledger stays OFF; retire it, activate, produce private human-text activity/results and prove old transports closed/no resumed delivery. Direct clients reconnect successfully; existing non-ledger MCP still works. This is future isolated runtime proof, not a mock check or production experiment.

14.3 Necessary extra existing path — opt-in registration upgrade

  1. scripts/register-claude-oauth-client.ts: add explicit --enable-private-work-ledger operator opt-in, leaving no-flag creation at existing scopes. For an existing uniquely marked client, transactionally lock/re-read that exact row FOR UPDATE, validate all immutable/config fields and permit only the exact old ceiling → old ceiling plus mcp:private-work-ledger transition. Preserve ID, redirect URIs, PKCE, consent/public/security settings and every unrelated field; reject extra scopes, duplicate markers, disabled/config drift or mismatched original state without writes. No wildcard repair or bulk client mutation. Repeated opt-in on the exact upgraded configuration is a no-op; no-flag rerun recognizes either sanctioned ceiling without downgrading or re-adding scope, while other drift still fails. Missing-row opt-in creation uses the explicit expanded ceiling. Unknown arguments fail. Keep explicit DATABASE_URL-before-import behavior and redact output. This is an additional existing implementation path, not edited/run by this seat.

Script creation and upgrade share one transaction-scoped advisory key for the fixed marker, then reselect and lock the exact existing row. This serializes concurrent no-flag/opt-in invocations, including missing-row creation, without assuming the JSON marker is uniquely constrained. Unknown or duplicate marker state still refuses; no attempt to repair unrelated registrations.

Operator ordering: all authorization servers first enforce direct ledger-scope issuance and explicit request (omitted scope must not implicitly grant ledger after ceiling expansion; keep existing non-ledger defaults). Then an independently authorized operator opts in the selected client, restarts/replaces EVERY trusted-cache holder while ledger remains OFF and observes fresh client metadata, then the user gives explicit direct ledger consent. auth.ts already maps scopes/clientRegistrationAllowedScopes; add the scope there without adding it to clientRegistrationDefaultScopes. Trusted clients cannot use the pinned update endpoint, so the script is the boring bounded path. Non-trusted owned clients may use that validated provider update path; unowned anonymous clients use supported fresh DCR with explicitly requested ceiling and the host's new client ID. Never mutate all registrations, weaken provider ownership or delete old clients/grants. Old access tokens and refresh families keep their original scopes and non-ledger behavior; only newly directly authorized grants get ledger. No automatic production/client update, token migration or provider fork.

Existing apps/mcp-server/test/oauth-auth.integration.test.ts (I) runs the real script against its isolated synthetic DB and real pinned-provider fixture: old registered ceiling gives invalid_scope; explicit upgrade plus fresh issuer succeeds; populated trusted cache stays stale until replacement; new direct consent grants ledger; old access/refresh remain ledger-denied but non-ledger-valid. Prove script opt-in/no-flag idempotence, unrelated drift refusal with no writes, and omitted-scope requests cannot silently gain ledger. Existing I auth-oauth tests cover all-issuer guard/default-scope behavior. No new test file; no source-text or mock forwarding assertions.

14.4 Exact necessary delta; no hidden overrun

Astra-only intermediate delta, retained for arithmetic history: 73 / 8,700 + one registration script +500 prospective lines = 74 / 9,200. A +100, C +50, G +150, I +200; intermediate A 950, B 1,800, C 1,450, D 800, F 800, G 1,550, H 150, I 1,700 (16 files). No honest removal was found: auth-startup mutation would be unsafe and deleting caller/tests loses required behavior. §15.5 now supersedes this intermediate total with the complete Opus closure. Neither delta is measured implementation or an approved cap increase; policy S+A is not reopened.

15. Independent merged-base Opus — eight individual dispositions

Source refresh is narrow and tied to the orchestrator-reported c767 merged base. No live deployment, account call, database mutation, product edit or synthetic runtime test was performed by this documentation seat. The mapped proof below is required future work, not evidence already obtained.

FindingDisposition and concrete closure
OpusM 1 — retained-build rollback bypasses privacy floor.ACCEPT the narrow gap; REJECT the subsequent supply-chain expansion. Fresh call-graph reads show rollback.sh:156/:233/:237 uses shared reconcile/start/swap; skip-swap restarts follow reconciliation. deploy.sh:784–790 enters bluegreen_main; recovery and route/current repair live in bluegreen.sh. J1/J2/J3 below add one shared version check, a pre-checkout hook call and one existing shell fixture. No rollback.sh/deploy.sh/install.sh edit, new helper, digest attestation or hostile-operator defense. Deployment orchestrator owns code handoff and activation.
OpusM 2 — clicky-proxy and raw mint escape S.ACCEPT. proxy auth.ts:16–41 independently authenticates bearer sessions; its bearer-only Better Auth projection cannot prove impersonated_by is null. Proxy main.ts:149–150 protects /scribe-token and /tts with that middleware. clicky-mint.ts:56–64 returns raw session.token, and loopback-handler.ts:34–39 exports token/cookie only after mint succeeds. Add the three I paths below; keep those callers unchanged by enforcing at the shared helper. G9 covers actual proxy routes, streaming and retirement; existing mint tests cover raw HTTP/loopback release refusal. Health/metrics and ordinary direct users retain their contracts.
OpusM 3 — 200 validations at concurrency four cannot finish in 20 seconds.REJECT the absolute production-latency claim: no measured provider latency establishes it. RETAIN conditional completeness and operational partial results even below 200. Fast 199/200/narrowed controls prove a possible successful path, not a production SLA. Narrowing MAY enable a figure, never guarantees it. An injected-latency G5/G6 control is justified only to catch scheduling/late-disclosure after the shared deadline; no benchmark claim, larger budget, always-null workaround or cached authorization.
OpusM 4 — configuration restamps regional Clio identity.ACCEPT. auth.ts:116–123 refreshes using cfg; :189–193 spreads metadata then overwrites api_host from cfg/env/default. ClioClient :50–53/:287–305 already normalizes and validates four regional HTTPS origins. B3 must preserve the producing connector's verified stored regional origin and use it for refresh/identity/exact reads; absent origin is unverified. G5 changes cfg/env after enrollment and proves stable origin/account/anchor, same-source dedup and no token sent to a newly configured region. Fix the mapped adapter, not the worker lock or global client.
OpusM 5 — DCR least privilege.CLARIFY existing choice: allow mcp:private-work-ledger in clientRegistrationAllowedScopes for explicit public-client requests, NEVER clientRegistrationDefaultScopes or omitted authorization scopes. New client ceiling is not user consent. I auth-oauth/integration tests drive real no-scope registration/authorization versus explicit DCR plus direct consent; old defaults/read/write and old access/refresh ceilings stay unchanged. Retain Astra's explicit trusted-client script upgrade, issuer-first ordering and full trusted-cache replacement.
OpusM 6 — prepared-snapshot map points at B8.ACCEPT correction: B6/B7/B10/B20/B21 are the preparation/scan chain; B8 is the credential HTTP writer and remains only enrollment/legacy-write closure. Spec §3.3 corrected. No new file or deadline semantics; C prepares ALL sources before one absolute 20-second phase.
OpusM 7 — MatterTable guards need current-base verification.VERIFIED on c767: header :138/cell :168 use showActions; RowActions :243 returns null without onAction. All three need independent personal Review. NOS-384 owns matters-page/table integration until handoff, NOT tasks/chats/new. The prior F4 hold was mistaken and is removed; Home/query wiring remains this lane's own surface. No competing matters edit.
OpusM 8 — session/grant references necessarily enter checkpoints.PARTIAL: persistence was plausible, not established. §15.4 traces installed LangGraph 1.3.2 and PostgresSaver 1.0.1: config is passed to the saver, but put serializes checkpoint/channel data, metadata and writes, not the full config. No unconditional arbitrary-configurable persistence claim survives. Choose supported request-local context, never restore authority from state/metadata/saved config. G9 must inspect actual persisted records and prove no-fresh-context resume denial; no serializer rewrite or new schema.

15.1 J. Three existing files for the supported deployment floor

  1. deploy/preprod/lib/bluegreen.sh: J1. Pin production minimum state to /home/kwiss/deploy/north-os-preprod-releases/private-work-ledger-compatibility.json, independently of environment roots. Store durable minimumVersion, NOT a per-SHA release approval map. Embed one static declaration, e.g. # private-work-ledger-compatibility: 1, in this existing library. Current checker reads candidate source as DATA via git show target:deploy/preprod/lib/bluegreen.sh before checkout; retained releases use that same static field from their library file. Parse exactly one canonical bounded nonnegative integer, no source/eval/command substitution of candidate text. Missing/malformed/duplicate declaration fails under an active floor. Version 1 promises D7/D9, permanent memory exclusion, activated S/direct issuance and J1/J2/floor preservation. Reuse all existing start/swap/reconcile/publication checks and production/test isolation; no new helper or manifest file. Keep bg_init_config a side-effect-free assignment as today. At bg_stage_release (bluegreen.sh:602), the marker≠DEPLOY_SHA refuse is AFTER the green-lit reuse early-return (:604–609) and immediately after the existing [ -e "$BG_RELEASE_DIR" ] refuse (:610–612), before set_phase :615 (and thus before rsync of DEPLOY_DIR :637–646); BG_RELEASE_DIR="${RELEASES_ROOT}/${DEPLOY_SHA}" at :53. Reuse of an existing green-lit release with a stale checkout marker still succeeds. (Superseded by §46 Opus 2: delete the reuse discriminator; no BG_RELEASE_REUSED. bg_refresh_runtime_scripts copies helpers from $BG_RELEASE_DIR unconditionally; both reuse and promote leave $BG_RELEASE_DIR green-lit. bg_refresh_post_receive_hook marker skip stays load-bearing, untouched.) J1 edit at bluegreen.sh:727–738: source="${BG_RELEASE_DIR}/deploy/preprod/bin/${name}.sh". Price inside J1's 190. Do not phrase $BG_RELEASE_DIR as current fact. Introduce no new discriminator (no BG_RELEASE_REUSED); leave existing BG_RELEASE_PROMOTED at :55/:607/:723 untouched. The hook refresh runs at :1511, before staging at :1515, so it cannot read $BG_RELEASE_DIR; skip is the only safe form. Hook skip (return 0, no copy) when the marker ≠ DEPLOY_SHA, or when the marker is absent and the active floor minimumVersion ≥ 1. Do not abort. bg_stage_release owns the only refusal. Stay in the three J files. Do not edit deploy.sh or rollback.sh.
  2. deploy/preprod/post-receive: J2. Add leaked-test-mode refusal before side effects and unconditional current-controller admission after flock, before mode branch/candidate source/materialization. Replace :148 mutable-main checkout with GIT_WORK_TREE="$DEPLOY_DIR" git read-tree --reset -u --no-sparse-checkout "$target_sha", using the SAME immutable commit admitted and read via git show. It updates index/worktree without detaching HEAD or changing refs; no reset/update-ref/history repair. After every successful read-tree, write a non-secret materialized-SHA marker beside .deploy-mode containing that admitted target_sha; preserve untracked runtime files. J2 writes that marker during the deployment owner's initial handoff install (same barrier as the durable minimum). J2 is the only marker writer. Retain router/static-devproxy handling and the post-materialization ref-change refusal, but remove the false promise that the later hook will deploy: it may have failed flock after its ref update. Abort with existing failed status and explicit authorized-retry-required; do not deploy/revalidate latest main, enqueue retries or rewind it. §23 owns exact source/proof; §26 owns the relocated stage-release refuse and operator recovery. Unchanged deploy.sh guards stay in place, unedited.
  3. deploy/preprod/bluegreen-sim.test.sh: J3. Preserve all minimum/fallback, rollback/recovery/skip-swap/route repair and mode/declaration cases. Add leaked-test-mode cases, plus routine compatible push/installed-hook continuity. Add §23's deterministic real-bare-Git fixture: pause A after admission, advance main to incompatible B whose hook fails flock, resume A, assert no B controller/source/route/service bytes or effects and unchanged bare symbolic HEAD/ref semantics. J3 then proves: (a) after that superseded abort, a supported direct deploy/preprod/deploy.sh "$DEPLOY_DIR" must refuse and must never stage RELEASES_ROOT/<B> from A's tree or stamp .deploy-release=B or swap; J3(a) asserts no phase change and no $BG_STAGE_DIR on refusal; hook/runtime-script bytes unchanged; bg_reconcile still runs; (b) with the marker present and stale, an ordinary later compatible push still read-tree, rewrites the marker and deploys; (c) retained rollback/recovery/skip-swap still work and do not consult the checkout marker; (d) reuse of an existing green-lit release with a stale marker still succeeds; (e) run_deploy writes the marker for the SHA it materializes, simulating J2 (fixture-only; J2 stays the only production writer); (a)/(b)/(d)/(g) materialize a different SHA first; (e)'s absence arm removes or omits the seed; with active floor minimumVersion ≥ 1 the marker is REQUIRED — absence → bg_stage_release refuse and refresh skip; with no active floor, absence is dormant; (f) when staging refuses after superseded abort, installed hook bytes are unchanged; (g) on stale-marker green-lit reuse where DEPLOY_DIR and $BG_RELEASE_DIR deliberately differ, asserts helper bytes; reuse still succeeds for staging and the hook is not rewritten from the unchecked tree. Existing fake Git's checkout-main/canned-SHA behavior cannot prove this race; update affected stubs without treating command echoes as proof. No new test file, attestation suite or live deployment. Current J allocation is 190/60/360 =3/610, not measured fit. J3(g) asserts helper bytes on a stale-marker green-lit reuse where DEPLOY_DIR and $BG_RELEASE_DIR deliberately differ. (h) if main advances between post-receive's ref check and deploy.sh's SHA derivation: refuse, no staging; a later push deploys.

J3 adds the leaked-test-mode case: active minimum1 plus alternate empty root and leaked BLUEGREEN_TEST_MODE refuses before side effects; valid marked fixture is the control. Retain copied-script isolation and existing fake external services, but §23's exact-tree/ref race and §26's (a)(b)(c) marker proofs use real Git in a throwaway fixture. J's 170/40/240 then 190/60/280 then 190/60/320 =3/570 allocations are historical; current J is 190/60/360 =3/610. No new path or deployment framework. Do not edit deploy.sh/rollback.sh.

J3 deletes only the compatibility json after a declaring controller is live and still refuses; it does not silently return to dormant-permissive. J1 judges missing json from the live controller's static declaration, not from the absent file. H names the restore obligation. No new test file.

State and activation: missing compatibility json is unimplementable as post-vs-pre by the json itself. If the live controller's static declaration is ≥ 1, missing json refuses; if the live controller has no declaration or declaration 0, missing json remains dormant. Malformed state refuses. H names the operator restore obligation. Under the existing lock, the deployment owner verifies active/fallback builds' static declarations and privacy behavior, installs the floor-aware controller/hook, and durably sets minimum 1 before admission. Ordinary subsequent releases carry their static declaration in code and are checked automatically; no operator per-SHA approval or state edit on each push. Minimum state survives status rewrites/GC/rollback and is restored with activated S configuration when a declaring controller is live. Stay in three J files; no second file.

Fresh entry graph: post-receive:31–36 flock → unconditional current-controller admission → existing :107 mode/:113 exact-target git show/:128 admitted library source → :110 sets DEPLOY_SHA=target then :137 bg_init_config (side-effect-free assignment) BEFORE materialization → replace :148 with exact-target read-tree → write non-secret materialized-SHA marker beside .deploy-mode → existing static map handling and ref-change refusal → :175 deploy.sh only if not superseded. deploy.sh:71–76 already uses DEPLOY_TARGET_SHA for the changelog target; :690–696 derives DEPLOY_SHA from git --git-dir "$BARE_GIT_DIR" rev-parse main, not DEPLOY_TARGET_SHA, so leave that script unedited. J1 bg_stage_release refuses when the marker exists and differs from DEPLOY_SHA AFTER the green-lit reuse early-return (:604–609) and immediately after the existing [ -e "$BG_RELEASE_DIR" ] refuse (:610–612), before set_phase :615; reuse of an existing green-lit release with a stale marker still succeeds. (Superseded by §46 Opus 2: delete the reuse discriminator; no BG_RELEASE_REUSED. bg_refresh_runtime_scripts copies helpers from $BG_RELEASE_DIR unconditionally; both reuse and promote leave $BG_RELEASE_DIR green-lit. bg_refresh_post_receive_hook marker skip stays load-bearing, untouched.) Hook skip (return 0, no copy) when the marker ≠ DEPLOY_SHA, or when the marker is absent and the active floor minimumVersion ≥ 1; do not abort; bg_stage_release owns the only refusal. A refused staging after superseded abort and a stale-marker green-lit reuse leave installed hook/runtime-script bytes unchanged (reuse still succeeds for staging; the hook is not rewritten from the unchecked tree); bg_reconcile still runs. A supported direct deploy.sh "$DEPLOY_DIR" after superseded abort would otherwise stage RELEASES_ROOT/B from A's tree. rollback.sh:54–58 takes DEPLOY_SHA from the prior record/argument, :126–127 calls bg_init_config, and resets the target only at :192; rollbacks reuse RELEASES_ROOT/<sha> and must not consult the checkout marker. No deploy.sh/rollback.sh edits.

Shared mutation closure: J1 checks the candidate before bg_start_color repoints/starts it; bg_swap checks both destination and possible automatic-return release before writing its journal. At bg_reconcile entry, after parsing any existing journal but BEFORE recovery effects, check the route/current and journal destinations that recovery can serve or restore as current. This covers bg_reconcile_transaction's finish-new/finish-old paths (:1025–1055), including the already-routed success branch that skips a switch, and bg_finish_transaction_old's oldCurrent restoration (:1004–1023). Without a journal, bg_repair_from_route_state (:1057–1075) must check the traffic-derived release before repointing current. Reuse the checker at bg_switch_color and bg_publish_route_color before route publication; the latter also closes explicit bg_repair_route_state (:338–372). All checks propagate failure without routing/restarting the incompatible release; no alternate unchecked fallback. Unused previous-active/retained releases are checked when selected, not deleted or globally banned.

Supported scope and ownership: normal push, direct deploy through the installed current controller, retained-release rollback through its driver and shared reconciliation/route repair under lock. Activation requires compatible current/fallback, blue/green/static-router mode and completed deployment-owner handoff. Candidate static declarations are trusted code-maintainer statements; the operator owns the durable minimum/restore procedure, not routine per-SHA approval. Reading source as data is not attestation or proof of hostile-code safety. Arbitrary legacy installers/binaries, privileged manual mode/state changes and DB bypass remain outside scope. No independent installed authority, digest manifest, daemon, multi-protocol downgrade framework or vendor erasure.

Why three files, not seven or two: bluegreen.sh owns the shared routing/recovery/current-pointer decisions; no separate rollback/deploy edit is needed. post-receive is necessary because its normal checkout replaces the controller before deploy.sh runs, so checking only the candidate's newer code misses ordinary older targets. The existing shell fixture already copies all these real scripts (:716–719), has run_reconcile (:819–829), run_deploy (:899–909), warm/cold rollback (:2022–2042) and interrupted rollback recovery. No new test file or installer coverage. H documents the trusted-operator activation/restore procedure, but does not substitute for J's executable checks.

Future proof, not run by this seat: printf '%s\n' deploy/preprod/lib/bluegreen.sh deploy/preprod/post-receive deploy/preprod/bluegreen-sim.test.sh | xargs -n 1 bash -n, then bash deploy/preprod/bluegreen-sim.test.sh. Use its isolated temporary roots/fake service commands/local fixture listeners only, never standalone live deploy/rollback as a test. G9 uses §7's isolated DB/Redis gate; apps/web includes bunx vitest run lib/clicky-mint.test.ts lib/auth-oauth.test.ts lib/api-auth.test.ts with the same isolated environment. Installation/activation/execution and deployment code handoff remain with the deployment orchestrator.

15.2 I. Three additional existing auth/test paths

  1. apps/clicky-proxy/src/auth.ts: requireSession keeps bearer-only/cookie-stripping semantics and rereads authoritative session identity/expiry/impersonated_by under S before next(), even though the local bearer-only plugin omits that field. Use the same activated support policy as other I gates; an omitted field is not direct proof. Deny old impersonated bearer sessions before /scribe-token or /tts performs vendor work. Preserve direct-user and non-org-scoped behavior; no organization requirement, Better Auth plugin expansion or vendor-credential revocation.
  2. apps/web/lib/clicky-mint.ts: gate the resolved exact raw session through I's authoritative direct-session policy before returning its bearer or permitting loopback cookie handoff. Share the narrow web session-policy check in already mapped session-resolver.ts without adding a firm-membership requirement to mint. Keep cookie modality, raw-token shape and ordinary direct login intact. Both API mint and resolveLoopback already call mintBearerToken before releasing credentials; those route/page callers stay unchanged.
  3. apps/web/lib/clicky-mint.test.ts existing: retain real bearer/signout positives; prove dormant support compatibility, then activated/rolled-back S denies raw bearer and loopback cookie delivery for an old impersonated session with no vendor request. Cover the real mint handler/shared loopback helper, not only a mocked null session. Direct session still completes both flows. G9 separately exercises real proxy HTTP admission with SQL-authoritative state and missing plugin projection; no source-text allowlist assertion as behavioral proof.

In-flight support stream retirement, not vendor credential erasure: stop new impersonation and reject existing impersonated sessions at proxy/mint/web/native entrypoints; drain or terminate preactivation application streams/runs before ledger activation. Proxy main.ts:105–115 clears its timer after headers and :219 returns the TTS body, so do not mistake that timer for completed stream retirement. G9 retains a held-open proxy stream alongside SSE/WS/run controls and proves its old application context is retired; direct users reconnect. Previously issued vendor tokens/direct vendor connections do not automatically grant ledger/history data. Their revocation, expiration and erasure are NOT ledger prerequisites or tests. No vendor lifetime manifest, revocation API or credential-erasure guarantee; unrelated vendor capability persistence does not keep ledger OFF.

15.3 Regional identity and conditional completeness

B3 uses ClioClient's existing normalized HTTPS regional origin, but derives the ledger identity from the producing connector's verified stored metadata, never cfg/env/default at read time. For an established origin, refresh and identity/exact transport stay on it; mismatched injected client/config cannot move the grant or source key. Fresh enrollment binds the actual producing client origin and verified current-user/account pair. Legacy missing/invalid/unverifiable metadata remains ledger-unverified; ordinary missing-origin refresh may use its preexisting fallback without claiming/stamping verified provenance. A deliberate regional change needs fresh verified enrollment/epoch, not refresh relabelling. G5 exercises equal account IDs in different regions, config/env change across refresh, missing-origin denial and unchanged same-source anchor/dedup. This is an auth.ts correction, not an extra worker/client file.

Keep ≤ 200 exact validations, ≤ 4 concurrency and one post-preparation 20-second phase; no observed production latency supports an absolute success or impossibility claim. Narrowing MAY make a complete figure possible; it does not guarantee source accessibility, observation coverage or time to finish. G6/G10's fast fixture is a positive control only. C/F guidance says “Narrow dates or apply a matter filter; a range figure is available only if source checks complete.” Operational failure remains a specific partial reason, not too-many-entries. Optional injected latency exercises deadline scheduling/abort/late-result refusal, not a new benchmark, provider budget or permanent test file. Prepared snapshots traverse B6/B7/B10/B20/B21; B8 remains credential enrollment closure.

15.4 Actual checkpoint trace and request-local authority

Current run.ts:2092–2144 passes graph.stream configurable fields; checkpointer.ts uses the default PostgresSaver rather than a custom serializer. Installed LangGraph 1.3.2 pregel/utils/config.js:10–50 recognizes top-level context and propagates only a seven-key configurable allowlist to metadata; an arbitrary actor reference is not on that list. pregel/index.js:994–1005 preserves/validates context. loop.js:163–184 merges saved checkpoint config; :558–587 passes config, checkpoint and independently constructed metadata to the saver. This means “config was passed to put” is not proof it was serialized.

Installed PostgresSaver 1.0.1 index.js:318–347 extracts thread/namespace/checkpoint IDs and persists checkpoint JSON, channel blobs and supplied metadata; :155–172/:357–370 serialize metadata and pending write values. getTuple :238–252 reconstructs config with only thread_id/checkpoint_ns/checkpoint_id. The inspected normal path does not serialize the whole config/context. Do not generalize that to every LangChain callback, tool error, application channel or debug event; an identifier copied into state/writes/metadata would persist. Neither absence nor persistence of an opaque ID establishes authority.

Decision: use only fresh optional context.work_ledger_actor for I run.ts → D8/C authority. Never configurable.ledger_block/ledger_coverage, which already carry coverage data at run.ts:2114/:2121. This naming clarification is NOT a new security finding; retain the existing nonpersistence decision and authority rechecks. No graph defaults/state/metadata/writes/interrupt/model/event copies and no resume fallback. G9 inspects real PostgresSaver records/events and resumes with absent versus fresh context, including plausible stale checkpoint IDs; no serializer rewrite or mocked saver proof.

15.5 Sole authoritative current map and prospective packet/slice caps

Complete mapped files: 118 unique paths (117 authored plus 1 generated Bun lock). A12; B21; C3; D21; F8; G24; H4; I22 (22 files); J3. Named indexes land in existing A1/A2/A3, not new paths. §26 staging-refuse stays inside the three J files, after green-lit reuse. Introduce no new discriminator (no BG_RELEASE_REUSED); leave existing BG_RELEASE_PROMOTED at :55/:607/:723 untouched. J1 edit at bluegreen.sh:727–738: source="${BG_RELEASE_DIR}/deploy/preprod/bin/${name}.sh" (do not phrase $BG_RELEASE_DIR as current fact; price inside J1's 190). The hook refresh runs at :1511, before staging at :1515, so it cannot read $BG_RELEASE_DIR; skip is the only safe form. bg_refresh_post_receive_hook marker skip stays load-bearing, untouched. J3(g) asserts helper bytes on a stale-marker green-lit reuse where DEPLOY_DIR and $BG_RELEASE_DIR deliberately differ. Hook skip (return 0, no copy) when the marker ≠ DEPLOY_SHA, or when the marker is absent and the active floor minimumVersion ≥ 1; do not abort; bg_stage_release owns the only refusal. With active floor minimumVersion ≥ 1, marker REQUIRED on the staging path: absence → bg_stage_release refuse, refresh skip; green-lit reuse unaffected; J3 absent-marker green-lit-reuse succeeds. No active floor → absence dormant. J3 fixture seeds the marker per run_deploy SHA (fixture-only; J2 stays the only production writer). Add the existing A generation unit's one SQL migration, snapshot and meta/_journal.json plus documentation STATUS.md/index.html: 123 expected repository output paths. SQL/snapshot basenames await the later #432 slot; no fabricated names, extra schema unit, serve.ts expansion or new runtime module.

Complete prospective estimate: historical §29 baseline 106/17,440 is not current. Current arithmetic remains the §40 sum 19,620 plus §41 +0/0 (reconciliation: G 5,310→5,510 already in that packet-table total) plus §42 +3/200 (D18/D19 80, G25 40, G5/G6/G10 rewrite + fingerprint/origin/J3 restatement 80) plus §43 +0/0 plus §44 +2/80 (D20 move-chat-to-matter.ts 40, D21 slot 40) plus §45 +0/0 (drop listChatsForMatter; reassign that 40 to D21 updateThreadMatter on packages/chat-runtime/src/threads.ts) plus §46 +1/40 (G26 move-chat-to-matter.test.ts; restore D21 listChatsForMatter on the same threads.ts slot; router priced on existing I) plus §47 +4/160 (D22 chats.ts 40, G27 threads.test.ts 40, G28 chats.test.ts 40, A12 clio-client.ts 40) plus §48 +2/80 (I22 chat-row.tsx 40, G29 read-only.test.ts 40) plus §49 +0/0 plus §50 −2/80 (drop D22 chats.ts 40, G28 chats.test.ts 40) plus §51 +0/0 (I 22→21) plus §52 +0/0 (I 21→22 restore) plus §53 +0/0 = 20,100 lines /118 paths. J stays three files, 190/60/360 =3/610; skip-not-abort, J3(e) seed-per-SHA and J3(g) sit inside those J lines. Indexes are schema in existing A files. No new product path/schema/task capability/detail-fetch feature. Generated output line counts excluded; prospective, not measured fit.

PacketRequired filesProspective lines
A121,530
B213,120
C31,570
D211,950
F81,260
G245,750
H4240
I224,070
J — deployment orchestrator handoff required3610
Current complete prospective total, not adopted or measured cap11820,100

Current slices: B enrollment 11/1,170 + source 8/1,750 + refresh 2/200 =21/3,120 (stable labels skip B19); J 190/60/360 =3/610. G 24, stable labels skip dropped G28. OAuth/schema unit remains indivisible and held by #432. Both pages planned, all gates unchecked, NOT CLEAN; six separate dispositions in §57 after §56. NOS-384/deployment and S/A unchanged. listChatsForMatter marker-EXISTS guard + test is a NOS-384 handoff condition. I = 22. §41 +0/0 reconciliation of G 5,310→5,510 already in Headers 19,620; §42 +3/200 prices D18/D19 tool-import-boundary, G25 transport-policy.test.ts, G5/G6/G10 rewrite, below-epoch in RR/G5(a)/fingerprint, fourth origin predicate, no BG_RELEASE_REUSED (superseded by §46 Opus 2), P3 NULL stamp / P4 helper, and GRANT ON work_episode/work_episode_evidence TO app_role. §44 +2/80 prices D20/D21; §45 +0/0 reassigns D21 from listChatsForMatter to updateThreadMatter on packet D threads.ts; §46 +1/40 restores D21 as listChatsForMatter on that same slot, prices G26, and prices router 422 on existing I. §50 drops D22/G28. Introduce no new discriminator (no BG_RELEASE_REUSED); leave existing BG_RELEASE_PROMOTED at :55/:607/:723 untouched. J1 edit at bluegreen.sh:727–738: source="${BG_RELEASE_DIR}/deploy/preprod/bin/${name}.sh" (do not phrase $BG_RELEASE_DIR as current fact; price inside J1's 190). The hook refresh runs at :1511, before staging at :1515, so it cannot read $BG_RELEASE_DIR; skip is the only safe form. bg_refresh_post_receive_hook marker skip stays load-bearing, untouched. Unseen is labelled only; delete the hard-incomplete arm of countEligibleNonVisibleCoverage. Unstamped EXISTS is source_authorization_epoch IS NULL only. P3 A7 stamps NULL only; enable helper stamp in P4 after B3 preserves stored origin, same release. Origin helper returns string | null and catches URL/TypeError and ClioApiError; A7 stamps NULL on any throw. Drop markActivitiesUnseen cleared_reason NULL change. No extra schema files or measured fit.

16. Fresh narrowed Astra full review: four accepted findings

FindingDisposition and proof owner
AstraN 1: version 1 omitted checker continuity.ACCEPT. bluegreen.sh:740–749/:1511 installs candidate post-receive. J1's trusted compatibility declaration includes J1/J2 and durable floor preservation, not only app guards. J3's two consecutive real hook pushes prove the compatible candidate cannot remove the next push's check. Same three files / historical 350 lines; historical J 570 is not current; current J is §15.5. No attestation/framework.
AstraN 2: native JSON loses int64 precision before parsing.ACCEPT. ClioClient.json:98–103 and page:125–137 call Response.json. B3/B7 validate decoded IDs before coercion; numeric IDs require positive safe integers, string IDs canonical positive decimal within int64. §16.2 traces mapper provenance loss and refuses ambiguous stored correspondence. G5 proves adjacent unsafe IDs cannot authorize equality or disclose another entry. No generic JSON retrofit.
AstraN 3: personal Clio authorization lacked a review owner.ACCEPT. F3 WorkLedgerReview owns Connect/Reconnect and invokes I's direct-session action; B4/B5 carry the selected return target through signed state. G10 proves unverified → authorize → usable reconciliation on the same review/window/matter. Organization Settings remains separate; no new UI path or NOS-384 hold on Home.
AstraN 4: existing auth test assumes configuration origin.ACCEPT. auth.test.ts:56–87 casts an exchange-only fake without baseUrl and asserts cfg origin. G15 below is a necessary additional existing file, not hidden in G5. Replace the plumbing echo with real ClioClient synthetic HTTP origin behavior and stable refresh-origin proof; preserve revoked/error contracts.

16.1 G15, G25 and G29: affected existing Clio suites

  1. packages/connector-clio/src/auth.test.ts existing: replace completeAuth's exchange-only cast/mock-argument echo with a real ClioClient using synthetic HTTP responses. Prove enrollment records the actual normalized allowed producing origin, not a differing cfg/env value. Refresh after cfg/env change must use the stored producing origin, retain account/identity and send no token to the newly configured region; mismatched injected client refuses before HTTP. Assert observable request destinations and returned verified origin, not forwarded fields or incidental metadata defaults. Keep invalid-state/declined/revoked behavior. Run bunx vitest run src/auth.test.ts from packages/connector-clio and its package suite after implementation; not executed by this docs seat.
  2. packages/connector-clio/src/transport-policy.test.ts existing G25: closed-inventory guard that must be updated, never removed. Classify the new helper in the closed call inventory and update the pinned token-request text. GET-only and no-unclassified-transport remain. Catch/destination proof stays G15/G5. Run bunx vitest run src/transport-policy.test.ts from packages/connector-clio after implementation; not executed by this docs seat.
  3. packages/connector-clio/src/read-only.test.ts existing G29: affected existing G path with lines. Keep GET-only / no-write closed. Classify the origin helper in the closed inventory if this suite pins it; destination proof stays G15/G5. Do not delete it as plumbing.

16.2 Narrow ID boundary and existing ingestion implications

Decoded wire IDs: Hours are lossless from the stored numeric onward; decoded Number at ingest is the same class as IDs (lossy JSON Number), tolerated for short decimals; no ingest/mapper code path. B3 exports one narrow validator through its existing public auth export for identity and B7 exact responses. Number input requires Number.isSafeInteger(value) and value > 0; only then convert to decimal. String input must match canonical ASCII [1-9][0-9]* and be ≤ 9223372036854775807, checked without Number coercion (length/lexicographic bound or BigInt after syntax validation). Reject zero, negatives, fractions, nonfinite/unsafe numbers, leading zeros, signs, whitespace, exponent notation and out-of-range strings. Apply before comparing every relevant account/user/activity/matter ID. Unsafe identity is UNVERIFIED; unsafe entries are withheld with explicit coverage, never equal-by-rounding. Never authorization or full zero totals. Native numeric int64 is NOT lossless; String/BigInt of an already-rounded Number cannot repair it.

Actual accessible legacy boundary: packages/connector-clio/src/activities.ts:47–66 obtains decoded pages and maps them; :80 String(raw.id) and :101–104 refId stringify user/matter/task references. packages/connector-clio/src/matters.ts:98–99 likewise stringifies matter.id. applyActivityPage receives only mapped strings, not original JSON types. Epoch stamping or matching a fresh string cannot prove a large stored key was not rounded. B7 rejects noncanonical or >9007199254740991 stored activity/user/matter keys, including matter.source_id, BEFORE URL construction/joins/disclosure. Check personal actor/selected matter before narrowed SQL; no complete-empty result for an unverified actor. Validate fresh decoded responses before mapping: genuine strings may validate through int64, but matching large legacy keys remain correspondence-unverified. Retain explicit incomplete coverage, withheld full figures and usable saved human work/offline exclusion. These two mapper paths are source citations, NOT added product-edit paths.

Tradeoff and proof: this sacrifices large-key ledger correspondence until trustworthy provenance exists; no repair of ingestion or full numeric-int64 claim. No mapper/client/schema/new provider request/generic lossless parser. G5 uses literal adjacent unsafe-number JSON and real mapActivity output, with safe successful reconciliation and string max/overflow controls. P1 grants remain pending. Four Astra findings accepted; §17 integrates the independent Opus findings returned on the prior snapshot, not a clean result for this revision.

17. Independent narrowed Opus: three dispositions

FindingDisposition and proof owner
OpusN 1: omitted authorize scope defaults to client ceiling.ACCEPT. Cached patched oauth-provider 1.6.23 index.mjs:3881–3884 sets scope from client.scopes ?? opts.scopes. DCR defaults alone are insufficient. Choose existing auth.ts before-hook normalization, including reentered original authorization context after login/consent (§17.1), with actual I runtime proof. No consent-only policy change, invented hook or pinned patch extension.
OpusN 2: compatible candidate removes future J checks.DUPLICATE of accepted AstraN 1. J1 version 1 and J3 two-push continuity already cover candidate-installed hook/library/floor preservation. No additional path or independent framework.
OpusN 3: leaked test mode changes floor identity.ACCEPT. bluegreen.sh:12–38 accepts environment release roots in test mode; deriving the floor from that root bypasses active production state. Pin production floor independently; J2 refuses test mode before side effects; allow fixture override only in isolated copied-script mode and prove leakage refusal in J3 (§17.2). Same three files / historical 350 lines; historical J 570 is not current; current J is §15.5. No hostile-root claim.

17.1 Existing authorize boundary, including login continuation

Verified source: cached installed patched @better-auth/oauth-provider 1.6.23 index.mjs:3839–3853 takes ctx.query and saves state; :3873–3884 validates explicit scopes or defaults absent scope to client.scopes ?? opts.scopes. :3934–3975 reaches reference checks, skip-consent or existing consent. signParams :4024–4035 signs the resulting ctx.query. A consent UI alone cannot distinguish explicit input from that default. consentEndpoint :33–38 supports only downscope, but relying on it misses skip/existing-consent paths and is NOT the chosen fix.

Chosen normalization: existing auth.ts hooks.before matches /oauth2/authorize and inspects original/reentered ctx.query, not Request URL. Absent scope becomes the exact registered ceiling minus ledger; null/absent stored ceiling uses the existing configured NON-LEDGER remainder and never grants explicit ledger until an explicit owner update/re-registration (§19.2). Preserve valid explicit scope and empty-array/empty-string semantics, then provider validation/signing; invalid query/client refuses. Use existing adapter/context override, no shadow cache/private utility import. Install normalization before any ledger ceiling becomes available; it applies across login/consent reentry.

Original request survives login: provider :2993–3017 verifies signed oauth_query and restores request state; :3026–3036 restores the original query after login. Crucially runOAuth2Authorize :2963–2969 calls exported dispatchAuthEndpoint; consent/continue :3086/:3113 also use it. Better Auth 1.6.23 dispatch.mjs:135–164 runs the configured before hook, :207–231 applies its context override before endpoint execution. Thus direct and resumed authorization both hit this real seam, even while the underlying Request URL/body belongs to login/consent. Initial normalization happens before state/default/signing, so signed continuations carry non-ledger scopes unless the original authorization explicitly requested ledger; an absent resumed scope is normalized again. Do not rewrite/re-sign unverified oauth_query or infer opt-in from consentReferenceId's resolved scopes. request_uri has no resolver configured and remains unsupported. I tests exercise actual code/access/refresh results across sessionless login, signed consent/continue, existing consent and trusted skip-consent; explicit direct ledger authorization is the positive control. No auth patch needed from this source trace; runtime proof remains pending.

17.2 Production floor identity and isolated simulation seam

J1 assigns the production floor's absolute path from the existing production releases location (:31), never inherited RELEASES_ROOT or BLUEGREEN_RELEASES_ROOT. At bg_init_config entry, test mode is valid only in a copied fixture library with a baked fixture-root constant, canonical script location under that root and a fixture-created marker there; overridden releases/state paths must stay inside that same isolated root. Environment alone cannot designate a fixture. J2 adds leaked-test-mode refusal: Standard J2 rejects BLUEGREEN_TEST_MODE=1 before its current :30 mkdir/:31 lock creation, without sourcing candidate code. Its production form has no fixture root; J3 may bake one into copied scripts using prepare_checkout's existing copy/substitution seam (:716–719/:732–752), with the same containment/marker check. Do not add a production env-controlled escape hatch.

J3 owns the marker and constant substitution only inside its temporary corpus, alongside existing fake commands/local listeners; continuity pushes install those same isolated candidate hooks. The leakage case uses the standard hook form without fixture opt-in, active isolated production-like floor and a misleading empty override root; assert refusal before checkout/status/lock/routing/service effects. Positive fixture operation proves tests do not need live paths. This guards accidental leaked environment and supported deploys, not a privileged operator rewriting scripts/markers or executing arbitrary old binaries. No new persistent helper, production trust protocol, installer edit or file count increase.

18. Complete Astra pass: one P2, mailbox-independent saved review

ACCEPT, verified source: (app)/layout.tsx:21–22 calls password then Outlook gates; connections/enforce-outlook.ts:23/:49–71 exempts only exact /account. Child/API correctness cannot fix full reload or personal Clio return after mailbox loss. Middleware overwrites x-pathname; request-pathname reads pathname. This complete Astra pass accepted prior fixes and found one P2; subsequent Opus ten dispositions are §19 and final Sol four are §20. No clean result.

18.1 Five necessary existing files, no new route or layout exception

  1. apps/web/middleware.ts: F6. Alongside its existing authoritative PATHNAME_HEADER overwrite, derive a minimal review-mode request header ONLY from request.nextUrl: pathname exactly /tasks/chats/new and getAll("review") exactly ["work"]. Always overwrite incoming review-mode headers with the derived true/false value on every final next() reaching gated layouts; never preserve caller input on non-review requests. Do not forward the whole query. Session-cookie/onboarding/matcher/API streaming behavior stays unchanged; no new public-page exemption. Duplicates, including identical repeated values or encoded duplicate keys, do not qualify.
  2. apps/web/lib/request-pathname.ts: F7. Add a narrow server-only reader for canonical work-review mode, requiring BOTH exact forwarded pathname and middleware-derived review flag. Missing/invalid context is not review. Keep readRequestPathname/isRequestPathname behavior and their existing callers unchanged. F4 and enforceOutlookConnected use this same mode reader; neither takes first/last review value from an independent query parser. This header carries routing mode, NEVER session/origin/authorization proof.
  3. apps/web/lib/connections/enforce-outlook.ts: F8. Retain exact /account exemption and all ordinary connector/status behavior; add ONLY the trusted canonical review-mode exception. Do not add /tasks/chats/new to EXEMPT_PATHS or exempt chats/tasks/matters by prefix. Layout calls remain unconditional and password/session/direct-origin admission remains separate. Review source operations still validate each connector; only saved review access is mailbox-independent. Update the gate's explanatory comments so “everything else” does not contradict this narrow exception.
  4. apps/web/middleware.test.ts: G16, existing. Exercise actual NextRequest → middleware forwarding plus the real server mode reader with controlled next/headers. A forged x-pathname/review header on ordinary chat or /matters cannot create a review exemption; canonical single review=work does. Duplicate keys in both orders and identical duplicates cannot disagree with the child, and missing context never grants an exception. Assert downstream mode/admission, not just copied header fields. Preserve the existing matcher/API-upload streaming contract tests; no new test file.
  5. apps/web/lib/connections/outlook-gate-exemption.test.ts: G17, existing. Replace the pathname-only module mock for the affected gate cases with real request-context reading (controlled next/headers), so forged/missing/duplicate context controls exercise the same reader as F4. Prove review-only admission without a usable mailbox versus ordinary-path redirect, retaining /account and authorized-mailbox positives. Keep recovery/onboarding behavior; touched plumbing echoes should be replaced with observable action/return behavior, not repinned. G10 supplies real full-page/password/direct-origin and callback-return proof.

Canonical routing and trust: keep every existing /tasks/chats/new?review=work action/deep link and validated period/window/timezone/matter/episode parameter. No alternate dashboard, rewrite alias, new route or action-link migration. The exact single decoded review value controls BOTH mailbox exception and F4 rendering; duplicate/unknown values take the ordinary gated path, never first-value exemption plus last-value chat. Valid review with malformed filters fails within review, never falls back to LiveChat. F4 explicitly rechecks the mailbox before ordinary chat on same-path query transitions; Back to chat does not inherit review admission. Middleware must run and overwrite headers for these page requests; the header itself is not cryptographic proof and direct origin-server bypass is not a supported serving path.

Authentication and unchanged callers: (app)/layout.tsx and (settings)/layout.tsx retain their unconditional password then mailbox calls; no layout edits or NOS-384 page/table work. F4 requires I's live direct render session, active organization/current membership before review; stale-cookie, impersonated, removed-member or password-reset users do not gain access from a route flag. C/API/Clio actions still enforce their independent authority and source checks. Existing request pathname users and render-session-consumers.test.ts keep their signatures, empty-header behavior and non-refreshing session contract; layout.test.ts's chat-switch/read scheduling contract is unchanged. No unrelated shell, chat-switch, global navigation-cache or session refactor. G10 verifies full reloads through the actual parents, not only child rendering or API calls.

Proof and cap: five additional EXISTING files / 400 prospective lines across F/G; already-mapped F4/G10 carry the user-visible branch and full reload/return cases. Future targeted commands from apps/web: bunx vitest run middleware.test.ts lib/connections/outlook-gate-exemption.test.ts lib/render-session-consumers.test.ts, plus the existing package suite and G10's deterministic browser fixture. No tests/runtime/server started in this docs-only task. Ordinary chat and /matters full reloads remain mailbox-gated with both sources lost; saved corrections and personal Clio authorization return remain reachable. No blanket ordinary-chat exemption or claim that connector loss exposes stale provider content.

19. Latest complete Opus pass: ten separately verified dispositions

Source-only verification, no product runtime or account calls. Preserve prior privacy/authority/navigation requirements. Ten rows below are dispositions, not ten blindly adopted changes. Sol has since completed its final four findings (§20); the integrated revision remains NOT CLEAN.

FindingEvidence, decision and proof owner
OpusF 1 — client mutation ceilings/defaults.ACCEPT coverage gap, REJECT universal operator opt-in for new DCR. auth.ts:192–209 already covers all five mutation paths but validates redirects/grants, not equal scope defaults. Pinned provider :1244 defaults DCR only; :1285–1289 uses the wider opts.scopes for non-DCR creation/update, and :1357 can leave stored scopes absent. §19.2 normalizes all paths/null ceilings in existing I files. Explicit public DCR plus direct consent remains allowed; only EXISTING pinned Claude upgrade uses the operator script.
OpusF 2 — routine release declarations missing.ACCEPT. Replace per-SHA approval-map wording with J1's single static compatibility value in existing bluegreen.sh, read as data via git show target:path before checkout. Operator sets/restores minimum and validates current/fallback; normal future compatible pushes need no state edit. Trusted declaration includes J1/J2 continuity, not attacker attestation. J3 proves ordinary compatible future push then incompatible second push via refreshed hook.
OpusF 3 — admitted chat text enters memory.ACCEPT full caller closure, not selection-only filtering. backfill.ts:72–123 selects owner/origin only; :149–155 strips the marker before mining. synthesize.ts:60–103 likewise selects chat messages; mine.ts:203/:320–340 extracts then create/supersede persists active personal/matter/firm candidates. §19.1 gates all extraction entrypoints and final shared writer transactions with C's thread lock, including in-flight extraction→admission. No whole-memory rewrite, new taint schema or historical erasure promise.
OpusF 4 — ledger-only grant rejected.ACCEPT least privilege, CLARIFY additive scope never promised forced firm access. oauth-auth.ts:108/:113 requires read in both rows; access.ts:198–211 currently checks write/membership only, relying on that base guard. Change the principal predicate AND family/catalogue/chassis guards together. D5 registration :602/:679 and D6 prompt :1023–1072 must not expose ordinary tools to ledger-only principals. G8/G22/G23 and I SQL proof cover lists/discovery, direct/chassis calls and narrowing; no broad bypass.
OpusF 5 — stale active organization dead end.ACCEPT ledger-specific recovery; REJECT global fallback removal or silent session writes. session-resolver.ts:137–153/:208–224 intentionally falls back to sole membership even with a stale active org; auth.ts:282–314 only defaults sessions at creation. auth-client.ts:9 already installs organizationClient; pinned organization/set-active :342–395 checks membership and writes authoritative session+cookie. F3/F4/I/C and G10 implement §19.3's explicit selection, including null/sole-org semantics.
OpusF 6 — early AS scope advertisement.ACCEPT. Pinned provider :2826–2827/:3046–3047/:4073–4074 uses advertisedMetadata.scopes_supported ?? opts.scopes; merely adding internal scope support advertises early. I auth.ts explicitly pins non-ledger metadata until the SAME C/D/I guarded-fleet activation flag that D6 uses at both RS metadata URLs. Real exported AS and RS responses before/after activation are the proof; no route wrapper or new metadata file.
OpusF 7 — conditional pre-checkout J2 bypass.CLARIFY actual ordering, accepted guard obligation. Existing post-receive :107 mode branch surrounds :128 candidate source, before :148 checkout. The current-controller subshell check MUST run unconditionally after flock and BEFORE that branch/source, regardless of .deploy-mode. Under active floor absent/corrupt mode refuses without replacing controller; J3 source sentinel plus checkout/current assertions prove ordering. Preserve narrowed three-file J and existing later router probe.
OpusF 8 — request_uri bypass.REJECT speculative future-feature premise; RETAIN unsupported invariant. No resolver is configured, and pinned provider :3839–3841 refuses request_uri before resolving authorization. I auth-oauth.test drives an ACTUAL request_uri request and observes refusal/no issued code or grant; no config/source-text assertion, resolver implementation or invented feature bug.
OpusF 9 — contradictory current cap tables.ACCEPT. §2/§13 are historical; §15.5 remains the sole current authority. This review's intermediate §18/§19 reconciliation was97 /12,750; §20 subsequently adds3/1,650 for100 /14,400. Both are prospective arithmetic, not measured fit; removed framework paths stay absent.
OpusF 10 — ambiguous ledger context name.CLARIFY, not a security finding. run.ts:2114/:2121 already sets configurable.ledger_block/ledger_coverage for coverage. Name fresh authority context.work_ledger_actor in I/D8/C; never repurpose those keys. Preserve §15.4's actual saver trace and G9 no-fresh-context resume proof, no additional file or taint system.

19.1 Eleven additional EXISTING paths; bounded service/worker/chassis closure

  1. packages/agent-runtime/src/memory/access.ts: D10. Own the narrow raw-marker predicate and final guard. For source-thread createMemory/supersedeMemory and guarded miner salience, discover relevant user/organization/optional target-matter IDs, lock roots first as §20 S4 specifies, then chat_thread FOR UPDATE NOWAIT with positive local timeouts. Freshly revalidate all discovered parents/thread identity and exact marker after thread lock, BEFORE duplicate success, predecessor retirement, salience or insert; hold to commit/rollback. Missing/deleted/foreign parents or contention give content-free skip/retry with no partial memory effects. No remote work under lock or global lock framework. Threadless independent Settings/MCP and ordinary retrieval bumps retain their contract. Existing export-star suffices; no export edit/import cycle. Export this predicate as the single SQL fragment/TS helper. D7/D16/D17/D20/D21 cite it.
  2. apps/worker-memory/src/backfill.ts: D11. Apply the shared eligibility predicate to count and paginated owner/origin selection; inspect raw messages/thread admission BEFORE filterInternalMessages, never infer eligibility from the filtered transcript. Skip already admitted threads, retain cursor/progress/error behavior. In-flight selection is not authority: mine and final service check again. No corpus rewrite or scheduler-role ledger-table access.
  3. apps/worker-memory/src/synthesize.ts: D12. Apply the same source-thread eligibility before selecting the incremental transcript, including direct per-thread jobs and post-turn/nightly deliveries. Preserve exact column-to-column watermark and retry semantics; admission is a policy skip, not an extraction outage. The shared miner/service close changes after selection. No queue/sweep rewrite: their existing ID-only jobs may enqueue, but cannot extract/persist admitted content.
  4. apps/worker-memory/src/mine.ts: D13. Recheck raw thread eligibility immediately before model extraction even for direct mineThread callers, and after each remote await before embedding/classification/new work. Pass the existing thread.id to all final memory writes and to guarded near-duplicate salience bumps. If admission wins during extraction, discard candidate/enrichment without predecessor retirement, candidate insert, embedding enqueue or retry-loop treatment. Use a content-free named refusal via existing error contract; create/supersede consumers keep their signatures. No candidate/prose logging or long-lived thread lock across model calls.
  5. apps/mcp-server/src/chassis.ts: D14. Preserve D4 common scope decision before handler/elicitation/provider effects, including null families. Normalize EVERY ledger exit to §7's strict receipt projection: scope refusal, limiter denial/outage, consent preparation/decline/cancel/unsupported/timeout, handler success/refusal/error and aborted/deadline outcomes. No exception or def.rateLimitText passthrough for ledger; classify without prose. D15 catches errors outside these branches, including SDK pre-handler validation and failed audit awaits. Ordinary tool result/error contracts remain unchanged; no chassis/context framework.
  6. packages/agent-runtime/src/memory/access.integration.test.ts: G18. Isolated real SQL C-admission/create/supersede/guarded-salience races: extraction pause→admission, both admission orders, AND delete-first/memory-first for user/org/optional matter. Prove bounded termination without deadlock or partial predecessor/salience changes; missing parents yield deterministic content-free skip, memory-first follows existing FK cascade/NO ACTION semantics, not a user-delete success promise. No new retrievable private sentinel after admission. Preserve normal unmarked/threadless positives and exact-marker/missing/foreign cases. Override every DSN per §7; no shared-dev default. Existing suite — keep plain describe (§58 Opus 2). It already runs unconditionally against the real DB under this package’s test script; runIntegration/describeIntegration would make it skip whenever RUN_DB_INTEGRATION is unset, a coverage loss introduced by a change meant to stop suites skipping. Take only the REQUIRE_DB_INTEGRATION=1 && !RUN throw. The describeIntegration idiom stays G24’s alone.
  7. apps/worker-memory/src/backfill.test.ts: G19. Replace marker-visibility-only assumptions with whole-thread exclusion before transcript/model, preserve actual user progress/cursor/outage behavior and a normal successful import. A human private-text sentinel with an internal marker must not reach extraction or candidate persistence. Do not claim mocked-query compilation proves SQL races; G18 owns those. Mocked; drop the REQUIRE_DB_INTEGRATION=1 && !RUN throw (G18 owns SQL races).
  8. apps/worker-memory/src/mine.test.ts: G20. Cover direct miner admission and admission arriving during extraction/classification: no candidate/enrichment/predecessor change or embed enqueue, no endless outage retry, ordinary mining positive. Update the affected mocks to exercise eligibility without replacing the final SQL race proof; preserve near-duplicate/tombstone behavior.
  9. apps/worker-memory/src/synthesize.integration.test.ts: G21. Real eligibility/message/watermark queries exclude admitted threads on direct, post-turn and nightly-style jobs; normal incremental/min-content/cursor precision still works. The existing mocked miner cannot establish final-persist serialization: G18 tests that real service boundary. No new integration harness or test file. Existing suite — keep plain describe (§58 Opus 2). It already runs unconditionally against the real DB under this package’s test script; runIntegration/describeIntegration would make it skip whenever RUN_DB_INTEGRATION is unset, a coverage loss introduced by a change meant to stop suites skipping. Take only the REQUIRE_DB_INTEGRATION=1 && !RUN throw. The describeIntegration idiom stays G24’s alone.
  10. apps/mcp-server/test/access.test.ts: G22. Add ledger-only family refusal for ordinary read/write, ledger+write versus read-only ledger controls and existing read/read-write membership positives. Fix the current write-only nonmember fixture (:184) to include read so it still reaches its intended membership boundary; separately assert write-only denial. Remove/replace touched plumbing echoes, not pin new field forwarding.
  11. apps/mcp-server/test/mcp-protocol.test.ts: G23. Keep real initialize/list/discovery/call/prompt, both RS metadata URLs, SDK/chassis sentinel matrix and ledger-only/combined/ordinary controls. §§21.3/24.1 require two real ledger calls spanning identical-scope access rotation, old-session404 and explicit authenticated-B initialization, live/recovered keys, open stream, pending elicitation and receipt-based no-double-write proof. §24.3 uses actual HTTP task params/malformed sentinel requests through the SDK, both default and existing spike-capable fixture modes: content-free correctly shaped protocol errors before effects, never fake task results. G8 retains all five operations/ordinary-memory positive; I's actual provider/resolver fixture establishes authority. No new test file or claim that arbitrary clients transparently retry.

Memory linearization and unchanged paths: D10 acquires §20 S4's parent roots BEFORE the same chat_thread row used by C admission; fresh marker read is a separate statement AFTER thread lock. NOWAIT contention returns bounded content-free retry/skip, never proceeds on a stale snapshot. Memory-first may commit before admission; admission-first forbids later candidate/retirement/bump writes. Preexisting memories are not erased. Product save/update-memory already pass threadId. Independent threadless Settings/MCP memory remains ordinary; its lack of a thread marker is why the user chose the ledger-MCP output restriction, not a reason to alter memory-save/update. Existing ID-only queues/export-star/classification remain unchanged. All memory consumers carry the permanent guard before activation and retain it on rollback.

19.2 Explicit ceilings, least-privilege discovery and advertised metadata

All mutation paths: reuse auth.ts's existing isCreation/isClientUpdate branch and body versus body.update selection. Wire field is scope (string), stored field scopes (array/null). Validate explicit requested values against the same allowed set on register/create/admin-create/update/admin-update, including explicit ledger only after guarded availability; registration itself is not user consent. Omitted creation scope persists existing non-ledger registration defaults, never an unbounded null. Omitted update scope preserves a present ceiling; a legacy null/absent ceiling is bounded to the configured non-ledger remainder at authorization, without a hidden write, until an explicit owner update/re-registration. Reject malformed/null wire scope under existing schemas rather than treating null as unlimited. Keep valid empty and unrelated scope semantics; no global client-create ban, operator approval list or new public admin endpoint.

Null ceiling and consent: at authorize, explicit ledger still requires an explicitly ledger-capable stored ceiling; null/absent is NOT opts.scopes permission to acquire the new scope. Absent authorization scope always normalizes to non-ledger before signing, including login/consent continuation and prior/skip-consent paths. New public DCR can explicitly request ledger and a direct user can explicitly consent; existing trusted Claude row uses the mapped operator opt-in script/cache replacement because the pinned provider refuses updating trusted rows (:1548–1551). Owner-authorized ordinary updates keep their existing privilege/ownership checks (:1543–1571). No existing access/refresh token gains ledger through a later ceiling/consent update.

Effective scope, not broad principal permission: oauth-auth must require a COMMON read or ledger scope in token ∩ live consent, preserving live org/member/client/consent and direct-issuance checks. Every resolved OAuth principal has explicit scopes; [] never means legacy default. Preserve the documented preexisting missing-scopes read-only contract ONLY for existing non-OAuth legacy/synthetic callers, never reinterpret an explicit ledger-only array as read. D4/D14 enforce ordinary family read/read+write at calls, D5 filters both catalogues and dynamic re-enablement, and D6 keeps protocol discovery usable without advertising ordinary tools/prompts or demanding read merely to initialize ledger. Check catalogue family-null definitions too. Existing tools-discovery.test.ts's legacy fixture contract remains unchanged; G8/G23 use real explicit grants.

Metadata and unsupported indirection: I sets advertisedMetadata.scopes_supported explicitly to existing non-ledger scopes while dormant/mixed-fleet, even if internal opts.scopes already knows ledger; expose ledger only under the SAME deployment activation flag as C/D admission and D6's two RS metadata routes. Existing AS export delegates to the provider, so no .well-known route edit. I real-provider tests GET exported AS metadata and attempt request_uri authorization; the pinned no-resolver path must refuse with no code/access/refresh/consent grant issued. G23 tests RS metadata and ledger-only discovery. No request_uri support, source-text/config assertion or fictional future resolver exploit.

19.3 Explicit active-organization recovery, not a resolver rewrite

Keep ordinary sole-membership fallback in both API/render resolvers. Ledger C/A5 still requires the authoritative session's explicitly selected active org to match the request/current membership; a stale non-null selection or null/absent selection is select-organization-required, even when ordinary resolution finds one firm. This strict choice avoids silently stamping authority and preserves the existing SQL equality contract. Fresh sessions normally receive the sole/default org in auth.ts's creation hook; legacy null sessions get a single explicit choice, not a permanent error or automatic write.

F4's trusted review branch first enforces authentication/password/direct origin; if organization selection is required it renders ONLY F3's recovery state with eligible current organization choices, not saved ledger/source data. User explicitly invokes existing authClient.organization.setActive({ organizationId }) from auth-client.ts (already has organizationClient). That real /api/auth/organization/set-active operation validates membership and updates session+cookie; no custom DB write/new action module. After success refresh the same canonical review/window/timezone/matter/episode URL, re-read authoritative session/member and reauthorize every referenced ID. A foreign/stale selected matter fails, never silently transfers work to another org. Selection errors remain actionable; strict personal connect/reconnect returns this recovery before provider work if needed. G10 verifies the real action and subsequent reload with Outlook absent, not a mocked successful callback.

Current proof/ownership: historical §15.5 105/17,100 through §28 is not current. Historical 106/17,440 is not current; §15.5 is sole current authority. Neither reviewer is running; later independent Astra+Opus dispositions are recorded separately, not a clean pair. Source/documentation checks only: all J3, SQL/owner/provider/SDK/protocol/rotation/elicitation/coverage/browser runtime proof remains pending. #432 retains schema generation.

20. Sol final RLS review: four dispositions

Sol originally left exact OAuth provenance and MCP free-text policy open. MCP was resolved by the user; §20.1 now resolves OAuth technically from pinned source, without a new user decision. A5/D10 corrections remain. All four are concrete design dispositions requiring implementation and independent/runtime proof; NOT CLEAN.

FindingDisposition, evidence and required proof
S1 — duplicate consent and recreated-grant provenance.ACCEPT, mechanism SELECTED in §20.1: five nullable fields on existing OAuth tables, exact binding through code and refresh, and the narrow existing1.6.23 patch with app-owned transactions. Pinned findOne/create and per-ID narrow/delete make tuple-only selection unsafe. Supported hooks cannot carry stored opaque refresh provenance. No unresolved design choice or unallocated work remains; real concurrency/rollback/ordinary-OAuth proof is still pending.
S2 — MCP free text reaches ordinary memory.ACCEPT finding; product choice RESOLVED by explicit user decision. memory-save.ts:130–151 writes threadId:null, so product-thread protection cannot contain returned human prose in a combined-scope host. Spec §7's named MCP output projection removes all ledger free text, not ordinary memory capability. Product-chat human text/marker protection and web descriptions/copy stay. D3/G8/G23 prove real response sentinel absence followed by an unrelated successful memory input. No arbitrary-copy detector, taint/declassification framework or external-host erasure claim.
S3 — A5 owner and sync actor GUC missing.ACCEPT. provision-roles.ts:687–699 already grants app_role auth DML, including row-lock permission. Ledger A5 owner is app_role, not provisioner/superuser; fixed pg_catalog, pg_temp search path, qualified relations, both actor GUCs validated, PUBLIC execute revoked, only app/sync execute. sync-tx.ts:26–66 checks sync_role but sets/restores only organization. Existing strict enrollment transactions must capture/set/restore transaction-local app.user_id without broadening every sync caller. G5/G6 execute real role, missing/wrong GUC and lock-contention cases, including restore after normal/savepoint/error exit; definition/config assertions are not proof.
S4 — thread-first memory persistence versus parent deletion.ACCEPT. chat_thread cascades from user/org; memory rows reference organization and proposed/reviewing users and optional matter, with no thread FK. A thread lock followed by insert's parent FK lock can cycle with parent deletion waiting for that thread. §20.2 makes D10 root-first, then thread NOWAIT and post-lock revalidation, including duplicate/supersede/salience. G18 owns real delete-first/memory-first SQL races and deterministic deleted-parent results, not mocked queries. No global lock or deletion framework.

20.1 Selected exact-generation binding: existing schema plus pinned patch

Decision: extend the existing @better-auth/oauth-provider1.6.23 patch, not its version; add five nullable fields to its existing tables and one narrowly typed patch option consentGeneration: { scope, run }. Set scope to mcp:private-work-ledger. The app-owned run callback in auth.ts executes the local SQL transaction and supplies its adapter explicitly to the provider continuation. This option DOES NOT exist upstream; it is the chosen patch contract. No new grant table, generic auth wrapper, opaque-token format change, metadata side channel or global OAuth rewrite. This is smaller than a sidecar: a sidecar would still need all consent/issuance/refresh interception, plus table lifecycle, RLS/grants and token-row association.

Supported seams inspected; why they do not solve provenance

Pinned dist/oauth-D74mBkw6.d.mts:622–626 gives consentReferenceId only user/session/scopes, no client or consent; referenceId already means organization. :777–793 gives customTokenResponseFields grantType/user/scopes/client metadata and verificationValue ONLY on code exchange, not refresh identity. index.mjs:512–518 calls it before token writes and :541–549 puts its result in the response envelope. customAccessTokenClaims :292–298 is JWT-only; this app uses opaque tokens (auth.ts:448–450). Token generators take no grant arguments (:373/:440). None is an authoritative persisted binding hook.

OAuthOptions.schema is InferOptionSchema (:324); better-auth types/plugins.d.mts:6–11 permits model/field renaming only. db/schema.mjs:12 supports top-level additionalFields only for user/session/account. A separate plugin could declare OAuth fields, but would not fix provider data flow: index.mjs uses raw ctx.context.adapter.create/update for consent/access/refresh, bypassing with-hooks.mjs:6–29/:43–66. Core adapter factory.mjs:101–114 iterates registered schema fields, so the patch must add field definitions as well as data. Do not invent an existing OAuth database hook or rely on unknown fields surviving input transformation.

Existing transaction boundary: createUserTokens :519–540 writes an early refresh, marks the old refresh revoked via incrementOne (:460–479), then creates access with Promise.all; no enclosing token transaction. auth.ts:155–157 leaves drizzleAdapter.transaction disabled; drizzle-adapter/index.mjs:578–586 implements it only when configured. Core runWithTransaction :53–80 uses async-local adapter context and can fall back to fn; merely calling it is not SQL proof. Chosen callback instead opens db.transaction itself, creates drizzleAdapter(tx, { provider: "pg", schema: dbSchema })(ctx.context.options) using the public @workspace/db schema export, and passes that exact adapter into a shallow request-local context clone. No global ctx mutation, async-local fallback or global adapter-transaction switch. All bound token reads/writes use the clone; internalAdapter is deliberately not invoked inside this transaction.

Persisted invariant and schema

A9 adds oauth_consent.consent_generation uuid NULL; access/refresh each add consent_id text NULL and consent_generation uuid NULL. No default/backfill or consent FK: immutable token snapshots preserve ordinary lifecycle. Both token pair CHECKs require both null OR both nonnull; consent nonnull generation requires ledger scope. §22.5 adds three ledger-scope CHECKs requiring nonnull user/reference on consent/access/refresh regardless of generation, closing the partial-index null loophole without changing ordinary scopes. Null token provenance remains ledger-denied; downscoped ordinary descendants retain their old pair. referenceId remains organization identity.

Add a unique nonnull consent-generation index and the partial tuple unique index (client_id,user_id,reference_id) WHERE reference_id IS NOT NULL AND scopes @> ARRAY['mcp:private-work-ledger']::text[]. It prevents duplicate ledger-bearing tuples, including generation-null rows. Ledger authorization uses exactly one qualifying candidate, never an arbitrary findOne. Every accept operation first selects that tuple's single ledger-bearing row when present, INCLUDING accepted scopes downscoped to ordinary-only, so an ordinary duplicate cannot absorb narrowing while leaving the ledger generation alive. With no ledger-bearing row, a ledger acceptance requires zero or one exact ordinary tuple row before create/upgrade; ambiguity refuses without repair. Entirely ordinary acceptance retains existing selection semantics. No new scope is available before migration; incompatible ledger duplicates make the generated unique index fail safely with rollout OFF, never trigger destructive dedup.

The patch adds these fields to index.mjs schema (:2584–2725), verificationValueSchema (:187–194), token/consent/VerificationValue declarations and the OAuthOptions declaration in dist/oauth-D74mBkw6.d.mts (and inferred returned schema declarations in dist/oauth-DZ80cNUW.d.mts). Fields are server-only input:false; public consent getters/update responses explicitly omit the new fields because getConsentEndpoint/getConsentsEndpoint return raw rows (:2128–2153). Never copy binding from query/body, custom response fields or client metadata; never publish it in tokens, receipts or discovery. Raw code verification JSON is server storage, not a public query carrier.

Exact patch callback and lock/transaction contract

run receives a patch-typed operation union (accept, authorize, exchange, refresh, update-consent, delete-consent, replay), trusted provider context, server-read identity/row IDs, accepted/requested scopes and optional exact expected binding. On success it invokes the provider continuation exactly once with the transaction adapter and validated binding, awaits it, and exposes its value only AFTER commit; refusal never invokes token writes. A replay returns a typed committed-refusal outcome so throwing an OAuth error afterward cannot roll back revocation. Accept/authorize/exchange bind the actual provider-resolved direct session (code exchange uses its consumed verification's session, not a new cookie). Refresh binds the stored row without browser liveness. Consent revocation and ordinary-only downscope retain existing owner/provider rules without new active-org/member prerequisites. No public operation or authority switch.

Exact callback inputs: common identity is {clientId,userId,referenceId}; binding is {consentId,consentGeneration}. Accept supplies direct sessionId + accepted scopes; authorize supplies direct sessionId + requested scopes + optional binding from trusted authorizeSettings; exchange supplies consumed-code sessionId/scopes + required binding; refresh supplies stored refreshId/token digest + requested scopes + its original nullable binding; update-consent supplies owner sessionId + exact consentId + replacement scopes; delete-consent supplies owner sessionId + exact consentId; replay supplies authenticated client identity + refreshId + original binding. These form a discriminated union in the patched declaration, not an open Record or public payload. Reread and compare the locked refresh digest/IDs/pair before use. A successful continuation returns T wrapped as {kind:"ok",value:T}; replay returns {kind:"replay"} without invoking issuance, commits, then the provider performs filtered cleanup and throws invalid_grant. Other errors throw inside the transaction and roll back. No true/false authority flags or null-as-new-generation behavior.

Within auth.ts db.transaction, set both local actor GUCs and positive statement/lock timeouts. For ledger issuance use A5's root order user SHARE → org SHARE → member KEY SHARE → surviving session SHARE → client SHARE → existing refresh UPDATE for rotation → consent SHARE for issuance or UPDATE for mutation, all NOWAIT and reread. Omit unissued access rows. Before consent discovery/locking take a transaction-scoped pg_try_advisory_xact_lock of a length-delimited client/user/reference tuple with an OAuth-ledger namespace; conflict aborts content-free, no retry loop. This serializes the empty-row create gap as well as replacement; uniqueness is the DB backstop. Sort any multiple IDs. Native cascade disagreement is handled by NOWAIT, not out-of-order lock additions. Provider PKCE/client checks remain; callback rereads live identity/client/scopes/expiry before writes and commit. All bounded work is local; no provider/model/KMS call or remote custom hook under these locks.

All three consent-writing seams—consentEndpoint:57–99, updateConsentEndpoint:2204–2214 and deleteConsentEndpoint:2169–2175—enter the callback before their write, including requests asking only for ordinary scopes. This prevents a non-ledger narrowing request racing a ledger grant from bypassing invalidation. In the locked ordinary-only case keep existing selection/ownership/scope behavior, with no new active-org/membership or direct-ledger requirement; only ledger issuance receives those stricter checks. Consent-only updates/deletes need no issued token. App-role auth DML suffices; no new table/grant surface.

Concrete issuance and revocation algorithm

  1. Accept explicit direct ledger consent: after existing signed oauth_query/downscope/direct-session checks, lock the tuple and selected row as above. Generate a fresh cryptographic UUID server-side on EVERY explicit ledger acceptance, including repeated same-row/same-scope acceptance; never preserve or reuse an older generation on reauthorization. Write accepted scopes and that generation atomically on the selected consent; return its exact ID/generation. An ordinary-scope acceptance which changes a ledger row's scopes clears the generation atomically; it cannot create one. SQL failure/unique contention rolls back the entire consent change.
  2. Carry acceptance across authorize reentry: extend the existing trusted authorizeSettings passed by runOAuth2Authorize (:2963–2969) with the exact accepted binding; consentEndpoint:105 passes it as a server-local setting, never in signed/public query parameters. authorizeEndpoint validates THAT binding, not a freshly selected replacement. If another acceptance wins between transactions, refuse/restart consent without issuing a code under its generation. Initial authorization may reuse exactly one currently active generation after live direct checks; legacy/null generation requires explicit consent. For ledger requests, skipConsent must not skip this branch: missing active generation prompts consent (prompt=none errors), never auto-mints a generation. Ordinary skipConsent remains unchanged.
  3. Issue code with immutable binding: authorize's locked read returns exact consentId/generation. redirectWithAuthorizationCode :3994–4001 adds them to the SAME verification JSON write as query/user/session/referenceId/authTime. No post-hoc metadata write. This existing internalAdapter verification write stays outside the short authority transaction; if revocation occurs afterward or between snapshot and insert, the code contains only the OLD binding and will fail exchange, never relink. A stale unusable code may exist until expiry; no false promise that every failed/revoked attempt leaves zero verification rows. Original acceptance commits before code creation; code-storage failure does not roll back an already accepted consent.
  4. Exchange: @better-auth/oauth-provider@1.6.23/dist/index.mjs:556–585 owns checkVerificationValue; :557 calls better-auth@1.6.23/dist/db/internal-adapter.mjs:640–720, which owns consumeVerificationValue and its expiry check. Keep code consumption OUTSIDE the token transaction and preserve the database-backed one-use/burn-on-failed-exchange contract, including wrong PKCE, stale generation or DB failure; never restore/replay the code. The core adapter is evidence, not an additional patch target. After validation pass the consumed server JSON's exact binding into run(exchange). Missing/malformed binding plus requested ledger returns invalid_grant. Locked ID/generation/tuple/live-scope checks precede refresh/access INSERTs with that pair; never choose a replacement generation by tuple. Callback failure rolls back ALL token writes and returns no tokens. Core secondary-storage fallback is not cross-process atomic proof; I's existing real-provider/SQL fixture proves the configured path.
  5. Bound token writes: patch createUserTokens :501–553, createRefreshToken :437–482 and createOpaqueAccessToken :370–387 to accept and propagate the exact pair. With binding or requested ledger use the explicit transaction adapter and sequential early-refresh → access writes, rather than launching Promise.all siblings which can outlive rollback. Generate the response inside the callback but expose it only after db.transaction resolves; recheck expiry/cancellation before commit. Preserve built-in hashed storage, opaque-token format and refreshId relation. Configured ledger mode requires the current opaque/non-openid configuration; no JWT/device/client-credentials ledger issuance. Existing custom response preparation runs before the transaction and is not authority. Non-ledger unbound issuance keeps the pinned path.
  6. Refresh: retain client authentication/requested-scope subset checks, then reread/lock the exact stored refresh in run(refresh) before revoked CAS. If locked row is revoked, choose consent UPDATE/replay mode before any consent lock; otherwise issuance uses SHARE, never lock upgrade. Copy its original consentId/generation into BOTH new rows; rotation never obtains a generation by tuple. Requested ledger requires live exact consent/scopes and A5 authority before CAS/write. Ordinary-only explicit downscope keeps existing provider eligibility, without new live-consent/org/member requirements; a deleted/changed consent does not block those ordinary rights, and the inherited pair remains merely a snapshot. Descendant stored scopes cannot upscope again. Successful bound rotation atomically revokes old and inserts both descendants; any failure rolls everything back and leaves original usable.
  7. Narrow/delete/regrant: update-consent is not a direct ledger-consent issuance endpoint. It cannot add ledger to a row lacking that scope; direct signed consent is required. Any changed scope set on a ledger-bearing/bound row clears consentGeneration in the SAME locked UPDATE (even when ledger remains but write is removed); an unchanged scope set is a no-op. Delete removes the exact locked row. Neither operation rewrites tokens. Old codes/access/refresh descendants fail ledger by missing/mismatched generation immediately after commit. Regrant or repeated explicit authorization creates a fresh UUID; same tuple or same consent ID never resurrects the old pair. Existing ordinary token rights remain governed by their original scope intersection, not the new ledger check.
  8. Refresh replay: pinned :774–778 and revokeRefreshToken :2343–2367 call invalidateRefreshFamily (:407–435). Bound replay authenticates matching client, rereads revoked under refresh lock and commits exact consent-ID/generation invalidation before throwing. EVERY cleanup call now has an explicit partition: bound selects/deletes exact pair plus client/user; unbound requires BOTH consentId IS NULL AND consentGeneration IS NULL plus client/user, never an omitted filter. Apply the partition to refresh selection, child-access deletion and final refresh deletion; bound access cannot reference an unbound refresh under the issuance invariant. Retain ordinary-family breadth within the null/null partition, and existing fresh-token CAS/access revocation. Newer ledger grants survive BOTH old bound replay and old ordinary replay. NOWAIT contention is not replay; real reuse invalidates only its generation. No global family-transaction rewrite; cleanup failure cannot restore denied ledger descendants.
  9. Resource server/final authority: oauth-auth and A5 grant ledger only when access.consentId/consentGeneration match the exact current consent and client/user/reference tuple, with one ledger-bearing candidate and required live scopes. Null legacy or old binding is ledger-denied regardless of later consent. Access refresh ancestry remains FK coordination, not browser liveness. Ordinary scopes retain existing admission; a combined token may retain those ordinary rights while losing ledger. No public principal field or client-supplied binding substitutes for the stored access row.

Failure/concurrency proof and bounded source ownership

I auth-oauth.test.ts plus oauth-auth.integration.test.ts and G6 use real patched provider + isolated PostgreSQL, not callback/adapter mocks. Required cases: concurrent first consent creates at most one ledger-bearing row; parallel accepts return different generations and the loser cannot issue a code bound to the winner; pause accept→authorize reentry then regrant; raw/duplicate/null tuple and null legacy refusal; forged query/body binding ignored/refused; skipConsent cannot initialize provenance; code write failure versus token-write rollback; same-row remove/regrant and new-row delete/recreate refuse every old code/access/refresh descendant while a fresh direct grant succeeds.

Pause exchange/refresh after locked validation versus narrow/delete in BOTH orders: revoke-first denies ledger with no token writes; token-first commits before revoke, then descendants lose ledger. Inject access INSERT failure after refresh CAS/INSERT: old refresh remains unrevoked, no orphan descendants/returned tokens; code exchange still burns its consumed code. Concurrent same-refresh rotation, replay versus rotation, replay after a fresh-generation regrant, family-cleanup failure after replay invalidation, refresh downscope carrying the original pair, and browser session SET NULL are separate required cases. Prove public JSON/logs contain no binding; normal read/read-write DCR/consent/code/refresh/logout still works with null fields and without ledger membership guards. Root/cascade contention is bounded, no 40P01/55P03 partial-success result. None is claimed executed by this documentation seat.

I's real-provider regression also starts with a revoked ordinary null/null refresh, grants fresh direct ledger to the SAME client/user, replays that legacy token through refresh and revoke family branches, then successfully uses the new ledger access and rotates its refresh. Ordinary sibling family tokens are still cleaned up; fresh bound rows are untouched. This closes the old unbound-path exception, not just the bound replay scenario.

  1. patches/@better-auth%2Foauth-provider@1.6.23.patch: I20, existing artifact. Extend only the consent/code/token seams, field/type schema and the patch-defined callback above; preserve its existing hashed-secret/openid fix. Internally patch dist/index.mjs and its two OAuth declaration chunks; these are dependency bytes captured in ONE repo artifact, not new repo modules. No version switch or upstream fork checkout.
  2. bun.lock: I21, existing generated dependency record. Regenerate through Bun with the existing exact-version patchedDependencies mapping; never hand-edit. Allow unchanged output if Bun retains the same lock bytes; the path is explicitly owned/count-budgeted, not promised churn. package.json:62–65 already points to this exact patch and remains unchanged unless Bun demonstrates a necessary mapping change, which would require a reported map delta.

Reproducible patch method, future implementation only: use bun patch @better-auth/oauth-provider@1.6.23 to detach package bytes from Bun's cache, edit only the prepared package's named dist files, and generate the combined artifact with bun patch --commit @better-auth/oauth-provider@1.6.23. Preserve the original fix; do not edit global cache or hand-invent patch/lock hashes. Run the real issuer/resolver tests on a fresh isolated install with the generated patch and bun install --frozen-lockfile; confirm1.6.23 and ordinary controls, not source-text assertions. Bun's patch-commit is dependency generation, not a git commit; git remains orchestrator-owned. This docs pass runs neither patch command nor install.

Allocation and scheduling: §20 reached 100/14,400; §§21–22 reached 101/15,600; §23 reached 101/15,700 with J 450; §24 added 300 for 101/16,000; §25 adds H4 +1 path and J +80 for historical 102/16,080; §26 adds 0; §27 adds two existing worker-context paths and 480 lines for historical 104/16,560. Those 102/16,080 and later 105/17,100 figures are historical; historical 106/17,440 is not current; §15.5 is sole current authority. OAuth fields/CHECKs, connector generation, ledger schema and named indexes still share ONE later SQL/snapshot/journal set after #432. A5 remains provisioning DDL with helper-owned transaction. No new schema or dependency patch target from §25–§30; no product/DB/git/schema/runtime operations here.

20.2 Root-first memory writes and strict sync enrollment ownership

D10 order: nonlocking discovery of the source thread, actor/proposer and affected memory target/predecessor; deduplicate/sort relevant user roots FOR KEY SHARE → organization FOR KEY SHARE → optional target matter FOR KEY SHARE → source chat_thread FOR UPDATE. Every acquisition NOWAIT with positive transaction-local limits; no lock wait while holding a child and no late out-of-order parent. After thread lock, freshly revalidate discovered IDs/ownership/org/matter eligibility and raw marker, then duplicate/retirement/salience/insert under the same transaction. Changed discovery, missing/deleted/foreign parent or marker yields content-free policy skip; contention yields content-free bounded retry, with no partial effects or remote work under lock. For supersede, include the affected predecessor's roots; guarded salience follows the same rule even without insert.

Deletion semantics are not rewritten: schema/agent.ts:28–30/:74–76 cascades user/org to chat_thread; agent_memory.ts:59–65 has org CASCADE and proposed-user NO ACTION, :115 reviewing-user NO ACTION, :168–172 matter-pair CASCADE, and :62 no thread FK. Delete-first either holds a root and causes immediate refusal or commits and produces missing-parent skip. Memory-first may commit, after which org/matter cascade or existing user NO ACTION governs deletion. Do not promise every user deletion succeeds or erase previously valid memory. G18 tests these actual outcomes and bounded completion in both orders, including no partial predecessor/salience changes; no permanent general-purpose lock test framework.

Strict sync actor plumbing: existing B9 user-tokens.ts and B18 connectors.ts strict enrollment transactions own capture of prior app.user_id, local set from independently authenticated actor, A5 call under matching org/user GUCs, and restoration on normal/savepoint exit. If the transaction is aborted, preserve its original error and rely on transaction-local rollback rather than masking it with restore failure. Do not edit the shared sync wrapper or silently supply a user to ordinary sync work. A5 executes as its app_role owner; test outside invocation still observes sync_role, correctly restored GUCs and actual denial/lock behavior. All work remains mapped to A5/B/G5/G6; no DB operation here.

21. Fresh Astra fullpass — three independent P2 dispositions

FindingDisposition and proof
AstraP 1 — ordinary replay deletes a fresh ledger grant.ACCEPT. Provider :407–435 filters only client/user, including the final deleteMany. §20.1 now partitions EVERY cleanup: null/null ordinary family or exact bound pair. I real-provider old-ordinary-replay→fresh-ledger-use/refresh proves the boundary; ordinary siblings still revoke.
AstraP 2 — SDK/chassis can bypass handler projection.ACCEPT. SDK1.29 validates before the callback and returns error.message; chassis has pre-handler and prose exits. §21.2 contains the actual complete SDK closure and all chassis results. Reflected caller text is a contract breach, not proof of stored-ledger disclosure; fixed generic prose is not itself a privacy leak. G8/G23 separately prove each boundary.
AstraP 3 — same-scope token replacement keeps captured A.ACCEPT. mcp-app.ts:938–944 keys only org/user/scopes; :1072–1075 captures init principal. §21.3 keys ledger sessions by authoritative access-row ID and uses existing404/reinitialize, with both stored/current capability states compared. No per-request context framework.

21.2 Complete ledger-only MCP result boundary

Verified seam: SDK1.29.0 server/mcp.js:125 validates input before executeToolHandler; :174–178 formats Zod error messages (strict unexpected keys can contain caller text); :135–161 returns that message. server/index.js:118–143 adds outer request/result validation. Public registerTool callbacks are too late; public setRequestHandler adds those validators outside its callback, and protocol.d.ts exposes no public getRequestHandler. A second registration interceptor would not contain every outer failure. The repo ALREADY wraps the complete installed closure in audit-boundary.ts:416–531, with a boot compatibility probe :680 onward; mcp-app.ts:1277 installs it before transport. Reuse that pinned seam, not a second convention, SDK patch, copied dispatcher or new private-field access.

  1. apps/mcp-server/src/audit-boundary.ts: D15, EXISTING mapped path. In the existing tools/call wrapper, classify the exact five ledger names regardless of current catalogue presence. BEFORE inner SDK validation/callback, any present task member on a named ledger request takes §24.3's unsupported-task protocol refusal, including malformed/null task; never run a handler and sanitize afterward. Ordinary non-task ledger returns/errors use D3's strict receipt projection. Preserve L1/L2 audit/settlement and an outer error boundary that normalizes even audit failures according to the original request kind: task-shaped requests throw a fixed data-free McpError, never become CallToolResult. Non-ledger returns/errors remain unchanged. Keep boot compatibility refusal/no outputSchema; no new private seam, SDK patch or task capability.

D3 owns one strict CallToolResult serializer used by D14/D15 for ORDINARY non-task ledger requests: rebuild allowed IDs/versions/counts/enums and validated review/action links, never spread _meta/structuredContent/content or error objects. Invalid results become fixed refused receipts; D14 retains named chassis outcomes. Task-augmented ledger invocations are unsupported protocol requests, not receipt or CreateTaskResult responses: §24.3's early data-free JSON-RPC error preserves shape and request correlation. Already-safe outer SDK capability/framing errors remain protocol errors; raw SDK validation errors that reach D15 never pass through unsanitized. No caller keys/arguments/error data in either ledger receipt or protocol error.

G8/G23 proof: real SDK Client/transport calls to real ledger registrations with an unexpected argument KEY carrying a caller-only sentinel; handler must not execute and the response must satisfy the strict receipt contract without that sentinel. Separately use stored human/source sentinels in valid calls, plus limiter false, limiter throw, scope denial, NorthToolRefusalError containing a sentinel, consent exits, disabled known ledger tool and audit failure. Assert actual result schema/content and no forbidden fields, not fixed wording or a mocked SDK response. Fixed rate-limit/outage prose is normalized for contract consistency, not mislabeled as stored-data leakage. Preserve ordinary tool errors and combined-grant unrelated memory persistence.

21.3 Bind live and recovered ledger sessions to the access row

Ledger-capable principals require I's server-resolved nonsecret access-row ID. Domain-separated ledger keys length-prefix org/user/sorted scopes plus that ID; ordinary keys retain old bytes. Store and compare complete live (:1293/:1521) and recovery (:1779/:1346) keys unconditionally, including ledger scope loss/gain and identical-scopes A→B. Never rewrite captured principal closures. Every replacement access row intentionally requires fresh initialization; auth.ts:451 sets the access lifetime to one hour, so routine hourly rotation has this compatibility cost even without scope changes. Existing404 is the required handshake, NOT a guarantee that every external client retries or that interruption is invisible. Explicit authenticated initialization with valid B succeeds. No credential/binding logs or generic request-context rewrite; final authority checks remain mandatory.

G23 executes §24.1's real two-call rotation/stream/elicitation/receipt lifecycle, plus existing reconsent, live/recovered scope transitions and ordinary recovery controls. A's in-flight work retains immutable A authority and original cancellation: it may finish only while A remains valid and uncancelled; B never rescues or silently takes over it. Expired/revoked/cancelled A cannot disclose or commit afterward; a completed commit remains completed, with its existing receipt rules protecting an explicit retry. Rotation alone does not necessarily revoke every old access token; use explicit expiry/revocation controls. I's real provider/resolver proof establishes grant validity; G23 proves transport lifecycle rather than a field-copy mock.

22. Completed Opus fullpass — five independent dispositions

FindingDisposition and owning work
OpusP 1 — A5 owner transfer lacks schema CREATE/owner membership.ACCEPT. §22.1 retains app_role but installs under explicit actual owner permissions with temporary CREATE, transfer and revoke in ONE provisioning transaction. Separate app login membership is not evidence. G6 owns non-superuser positive/negative/rollback proof; no permanent CREATE or broader auth grants.
OpusP 2 — enrollment generation has no named column.ACCEPT. A8 adds nullable connectors.enrollment_generation; §22.2 defines START snapshot and SQL CAS/increment through all named Outlook/personal Clio callers. It is distinct from authorized_at, source observation epochs and sync-only binding/revision. One later schema unit, no generation now.
OpusP 3 — redundant lastVisibleAssistantText edit/test premise.ACCEPT deletion of planned work. Sole production caller threads.ts:883–884 receives listMessages' filterInternalMessages output (:993–998). D9 changes only marker filtering. G9 keeps real earlier-meaningful-summary proof through persisted reload, not an isolated raw-helper test.
OpusP 4 — nullable organization status should COALESCE active.ACCEPT nullable source fact; REJECT fail-open recommendation. Unknown is not positive active authority. §22.4 preserves status='active' and structured inactive-or-unverified denial, without global resolver change or NOT NULL migration.
OpusP 5 — nullable consent user escapes partial tuple uniqueness.ACCEPT. §22.5 chooses strong ledger-only scope CHECKs on all three OAuth tables. Every ledger-bearing consent has nonnull user/reference; ordinary null semantics remain. G6/I prove null rejection and valid direct-grant uniqueness, not a universal claim over ordinary rows.

22.1 Executable least-privilege A5 ownership installation

Source and decision: provision-roles.ts:19–27 connects as OWNER_DATABASE_URL without role assumption; :573 grants app_role only schema USAGE; :1291–1309 grants roles to DB_APP_LOGIN_USER, which need not be that connection. PostgreSQL ALTER FUNCTION requires function ownership, ability to SET ROLE to the target, and target CREATE on its schema. Do not infer any from an environment variable name, CREATEROLE alone or the app-login grant. Deployment docs describe a superuser DSN, not verified non-superuser SET membership. Retain app_role's existing auth DML instead of moving the definer to a broader owner or granting auth UPDATE to sync.

A5's bounded provisioning entry must preflight the ACTUAL connecting role: schema ownership/CREATE grant option, CREATE ability, and pg_has_role(current_user, 'app_role', 'SET') (or actual superuser); existing A5 must be owned by app_role or the installer, otherwise refuse. Supported non-superuser owner credentials explicitly require existing SET membership to app_role, not inherited application privileges; verify it, never auto-grant membership or assume DB_APP_LOGIN_USER establishes it. Require app_role effective schema CREATE false initially, including PUBLIC/inherited rights; unexpected broader privileges cause a specific prerequisite refusal, not global ACL repair. Missing rights leave provisioning failed and activation OFF, never change the definer owner as fallback.

Execute ONE unparameterized simple-protocol SQL statement list through runLockBoundedStatement, omitting caller-supplied values (helper default[]); no prepared query/name or parameterized multi-command workaround. The helper alone begins (:338), sets local timeouts/search_path (:342–348), commits (:366) and rolls back (:369). Do NOT include SQL transaction BEGIN/COMMIT/ROLLBACK or duplicate timeout settings in the supplied list. Issue existing schema USAGE and temporary GRANT CREATE TO app_role; absent/installer-owned A5 is CREATE OR REPLACE by installer then ALTER FUNCTION OWNER TO app_role. For an app_role-owned repeat, SET LOCAL ROLE app_role before replacement. On both paths act locally as app_role to revoke PUBLIC EXECUTE and grant only app/sync EXECUTE, then SET LOCAL ROLE NONE to reset to the verified unassumed installer. REVOKE CREATE and verify effective CREATE false/exact function owner inside that SAME helper-owned transaction. Keep verification failures inside SQL; do not depend on a multi-command result having one rows array. No per-statement helper calls, session-wide role setting, permanent CREATE or broader auth grants. Failure rolls back function and ACL changes before helper commit.

Mechanistic correction: PostgreSQL BEGIN inside a transaction warns and leaves transaction state unchanged; it does not commit a grant before execution. An inner COMMIT ends the existing transaction, bypassing the helper's ownership. Accept the single-owner fix and reject that imprecise early-commit explanation. G6's existing actual-entry/non-superuser/failure-after-transfer proof remains required; no helper rewrite or SQL execution by this seat.

G6 fixture: execute the actual A5 provisioning entry, not copied SQL, on an isolated database with a real nonsuperuser schema-owner login and explicit SET-only app_role membership, distinct from the synthetic app login. Assert first install, repeat and function execution under app/sync with both GUCs; app_role CREATE fails afterward. A second owner lacking SET membership must fail even though DB_APP_LOGIN_USER can SET ROLE. Inject failure after temporary CREATE/transfer and verify rollback leaves no CREATE privilege or partial function/ACL change. Role setup is coordinator-controlled synthetic fixture work, never a shared-role mutation by this docs seat. This defines supported owner preconditions; no live owner membership was inspected or claimed.

22.2 Named connector fence and exact compare/increment

A8 adds nullable connectors.enrollment_generation timestamptz with mode:string, no default/backfill. Existing connectors.ts:66–69 contains authorized_at, not this fence; generic connector writer :794–796 advances authorization epoch independently. The new field is safe metadata, not the Outlook selected binding, which stays on sync-only connector_user_tokens. Never overwrite/reuse authorized_at, credential revision, clio_activity.source_authorization_epoch or the OAuth UUID consent generation as this CAS value.

At START, B13/B16 and personal Clio B3/B4 read the owned connector's exact lossless generation and sign it with actor/org/session/member/connector/nonce; START does not advance it or invalidate a working selection. Null is a real initial snapshot, not a wildcard. B17/B5 callbacks and B9/B18 strict writers compare that original using enrollment_generation IS NOT DISTINCT FROM $expected::timestamptz under the existing ordered connector lock. On successful enrollment, atomically write credentials/binding and advance ONLY this fence with GREATEST(clock_timestamp(), COALESCE(enrollment_generation, '-infinity'::timestamptz) + interval '1 microsecond'); separately advance authorized_at under its existing strict epoch rule. RETURNING supplies exact new fence/epoch/revision for B8/B14/B15 receipts. Never round-trip either timestamp through JS Date/milliseconds.

Two starts may share a snapshot; the first committed completion wins, not the latest started flow. Later callback/error writes cannot update a newer generation: every scoped bound error path compares the original before mutation and advances the fence if it commits an invalidating state change. B9 legacy Microsoft replacement/disconnect and B18 personal invalidation advance the same fence atomically with clearing/invalidating strict selection; ordinary credential refresh preserves enrollment generation and grant identity, advancing only its credential revision. No scope/type/owner transfer or global worker-refresh rewrite. G5/G11/G14 and personal Clio tests cover null initialization, same/backward clock, microsecond-distinct values, parallel starts, B commit before delayed A success/error, legacy/disconnect invalidation, refresh preservation and rollback with independent epochs.

22.3 Remove redundant summary-helper work

Full caller search finds only buildPriorTurnSummary calling lastVisibleAssistantText in product code. listMessages already filters internal rows before that call. Keep marker-only filtering and real persisted-history/summary regression in G9; remove the planned helper change and any isolated unfiltered-input test obligation. Real blank/mixed assistant behavior stays unchanged, not a defense-only semantic expansion.

22.4 Unknown organization status is not active

schema/auth.ts:108 defaults status to active but leaves it nullable. Current session-resolver.ts:123–178 selects membership and resolves organization ID; it does NOT prove status or establish NULL-as-active semantics. A5/C/I ledger checks and both RLS policies require the positive status='active' predicate. Null/inactive yields structured organization_inactive_or_unverified before ledger/source disclosure or writes; preserve separate select-organization-required recovery and ordinary resolver behavior. G6 plus I/G8 test a valid member/direct session with NULL status: no ledger rows or provider work and no silent activation; explicit active is the positive control. No COALESCE active, status rewrite or NOT NULL migration.

22.5 Three ledger-scope identity CHECKs

A9 defines oauth_consent_ledger_identity_check, oauth_access_token_ledger_identity_check and oauth_refresh_token_ledger_identity_check. On EACH table: NOT (scopes @> ARRAY['mcp:private-work-ledger']::text[]) OR (user_id IS NOT NULL AND reference_id IS NOT NULL). scopes is already NOT NULL (:70/:99/:120). These apply even with null consent generation; ordinary scopes retain existing nullable fields. Combined with the partial ledger-bearing tuple index, EVERY ledger consent tuple has nonnull client/user/reference. Retain the two token pair CHECKs and consent-generation-implies-ledger CHECK separately; exact binding still gates authority. G6 SQL rejects ledger user-null/reference-null rows on all three tables, accepts legitimate ordinary nullable rows, and races valid direct ledger tuples. Incompatible preactivation rows fail the generated migration safely; no grandfathered ledger grant or destructive repair. All three CHECKs share the scheduled A migration after #432.

Review/evidence boundary: Astra3 and Opus5 are completed reviews of the prior snapshot; the integrated revision is NOT CLEAN. Opus's confirmation of hook/patch mechanics and prior arithmetic is retained, not runtime verification. This documentation pass reads sources and validates pages/map/indexes only; real owner/SQL/provider/SDK/protocol proof and re-review remain pending. Same four documentation outputs only.

23. Latest complete Astra review — one P1, exact admitted-tree materialization

FindingDisposition and proof owner
AstraR 1 — admitted A, mutable-main checkout installs unchecked B.ACCEPT P1. post-receive:19–22 captures target_sha, but :148 checks out main and :167–173 detects drift after replacing controller/map. B can update main before its hook fails flock (:31–36). J2 must materialize only admitted SHA with read-tree; J3 owns real-Git race proof. This disposition and exact-SHA read-tree survive later reviews. §25 extends the same three J files with the materialized-SHA marker (J530). No clean or runtime result claimed.

Chosen Git operation: GIT_WORK_TREE="$DEPLOY_DIR" git read-tree --reset -u --no-sparse-checkout "$target_sha" replaces checkout -f main. The full commit ID from receive input remains fixed through admission, candidate git show, materialization, status and DEPLOY_TARGET_SHA. Git's read-tree contract updates the index and, with reset/-u, worktree; no ref/HEAD update is involved. Disable sparse checkout for a complete tracked tree. Reuse the existing bare repository/index and deployed worktree under fd200 flock; preserve untracked runtime configuration outside tracked conflicts, existing static .devproxy-ignore/map handling and normal Git index locking. No detached checkout against the shared bare repository, which would change its HEAD; no reset main, update-ref, symbolic-ref restoration race, new linked worktree or incidental history mutation.

Minimal concurrent-push handling: retain the post-materialization refs/heads/main comparison as a superseded-attempt refusal, not as admission or a scheduler. If B advanced the ref, A leaves only admitted A's tracked controller tree, publishes existing failed checkout status and exits before deploy.sh; it neither loads B nor promises B will run. The post-receive contract runs after refs update and cannot undo that update. B may already have failed flock. Report deployment incomplete and require the existing explicitly authorized retry flow once contention is resolved; do not auto-retry/re-push, wait-loop, enqueue or rewind main. A push of an already-current ref need not invoke another hook; no promise that a bare repeat push retries deployment.

Later window and supported consumers: deploy.sh:71–76 pins CHANGELOG_GATE_TARGET_SHA to exported DEPLOY_TARGET_SHA; its later :690–704 compares that changelog target to DEPLOY_SHA. Preserve deploy.sh unchanged rather than expanding the caller map. :690–696 derives DEPLOY_SHA from git --git-dir "$BARE_GIT_DIR" rev-parse main, not DEPLOY_TARGET_SHA, so a supported direct deploy.sh "$DEPLOY_DIR" after superseded abort can still stage A's tree as RELEASES_ROOT/B; §26 closes that with the stage-release refuse. A still-later push cannot change A's materialized tree while A holds the deploy lock; B's hook cannot materialize under that lock. Current rollback.sh:125–127 and deploy.sh:788 therefore load only the admitted controller, whose existing J1 checks still govern selected retained/direct-deploy targets. No claim of an atomic filesystem swap or guaranteed latest-main deployment; the security invariant is that unchecked B never becomes controller code through this race.

J3 deterministic runtime proof, pending: within the existing test file capture the real Git binary before fake-command PATH injection. Use an isolated real bare repository/worktree with symbolic HEAD→refs/heads/main, distinct committed A/B controller and tracked added/removed-file sentinels, minimum1, and copied fixture hook/controller. A is compatible; B is below-floor or omits the required checker. Pause A AFTER successful admission and BEFORE materialization using a fixture-only FIFO/barrier around the real materialization command, not a production pause flag or timing sleep. Advance bare main to B through the isolated receive path; await B's actual hook lock-refusal completion, then release A. Verify the index and materialized controller/scripts/added/removed files match A, static devproxy map handling uses A's approved map rather than B, and unrelated untracked runtime configuration survives. Bare main stays B and symbolic HEAD is unchanged. Neither B source sentinel nor B controller bytes escape; A reports superseded failure and no deploy/route/current/service action executes. Then exercise supported rollback/direct-deploy guards from the remaining admitted controller and prove no incompatible service/route selection. Keep a non-racing compatible push as the success control and existing refreshed-hook continuity. Fake service effects are appropriate; fake Git that always copies A/returns the canned SHA is not this proof.

Bounded allocation/evidence: §23 snapshot was J1 bluegreen.sh 170, J2 post-receive 40, J3 bluegreen-sim.test.sh 240 =three paths/450 and 101/15,700; §24 added only its named 300 elsewhere. §25 supersedes J to 190/60/280 =530; §26 relocates the J1 refuse inside that same 530; §27 adds J3 missing-state 40 for 190/60/320 =570. No Git command, product/deploy/database/schema/runtime operation was performed by this documentation seat. J3 and re-review remain pending; neither reviewer is running.

24. Latest completed Opus — five separate dispositions

FindingDisposition, reason and owner
OpusR 1 — nested provisioning transaction ownership.PARTIAL ACCEPT: remove supplied transaction delimiters/timeouts and let runLockBoundedStatement own the single transaction (§22.1). REJECT “inner BEGIN commits the grant before execution”: PostgreSQL warns on nested BEGIN; the inner COMMIT ends the outer transaction. Same A5/G6, no helper rewrite.
OpusR 2 — access-row keys interrupt same-scope rotation.ACCEPT compatibility/proof gap. Keep the intentional access-row key and require explicit B reinitialization even on hourly identical-scope rotation. Reject universal transparent-retry/no-interruption claims. D6/G23 preserve A's immutable authority, cancellation and existing receipt idempotence; §24.1 defines actual lifecycle proof.
OpusR 3 — aggregate attachment budget hides per-episode uncertainty.ACCEPT. B1/C/F3 and G5/G10 use existing coverage for unchecked versus partial versus completely listed/verified-empty. No implicit detail fetch, schema field or larger budget; MCP gets safe enums/counts, never explanatory free text (§24.2).
OpusR 4 — task-augmented response shape and private validation errors.ACCEPT contract gap; REJECT fake CreateTaskResult, task capability expansion and raw-error passthrough. Plain ledger registration is task-forbidden. D15 refuses present task params before inner execution using a fixed data-free JSON-RPC protocol error; ordinary requests alone receive ledger CallToolResult. G23 exercises real wire/schema boundaries (§24.3).
OpusR 5 — ambiguous source locations.ACCEPT citation correction, not product scope. §16.2/spec §3.2 now name packages/connector-clio/src/activities.ts and packages/connector-clio/src/matters.ts. §20.1 distinguishes @better-auth/oauth-provider@1.6.23/dist/index.mjs:556–585 from better-auth@1.6.23/dist/db/internal-adapter.mjs:640–720. No mapper/core-adapter edit or new patch target.

24.1 Rotation is a compatibility boundary, not silent principal replacement

Required G23 sequence: use the real HTTP transport and ledger registration. Initialize with valid A, perform a ledger call and keep the actual standalone stream open. Rotate to a distinct valid B with identical org/user/scopes, make the second ledger call carrying A's session ID and observe404 with no handler/ledger effect; explicitly initialize B, then repeat that intended call successfully under B. Exercise live and recovered initialization records. Do not hide the404 in the fixture resolver or assume an arbitrary external client's SDK retries. Preserve existing stream cap/closure/recovery behavior (mcp-app.ts:92/:392–396 and mcp-protocol.test.ts:4856 onward); key mismatch does not itself prove A's open stream was closed or its prior access revoked.

Also pause the existing chassis elicitation path (apps/mcp-server/src/chassis.ts:390–455) on A's transport during rotation. That range is elicitConsent / consent-result handling, not mcp-app.ts's SSE lifetime timer at :390–396. A B-authenticated response/reuse of A's session must not complete or rebind A's pending interaction. Retain A's original signal, expiry and fresh authority checks: valid uncancelled A may settle under A; revoked/expired/cancelled A refuses before later disclosure or write. B's initialized transport can make a fresh explicitly authorized call, never inherit old acceptance. Use existing real consent/transport behavior, not a new ledger elicitation/task feature. Drain/close fixtures deterministically and observe no orphan handler or late write.

For a ledger mutation committed under A whose response was interrupted, explicitly retry the SAME UUID/payload under valid B after reinitialization. Existing latest-mutation receipt or structural lineage returns the same safe outcome without another version/anchor/write; an intervening mutation yields the existing stale conflict, never replay-as-new. For cancellation before commit, observe zero commit before the explicit B attempt. G23 observes transport results backed by the existing real service/isolated SQL fixture; G6 remains the authoritative receipt/concurrency proof. No new receipt store, lifecycle framework, automatic retry or exactly-once network-delivery claim. I's provider/resolver suite separately proves actual rotated B and explicit expired/revoked A authority.

24.2 Existing coverage records uncertainty at message and episode scope

B1 initializes each candidate message's attachment coverage to attachments_unchecked before scheduling. If the aggregate100-record/5-attempt/page budget, concurrency admission or shared deadline prevents its listing, it stays unchecked: hasAttachments=false, no scheduled request and zero retained attachment rows are never complete-empty proof. A started collection with failure, cut response page, unvisited nextLink or incomplete body/parsing is partial; successful complete traversal of that message's collection with no truncation and zero records alone permits verified-empty. Preserve known records without claiming the remainder absent; nonzero completely listed is complete, not empty. No collection state implies authored effort.

C carries these facts in the EXISTING coverage response associated with the relevant source message/episode and its observation, not a new table/column or artificial attachment evidence row. A combined/split episode cannot inherit one input's complete state for its other messages; all relevant message collections must be complete before episode-wide complete/empty, and every completely listed collection must be empty before verified-empty. Mixed complete/unchecked inputs remain incomplete with the unchecked reason retained. Saved reads with no current listing coverage default unchecked; do not persist/infer authority from old zero rows or launch requests merely to fill the label. Existing authorized bounded scans/hydration remain the only source paths; no new fetch-on-expand/detail endpoint.

F3 renders explicit unchecked/partial labels beside existing episode/source coverage; complete-empty is scoped to the successful listing, never a timeless claim. D3's MCP projection permits only existing safe coverage enums/counts/owned episode IDs, not provider message IDs, names or prose labels. G5 supplies separate messages: an actually complete empty listing, a cut page/nextLink left unread, and a message never scheduled after exhaustion, including hasAttachments=false. G10 observes those distinct labels and saved-read unknown fallback without extra calls; combined/split coverage cannot upgrade uncertainty. All original metadata/no-bytes/permission limits remain.

24.3 No ledger task capability; protocol errors stay protocol-shaped and content-free

Verified registration: apps/mcp-server/src/chassis.ts:304–311 uses plain registerTool. Pinned @modelcontextprotocol/sdk1.29.0 dist/esm/server/mcp.js:699–704 stamps taskSupport='forbidden'. mcp-app.ts:1019–1049 creates/advertises tasks only for the existing optional spike fixture; this lane adds none. SDK shared/protocol.js:361–367 checks global task capability before the installed handler; server/index.js:118–143 validates task-shaped results as CreateTaskResult, unlike ordinary CallToolResult. server/mcp.js:109–141 is not a reliable early per-tool forbidden-task refusal: callbacks/validation can run before outer result rejection. Therefore reject at D15 BEFORE inner, not merely after receiving an incompatible result.

For a valid JSON-RPC tools/call request naming one of the five ledger tools, any own params.task member, including null/malformed metadata, is unsupported. If it reaches D15, audit refusal using the existing content-free classification and throw a fresh McpError(ErrorCode.InvalidParams, "Invalid params") with NO data/cause/input/issue payload. Keep this task branch through audit/settlement failure; never let the ordinary receipt catch turn it into a success envelope. The SDK emits the correctly correlated jsonrpc/id/error:{code,message} envelope (shared/protocol.js:394–412); no result, task handle, fake cancelled task, receipt body or reflected argument key. Existing earlier fixed capability rejection when tasks are globally absent is already safe and stays unchanged. Do not pass through arbitrary SDK/Zod messages from malformed task metadata.

G23 wire proof: send real HTTP JSON-RPC requests so client-side capability/schema checks cannot intercept the test. Cover valid task metadata with otherwise valid mutation arguments and malformed task values/keys carrying private sentinels, with global tasks absent and with the EXISTING spike-capable fixture enabled (ledger remains forbidden). Assert a valid correlated protocol error, no result/task/receipt/data/private sentinel, zero ledger/provider/elicitation/task-creation effects, and unchanged ordinary-tool behavior. Then call the same ledger tool without task and observe the ordinary strict CallToolResult contract; malformed ordinary ledger arguments still get sanitized receipts. No new SDK patch, task store, feature flag, registered capability or test file.

Allocation and evidence: §24 allocated +300 within existing B1/C/D15/F3/G5/G10/G23 paths; A5 correction fits its existing owner-install allocation. §25 then maps H4 and adds J +80. §26 relocates that J refuse and names the B7/C split with no added path. Exact-SHA §23 remains mandatory. §24.2's 100-record/5-attempt budget stands. Both reviews of that snapshot are completed; later four findings are separately dispositioned, not runtime proof or clean sign-off. Only these canonical pages and generated STATUS/index are written; SQL/provider/SDK/transport/browser/J proof is explicitly pending.

25. Latest Astra clean; independent Opus — three separate dispositions

Astra returned clean against this revision's exact-SHA read-tree, H mapping and citations. Independent Opus then returned three verified findings. Record each separately. Neither reviewer is running. NOT CLEAN.

FindingDisposition, reason and owner
OpusS 1 — P2 deploy SHA mismatch after exact read-tree.ACCEPT. deploy.sh:690–696 derives DEPLOY_SHA from git --git-dir "$BARE_GIT_DIR" rev-parse main, not DEPLOY_TARGET_SHA (changelog only at :70–76). §23 already requires git read-tree --reset -u --no-sparse-checkout of the validated target_sha, preserving bare HEAD/refs. That leaves DEPLOY_DIR at A while main is B. A supported direct deploy/preprod/deploy.sh "$DEPLOY_DIR" then rsyncs A's tree into RELEASES_ROOT/B and stamps .deploy-release=B; later B push can skip staging (bluegreen.sh:604–609). Stay inside existing three J files: after every successful read-tree J2 writes a non-secret materialized-SHA marker beside .deploy-mode; J1 bg_init_config refuses when the marker exists and differs from DEPLOY_SHA; J3 after superseded abort proves direct deploy.sh refuses and never stages RELEASES_ROOT/B from A's tree. Controller/routes/services unchanged. Do not edit deploy.sh/rollback.sh. Preserve untracked runtime files.
OpusS 2 — P2 research report unmapped and href 404s.ACCEPT. Both canonical pages previously used href="../../research/2026-09-15-private-work-ledger-provider-preflight.md". The file exists at that repo path, currently untracked. docs/superpowers/serve.ts ROOT is docs/superpowers; TYPES has no .md, so those hrefs 404 on the docs site. KEEP the report as H4 existing path docs/research/2026-09-15-private-work-ledger-provider-preflight.md. Cite that repo path in prose, never as a live docs-site URL. Do not expand serve.ts or move the file under docs/superpowers. H 3→4, 101→102 paths, outputs 106→107.
OpusS 3 — P3 unqualified chassis elicitation citation.ACCEPT, citation only. In §24.1 the phrase chassis elicitation path (:390–455) resolved to mcp-app.ts's SSE timer. Write apps/mcp-server/src/chassis.ts:390–455 (elicitConsent). No new path or product change.

25.1 Materialized-SHA marker on the existing three J files

Historical §25 placement, superseded by §26: J2 writes the marker only after a successful admitted-SHA read-tree, as a sibling of existing .deploy-mode in DEPLOY_DIR. Contents are the admitted full commit ID, non-secret. Untracked runtime files including .deploy-mode, .env.local and this marker stay untracked; read-tree --reset -u must not delete them. §25 put the mismatch refuse in J1's bg_init_config (already called by deploy.sh:788 and rollback.sh:127). That placement deadlocks ordinary pushes (post-receive:110/:137 call it BEFORE materialization) and breaks rollback.sh (:54–58/:126–127/:192) plus sim reconcile-only entries. Live contract is §26: keep bg_init_config side-effect-free; refuse in bg_stage_release only.

25.2 Keep the provider report at its existing path

H4 is the already-written report at docs/research/2026-09-15-private-work-ledger-provider-preflight.md. Canonical pages cite that repo path in prose. It is not a docs-site URL, not relocated under docs/superpowers, and not served by expanding serve.ts. Historical H lines at this snapshot were 200: the report is complete and is not rewritten by this pass. Historical H 220 at this snapshot; one current H value is packet-table 240 in §15.5.

Allocation: H4 adds one existing path (H 3→4, +0 lines). J stays three files; +80 prospective lines (J1 marker refuse 20, J2 marker write 20, J3 post-abort direct-deploy refuse 40) for J 190/60/280 =530. Historical §15.5 at this snapshot was 102 paths /16,080 lines with 107 expected outputs; those totals are not current. Historical 106/17,440 is not current; §15.5 is sole current authority. Chassis citation qualification is documentation-only. §26 relocates the J1 refuse and does not change these historical totals. No deploy.sh/rollback.sh/serve.ts edit, no product/git/runtime/DB/schema work. Settled S/A, MCP IDs/versions/structured receipts/authenticated links, web full evidence, product-chat marker-private and memory-excluded, exact consent-generation on the existing 1.6.23 patch, exact-SHA read-tree, helper-owned A5 transaction, and null org status denying ledger remain.

26. Independent Astra+Opus after §25 — four separate dispositions

Four findings after the §25 snapshot. Record each separately. Findings 1 and 2 ACCEPT the same root cause. Neither reviewer is running. NOT CLEAN.

FindingDisposition, reason and owner
1 — marker≠DEPLOY_SHA refuse in bg_init_config deadlocks ordinary pushes.ACCEPT, same root cause as finding 2. post-receive:110 sets DEPLOY_SHA=target then :137 bg_init_config BEFORE materialization. Marker A ≠ incoming B on every ordinary push. Putting the refuse in bg_init_config deadlocks those pushes and also breaks rollback.sh (DEPLOY_SHA from prior record/argument at :54–58, bg_init_config at :126–127, target reset only at :192) and sim reconcile-only entries. KEEP bg_init_config a side-effect-free assignment as today. MOVE the marker≠DEPLOY_SHA refuse into bg_stage_release (bluegreen.sh:602; rsync of DEPLOY_DIR at :637–646; BG_RELEASE_DIR="${RELEASES_ROOT}/${DEPLOY_SHA}" at :53) — the only path that turns DEPLOY_DIR into a release. Stay in existing three J files. No deploy.sh/rollback.sh edit. Supersedes §25.1's refuse placement.
2 — direct deploy after superseded abort must still refuse A's tree as RELEASES_ROOT/B.ACCEPT, same root cause as finding 1. Direct deploy.sh "$DEPLOY_DIR" after superseded abort (marker A, main B) must refuse and never stage RELEASES_ROOT/B from A's tree. Ordinary later compatible push must still read-tree, rewrite the marker and deploy. Rollbacks reuse RELEASES_ROOT/<sha> and must not consult the checkout marker. J3: (a) superseded abort then direct deploy.sh refuses; (b) marker present and stale, ordinary push still succeeds; (c) retained rollback/recovery/skip-swap still work. J2 is the only marker writer. Operator recovery: a manual git checkout -f main in DEPLOY_DIR can leave a stale marker that then blocks the next staging deploy until J2 rewrites it on a successful admitted materialization or the operator re-pushes so J2 rewrites it.
3 — app_role cannot SELECT clio_activity; sync_role cannot read ledger tables.ACCEPT role split, no new grants. provision-roles.ts:829–830 grants clio_activity to sync_role only; ledger tables deny sync_role. B7 (sync_role) owns canonical-key selection, dedup, population size/overflow and recorded-time visible-minutes/unknown-count aggregates, returning figures plus ≤ 200 keys. C (app_role) joins those keys against work_episode_evidence via bounded VALUES and owns only the saved-work aggregate. Narrow "never sum pages" to hydration pages so C may combine the bounded server-computed result in process. No app_role SELECT on clio_activity, no sync_role ledger grants. Named in spec §8.1 and plan B7+C. No new path.
4 — raise the attachment budget or drop attachment metadata from v1.REJECT as re-litigation of the explicit labeled budget in §24.2. Keep the 100-record / 5-attempt cap and attachments_unchecked as the honest tail for unscheduled messages. Do not raise the budget or drop attachment metadata from v1. No user question.

26.1 Relocated marker refuse; operator recovery

J2 remains the only marker writer: after every successful admitted-SHA read-tree, write the non-secret full commit ID beside .deploy-mode. J2 writes that marker during the deployment owner's initial handoff install (same barrier as the durable minimum). Keep bg_init_config a side-effect-free assignment of roots/ports/stage paths, including BG_RELEASE_DIR="${RELEASES_ROOT}/${DEPLOY_SHA}" at bluegreen.sh:53. The mismatch refuse lives only in bg_stage_release, AFTER the green-lit reuse early-return (:604–609) and immediately after the existing [ -e "$BG_RELEASE_DIR" ] refuse (:610–612), before set_phase :615 (and thus before rsync of DEPLOY_DIR :637–646 into a new RELEASES_ROOT/$DEPLOY_SHA). With active floor minimumVersion ≥ 1, marker REQUIRED on the staging path: absence → bg_stage_release refuse, refresh skip; green-lit reuse unaffected; J3 absent-marker green-lit-reuse succeeds. No active floor → absence dormant. J3(e) sets the floor. Missing marker does not invent a SHA. Reuse of an existing green-lit release with a stale checkout marker still succeeds. (Superseded by §46 Opus 2: delete the reuse discriminator; no BG_RELEASE_REUSED. bg_refresh_runtime_scripts copies helpers from $BG_RELEASE_DIR unconditionally; both reuse and promote leave $BG_RELEASE_DIR green-lit. bg_refresh_post_receive_hook marker skip stays load-bearing, untouched.) J1 edit at bluegreen.sh:727–738: source="${BG_RELEASE_DIR}/deploy/preprod/bin/${name}.sh". Price inside J1's 190. Do not phrase $BG_RELEASE_DIR as current fact. Hook skip (return 0, no copy) when the marker ≠ DEPLOY_SHA, or when the marker is absent and the active floor minimumVersion ≥ 1. Do not abort. bg_stage_release owns the only refusal. J3: hook bytes unchanged; bg_reconcile still runs. J3(a): no RELEASES_ROOT/<B>, no .deploy-release=B, no swap, hook/runtime-script bytes unchanged. Rollbacks reuse an existing RELEASES_ROOT/<sha> and must not consult the checkout marker. Sim reconcile-only entries call bg_init_config without staging and must keep working.

Operator recovery: J2 is the only marker writer. A manual git checkout -f main in DEPLOY_DIR can leave a stale marker that then blocks the next staging deploy until J2 rewrites it on a successful admitted materialization or the operator re-pushes so J2 rewrites it.

26.2 B7/C role split, no new grants

B7 runs as sync_role and returns recorded-time figures plus ≤ 200 canonical keys. C runs as app_role, joins those keys with bounded VALUES against work_episode_evidence, and owns only the saved-work aggregate. C may combine those bounded server-computed results in process; it must not sum visible hydrateWorkEpisodes pages. No grant changes. §27 refines B7's internal result to a bounded row projection and splits saved-work into an independent episode query; this role split, no-new-grants floor, and §26.1 staging-refuse remain.

Allocation: §26 invents no path and does not change packet lines. J1's existing 20 refuse lines relocate from bg_init_config to bg_stage_release; J3's existing 40 covers (a)(b)(c) of the same marker contract. B7/C stay inside then-current 20/2,200 and 3/1,470. Finding 4 changes nothing in §24.2. Current map is §15.5 via §27. No deploy.sh/rollback.sh/serve.ts/product/git/runtime/DB/schema work.

27. Independent Astra three + Opus eight after §26 — separate dispositions

Eleven findings after the §26 snapshot. Record Astra's three and Opus's eight separately. Preserve §26 staging-refuse, settled S/A, MCP IDs-only, exact generation, query-param /tasks/chats/new?review=work, and the durable assistant message marker. Neither reviewer is running. NOT CLEAN.

27.1 Astra — three ACCEPT dispositions

FindingDisposition, reason and owner
Astra 1 — B7 internal result is keys+totals only.ACCEPT. B7 (sync_role) internal result is a bounded row projection, not keys+totals only: canonical key, date/matter, visible-or-unknown minutes, redacted flag, authorized note/URL, row epoch, plus source population fingerprint (count + epoch/watermark). C joins that projection to work_episode_evidence for association/exclusion only. Unlinked TimeEntries still appear with empty relation. No app_role SELECT on clio_activity.
Astra 2 — overflow via a 201st key or five full pages.ACCEPT. Overflow is the named query countEligibleRecordedTimeOverflow, a separate COUNT/EXISTS over the eligible population, not a 201st materialized key and not “5 full pages”. Keep LIMIT 200 for the projection. Name that query in B7 and G6. 200 complete versus 201 too-many must both be expressible.
Astra 3 — saved-work derived from Clio keys.ACCEPT. Saved-work is a separate app_role query over work_episode by stored work_date/matter, independent of Clio keys. VALUES join only decorates B7's recorded-time projection. Mail-only episodes count in saved-work.

27.2 Opus — eight separate dispositions

FindingDisposition, reason and owner
Opus 1 — new connectors column for credential revision.CLARIFY, not a new connectors column. Credential revision remains the named lossless field inside the selected connector_user_tokens binding JSON, bumped under the selected-row lock by B9/B11/B12 and legacy Microsoft write. Distinct from enrollment_generation. Enumerate writers and compare predicate. No extra A column.
Opus 2 — missing named indexes.ACCEPT named indexes in the same later A unit: clio_activity (organization_id, user_source_id, date); work_episode (organization_id, owner_user_id, work_date) partial lifecycle=active; evidence index needed for the bounded key join beyond the existing unique. Indexes are schema in existing A files, not new paths. No db:generate now.
Opus 3 — timezone vs stored work_date; null Clio date.ACCEPT. Saved-work compares stored work_date to the request's local date bounds; episode timezone is display metadata. clio_activity.date IS NULL is an explicit coverage-exclusion that forbids the complete-figure claim.
Opus 4 — digest loader omits the raw-marker predicate.ACCEPT. Apply D10's shared raw-marker eligibility predicate in existing apps/worker-context/src/thread-loader.ts (D16); add the assertion to existing apps/worker-context/src/digest.integration.test.ts (G24). Admitted threads produce no new digest rows. No taint framework.
Opus 5 — add a new review pathname.REJECT. Query-param /tasks/chats/new?review=work is the settled mailbox-independent entry so Home and back-to-chat keep one surface. Do not add a new pathname.
Opus 6 — replace admission with a chat_thread column.REJECT. Admission remains a durable assistant marker, same permanence class as data-netdocs-taint. Do not replace with a chat_thread column.
Opus 7 — v1 looks like complete personal work.ACCEPT one §1/coverage sentence: v1 excludes meetings and North document drafts; F3 per-source panel labels that absence. Not a complete personal-work claim.
Opus 8 — missing compatibility state after activation.ACCEPT post-activation: missing compatibility state file refuses, it does not silently return to dormant-permissive. Pre-activation absence remains dormant. Name the restore obligation in H. J3 asserts the missing-state post-activation case.

27.3 Two existing worker-context paths

  1. apps/worker-context/src/thread-loader.ts existing D16: the eligibility predicate is EXISTS over chat_message parts inside findDigestThread's existing withRlsTransaction. Skip already admitted threads; produce no digest input from them. Checkpoint/graph.getState does not contain the marker. Do not inspect raw messages or infer eligibility from a filtered transcript for this path. Cite D10's exported predicate. No taint/serializer framework or new digest rows for admitted threads. Pre-model window and context_digest_attempt rows are deliberately out of scope (ids/spend only). Do not add a D13-style recheck.
  2. apps/worker-context/src/digest.integration.test.ts existing G24: an admitted thread (exact assistant marker) produces no new context_digest rows; unmarked positive control still writes. G24: concurrent C admission committed before the upsert write → digestRowCount unchanged (no new row, no conflict-update of existing toolCallId). digestRowCount alone is insufficient: the admitted thread's existing (thread_id, tool_call_id) digest/content_sha256/created_at stay unchanged. G24 two-thread mixed threadId throws, zero rows written, not a mixed write. If admission commits after the write, later runs skip; that is the existing memory-first-may-finish rule. Do not FOR SHARE/FOR UPDATE chat_thread from digest (that blocks C's FOR UPDATE and invents a gap G24 cannot observe). Upsert asserts uniform threadId beside organizationId; mixed threadId throws, zero rows written. Use the existing live-PG fixture; no new test file. Unified all-guards assertion hosted here (G24 = apps/worker-context/src/digest.integration.test.ts): D7/D16/D17/D21 all refuse the same seeded marker row. Do not put it in G9. D20's arm lives in G26. G24 skip idiom: runIntegration/describeIntegration plus REQUIRE_DB_INTEGRATION=1 && !RUN throw so the throw bites.

Allocation: §27 added two existing paths (D 15→16, G 19→20) and 480 prospective lines. §28 supersedes the then-current 104/16,560 map. Indexes stay in A. J 190/60/320 =570. No deploy.sh/rollback.sh/serve.ts/product/git/runtime/DB/schema work. Settled S/A, MCP IDs-only, exact generation, query-param review URL and message marker remain.

28. Independent Opus ten + Astra two after §27 — separate dispositions

Twelve findings after the §27 snapshot. Record Opus's ten and Astra's two separately. Preserve §26 staging-refuse, query-param /tasks/chats/new?review=work, durable assistant message marker, and settled S/A/MCP IDs-only. Neither reviewer is running. NOT CLEAN.

28.1 Opus — ten ACCEPT dispositions

FindingDisposition, reason and owner
Opus 1 — thread-loader alone is insufficient.ACCEPT. Map existing packages/agent-runtime/src/context/digest.ts (D17). Re-read the raw marker after the last remote await and immediately before repository.upsert; hold that read through commit (superseded by §30). G24 asserts admission between load and upsert produces zero new rows. thread-loader alone is insufficient.
Opus 2 — inspect-raw-messages wording for findDigestThread.ACCEPT. The predicate is EXISTS over chat_message parts inside findDigestThread's existing withRlsTransaction. Checkpoint/graph.getState does not contain the marker. Drop “inspect raw messages” wording for this path.
Opus 3 — worker-context/digest missing from admission/rollback floor.ACCEPT. Add worker-context/digest (D16/D17) to spec §7 admission floor and plan §8 activation + rollback-compatible-build list, same class as D7/D9/memory.
Opus 4 — B7 projection/overflow/fingerprint not one snapshot.ACCEPT. B7 projection + overflow + fingerprint run in one REPEATABLE READ transaction on the B7 connection. Carry that snapshot fingerprint in the sealed cursor; refuse the full figure on fingerprint difference. G5 interleaves a feeder commit between reads; figure is null/partial, never a complete 200.
Opus 5 — B7 missing ingest-connector set.ACCEPT. B7 predicate includes the ingest-connector set resolved by verified origin+account (superseded by §32). Index is clio_activity_org_connector_user_date_idx on (organization_id, connector_id, user_source_id, date). G5 two connectors same user_source_id → zero cross-connector disclosure.
Opus 6 — Number() on hours numeric string.ACCEPT. Never Number() the numeric string. Aggregate unrounded exact decimal hours, round once at the named minute figure. G5/G6 include a non-integral fixture whose per-entry vs aggregate rounding differ.
Opus 7 — overflow proof on the wrong process.ACCEPT. Overflow/200-vs-201/narrowed boundary proof lives in G5 (connectors-api, real sync_role). G6 only C composition against a real connectors-api fixture; name the process.
Opus 8 — stale J 530 and missing-state absent from spec §3.4.ACCEPT. Spec J 190/60/320 =570 in both stale places; add post-activation missing-state refuse + restore obligation to spec §3.4. Astra 1 then names how missing json is judged.
Opus 9 — unnamed evidence index.ACCEPT. Name work_episode_evidence_org_owner_kind_key_idx on (organization_id, owner_user_id, source_kind, source_key); the join is owner-scoped.
Opus 10 — work_date nullability and timezone coverage.ACCEPT work_date NOT NULL in A1. Coverage label for episodes excluded solely by timezone difference between stored episode timezone and request; do not change the stored-work_date predicate.

28.2 Astra — two ACCEPT dispositions

FindingDisposition, reason and owner
Astra 1 — missing json unimplementable as post-vs-pre by the json itself.ACCEPT. J1: if the live controller's static declaration is ≥ 1, missing compatibility json refuses; if the live controller has no declaration or declaration 0, missing json remains dormant. J3 deletes only the json after a declaring controller is live and still refuses. No second file required. Stay in 3 J files.
Astra 2 — null-date rows invisible to overflow.ACCEPT. Named coverage EXISTS countEligibleNullDateCoverage of eligible-actor TimeEntries with date IS NULL, same REPEATABLE READ snapshot as projection/overflow, forces incomplete. Overflow over dated-in-range rows does not see NULLs. G5/G6 seed a null-date row next to the 200 complete control.

28.3 Existing digest.ts path

  1. packages/agent-runtime/src/context/digest.ts existing D17: inside ContextDigestRepository.upsert's withRlsTransaction, mixed threadId throws before the write, zero rows written. The uniform-threadId construction is INSERT … SELECT … WHERE NOT EXISTS(marker) … ON CONFLICT DO UPDATE, correlated on the asserted single threadId — ONE statement that skips insert AND ON CONFLICT update when the admission marker exists. Do NOT FOR SHARE/FOR UPDATE chat_thread from digest. Drop marker-row lock wording. Upsert asserts uniform threadId beside organizationId. runContextDigestJob cannot hold a lock across repository.upsert; keep job-body cancellation. Admitted threads write zero new context_digest rows. G24: digestRowCount alone is insufficient; admitted thread's existing (thread_id, tool_call_id) digest/content_sha256/created_at unchanged; G24 two-thread mixed threadId throws, zero rows written, not a mixed write. If admission commits after the write, later runs skip (memory-first-may-finish). thread-loader (D16) alone is insufficient; digest.ts is already mapped. §30 Astra 1 supersedes the §29 FOR SHARE / mixed-threadId INSERT…SELECT ALTERNATIVE; the uniform-threadId D17 construction remains INSERT … SELECT … WHERE NOT EXISTS(marker) … ON CONFLICT DO UPDATE. Pre-model window and context_digest_attempt rows are deliberately out of scope (ids/spend only). Cite D10's exported predicate. Do not add a D13-style recheck.

Allocation: §28 adds one existing path (D 16→17) and 540 prospective lines as named in then-current §15.5. J stays 190/60/320 =570. §29 supersedes the then-current 105/17,100 map. No deploy.sh/rollback.sh/serve.ts/product/git/runtime/DB/schema work. Settled S/A, MCP IDs-only, exact generation, query-param review URL and message marker remain.

29. Independent Astra+Opus after §28 — separate dispositions

Eight findings after the §28 snapshot. Record Astra+Opus 1–7 and Astra 3 separately. Preserve §26 staging-refuse after green-lit reuse, query-param /tasks/chats/new?review=work, durable assistant message marker, and settled S/A/MCP IDs-only. Neither reviewer is running. NOT CLEAN.

29.1 Astra+Opus — seven ACCEPT dispositions

FindingDisposition, reason and owner
1 — digest guard cannot be held across repository.upsert.ACCEPT. The digest guard lives inside ContextDigestRepository.upsert, same withRlsTransaction as the insert: SELECT … FOR SHARE on chat_thread / marker or INSERT…SELECT WHERE NOT EXISTS (superseded by §30). runContextDigestJob cannot hold a lock across repository.upsert. Keep job-body cancellation. G24 admits between guard read and insert, not only load vs upsert. Map digest.ts already.
2 — B7 REPEATABLE READ needs the existing sync wrapper.ACCEPT. Map existing packages/db/src/sync-tx.ts (B22). Add optional isolationLevel forwarded to drizzle db.transaction so BEGIN ISOLATION LEVEL REPEATABLE READ precedes current_user. Default unchanged; existing callers stay READ COMMITTED. Do not drop the wrapper. Count the path in §15.5.
3 — G5 same-request “never a complete 200” is the wrong RR property.ACCEPT. Split G5: (a) same-request feeder interleave → projection/overflow/null-date/fingerprint all agree (RR property; READ COMMITTED must fail). (b) cross-request sealed cursor after feeder → fingerprint differs, full figure refused. Drop “never a complete 200” from same-request.
4 — null-date EXISTS is unbounded.ACCEPT. Null-date EXISTS is scoped to actor + ingest-connector + selected matter, and bounded by last_seen_at overlapping the window (superseded by §30). Out-of-overlap null-date rows are a labelled coverage count, not hard incomplete. Bound stated in spec §3.2.
5 — null-date fixture contaminates 199/200/201/narrowed controls.ACCEPT. Null-date fixture is disjoint from 199/200/201/narrowed controls. Boundary fixtures contain zero null-date eligible rows.
6 — stage-release refuse can run before green-lit reuse.ACCEPT. Stage-release refuse is AFTER green-lit reuse early-return (:604–609) and BEFORE rsync (:637). J3: reuse of existing green-lit release with stale marker still succeeds.
7 — leftover spec 530 / 190/60/280.ACCEPT. Replace remaining spec 530 / 190/60/280 with 190/60/320 =570.

29.2 Astra 3 — lossless hours and round-once aggregate

FindingDisposition, reason and owner
Astra 3 — named figure must not sum per-row display minutes.ACCEPT. B7 returns lossless hours (numeric string) per row AND a separate round-once aggregate minutes from the same REPEATABLE READ snapshot. C uses that aggregate for the named figure; it does not sum per-row display minutes. G5 asserts both on the B7 result.

29.3 Existing sync-tx.ts path

  1. packages/db/src/sync-tx.ts existing B22: keep withSyncTenantTransaction. Optional isolationLevel is forwarded to drizzle db.transaction so BEGIN ISOLATION LEVEL REPEATABLE READ precedes current_user. Default unchanged; existing callers stay READ COMMITTED. Do not drop the wrapper. When isolationLevel is requested, SELECT current_setting('transaction_isolation') must equal repeatable read; throw otherwise. G5 (a) nested call throws.

Allocation: §29 adds one existing path (B 20→21) and 340 prospective lines as named in then-current §15.5. J stays 190/60/320 =570. §30 prices D17 one-statement and last_seen_at drop (+0/80). Then-current map 106 paths /17,440 lines is historical; §15.5 is sole current authority. No deploy.sh/rollback.sh/serve.ts/product/git/runtime/DB/schema work. Settled S/A, MCP IDs-only, exact generation, query-param review URL and message marker remain.

30. Independent Astra+Opus after §29 — separate dispositions

Eleven findings after the §29 snapshot. Record Astra 1–3 and Opus 1–8 separately. Pick ONE digest recipe: ACCEPT Astra 1, REJECT Opus 2. Preserve §26 staging-refuse after green-lit reuse, query-param /tasks/chats/new?review=work, durable assistant message marker, settled S/A, MCP IDs-only, RR commit-before-provider, and green-lit reuse then refuse. Neither reviewer is running. NOT CLEAN.

30.1 Astra — three dispositions

FindingDisposition, reason and owner
Astra 1 — digest recipe versus marker-row lock.ACCEPT. Inside ContextDigestRepository.upsert's withRlsTransaction, ONE statement that skips insert AND ON CONFLICT update when the admission marker exists. Do NOT FOR SHARE/FOR UPDATE chat_thread from digest (that blocks C's FOR UPDATE and invents a gap G24 cannot observe). Drop marker-row lock wording. Upsert asserts uniform threadId beside organizationId; mixed threadId throws, zero rows written. G24: concurrent C admission committed before that write → digestRowCount unchanged (no new row, no conflict-update of existing toolCallId). If admission commits after the write, later runs skip; that is the existing memory-first-may-finish rule. Keep job-body cancellation. REJECT Opus 2's lock recipe.
Astra 2 — last_seen_at as a work-window predicate.ACCEPT DROP. Drop last_seen_at as a work-window predicate. Any currently eligible visible TimeEntry with date IS NULL in the actor + ingest-connector set (and selected matter only when the request selected a matter) is hard incomplete. Narrowing matter can clear it; narrowing dates cannot. Out-of-connector/actor rows do not count. Supersedes §29 finding 4's overlap bound.
Astra 3 — matter predicate on week-wide eligible set.ACCEPT. Matter predicate only when selected; week-wide uses the unfiltered eligible set.

30.2 Opus — eight separate dispositions

FindingDisposition, reason and owner
Opus 1 — visible NULL user_source_id read as “not mine”.ACCEPT. Visible NULL user_source_id is labelled incomplete coverage, never evidence of “not mine”. Same eligible set as null-date, optional matter. Disjoint G5 fixture.
Opus 2 — digest FOR SHARE / FOR UPDATE chat_thread.REJECT. Same finding as Astra 1's rejected alternative. Digest must not lock chat_thread; that blocks C's FOR UPDATE and invents a gap G24 cannot observe. Astra 1's one-statement skip is the recipe.
Opus 3 — named figure still published after withheld rows/hours.ACCEPT. Validation-time whole-entry denial or hours redaction refuses the named figure (partial reason). An already-known quantity_redacted ingestion row contributes zero minutes and one unknown-duration count and does not refuse. Do not publish B7 snapshot aggregate as complete when validations withheld rows/hours. Split the G5/G6 fixture.
Opus 4 — provider work under the B7 REPEATABLE READ transaction.ACCEPT. The REPEATABLE READ transaction commits after projection/overflow/null-date/fingerprint and before any exact-entry validation call. Sealed fingerprint, not an open transaction, revalidates later. Same as no provider under locks. (Refined: no provider work inside the open transaction; identity GETs stay in the 20-second source phase, not C prepare.)
Opus 5 — UTC range used as TimeEntry date predicate.ACCEPT. B7 compares clio_activity.date to inclusive local date bounds; derived UTC range is for sentDateTime only. G5/G6 boundary-day non-UTC fixture.
Opus 6 — J3(a) over-claims hook/reconcile silence.ACCEPT. J3(a) claims only no RELEASES_ROOT/<B> staging, no .deploy-release=B, no swap. Hook refresh and reconcile may run from admitted A first. (Superseded by §35: inside both refresh functions, skip return 0, no copy; do not abort; bg_stage_release owns the only refusal; hook/runtime-script bytes unchanged; bg_reconcile still runs.)
Opus 7 — two owners for overflow versus composed figures.ACCEPT. G5 owns overflow query and 200-vs-201 under sync_role; G6/G10 own composed C/UI figures. One owner per case.
Opus 8 — stale 105/17,100 and 102/16,080 still read as current.ACCEPT. Mark stale 105/17,100 and 102/16,080 plan lines historical; then-current 106/17,440 is historical; §15.5 is sole current authority.

Allocation: §30 +0/80 (D17 one-statement 40 + last_seen_at drop 40). J stays 190/60/320 =570. Then-current after §30 is 106/17,520; then-current 106/17,940 is historical; §15.5 is sole current authority. Historical 105/17,100 and 102/16,080 plan lines are not current. No deploy.sh/rollback.sh/serve.ts/product/git/runtime/DB/schema work. Settled S/A, MCP IDs-only, exact generation, query-param review URL and message marker remain.

31. Astra CLEAN after §30; independent Opus — eight separate dispositions

Astra returned CLEAN against the §30 integrated revision. Independent Opus then returned eight verified findings. Record each separately. Preserve the settled one-statement digest (no thread lock), query-param /tasks/chats/new?review=work, durable assistant message marker, and settled S/A/MCP IDs-only. Opus has not re-run. NOT CLEAN.

FindingDisposition, reason and owner
Opus 1 — null-date/null-user reuse the “narrow dates” string.ACCEPT. Null-date and null-user coverage each get their own partial-reason enum and guidance. Null-date/null-user must not use the “narrow dates” string. Null-date guidance is matter-filter only; null-user guidance is awaiting feeder. G10: “narrow dates” string absent on a null-date refusal.
Opus 2 — null-user predicate was actor-copied from null-date.ACCEPT. Write the null-user predicate literally: ingest-connector set ∧ observation_state='visible' ∧ TimeEntry ∧ user_source_id IS NULL ∧ (matter only when selected). No actor predicate, no date predicate (date window added in §33). G5 disjoint fixture includes one row NULL on both date and user_source_id. (Superseded by §39 Opus 2: both-NULL row is labelled, not refuse.)
Opus 3 — ingested quantity_redacted treated as validation-time refuse.ACCEPT. Split: validation-time denial/redaction refuses the named figure; an already-known quantity_redacted ingestion row contributes zero minutes and one unknown-duration count and does not refuse. Split the G5/G6 fixture.
Opus 4 — stage-release refuse sits before set_phase.ACCEPT. Stage-release refuse immediately after existing [ -e "$BG_RELEASE_DIR" ] refuse (:610–612) and before set_phase :615. J3(a) asserts no phase change and no $BG_STAGE_DIR on refusal.
Opus 5 — marker absence treated as permanently dormant.ACCEPT. Absence is dormant only until the first successful J2 materialization. J2 writes the marker during the deployment owner's initial handoff install (same barrier as durable minimum). Replace “or an operator deletes the marker” with “or the operator re-pushes so J2 rewrites it”. J3 absent-marker direct-deploy case after first materialization refuses. (Superseded by §39 Opus 5: delete “after first materialization” as live predicate; floor ≥ 1 ⇒ marker required; no floor ⇒ absence dormant; J3(e) sets the floor.) (Superseded by §36: with active floor minimumVersion ≥ 1, marker REQUIRED — absence → bg_stage_release refuse, refresh skip; no active floor → absence dormant; J3(e) sets the floor.) (Superseded by §36: with active floor minimumVersion ≥ 1, marker REQUIRED — absence → bg_stage_release refuse, refresh skip; no active floor → absence dormant; J3(e) sets the floor.) (Superseded by §36: with active floor minimumVersion ≥ 1, marker REQUIRED — absence → bg_stage_release refuse, refresh skip; no active floor → absence dormant; J3(e) sets the floor.)
Opus 6 — §28/§29 still read as live digest/window recipes.ACCEPT. Mark superseded live recipes in §28/§29 (hold-through-commit, FOR SHARE chat_thread, last_seen_at) with “superseded by §30” in place, including spec:374.
Opus 7 — leftover J 350 and H 200 read as current.ACCEPT. Mark plan:556/:576 J “350 lines” and plan:788 H 200 as historical; then-current J 570 is historical; current J is 190/60/360 =610; historical H 220; one current H value is packet-table 240 in §15.5.
Opus 8 — digest upsert can mix threadId across a batch.ACCEPT. Digest upsert asserts uniform threadId beside organizationId; mixed threadId throws, zero rows written. INSERT…SELECT deleted as the alternative. G24 two-thread mixed threadId is throw, not mixed write.

Allocation: §31 +0/180 (B7 predicates 40, G5 fixtures 40, G6 fixtures 40, G10 fixtures 40, guidance 20). J stays 190/60/320 =570. Then-current after §31 is 106/17,700; then-current 106/17,940 is historical; §15.5 is sole current authority. No deploy.sh/rollback.sh/serve.ts/product/git/runtime/DB/schema work. Settled S/A, MCP IDs-only, exact generation, query-param review URL and message marker remain.

32. Independent Astra 1 + Opus 1–6 after §31 — separate dispositions

Seven findings after the §31 snapshot. Record Astra 1 and Opus 1–6 separately. Preserve the settled one-statement digest (no thread lock), query-param /tasks/chats/new?review=work, durable assistant message marker, and settled S/A/MCP IDs-only. Do not add firm who_am_i on ingest credentials. Neither reviewer is running. NOT CLEAN.

FindingDisposition, reason and owner
Astra 1 — fingerprint omits null-date/null-user flags.ACCEPT. Seal countEligibleNullDateCoverage and countEligibleNullUserCoverage (or a coverage digest) with the fingerprint. Cursor reuse refuses the full figure if the fingerprint OR those flags change. G5 (b): dated overflow count unchanged; a null-date insert → figure refused. Null-user/both-NULL only labelled flag/fingerprint, do not refuse. (Superseded in part by §40 Astra 2.)
Opus 1 — G24 digestRowCount alone is insufficient.ACCEPT. G24 observables: the admitted thread's existing (thread_id, tool_call_id) digest/content_sha256/created_at stay unchanged; two-thread batch: the unmarked thread's row IS written, the admitted thread's is not (superseded by §33: G24 two-thread mixed threadId is throw, zero rows written). digestRowCount alone is insufficient.
Opus 2 — null-user is not hard incomplete.ACCEPT. Null-user is hard incomplete, same sentence shape as null-date: any currently eligible visible TimeEntry with user_source_id IS NULL in the ingest-connector set (and selected matter only when selected) is hard incomplete. G5 and G6 assert the named figure is refused when countEligibleNullUserCoverage > 0. (Superseded by §37 Opus 5: visible redacted-user is labelled incomplete, not org-wide refuse.)
Opus 3 — ingest origin+account for personal ledger.ACCEPT drop ingest origin+account. Eligible ingest set is type='clio' AND scope='organization' AND status='authorized' (revoked leftovers out); do not decrypt/refresh firm ingest or call who_am_i for personal ledger. G5 two connectors same user_source_id isolate on connector_id. Ingest identity unverified is not a path. (Ingest-connector set tightened in §33 to type/scope/status=authorized; revoked leftovers out.)
Opus 4 — §30/§31 claimed +0/0.ACCEPT. Price §30/§31 in §2 delta and §15.5 line list (B7 predicates, G5/G6/G10 fixtures, guidance). Do not claim +0/0.
Opus 5 — accidental re-theme of another lane.ACCEPT. Restore the two outlook-automatic-recovery files from HEAD. One sentence in §1: generated STATUS/index only; do not ship another lane's pages.
Opus 6 — uniform-threadId missing from spec §7 floor.ACCEPT. Add uniform-threadId / two-thread batch to spec §7 floor paragraph beside the one-statement guard.

Allocation: §32 +0/240 (fingerprint seal 40, G5 (b) overflow-unchanged + null-date insert + null-user insert refuse 40 (Superseded by §40 Astra 2: null-user/both-NULL labelled only; G5(b) keeps overflow-unchanged + null-date refuse), G24 admitted (thread_id, tool_call_id) digest/content_sha256/created_at unchanged 40, G24 two-thread unmarked written/admitted not 40, null-user hard incomplete 40, ingest set owned org Clio rows visible to sync_role 40). J stays 190/60/320 =570. Then-current map 106 paths /17,940 lines; current is §15.5. No deploy.sh/rollback.sh/serve.ts/product/git/runtime/DB/schema work. Settled S/A, MCP IDs-only, exact generation, query-param review URL and message marker remain.

33. Independent Astra 1 + Opus 2–7 after §32 — separate dispositions

Seven findings after the §32 snapshot. Record Astra 1 and Opus 2–7 separately. Preserve the settled one-statement digest (no thread lock), query-param /tasks/chats/new?review=work, durable assistant message marker, and settled S/A/MCP IDs-only. Do not add firm who_am_i on ingest credentials. Neither reviewer is running. NOT CLEAN.

FindingDisposition, reason and owner
Astra 1 — ingest-connector includes revoked leftovers.ACCEPT. Ingest-connector set is type='clio' AND scope='organization' AND status='authorized'. Revoked leftovers out. Still no decrypt/refresh/who_am_i. G5 two-connector isolation includes revoked leftover vs live authorized.
Opus 2 — null-user date window.ACCEPT. Null-user keeps no actor predicate; add window date BETWEEN bounds OR date IS NULL so NULL-date+NULL-user stay hard incomplete. (Superseded by §39 Opus 2: both-NULL row is labelled, not refuse.) G5 out-of-window null-user row must not refuse.
Opus 3 — spec §8.1 cursor omits coverage flags.ACCEPT. Spec §8.1 cursor seal includes null-date/null-user flags (or coverage digest), same wording as §3.2.
Opus 4 — mixed threadId can write.ACCEPT pick assertion: upsert throws on mixed threadId, zero rows written. G24 two-thread case is throw, not mixed write. Delete INSERT…SELECT as the mixed-threadId ALTERNATIVE only. Uniform-threadId D17 construction remains INSERT … SELECT … WHERE NOT EXISTS(marker) … ON CONFLICT DO UPDATE, correlated on the asserted single threadId; mixed threadId still throws before that write. (§34 scopes this deletion.)
Opus 5 — plan:848 origin+account still live.ACCEPT. Mark plan:848 origin+account recipe superseded by §32 in place.
Opus 6 — leftover 106/17,440 read as current.ACCEPT. Mark leftover 106/17,440 sentences historical; §15.5 is sole current authority.
Opus 7 — isolationLevel not verified.ACCEPT. When isolationLevel requested, SELECT current_setting('transaction_isolation') must equal repeatable read; throw otherwise. G5 (a) nested call throws.

Allocation: §33 +0/200 (authorized ingest-connector type/scope/status + G5 revoked leftover vs live authorized 40, null-user window date BETWEEN OR NULL + G5 out-of-window must not refuse 40, spec §8.1 cursor seal same wording as §3.2 40, upsert mixed-threadId throw / G24 throw not mixed write / delete INSERT…SELECT as mixed-threadId ALTERNATIVE only 40, isolationLevel current_setting + G5 (a) nested throw 40). Opus 5–6 are in-place historical marks. Then-current J 190/60/320 =570 and 106/18,140 are historical; current is §15.5 via §34. No deploy.sh/rollback.sh/serve.ts/product/git/runtime/DB/schema work. Settled S/A, MCP IDs-only, exact generation, query-param review URL and message marker remain.

34. Independent review after §33 — seven separate ACCEPT dispositions

Seven findings after the §33 snapshot. Record each separately. Preserve the settled one-statement digest (no chat_thread lock), query-param /tasks/chats/new?review=work, durable assistant message marker, settled S/A, MCP IDs-only, no firm who_am_i, and mixed-threadId throw. Neither reviewer is running. NOT CLEAN.

FindingDisposition, reason and owner
1 — refresh hook/scripts ungated vs marker.ACCEPT. Gate bg_refresh_post_receive_hook and bg_refresh_runtime_scripts on the same marker predicate as bg_stage_release: skip/refuse when the marker is absent after first materialization or ≠ DEPLOY_SHA. (Superseded by §39 Opus 5: delete “after first materialization” as live predicate; floor ≥ 1 ⇒ marker required; no floor ⇒ absence dormant; J3(e) sets the floor.) Stay in 3 J files. J3: hook bytes unchanged when staging refuses after superseded abort AND on stale-marker green-lit reuse (reuse still succeeds for staging; the hook is not rewritten from the unchecked tree). (Superseded by §35: skip return 0, no copy; do not abort; bg_stage_release owns the only refusal.)
2 — §33 Opus 4 deleted the D17 construction.ACCEPT. Scope that deletion to the mixed-threadId ALTERNATIVE only. Name D17 construction: INSERT … SELECT … WHERE NOT EXISTS(marker) … ON CONFLICT DO UPDATE, correlated on the asserted single threadId. Mixed threadId still throws before that write.
3 — missing non-visible coverage EXISTS.ACCEPT. Third coverage EXISTS countEligibleNonVisibleCoverage on the same REPEATABLE READ snapshot: ingest-connector set, observation_state IN ('cleared','unseen') OR coverage='unknown' (Superseded by §39 Astra 2 / Opus 3: delete cleared from this EXISTS; non-visible is unseen or coverage='unknown' only; cleared ignored for counts/flags/fingerprint.), window date BETWEEN OR date IS NULL, actor predicate where user_source_id is non-null. Seal with the fingerprint. Own partial-reason + guidance. G5/G6/G10. (Superseded by §35 reshape: TimeEntry ∧ ingest-connector ∧ (cleared/unseen OR coverage='unknown') ∧ (date BETWEEN OR date IS NULL) ∧ (actor if user_source_id non-null else incomplete) ∧ matter only when selected; PARTITION unseen/coverage='unknown' and matter_redacted hard incomplete, matter_ineligible/matter_absent labelled coverage count.)
4 — null-user guidance omits matter filter.ACCEPT. Null-user guidance names labelled incomplete for visible redacted-user; drop “awaiting feeder” as the clear path for redaction. Distinct enum. No “narrow dates”. G10 matter-filter affordance; G5/G6 matter-scoped request clears week-wide null-user refusal. (Superseded by §44 Astra 2: null-user labelled flag/fingerprint only; G5/G6 matter-scoped request does not clear week-wide null-user refusal.)
5 — outlook-automatic-recovery HTML still dirty.ACCEPT. git restore --source=HEAD --worktree --staged of docs/superpowers/plans/2026-09-09-outlook-automatic-recovery.html and docs/superpowers/specs/2026-09-09-outlook-automatic-recovery-design.html. Generated STATUS/index only; do not ship another lane's pages.
6 — hours Number() wording overclaims ingest.ACCEPT. Hours lossless from stored numeric onward; decoded Number at ingest is the same class as IDs, tolerated for short decimals. Qualify spec §3.2 / plan §16.2. No code path.
7 — packet B preflight origin+account reads as ingest comparison.ACCEPT. Qualify packet B preflight origin+account to personal-grant successive enrollments, not ingest comparison.

Allocation: §34 +0/200 (B7 countEligibleNonVisibleCoverage + fingerprint seal 40, G5/G6/G10 non-visible fixtures 40, non-visible partial-reason/guidance 40, null-user matter-filter guidance + G5/G6 matter-scoped clear 40, J1 refresh-hook/runtime-scripts marker gate + J3 hook-bytes-unchanged 40). D17 naming, hours and packet-B preflight are in-place qualifications. Stay in 3 J files; J 190/60/360 =610. Then-current map 106 paths /18,340 lines is historical; current is §15.5 via §35. 111 expected outputs. No deploy.sh/rollback.sh/serve.ts/product/git/runtime/DB/schema work. Settled S/A, MCP IDs-only, exact generation, query-param review URL, message marker, mixed-threadId throw and no firm who_am_i remain.

35. Independent Astra 1–3 + Opus 1–5 after §34 — six separate dispositions

Six findings after the §34 snapshot. Record Astra 1 / Opus 3, Astra 2 / Opus 2, Astra 3, Opus 1, Opus 4 and Opus 5 separately. Preserve the settled one-statement digest (no chat_thread lock), query-param /tasks/chats/new?review=work, durable assistant message marker, settled S/A, MCP IDs-only, no firm who_am_i, and mixed-threadId throw. Neither reviewer is running. NOT CLEAN.

FindingDisposition, reason and owner
Astra 1 / Opus 3 — refresh functions abort instead of skip.ACCEPT skip not abort. Inside both bg_refresh_post_receive_hook and bg_refresh_runtime_scripts, skip (return 0, no copy) when the marker is absent after first materialization or ≠ DEPLOY_SHA. (Superseded by §39 Opus 5: delete “after first materialization” as live predicate; floor ≥ 1 ⇒ marker required; no floor ⇒ absence dormant; J3(e) sets the floor.). (Superseded by §46 Opus 2: skip-both half; sole live J contract is hook skip only; bg_refresh_runtime_scripts copies helpers from $BG_RELEASE_DIR with no discriminator.). Do not abort. bg_stage_release owns the only refusal. J3: hook bytes unchanged; bg_reconcile still runs. J3(a): no RELEASES_ROOT/<B>, no .deploy-release=B, no swap, hook/runtime-script bytes unchanged. Stay in 3 J files. Mark §30.2 Opus 6 superseded in place.
Astra 2 / Opus 2 — non-visible EXISTS is unpartitioned.ACCEPT reshape. countEligibleNonVisibleCoverage: TimeEntry ∧ ingest-connector ∧ (observation_state IN ('cleared','unseen') OR coverage='unknown') (Superseded by §39 Astra 2 / Opus 3: delete cleared from this EXISTS.) ∧ (date BETWEEN bounds OR date IS NULL) ∧ (actor predicate if user_source_id is non-null else incomplete) ∧ (matter only when selected). PARTITION: unseen/coverage='unknown' and matter_redacted are hard incomplete; matter_ineligible/matter_absent are labelled coverage count, not hard incomplete. Name those affordances. G5 Expense / other-matter / null-user-non-visible cases. (Superseded by §36: partition by cleared_reason not coverage; hard incomplete only unseen/coverage='unknown' WITH user_source_id=actor AND date in window; demote every cleared_reason including matter_redacted to labelled coverage count.)
Astra 3 — §30.2 Opus 6 still reads as live hook-refresh.ACCEPT. Mark §30.2 Opus 6 superseded in place. Live contract is skip (return 0, no copy) in both refresh functions; hook/runtime-script bytes unchanged; bg_reconcile still runs. (Superseded by §46 Opus 2: skip-both; sole live J contract is hook skip only; runtime-scripts copy from $BG_RELEASE_DIR with no discriminator.)
Opus 1 — missing unstamped-epoch coverage EXISTS.ACCEPT fourth EXISTS countEligibleUnstampedCoverage on the same REPEATABLE READ snapshot: ingest-connector ∧ visible TimeEntry ∧ (three named predicates: countEligibleUnstampedCoverage = source_authorization_epoch IS NULL (refuse); countEligibleBelowEpochSameOriginCoverage labelled, sealed in fingerprint, not G5(b) refuse; origin mismatch = existing identity-unverified refuse. Drop “state epoch equals current” until a full scan completes under the new epoch) ∧ same window/actor. Seal with the fingerprint. Own partial-reason + “awaiting feeder reobservation; ledger refresh cannot repair”. Overflow/200-vs-201 measured on the same set as the projection. Delete the pre-epoch-filter overflow refinement. G5/G6/G10. (Superseded by §37 Opus 4: overflow on the same set as the projection; this §35 row originally said pre-epoch-filter. Superseded by §41/§42: three separately named literal predicates; unstamped EXISTS is shared population ∧ source_authorization_epoch IS NULL only, nothing else inside that EXISTS.)
Opus 4 — Clio unique/dismiss key includes connector_id.ACCEPT. Clio durable anchor is verified account (canonical regional origin + data.account.id) + activity ID. connector_id is a selection predicate and non-key provenance snapshot, not part of the unique/dismiss key. Dismissals survive disconnect+reconnect of the same account and apply only while ingest is the same Clio account as the personal verified account. G5: reconnect ingest under a different account → prior dismissals do not apply. No who_am_i. G5 live-authorized vs revoked leftover still isolates on connector_id for selection, not for dismiss keys. (Superseded by §38 Astra 4 / Opus 9: drop ingest-same-account comparison and different-account G5; dismiss key is personal verified account + activity ID.)
Opus 5 — generated-index mtime / HANDOVER-clicky-surface.REJECT as generated-index mtime residue of HANDOVER-clicky-surface.html, not a ledger contract. Do not fight status.ts mtime sort. Do not ship other-lane content edits.

Allocation: §35 +0/200 (B7 countEligibleUnstampedCoverage + fingerprint seal 40, own partial-reason + feeder-reobservation guidance 40, overflow/200-vs-201 on the same set as the projection 40, G5/G6/G10 unstamped fixtures 40, G5 Expense/other-matter/null-user-non-visible partition 40). Skip-not-abort, nonvisible reshape, Clio dismiss-key and §30.2 Opus 6 mark are in-place qualifications. Stay in 3 J files; J 190/60/360 =610. Then-current map 106 paths /18,540 lines is historical; current is §15.5 via §36. 111 expected outputs then; current 112. No deploy.sh/rollback.sh/serve.ts/product/git/runtime/DB/schema work. Settled S/A, MCP IDs-only, exact generation, query-param review URL, message marker, mixed-threadId throw and no firm who_am_i remain. Generated STATUS/index only; do not ship another lane's pages.

36. Independent review after §35 — eight separate ACCEPT dispositions

Eight findings after the §35 snapshot. Record each separately. Preserve the settled skip-not-abort, one-statement digest (no chat_thread lock), query-param /tasks/chats/new?review=work, durable assistant message marker, settled S/A, MCP IDs-only, no firm who_am_i, mixed-threadId throw, and Clio account+activity dismiss key. Neither reviewer is running. NOT CLEAN.

FindingDisposition, reason and owner
1 — fingerprint omits feeder-scan completeness.ACCEPT. Fifth fingerprint-sealed precondition: parseActivitySyncState in the same REPEATABLE READ snapshot. Refuse the named figure unless completed_updated_since IS NOT NULL and last_full_scan_completed_at is present. Drop “state epoch equals current” until a full scan completes under the new epoch. A zero-row fresh connector refuses, not 0 minutes. G5/G6/G10.
2 — unseen vs cleared still share hard incomplete.ACCEPT split. Hard incomplete only unseen/coverage='unknown' WITH user_source_id=actor AND date in window. Demote every cleared_reason including matter_redacted to labelled coverage count. Do not change A6 clear-writer (it NULLs date/user/matter). Name column cleared_reason with exact three literals matter_redacted/matter_ineligible/matter_absent; non-NULL only when observation_state='cleared'; default arm for NULL/unknown reasons. Drop the cleared_reason CHECK from this lane. Writer markActivitiesUnseen NULLs cleared_reason. Throwaway backfill for P3 clones only. No generated CHECK, no hand-edited SQL, no deploy-path backfill. G5: week-wide ineligible does not refuse; matter-scoped redacted does not refuse the named figure (labelled only). (Superseded in part by §37 Astra 1 and by §38 Astra 1: drop remaining labelled cleared coverage; G5 does not count labelled cleared rows.)
3 — partition named coverage not cleared_reason.ACCEPT. Name cleared_reason not coverage for the partition.
4 — CLIO_AUTHORIZATION_EPOCH_SQL lives in worker-clio.ACCEPT. Move CLIO_AUTHORIZATION_EPOCH_SQL into packages/clio-sync (packages/clio-sync/src/activity-sync-state.ts, mapped existing A10). Shared SQL returns text; B7 casts. Unstamped comparison numeric. B7 uses that named expression with numeric/bigint comparison. Worker A7 imports it; no second definition.
5 — overflow reason beats coverage on combined fixtures.ACCEPT. Coverage reasons before overflow. Combined >200 AND null-date emits the coverage reason, never “narrow dates”. G6/G10 combined fixture.
6 — two-connector isolation overclaims concurrent authorized org Clio.ACCEPT. At most one authorized org Clio connector (connectors_org_scope_unique). Isolation fixture is authorized vs revoked leftover. State the invariant.
7 — marker absence vs active floor.ACCEPT. With active floor minimumVersion ≥ 1, marker REQUIRED on the staging path: absence → bg_stage_release refuse, refresh skip; green-lit reuse unaffected; J3 absent-marker green-lit-reuse succeeds. No active floor → absence dormant. J3(e) sets the floor. Stay in 3 J files; J 190/60/360 =610. (Superseded in part by §37 Opus 10: absence refuses on the staging path only; green-lit reuse unaffected; J3 absent-marker green-lit-reuse succeeds.)
8 — leftover spec J 570 reads as current.ACCEPT. Spec J 190/60/360 =610; mark leftover 570 historical.

Allocation: §36 +1/200 (parseActivitySyncState fifth fingerprint-sealed precondition + G5/G6/G10 zero-row 40, unseen vs cleared split + G5 week-wide ineligible / matter-scoped redacted 40, A10 activity-sync-state.ts + CLIO_AUTHORIZATION_EPOCH_SQL numeric/bigint 40, coverage-before-overflow + G6/G10 combined fixture 40). cleared_reason naming, one-authorized-org-connector invariant, J3(e) floor-gated marker and leftover 570 mark are in-place qualifications. Stay in 3 J files; J 190/60/360 =610. Then-current map 107 paths /18,740 lines is historical; current is §15.5 via §37. 112 expected outputs then; current 113. No deploy.sh/rollback.sh/serve.ts/product/git/runtime/DB/schema work. Settled skip-not-abort, one-statement digest, query-param review URL, message marker, S/A, MCP IDs-only, no firm who_am_i, mixed-threadId throw, Clio account+activity dismiss key and scan-state precondition remain. Generated STATUS/index only.

37. Independent Astra 1–2 + Opus 4–10 after §36 — nine separate ACCEPT dispositions

Nine findings after the §36 snapshot. Record Astra 1–2 and Opus 4–10 separately. Preserve the settled skip-not-abort, one-statement digest (no chat_thread lock), query-param /tasks/chats/new?review=work, durable assistant message marker, settled S/A, MCP IDs-only, no firm who_am_i, mixed-threadId throw, Clio account+activity dismiss key, and scan-state precondition. Neither reviewer is running. NOT CLEAN.

FindingDisposition, reason and owner
Astra 1 — labelled cleared coverage still windows actor/date/matter.ACCEPT drop labelled cleared coverage that needs actor/date/matter. Keep A6 clear-writer NULLs. Hard incomplete only unseen/coverage='unknown' WITH user_source_id=actor AND date in window. Do not window labelled coverage on cleared rows. G5: labelled cleared rows still count when date/user/matter are NULL. (Superseded by §38 Astra 1: drop remaining labelled cleared coverage; G5 does not count labelled cleared rows.)
Astra 2 — missing cleared_reason CHECK; unseen writer leaves stale reason.ACCEPT CHECK: (observation_state='cleared' AND cleared_reason IN (matter_redacted, matter_ineligible, matter_absent)) OR (observation_state <> 'cleared' AND cleared_reason IS NULL). markActivitiesUnseen must NULL cleared_reason — absence-writer change in already-mapped A6 packages/clio-sync/src/activity-events.ts. G6 invalid SQL.
Opus 4 — overflow measured on the pre-epoch-filter set.ACCEPT. Measure overflow on the same set as the projection. Delete the pre-epoch-filter overflow refinement. G5 200-vs-201 on that same set.
Opus 5 — org-wide hard-incomplete for null-user is too harsh.ACCEPT. Visible redacted-user is labelled incomplete, not org-wide refuse. Do not change mapActivity in this pass; nested visibility still P1. Drop “awaiting feeder” as the clear path for redaction. G5/G6: visible NULL user_source_id does not refuse the named figure (labelled only).
Opus 6 — empty projection hides unauthorized ingest connector.ACCEPT. Named partial reason: ingest connector present but not authorized (error/requires_reauth). Empty projection shows that, not no-match. G5/G10. (Superseded by §38 Astra 3 / Opus 8, then §39 Astra 1 / Opus 1 / Astra 3: ingest-not-authorized only on empty authorized set; exclude revoked/archived from that EXISTS; authorized present → ignore leftovers, parse authorized scan-state; empty authorized → unique live org Clio; leftover only if none; zero org Clio rows → missing-feeder. Superseded by §40 Astra 4: leftover revoked/archived is ingest-not-authorized, never 0 minutes; missing-feeder only zero org Clio rows.)
Opus 7 — epoch comparison type.ACCEPT. source_authorization_epoch bigint (or text compared via ::bigint). Unstamped comparison numeric. Shared SQL returns text; B7 casts.
Opus 8 — clio-sync public index unmapped.ACCEPT. Map packages/clio-sync/src/index.ts as A11. Re-export CLIO_AUTHORIZATION_EPOCH_SQL, parseActivitySyncState, applyActivityPage and markActivitiesUnseen through the existing public package surface. B7/A7 import from that surface, not a deep path.
Opus 9 — dismissals after ingest account change.ACCEPT. Dismissals apply only while ingest is the same Clio account as the personal verified account. G5: reconnect ingest under a different account → prior dismissals do not apply. No who_am_i. (Superseded by §38 Astra 4 / Opus 9: drop different-account G5; dismiss key is personal verified account + activity ID; no stored ingest account.)
Opus 10 — marker absence refuses green-lit reuse.ACCEPT. Marker absence refuses on the staging path only; green-lit reuse unaffected. J3 absent-marker green-lit-reuse succeeds. Stay in 3 J files; J 190/60/360 =610.

Allocation: §37 +1/200 (A11 packages/clio-sync/src/index.ts public export 40, drop labelled-cleared actor/date/matter window 40, CHECK + markActivitiesUnseen NULL cleared_reason 40, overflow on the same set as the projection 40, visible redacted-user labelled incomplete + ingest not-authorized partial reason + bigint epoch/B7 cast + same-account ingest dismissals + staging-only absence 40). Skip-not-abort, one-statement digest, query-param review URL, message marker, S/A, MCP IDs-only, no firm who_am_i, mixed-threadId throw, Clio account+activity dismiss key and scan-state precondition remain. Stay in 3 J files; J 190/60/360 =610. Then-current map 108 paths /18,940 lines; 113 expected outputs. No deploy.sh/rollback.sh/serve.ts/product/git/runtime/DB/schema work. Generated STATUS/index only.

38. Independent Astra 1–4 + Opus 6–9 after §37 — seven separate dispositions

Seven findings after the §37 snapshot. Record Astra 1–2, Astra 3, Astra 4 / Opus 9, Opus 6, Opus 7 and Opus 8 separately. Preserve the settled skip-not-abort, one-statement digest (no chat_thread lock), query-param /tasks/chats/new?review=work, durable assistant message marker, settled S/A, MCP IDs-only, and no firm who_am_i. Neither reviewer is running. NOT CLEAN.

FindingDisposition, reason and owner
Astra 1 — remaining labelled cleared coverage.ACCEPT drop remaining labelled cleared coverage. Hard incomplete only unseen/coverage='unknown' WITH user_source_id=actor AND date in window. Keep A6 NULLs. G5 does not count labelled cleared rows. (Superseded by §39 Astra 2 / Opus 3: delete cleared from countEligibleNonVisibleCoverage; cleared ignored for counts/flags/fingerprint; §8 redacted-matter no signal.)
Astra 2 — CHECK / unseen cleared_reason.ACCEPT CHECK as already specified: (observation_state='cleared' AND cleared_reason IN (matter_redacted, matter_ineligible, matter_absent)) OR (observation_state <> 'cleared' AND cleared_reason IS NULL). Unseen NULLs cleared_reason. markActivitiesUnseen already NULLs it. (Superseded by §40 Astra 3 / Opus 3: change markActivitiesUnseen to NULL cleared_reason; throwaway backfill before migrate; delete “already NULLs it”; CHECK after backfill.)
Astra 3 — scan-state on empty authorized set.ACCEPT. Before no-match/scan-state on empty authorized set, EXISTS org Clio status IN ('error','requires_reauth','pending') or status <> 'authorized' emits ingest-not-authorized with the status enum. Do not parse scan-state of a non-authorized row. (Superseded by §39 Astra 1 / Opus 1 / Astra 3: ingest-not-authorized only on empty authorized set; exclude revoked/archived from that EXISTS; authorized present → ignore leftovers, parse authorized scan-state; empty authorized → unique live org Clio; leftover only if none; zero org Clio rows → missing-feeder. Superseded by §40 Astra 4: leftover revoked/archived is ingest-not-authorized, never 0 minutes; missing-feeder only zero org Clio rows.)
Astra 4 / Opus 9 — different-account G5.ACCEPT drop different-account G5. Dismiss key is personal verified account + activity ID. No stored ingest account, no who_am_i. Drop “ingest same account as personal” comparison. G5 does not assert reconnect-different-account.
Opus 6 — HITL first-admission.REJECT HITL first-admission. Settled: first ledger tool use admits the thread. Prompt/tool-help explain permanence. Do not add interrupt.
Opus 7 — outlook HTML dirty after regen / unused CDN.ACCEPT restore after regen of docs/superpowers/plans/2026-09-09-outlook-automatic-recovery.html and docs/superpowers/specs/2026-09-09-outlook-automatic-recovery-design.html. The CDN-script-drop clause is withdrawn in place — tags are the house head template, not ledger content. Generated STATUS/index only; do not ship another lane's pages.
Opus 8 — missing-feeder versus not-authorized.ACCEPT. Any org Clio row with status <> 'authorized' emits the partial reason ingest-not-authorized; only complete absence of org Clio is missing-feeder. G5/G10 pending case. (Superseded by §39 Astra 1 / Opus 1 / Astra 3: ingest-not-authorized only on empty authorized set; exclude revoked/archived; leftover only if none live; zero org Clio rows is missing-feeder. Superseded by §40 Astra 4: leftover revoked/archived is ingest-not-authorized, never 0 minutes; missing-feeder only zero org Clio rows.)

Allocation: §38 +0/200 (drop remaining labelled cleared coverage 40, ingest-not-authorized empty-authorized-set gate + do-not-parse non-authorized scan-state 40, drop different-account G5 / personal-account dismiss key 40, HITL reject — prompt/tool-help permanence only 40, G5/G10 pending and missing-feeder-only-on-complete-absence 40). Skip-not-abort, one-statement digest, query-param review URL, message marker, S/A, MCP IDs-only, no firm who_am_i and mixed-threadId throw remain. Stay in 3 J files; J 190/60/360 =610. Then-current map 108 paths /19,140 lines is historical; current is §15.5 after §39. 113 expected outputs. No deploy.sh/rollback.sh/serve.ts/product/git/runtime/DB/schema work. Generated STATUS/index only.

39. Independent Astra 1–3 + Opus 1–6 after §38 — seven separate ACCEPT dispositions

Seven findings after the §38 snapshot. Record Astra 1 / Opus 1, Astra 2 / Opus 3, Astra 3, Opus 2, Opus 4, Opus 5 and Opus 6 separately. Preserve the settled skip-not-abort, one-statement digest (no chat_thread lock), query-param /tasks/chats/new?review=work, durable assistant message marker, settled S/A, MCP IDs-only, no firm who_am_i, HITL reject, and personal-account+activity dismiss key. Neither reviewer is running. NOT CLEAN.

FindingDisposition, reason and owner
Astra 1 / Opus 1 — ingest-not-authorized vs leftovers.ACCEPT. ingest-not-authorized only on empty authorized set. Exclude revoked/archived from that EXISTS. If authorized org Clio exists, ignore leftovers and parse scan-state of the authorized row. If authorized empty, remaining live org Clio (status NOT IN revoked/archived) is ingest-not-authorized; zero org Clio rows is missing-feeder. G5/G10. (Superseded by §41 Opus 5: authorized empty AND any org Clio row EXISTS, no status filter → ingest-not-authorized; missing-feeder only zero org Clio rows. Marked superseded in §42 Opus 7.)
Astra 2 / Opus 3 — cleared still inside non-visible.ACCEPT. Delete observation_state='cleared' from countEligibleNonVisibleCoverage. Non-visible is unseen or coverage='unknown' only. Drop second-scan/freshness split. No extra schema. Unseen rows are labelled coverage (deletion/redaction class), not hard-incomplete. Hard-incomplete remains coverage='unknown' with observation_state='visible' AND user_source_id=actor AND date in window. Split the EXISTS. G5(b) does not refuse labelled unseen. Cleared rows ignored for counts/flags/fingerprint. §8: redacted-matter entries leave the named figure with no signal. Keep A6 NULLs. (Superseded by §42 Opus 1 option B / §43 Astra 1 / Opus 2: delete that hard-incomplete arm; unseen labelled only; non-visible is unseen or coverage='unknown' only; cleared rows ignored.)
Astra 3 — empty-authorized status source.ACCEPT. Empty authorized: status from unique live org Clio (status NOT IN revoked/archived); leftover only if none. (Superseded by §40 Astra 4: leftover revoked/archived is ingest-not-authorized, never 0 minutes; missing-feeder only zero org Clio rows.)
Opus 2 — both-NULL still hard incomplete.ACCEPT. NULL-user is labelled. Delete the stale both-NULL hard-incomplete sentence. Both-NULL row is labelled, not refuse. Fix G5 expectation.
Opus 4 — ingest origin unstamped.ACCEPT. Feeder stamps ingest-side normalized origin onto clio_activity in the same A6/A7 delta as source_authorization_epoch. Refuse the named figure (identity-unverified; drop “label”) when absent or differs from personal verified origin. No who_am_i. Delete “already held in activities.ts”. (Superseded by §40 Astra 1 / Opus 2, then §41 Opus 1 / Astra 4 / Opus 10: A7 helper, not A6 normalizeHost.)
Opus 5 — after-first-materialization still live.ACCEPT. Delete “after first materialization” as live predicate. Keep only floor ≥ 1 ⇒ marker required; no floor ⇒ absence dormant. J3(e) sets the floor. Skip-not-abort remains: skip (return 0, no copy) when the marker ≠ DEPLOY_SHA, or when the marker is absent and floor ≥ 1; do not abort; bg_stage_release owns the only refusal.
Opus 6 — spec:357 arithmetic.ACCEPT. spec:357 +35/+10,240 was wrong for the §38 snapshot; correct was +35/+10,440 (= 108/19,140). §15.5 sole authority. §39 +0/200 makes current +35/+10,640 / 108 paths /19,340 lines.

Allocation: §39 +0/200 (ingest-not-authorized empty-authorized-only + exclude revoked/archived + unique live status 40, drop cleared from non-visible + ignore cleared counts/flags/fingerprint + §8 no-signal 40, both-NULL labelled not refuse + G5 40, ingest-origin stamp + identity-unverified 40, delete after-first-materialization live predicate 40). Opus 6 arithmetic is in-place. Skip-not-abort, one-statement digest, query-param review URL, message marker, S/A, MCP IDs-only, no firm who_am_i, HITL reject and personal-account+activity dismiss key remain. Stay in 3 J files; J 190/60/360 =610. Then-current map 108 paths /19,340 lines is historical; current is §15.5 after §40. 113 expected outputs. No deploy.sh/rollback.sh/serve.ts/product/git/runtime/DB/schema work. Generated STATUS/index only.

40. Independent Astra 1–4 + Opus 1–5 after §39 — eight separate ACCEPT dispositions

Eight findings after the §39 snapshot. Record Astra 1 / Opus 2, Astra 2, Astra 3 / Opus 3, Astra 4, Opus 1, Opus 4, Opus 5 and the seat note separately. Preserve the settled skip-not-abort, one-statement digest (no chat_thread lock), query-param /tasks/chats/new?review=work, durable assistant message marker, settled S/A, MCP IDs-only, no firm who_am_i, HITL reject, and personal-account+activity dismiss key. Neither reviewer is running. NOT CLEAN.

FindingDisposition, reason and owner
Astra 1 / Opus 2 — ingest origin via ClioClient.ACCEPT. A7 passes bundle raw provider_metadata.api_host (NULL if not a string). A6 runs existing normalizeHost on that string only; never construct ClioClient to learn origin. Stamp NULL when absent. B7 compares that same url.origin to personal verified origin in the same REPEATABLE READ snapshot. Absent/mismatch refuses the named figure (drop “label”). Never stamp ClioClient.baseUrl or env default. No who_am_i. (Superseded by §41 Opus 1 / Astra 4 / Opus 10: do not call normalizeHost from A6; A7 stamps helper return value.)
Astra 2 — G5(b) null-user refuse.ACCEPT. Mark §32/:945 and §15.5 G5(b) null-user refuse superseded. G5(b) keeps overflow-unchanged + null-date refuse. Null-user/both-NULL only labelled flag/fingerprint, do not refuse.
Astra 3 / Opus 3 — unseen cleared_reason.ACCEPT. Same A unit: change markActivitiesUnseen to NULL cleared_reason; throwaway packages/db/scripts/ backfill SET cleared_reason=NULL WHERE observation_state <> 'cleared' run before migrate (P3 records row count). Delete “already NULLs it”. CHECK after backfill. (Superseded by §41 Astra 3 / Opus 4: drop CHECK; throwaway backfill for P3 clones only; no generated CHECK, no hand-edited SQL, no deploy-path backfill.)
Astra 4 — leftover revoked as 0 minutes.ACCEPT. Empty authorized still refuses. Status from unique live org Clio when one exists; otherwise leftover revoked/archived is ingest-not-authorized, never 0 minutes. Missing-feeder only zero org Clio rows. G5/G10 revoked-only fixture. (Superseded by §41 Opus 5: three org-Clio branches; authorized empty AND any org Clio row EXISTS, no status filter.)
Opus 1 — unseen hard-incomplete forever.ACCEPT. Hard-incomplete unseen only while fresh against last completed scan (last_seen_at vs last_full_scan_started_at). A row that survives a second completed full scan as unseen is a deletion → labelled coverage, same class as cleared. No schema. (Superseded by §41 Astra 1 / Opus 3: drop second-scan/freshness split; unseen labelled, not hard-incomplete.)
Opus 4 — green-lit reuse skips drain/probe copy.ACCEPT. Green-lit reuse copies drain/probe from $BG_RELEASE_DIR/deploy/preprod/bin/ (admitted tree), not skip. J3(g) asserts helper bytes match the promoted release. Skip-not-abort remains for both refresh functions on marker mismatch. (Superseded by §41 Opus 6: discriminator once; copy only on BG_RELEASE_PROMOTED=1; marker skip off that path; bg_refresh_post_receive_hook keeps skipping.)
Opus 5 — below-epoch unstamped refuse.ACCEPT. Unstamped refuse is source_authorization_epoch IS NULL only. Below-epoch-same-origin is labelled. Origin mismatch refuses. Status recovery that bumps authorized_at must not refuse the figure until a full scan. (Superseded in part by §41 Astra 2 / Opus 7: named countEligibleBelowEpochSameOriginCoverage; drop “state epoch equals current” until a full scan under the new epoch.)
Seat note — Claude panes for product/prose.ACCEPT. Product code and prose workers are omp Astra seats per the 2026-09-08 standing rule, not new Claude panes. Dual reviewers stay two models (Astra + Opus).

Allocation: §40 +0/280 (A7 raw api_host + A6 normalizeHost-only stamp + B7 url.origin refuse 40, G5(b) overflow-unchanged + null-date refuse / null-user labelled 40, markActivitiesUnseen NULL cleared_reason + throwaway backfill + CHECK after 40, leftover revoked/archived ingest-not-authorized + G5/G10 revoked-only 40, hard-incomplete unseen only while fresh 40, green-lit reuse copies drain/probe + J3(g) 40, unstamped IS NULL refuse / below-epoch labelled / origin mismatch refuse / authorized_at bump waits for full scan 40). Seat note is in-place. Skip-not-abort, one-statement digest, query-param review URL, message marker, S/A, MCP IDs-only, no firm who_am_i, HITL reject and personal-account+activity dismiss key remain. Stay in 3 J files. Several §40 live contracts are superseded by §41.

41. Independent Opus 1–2 / Astra 1–4 / Opus 3–10 after §40 — ten separate ACCEPT dispositions

Ten findings after the §40 snapshot. Record Opus 1 / Astra 4, Opus 2, Astra 1 / Opus 3, Astra 2 / Opus 7, Astra 3 / Opus 4, Opus 5, Opus 6, Opus 8, Opus 9 and Opus 10 separately. Preserve the settled skip-not-abort, one-statement digest (no chat_thread lock), query-param /tasks/chats/new?review=work, durable assistant message marker, settled S/A, MCP IDs-only, no firm who_am_i, HITL reject, and personal-account+activity dismiss key. Neither reviewer is running. NOT CLEAN.

FindingDisposition, reason and owner
Opus 1 / Astra 4 — do not call normalizeHost from A6.ACCEPT. Do not call normalizeHost from A6. A7 (apps/worker-clio, already depends on connector-clio) stamps origin. Export a string-returning origin helper from connector-clio (map packages/connector-clio/src/clio-client.ts export-only; wrap/catch ClioApiError). Stamp the return value, not .origin. Absent or throw → explicit NULL. B7 compares that same helper output to personal verified origin. Never raw api_host. Never ClioClient.baseUrl/env default. (Superseded in part by §42 Astra 5: helper returns string | null and catches URL/TypeError and ClioApiError.)
Opus 2 — Pre-B3 origin stamp.ACCEPT. Pre-B3 rows stamp NULL. A origin stamp ships only after B3 preserves verified origin; until then source_origin is NULL and B7 identity-unverified refuses. (Superseded by §56 Astra 1: NULL-origin is (11) only, not identity-unverified.)
Astra 1 / Opus 3 — second-scan/freshness split.ACCEPT. Drop second-scan/freshness split. No extra schema. Unseen rows are labelled coverage (deletion/redaction class), not hard-incomplete. Hard-incomplete remains coverage='unknown' with observation_state='visible' AND user_source_id=actor AND date in window. Split the EXISTS. G5(b) does not refuse labelled unseen. (Superseded by §42 Opus 1 option B: delete that hard-incomplete arm; unseen labelled only.)
Astra 2 / Opus 7 — three named epoch/origin predicates.ACCEPT. Three named predicates: countEligibleUnstampedCoverage = source_authorization_epoch IS NULL (refuse). countEligibleBelowEpochSameOriginCoverage labelled, sealed in fingerprint, not G5(b) refuse. Origin mismatch = existing identity-unverified refuse. Rewrite spec:146. Drop “state epoch equals current” until a full scan completes under the new epoch (Astra 5). (Superseded in part by §42 Astra 1 / Astra 3 / Opus 3: origin compare not inside unstamped; below-epoch in RR/G5(a)/fingerprint; fourth named origin predicate sealed.)
Astra 3 / Opus 4 — drop cleared_reason CHECK.ACCEPT. Drop the cleared_reason CHECK from this lane. Writer markActivitiesUnseen NULLs cleared_reason. Throwaway backfill for P3 clones only. No generated CHECK, no hand-edited SQL, no deploy-path backfill. (Superseded by §42 Opus 6: drop the writer/backfill; no feeder edit for a dead column.)
Opus 5 — three org-Clio branches.ACCEPT. Name three org-Clio branches: (1) authorized exists → parse that row’s scan-state; (2) authorized empty AND any org Clio row EXISTS (no status filter) → ingest-not-authorized; (3) zero org Clio rows → missing-feeder. (Branch (2) status source extended by §43 Opus 8: partial-unique live org Clio row when it exists, else any leftover; extend partial-reason enum to cover revoked/archived.)
Opus 6 — reuse discriminator.ACCEPT. (Superseded by §46 Opus 2: delete the reuse discriminator; no BG_RELEASE_REUSED. bg_refresh_runtime_scripts copies helpers from $BG_RELEASE_DIR unconditionally; both reuse and promote leave $BG_RELEASE_DIR green-lit. bg_refresh_post_receive_hook marker skip stays load-bearing, untouched.) (Superseded by §42 Opus 2: distinct BG_RELEASE_REUSED=1 only at bluegreen.sh:607; runtime-scripts skip dead on ordinary path.)
Opus 8 — header arithmetic.ACCEPT. Headers 19,620.
Opus 9 — packet table vs §15.5.ACCEPT. Reconcile packet table to §15.5; one H value (240). G 20/5,510 so packet rows sum to 19,620.
Opus 10 — delete “already held in activities.ts”.ACCEPT. Delete “already held in activities.ts”. A7 passes raw api_host; A7/helper normalizes.

Allocation: §41 +0/0 (reconciliation: G 5,310→5,510 already in packet-table Headers 19,620); one H 240. No new path. Skip-not-abort, one-statement digest, query-param review URL, message marker, S/A, MCP IDs-only, no firm who_am_i, HITL reject and personal-account+activity dismiss key remain. Stay in 3 J files; J 190/60/360 =610. Neither reviewer is running. NOT CLEAN. No product/git/runtime/deploy/DB/schema work.

42. Independent Opus 1 / Astra 1 / Astra 3–5 / Opus 2–8 after §41 — twelve separate ACCEPT dispositions

Twelve findings after the §41 snapshot. Record Opus 1, Astra 1, Astra 3, Astra 4, Astra 5, Opus 2, Opus 3, Opus 4, Opus 5, Opus 6, Opus 7 and Opus 8 separately. Preserve the settled skip-not-abort, one-statement digest (no chat_thread lock), query-param /tasks/chats/new?review=work, durable assistant message marker, settled S/A, MCP IDs-only, no firm who_am_i, HITL reject, and personal-account+activity dismiss key. Neither reviewer is running. NOT CLEAN.

FindingDisposition, reason and owner
Opus 1 — hard-incomplete arm of non-visible (option B).ACCEPT option B. Delete the hard-incomplete arm of countEligibleNonVisibleCoverage and its G5/G6/G10 assertions. Unseen is labelled only. Do not keep a predicate no writer can produce (coverage='unknown' with observation_state='visible' AND user_source_id=actor AND date in window). Rewrite G5/G6/G10 (Astra 2). Supersedes §41 Astra 1 / Opus 3 hard-incomplete remainder.
Astra 1 — unstamped vs below-epoch vs origin.ACCEPT. Unstamped EXISTS is source_authorization_epoch IS NULL only. Below-epoch is a separate labelled EXISTS. Origin compare is helper-output vs personal verified origin refuse, not inside unstamped.
Astra 3 — below-epoch in RR / G5(a) / fingerprint.ACCEPT. Add countEligibleBelowEpochSameOriginCoverage to the RR read set, G5(a) agreement list, and B7 fingerprint. Commit after that read, before provider calls.
Astra 4 — P3 NULL stamp / P4 helper.ACCEPT. P3 A7 stamps NULL only. Enable the helper stamp in P4 after B3 preserves stored origin, same release.
Astra 5 — helper return and catch.ACCEPT. Origin helper returns string | null and catches URL/TypeError and ClioApiError; A7 stamps NULL on any throw.
Opus 2 — BG_RELEASE_REUSED discriminator.ACCEPT. Distinct BG_RELEASE_REUSED=1 only at bluegreen.sh:607. Discriminator keys on that. Marker skip on bg_refresh_runtime_scripts is dead on the ordinary path; keep skip load-bearing on bg_refresh_post_receive_hook. J3 restated accordingly. Supersedes §41 Opus 6 BG_RELEASE_PROMOTED=1 (Superseded by §46 Opus 2: delete the reuse discriminator; unconditional copy from $BG_RELEASE_DIR; hook skip stays load-bearing.)
Opus 3 — fourth named origin predicate.ACCEPT. Fourth named predicate: source_origin IS NULL OR source_origin <> $personalVerifiedOrigin → identity-unverified refuse. Seal it in fingerprint (spec:154/303, plan:144/164). G5 stamped-epoch/NULL-origin row. (Superseded by §56 Astra 1: split the combined fourth predicate; source_origin IS NULL is (11) only; non-null origin differs is (6).)
Opus 4 — MCP import boundary as D-packet.ACCEPT. Map apps/mcp-server/src/tool-import-boundary.ts + its test as D-packet. Admitting server-only ledger subpath means IDs-only is adapter discipline from that point.
Opus 5 — transport-policy suite.ACCEPT. Map packages/connector-clio/src/transport-policy.test.ts as affected existing suite. Do not delete it as plumbing. Price inventory extension.
Opus 6 — drop cleared_reason feeder edit.ACCEPT. Drop markActivitiesUnseen cleared_reason NULL change. Drop enum/default-arm/partition-uses-cleared_reason residue. No feeder edit for a dead column. Supersedes §41 Astra 3 / Opus 4 writer/backfill.
Opus 7 — §39 exclude-revoked superseded.ACCEPT. Mark §39 ingest-not-authorized exclude-revoked sentence superseded by §41 Opus 5 (no status filter).
Opus 8 — ledger TABLE_GRANTS.ACCEPT. plan:127 names GRANT ON work_episode and work_episode_evidence TO app_role in TABLE_GRANTS.

Allocation: §42 +3/200 (D18/D19 tool-import-boundary + test 80, G25 transport-policy.test.ts 40, G5/G6/G10 rewrite + fingerprint/origin/J3 restatement 80) so D 17→19 /1,790→1,870 and G 20→21 /5,510→5,630. Packet rows sum to 19,820; one H 240. Unique paths 108→111 (110 authored plus generated Bun lock). Expected outputs 113→116. Skip-not-abort, one-statement digest, query-param review URL, message marker, S/A, MCP IDs-only, no firm who_am_i, HITL reject and personal-account+activity dismiss key remain. Stay in 3 J files; J 190/60/360 =610. Neither reviewer is running. NOT CLEAN. No product/git/runtime/deploy/DB/schema work.

43. Independent Astra 1 / Opus 2, Astra 2 / Opus 1, Opus 3–10 after §42 — ten separate ACCEPT dispositions

Ten findings after the §42 snapshot. Record Astra 1 / Opus 2, Astra 2 / Opus 1, Opus 3, Opus 4, Opus 5, Opus 6, Opus 7, Opus 8, Opus 9 and Opus 10 separately. Preserve the settled skip-not-abort, one-statement digest (no chat_thread lock), query-param /tasks/chats/new?review=work, durable assistant message marker, settled S/A, MCP IDs-only, no firm who_am_i, HITL reject, and personal-account+activity dismiss key. Neither reviewer is running. NOT CLEAN.

FindingDisposition, reason and owner
Astra 1 / Opus 2 — leftover hard-incomplete remainder.ACCEPT. Delete every “Hard-incomplete remains coverage='unknown' with observation_state='visible'…” sentence from plan:144 and spec:144 (both occurrences). Strike hard-incomplete G10/G5/G6 assertions at spec:336 and plan:212. Keep only “non-visible is unseen or coverage='unknown' only; cleared rows ignored; unseen labelled only.”
Astra 2 / Opus 1 — writer/backfill for dead column.ACCEPT. Delete “Writer markActivitiesUnseen NULLs cleared_reason” and throwaway-backfill sentences from plan:144 and spec:144. Rewrite spec:101 cell to “cleared_reason is unused by this lane; no feeder edit”. Keep “Drop the CHECK”.
Opus 3 — fingerprint list missing below-epoch.ACCEPT. Copy plan:164 fingerprint list onto plan:144 word for word: include countEligibleBelowEpochSameOriginCoverage and the origin-null/mismatch predicate.
Opus 4 — §15.5 packet table vs plan:97 chain.ACCEPT. §15.5 packet table is sole authority. Fix plan:97 chain to match packet-row total (19,820). Record §41 as +0/0 (reconciliation of G 5,310→5,510 already in packet-table 19,620), with that one-clause note. Do not invent a 20,020 cap.
Opus 5 — Fable Claude launch sentence.ACCEPT. Replace plan:115 Fable Claude launch sentence with the omp Astra seat line. Caption the owner table historical (already: superseded by §15.5).
Opus 6 — G25/D19 closed-inventory guards.ACCEPT. Qualify plan:227: G25 and D19 are closed-inventory guards that must be updated, never removed. Repeat at plan:569 and plan:190.
Opus 7 — G25 restated.ACCEPT. Restate G25 as classify the new helper in the closed call inventory and update the pinned token-request text; GET-only and no-unclassified-transport remain. Catch/destination proof stays G15/G5.
Opus 8 — branch (2) status source.ACCEPT. Branch (2) status source: partial-unique live org Clio row when it exists, else any leftover. Extend partial-reason enum to cover revoked/archived. Written in plan:144/:164 and spec:144/:154.
Opus 9 — CLIO_AUTHORIZATION_EPOCH_SQL current declaration.ACCEPT. spec:101 cites apps/worker-clio/src/loops/activity-sync.ts:580 as current declaration; rewrite cell “move into activity-sync-state.ts (A10), re-export from A11”.
Opus 10 — CDN-script-drop withdrawn.ACCEPT. Mark the CDN-script-drop clause withdrawn in place — tags are the house head template, not ledger content.

Allocation: §43 +0/0 (docs-only live-card alignment; packet table remains 19,820). Unique paths 111 (110 authored plus generated Bun lock). Expected outputs 116. Skip-not-abort, one-statement digest, query-param review URL, message marker, S/A, MCP IDs-only, no firm who_am_i, HITL reject and personal-account+activity dismiss key remain. Stay in 3 J files; J 190/60/360 =610. Neither reviewer is running. NOT CLEAN. No product/git/runtime/deploy/DB/schema work.

44. Independent Astra 1 / Opus 3, Opus 1–2, Opus 4, Astra 2, Opus 5–8 after §43 — nine separate ACCEPT dispositions

Nine findings after the §43 snapshot. Record Astra 1 / Opus 3, Opus 1, Opus 2, Opus 4, Astra 2, Opus 5, Opus 6, Opus 7 and Opus 8 separately. Preserve the settled skip-not-abort, one-statement digest (no chat_thread lock), query-param /tasks/chats/new?review=work, durable assistant message marker, settled S/A, MCP IDs-only, no firm who_am_i, HITL reject, and personal-account+activity dismiss key. Neither reviewer is running. NOT CLEAN.

FindingDisposition, reason and owner
Astra 1 / Opus 3 — circular “three named predicates” parenthetical.ACCEPT. On plan:144 and spec:144 ONLY, drop the “three named predicates” parenthetical from the unstamped sentence. Split into three separately named literal predicates. Mark spec:380 / plan:996 superseded by §41/§42. Unstamped: shared population ∧ source_authorization_epoch IS NULL → refuse. Nothing else inside that EXISTS.
Opus 1 — non-visible still not a labelled literal.ACCEPT. Restate countEligibleNonVisibleCoverage as a literal labelled (never refuse) predicate: ingest-connector ∧ observation_state='unseen' ∧ (coverage='unknown' OR true) ∧ same window/actor as null-user (user_source_id=actor when non-null). G5 one labelled-flag assertion that bites, not refuse. Keep it in RR/fingerprint. (Superseded by §45 Astra 3 / Opus 4–6: one literal, no cross-reference, no OR true.)
Opus 2 — below-epoch missing a literal.ACCEPT. Write countEligibleBelowEpochSameOriginCoverage literal on plan:144 and spec:146: shared population ∧ source_authorization_epoch IS NOT NULLsource_authorization_epoch < (authorized ingest connector’s CLIO_AUTHORIZATION_EPOCH_SQL from connectors.authorized_at) ∧ source_origin = $personalVerifiedOrigin. Labelled, sealed, not G5(b) refuse.
Opus 4 — origin predicate unscoped.ACCEPT. Fourth origin predicate, one form quoted everywhere: shared population ∧ (source_origin IS NULL OR source_origin <> $personalVerifiedOrigin) → identity-unverified refuse. Not unscoped. Not on cleared rows. (Superseded by §56 Astra 1: split the combined fourth predicate; source_origin IS NULL is (11) only; non-null origin differs is (6).)
Astra 2 — matter-scoped null-user refusal.ACCEPT. Delete “G5/G6 matter-scoped request clears week-wide null-user refusal” from plan:144, spec:144, plan:209, plan:220. Null-user labelled flag/fingerprint only. Keep G10 matter-filter for null-date refuse.
Opus 5 — marked thread still moveable onto a matter.ACCEPT. D packet: refuse move_chat_to_matter on a marked thread under the same chat_thread lock C/grantThreadShare already take. G9/G10: admitted thread never appears on the matter page for a second member. Map packages/agent-runtime/src/tools/move-chat-to-matter.ts and listChatsForMatter. (Superseded in part by §45 Astra 1 / Opus 9: drop D21 listChatsForMatter and the G9/G10 second-member assertion; keep D20. Superseded in part by §45 Opus 10: admission ACCEPTS a matter-filed thread.)
Opus 6 — partial-reason members restated ad hoc.ACCEPT. One enumerated partial-reason list on the B7 card with every member and its guidance string, including four coverage reasons and revoked/archived. Every other mention cites it. (Extended by §45 Astra 4 / Opus 7: guidance strings for every enum member; add validation-denial/hours-redaction and unsafe-ID identity-unverified.)
Opus 7 — Fable-worker rows still live.ACCEPT. Mark plan:385 and plan:412 Fable-worker rows superseded in place with the omp Astra seat line.
Opus 8 — 73 / 8,700 still reads as current.ACCEPT. plan:241 “the adopted 73 / 8,700 baseline (historical; §15.5 is the current map)”.

Allocation: §44 +2/80 (D20 move-chat-to-matter.ts 40, D21 listChatsForMatter 40) so D 19→21 /1,870→1,950. Packet rows sum to 19,900; one H 240. Unique paths 111→113 (112 authored plus generated Bun lock). Expected outputs 116→118. Predicate split, shared population, origin form, non-visible labelled literal, null-user flag-only, partial-reason list, Fable-seat mark and 73/8,700 baseline caption are in-place qualifications. Skip-not-abort, one-statement digest, query-param review URL, message marker, S/A, MCP IDs-only, no firm who_am_i, HITL reject and personal-account+activity dismiss key remain. Stay in 3 J files; J 190/60/360 =610. Neither reviewer is running. NOT CLEAN. No product/git/runtime/deploy/DB/schema work.

45. Independent Astra 1 / Opus 9, Astra 2, Opus 10, Astra 3 / Opus 4–6, Astra 4 / Opus 7, Opus 1–3, Opus 8 after §44 — nine separate ACCEPT dispositions

Nine findings after the §44 snapshot. Record Astra 1 / Opus 9, Astra 2, Opus 10, Astra 3 / Opus 4–6, Astra 4 / Opus 7, Opus 1, Opus 2, Opus 3 and Opus 8 separately. Preserve the settled skip-not-abort, one-statement digest (no chat_thread lock), query-param /tasks/chats/new?review=work, durable assistant message marker, settled S/A, MCP IDs-only, no firm who_am_i, HITL reject, and personal-account+activity dismiss key. Neither reviewer is running. NOT CLEAN.

FindingDisposition, reason and owner
Astra 1 / Opus 9 — D21 listChatsForMatter has no production caller.ACCEPT drop D21. Sidebar uses listThreads/threadVisible. Drop D21, drop the G9/G10 matter-page second-member assertion. Do not add userId to an unused helper. (Superseded by §46 Opus 4+5: restore D21 as marker EXISTS exclusion in listChatsForMatter, no userId; one biting G9/G10 second-member assertion. Keep spec:336.)
Astra 2 — marked-thread matter filing still open on updateThreadMatter.ACCEPT. Same chat_thread FOR UPDATE + marker refuse C/grantThreadShare already use, on updateThreadMatter (covers router + ChatRow). Keep D20. Map packages/chat-runtime/src/threads.ts; router stays a caller. (Extended by §46 Astra 2 / Opus 3: updateThreadMatter returns a discriminated result; router maps it to 422 not_shareable-class like grantThreadShare; ChatRow already has !res.ok; price existing I router.ts + router.test.ts.)
Opus 10 — admission of a matter-filed thread.ACCEPT. Admission ACCEPTS a matter-filed thread (matter-table “Review my work” is the product). Do not refuse, do not unfile. G9 asserts D20 still blocks subsequent moves in the same fixture.
Astra 3 / Opus 4–6 — non-visible still not one literal.ACCEPT. One literal, no cross-reference, no OR true: countEligibleNonVisibleCoverage = ingest-connector ∧ observation_state='unseen' ∧ type='TimeEntry' ∧ (matter only when selected) ∧ (date BETWEEN bounds OR date IS NULL) ∧ (user_source_id = $actor OR user_source_id IS NULL) ∧ matter_id IS NOT NULL. Labelled, never refuse. Other users’ unseen must not set the flag.
Astra 4 / Opus 7 — shared population and partial-reason gaps.ACCEPT. Shared population includes type='TimeEntry' stated once. Precedence: origin mismatch/NULL wins over unstamped when both true. Guidance strings for every enum member. Add validation-denial/hours-redaction and unsafe-ID identity-unverified as members with strings on the B7 card.
Opus 1 — §15.5 expected outputs still 116.ACCEPT. §15.5 complete mapped files: expected outputs 116→118.
Opus 2 — remaining dispositions cited by stale line numbers.ACCEPT. Cite remaining dispositions by §/packet label, not stale line numbers.
Opus 3 — §34 finding 4 week-wide null-user refusal still live.ACCEPT. Mark §34 finding 4 and spec §34 “G5/G6 week-wide null-user refusal” superseded by §44 Astra 2.
Opus 8 — existing move-chat G suite unmapped.ACCEPT. Map packages/agent-runtime/src/tools/move-chat-to-matter.test.ts as affected existing G suite. Name bg_gate_cross_matter as a gate that must stay green. (Superseded by §46 Opus 8: map it as counted G 21→22; total 113→114 with lines.)

Allocation: §45 +0/0 (drop D21 listChatsForMatter; reassign that 40 to D21 updateThreadMatter on packages/chat-runtime/src/threads.ts; G test/gate named, not a new path) so D stays 21 /1,950. Packet rows sum to 19,900; one H 240. Unique paths 113 (112 authored plus generated Bun lock). Expected outputs 118. Non-visible literal, TimeEntry in shared population, origin-over-unstamped precedence, complete partial-reason strings, matter-filed admission, D20 subsequent-move fixture, §34 superseded mark and §/packet citations are in-place qualifications. Skip-not-abort, one-statement digest, query-param review URL, message marker, S/A, MCP IDs-only, no firm who_am_i, HITL reject and personal-account+activity dismiss key remain. Stay in 3 J files; J 190/60/360 =610. Neither reviewer is running. NOT CLEAN. No product/git/runtime/deploy/DB/schema work.

46. Independent Opus 1–2, Astra 2 / Opus 3, Opus 4–5, Astra 1 / Opus 6, Opus 7–11 after §45 — ten separate ACCEPT dispositions

Ten findings after the §45 snapshot. Record Opus 1, Opus 2, Astra 2 / Opus 3, Opus 4–5, Astra 1 / Opus 6, Opus 7, Opus 8, Opus 9, Opus 10 and Opus 11 separately. Preserve the settled skip-not-abort (hook skip load-bearing; runtime-scripts copy unconditionally), one-statement digest (no chat_thread lock), query-param /tasks/chats/new?review=work, durable assistant message marker, settled S/A, MCP IDs-only, no firm who_am_i, HITL reject, personal-account+activity dismiss key, and admission ACCEPTS a matter-filed thread. Neither reviewer is running. NOT CLEAN.

FindingDisposition, reason and owner
Opus 1 — non-visible missing matter_id IS NOT NULL.ACCEPT. Add ∧ matter_id IS NOT NULL to the non-visible literal. G5: clear a row, run markActivitiesUnseen, flag stays off for a different actor.
Opus 2 — reuse discriminator.ACCEPT. Delete the reuse discriminator. bg_refresh_runtime_scripts copies helpers from $BG_RELEASE_DIR unconditionally (both reuse and promote leave $BG_RELEASE_DIR green-lit). No BG_RELEASE_REUSED. bg_refresh_post_receive_hook marker skip stays load-bearing, untouched. Supersede plan:513/520/809 PROMOTED sentences.
Astra 2 / Opus 3 — updateThreadMatter still a boolean/throw.ACCEPT. updateThreadMatter returns a discriminated result. Router maps it to 422 not_shareable-class like grantThreadShare. Price apps/web/lib/chat/router.ts + router.test.ts (existing I, not a new unique path). ChatRow already has !res.ok. Keep D20.
Opus 4+5 — D21 listChatsForMatter dropped.ACCEPT. Restore D21 as marker EXISTS exclusion in listChatsForMatter (no userId). Same class as D7/D9/D16/D17. One biting G9/G10: admitted thread never appears for a second member. Keep spec:336. (Superseded by §47 Astra 1 / Opus 1: listChatsForMatter lives on D22 chats.ts; threads.ts remains the updateThreadMatter slot. Superseded by §47 Opus 2: biting second-member assertion lives in chats.test.ts calling listChatsForMatter, not G9.)
Astra 1 / Opus 6 — “every coverage EXISTS quotes shared population” over-applied.ACCEPT. That quote rule applies only to unstamped / below-epoch / origin. Non-visible, null-date, null-user keep their own literals. User clause on the quoting three is the literal (user_source_id = $actor). Unstamped NULL-user does not refuse (labelled via null-user).
Opus 7 — marked-thread move/file not on spec admission.ACCEPT. Fold into spec:272: refuse move_chat_to_matter/updateThreadMatter on a marked thread; admission accepts a matter-filed thread.
Opus 8 — move-chat G suite uncounted.ACCEPT. Map move-chat-to-matter.test.ts as G 21→22; total 113→114 with lines. Name bg_gate_cross_matter.
Opus 9 — plan:97 authored count stale.ACCEPT. plan:97 → 112 authored (pre-§46; live after G26 is 113 authored plus generated Bun lock).
Opus 10 — partial-reason guidance mixed with implementation.ACCEPT. Split each partial-reason enum member into an implementation note and a lawyer-facing user sentence. H owns the ten user strings under spec §10 register.
Opus 11 — silent assertion deletion.ACCEPT. Invert plan:229 default: worker’s final report enumerates every removed assertion; reviewers approve the list. Keep G25/D19 carve-out.

Allocation: §46 +1/40 (G26 move-chat-to-matter.test.ts 40; restore D21 listChatsForMatter on the existing threads.ts slot; router 422 priced on existing I) so G 21→22 /5,630→5,670. Packet rows sum to 19,940; one H 240. Unique paths 113→114 (113 authored plus generated Bun lock). Expected outputs 118. Non-visible ∧ matter_id IS NOT NULL, unconditional runtime-scripts copy, discriminated updateThreadMatter 422, restored D21 EXISTS exclusion, shared-population quote scope, spec:272 fold, §10 user register, inverted assertion-removal default and plan:97 authored fix are in-place qualifications. Skip-not-abort (hook skip load-bearing), one-statement digest, query-param review URL, message marker, S/A, MCP IDs-only, no firm who_am_i, HITL reject, personal-account+activity dismiss key and admission ACCEPTS a matter-filed thread remain. Stay in 3 J files; J 190/60/360 =610. Neither reviewer is running. NOT CLEAN. No product/git/runtime/deploy/DB/schema work.

47. Independent Astra 1 / Opus 1, Astra 2, Opus 2–7 after §46 — seven separate ACCEPT dispositions

Seven findings after the §46 snapshot. Record Astra 1 / Opus 1, Astra 2, Opus 2 / Opus 4, Opus 3, Opus 5, Opus 6 and Opus 7 separately. Preserve the settled skip-not-abort (hook skip only), one-statement digest (no chat_thread lock), query-param /tasks/chats/new?review=work, durable assistant message marker, settled S/A, MCP IDs-only, no firm who_am_i, HITL reject, personal-account+activity dismiss key, and admission ACCEPTS a matter-filed thread. Neither reviewer is running. NOT CLEAN.

FindingDisposition, reason and owner
Astra 1 / Opus 1 — listChatsForMatter unmapped on chats.ts.ACCEPT. Map apps/web/lib/matters/chats.ts as its own D path (D 21→22, +1 path, +40). Keep packages/chat-runtime/src/threads.ts as the updateThreadMatter slot. Do not put listChatsForMatter on threads.ts. Re-state §15.5 path count after this plus the other mapped files below.
Astra 2 — §35 skip-both still reads as live.ACCEPT. Mark §35 Astra 1 (skip-both half) and Astra 3 superseded by §46 Opus 2 in place. Sole live J contract: hook skip only; runtime-scripts copy from $BG_RELEASE_DIR with no discriminator.
Opus 2 / Opus 4 — biting second-member suite unmapped.ACCEPT. Map packages/chat-runtime/src/threads.test.ts and apps/web/lib/matters/chats.test.ts as affected existing G paths with lines (G 22→24). The biting second-member assertion lives in chats.test.ts calling listChatsForMatter, not G9. Drop the “biting G9/G10” claim. Opus 4 ACCEPT via Opus 2: chats.test.ts is the named biting suite.
Opus 3 — origin helper file unmapped.ACCEPT. Map packages/connector-clio/src/clio-client.ts as A12 for the export-only origin helper. Do not leave it unmapped.
Opus 5 — expected outputs one short.ACCEPT. Expected outputs = unique paths + 5 generated. Recalculate after the new paths: 118 unique + 5 generated = 123. Do not leave outputs one short.
Opus 6 — B slices do not sum to the packet row.ACCEPT. Re-price the three B slices so they sum to the packet-row 3,120: enrollment 11/1,170 + source 8/1,750 + refresh 2/200 =21/3,120. Leave the packet row and total alone.
Opus 7 — item (10) run-on.ACCEPT. Terminate item (10) with “never equal-by-rounding.” identically on both B7 cards. Start the following sentence separately.

Allocation: §47 +4/160 (D22 apps/web/lib/matters/chats.ts 40, G27 packages/chat-runtime/src/threads.test.ts 40, G28 apps/web/lib/matters/chats.test.ts 40, A12 packages/connector-clio/src/clio-client.ts 40) so D 21→22 /1,950→1,990, G 22→24 /5,670→5,750, A 11→12 /1,490→1,530. Packet rows sum to 20,100; one H 240. Unique paths 114→118 (117 authored plus generated Bun lock). Expected outputs = unique paths + 5 generated = 123. B enrollment 11/1,170 + source 8/1,750 + refresh 2/200 =21/3,120; B packet-row 21/3,120 left alone. Item (10) terminates “never equal-by-rounding.” identically on both B7 cards. §35 Astra 1 skip-both half and Astra 3 superseded by §46 Opus 2 in place. Skip-not-abort (hook only), one-statement digest, query-param review URL, message marker, S/A, MCP IDs-only, no firm who_am_i, HITL reject, personal-account+activity dismiss key and admission ACCEPTS a matter-filed thread remain. Stay in 3 J files; J 190/60/360 =610. Neither reviewer is running. NOT CLEAN. No product/git/runtime/deploy/DB/schema work.

48. Independent Opus 1, Astra 1–2, Opus 2–8 after §47 — ten separate ACCEPT dispositions

Ten findings after the §47 snapshot. Record Opus 1, Astra 1, Astra 2, Opus 2, Opus 3, Opus 4, Opus 5, Opus 6, Opus 7 and Opus 8 separately. Preserve the settled skip-not-abort (hook skip only), one-statement digest (no chat_thread lock), query-param /tasks/chats/new?review=work, durable assistant message marker, settled S/A, MCP IDs-only, no firm who_am_i, HITL reject, personal-account+activity dismiss key, and admission ACCEPTS a matter-filed thread. Neither reviewer is running. NOT CLEAN.

FindingDisposition, reason and owner
Opus 1 — D22 still reads as a biting second-member page guard.ACCEPT. Keep D22 as an explicitly inert forward guard (no production caller today; NOS-384 may wire it). Restore marker EXISTS exclusion in listChatsForMatter (no userId). Delete “one biting G9/G10”, “named biting suite”, and “admitted thread never appears for a second member” from plan:194/:218 and spec:336. Do not drop the helper guard.
Astra 1 — item (10) still run-on on the B7 ID cards.ACCEPT. Terminate item (10) with “never equal-by-rounding.” identically on spec:148 and plan:579. Start the authorization/full-zero sentence separately, identical wording: “Never authorization or full zero totals.”
Astra 2 — G28 still a second-member page assertion.ACCEPT. Rewrite G28 as real SQL like threads.test.ts: seed a marker chat_message; assert the admitted id is absent from listChatsForMatter. Do not add userId. The suite proves the helper, not a page.
Opus 2 — §11 missing I/J/S proof checkboxes.ACCEPT. Three more §11 checkboxes: packet I consent provenance (auth-oauth.test.ts / oauth-auth.integration.test.ts), packet J deployment floor (J3), policy S activation/stream retirement (G9).
Opus 3 — B7 “sole technical authority” covers operational reasons.ACCEPT. Narrow B7 sole technical authority to source-eligibility reasons. H owns operational reasons (budget exhaustion, 20s deadline, cancellation, mid-request authorization loss) with §10.1 strings.
Opus 4 — organization_inactive_or_unverified missing from required-states.ACCEPT. Add organization_inactive_or_unverified to spec:338 required-states and a §10.1 sentence. One P0/P6 line preflighting organization.status = 'active' on target orgs plus the operator fix.
Opus 5 — A/G ordinals disagree with labels.ACCEPT. Move appended existing paths to the end of each A/G list so ordinal and label agree.
Opus 6 — ChatRow generic move failure for marked-thread 422.ACCEPT. One reason-keyed string in ChatRow’s !res.ok branch for marked-thread 422. Map apps/web/components/app-shell/chat-row.tsx as I22 +40.
Opus 7 — Clio read-only suite unmapped.ACCEPT. Map packages/connector-clio/src/read-only.test.ts as affected existing G29 with lines (G 24→25, +1 path, +40).
Opus 8 — “dead column” still on plan:125/:128.ACCEPT. Replace “dead column” with “unused by this lane; no feeder edit” on plan:125 and :128.

Allocation: §48 +2/80 (I22 apps/web/components/app-shell/chat-row.tsx 40, G29 packages/connector-clio/src/read-only.test.ts 40) so I 21→22 /4,030→4,070, G 24→25 /5,750→5,790. Packet rows sum to 20,180; one H 240. Unique paths 118→120 (119 authored plus generated Bun lock). Expected outputs = unique paths + 5 generated = 125. D22 is an inert forward guard; G28 is helper SQL not a page; item (10) terminates “never equal-by-rounding.” on both ID cards; B7 authority is source-eligibility only. Skip-not-abort (hook only), one-statement digest, query-param review URL, message marker, S/A, MCP IDs-only, no firm who_am_i, HITL reject, personal-account+activity dismiss key and admission ACCEPTS a matter-filed thread remain. Stay in 3 J files; J 190/60/360 =610. Neither reviewer is running. NOT CLEAN. No product/git/runtime/deploy/DB/schema work.

49. Independent Opus 1–4, Astra 1 / Opus 5, Opus 6–7, Astra 3 / Opus 8, Astra 2 after §48 — nine separate ACCEPT dispositions

Nine findings after the §48 snapshot. Record Opus 1, Opus 2, Opus 3, Opus 4, Astra 1 / Opus 5, Opus 6, Opus 7, Astra 3 / Opus 8 and Astra 2 separately. Preserve the settled skip-not-abort (hook only), one-statement digest (no chat_thread lock), query-param /tasks/chats/new?review=work, durable assistant message marker, settled S/A, MCP IDs-only, no firm who_am_i, HITL reject, personal-account+activity dismiss key, admission ACCEPTS a matter-filed thread, and D22 as an explicitly inert forward guard. Neither reviewer is running. NOT CLEAN.

FindingDisposition, reason and owner
Opus 1 — applyActivityPage ON CONFLICT omits epoch/origin restamp.ACCEPT. Name source_authorization_epoch and source_origin on applyActivityPage's ON CONFLICT DO UPDATE SET list (activity-events.ts:145–166). G4 asserts restamping of an existing row after a second observation, not only a new insert.
Opus 2 — runtime helpers still sourced from DEPLOY_DIR.ACCEPT. J1 edit at bluegreen.sh:727–738: source="${BG_RELEASE_DIR}/deploy/preprod/bin/${name}.sh". Price inside J1's 190. J3(g) asserts helper bytes on a stale-marker green-lit reuse where DEPLOY_DIR and $BG_RELEASE_DIR deliberately differ. Do not phrase $BG_RELEASE_DIR as current fact.
Opus 3 — D16/D17 claimed same class as D7/D9/memory.ACCEPT. On D16/D17 cards: pre-model window and context_digest_attempt rows are deliberately out of scope (ids/spend only). Stop claiming “same class as D7/D9/memory”. Do not add a D13-style recheck.
Opus 4 — G28 real SQL / apps/web DB integration.ACCEPT. Keep G28 mocked. Put the biting marker-exclusion assertion in packages/chat-runtime/src/threads.test.ts (already live Postgres). Do not add apps/web DB integration.
Astra 1 / Opus 5 — headers and §8 still 118/20,100.ACCEPT. Headers and §8 completion: 120 paths / 20,180. (Superseded by §50: −2/80 → 118 unique paths / 20,100 lines / 123 expected outputs; §15.5 is the sole authority.)
Opus 6 — §15.5 expected outputs still 123.ACCEPT. §15.5 expected outputs 123→125. (Superseded by §50: −2/80 → 118 unique paths / 20,100 lines / 123 expected outputs; §15.5 is the sole authority.)
Opus 7 — H non-visible user string.ACCEPT. H non-visible user string: “Some recorded time we previously ingested no longer appears in Clio's scan.” Labelled-never-refuse unchanged.
Astra 3 / Opus 8 — P3 still “dead column”.ACCEPT. P3: “no feeder edit; cleared_reason is unused by this lane.”
Astra 2 — item (10) second sentence missing on B7 map / §16.2.ACCEPT. Copy the two item-(10) sentences identically onto plan §16.2 and the B7 map card if still missing: “never equal-by-rounding.” then “Never authorization or full zero totals.”

Allocation: §49 +0/0. Unique paths remain 120 / 20,180 (119 authored plus generated Bun lock). Expected outputs 125. (Superseded by §50: −2/80 → 118 unique paths / 20,100 lines / 123 expected outputs; §15.5 is the sole authority.) J stays 190/60/360 =610. D22 remains an inert forward guard. G28 stays mocked; biting marker-exclusion lives in threads.test.ts. Skip-not-abort (hook only), one-statement digest, query-param review URL, message marker, S/A, MCP IDs-only, no firm who_am_i, HITL reject, personal-account+activity dismiss key and admission ACCEPTS a matter-filed thread remain. Stay in 3 J files. Neither reviewer is running. NOT CLEAN. No product/git/runtime/deploy/DB/schema work.

50. Astra CLEAN after §49; independent Opus 1, Opus 1+2, Opus 3–6 — six separate ACCEPT dispositions

Astra returned CLEAN against the §49 integrated revision. Independent Opus then returned six verified findings. Record Opus 1, Opus 1+2, Opus 3, Opus 4, Opus 5 and Opus 6 separately. Preserve the settled skip-not-abort (hook only), one-statement digest (no chat_thread lock), query-param /tasks/chats/new?review=work, durable assistant message marker, settled S/A, MCP IDs-only, no firm who_am_i, HITL reject, personal-account+activity dismiss key, and admission ACCEPTS a matter-filed thread. Neither reviewer is running. NOT CLEAN.

FindingDisposition, reason and owner
Opus 1 — updateThreadMatter(..., null) refused on a marked thread.ACCEPT. updateThreadMatter(..., null) is allowed on a marked thread (unfile). Refuse only a non-null target (cannot file/refile after admission). Admission still ACCEPTS a matter-filed thread. Keep D20.
Opus 1+2 — D22 inert helper untested / cross-workspace.ACCEPT. Drop D22 from this lane. Name the listChatsForMatter marker-EXISTS guard + test as a NOS-384 handoff condition at plan:92. Reclaim chats.ts / chats.test.ts paths and lines. Do not leave an untested helper. Do not add a cross-workspace import. G27 proves discriminated updateThreadMatter only.
Opus 3 — J3(e)/(f) floor mutation vs fixture marker.ACCEPT seed-per-SHA. The fixture seeds the marker per run_deploy SHA (fixture-only; J2 stays the only production writer). Do not have J3(e)/(f) set → assert → restore 0 on the live floor.
Opus 4 — reconciliation enabled before a completed full scan.ACCEPT. One P6/P8 line plus a §11 checkbox requiring an observed completed full Clio scan under the new epoch and origin on the target org before the reconciliation surface is enabled. H sentence in spec §10.1 for the interval.
Opus 5 — hook refresh can read $BG_RELEASE_DIR.ACCEPT. J1 card: the hook refresh runs at :1511, before staging at :1515, so it cannot read $BG_RELEASE_DIR; skip is the only safe form.
Opus 6 — §11 missing mailbox-gate and digest-floor proof.ACCEPT. Two more §11 checkboxes: mailbox-gate exception (G16/G17) and worker-context digest floor (G24).

Allocation: §50 −2/80 (drop D22 apps/web/lib/matters/chats.ts 40 and G28 apps/web/lib/matters/chats.test.ts 40). D 22→21 /1,990→1,950; G 25→24 /5,790→5,750. Packet rows sum to 20,100; one H 240. Unique paths 120→118 (117 authored plus generated Bun lock). Expected outputs = unique paths + 5 generated = 123. listChatsForMatter marker-EXISTS guard + test is a NOS-384 handoff condition at plan:92; do not leave an untested helper; do not add a cross-workspace import. updateThreadMatter(..., null) is allowed on a marked thread (unfile); refuse only a non-null target. J3 fixture seeds the marker per run_deploy SHA (fixture-only; J2 stays the only production writer). Hook refresh runs at :1511, before staging at :1515, so it cannot read $BG_RELEASE_DIR; skip is the only safe form. Skip-not-abort (hook only), one-statement digest, query-param review URL, message marker, S/A, MCP IDs-only, no firm who_am_i, HITL reject, personal-account+activity dismiss key and admission ACCEPTS a matter-filed thread remain. Stay in 3 J files. Neither reviewer is running. NOT CLEAN. No product/git/runtime/deploy/DB/schema work.

51. Independent Astra 1–2 + Opus 1–5 after §50 — seven separate ACCEPT dispositions

Seven findings after the §50 snapshot. Record Astra 1, Astra 2, Opus 1, Opus 2, Opus 3, Opus 4 and Opus 5 separately. Preserve the settled skip-not-abort (hook only), one-statement digest (no chat_thread lock), query-param /tasks/chats/new?review=work, durable assistant message marker, settled S/A, MCP IDs-only, no firm who_am_i, HITL reject, personal-account+activity dismiss key, and admission ACCEPTS a matter-filed thread. D22 stays dropped; listChatsForMatter marker-EXISTS guard + test remains a NOS-384 handoff condition at plan:92. Neither reviewer is running. NOT CLEAN.

FindingDisposition, reason and owner
Astra 1 — D21 mutates without the C/share lock recipe.ACCEPT. D21 card: same chat_thread FOR UPDATE as C/grantThreadShare, then marker check, then mutate. Non-null + marker → refuse; null unfile still allowed.
Astra 2 — unfile clears the marker or re-opens filing/share.ACCEPT. Unfile sets matter_id null only; marker stays. G27: unfile then non-null still refuses. G9: share still not_shareable after unfile.
Opus 1 — admitted threads can appear in matter-scoped listings.ACCEPT. Add the invariant to the §8 floor and a §11 checkbox at capability level: no admitted thread is returned by any matter-scoped thread listing. Keep plan:92 handoff sentence as the delivery note.
Opus 2 — D20 refuses all marked-thread matter moves including unfile.ACCEPT. D20 same discriminated rule: allow targetMatterId === null, refuse non-null. Propagate to spec:272, plan:192, plan:214, G26.
Opus 3 — J3(e) seed vs the SHA run_deploy materializes.ACCEPT. Restate J3(e): run_deploy writes the marker for the SHA it materializes, simulating J2. (a)/(b)/(d)/(g) materialize a different SHA first. (e)'s absence arm removes or omits the seed.
Opus 4 — NULL-origin lumped with origin mismatch.ACCEPT. Give the NULL-origin arm its own enum member carrying the spec:362 sentence (same class as unstamped — awaiting feeder). Leave mismatch on member (6). §11 checkbox is operator-attested with named observable: query activity_sync_state.last_full_scan_completed_at plus stamped source_origin.
Opus 5 — I packet counts a skipped I19 as a path.ACCEPT. I = 21, add “stable labels skip I19”. Recalculate total/outputs. Do not invent a path. (Superseded by §52 Astra 2: I=22; unique 118; outputs 123.)

Allocation: §51 +0/0 (I 22→21 count correction; no new path; lines unchanged). Packet rows sum to 20,100; one H 240. Unique paths 118→117 (116 authored plus generated Bun lock). Expected outputs = unique paths + 5 generated = 122. (Superseded by §52 Astra 2: I=22; unique 118; outputs 123.) D21 lock-then-check-then-mutate; unfile sets matter_id null only and marker stays; D20 same discriminated null-allow/non-null-refuse rule; J3(e) run_deploy writes the marker for the SHA it materializes, simulating J2; NULL-origin own enum member; §8 floor + §11 capability checkbox: no admitted thread is returned by any matter-scoped thread listing; plan:92 handoff sentence remains the delivery note. D22 stays dropped. Skip-not-abort (hook only), one-statement digest, query-param review URL, message marker, S/A, MCP IDs-only, no firm who_am_i, HITL reject, personal-account+activity dismiss key and admission ACCEPTS a matter-filed thread remain. Stay in 3 J files. Neither reviewer is running. NOT CLEAN. No product/git/runtime/deploy/DB/schema work.

52. Independent Astra 1–2 + Opus 1–12 after §51 — fourteen separate ACCEPT dispositions

Fourteen findings after the §51 snapshot. Record Astra 1, Opus 1, Astra 2, Opus 2, Opus 3, Opus 4, Opus 5, Opus 6, Opus 7, Opus 8, Opus 9, Opus 10, Opus 11 and Opus 12 separately. Preserve the settled skip-not-abort (hook only), one-statement digest (no chat_thread lock), query-param /tasks/chats/new?review=work, durable assistant message marker, settled S/A, MCP IDs-only, no firm who_am_i, HITL reject, personal-account+activity dismiss key, and admission ACCEPTS a matter-filed thread. D22 stays dropped; listChatsForMatter marker-EXISTS guard + test remains a NOS-384 handoff condition at plan:92. Neither reviewer is running. NOT CLEAN.

FindingDisposition, reason and owner
Astra 1 — §8/§11 floor hides owner Review-my-work from the matter sidebar.ACCEPT. Scope the §8/§11 floor to firm-wide listings that omit owner (listChatsForMatter, NOS-384). Explicitly exclude owner listThreads / GET /threads?matterId=. Do not hide the owner's Review-my-work chat from the matter sidebar.
Opus 1 — §11 listing box gates this lane.ACCEPT in Astra 1's scope. Mark the §11 box NOS-384-owned so it does not gate this lane's completion. Keep plan:92 handoff. Do not add a G27 assertion that imports apps/web.
Astra 2 — I=21 retired a mapped path.ACCEPT. Keep all 22 I files. I = 22. Unique 118. Outputs 123. “Skip I19” cannot retire a mapped path. Undo the I=21 correction.
Opus 2 — spec:144 enum lags plan:145 members (6)+(11).ACCEPT. Copy plan:145 members (6)+(11) verbatim onto spec:144. Plan:145 is the copy of record for the enum.
Opus 3 — user sentences cited by stale line numbers.ACCEPT. Cite user sentences by name (“spec §10.1 null-origin”), not stale line numbers.
Opus 4 — duplicate operator-interval sentence.ACCEPT. Keep the string on the null-origin member. Delete the duplicate operator-interval sentence.
Opus 5 — member (5) user string uses waiting.ACCEPT. Member (5) below-epoch user string is an observation fact: “Some recorded time was observed under an earlier Clio authorization.” Keep “waiting” only where it refuses (unstamped, null-origin).
Opus 6 — cross-account isolation treated as proved.ACCEPT. On both B7 cards: cross-account isolation rests on unproved global uniqueness of Clio user ids. Change P1 from “different-account rejection” to recording the observed uniqueness scope on the two designated test accounts. Align plan:291. No firm who_am_i.
Opus 7 — reuse discriminator restatement over-deletes PROMOTED.ACCEPT. Introduce no new discriminator (no BG_RELEASE_REUSED); leave existing BG_RELEASE_PROMOTED at :55/:607/:723 untouched.
Opus 8 — marker guards restate the predicate ad hoc.ACCEPT. Export D10's predicate as the single SQL fragment/TS helper. D7/D16/D17/D20/D21 cite it. One assertion that every guard refuses the same seeded marker row, hosted in G24 (apps/worker-context/src/digest.integration.test.ts). Do not put it in G9.
Opus 9 — plan:97/spec:392 restates a live total.ACCEPT. Defer plan:97/spec:392 to §15.5. With I=22 the chain is 118.
Opus 10 — expected outputs not 118+5.ACCEPT. plan:545 expected outputs 123 (118+5).
Opus 11 — J3 misses the mid-hook SHA race.ACCEPT. J3 case: main advances between post-receive's ref check and deploy.sh's SHA derivation → refuse, no staging, later push deploys. One H sentence.
Opus 12 — unfile looks reversible.ACCEPT. One sentence in D20 tool description, prompt.ts, and F3/ChatRow unfile affordance: unfiling an admitted thread is permanent and re-filing is refused. Assert in G26/G27.

Allocation: §52 +0/0 (I 21→22 restore; no new path; lines unchanged). Packet rows sum to 20,100; one H 240. Unique paths 117→118 (117 authored plus generated Bun lock). Expected outputs = unique paths + 5 generated = 123. §8/§11 floor scoped to firm-wide omit-owner listings; §11 box NOS-384-owned; plan:145 members (6)+(11) copied onto spec:144; user sentences cited by name; null-origin string kept, duplicate interval deleted; member (5) observation fact; P1 records uniqueness scope; no new discriminator, existing BG_RELEASE_PROMOTED untouched; D10 predicate exported; unfile permanence asserted. D22 stays dropped. Skip-not-abort (hook only), one-statement digest, query-param review URL, message marker, S/A, MCP IDs-only, no firm who_am_i, HITL reject, personal-account+activity dismiss key and admission ACCEPTS a matter-filed thread remain. Stay in 3 J files. Neither reviewer is running. NOT CLEAN. No product/git/runtime/deploy/DB/schema work.

53. Astra CLEAN after §52; independent Opus 2–6 — five separate ACCEPT dispositions

Astra returned CLEAN against the §52 integrated revision. Independent Opus then returned five verified leftovers. Record Opus 2, Opus 3, Opus 4, Opus 5 and Opus 6 separately. Preserve the settled skip-not-abort (hook only), one-statement digest (no chat_thread lock), query-param /tasks/chats/new?review=work, durable assistant message marker, settled S/A, MCP IDs-only, no firm who_am_i, HITL reject, personal-account+activity dismiss key, and admission ACCEPTS a matter-filed thread. D22 stays dropped; listChatsForMatter marker-EXISTS guard + test remains a NOS-384 handoff condition at plan:92. Floor stays firm-wide omit-owner. I = 22. D10 owns the marker predicate. Neither reviewer is running. NOT CLEAN.

FindingDisposition, reason and owner
Opus 2 — D20 arm hosted in G24 unified assertion / new subpath risk.ACCEPT. Host the D20 arm in G26 packages/agent-runtime/src/tools/move-chat-to-matter.test.ts (already imports the tool). Scope G24's unified assertion to D7/D16/D17/D21. Do not add a ./tools/move-chat-to-matter subpath.
Opus 3 — worker-context/memory integration commands missing from §7.ACCEPT. Add to §7: apps/worker-contextRUN_DB_INTEGRATION=1 REQUIRE_DB_INTEGRATION=1 bunx vitest run src/digest.integration.test.ts src/thread-loader.test.ts and apps/worker-memoryRUN_DB_INTEGRATION=1 bunx vitest run src/backfill.test.ts src/mine.test.ts src/synthesize.integration.test.ts. Name both packages in plan:339 changed-exports with the isolated all-DSN map.
Opus 4 — G24 can skip under the gate; §11 records a green exit.ACCEPT. G24 card: require REQUIRE_DB_INTEGRATION=1 && !RUN throw so the suite cannot skip under the gate. §11 gate record names the executed G24 cases, not a green exit.
Opus 5 — member (11) run-on into the list closer / card prose.ACCEPT. Terminate member (11) at “implementation note: source_origin IS NULL.” Restore “Never authorization or full zero totals.” as the list's own closing sentence, identical on spec:144 and plan:145. Card prose starts separately.
Opus 6 — dropped G28 still an unlabeled skip.ACCEPT. Add “stable labels skip dropped G28” to plan:207 and the §15.5 slice note, beside B19.

Allocation: §53 +0/0 (no new path; lines unchanged). Packet rows sum to 20,100; one H 240. Unique paths 118 (117 authored plus generated Bun lock). Expected outputs = unique paths + 5 generated = 123. D20 arm hosted in G26; G24 unified assertion D7/D16/D17/D21; no ./tools/move-chat-to-matter subpath. §7 names worker-context and worker-memory commands; plan:339 names both packages with the isolated all-DSN map. G24 cannot skip under REQUIRE_DB_INTEGRATION=1 && !RUN throw; §11 names executed G24 cases. Member (11) terminates at the implementation note; list closer identical on spec:144 and plan:145. Stable labels skip dropped G28 beside B19. Floor stays firm-wide omit-owner. I = 22. D10 owns the marker predicate. D22 stays dropped. Skip-not-abort (hook only), one-statement digest, query-param review URL, message marker, S/A, MCP IDs-only, no firm who_am_i, HITL reject, personal-account+activity dismiss key and admission ACCEPTS a matter-filed thread remain. Stay in 3 J files. Neither reviewer is running. NOT CLEAN. No product/git/runtime/deploy/DB/schema work.

54. Independent Astra 1 + Opus 1–6 ACCEPT, Opus 7 NOTE after §53 — eight separate dispositions

Eight findings after the §53 snapshot. Record Astra 1, Opus 1, Opus 2, Opus 3, Opus 4, Opus 5, Opus 6 and Opus 7 separately. Preserve the settled skip-not-abort (hook only), one-statement digest (no chat_thread lock), query-param /tasks/chats/new?review=work, durable assistant message marker, settled S/A, MCP IDs-only, no firm who_am_i, HITL reject, personal-account+activity dismiss key, and admission ACCEPTS a matter-filed thread. D22 stays dropped; listChatsForMatter marker-EXISTS guard + test remains a NOS-384 handoff condition at plan:92. Floor stays firm-wide omit-owner for this lane's owner paths. I = 22. D10 owns the marker predicate. D20 arm stays in G26. G24 stays D7/D16/D17/D21. Neither reviewer is running. NOT CLEAN.

FindingDisposition, reason and owner
Astra 1 — G10 / §11 Home and /matters / usable-UI matrix mixed with NOS-384 matter-row.ACCEPT. Qualify G10, spec §11 “Actual Home and /matters flows”, and the usable-UI matrix the same way as the listing box: Home is this lane and still gates G/P6/P8; /matters matter-row (showActions false / onAction absent / header/cell/menu Review) is NOS-384-owned and does not gate this lane. F5 stays paused. Do not edit matter-table.tsx in this lane.
Opus 1 — J2/J3 “preserve leaked-test-mode refusal”.ACCEPT. Reword to “add” on plan:523/:524/:526/:601 and spec:176. Do not re-price J; the add is inside the priced J2/J3 lines.
Opus 2 — access.integration.test.ts missing from §7; G18 can skip.ACCEPT. Add to §7: packages/agent-runtime RUN_DB_INTEGRATION=1 REQUIRE_DB_INTEGRATION=1 bunx vitest run src/memory/access.integration.test.ts. G18 card: REQUIRE_DB_INTEGRATION=1 && !RUN throw.
Opus 3 — REQUIRE throw not uniform; §11 records a green exit; oauth command missing.ACCEPT. Apply REQUIRE_DB_INTEGRATION=1 plus && !RUN throw uniformly to G5/G8/G9/G18/G19/G21. (Superseded in part by §57 Opus 3: drop the throw from G19; G18/G21 take the G24 skip idiom so the throw bites; G5/G8/G9 stay new files with the throw. (Superseded by §58 Opus 2 for G18/G21: both already run unconditionally, so they keep plain describe and take the throw only; describeIntegration stays G24’s alone.)) §11 names executed case ids for those, not a green exit. Add oauth-auth.integration.test.ts §7 command (existing test:oauth-integration already has the throw).
Opus 4 — firm-wide omit-owner still in the §8 rollout floor.ACCEPT. Drop “Firm-wide listings that omit owner (listChatsForMatter, NOS-384) exclude admitted threads…” from the §8 rollout floor. Keep it as the plan:92 NOS-384 prerequisite only. Do not add a C/D activation guard for it. Do not re-map D22.
Opus 5 — §51 still reads I=21 / unique 117 / outputs 122 / skip I19.ACCEPT. Mark §51 Opus 5 superseded in place by §52 Astra 2: I=22; unique 118; outputs 123. All three sites (plan:1299, plan:1302, spec:430).
Opus 6 — enum members (4) and (11) withhold/refuse without the complete-figure shape.ACCEPT. Restate enum members (4) and (11) in the “a complete figure is unavailable” shape used by (1) and (9). Do not withhold rows. Align H user strings (unstamped / null-origin) with that. Identical on spec:144 and plan:145.
Opus 7 — STATUS.md vs index.html document-count off-by-one.NOTE only. STATUS.md vs index.html document-count off-by-one is a pre-existing generator discrepancy (HEAD 279/24 vs 280/29); this lane does not hand-edit the generator. Do not invent a new issue.

Allocation: §54 +0/0 (no new path; lines unchanged). Packet rows sum to 20,100; one H 240. Unique paths 118 (117 authored plus generated Bun lock). Expected outputs = unique paths + 5 generated = 123. Home still gates G/P6/P8; /matters matter-row is NOS-384-owned and does not gate this lane; F5 stays paused; do not edit matter-table.tsx in this lane. J2/J3 add leaked-test-mode refusal inside priced J lines. §7 names agent-runtime access.integration and oauth-auth.integration commands; G5/G8/G9/G18/G19/G21 cannot skip under REQUIRE_DB_INTEGRATION=1 && !RUN throw (Superseded by §57 Opus 3 for G19 (mocked) and by §58 Opus 2 for G18/G21 — plain describe, throw only); §11 names executed cases. Firm-wide omit-owner listings stay the plan:92 NOS-384 prerequisite only, not the §8 floor. §51 Opus 5 superseded by §52 Astra 2 (I=22; unique 118; outputs 123). Members (4) and (11) use the complete-figure-unavailable shape without withholding rows. D22 stays dropped. Floor stays firm-wide omit-owner for this lane's owner paths. I = 22. D10 owns the marker predicate. D20 arm stays in G26. G24 stays D7/D16/D17/D21. Skip-not-abort (hook only), one-statement digest, query-param review URL, message marker, S/A, MCP IDs-only, no firm who_am_i, HITL reject, personal-account+activity dismiss key and admission ACCEPTS a matter-filed thread remain. Stay in 3 J files. Neither reviewer is running. NOT CLEAN. No product/git/runtime/deploy/DB/schema work.

55. Astra CLEAN after §54; independent Opus 1–2 — two separate ACCEPT dispositions

Astra returned CLEAN against the §54 integrated revision. Independent Opus then returned two verified leftovers. Record Opus 1 and Opus 2 separately. Preserve the settled skip-not-abort (hook only), one-statement digest (no chat_thread lock), query-param /tasks/chats/new?review=work, durable assistant message marker, settled S/A, MCP IDs-only, no firm who_am_i, HITL reject, personal-account+activity dismiss key, and admission ACCEPTS a matter-filed thread. D22 stays dropped; listChatsForMatter marker-EXISTS guard + test remains a NOS-384 handoff condition at plan:92. G10 Home gates this lane; /matters is NOS-384. Floor stays firm-wide omit-owner for this lane's owner paths. I = 22. D10 owns the marker predicate. D20 arm stays in G26. G24 stays D7/D16/D17/D21. REQUIRE_DB_INTEGRATION=1 && !RUN throw on G5/G8/G9/G18/G21/G24 — G19 dropped (mocked; §57 Opus 3), G18/G21 keep plain describe (§58 Opus 2). Enum (4)/(11) complete-figure-unavailable. Neither reviewer is running. NOT CLEAN.

FindingDisposition, reason and owner
Opus 1 — RR commit “before any provider call” swallows identity GETs.ACCEPT. Replace “before any provider call” with “before any exact-entry validation call” on spec:154, spec:303, plan:165, plan:226. State the order once on the B7 card: single identity resolution → RR snapshot → commit → exact validations. Mark plan:933 refined in place; keep no-provider-work-inside-the-open-transaction. Identity GETs stay in the 20-second source phase, not C prepare.
Opus 2 — $actor vs enrollment-stored personal user id.ACCEPT. One sentence on the B7 card: $actor is this request’s resolved who_am_i data.id, never the enrollment-stored personal user id. Add one G5 case: live who_am_i data.id ≠ enrollment-stored personal user id → identity-unverified refuse, zero rows disclosed. Do not revive the dropped different-account dismiss-key G5.

Allocation: §55 +0/0 (no new path; lines unchanged). Packet rows sum to 20,100; one H 240. Unique paths 118 (117 authored plus generated Bun lock). Expected outputs = unique paths + 5 generated = 123. Order once on the B7 card: single identity resolution → RR snapshot → commit → exact validations. Identity GETs stay in the 20-second source phase, not C prepare. No provider work inside the open transaction. $actor is this request’s resolved who_am_i data.id, never the enrollment-stored personal user id. G5 live who_am_i mismatch refuses identity-unverified with zero rows disclosed. Do not revive the dropped different-account dismiss-key G5. D22 stays dropped. G10 Home gates this lane; /matters is NOS-384. Floor stays firm-wide omit-owner for this lane's owner paths. I = 22. D10 owns the marker predicate. D20 arm stays in G26. G24 stays D7/D16/D17/D21. REQUIRE_DB_INTEGRATION=1 && !RUN throw on G5/G8/G9/G18/G21/G24 — G19 dropped (mocked; §57 Opus 3), G18/G21 keep plain describe (§58 Opus 2). Enum (4)/(11) complete-figure-unavailable. Skip-not-abort (hook only), one-statement digest, query-param review URL, message marker, S/A, MCP IDs-only, no firm who_am_i, HITL reject, personal-account+activity dismiss key and admission ACCEPTS a matter-filed thread remain. Stay in 3 J files. Neither reviewer is running. NOT CLEAN. No product/git/runtime/deploy/DB/schema work.

56. Independent Astra 1 + Opus 1–2 after §55 — three separate ACCEPT dispositions

Three findings after the §55 snapshot. Record Astra 1, Opus 1 and Opus 2 separately. Preserve the settled skip-not-abort (hook only), one-statement digest (no chat_thread lock), query-param /tasks/chats/new?review=work, durable assistant message marker, settled S/A, MCP IDs-only, no firm who_am_i, HITL reject, personal-account+activity dismiss key, and admission ACCEPTS a matter-filed thread. D22 stays dropped; listChatsForMatter marker-EXISTS guard + test remains a NOS-384 handoff condition at plan:92. G10 Home gates this lane; /matters is NOS-384. Floor stays firm-wide omit-owner for this lane's owner paths. I = 22. D10 owns the marker predicate. D20 arm stays in G26. G24 stays D7/D16/D17/D21. REQUIRE_DB_INTEGRATION=1 && !RUN throw on G5/G8/G9/G18/G21/G24 — G19 dropped (mocked; §57 Opus 3), G18/G21 keep plain describe (§58 Opus 2). Enum stays 11 members. Do not add member (12). No new path. Do not re-price a packet. B7 order stays identity GET → RR snapshot → commit → exact validations. Neither reviewer is running. NOT CLEAN.

FindingDisposition, reason and owner
Astra 1 — combined fourth origin predicate.ACCEPT. Split the combined fourth predicate. source_origin IS NULL is (11) only (complete figure unavailable; do not withhold rows). source_origin <> $personalVerifiedOrigin when non-null is (6) identity-unverified. Delete source_origin IS NULL OR source_origin <> … as a single arm. Recorded-time projection, canonical/dismiss keys, and exact GET require source_origin = $personalVerifiedOrigin. (Superseded by §58 Opus 1: the predicate is source_origin IS NULL OR source_origin = $personalVerifiedOrigin; NULL-origin rows are keyed and never withheld.) Existence of non-null differing origin is (6) coverage EXISTS that refuses the named figure; those rows are not keyed as personal and do not occupy the personal LIMIT 200. Matching-origin rows are not withheld. G5 two cases, not one combined: stamped-epoch/NULL-origin is (11) — that fixture asserts the row IS in the projection, IS keyed under the actor’s verified origin, AND that the named figure is refused with (11); non-null origin differs is (6) — that fixture asserts the row is NOT keyed, NOT in the LIMIT 200, and coverage EXISTS. G5: an AU-origin row is not in the keyed set; no collision with a US source_id; no exact GET on US origin for the AU id. Matching-origin rows are not withheld.
Opus 1 — live who_am_i versus enrollment-stored personal user id before the snapshot.ACCEPT. On the B7 card, before the snapshot: if live who_am_i data.id ≠ enrollment-stored personal user id, refuse identity-unverified, zero rows, no snapshot. Reuse (6) H string. Do not add member (12). $actor remains this-request who_am_i data.id; the comparison is request-level admission, not a row predicate. G5 case stays.
Opus 2 — spec:408 still “before any provider call”.ACCEPT. Mark spec:408 refined in place like plan:933: no provider work inside the open transaction; identity GETs stay in the 20-second source phase.

Allocation: §56 +0/0 (no new path; lines unchanged). Packet rows sum to 20,100; one H 240. Unique paths 118 (117 authored plus generated Bun lock). Expected outputs = unique paths + 5 generated = 123. Split combined fourth predicate: source_origin IS NULL is (11) only; non-null origin differs is (6). G5 two cases. Before the snapshot, live who_am_i mismatch refuses identity-unverified with zero rows and no snapshot; reuse (6) H string; do not add member (12); $actor remains this-request who_am_i data.id. spec:408 refined in place like plan:933. B7 order stays identity GET → RR snapshot → commit → exact validations. D22 stays dropped. G10 Home gates this lane; /matters is NOS-384. Floor stays firm-wide omit-owner for this lane's owner paths. I = 22. D10 owns the marker predicate. D20 arm stays in G26. G24 stays D7/D16/D17/D21. REQUIRE_DB_INTEGRATION=1 && !RUN throw on G5/G8/G9/G18/G21/G24 — G19 dropped (mocked; §57 Opus 3), G18/G21 keep plain describe (§58 Opus 2). Enum stays 11 members. Skip-not-abort (hook only), one-statement digest, query-param review URL, message marker, S/A, MCP IDs-only, no firm who_am_i, HITL reject, personal-account+activity dismiss key and admission ACCEPTS a matter-filed thread remain. Stay in 3 J files. Neither reviewer is running. NOT CLEAN. No product/git/runtime/deploy/DB/schema work.

57. Independent Astra 1 + Opus 1–5 after §56 — six separate ACCEPT dispositions

Six findings after the §56 snapshot. Record Astra 1, Opus 1, Opus 2, Opus 3, Opus 4 and Opus 5 separately. Preserve the settled skip-not-abort (hook only), one-statement digest (no chat_thread lock), query-param /tasks/chats/new?review=work, durable assistant message marker, settled S/A, MCP IDs-only, no firm who_am_i, HITL reject, personal-account+activity dismiss key, and admission ACCEPTS a matter-filed thread. D22 stays dropped; listChatsForMatter marker-EXISTS guard + test remains a NOS-384 handoff condition at plan:92. G10 Home gates this lane; /matters is NOS-384. Floor stays firm-wide omit-owner for this lane's owner paths. I = 22. D10 owns the marker predicate. D20 arm stays in G26. G24 stays D7/D16/D17/D21. Enum stays 11 members. Do not add member (12). No new path. Do not re-price a packet. B7 order stays identity GET → RR snapshot → commit → exact validations. (11) NULL-origin complete-figure-unavailable do not withhold. (6) non-null origin differs. Neither reviewer is running. NOT CLEAN.

FindingDisposition, reason and owner
Astra 1 — personal keyed set vs differing origin.ACCEPT. Recorded-time projection, canonical/dismiss keys, and exact GET require source_origin = $personalVerifiedOrigin. (Superseded by §58 Opus 1: the predicate is source_origin IS NULL OR source_origin = $personalVerifiedOrigin; NULL-origin rows are keyed and never withheld.) Existence of non-null differing origin is (6) coverage EXISTS that refuses the named figure; those rows are not keyed as personal and do not occupy the personal LIMIT 200. Matching-origin rows are not withheld. G5: an AU-origin row is not in the keyed set; no collision with a US source_id; no exact GET on US origin for the AU id. (11) NULL-origin complete-figure-unavailable do not withhold. Keep the §56 split: NULL-origin is (11) only; non-null origin differs is (6).
Opus 1 — noncanonical stored user_source_id silently omitted.ACCEPT. Coverage EXISTS for noncanonical stored user_source_id in the selected window (visible TimeEntry, ingest-connector, date/matter) that is not equal to $actor. Member (10) fires. No complete-empty / silent omit. G5: stored user_source_id = '9007199254740992' with live data.id = '9007199254740993' must not publish a complete figure. Do not add member (12).
Opus 2 — enrollment-stored personal user id has no named key.ACCEPT. Persist who_am_i data.id at enrollment inside existing B18 encrypted provider_metadata as personal_user_id (canonical decimal string). State that key on B3/B4/B5/B18 so the pre-snapshot live≠stored comparison has a left-hand side. No new path, no new column. Keep request-level admission, reuse (6) H string, no member (12).
Opus 3 — G19 REQUIRE throw is mocked; G18/G21 throw does not bite.ACCEPT. Drop the REQUIRE_DB_INTEGRATION=1 && !RUN throw from G19 (mocked; G18 owns SQL races). G18 and G21 get the G24 skip idiom (runIntegration/describeIntegration plus REQUIRE && !RUN throw) so the throw bites. (Superseded by §58 Opus 2 for G18/G21: both already run unconditionally, so they keep plain describe and take the throw only; describeIntegration stays G24’s alone.) G5/G8/G9 stay as new files with the throw.
Opus 4 — share-first admission refuse has no operational sentence.ACCEPT. Add operational required-state already_shared_thread and a §10.1 operational sentence for share-first admission refuse (already-shared thread). Not an enum member. F3/prompt cite that sentence.
Opus 5 — spec:304 complete-figure gate reverses labelled (2)(3)(5).ACCEPT. spec:304 and the twin C figure-gate sentence carve out labelled coverage (2)(3)(5). Do not reverse those label-only members.

Allocation: §57 +0/0 (no new path; lines unchanged). Packet rows sum to 20,100; one H 240. Unique paths 118 (117 authored plus generated Bun lock). Expected outputs = unique paths + 5 generated = 123. Recorded-time projection, canonical/dismiss keys, and exact GET require source_origin = $personalVerifiedOrigin. (Superseded by §58 Opus 1: the predicate is source_origin IS NULL OR source_origin = $personalVerifiedOrigin; NULL-origin rows are keyed and never withheld.) Differing-origin rows are (6) coverage EXISTS, not keyed as personal, not in LIMIT 200; matching-origin rows are not withheld. (11) NULL-origin do not withhold. Member (10) coverage EXISTS for noncanonical stored user_source_id$actor; no complete-empty / silent omit. Named key provider_metadata.personal_user_id on B3/B4/B5/B18. Drop REQUIRE throw from G19; G18/G21 use G18/G21 keep plain describe with the REQUIRE && !RUN throw only (§58 Opus 2); G5/G8/G9 stay new files with the throw; G24 keeps describeIntegration. Operational already_shared_thread + §10.1 share-first sentence; F3/prompt cite it; not an enum member. spec:304 and C figure-gate carve out labelled (2)(3)(5). D22 stays dropped. G10 Home gates this lane; /matters is NOS-384. Floor stays firm-wide omit-owner for this lane's owner paths. I = 22. D10 owns the marker predicate. D20 arm stays in G26. G24 stays D7/D16/D17/D21. Enum stays 11 members. Do not add member (12). B7 order stays identity GET → RR snapshot → commit → exact validations. Skip-not-abort (hook only), one-statement digest, query-param review URL, message marker, S/A, MCP IDs-only, no firm who_am_i, HITL reject, personal-account+activity dismiss key and admission ACCEPTS a matter-filed thread remain. Stay in 3 J files. Neither reviewer is running. NOT CLEAN. No product/git/runtime/deploy/DB/schema work.

58. Independent Opus after §57 — five separate ACCEPT dispositions; plan review CLOSED

Five findings after the §57 snapshot, all ACCEPT and all folded in place. The reviewer is a fresh read-only Opus 5 seat against c767e888; its citation spot-check and the §15.5 packet arithmetic (12+21+3+21+8+24+4+22+3 = 118 paths; 1,530+3,120+1,570+1,950+1,260+5,750+240+4,070+610 = 20,100 lines; 118+5 = 123 outputs) both reconcile. kwiss closed plan review at this section on 2026-09-16: the next artifact this lane produces is code. No further plan-review round is authorized; findings from here on are raised against the implementation, not against these two pages. The Grok deletions seat and the Codex-backed seats are unavailable (Grok out of credits to ~21 September; Codex bucket at 86% of its 7-day allowance), so §7 adversarial convergence runs with Astra + Opus only and that substitution is recorded here rather than absorbed silently.

FindingDisposition, reason and owner
Opus 1 — member (11) “do not withhold rows” is unimplementable under the §57 projection predicate.ACCEPT, substantive. source_origin = $personalVerifiedOrigin is never true for a NULL stamp, so every pre-stamp row was withheld — the silent absence §8 forbids (“Hidden hours are still an entry”), across the whole pre-B3 corpus that P3 deliberately stamps NULL. Widen the predicate to source_origin IS NULL OR source_origin = $personalVerifiedOrigin at every live site (plan §§1/3 A3/B7/C, spec §§3.2/4/8): NULL-origin rows stay in the projection, are keyed under the actor’s own verified origin, and (11) refuses the named figure only — the same (4)/(11) complete-figure-unavailable shape §54 Opus 6 accepted, and exactly what spec §3.2/§8 already meant by “shared population ∧ source_origin IS NULL → null-origin refuse”. Member (6) is unchanged: non-null differing origin is coverage EXISTS, not keyed, not in the LIMIT 200. G5’s two cases are now separately assertable — (11) asserts row present + keyed + figure refused; (6) asserts not keyed + not in LIMIT 200 + coverage EXISTS.
Opus 2 — the G18/G21 skip idiom converts two unconditional suites into opt-in suites.ACCEPT, verified against current source. packages/agent-runtime/src/memory/access.integration.test.ts:110 and apps/worker-memory/src/synthesize.integration.test.ts:101 use a plain describe against OWNER_DATABASE_URL || DATABASE_URL and run under each package’s bare test script today; only apps/worker-context/src/digest.integration.test.ts:23–24 carries runIntegration/describeIntegration. Adopting G24’s idiom would have made both skip whenever RUN_DB_INTEGRATION is unset — including plan §7’s bare bun run test — silently dropping G18’s RLS owner-wall/lifecycle proof and G21’s watermark proof, a coverage loss from a change whose whole purpose was to stop suites skipping. G18/G21 keep plain describe and take only the REQUIRE_DB_INTEGRATION=1 && !RUN throw. describeIntegration stays G24’s alone; G5/G8/G9 are new files and keep it with the throw. G19 stays dropped per §57 Opus 3.
Opus 3 — §49 restates 120 paths / 20,180 lines / 125 outputs as current, unmarked.ACCEPT, editorial but load-bearing. §50 dropped D22/G28 (−2/80) back to 118/20,100/123 and §15.5 is sole authority, yet §49’s two disposition rows and its Allocation say “remain”, so a worker pricing the packet from §49 top-down ships a 20,180 cap and 125 outputs. Marked superseded by §50 at all three sites, the same treatment §54 Opus 5 required of §51.
Opus 4 — §54 Opus 3’s uniform-throw list (including G19) is still live in six restatements.ACCEPT. §57 Opus 3 dropped the throw from G19 (mocked; G18 owns the SQL races) and plan:1349 carries it, but the §54 Allocation and the §55/§56/§57 header preserve-lists still name G19 as a live invariant, and the spec’s §54 row was unmarked entirely, so spec and plan disagreed about one disposition. The four live preserve-lists now read G5/G8/G9/G18/G21/G24 with both carve-outs named; the §54 Allocation and spec §12’s §54 row are marked in place.
Opus 5 — deploy.sh:690 is cited as the DEPLOY_SHA assignment.ACCEPT, citation only. Current source: :690 checkout_sha="$(git --git-dir="$BARE_GIT_DIR" rev-parse main)", then :692 DEPLOY_SHA="$existing_sha" / :695 DEPLOY_SHA="$checkout_sha". The substance is unchanged — DEPLOY_SHA derives from rev-parse main, not DEPLOY_TARGET_SHA, which is why §26’s stage-release refuse is what stops a direct deploy.sh "$DEPLOY_DIR" after a superseded abort, and why the changelog comparison at :701–705 cannot catch it. Cite :690–696 at all four sites.

Allocation: §58 +0/0 — no new path, no new line. Packet rows still sum to 20,100; unique paths 118 (117 authored plus the generated Bun lock); expected outputs = 118 + 5 generated = 123; §15.5 remains the sole authority. Recorded-time projection, canonical/dismiss keys, and exact GET require source_origin IS NULL OR source_origin = $personalVerifiedOrigin. Enum stays 11 members; do not add member (12). REQUIRE_DB_INTEGRATION=1 && !RUN throw on G5/G8/G9/G18/G21/G24. Skip-not-abort (hook only), one-statement digest, query-param review URL, assistant marker, S/A, MCP IDs-only, no firm who_am_i, HITL reject, personal-account+activity dismiss key and admission ACCEPTS a matter-filed thread all remain. CLEAN. Plan review closed; implementation may start.

59. kwiss, 2026-09-16 — one admin-level connection; there is no personal chain

Product decision, not a review disposition. It supersedes every personal-Clio-enrollment requirement in this plan and in the design spec. His words: "Il n'y a pas de personnel qui est au chain, il ne doit pas y en avoir, il ne peut pas y en avoir parce que mes clients ne peuvent pas se connecter sur leur account, sur Nord, parce que si je fais ça pour chaque connexion, ça va être abusé. Donc ce qu'il faut, c'est une seule connexion niveau admin, c'est ce qu'il y a actuellement et c'est ce qu'on va utiliser."

The constraint is stronger than a preference between two identity mechanisms: a per-user Clio connection is not permitted to exist. A firm's lawyers cannot be asked to connect their own accounts to North, and requiring it per connection would be abusive to them. The single organization-scoped Clio connection that exists today is the only one there will ever be, for every tenant. This closes the question permanently; it is not deferred and it is not to be reopened by a later review round.

Origin of the decision. The sibling actionable-matters lane needed a viewer's Clio identity and had been waiting on this lane's personal enrollment to supply it. kwiss overturned that on 2026-09-16 (their commit 2f917c29) in favour of the organization's own Clio directory joined on verified email, then generalised the rule to this lane. Both lanes put the generalisation to him and he ruled as above.

59.1 What this supersedes, and what it does not

Superseded — do not build:

Survives unchanged:

59.2 Viewer identity after the decision

The viewer's Clio identity resolves from the organization's own Clio directory: one org-scoped GET /api/v4/users.json?fields=id,name,first_name,last_name,email,enabled through the existing organization connector, joined to the Better Auth account on exact lowercased email equality, admitted only when that address sits on one of the organization's registered mail domains (organization.domain plus organization_domain) and the Clio user is enabled. No display-name comparison, no local-part heuristic, no hardcoded tenant domain.

Ambiguity refuses; it never picks. More than one enabled Clio user for a verified address inside a registered domain resolves to unmapped with a named reason. Measured on the live HSE directory: Clio addresses are not normalized (SBeckerman@hseny.com), and one address can carry two user records (jkwak@, tlee@ — all four currently disabled, which is luck rather than design). For the sibling's matters column a wrong pick is a wrong row; here it would show a lawyer another lawyer's billable hours, so the stricter rule governs both lanes.

Measured coverage (sibling lane, live HSE tenant, 2026-09-16): 59 Clio users, 31 enabled, every one carrying an email; 27 of 29 firm humans join on email and 25 resolve to an enabled Clio user, against 0 of 34 for display names. All 46 distinct user_source_id values in clio_activity are present in that directory with none orphaned, and 21 of the 46 resolve to a North account — that is the population this ledger can currently attribute work to. The remainder is covered by a recorded manual override, not by a new connection.

Open and owned by this lane: where the resolved (organization_id, user_id, clio_user_id) triple is stored and how a manual override is recorded — the measured case is Clio jjones@hseny.com against North jeremy@forma.law. This lane holds the schema slot, so it sequences that table; the sibling lane will not run db:generate.

59.2a Correction — enabled decides the link's state, not whether a link exists

Adopted 2026-09-16 from the actionable-matters lane, and verified here against live preprod. The rule as §59.2 first stated it admitted the join only for enabled Clio users. That permanently denies a link to anyone already disabled at the first refresh — and with no link, their own past work is invisible to them forever. unlinked_at only ever protects someone who was linked while enabled; it does nothing for the initial state. That is the silent-absence failure spec §8 forbids ("Hidden hours are still an entry"; never absence), reached by a different route than the delete-on-disable case it replaced: the transition was fixed and the initial state left broken.

Corrected rule. A single candidate in a registered domain is linked whatever its enabled value, with unlinked_at stamped at insert when the directory already reports it disabled. Ambiguity is evaluated over all candidate records rather than only enabled ones — which changes no current outcome but makes the refusal correct by construction rather than by luck.

Measured on live preprod from this lane. The case is real and it is one person: tbrennan@hseny.com is a North member of the organization, sits in a registered domain, has exactly one Clio directory record (id 358934131, enabled: false), and holds 465 clio_activity rows. He is the only disabled directory user who is both a North member and holds activity, so the whole cost of the original rule was one person and 465 entries of their own work — all of it invisible, with no reason shown.

Recount over the 34 North members: 25 live links, 1 created already unlinked, 1 refused as ambiguous, 7 with no candidate or outside a registered domain. Corpus attribution rises from 20 to 21 of the 46 distinct user_source_id values. The sibling lane's originally reported 21 was the looser count and happened to coincide with what the corrected rule produces; this lane's 20 was correct under the rule as written. Both numbers were right about different things, which is why the one-row discrepancy was worth reconciling rather than carrying.

Reader obligation. A link carrying unlinked_at still attributes its work. The reader reports the state; it never withholds the rows. A lawyer whose Clio account has been disabled sees their history with an honest reason attached, not an empty ledger.

59.3 A correction this lane must not repeat

Both lanes were briefly told that the decision turns two partial-reason members — (6) identity-unverified-origin and (11) null-origin — into unreachable code. That was over-claimed and is withdrawn. (11) survives unchanged and is in fact the initial state of the entire corpus: preprod's clio_activity carries neither stamped column today, and 0118 adds both nullable with no backfill, so on deploy all 63,802 existing rows are NULL-origin. (6) survives in a narrower form — with one connector it can no longer mean "this row belongs to a different Clio account", but it still fires for rows ingested under a previous connector origin after a re-enrollment pointed at another region. What collapses is the enrollment chain and the identity-verification half of packet B, not the origin enum. The decision rests on kwiss's product reason, which stands on its own.

Consequence for fixtures: because NULL-origin is the corpus's starting state rather than an edge case, G5's seeds must construct the stamped case, which is the inverse of the earlier assumption. And since the live corpus carries zero redacted, zero null-date, zero null-user, zero null-matter and nothing unseen, every degraded branch in this design is exercisable only by a synthetic fixture — a green run against preprod is not evidence for any of them.

59.4 Allocation

Packet B drops its enrollment slice: 21 files / 3,120 lines becomes the source slice (8 / 1,750) plus the refresh slice (2 / 200) plus what survives of B3/B5/B8, and gains the resolved-identity mapping table and its reader. The exact recount is deferred to the next packet-B brief rather than guessed here; §15.5's totals are stale from this section onward and must not be quoted as current. No other packet changes. Status stays in-progress.

60. kwiss, 2026-09-16 — packet F is cut; /tasks/chats/new is not a destination

Two corrections, both his, both recorded because the plan asserted the opposite.

Packet F is cut. His words: "on a jamais parlé de F ensemble c'est pas ce que je veux la page /matters c'est une visualisation le mcp c'est l'input l'action la lecture les demandes." He never agreed to packet F and does not want a bespoke review surface. The architecture is: /matters is a visualisation, owned by the sibling actionable-matters lane; input, action, reading and requests happen through MCP. A dedicated WorkLedgerReview component with its own Hono router, its own reconciliation POST and its own connect control would have been a second place to maintain every rule this design carries — the partial reasons, the unmapped and unlinked states, structure editing — competing with the tool surface for the same behaviour. F5 was already paused and §59 had already killed F3's connect control.

/tasks/chats/new?review=work&matter_id= is not a destination. That route is going away. It appears 41 times in this plan and 5 times in the design spec as a settled decision; every one of those references is superseded and must not be built against. It was carried forward through roughly fifty review rounds without anyone asking whether the route had a future, and this lane repeated it — including to the sibling lane as a settled contract, which was wrong and has been corrected with them. Repetition across review rounds is not verification. No replacement destination is recorded here, because none has been decided.

60.1 Measured contents, replacing the projected totals

§15.5's file and line totals were already stale at §59 and the deferred recount never happened. They are withdrawn, not updated — a projection that has been wrong twice should not be re-projected a third time. What this packet actually contains, measured from the branch rather than estimated:

Future accounting states what landed, measured. It does not carry forward an estimate.

60.2 Carried into the pull request, not fixed under time pressure

61. PR #444 — foundation-only corrections, 2026-09-16

Authority and status. This section supersedes §60.1's blanket packet-completion wording and the current-tense defect claims in §60.2; it preserves their historical record. §§59–60 still remove personal Clio enrollment and packet F. This packet supplies schema, authority/recency primitives and bounded source readers, not a ledger service, UI, product tool or MCP feature. No new roadmap or replacement review destination is authorized. The broader plan/spec remain in-progress.

Review boundary. Initial Astra review examined df23de95890f57b750eceab269f4a2b41d321ee2 against 7b017a97d3deabdd29351cbecbcc54cdbc036eb4 and requested changes. Corrections are in the uncommitted working tree; there is no final corrected SHA. Fresh final review and independent-family review remain outstanding. Prior plan-review closure and prior CI do not establish implementation convergence, merge readiness or deployment.

All seven original PR limitations. Unsigned snapshot, always-null prepared origin, always-null ingest origin and missing nested-isolation rejection are corrected in source, pending final review. The independent episode-owner predicate remains redundant with RLS; effective owner/member/status denial is now tested without BYPASSRLS. Empty identity mapping remains an explicit sibling dependency. Load-time sql.raw has no verified residual failure in the exercised mocks; no general module rewrite is claimed.

Evidence, not final approval. Worker reports: authority 31, recency/RLS 15, isolation 8; full DB suite 353 passed / 63 skipped (48 files passed / 16 skipped); Graph targeted 29 and full provider 145 passed / 1 skipped; Clio routes 72, integration 27, runtime 12, auth 33, worker 26 passed. Clio opt-out intentionally skipped 27; missing gate prerequisites failed collection. Root lint/typecheck reported 62/62 tasks plus scripts; existing warnings remain. Initial Astra 242 passed / 43 skipped is pre-fix evidence, not final proof. Disposable-DB CI now explicitly enables the DB proofs; final pushed-head CI is still to be recorded. No merge/deploy has occurred in this handover.

R2 correction: the source resolver now requires an exact member incarnation and direct-session or MCP-grant descriptor, validates A5 under both actor GUCs before signing, and carries that signed authority through prepare and scan for fresh ordered-lock validation. A5 discovers, sorts/deduplicates and locks surviving token-session references before clients/tokens, then rejects changed links; deleting a browser session does not revoke a durable grant. Scan work is bounded by the earlier pin expiry/source deadline, checks cancellation after waits and before subsequent queries, settles started work and rechecks expiry before disclosure. The three DB suites gate teardown writes on successful database verification and always close connections. Clone regressions and mutation proofs cover these corrections; this is not independent-family approval or authorization for service/UI/MCP work.

R3 correction: the retained safe-range stored-ID and origin boundaries now govern projection and overflow, with separate unavailable-figure coverage and NULL-origin row retention. Real PostgreSQL regressions cover adjacent rounded mapper IDs, unsafe activity/matter/source-matter keys, 200 previous-origin rows, session/access expiry during admitted corpus work, and second-connection revocation while KMS is held. A5 uses wall-clock expiry and exposes only a transaction-local expiry bound alongside its unchanged verdict contract; consumers check that bound after transaction settlement. Installed oauth-provider 1.6.23 rotation and revocation functions were exercised against synthetic SQL: rotation retains old access, whereas actual refresh revocation deletes child access. Issuance still requires a usable refresh. Prepare decrypts outside locks, then locks and compares the connector/credential fence and revalidates authority before signing. Exact Graph five-attempt/100-record completions pass, and the existing real resolver proof is included in the step-local CI gate. Earlier refresh-revocation smoke wording conflated rotation with access revocation and is withdrawn, not relabelled as proof. These are scoped fixes, not final adversarial approval; no schema, grant, service, UI or MCP expansion.

R4 correction: recency rejects a linked Clio actor outside the retained positive safe-integer range before correlating activity, so a rounded legacy id cannot attribute another provider user's metadata. An unknown expected origin independently refuses the named Clio figure even for a truly empty corpus, while NULL-origin rows remain visible. The personal Sent Items reader disables SDK redirects as well as retries, making each charged HTTP attempt one transport request without changing other Graph consumers. Synthetic provider fixtures use neutral identities and a reserved domain. These remain foundation-only corrections; no schema, migration, grant, service, UI, MCP or ProviderContext ABI change is authorized.

R5 bounded correction: the Clio aggregate now sums stored PostgreSQL numeric hours inside the bounded eligible projection and rounds once only after exact summation; per-row floating display minutes are never summed. Regressions pin 0.285 + 0.04 hours to 20 minutes and preserve the negative half-minute boundary. Fresh OAuth completion stamps the normalized origin of the actual ClioClient that performed the exchange, even when injected client configuration differs; legacy unknown-origin refresh and established-origin mismatch refusal remain unchanged. The mapping comment now states that a unique disabled candidate is linked already unlinked and that ambiguity spans all candidates; it does not claim a future writer implementation. No schema, migration or grant changes. New comments and the transaction-options type use organization/account terminology; retained wire fields and withSyncTenantTransaction remain unchanged.