You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Officer impact: None yet. This child defines and tests an unused provider-link lifecycle 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 provider-link lifecycle 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 a provider-neutral external-account contract with consent, desired and observed state, last reconciliation, safe error evidence, and a stable command ID. Merged child #367 provides a stateless reconciliation classifier and #370 provides effective versioned consent, but neither defines a durable revisioned lifecycle.
The missing lifecycle means duplicate or out-of-order commands, disconnect/relink, a changed provider account, and partial or unknown provider outcomes are not yet represented by one tested contract. Google, WhatsApp, and Strava runtime adoption remains blocked; this child defines only the policy-neutral mechanism they may later consume.
Atomic outcome
Add one unused CommonJS pure module and focused tests that define a versioned provider-link lifecycle snapshot plus an optimistic-concurrency command reducer. The reducer composes the existing #367 reconciliation classifier rather than duplicating its collision/consent/desired-versus-observed policy.
The snapshot carries only:
bounded opaque membership and provider-account references;
one provider from the existing provider-neutral vocabulary;
effective consent disposition;
desired and observed link state;
an optional opaque bound-membership reference used only for collision evidence;
safe-integer revision;
last reconciliation sequence, outcome, opaque attempt reference, and fixed error code;
the latest stable command ID, command payload hash, and expected revision.
The command vocabulary covers:
consent-disposition change;
request link or unlink;
record a reconciliation success, definitive failure, or outcome-unknown result; and
replace a provider-account reference only after unlink is both desired and observed.
Exact latest-command retry is read-only. Changed reuse, stale expected revision, skipped/repeated reconciliation sequence, stale observation, wrong-account evidence, provider-account replacement while linked or unknown, and out-of-order relink fail closed.
Invariants
Every record and verdict hard-codes grantsAuthority: false. A provider link 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.
It composes classifyProviderLinkReconciliation; it does not reimplement provider collision, consent gating, or desired/observed drift policy.
Provider-account replacement is never an in-place silent relink while the prior account may remain linked. The prior record must be desired-unlinked and observed-unlinked first.
An outcome-unknown reconciliation preserves the desired state and never guesses provider success. A definitive failure preserves the last known observation.
A successful observation is accepted only at the exact next reconciliation sequence and becomes the new known observation.
Error evidence is one closed non-identifying enum. No raw provider error, status/body, URL, token, email, phone, name, or free text is accepted, returned, stored, or logged.
Stable command IDs and payload hashes are bounded opaque values. The latest exact retry is idempotent; conflicting reuse fails. Durable all-history replay protection remains future trusted-runtime work.
Inputs are strict exact plain data objects. Proxies, accessors, inherited, extra, missing, symbol, non-enumerable, malformed, or out-of-range fields fail through one fixed non-echoing boundary.
Outputs and nested records are frozen.
No Firestore schema/Rules/index, Auth/custom claim, endpoint, UI, package, workflow, provider configuration, migration, deployment, production record, or live behavior changes.
Required tests
Create one valid unlinked lifecycle record for every existing provider; all results grant no authority.
Do not edit Functions index/endpoints, 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, tokens, errors, or production data.
Claim protocol
Creation is not a claim. Before any 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.
Officer impact: None yet. This child defines and tests an unused provider-link lifecycle 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 provider-link lifecycle section in
docs/officers/EVENTS_SHOP_MEMBERS.md, including a Mermaid diagram and text alternative. Current officer procedures remainNOT 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 a provider-neutral external-account contract with consent, desired and observed state, last reconciliation, safe error evidence, and a stable command ID. Merged child #367 provides a stateless reconciliation classifier and #370 provides effective versioned consent, but neither defines a durable revisioned lifecycle.
The missing lifecycle means duplicate or out-of-order commands, disconnect/relink, a changed provider account, and partial or unknown provider outcomes are not yet represented by one tested contract. Google, WhatsApp, and Strava runtime adoption remains blocked; this child defines only the policy-neutral mechanism they may later consume.
Atomic outcome
Add one unused CommonJS pure module and focused tests that define a versioned provider-link lifecycle snapshot plus an optimistic-concurrency command reducer. The reducer composes the existing #367 reconciliation classifier rather than duplicating its collision/consent/desired-versus-observed policy.
The snapshot carries only:
The command vocabulary covers:
Exact latest-command retry is read-only. Changed reuse, stale expected revision, skipped/repeated reconciliation sequence, stale observation, wrong-account evidence, provider-account replacement while linked or unknown, and out-of-order relink fail closed.
Invariants
grantsAuthority: false. A provider link never grants membership, price, payment state, role, or admin authority.classifyProviderLinkReconciliation; it does not reimplement provider collision, consent gating, or desired/observed drift policy.Required tests
Exact owned paths
functions/membershipProviderLinkLifecycle.jsfunctions/membershipProviderLinkLifecycle.test.jsSYSTEM_DESIGN.mddocs/officers/EVENTS_SHOP_MEMBERS.mdDependencies and coordination
Explicitly out of scope
Do not edit Functions index/endpoints, 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, tokens, errors, or production data.
Claim protocol
Creation is not a claim. Before any 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 movestatus:readytostatus:in-progress. Earliest valid claim wins.