Follow-up to #443 (Phase 2: review queue + signed memos). Filed under the Option B stop rule: the PR's last Codex gate verdict (round 49, 2026-09-03 19:40Z on 9ac7a463) has no P0, so it merges after Phase 1 once its apply script has run; the remaining P1 findings are tracked here. Round 48's Critical items (split-identity manifest failing open, quarantine store failing open, delivery reservation committing on a lost CAS) were fixed in that push.
From round 49
-
[P1] Quarantine persistence failures still return success and lose the retry. When persistQuarantine() fails, src/app/api/cron/bank-register-pull/route.ts:966 records only quarantineStateReason; summary.ok stays true, so the continuation marker at :1014 is not set and :1032 returns HTTP 200. A transient quarantine read/write failure withholds freshness while every continuation slot reports nothing-in-progress, suppressing that day's receipt cards. Fix: treat !quarantine.ok as a failed run, persist a continuation obligation, return non-2xx; test with an injected write failure and assert the next slot retries.
-
[P1] Database failures reading the split manifest bypass the recovery path. The findUnique at route.ts:702 sits outside the conversion to SplitManifestUnreadableError; only JSON/schema failures get that type. A transient DB error is rethrown past the handler at :484, so no continuation or blocked reason is recorded, and with completed window state every scheduled continuation skips until the next nightly full run. Fix: route the read failure through the same recoverable path (or persist a retry obligation before rethrowing); test with an injected DB error.
Also open
- No operator path to accept a quarantine. The acceptance mechanism is versioned, but an operator would have to hand-write the
AutomationSetting row. Add a manager-page action (or MCP tool) that records an acceptance bound to the current quarantine version.
- The apply-script e2e driver has only ever run in CI. Docker Desktop does not start on the dev box and there is no local Postgres, so seed → apply → re-apply → parity was never run locally; both driver failures this PR (a pre-Phase-2 column, a text param bound into a
timestamp) would have been caught locally. Worth a local Postgres recipe before the next apply-script change.
Not a code item
- Before merge, the Beverly bridge (gtr-books) must send
n and request_id with every signed-memo answer, or answers are refused as association-incomplete.
Companion work on the Apps Script side (found 2026-09-04, blocks the chat-signing feature, not the merge)
The contract ProBuild enforces: POST /api/automation/receipt-requests/answers (auth RECEIPT_BRIDGE_SECRET) requires thread, integer n, and request_id (requestId also accepted) and matches all three against the posted card's stored record; GET /api/automation/receipt-requests/threads emits {owner, owner_user, message_name, request_id, items:[{n, fingerprint, ...}]} with request_id = receipt-req-<owner>-<date>.
What exists today in C:\Users\jat00\workspaces\golden-touch (clasp folders, no git):
beverly-chat-app/chatAffidavitApp.js — PREPARED (not pushed): signAffidavitCore_ now writes n and request_id into each signed answer record, read from affidavit-threads.json by thread + fingerprint; 6 new tests in beverly-chat-app/test/signAffidavit.test.js (15/15 green, mutation-checked). Deploy = clasp push from that folder (HEAD deployment is live immediately; script id in .clasp.json).
qbo-clasp/sweepChatReceipts.js — unchanged; none of its answer writers are signatures, so ProBuild ignores them.
What does NOT exist and must be built before a signed memo can reach ProBuild:
- Threads mirror (Phase 2 spec §4 item 2,
mirrorReceiptRequestThreads()): poll the threads endpoint and merge into affidavit-threads.json by thread key without clobbering Beverly's own entries; needs the bridge secret in Script Properties and a post-07:45 trigger.
- Answers forwarder: nothing POSTs
chat-job-answers.json records to the answers endpoint.
- Product call: a signature with no card association still produces a PDF and an unusable answer (logged, not refused), because Beverly's non-ProBuild affidavits legitimately have no thread entry.
Until 1 and 2 ship, ProBuild refuses every chat answer as association-incomplete and the chase stays open; the existing Beverly flow (chat signing → PDF → email/archive) is unaffected.
4. Possible fourth writer. Beverly's Python runner (affidavit_chat_request.py, runs on Justin's PC, not in any repo here) may also append signed:true records to chat-job-answers.json; it was not found on disk to check. Recommended durable shape: have the (new) answers forwarder back-fill n and request_id from affidavit-threads.json at forward time, which covers every writer at once, instead of patching each producer.
Follow-up to #443 (Phase 2: review queue + signed memos). Filed under the Option B stop rule: the PR's last Codex gate verdict (round 49, 2026-09-03 19:40Z on
9ac7a463) has no P0, so it merges after Phase 1 once its apply script has run; the remaining P1 findings are tracked here. Round 48's Critical items (split-identity manifest failing open, quarantine store failing open, delivery reservation committing on a lost CAS) were fixed in that push.From round 49
[P1] Quarantine persistence failures still return success and lose the retry. When
persistQuarantine()fails,src/app/api/cron/bank-register-pull/route.ts:966records onlyquarantineStateReason;summary.okstays true, so the continuation marker at:1014is not set and:1032returns HTTP 200. A transient quarantine read/write failure withholds freshness while every continuation slot reportsnothing-in-progress, suppressing that day's receipt cards. Fix: treat!quarantine.okas a failed run, persist a continuation obligation, return non-2xx; test with an injected write failure and assert the next slot retries.[P1] Database failures reading the split manifest bypass the recovery path. The
findUniqueatroute.ts:702sits outside the conversion toSplitManifestUnreadableError; only JSON/schema failures get that type. A transient DB error is rethrown past the handler at:484, so no continuation or blocked reason is recorded, and with completed window state every scheduled continuation skips until the next nightly full run. Fix: route the read failure through the same recoverable path (or persist a retry obligation before rethrowing); test with an injected DB error.Also open
AutomationSettingrow. Add a manager-page action (or MCP tool) that records an acceptance bound to the current quarantine version.timestamp) would have been caught locally. Worth a local Postgres recipe before the next apply-script change.Not a code item
nandrequest_idwith every signed-memo answer, or answers are refused asassociation-incomplete.Companion work on the Apps Script side (found 2026-09-04, blocks the chat-signing feature, not the merge)
The contract ProBuild enforces:
POST /api/automation/receipt-requests/answers(authRECEIPT_BRIDGE_SECRET) requiresthread, integern, andrequest_id(requestIdalso accepted) and matches all three against the posted card's stored record;GET /api/automation/receipt-requests/threadsemits{owner, owner_user, message_name, request_id, items:[{n, fingerprint, ...}]}withrequest_id = receipt-req-<owner>-<date>.What exists today in
C:\Users\jat00\workspaces\golden-touch(clasp folders, no git):beverly-chat-app/chatAffidavitApp.js— PREPARED (not pushed):signAffidavitCore_now writesnandrequest_idinto each signed answer record, read fromaffidavit-threads.jsonby thread + fingerprint; 6 new tests inbeverly-chat-app/test/signAffidavit.test.js(15/15 green, mutation-checked). Deploy =clasp pushfrom that folder (HEAD deployment is live immediately; script id in.clasp.json).qbo-clasp/sweepChatReceipts.js— unchanged; none of its answer writers are signatures, so ProBuild ignores them.What does NOT exist and must be built before a signed memo can reach ProBuild:
mirrorReceiptRequestThreads()): poll the threads endpoint and merge intoaffidavit-threads.jsonby thread key without clobbering Beverly's own entries; needs the bridge secret in Script Properties and a post-07:45 trigger.chat-job-answers.jsonrecords to the answers endpoint.Until 1 and 2 ship, ProBuild refuses every chat answer as
association-incompleteand the chase stays open; the existing Beverly flow (chat signing → PDF → email/archive) is unaffected.4. Possible fourth writer. Beverly's Python runner (
affidavit_chat_request.py, runs on Justin's PC, not in any repo here) may also appendsigned:truerecords tochat-job-answers.json; it was not found on disk to check. Recommended durable shape: have the (new) answers forwarder back-fillnandrequest_idfromaffidavit-threads.jsonat forward time, which covers every writer at once, instead of patching each producer.