Skip to content

PAY-002B1 — Define caller-scoped command identity and deterministic Stripe keys #163

Closed

Description

@daliu

Officer impact: None. This creates an unused server-side safety library. It does not add checkout, refund, admin, or officer actions.

Officer documentation: None — no officer-visible behavior, procedure, permission, provider setting, or deployed surface changes. Current commerce remains NOT AVAILABLE YET.

Deployment evidence: None at creation. Closure proves source, tests, review, merge, and exact-commit CI only. It must not claim website publication, Firebase deployment, Stripe configuration, provider objects, or live behavior.

Parent and dependencies

Atomic pure source child of catalogued PAY-002B.

No live PAY-002B issue, claim, or open pull request existed at creation. The current queue had no unclaimed status:ready issue.

Problem

Retries currently have no shared identity. A double click or lost network response can create a second Stripe object. Different endpoints could also invent incompatible key formats, leak caller information into provider keys, or mistake a reused command ID for a safe retry.

The cryptographic identity contract must be pure and stable before Firestore lease logic or a checkout endpoint adopts it.

Atomic outcome

Create one dependency-free CommonJS library that:

  1. accepts one canonical lowercase UUID v4 commandId;
  2. accepts a server-derived caller scope and explicit environment;
  3. creates a caller-scoped command document key;
  4. creates a versioned payload fingerprint from the trusted command type and an already schema-validated, deeply frozen payload;
  5. creates a deterministic versioned Stripe idempotency key for one logical provider operation and immutable provider-attempt number.

No runtime endpoint imports the module in this child.

Identity contract

Command key

commandKeyHash = SHA-256(domain, version, environment, caller-scope-kind, caller-scope-value, commandId)

  • The key deliberately does not include command type or provider operation.
  • The same caller cannot reuse one command ID for another operation without PAY-002B2 seeing the same record and a different fingerprint.
  • Environment separates local, test, staging, and production records.
  • Caller scope comes from trusted server context: a verified Firebase UID, a server-issued anonymous principal, or a fixed internal-system principal.
  • Email, phone, name, IP address, browser role, body-supplied membership state, and unverified provider identity are forbidden caller scopes.
  • Caller scope is collision/idempotency identity only. It never authorizes the command.

Payload fingerprint

payloadFingerprint = SHA-256(domain, version, commandType, canonicalPayload)

  • commandType is a trusted code constant using a bounded safe slug.
  • The payload must already have passed its endpoint-specific PAY-001 schema.
  • This helper independently verifies a deeply frozen, accessor-free, proxy-free, bounded JSON-like value with sorted object encoding and integer-only numbers.
  • The helper does not make an unknown field valid and does not replace an endpoint schema.
  • Object key order is irrelevant; array order remains meaningful.

Stripe key

A deterministic ASCII key derives from:

  • contract version;
  • expected Stripe mode (test or live);
  • bounded provider operation;
  • command key hash;
  • positive bounded provider-attempt number.

The key contains no raw caller, command, aggregate, payload, provider object, or personal value and remains comfortably below Stripe's 255-character maximum.

A Firestore lease takeover must preserve the same provider-attempt number and Stripe key. PAY-002B2 may advance the provider attempt only after a separately proven safe recovery decision. An uncertain or lost Stripe response preserves the same key. PAY-002B2 may repeat the exact POST only inside its conservative stored safe-send window; after that deadline or when send time is unknown, it must stop POSTing and reconcile. The local journal remains authoritative after Stripe may prune a key.

Official constraints:

Exact source boundary

Create only:

  • functions/commerceCommandIdentity.js
  • functions/commerceCommandIdentity.test.js

Minimum exact design/status hunks only in:

  • STRIPE_COMMERCE_DESIGN.md
  • SYSTEM_DESIGN.md
  • SECURITY.md
  • IMPLEMENTATION_PLAN.md
  • GITHUB_ISSUES.md
  • GITHUB_ISSUE_SLICES.md

Post a scope amendment before editing any endpoint, webhook, Function export/index, Firestore/Rules file, package/lockfile, workflow, environment file, UI, officer guide, provider setting, or production data.

Security and correctness invariants

  • No Firebase, Stripe SDK, network, filesystem, logger, clock, randomness, process environment, or provider import/call.
  • Use domain-separated, length-framed hashing so concatenation cannot create ambiguity.
  • Return recursively frozen fixed-shape values.
  • Throw only fixed-message/fixed-reason safe errors; never echo an unknown value.
  • Do not mutate or log inputs.
  • Reject raw/mutable objects, custom prototypes, proxies, accessors, symbols, cycles, sparse arrays, non-finite numbers, fractions, negative zero, malformed Unicode, dangerous keys, and over-budget payloads without invoking hostile code.
  • UUID input is canonical lowercase RFC 4122 variant UUID v4 only. Reject uppercase, padded, nil, other versions, and lookalikes.
  • Provider-attempt is a bounded positive safe integer supplied by trusted journal state, never the browser.
  • Digest/key outputs must not contain raw IDs, PII-like canaries, payload text, or secrets.
  • A matching hash does not authorize a command and does not itself prove replay; PAY-002B2 compares the stored fingerprint and state.
  • No source library enables commerce or proves Stripe/Firebase configuration.

Acceptance criteria

  • Canonical UUID v4 validation has fixed safe failures.
  • Command key is deterministic and changes when environment, caller scope kind/value, or command ID changes.
  • Command key does not change with command type; the fingerprint does.
  • Fingerprint is stable across object-key order and changes for array order, type, or any value.
  • Stripe key is deterministic, safe-character-only, below 255 characters, and separated by mode, provider operation, command, and provider attempt.
  • No output/error/log contains any raw caller, UUID, payload, or hostile canary.
  • Payload verification is immutable, integer-only, bounded, and fails closed on hostile structures without invoking getters/proxy traps.
  • All returned objects and nested values are frozen.
  • Source-boundary tests prove no forbidden import or side effect.
  • Node 20 focused/full Functions tests, Functions lint, frontend/SPA/Rules regressions, diff/sensitive-pattern review, and two independent exact-snapshot reviews pass.
  • Root docs distinguish this pure contract from PAY-002B2 journal persistence, runtime adoption, deployment, provider configuration, and live behavior.
  • No endpoint, webhook, Rule, package, workflow, environment, UI, officer, provider, or production setting changes.

Required tests

  • Malformed, uppercase, nil, non-v4, wrong-variant, padded, and Unicode-lookalike UUIDs.
  • Same tuple stability and one-dimension-at-a-time command-key separation.
  • Cross-command-type same key plus different fingerprint.
  • Equivalent object insertion orders, nested objects, significant array order, and integer boundaries.
  • Mutable root/nested values, cycles, proxies, accessors, custom/polluted prototypes, symbols, sparse/extra-key arrays, malformed Unicode, dangerous keys, fractions, non-finite values, negative zero, and payload byte/depth/entry limits.
  • Stripe key stability, mode/operation/command/attempt separation, bounded attempt validation, safe charset/length, and raw-value absence.
  • Repeated calls, input descriptor snapshots, frozen results, no console/logger output, and static forbidden-import/global scans.

Excluded

  • Firestore reads/writes, transactions, leases, fencing epochs, clocks, result replay, audit events, or emulator tests (PAY-002B2).
  • Stripe calls or provider-object recovery.
  • Endpoint schemas and adoption (PAY-001B/C/D and PAY-002C/D).
  • Webhook, refund, capacity, inventory, mail, admin, rate-limit, confirmation-capability, or legacy migration work.
  • Website/Firebase/provider deployment or production testing.
  • Officer procedures because no officer action exists.

Claim protocol

Creation is not a claim. Before editing: confirm exact current main, assign the issue, post CLAIMED by /root at ; branch codex/issue--command-identity, apply status:in-progress, and use an isolated worktree.

Activity

  1. added
    priority:P0Launch blocker or urgent security risk
    type:securitySecurity or privacy boundary
    size:SSmall focused issue
    status:readyDependencies complete and ready to claim
    area:stripeStripe payments and financial lifecycle
    status:in-progressClaimed and actively being implemented
    and removed
    status:readyDependencies complete and ready to claim
    on Jul 13, 2026
  2. self-assigned this
    on Jul 13, 2026
  3. daliu commented on Jul 13, 2026

    @daliu
    ContributorAuthor

    CLAIMED by /root at 2026-07-13T15:54:14Z; branch codex/issue-163-command-identity; exact base 68dbcae. Ownership is limited to the two new functions/commerceCommandIdentity files and the six root-document status/design hunks listed in the issue.

  4. daliu commented on Jul 13, 2026

    @daliu
    ContributorAuthor

    Pre-implementation contract amendment — security review

    This amendment supersedes any looser wording in the issue body. The file boundary does not change.

    Stripe retry safety

    • providerAttempt means one logical provider generation. It is not an HTTP retry count.
    • A network error, timeout, Stripe 5xx response, or unknown outcome never advances providerAttempt.
    • Before a conservative stored first-send deadline, a retry uses the exact same parameters, providerAttempt, and key.
    • Stripe may prune an idempotency key after at least 24 hours. Reusing that old key can then execute a new request, so “same key” is necessary but not sufficient forever.
    • After the safe deadline—or when first-send time is unknown—PAY-002B2 must stop automatic POST retries and reconcile stored provider references, verified webhook/metadata evidence, or an authorized operator result.
    • A later providerAttempt may advance only after separately proven non-execution or an explicitly safe corrected/new logical provider operation. B1 derives identity only; B2 owns time, blocking, evidence, and reconciliation.

    Closed environment/provider pairing

    Exact environments are local, test, staging, and production. Exact Stripe modes are test and live.

    • production requires live;
    • local, test, and staging require test;
    • every mismatch is rejected, not merely hashed differently;
    • environment remains bound into the provider-key digest.

    Identity, privacy, and encoding

    • Caller-scope kinds are closed: firebase_uid, anonymous_principal, and internal_system.
    • Firebase UID provenance must be verified server context. Email, phone, IP, name, token email, request-body role/membership, and provider identity are never substitutes.
    • Anonymous scope must already be a stable server-issued/validated principal.
    • internal_system accepts only one fixed exported principal, not an arbitrary value.
    • Hashes are pseudonymous server-only identifiers, not anonymization. Do not return them to a browser or put them in logs, URLs, analytics, screenshots, or issues.
    • Multi-tenant Auth is unsupported by version 1; a future version must bind tenant identity.
    • Provider operation is hashed and never copied raw into a Stripe key.
    • The version-1 wire algorithm uses distinct literal domains, field/type tags, UTF-8 byte-length frames, deterministic key sorting, and exact safe-integer decimal encoding. Golden vectors and frame-collision/field-swap tests freeze it; any encoding change requires version 2.
    • Canonical payloads accept recursively frozen null-prototype objects and ordinary frozen arrays with exact own data descriptors. Endpoint normalization owns Unicode normalization; B1 hashes exact well-formed UTF-8 bytes, so NFC and NFD remain distinct.
    • B2 must persist the B1 contract version, fingerprint, endpoint schema version, and semantic command type so a deployment cannot silently reinterpret an existing command.

    Additional required tests

    Add hard-coded golden digests, wrong environment/mode pairs, every UUID variant/form, nested hostile descriptors/proxies, prototype pollution, Date/Map/Set/Buffer/typed arrays/classes, shared aliases versus cycles, safe-integer boundaries, exact-byte Unicode distinction, fixed error serialization/inspection, and zero console calls.

    Official references:

  5. daliu commented on Jul 13, 2026

    @daliu
    ContributorAuthor

    EXTRACTION FINISHED by /root at 2026-07-13T16:18:24Z.

    Exact staged boundary: the eight claimed paths only. Cached diff SHA-256: 180ef0213fc6e881a5b3a06d94c65a6566d069824e15e4989934a6c7c4e1defb.

    Exact source SHA-256: 9458adfba96da5218f20df005dd8a268755bfb3a10ffb58755b8cd9a57781af7.
    Exact test SHA-256: db36cec470c6ddba4e8a80f9513cc7fb8319281a8ecde169f63dbeca74f624d1.

    Local Node 20 proof:

    • focused command identity: 91/91
    • full Functions: 658 passed, 1 intentional skip
    • frontend: 132/132
    • SPA navigation: 10/10
    • Firestore Rules with Java 17 and demo project: 305/305
    • Functions lint, syntax, diagnostic production build, diff check, relative-link check, and credential-pattern scan: passed

    Independent exact-snapshot reviews: code APPROVED, adversarial security APPROVED after the revoked nested Proxy regression was fixed, and documentation/scope APPROVED after the Stripe safe-send cutoff and multi-tenant boundary were clarified.

    This is source and local-test evidence only. No endpoint imports the library. No website, Firebase, Stripe provider, environment, or production behavior changed or was verified.

  6. daliu commented on Jul 13, 2026

    @daliu
    ContributorAuthor

    COMPLETED by /root at 2026-07-13T16:24:06Z.

    PR #164 was squash-merged to main as exact commit 6c5d39ad3204c24928c11f194de9f3330198dc6d.

    Proof:

    • Exact-head hosted CI run 29265911124 passed frontend, Functions, and Firestore Rules.
    • Netlify deploy preview 6a550ff61ece23000829a97c reached ready for exact PR head 3005fbbdb1191a6f6ac4b679286fdde24cdbde4f.
    • Exact post-merge CI run 29266062306 passed frontend, Functions, and Firestore Rules.
    • Three independent exact-snapshot reviews approved after revoked root/nested Proxy and documentation retry-boundary findings were fixed.
    • Acceptance boxes are checked and the active claim label is released.

    Release truth:

    • Protected production release run 29266174134 passed exact-current-main and exact-push-CI preflight.
    • It then failed closed because the required public App Check site key repository/environment variable is absent.
    • Website artifact build, GitHub Pages publication, and Firebase deployment were skipped.
    • Automatic Netlify production deploy 6a55107e1ece2300082bb2cb is error and unpublished.
    • runmprc.com returned HTTP 200, but the latest ready Netlify production deploy remains e86a0f702cff6495f50630c5de3337290db8b8cb; this merge is not live there.

    This outcome is intentionally source-only. No endpoint imports the library, so no checkout/refund behavior changed. PAY-002B2 remains the required journal/lease/replay/reconciliation adoption slice.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:stripeStripe payments and financial lifecyclepriority:P0Launch blocker or urgent security risksize:SSmall focused issuetype:architectureArchitecture and contract designtype:reliabilityReliability and recoverytype:securitySecurity or privacy boundary

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions