Skip to content

MEMBERS-IDENTITY-001G — Define append-oriented consent-decision receipts #447

Description

@daliu

Officer impact: None yet. This child defines and tests an unused technical consent-decision receipt contract. It does not change signup, membership, discounts, Google, WhatsApp, Strava, officer screens, or any live account.

Officer documentation: Update only a named future-state consent-receipt section in docs/officers/EVENTS_SHOP_MEMBERS.md, including a Mermaid diagram and text alternative. Current officer procedures remain NOT AVAILABLE YET.

Deployment evidence: None at issue creation. Closure must distinguish source, tests, merge, website publication, Firebase deployment, provider configuration, production data, migration, and live behavior. A green workflow does not prove an external surface is live.

Problem

Parent #81 requires provider-neutral consent as part of external-account state. Merged child #370 derives effective consent only from caller-supplied latest-decision evidence, and merged child #445 consumes that effective disposition in provider-link lifecycle reconciliation. Neither contract defines how a grant or withdrawal becomes an immutable, ordered, replay-safe technical receipt.

Without this seam, a future runtime could overwrite the latest decision, accept a skipped or conflicting command, silently reorder withdrawal and re-grant, or feed #370 evidence that was not projected from the canonical technical history head.

Atomic outcome

Add one unused CommonJS pure module and focused tests that:

  1. create a bounded provider-neutral consent-decision track head;
  2. append one immutable grant or withdrawal receipt from the current head and one exact command; and
  3. derive the effective disposition by projecting the head into the real classifyConsentState contract from MEMBERS-IDENTITY-001D — Provider-neutral versioned consent-state derivation (source only, unused) #370.

Use append-oriented individual receipts plus one bounded latest-state head. Do not place an unbounded receipts[] history in a mutable record.

Invariants

  • Every track, receipt, and disposition hard-codes grantsAuthority: false. Consent never grants membership, price, payment state, role, or admin authority.
  • The module is pure, deterministic, clockless, unused, CommonJS, and imported by no runtime or Functions index.
  • One track is bound to exactly one provider, opaque subject reference, and opaque scope reference.
  • The empty head has revision zero and represents no decision. none is never accepted as an append command.
  • The first receipt has revision one and no prior receipt; every later receipt advances the exact current revision by one and binds the exact latest receipt.
  • Exact retry of the latest command is read-only and returns the same canonical frozen receipt/head. Reusing that command ID with a changed receipt ID, expected revision, decision, or policy version fails closed.
  • A stale, future, skipped, repeated, or unsafe revision; current receipt-ID reuse; cross-track evidence; an internally inconsistent head; and safe-integer exhaustion fail closed.
  • Withdrawal never deletes or rewrites a prior grant. A later grant after withdrawal creates a new receipt.
  • Ordering comes only from the monotonic receipt revision. No timestamp, policy-version ordering, or recency rule is invented.
  • Policy versions are compared for equality only by MEMBERS-IDENTITY-001D — Provider-neutral versioned consent-state derivation (source only, unused) #370. This child defines no policy text, effective date, notice, scope vocabulary, or consent rule.
  • Inputs are strict exact plain data objects. Proxies, accessors, inherited fields, extra or missing fields, symbols, non-enumerable fields, malformed identifiers, PII-shaped values, and unsafe integers fail through one fixed non-echoing boundary.
  • Outputs and nested values are frozen. No input value is logged.
  • Durable all-history identifier uniqueness and create-only persistence remain future trusted-runtime responsibilities; this bounded source contract detects only conflicts visible at the current head.
  • Structural validation cannot authenticate a self-consistent rewritten head. Trusted authorization, canonical reads, create-only writes, and durable receipt preservation remain future runtime responsibilities.
  • The technical receipt is not proof of informed consent, legal compliance, actor authorization, notice delivery, or provider acceptance.

Required tests

  • Create an empty track and first grant/withdrawal receipt for every provider supported by MEMBERS-IDENTITY-001D — Provider-neutral versioned consent-state derivation (source only, unused) #370.
  • Append grant → withdrawal → grant without mutating any prior head or receipt.
  • Feed an empty head, a current grant, a grant under another required policy version, and a withdrawal through the real MEMBERS-IDENTITY-001D — Provider-neutral versioned consent-state derivation (source only, unused) #370 classifier and obtain not_consented, active, reaffirmation_required, and withdrawn.
  • Prove provider neutrality and exact provider/subject/scope binding against an independent test oracle.
  • Prove exact latest retry is read-only and returns the same frozen canonical receipt/head.
  • Reject changed latest-command reuse, stale/future/skipped/repeated revision, current receipt-ID reuse, cross-track and internally inconsistent heads, safe-integer exhaustion, malformed policy versions, and unknown enum values.
  • Prove hostile values/getters/proxies and raw email-, phone-, URL-, token-, error-, name-, or policy-text-shaped values never enter output, error, or logs.
  • Prove exact objects only: missing, extra, inherited, symbol, non-enumerable, accessor, and revoked-proxy inputs fail without getter invocation or input echo.
  • Prove outputs and nested enum collections are frozen.
  • Source scans prove only node:util and ./membershipConsentState are imported; there is no clock, environment, randomness, network, Firebase, provider SDK, logger, runtime/index import, or receipt-history array.
  • Focused tests, Functions lint/full tests, and applicable repository gates pass on Node 20.

Exact owned paths

  1. New functions/membershipConsentReceipt.js
  2. New functions/membershipConsentReceipt.test.js
  3. Only the named consent-receipt section/diagram in SYSTEM_DESIGN.md
  4. Only the matching future-state section/diagram in docs/officers/EVENTS_SHOP_MEMBERS.md

Dependencies and coordination

Remaining #81 work includes approved minimization/retention/deletion/access-revocation policy, runtime actor authorization, trusted capture facts, persistence, durable all-history command uniqueness, cross-record uniqueness, Firestore schema/Rules, Auth claim writes and token actions, provider wiring, migration, deployment, and live verification.

Explicitly out of scope

Do not add actor identity, capture method, notice or policy text, evidence content, timestamps represented as legal proof, provider-specific scope values, retention/deletion periods, withdrawal SLA, access roles, assisted-consent policy, or provider calls.

Do not edit Functions index/endpoints, Firestore Rules, packages, workflows, existing Google/WhatsApp/Strava runtime, GITHUB_ISSUES.md, GITHUB_ISSUE_SLICES.md, or provider/deployment configuration.

Do not use real members, accounts, provider identifiers, policy text, tokens, errors, or production data.

Claim protocol

Creation is not a claim. Before any repository file edit, assign the issue, create and push a unique branch from exact current main, post CLAIMED by <canonical agent> at <UTC>; branch <branch>; exact base <SHA>, re-read live comments, and move status:ready to status:in-progress. Earliest valid claim wins.

Metadata

Metadata

Assignees

Labels

area:authAuthentication and authorizationarea:firebaseFirebase services and dataarea:membershipMembership lifecycle and reconciliationpriority:P0Launch blocker or urgent security risksize:SSmall focused issuetype:architectureArchitecture and contract designtype:securitySecurity or privacy boundary

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions