Skip to content

feat(db): add dormant protected first-capture intent (migration 0014) - #16

Merged
daniel-tsx merged 1 commit into
mainfrom
feat/r2-prerequisite-p-c
Oct 27, 2026
Merged

daniel-tsx merged 1 commit into
mainfrom
feat/r2-prerequisite-p-c

Conversation

@daniel-tsx

Copy link
Copy Markdown
Owner

What and why

Adds migration 0014_first_capture_intent (P-C of the R2 prerequisites): one atomic, dormant PostgreSQL operation that records the first capture intent for a payment backed by a completed, immutable P-B MANUAL authorization. The intent request, manifest and acceptance, the stable capture:attemptUUID provider command (payment, attempt, outbox, command authority, audit) and the revision bump commit together or not at all. No capture journal, lot or fee accounting, no runtime or login-role grant, no scope activation. B2-A, B2-B and B2-C are not started.

Review guide

Read carefully (T2 areas):

  • apps/api/drizzle/0014_first_capture_intent.sql (578 lines): request wrapper and replay (first_capture_intent_request), the prepare/apply/accept entry points, the normalized actual-owner projection (first_capture_intent_record_owners, used at prepare and in immediate and deferred reverse closure), check_first_capture_intent (six exact child groups), the issuance guard (SECURITY INVOKER; binds current_user, transaction, backend pid and session_user), the dispatch rewrites of existing 0009/0010/0013 guard functions (pg_get_functiondef overlay; fails closed on anchor mismatch), NOLOGIN roles and PUBLIC revocations.
  • Lock order must stay: request key, account SHARE, merchant/currency advisory, scope FOR NO KEY UPDATE, payment, attempts by id; no old AUTH inbox lock after the scope.
  • apps/api/drizzle/meta/_journal.json (appended idx 14, when greater than 0013) and apps/api/src/database/schema.ts (mapping only).

Skim: harness and spec count bumps from 14 to 15 migrations and staged(14) pinning of P-B tests; four doc paragraphs; first-capture-intent-harness.ts fixtures (fake test key, test-DB-only grants).

Least confident about: (1) full-table, non-sargable owner-resolution scans of outbox_events and audit_logs while the scope lock is held (correctness fine, cost unmeasured); (2) the xmin::text versus pg_current_xact_id()::text not-equal idiom (copied from 0009; fails open after xid epoch rollover, bookkeeping columns only); (3) behaviour on PostgreSQL versions other than 17 (pg_input_is_valid needs 16 or later; only PG17 was run).

Tests removed, weakened or skipped: none removed or weakened. settlement-f01.pending.spec.ts is unmodified (its six F01 assertions still fail as expected with RUN_F01_PENDING=1). Edits to existing specs are migration-count bumps and an added 0014 phase. The review-signals guard warns about two weak matchers (expect(later.error).toBeDefined()) in the NEW first-capture-intent.spec.ts race tests; they are paired with row-count assertions but do not pin an error message (reviewer follow-up CR7-F3). The six skips are the unchanged env-gated F01 suite.

Task size

T2. Invariants touched: idempotency (request unique per merchant, operation and key; payload, hash and principal compared; replay is read-only); local intent, outbox, audit and replay response commit atomically; provider command key stable (capture:attemptUUID); ledger and evidence immutability and uniqueness unchanged (new tables under protect_financial_evidence; no journal or lot is written); captured is not settled is not paid out (capture accounting stays blocked: receipt AWAITING_APPLICATION, D02 BLOCKING_REVIEW). Transaction boundary: one READ COMMITTED transaction holding one exclusive APPLICATION/CAPTURE_INTENT permit; deferred constraint triggers re-check the six groups and exactly one revision +1 at commit; a prepare-only commit cannot succeed.

Checks

Local runs on commit fcdc190 (rebased on origin/main f623df0), Docker PostgreSQL 17, mocked Stripe and broker boundaries. Local runs are not hosted CI.

Result Command Scope / notes
PASS pnpm test:integration:docker 1240 passed, 0 failed, 6 skipped (env-gated F01), 40 suites; own resources and 112 spec byproducts removed
PASS focused P-C suites (first-capture-intent and its migration spec), run on the identical tree before commit 111 passed, 0 failed (also inside the 1240)
PASS (expected failures preserved) RUN_F01_PENDING=1 settlement-f01.pending.spec.ts, run on the identical tree before commit 6 failed, 0 passed; names and assertion digests match the accepted baseline 6 of 6
PASS pnpm lint, pnpm build, pnpm typecheck API and web
PASS pnpm test API 50 passed, 1246 environment-gated skips, 12 suites; web 3 passed
PASS pnpm test:verification 34 passed
PASS node scripts/check-migration-history.mjs origin/main HEAD append-only; one new SQL file, one appended journal entry
PASS node scripts/check-pr-review-signals.mjs origin/main HEAD exit 0; labels T2 area; two weak-matcher warnings listed above
PASS node scripts/sync-agent-skills.mjs --check, node scripts/check-engineering-workflow.mjs, diff --check run on the identical tree before commit
NOT RUN hosted CI PR was not open when this was written; read the PR checks for the exact head SHA
NOT RUN Node 22, PostgreSQL 18, owner-doc link script re-execution link check last passed (54 references, 0 errors) on byte-identical docs

Unverified boundaries

Hosted CI; Node 22; PostgreSQL versions other than 17; live Stripe and multicapture entitlement; RabbitMQ and Redis runtime; production-size table performance; any real role-grant topology outside the migrations. No runtime path invokes the new SQL.

Review

T2 workflow engineering-workflow, task r2-prerequisite-p-c-implementation (HIGH_RISK), ledger at HUMAN_APPROVAL; correction budget 2 of 2 used and not reset.

  • Plan: approved by a fresh independent reviewer. Code: one fresh reviewer requested changes (two P2 ownership and cardinality gaps), corrected in correction 2 (normalized owner projection plus ten permanent regressions), then a second reviewer approved; after a documented VERIFY rebaseline a further fresh read-only reviewer approved again with no P0 or P1 (six non-blocking P2: scan cost, xid idiom, test isolation and message pinning, upgrade-test thinness, two possibly unneeded SELECT grants, missing drain note). These independent reviews used fresh read-only subagents (the documented fallback), not Codex. The last one reviewed the identical working-tree content before it was committed and rebased; the commit differs only by SHA and the AGENTS.md change already on main.
  • Security (required for payments and auth): the host built-in /security-review command was run through the Claude Code Skill tool on the COMMITTED diff against origin/main (17 files, 1330 insertions) and reported no finding at or above 0.7 confidence; a supplemental custom-prompt security review of the same content returned SECURITY_APPROVED. Limitations: both are static reads by sub-tasks (no SQL executed); the built-in run did not read migrations 0001-0006, 0008, 0011, 0012 (P-B proof validators) or the RabbitMQ consumer; it was not the claude CLI binary, whose earlier attempts failed on credit or egress approval; an earlier built-in run saw an empty diff (uncommitted work) and is superseded. Treat security coverage as adequate for the dormant state, not as a proof for activation.

Activation prerequisites (security; not P-C scope; none exploitable while no scope is enrolled and no login role holds EXECUTE)

  • A-1 provider_command_authorities (activation-blocking): the unchanged relay (outbox-relay.service.ts) and handler (payment-command-handler.service.ts) execute any PENDING provider.capture.requested row from its payload without reading provider_command_authorities, provider mode or provider account, and the outbox ownership guard does not resolve providerPaymentId. Before any scope is enrolled or the CAPTURE contract installed: require a matching authority row in the relay and handler (or deny such outbox rows in SQL without one), prove the TEST or live account binding, and add a negative test for a raw outbox row with unrelated UUIDs and a real providerPaymentId.
  • A-2 enrollment order: P-C guards switch on only with the CAPTURE contract installed or a manifest present; document and enforce the order.
  • A-3 tenancy: entry points take a root, not a caller or merchant; bind merchant before any request-scoped principal gets EXECUTE.
  • A-4 the delivery-bookkeeping carve-out lets any outbox updater re-arm a published accepted capture row to PENDING; add the replay horizon and authority check on the relay path (partial captures could repeat after the provider idempotency window).
  • A-5 never make a login role a member of the NOLOGIN writer role (broad INSERT, accept_provider_command, whole-row payments SELECT); grant only EXECUTE on the three entry points.
  • A-6 the audit row exposes the internal database role as actor_id through GET /audit-logs; redact or replace before activation.
  • A-7 the full payments row including payment_method_token is copied into immutable manifests (same pattern as 0013); decide retention and deletion handling before activation.

Docs and temporary files

Owning docs updated: docs/payment-lifecycle.md, docs/database-design.md, docs/concurrency.md, docs/webhooks-and-idempotency.md. Approved decision records: the P-A and P-B payment-proof and MANUAL authorization contracts were read for T2; no new decision record. Temporary files kept (ignored, until merge or rejection) under .tmp/agent-workflows/r2-prerequisite-p-c-implementation/ (state, plan, immutable reviewer and security reports, verification files, final-checks files, handoff) and .tmp/r2-prerequisite-p-c-implementation/ (pr.md and the commit message).

🤖 Generated with Claude Code

Adds the P-C prerequisite for R2: one atomic, dormant SQL operation that
accepts the first capture intent for a payment backed by a completed
immutable P-B MANUAL authorization.

- request, manifest and acceptance tables with unique first intent per
  payment and per merchant/operation/key; exact replay is read-only
- shared exclusive APPLICATION/CAPTURE_INTENT permit; exact six-group
  child images (payment, attempt, outbox, authority, audit, acceptance)
  commit atomically with a stable capture:attemptUUID command
- SECURITY DEFINER code pins search_path, current_user and session_user;
  NOLOGIN owner roles; PUBLIC revoked; no login or runtime role granted
- normalized actual-owner projection used at prepare and in immediate and
  deferred reverse closure, so extra earlier owned audits or outbox rows
  (including uppercase UUID variants) are rejected
- lock order preserved: request key, account SHARE, advisory, scope,
  payment, attempts by id
- migrations 0000-0013 unchanged; 0014 is a forward migration with an
  appended journal entry; historical F01 assertions unchanged

No capture journal, lot or fee accounting, no runtime grant and no scope
activation. Activation prerequisite: the relay and handler must require a
provider_command_authorities row for provider.capture.requested (see PR).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added the t2-area Touches a T2 area: read carefully label Oct 27, 2026
@daniel-tsx
daniel-tsx merged commit 21653e7 into main Oct 27, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

t2-area Touches a T2 area: read carefully

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant