Repository navigation
PAY-002B2C1 — Bind the immutable initial Stripe provider plan #173
Description
Officer impact: None. This is unused server-only payment safety source. It does not add checkout, refund, admin, or officer actions.
Officer documentation: None — no officer-visible behavior, procedure, permission, collected data, website surface, provider setting, or deployed service changes. Stripe commerce remains NOT AVAILABLE YET.
Deployment evidence: Closure may prove source, synthetic local tests, independent review, merge, and exact-commit hosted CI only. It must separately state website publication, runmprc.com, Firebase deployment, Stripe/provider configuration, and live behavior. This issue must not claim any external outcome.
Parent and dependencies
Atomic PAY-002B2C1 child of the catalogued PAY-002 command-safety design.
- PAY-002B1/PAY-002B1 — Define caller-scoped command identity and deterministic Stripe keys #163 merged as
6c5d39ad3204c24928c11f194de9f3330198dc6dand supplies canonical command, payload, and Stripe idempotency-key identity. - PAY-002B2A/PAY-002B2A — Persist immutable command registration and first audit event #165 merged as
59aa088a50ba2934d830a460c0a865a631a293d6and supplies the immutable registered root plus deterministic revision-1 audit. - PAY-002B2B/PAY-002B2B — Add command leases, fencing, and terminal commitments #169 merged as
2eff10730e4ac60a6b2de6bcd07fb0ba899d591band supplies the unused server lease, monotonic fence, terminal commitment, and lifecycle audit. - CI-001B3/CI-001B3 — Gate the commerce command journal on the Firestore emulator #167 runs the exact command-journal emulator file as a named hosted release prerequisite.
- DATA-001B — Inventory personal data and approve a minimization/retention matrix #110 remains the owner of retention/deletion policy. It blocks TTL/deletion, not this additive unused schema.
- BACKEND-001 — Audit the Firebase cutover and approve legacy data disposition #113/CI-001C — Configure protected short-lived Firebase deployment identity #133/CI-001D — Prove backend-first staging and deploy the profile repair #136/WEB-001 remain the owners of real account inventory, protected configuration, staging, deployment, provider readback, and live proof.
At creation, exact remote main is 2eff10730e4ac60a6b2de6bcd07fb0ba899d591b. There is no open pull request, duplicate PAY-002B2C issue, or active overlapping claim.
Problem
A fenced worker still has no immutable record of the exact Stripe context it intends to use. Without that record, retries could silently change the Stripe account, test/live mode, API version, endpoint, operation, parameters, or deterministic idempotency key.
This slice binds one immutable initial provider plan while the caller holds the current unexpired lease. It deliberately does not create a send marker or return permission to call Stripe.
Exact API
Add one unused server-only export to functions/commerceCommandJournal.js:
bindInitialStripeProviderPlan({
db,
environment,
callerScope,
commandId,
commandType,
endpointSchemaVersion,
payload,
leaseId,
expectedFenceEpoch,
stripeAccountId,
stripeMode,
stripeApiVersion,
endpointPath,
providerOperation,
providerParameters,
})The module must:
- recompute the B1 command key and payload fingerprint; never accept a command hash;
- hard-code provider
stripe, HTTP methodPOST, and provider attempt1; never accept any of those from the caller; - validate the exact B2A root/revision-1 audit and exact B2B lifecycle/current-audit partner;
- require lifecycle state
leased, the exact current holder fingerprint and fence, and a lease that is active at the one captured trusted timestamp; - require deeply frozen, canonically encodable, bounded
providerParameters; store no raw parameter; - validate the environment/test-live pairing and conservative bounded account, API-version, endpoint-path, and operation forms;
- derive the exact B1 Stripe idempotency key for attempt 1 and never store, log, or return that raw key.
The current configuration layer proves only test/live key-prefix compatibility. It does not prove that a key belongs to the supplied Stripe account. C1 records a commitment only; account-context/provider verification remains mandatory before C2 may send.
Fixed outputs
Every output is a frozen fixed-shape object:
{
journalSchemaVersion: 1,
providerPlanSchemaVersion: 1,
outcome: provider_plan_bound | provider_plan_existing,
state: planned
}
No output contains a command hash/path, fence, time, lease ID/fingerprint, Stripe account, API version, endpoint, operation, parameter/key value or fingerprint, provider object, authorization decision, or shouldSend/shouldExecute field.
Immutable provider plan
Create only this first-attempt document:
checkoutRequests/{commandKeyHash}/providerAttempts/0000000001
Exact fields:
providerPlanSchemaVersion: 1
commandIdentityVersion: 1
commandKeyHash: lowercase SHA-256
environment: local | test | staging | production
provider: stripe
providerAttempt: 1
providerOperation: bounded safe slug
stripeMode: test | live
stripeAccountFingerprint: lowercase SHA-256 commitment
stripeApiVersion: strict bounded API-version string
httpMethod: POST
endpointPath: strict bounded Stripe API path without scheme, host, query, fragment, traversal, or encoded ambiguity
parametersFingerprint: lowercase SHA-256 commitment
idempotencyKeyFingerprint: lowercase SHA-256 commitment
boundFenceEpoch: positive bounded integer
boundAt: Firestore Timestamp
The account, parameter, and key fingerprints are versioned, length-framed, domain-separated, and command-bound commitments over the canonical input/B1 value plus commandKeyHash. They are pseudonymous equality evidence, not anonymization, authorization, configured-account proof, provider-execution proof, or response replay.
No raw account ID, provider parameters, idempotency key, secret, Checkout URL, provider object, personal data, business-record path, result, or error text is stored.
Deterministic audit partner
Atomically create:
auditEvents/commerce_provider_attempt_{commandKeyHash}_0000000001
Exact fields:
providerPlanAuditSchemaVersion: 1
aggregateType: commerce_provider_attempt
commandKeyHash: lowercase SHA-256
providerAttempt: 1
eventType: provider_plan_bound
provider: stripe
environment: closed enum
stripeMode: test | live
providerOperation: bounded safe slug
boundFenceEpoch: positive bounded integer
occurredAt: Firestore Timestamp
The audit excludes the account, parameter, and key commitments and every raw provider/request value.
Transaction and retry rules
Before any write, one Firestore transaction reads:
- the immutable B2A command root and revision-1 audit;
lifecycle/currentand its exact current-revision lifecycle audit;- the first-attempt provider plan and deterministic provider-plan audit partner.
All reads occur before any create. The callback uses only prepared immutable values and Firestore reads/writes. It contains no clock, random generator, logger, network, Stripe, filesystem, environment lookup, or mutable application-state side effect.
Rules:
- First bind requires the exact current unexpired holder/fence and atomically creates the plan plus audit.
- Same exact stored plan/audit plus the exact current unexpired holder/fence returns
provider_plan_existingwith no write. - If the lease expires, the caller must reacquire or take over first. An expired holder cannot bind or observe the fixed existing outcome.
- After takeover, the new exact active holder may read the same exact existing plan without rewriting
boundFenceEpochorboundAt. - A captured time earlier than a newly observed lease acquisition is observation-only and can never create a plan. No stale clock observation may mutate state.
- A conflicting plan, wrong holder/fence, terminal lifecycle, missing/malformed/future/orphan root/lifecycle/plan/audit, preseeded partner, impossible timestamp, or bounded-integer overflow rejects without repair.
- Exact retry after a lost response is read-only. Every rejection is read-only.
- B2A root/audit and B2B lifecycle/audit records remain byte-for-byte unchanged.
- Attempt 2 or later cannot be represented or created by this API.
The fixed Firestore retry maximum remains source-owned. Caller input cannot choose retry count, time, attempt, provider, or method.
State sequence
registered -> leased/fence N
leased/fence N + first exact plan -> plan/audit created (lifecycle unchanged)
leased/fence N + same exact plan -> existing (read-only)
leased/fence N + conflicting plan -> reject (read-only)
expired/wrong/stale/terminal holder -> reject (read-only)
takeover/fence N+1 + same exact plan -> existing (read-only)
attempt > 1 -> not available in C1
The plan is configuration equality evidence only. It does not reserve capacity/inventory, authorize the caller, prove a Stripe key/account relationship, or permit a provider request.
Exact source boundary
Code and tests:
functions/commerceCommandJournal.jsfunctions/commerceCommandJournal.test.jsfunctions/commerceCommandJournal.emulator.test.jstests/firestore-rules/shop-and-locked.test.js
Minimum exact design/status hunks only:
STRIPE_COMMERCE_DESIGN.mdSYSTEM_DESIGN.mdSECURITY.mdIMPLEMENTATION_PLAN.mdGITHUB_ISSUES.mdGITHUB_ISSUE_SLICES.md
The existing Rules-v2 recursive denial under checkoutRequests/{document=**} and top-level auditEvents denial should already cover the new documents. Add explicit browser-denial tests for the provider-attempt path. Do not edit firestore.rules unless a read-only audit proves the existing rule is insufficient and a scope amendment is posted first.
Post a scope amendment before editing an endpoint, functions/index.js, server configuration, Stripe adapter, workflow, package/lockfile, Firestore index, environment file, UI, officer guide, Rules source, or production/provider setting/data.
Required tests
Mock/unit tests:
- Exact input/output/plan/audit schemas, frozen output, one trusted timestamp, and all reads before creates.
- Golden command-bound account, parameters, and idempotency-key commitments; raw/canary values absent from storage, output, audit, errors, and logs.
- Exact B1 key derivation for provider attempt 1 and environment/mode mismatch rejection.
- Deep-frozen canonical provider-parameter equivalence and conflicts; prototype/accessor/proxy/symbol/extra-key/budget rejection.
- Conservative account, API-version, endpoint-path, operation, lease UUID, and fence boundaries.
- First bind, exact lost-response retry, conflicting plan fields, wrong holder, independent wrong fence, expiry equality, takeover, terminal lifecycle, and earlier-captured-time cases.
- Missing/malformed/future/orphan plan/audit, preseeded audit partner, malformed B2A/B2B partner, and commit failure leave all records unchanged.
- B2A registration and B2B lifecycle APIs remain byte-preserving before and after plan binding.
- Closed export/dependency boundary: no endpoint/index import and no send/reconcile/advance/result API, Stripe/logger/network/random dependency, or
shouldSendfield.
Existing hosted emulator file functions/commerceCommandJournal.emulator.test.js:
- 24 same-plan/current-holder calls create exactly one plan/audit and return one bound plus read-only existing outcomes.
- Distinct plans race: exactly one immutable pair wins and every loser conflicts without overwrite.
- Lost-response retry is byte-preserving and appends no second audit.
- Expired holder cannot bind or observe existing; one takeover may read the same plan without rewriting it.
- Wrong holder/fence, terminal lifecycle, preseeded partner, commit failure, and malformed/orphan/future records leave all related records unchanged.
- B2A root/revision-1 audit and B2B lifecycle/current audit remain byte/update-time equivalent through every provider-plan test.
Rules tests:
- Anonymous, signed-in member, and browser-admin clients cannot read, create, update, or delete
providerAttempts/0000000001. - Existing protected collection and collection-group tests remain green.
Run Node 20 focused unit/emulator tests, Functions lint/full tests, frontend Jest, SPA, Rules, diagnostic build, diff/sensitive scan, and at least two independent exact-snapshot reviews.
Acceptance criteria
- One exact initial Stripe plan and deterministic audit partner are created atomically only for the current unexpired holder/fence.
- The plan binds account, mode, API version, endpoint, operation, canonical parameters, B1 key, command, and original binding fence without storing raw sensitive/request values.
- Account/parameter/key commitments are domain-separated and command-bound and are never described as anonymization or provider proof.
- Exact active-lease retry is read-only; conflicts, stale/expired/wrong/terminal callers, and every malformed/orphan/future/preseeded state fail closed without repair.
- A valid takeover can observe the same immutable plan but cannot rewrite it.
- B2A/B2B records and audits remain byte-for-byte unchanged.
- Browser access to the provider-attempt path is explicitly denied by tests.
- The existing CI-001B3 — Gate the commerce command journal on the Firestore emulator #167 hosted emulator job runs the expanded focused file and passes on pull-request and merge commits.
- Full relevant local/hosted checks and two independent reviews pass.
- Root docs distinguish plan binding, pre-POST evidence, send/retry cutoff, ambiguity, reconciliation, deployment, provider configuration, and live behavior.
- No runtime adoption, endpoint/index export, Firebase deployment, Stripe/provider action, production read/write, TTL, migration, or officer behavior changes or is claimed.
Migration and rollback
Additive target schema only. No backfill, production read, provider inventory, deployment, or existing business-record mutation. Before adoption/deployment, #113 must approve any existing collection/account context; unknown records fail closed. No index is required.
Rollback is the reviewed unused source/schema/docs commit only. Never delete journal/provider-plan evidence as a casual rollback; deletion can remove the duplicate barrier and remains blocked on #110.
Required follow-on split
- PAY-002B2C2: atomically store first-send time/evidence before any POST, reuse the exact plan/key within a conservative source-owned window, stop automatic POST after an old or unknown send time, and preserve ambiguous outcomes. No automatic attempt advancement.
- PAY-002B2C3: reconcile verified Stripe/business/webhook/operator evidence and permit a later provider attempt only after separately proven non-execution or an explicitly safe corrected/new logical operation.
PAY-002C/D still own domain-specific reservations/result envelopes and actual response replay. PAY-003 owns verified webhook evidence. PAY-005 owns refund reconciliation. C1 must not absorb those schemas.
Excluded
- Stripe/provider call, raw key, provider object/response, account verification, send marker/permission, timeout retry, ambiguity, reconciliation, or attempt advancement.
- Domain business-record/result-envelope creation or dereferenceable response replay.
- Endpoint/index export or runtime adoption.
- Authorization, price, discount, tax, capacity, inventory, refund, webhook, mail, rate-limit, admin, confirmation-capability, or legal/policy behavior.
- Lease renewal, lifecycle mutation, terminal transition, TTL, deletion, backfill, migration, package/lockfile/workflow/index/environment/UI/officer/Rules-source/provider changes.
- Firebase deployment, production read/write, live checkout, or live testing.
Claim protocol
Creation is not a claim. Before editing: confirm exact current origin/main, assign the issue, post CLAIMED by /root at <UTC> with branch codex/issue-<number>-provider-plan-binding, apply status:in-progress, and use an isolated worktree.
Activity
daliu commentedon Jul 13, 2026
CLAIMED by /root at 2026-07-13T18:16:31Z; branch codex/issue-173-provider-plan-binding; exact base 2eff107.
Ownership is limited to the 10 paths listed in the issue: the three commerce-command source/test files, the focused existing Rules test, and six minimum root design/status documents. No Rules source, endpoint/index, server config, Stripe adapter, workflow, package/lockfile, environment, provider setting, production data, or officer procedure is claimed. Implementation will remain unused source and synthetic tests.
daliu commentedon Jul 13, 2026
EXTRACTION FINISHED by /root at 2026-07-13T18:49:13Z.
Review state: in review via PR #174 from exact commit f1d1dd6.
Exact scope: the 10 claimed paths only. Exact reviewed pre-commit diff SHA-256: 43cabd3865a5931a7a0d9e72c877e54d5ea5ba8f584b0482cabcbe455ea2991c.
Local proof: Node 20 focused 176/176; full Functions 834 passed; Java 17 Rules 311/311; frontend 134/134; SPA 10/10; build/lint/link/diff/sensitive scans green; final focused emulator 25/25 three consecutive times. Two independent exact-snapshot reviews approved with no findings after all review blockers were fixed.
This is still unused source. Website, runmprc.com, Firebase, Stripe/provider configuration, production data, and live behavior remain unchanged/unverified. User standing authorization permits merge/deploy after exact-head hosted checks pass.
daliu commentedon Jul 13, 2026
COMPLETED / claim released by /root at 2026-07-13T18:54:03Z.
Source and merge:
Hosted proof:
Deployment/provider truth:
This issue is complete as its explicitly unused source/test/design slice. PAY-002B2C2 and C3 remain required before any Stripe send/reconciliation behavior. The 10-path claim is released; refresh from acaa961 before touching these files.