Repository navigation
PAY-002B2A — Persist immutable command registration and first audit event #165
Description
Officer impact: None. This is unused server-only infrastructure. It does not add checkout, refund, admin, or officer actions.
Officer documentation: None — no officer-visible behavior, procedure, permission, provider setting, collected data, or deployed surface changes. Commerce remains NOT AVAILABLE YET.
Deployment evidence: Closure proves source, local tests, independent review, merge, and exact-commit hosted CI only. It must not claim website publication, Firebase deployment, Stripe configuration, provider objects, or live behavior.
Parent and dependencies
First persistence child of catalogued PAY-002B2.
- PAY-002B1/PAY-002B1 — Define caller-scoped command identity and deterministic Stripe keys #163 is merged as
6c5d39ad3204c24928c11f194de9f3330198dc6dand supplies canonical command/payload hashes. - Existing Firestore Rules already deny browser reads and writes to root
checkoutRequestsandauditEvents. - No endpoint-schema dependency exists because this child has no runtime adopter.
- PAY-002B2B will own lease/fence/terminal-pointer behavior without provider-send permission.
- PAY-002B2C will own immutable provider-plan binding, safe-send cutoff, ambiguous outcome, and reconciliation.
No live PAY-002B2 issue, claim, or open pull request existed at creation. The live status:ready queue was empty.
Problem
B1 can derive stable hashes but creates no durable record. Two Functions can therefore see the same command at the same time without a transactional winner. Reusing one caller command ID with a different operation or payload also has no persistent conflict boundary.
This slice creates the first durable command record and its first audit event. It deliberately creates no lease and no permission to contact Stripe.
Atomic outcome
Add registerCommerceCommand(...), an unused server-only Firestore transaction that:
- validates and derives B1 command identity before the transaction;
- captures one trusted server timestamp before the transaction callback;
- reads the deterministic command and audit documents before any write;
- creates one immutable registration plus one deterministic append-oriented audit event atomically when both are absent;
- returns a fixed read-only existing classification for an exact prior registration;
- rejects reuse of the same B1 command key with another command type, endpoint schema version, or payload fingerprint;
- rejects malformed, missing-pair, future-version, or unexpected-state records without repair;
- performs no non-Firestore side effect inside or after the transaction.
Registration is not authorization and is not execution permission.
Exact API
registerCommerceCommand({
db,
environment,
callerScope,
commandId,
commandType,
endpointSchemaVersion,
payload,
})callerScope,commandId, andpayloadare passed directly to B1 and are never stored or returned.payloadmust already be endpoint-schema-validated and deeply frozen.endpointSchemaVersionis a bounded positive safe integer supplied by trusted code.- B1 intentionally binds environment and caller-scope kind/value into the document hash. Changing environment or caller scope creates a different B1 identity and document; B2A does not add a forbidden raw-UUID registry or cross-scope query.
- A stored environment or caller-scope-kind value that disagrees with its derived document input is treated as an unrecognized/corrupt record, not a normal retry conflict.
- The returned fixed frozen value contains only
journalSchemaVersion,outcome, andstate. - Outcomes are
registered_newandregistered_existing; state isregistered. - No output contains the command hash, fingerprint, document path, raw caller, UUID, payload, lease, fence, provider key, provider object, result, or
shouldExecuteflag.
Unknown Firestore/provider details map to one fixed journal_unavailable error. B1 input failures map to one fixed safe input error. Stored conflicts and unrecognized records have fixed non-reflective reasons.
Command document
Path: checkoutRequests/{commandKeyHash}
Exact version-1 fields:
journalSchemaVersion: 1
commandIdentityVersion: 1
endpointSchemaVersion: bounded positive integer
environment: local | test | staging | production
callerScopeKind: firebase_uid | anonymous_principal | internal_system
commandType: trusted bounded slug
payloadFingerprint: lowercase SHA-256
state: registered
revision: 1
createdAt: Firestore Timestamp
updatedAt: the same Firestore Timestamp
The document ID is the B1 command-key hash. Do not store a raw command ID, caller value, payload, PII, Checkout URL, confirmation token, Stripe key/object ID, provider attempt, result, lease, failure text, or reconciliation evidence.
revision is aggregate/audit revision only. It is not a lease fencing epoch.
Audit document
Path: auditEvents/commerce_command_{commandKeyHash}_0000000001
Exact version-1 fields:
auditSchemaVersion: 1
aggregateType: commerce_command
commandKeyHash: lowercase SHA-256
commandRevision: 1
eventType: command_registered
fromState: null
toState: registered
environment: closed enum
callerScopeKind: closed enum
commandType: trusted bounded slug
occurredAt: the same Firestore Timestamp
A matching retry creates no second event. A rejected conflict creates no event. An existing command is accepted only when the exact deterministic audit partner also exists and matches; missing or malformed pairs fail closed without repair.
State machine
absent pair -> registered pair
registered pair + exact identity -> registered (read-only)
registered pair + mismatch -> reject
orphan/malformed/future/unexpected pair -> reject
There is no leased, executing, provider_sending, completed, failed, retry, result-replay, attempt-advance, or reconciliation state in this child.
Exact source boundary
Create only:
functions/commerceCommandJournal.jsfunctions/commerceCommandJournal.test.jsfunctions/commerceCommandJournal.emulator.test.js
Minimum exact status/design hunks only:
STRIPE_COMMERCE_DESIGN.mdSYSTEM_DESIGN.mdSECURITY.mdIMPLEMENTATION_PLAN.mdGITHUB_ISSUES.mdGITHUB_ISSUE_SLICES.md
Post a scope amendment before editing an endpoint, functions/index.js, package/lockfile, Firestore Rules/indexes, workflow, environment file, UI, officer guide, provider setting, or production data.
Security and correctness invariants
- Command and first audit event commit together or neither commits.
- All transaction reads occur before writes.
- The transaction callback may rerun and contains no logger, clock, random generator, network call, Stripe call, or mutable application-state side effect.
- Within one environment and caller scope, the same command ID plus another semantic operation reaches the same document and conflicts.
- Matching retry is byte-preserving and performs no write.
- No API grants provider-send or business-side-effect permission.
- Stored hashes are pseudonymous server-only data, not anonymization.
- Browser access remains denied; Admin SDK/IAM remains a separate trust boundary.
- No TTL is added. Deleting a journal can re-enable a duplicate command; retention must later coordinate with DATA-001B — Inventory personal data and approve a minimization/retention matrix #110.
- No automatic repair or guessed migration of unknown records.
- A hash match does not authorize a caller, prove price/capacity/inventory/refund eligibility, or replay a result.
Firestore can rerun a transaction callback after contention, so only Firestore reads/writes belong inside it: https://firebase.google.com/docs/firestore/manage-data/transactions
Acceptance criteria
- Concurrent identical registration produces one command, one audit event, one new outcome, and existing outcomes for the rest.
- Within one B1 key, another command type, endpoint version, or payload rejects without overwrite; a stored environment/caller-scope-kind mismatch is rejected as an unrecognized record.
- Matching registration is read-only and leaves both snapshots equivalent.
- Orphan, malformed, accessor/proxy, unknown-version, unexpected-state, and timestamp-mismatch records reject without repair.
- Transaction commit failure leaves neither half of a new pair.
- Results and errors are frozen, fixed-shape, and contain no raw UUID, caller, payload, canary, path, hash, or Firestore detail.
- Static tests prove no endpoint/index import and no Stripe/logger/network/filesystem/environment/random dependency.
- Existing Rules tests continue to deny browser access to both collections.
- Node 20 focused/unit/local-emulator/full Functions tests and lint, frontend, SPA, Rules, build, diff/sensitive scan, and two independent exact-snapshot reviews pass.
- Root docs split B2A registration from B2B leases/replay and B2C provider-plan/safe-send/reconciliation.
- No runtime, deployment, provider, production, or officer behavior is claimed.
The current hosted Functions CI does not start the exact demo-pay002b2-test project or set REQUIRE_COMMERCE_COMMAND_JOURNAL_EMULATOR=1. The standard differently named Functions demo run must safely skip this isolated suite. Closure must state dedicated local-emulator evidence separately and update #105 with this ungated gap; do not imply hosted emulator coverage.
Required tests
Unit/mocked transaction:
- New pair and exact existing pair.
- Command type, endpoint-schema, and payload conflicts under one B1 key; stored environment/caller-scope-kind corruption.
- Exact root fields, versions, enums, hashes, state, revisions, and Timestamp equality.
- Missing command, missing audit, preexisting audit, malformed pair, custom prototypes, accessors, proxies, symbols, and extra keys.
- Firestore failure redaction and B1 failure redaction.
- Repeated callback simulation with no clock/logger/network/random side effect.
- Frozen fixed output/error and raw-canary absence.
- Export/import boundary with no execute/send/lease/result API.
Demo-only emulator:
- Run only with exact opt-in
REQUIRE_COMMERCE_COMMAND_JOURNAL_EMULATOR=1, exactdemo-pay002b2-test, and an explicit loopback Firestore host; wrong host/project must fail closed, while the standard differently named demo run skips. - 20 or more concurrent identical calls.
- Concurrent two-payload race: one identity wins, matching calls reuse it, conflicting calls reject, pair is never overwritten.
- Preseed deterministic audit ID and prove failed transaction leaves no orphan command.
- Existing exact pair remains unchanged after repeated calls.
- Existing malformed/future pair remains untouched.
- Cleanup only synthetic deterministic documents.
Migration impact
Additive target schema only. No backfill, production read, provider inventory, or existing business-record mutation. Before any deployment/adoption, privately inventory existing checkoutRequests within #113-approved boundaries. Unknown records fail closed. No new Firestore index is required.
Required follow-ons
- PAY-002B2B: lease, monotonic fence epoch, stale-worker rejection, terminal server-side pointer, fixed final failure, and append-only transition audit. It still grants no Stripe POST permission.
- PAY-002B2C: immutable provider request-plan fingerprint plus Stripe account/sandbox scope, API version, endpoint/operation, mode, attempt, key, pre-POST first-send time, conservative cutoff, ambiguous outcome, verified reconciliation, and safe attempt advancement.
B1 payload identity does not prove an identical Stripe POST. Any future send permit must bind the complete provider plan. Stripe may prune an idempotency key after it is at least 24 hours old, so an old deterministic key can execute again: https://docs.stripe.com/api/idempotent_requests
PAY-002C/D, PAY-003B, PAY-004, and PAY-005 must depend on B2C, not B2A or B2B.
Excluded
- Any endpoint/export/index import or runtime adoption.
- Lease, fence, provider attempt, provider plan, safe-send deadline, reconciliation, terminal result, or result replay.
- Stripe call, webhook, checkout, refund, capacity, inventory, mail, admin, rate-limit, or confirmation-capability behavior.
- Rules, index, package, lockfile, workflow, environment, UI, officer, provider, Firebase deployment, production read/write, or live testing.
Claim protocol
Creation is not a claim. Before editing: confirm exact current main, assign the issue, post CLAIMED by /root at <UTC> with branch codex/issue-<number>-command-registration, apply status:in-progress, and use an isolated worktree.
Activity
daliu commentedon Jul 13, 2026
CLAIMED by /root at 2026-07-13T16:32:37Z.
Branch:
codex/issue-165-command-registrationWorktree: isolated
Run-MPRC.issue-165Exact base:
6c5d39ad3204c24928c11f194de9f3330198dc6dOwned files are exactly the three new journal/test files plus the six minimum root documentation/status files named in the issue. No endpoint/index, package/lock, Rule/index, workflow, environment, UI, officer, provider, or production file is claimed.
B2A creates only an atomic registered command/audit pair and exact match/conflict classification. It grants no lease, execute/send permission, result replay, provider attempt, Stripe call, or reconciliation transition.
daliu commentedon Jul 13, 2026
Pre-implementation contract clarification (no source change yet):
B1 includes environment and caller-scope kind/value in
commandKeyHash. Therefore a different environment or caller scope intentionally addresses a different document. B2A will not create a raw-UUID registry or query across scopes.Normal
command_conflictcovers a different command type, endpoint schema version, or payload fingerprint under the same B1 key. A hostile/preseeded record whose stored environment or caller-scope kind disagrees with the derived input isjournal_record_invalidcorruption. The issue body and tests now state this exact boundary.daliu commentedon Jul 13, 2026
EXTRACTION FINISHED — 2026-07-13T16:52:45Z
Exact owned paths: 9 (the three new
functions/commerceCommandJournal*files plus the six claimed root design/status documents).Exact source/test hashes:
a12aa577c003c05c93ce4e66e97e317ddea268d82b60f491c2c493a9c0fa4ec00451a3129591e8d9b9cc39af98a1e46ee6e7c4b9a89663aadff0537c0207066a6f320581b96df19cca132bf8df3b9e1f5200018b70300088110c8d2ac8e9b825Local verification:
Three independent reviews approved the exact code, emulator, documentation, and scope. Hosted CI intentionally does not run the opt-in dedicated emulator suite; that gap is recorded and is not being hidden as hosted evidence.
This slice registers the command and first immutable audit record atomically. It grants no lease, provider-send permission, Stripe action, result replay, endpoint, deployment, or production behavior.
feat(commerce): register durable command identity (#165) (#166)
daliu commentedon Jul 13, 2026
COMPLETE — merged through PR #166 as exact main
59aa088a50ba2934d830a460c0a865a631a293d6.Verified evidence:
29268258617: Frontend, Functions, and Rules passed; Netlify preview6a55180c121b750008a98ec0was ready29268403718: Frontend, Functions, and Rules passed on the merge commitDeployment truth:
29268514430proved exact-current-main and successful push CI, then failed closed before build because the required public App Check/reCAPTCHA site key is missing6a55188fe61cd200085baa66errored and was not publishedrunmprc.comremained HTTP 200 on the last ready production commite86a0f702cff6495f50630c5de3337290db8b8cb; this change is not liveThe module remains unused by runtime entrypoints. PAY-002B2B must add lease/fencing without send permission; PAY-002B2C must add the full provider-plan and safe-send/reconciliation boundary before any Stripe caller may adopt it.
Hosted-gap truth: standard hosted Functions CI safely skips the dedicated
demo-pay002b2-testsuite because it does not set the opt-in flag. The local emulator proof is recorded separately, and the missing hosted gate is also recorded on #105.