diff --git a/IMPLEMENTATION_PLAN.md b/IMPLEMENTATION_PLAN.md index c2cc8cca..d32b523a 100644 --- a/IMPLEMENTATION_PLAN.md +++ b/IMPLEMENTATION_PLAN.md @@ -28,7 +28,7 @@ MPRC can open a race or merchandise item for sale only when the platform can: 3. Schema changes are additive first. Backfills are idempotent, dry-runnable, and report counts/anomalies. 4. Backend/rules/indexes deploy before dependent clients. Legacy reads remain until migration evidence is complete. 5. No issue requires production secrets for development or CI. -6. Live-mode enabling is a separate protected operation. #135 supplies a manual, exact-commit, backend-first source gate and pauses ordinary Git-triggered Netlify production builds. WEB-UX-001A [#457](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/457) used one temporary web-only exception that required one exact `main` parent and independently verified one pinned source commit/tree. An overbroad WEB-002A attempt was rolled back on 2026-08-01; bounded replacement [#473](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/473) then published only the static Shop and fixed Events/Calendar failure messages as exact deploy `6a6dc9ea588b0c0008036312`. Its public marker and page checks passed, and its temporary manifest is inactive again. This does not restore event records. Neither exception authorizes Firebase, commerce, or a reusable release path. Protected environment/OIDC configuration, staged/live proof, fail-closed lint, required checks, and a protected live-Netlify path remain open under #105/#133/#136/WEB-001. +6. Live-mode enabling is a separate protected operation. #135 supplies a manual, exact-commit, backend-first source gate and pauses ordinary Git-triggered Netlify production builds. WEB-UX-001A [#457](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/457) used one temporary web-only exception that required one exact `main` parent and independently verified one pinned source commit/tree. An overbroad WEB-002A attempt was rolled back on 2026-08-01; bounded replacement [#473](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/473) then published only the static Shop and fixed Events/Calendar failure messages as exact deploy `6a6dc9ea588b0c0008036312` from source `39ab8649df411262c8109a3c81a57bc38f1e168b`. Its public marker and page checks passed, and its temporary manifest became inactive again. WEB-002C [#623](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/623) is now an active one-shot under review for one frozen inert member-directory interface artifact: source `c2d87d1f69f15e128a0bc9b1b9f915b7c8417aec`, tree `411aa6ec9a9459f5d923030533ffc7c007fe6908`, 62 files, digest `d837272a1e5efc1575809e87f532276b38d1a63f1dd79ec1aef0533f6da8afb1`, and expected first parent `019353361210021483f23003e09ee6924b78e67c`. Until exact preview, merge, signed-out production readback, and immediate repause pass, #473 remains production and its deploy is the rollback target. #623 does not restore event records or authorize Firebase, providers, sign-in, production data, connected directory behavior, commerce, or a reusable release path. Protected environment/OIDC configuration, staged/live proof, fail-closed lint, required checks, and a protected live-Netlify path remain open under #105/#133/#136/WEB-001. 7. A provider Console setting is not complete until its non-secret configuration and verification evidence are recorded privately. 8. Security controls fail closed in hosted environments and remain developer-friendly only in explicit local/CI environments. 9. Do not trade payment integrity for UI responsiveness. Confirmation may say “processing”; it must not guess “paid.” @@ -169,7 +169,7 @@ Exit gate: - Payment SLOs, structured redacted logs, and actionable alerts are live. - Backup restoration into isolated infrastructure succeeds and is documented. -**Current optional-directory boundary:** Parent [#504](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/504) defines a private, opt-in officer people finder as name search with voluntary thumbnails—not facial recognition. MEMBERS-DIRECTORY-001A [#505](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/505) owns the signed-in person's server-only processed thumbnail and independent default-off preference. MEMBERS-DIRECTORY-001B [#506](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/506) adds source for the minimum server-only projection, current-state reconciler, bounded verified-admin name-prefix callable, query-free audit, and separate `/admin/member-directory` gallery. It returns at most 24 current opted-in display-name/optional-thumbnail cards and has no image query, facial recognition, cursor, total, export, or membership authority. MEMBERS-DIRECTORY-001D [#621](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/621) makes the shipped frontend boundary default unavailable: My Account and the guarded officer route show only visibly described, disabled layouts and perform zero directory reads, uploads, searches, saves, request-ID creation, or service calls. [#507](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/507) still owns #110 policy completion, scoped authorization, #133 protected authority, isolated staging, required backend/index deployment and readback, and a later separately reviewed source flip before connected website publication and live proof. Do not reuse the current browser-side full-account filter or describe a disabled preview or #505/#506 source as an available directory. +**Current optional-directory boundary:** Parent [#504](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/504) defines a private, opt-in officer people finder as name search with voluntary thumbnails—not facial recognition. MEMBERS-DIRECTORY-001A [#505](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/505) owns the signed-in person's server-only processed thumbnail and independent default-off preference. MEMBERS-DIRECTORY-001B [#506](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/506) adds source for the minimum server-only projection, current-state reconciler, bounded verified-admin name-prefix callable, query-free audit, and separate `/admin/member-directory` gallery. It returns at most 24 current opted-in display-name/optional-thumbnail cards and has no image query, facial recognition, cursor, total, export, or membership authority. MEMBERS-DIRECTORY-001D [#621](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/621) makes the shipped frontend boundary default unavailable: My Account and the guarded officer route show only visibly described, disabled layouts and perform zero directory reads, uploads, searches, saves, request-ID creation, or service calls. #623 freezes that inert interface over the current live source for one bounded Netlify release, but it is under review and not published. Protected layout proof remains synthetic; signed-out public proof, if publication later passes, is limited to revision and guard readback plus the absence of a directory request. [#507](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/507) still owns #110 policy completion, scoped authorization, #133 protected authority, isolated staging, required backend/index deployment and readback, and a later separately reviewed source flip before connected website publication and live proof. Do not reuse the current browser-side full-account filter or describe a disabled preview, #623 release candidate, or #505/#506 source as an available directory. ### Phase 5 — End-to-end qualification diff --git a/OFFICER_START_HERE.md b/OFFICER_START_HERE.md index 4e91c2e5..4edd6fa3 100644 --- a/OFFICER_START_HERE.md +++ b/OFFICER_START_HERE.md @@ -46,8 +46,8 @@ Use the club's approved password manager for access. Share only a public link or - **Website live:** Did Netlify identify that commit, and was the exact change then seen on [runmprc.com](https://runmprc.com)? - **Outside service verified:** If Stripe, Netlify, DNS, Google, or email changed, was that service checked separately? -As of **2026-08-01**, a merge runs checks but does not start the GitHub release. The protected release is **NOT AVAILABLE YET** until its short-lived cloud identity and named environment approvers are configured under issue #133. Ordinary Git-triggered Netlify production builds are paused. An overbroad #473 web artifact was published and immediately rolled back. The bounded replacement is now live as Netlify deploy `6a6dc9ea588b0c0008036312`: Shop is the static in-person catalog, while Events and Calendar show a fixed retry-later notice instead of a raw provider error. Event records are still unavailable because no Firebase repair was deployed. The temporary release manifest is inactive again. This does not change sign-in, expose protected event offers, add officer editing, or make commerce safe. GitHub Pages still reports `runmprc.com` as its custom domain even though Netlify serves that name; source removal is not provider proof. A green test or workflow does **not** by itself prove that GitHub Pages, `runmprc.com`, Firebase, or that domain setting changed. +As of **2026-08-13**, a merge runs checks but does not start the GitHub release. The protected release is **NOT AVAILABLE YET** until its short-lived cloud identity and named environment approvers are configured under issue #133. Ordinary Git-triggered Netlify production builds are paused. An overbroad #473 web artifact was published and immediately rolled back. The bounded replacement is still live as Netlify deploy `6a6dc9ea588b0c0008036312` from source `39ab8649df411262c8109a3c81a57bc38f1e168b`: Shop is the static in-person catalog, while Events and Calendar show a fixed retry-later notice instead of a raw provider error. Event records are still unavailable because no Firebase repair was deployed. #623 is a separate one-shot release under review for a frozen inert member-directory interface. It has not published or been verified, and the current #473 deploy is its rollback target. This does not change sign-in, expose protected event offers, add officer editing, or make commerce safe. GitHub Pages still reports `runmprc.com` as its custom domain even though Netlify serves that name; source removal is not provider proof. A green test or workflow does **not** by itself prove that GitHub Pages, `runmprc.com`, Firebase, or that domain setting changed. -The optional profile-photo and officer People-finder functions are still **NOT AVAILABLE YET**. #621 makes the frontend default an inert preview: My Account shows the future photo and separate finder-choice controls disabled, while the People finder stays behind the administrator guard and shows its name field and Search button disabled. The preview reads no saved photo or setting, accepts or uploads no photo, searches no name, and saves nothing; it shows no people or sample results. Officers inspect those protected disabled layouts only in synthetic local artifacts. After publication, signed-out public review proves only the exact revision, normal sign-in and administrator guards, and absence of a member-directory network request. Do not choose a real photo, enter a real name, or treat the preview as a directory. #507 must later prove the privacy, authorization, staging, backend-first deployment, and readback gates before a separate reviewed source change may connect it. Follow the preview and source-review procedure in [Events, shop, members, and money](./docs/officers/EVENTS_SHOP_MEMBERS.md). +The optional profile-photo and officer People-finder functions are still **NOT AVAILABLE YET**. #621 makes the frontend source default an inert preview: My Account shows the future photo and separate finder-choice controls disabled, while the People finder stays behind the administrator guard and shows its name field and Search button disabled. The preview reads no saved photo or setting, accepts or uploads no photo, searches no name, and saves nothing; it shows no people or sample results. #623 freezes that interface over the current live source for one exact release, but the release is still under review and production still serves #473. Officers inspect the protected disabled layouts only in synthetic local artifacts. If #623 later publishes, signed-out public review proves only the exact revision, normal sign-in and administrator guards, and absence of a member-directory network request. Do not sign in to production, choose a real photo, enter a real name, or treat the preview as a directory. #623 changes no Firebase, provider, account, sign-in, or production data. #507 must later prove the privacy, authorization, staging, backend-first deployment, and readback gates before a separate reviewed source change may connect it. Follow the preview and source-review procedure in [Events, shop, members, and money](./docs/officers/EVENTS_SHOP_MEMBERS.md). For the concise handbook, see [OFFICER_HANDBOOK.md](./OFFICER_HANDBOOK.md). The expanded task index is [docs/officers/README.md](./docs/officers/README.md). diff --git a/OPERATIONS_RUNBOOK.md b/OPERATIONS_RUNBOOK.md index 20b17992..9188b8d6 100644 --- a/OPERATIONS_RUNBOOK.md +++ b/OPERATIONS_RUNBOOK.md @@ -689,6 +689,8 @@ Post-release control result: safety merge `04d6d2cd88dda0e6ec7e2b6a71514e6ce2e4d WEB-002A [#473](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/473) completed its bounded web-only permissions containment on 2026-08-01. Exact two-parent merge `9ad6837756cdd409d296009fde5082eeeae5c059` published Netlify deploy `6a6dc9ea588b0c0008036312` from frozen source `39ab8649df411262c8109a3c81a57bc38f1e168b`, tree `d76b496cdc5e79015bc048cb961758abbe88b9ce`, 60 files, and digest `07b10c7d5ff176a1ad893d7549b07042312df35f4a4126e8765824ca94eaefb8`. Preview and live marker/header/robots/deep-route checks passed. Signed-out Shop, Events, Calendar, and Account checks passed; Shop shows the approved static catalog, and Events/Calendar show only the fixed retry-later notice. Final control merge `cb6a8f0a418fc14b448bce5ded71d68520415c92` made the temporary manifest inactive; Netlify attempt `6a6dcdd47bc81e000859a249` stopped unpublished, exact-main CI run `30696264830` passed all five jobs, and provider plus public-marker readback left deploy `6a6dc9ea588b0c0008036312` live. Both temporary release-source refs are absent; exact rollback source `ed1b0833f25822cee80c99ded8753722b5608a3f` remains. This did not restore event records or deploy Firebase, Rules, Functions, provider configuration, account state, protected offers, officer editing, ledger, or production data. Keep rollback deploy `6a6dc219a8136300081811db` as dated incident evidence. +WEB-002C [#623](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/623) is an active one-shot Netlify release under review, not a completed publication. Its only frontend input is frozen inert source `c2d87d1f69f15e128a0bc9b1b9f915b7c8417aec`, tree `411aa6ec9a9459f5d923030533ffc7c007fe6908`, built as 62 files with digest `d837272a1e5efc1575809e87f532276b38d1a63f1dd79ec1aef0533f6da8afb1`. The release control expects exact first parent `019353361210021483f23003e09ee6924b78e67c`. Until its pinned preview, exact two-parent merge, production marker, signed-out route/network checks, and immediate repause all pass, production remains #473 deploy `6a6dc9ea588b0c0008036312` from source `39ab8649df411262c8109a3c81a57bc38f1e168b`; that same deploy is #623's rollback target. Protected Account and administrator layouts are proved only with synthetic local artifacts. Production checks must stay signed out and can prove only the exact revision, normal sign-in and administrator guards, and absence of a member-directory request. Stop for any source, tree, count, digest, parent, marker, route, network, or repause mismatch. #623 has no authority for Firebase, Rules, Functions, indexes, outside-provider configuration, account/sign-in changes, real names/photos, or production data. Connected behavior remains **NOT AVAILABLE YET** under [#507](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/507). Follow the pending procedure in `docs/officers/PUBLISH_AND_CHECK.md`; do not reuse it as a general release button. + Incident record: on 2026-08-01, overbroad source `094af1096ed8721597561cd59bf695d4c4a9d210` was published by merge `40728ff6141e34a279b70cc41d983c22ac5f0daa` as deploy `6a6dc0167fbe68000816b448` after a release-blocker comment. Exact rollback merge `1099ee8e6fdb81141fd9460de175b6d854cbcfdd` published deploy `6a6dc219a8136300081811db`, restoring source `ed1b0833`, tree `878c6628d961f4484cb49208aef53f1e9f2e3b47`, 60 files, and digest `7570955c2a00926e5813aef135f1799172cfd046072ac89fb4e492bed0797092`. Safety merge `dee79511b6e371329aa129139729e112e7a51aad` re-paused the manifest; its Netlify attempt `6a6dc35767a4ef000877e74b` did not publish, and provider readback left the rollback deploy live. The overbroad release ref was deleted and verified absent. This incident changed no Firebase, outside-provider configuration, account, payment, or production data. ### Production approvals diff --git a/SECURITY.md b/SECURITY.md index f89b2a81..c6daaba4 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -68,7 +68,7 @@ The findings below describe the repository at the start of the 2026-07-12 assess | RISK-023 | The legacy `functions.config().api.key` endpoint uses a static shared key with no replay control, source restriction, App Check, authenticated operator, or request audit. The API is also scheduled for Firebase decommissioning in March 2027. | Key theft permits bulk role changes and future deployment failure. | Retire it in favor of authenticated admin workflow or narrowly authenticated scheduled import; migrate any remaining config to Secret Manager. | | RISK-024 | OAuth tokens remain plaintext. Repository Rules deny browser access, but protected deployment and live behavior are unproven; least-privilege runtime IAM and an encryption decision remain open. OAUTH-001A1H [#606](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/606) source transactionally rereads the exact access-token, refresh-token, and expiry tuple before persisting a refresh response. A retry rereads the tuple; changed, missing, or malformed state returns one fixed failure without a stale write or downstream bearer call. OAUTH-001B9 [#608](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/608) source records the disconnect secret's Firestore update time before optional revocation, then transactionally rereads it and deletes the local secret/connection pair only when the secret is absent or still has that exact version. A newer version, including byte-identical replacement, is preserved with one fixed unknown result; a transaction conflict rereads without repeating provider work. OAUTH-001B10 [#610](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/610) browser source admits only a fulfilled response value that presents exactly one own enumerable data field, `ok` equal to `true`; it returns a new frozen confirmation and routes other fulfilled results or exceptional inspection through the existing fixed unknown-result view without hiding the connection. Browser JavaScript cannot distinguish a transparent Proxy that faithfully presents that contract, so the received object is never retained. Synthetic tests use no real token, account, or provider call. Provider calls are still not serialized; an exact browser confirmation does not independently prove provider revocation, and provider/local and lost-acknowledgement reconciliation, a trusted connection-only write that does not also version the secret, deployment, and live behavior remain unproven. | Token theft exposes member activity data; an undeployed boundary or an unhandled provider/acknowledgement race can still lose availability or leave local and provider state ambiguous. | Deploy and read back server/IAM-only secret access, decide encryption, serialize or otherwise reconcile refresh and disconnect operations and lost acknowledgements, minimize scopes, audit access, and verify only with approved made-up staged accounts. | | RISK-025 | Admin authentication relies on password Auth plus a long-lived role token; no repository evidence of MFA, recent-auth checks, re-auth for refunds/role grants, or rapid revocation workflow. | A stolen admin session has broad durable impact. | Require MFA for privileged accounts, recent-auth for sensitive actions, capability roles, short sessions/forced refresh, and break-glass procedures. | -| RISK-042 | Profile photos and an officer people finder have no approved privacy notice, retention/backup treatment, scoped capability, or production release path. The existing Admin Members page downloads complete account documents and cannot enforce an opt-out promise. #505/#506 source supplies the private processed thumbnail, default-off preference, minimum server-only projection, and bounded audited verified-admin name search, but none is deployed. MEMBERS-DIRECTORY-001D [#621](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/621) makes the frontend default an inert, disabled preview with zero directory reads, uploads, searches, saves, request IDs, or service calls; it does not make the backend available. | A photo, name, query, preference, prefix digest, or stable entry reference could be exposed, retained, enumerated, correlated, or mistaken for membership evidence if the connected path is enabled prematurely; automated face matching would add unapproved biometric processing. A browser availability constant is not authorization. | Keep the source-controlled availability boundary off and the feature separate from the official roster. Preserve current-source revalidation, direct Rules denials, fixed query/result bounds, no-store responses, pseudonymous rate keys, query-free audit, and synthetic race/opt-out/removal proof. Never store the original, persist raw queries, or add face recognition. Complete #110 policy inputs, AUTH-003 scoped authorization follow-up, #133 protected authority, and #507 staging plus backend-first deployment/readback before a reviewed source flip and connected website publication. | +| RISK-042 | Profile photos and an officer people finder have no approved privacy notice, retention/backup treatment, scoped capability, or production release path. The existing Admin Members page downloads complete account documents and cannot enforce an opt-out promise. #505/#506 source supplies the private processed thumbnail, default-off preference, minimum server-only projection, and bounded audited verified-admin name search, but none is deployed. MEMBERS-DIRECTORY-001D [#621](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/621) makes the frontend default an inert, disabled preview with zero directory reads, uploads, searches, saves, request IDs, or service calls; it does not make the backend available. WEB-002C [#623](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/623) has one frozen inert frontend artifact under review for a bounded Netlify release, but it is not published and does not connect the backend. | A photo, name, query, preference, prefix digest, or stable entry reference could be exposed, retained, enumerated, correlated, or mistaken for membership evidence if the connected path is enabled prematurely; automated face matching would add unapproved biometric processing. A browser availability constant is not authorization. | Keep the source-controlled availability boundary off and the feature separate from the official roster. Preserve current-source revalidation, direct Rules denials, fixed query/result bounds, no-store responses, pseudonymous rate keys, query-free audit, and synthetic race/opt-out/removal proof. Never store the original, persist raw queries, or add face recognition. Complete #110 policy inputs, AUTH-003 scoped authorization follow-up, #133 protected authority, and #507 staging plus backend-first deployment/readback before a reviewed source flip and connected website publication. | | RISK-026 | Firestore Admin SDK bypasses rules and the Functions runtime IAM scope is not documented. #135 removes the long-lived service-account JSON path from release source, but the least-privilege short-lived deploy identity is not configured yet. | Server or CI compromise may expose the entire project; missing provider configuration also blocks release. | Complete the private IAM inventory and #133 OIDC/WIF configuration; keep runtime and deploy identities separate; never restore a JSON key shortcut. | | RISK-027 | Sentry optionally records user email and enables 100% replay on error; analytics/privacy consent, redaction, retention, and field-deny policies are not evidenced. | Forms and account flows contain PII that may be sent to monitoring vendors unexpectedly. | Disable replay on sensitive routes or configure strict masking, avoid email user context, set scrubbing/retention, consent policy, and vendor agreements. | | RISK-028 | No payment reconciliation job, dead-letter/quarantine workflow, or alert proves paid Stripe objects match Firestore. | Missed webhooks and partial failures can persist unnoticed. | Add scheduled reconciliation, alert thresholds, operator repair tooling, and a daily finance report. | @@ -85,7 +85,7 @@ The findings below describe the repository at the start of the 2026-07-12 assess | RISK-033 | CSV export mitigates formula injection, but roster export has no explicit re-auth, download audit, row limit/streaming limit, or data-minimization profiles. | Add scoped exports, reason/re-auth, audit, minimum columns, safe filename IDs, and large-export controls. | | RISK-034 | Webhook error response includes the Stripe library's signature error detail. | Return generic client errors; keep sanitized structured diagnostics server-side. | | RISK-035 | The deterministic frontend Jest suite and standalone SPA callback suite run as separate blocking hosted CI steps. CI-001B4/#186 merged a non-mutating frontend lint gate as `bec7d5e365eacb418563a172029f241f660d9768`; exact PR and post-merge runs passed. CI-001B4A [#227](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/227) removes one reviewed `arrow-body-style` error, CI-001B4B [#239](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/239) removes one stale `AdminMembers` unknown-rule suppression record, and PAY-004C1 [#359](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/359) retires one `no-alert` warning plus two label-association errors with the unsafe reusable-link controls. Reviewed functional changes in [PR #391](https://github.com/Run-MPRC/Run-MPRC.github.io/pull/391) and [PR #392](https://github.com/Run-MPRC/Run-MPRC.github.io/pull/392) add one and two TypeScript files respectively while retiring two TSX errors each. CI-001B4C [#449](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/449) replaces the four remaining stale `react-hooks/exhaustive-deps` directives with ordinary same-line comments, without changing executable code or the gate. MEMBERS-CONTENT-001B [#492](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/492) removes one finding-free dormant JSX file. MEMBERS-DIRECTORY-001A [#505](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/505) adds four finding-free TypeScript/TSX files, MEMBERS-DIRECTORY-001B [#506](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/506) adds four more, WEB-SUGGESTIONS-001A [#618](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/618) adds one finding-free JSX page, and MEMBERS-DIRECTORY-001D [#621](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/621) adds one finding-free TypeScript availability module. The current baseline scans 120 files and still records 113 configured errors and 6 warnings after the lint process disables the repository's severity-masking `eslint-plugin-only-warn` hook. Branch protection, remaining lint-debt cleanup, and broader domain/integration coverage remain incomplete. | Continue reducing the reviewed finding baseline in focused changes, prove required branch checks, and add domain/integration coverage. Never regenerate the baseline merely to make CI green. | -| RISK-036 | #135 adds a manual exact-commit source gate, fixed profile-recovery targets, backend-first order, missing-config failure, and ordinary Git-triggered Netlify production containment. WEB-UX-001A [#457](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/457) used a temporary exact-artifact exception. On 2026-08-01 an overbroad WEB-002A artifact was merged after a late blocker and published; exact rollback merge `1099ee8` restored source `ed1b0833`, and `dee7951` paused the manifest. Bounded replacement [#473](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/473) then published exact source `39ab8649` as deploy `6a6dc9ea588b0c0008036312`; its delta is only Shop and Events/Calendar failure containment, and its public checks passed. Final control `cb6a8f0` made the manifest inactive; Netlify attempt `6a6dcdd47bc81e000859a249` stopped unpublished and left the bounded deploy live. Protected environments/OIDC, isolated staging, a reusable live-Netlify path, and provider-owned atomic rollback remain unverified. | Require a final blocker re-read and an executable delta from the live artifact before every release merge. Preserve the exact #473 marker and rollback evidence, keep ordinary publication paused, and keep the reviewed Git rollback projection available. Complete #133 and #136, provision isolated staging, protect the reusable Netlify release path under WEB-001, and rehearse provider rollback before broader production work. | +| RISK-036 | #135 adds a manual exact-commit source gate, fixed profile-recovery targets, backend-first order, missing-config failure, and ordinary Git-triggered Netlify production containment. WEB-UX-001A [#457](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/457) used a temporary exact-artifact exception. On 2026-08-01 an overbroad WEB-002A artifact was merged after a late blocker and published; exact rollback merge `1099ee8` restored source `ed1b0833`, and `dee7951` paused the manifest. Bounded replacement [#473](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/473) then published exact source `39ab8649` as deploy `6a6dc9ea588b0c0008036312`; its delta is only Shop and Events/Calendar failure containment, and its public checks passed. Final control `cb6a8f0` made the manifest inactive; Netlify attempt `6a6dcdd47bc81e000859a249` stopped unpublished and left the bounded deploy live. WEB-002C [#623](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/623) is now an active one-shot release under review. It pins frozen inert source `c2d87d1f69f15e128a0bc9b1b9f915b7c8417aec`, tree `411aa6ec9a9459f5d923030533ffc7c007fe6908`, 62 files, and digest `d837272a1e5efc1575809e87f532276b38d1a63f1dd79ec1aef0533f6da8afb1` to exact expected first parent `019353361210021483f23003e09ee6924b78e67c`, with current deploy `6a6dc9ea588b0c0008036312` as rollback. Until preview, merge, production readback, and immediate repause all pass, production remains the #473 deploy and source `39ab8649df411262c8109a3c81a57bc38f1e168b`. Protected environments/OIDC, isolated staging, a reusable live-Netlify path, and provider-owned atomic rollback remain unverified. | Require a final blocker re-read and an executable delta from the live artifact before every release merge. Preserve the exact #473 marker and rollback evidence, keep ordinary publication paused, and keep the reviewed Git rollback projection available. #623 must fail closed on any parent, source, tree, count, digest, marker, or public-check mismatch and must be re-paused immediately after successful readback. Complete #133 and #136, provision isolated staging, protect the reusable Netlify release path under WEB-001, and rehearse provider rollback before broader production work. | | RISK-037 | Account/registration deletion, export, retention, backup, and restore procedures are incomplete. | Approve retention matrix, automate minimization, support access/deletion requests, and test backup restoration. | | RISK-038 | Source-controlled secret scan is ad hoc; no continuous secret scanner, dependency update bot, SBOM, provenance, or branch protection is documented. | Add secret/dependency/code scanning, reviewed lockfile updates, protected environments/branches, and artifact provenance appropriate to project scale. | | RISK-039 | Some authenticated accounts can lack `members/{uid}` after the Firebase cutover; the account screen hid the read failure and exposed an update that could only fail. Manual database/account repair could corrupt roles or private data. | Use an authenticated create-once server bootstrap, keep browser creation denied, fail the UI closed, and prove backend-first deployment with synthetic accounts. | @@ -99,7 +99,7 @@ These entries are implementation evidence, not a production risk-acceptance deci | --- | --- | --- | | Source-only containment for RISK-008, RISK-017, RISK-022, RISK-032, and RISK-042 | MEMBERS-DIRECTORY-001A [#505](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/505) adds four caller-only profile callables with native App Check enforcement and fail-closed private no-store responses. Strict requests, revisions, current-name opt-in, normalized 256-by-256 WebP output, metadata/animation/size rejection, separate default-off visibility, server-only preference/photo records, atomic append-oriented audits, and direct-browser denials are covered with generated non-face data. No original image, filename, provider photo URL, image-derived digest, face matching, or membership authority is stored or returned. The exact `sharp@0.35.0` dependency has no finding in the 2026-08-01 production audits; the audits still report 4 existing root findings (1 high, 2 moderate, 1 low) and 10 existing Functions findings (9 moderate, 1 low). | This slice is not live and by itself supplies no officer search; #506 adds that separate source boundary. Every upload or retry still writes stable, domain-separated, SHA-256 account-key abuse-control bookkeeping, consumes quota, and depends on an unverified TTL; that pseudonym is linkable and is not anonymization. Profile/photo/audit state alone is read-only on an exact retry. Complete #110 privacy/retention/backup decisions, #133 protected short-lived authority, #507 isolated staging and backend-first deployment/readback, approved public notice wording, synthetic opt-out/removal/cache proof, website publication, and `runmprc.com` verification before use. Do not inspect real profiles or enable biometric processing. | | Source-only containment for RISK-008, RISK-017, RISK-022, RISK-032, and RISK-042 | MEMBERS-DIRECTORY-001B [#506](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/506) adds one separate officer people-finder page, one native-App-Check verified-admin callable, and one current-state projection reconciler with idempotent background retry. The browser submits only an explicit UUID/name-prefix command and accepts at most 24 exact display-name/optional-thumbnail projections. Direct Firestore access is denied. The server rejects Unicode controls/formats and invalid canonical bounds, stores only prefix digests under a proven 272-entry ceiling, permits 30 searches per verified-admin account/hour, queries at most 48 candidates, re-reads each current member/preference/photo record before returning it, and atomically records one minimal query-free audit. An opted-out, missing-name, malformed, stale, or now-nonmatching candidate is hidden. The existing full-account admin filter is visibly separate. | The projection's internal UID key, prefix digests, entry reference, rate key, and audit identity remain stable/linkable server-side pseudonyms, not anonymization. A response still contains personal data for its approved purpose. Exact request-ID reuse gets a fixed denial instead of retained-result replay and creates no second audit; the rate limiter may still consume an attempt. The current verified `admin` role is only a documented compatibility boundary, not a scoped people-finder capability. Source/tests/preview do not configure TTL, privacy notice, retention/backups, repair/backfill ownership, staging, Firebase Rules/Functions/indexes or trigger policy, Netlify, or production behavior. Complete #110, AUTH-003, #133, and #507 before use; never test against real profiles or add image queries, facial recognition, embeddings, similarity, analytics, or raw-query logs. | -| Frontend-only containment for RISK-042 | MEMBERS-DIRECTORY-001D [#621](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/621) adds one source-controlled availability boundary that defaults unavailable. The account page and `AdminGuard`-protected People finder render the future layout as visibly described, natively disabled controls. The default path does not mount the connected components, read a file or saved state, accept a name, create a request ID, initialize or call a directory service, or render sample or result cards. Focused tests preserve the connected source behind an explicit seam using only synthetic data. | This is an accidental-call and misleading-interface containment, not an authorization, privacy, retention, backend, provider, deployment, or live-behavior control. A later build must not turn availability on through configuration drift or an unreviewed environment value. #507 must complete approved notice and backup/removal wording, scoped authorization, protected authority, isolated staging, backend-first Rules/Functions/index deployment and readback, synthetic privacy/race proof, and then a separately reviewed source flip before connected website publication. Until then, no real name or photo may be entered, read, uploaded, searched, or saved. | +| Frontend-only containment for RISK-042 | MEMBERS-DIRECTORY-001D [#621](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/621) adds one source-controlled availability boundary that defaults unavailable. The account page and `AdminGuard`-protected People finder render the future layout as visibly described, natively disabled controls. The default path does not mount the connected components, read a file or saved state, accept a name, create a request ID, initialize or call a directory service, or render sample or result cards. Focused tests preserve the connected source behind an explicit seam using only synthetic data. #623 freezes the equivalent inert interface projection from the current live source into an exact 62-file artifact, but the one-shot publication remains under review. Protected-layout evidence stays synthetic; any later public check must stay signed out and can prove only the exact revision, normal sign-in/admin guards, and absence of a member-directory request. | This is an accidental-call and misleading-interface containment, not an authorization, privacy, retention, backend, provider, deployment, or live-behavior control. A later build must not turn availability on through configuration drift or an unreviewed environment value. #623 changes no Firebase, provider, account, sign-in, or production data. #507 must complete approved notice and backup/removal wording, scoped authorization, protected authority, isolated staging, backend-first Rules/Functions/index deployment and readback, synthetic privacy/race proof, and then a separately reviewed source flip before connected website publication. Until then, no real name or photo may be entered, read, uploaded, searched, or saved. | | Immediate Product-binding containment part of RISK-031 | PAY-PRODUCT-001A is tracked in live [#353](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/353). One dependency-free projection is used by the paid race and Shop checkout paths. A present stored Product link must be an own primitive non-empty string from 1 through 255 JavaScript code units and is copied without trimming, conversion, character restrictions, or `prod_` inference. Only a genuinely missing own field retains the compatibility Product-create path. Before any mapping write, a resolved Product must provide a bounded custom ID, exact Product kind, expected declared mode, and an installed-SDK 2xx response marker. Malformed stored links stop before token, registration/order identifier, Product-link or business-record write, or Stripe work; earlier access and request-count checks and their safety-counter writes may already have run. Malformed created results stop after at most one Product attempt but before mapping, Checkout Session, or business-record writes. | This structural containment does not prove provider origin, Stripe account ownership, intended catalog identity, metadata binding, Product status, price, dispatch, delivery, or reconciliation. A rejected create result may leave an orphaned Product; do not retry automatically. Anonymous clean-missing creation remains concurrency-prone and reachable from public traffic. Complete #113 inventory/disposition, authenticated idempotent catalog synchronization, Product-specific plan/pre-send/result/lost-acknowledgement/reconciliation controls, isolated staging, protected Firebase deployment, and provider proof before live commerce. | | Immediate current-handler containment part of RISK-003 | PAY-SESSION-001A is tracked in live [#357](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/357). The current paid race and Shop handlers build one immutable expectation before the Session call, catch rejection without opening it, and immediately project only a closed installed-SDK result. A result must match declared test/live mode, payment/open/unpaid state, exact cents, USD, buyer email, exact closed metadata, exact callbacks, a mode-compatible bounded Session ID, one canonical HTTPS capability at the hard-coded default `checkout.stripe.com` origin, and an installed-SDK 200 marker. Only copied ID/URL values continue; records store the ID but never the URL. Invalid results create no registration/order record and return one fixed unknown-result message. The website makes rejection or a missing paid URL terminal for that page visit, retains the form, and blocks a second direct handler call. | This is a narrow legacy compatibility stop, not trusted provider/account origin, deterministic business idempotency, dispatch/delivery proof, durable result evidence, payment proof, or adoption of the unused C4 chain. The Session call occurs first, so a rejected result may leave a payable orphan; earlier rate-counter, token/identifier, and lazy Product-mapping effects may remain. Reload, another tab/device, or a scripted caller bypasses the page lock. A configured Stripe custom checkout domain is blocked until #113 and a protected configuration boundary approve it. Complete PAY-002C/D persistence-first sagas, trusted C4 controller/result persistence, reconciliation, protected staging/deployment, provider readback, and exact website/Firebase/live proof. | | Immediate pre-render browser containment part of RISK-003 | PAY-SESSION-001B is tracked in live [#503](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/503). Each current public race and Shop page now keeps one component-local in-memory marker. After existing local checks admit a request, the handler sets that marker synchronously before analytics or its first Checkout promise can settle. A same-action or later submission on that mounted page is inert, including the gap before React renders the existing pending disabled button. Focused actual-route tests prove one service/analytics attempt across same-action and post-render repeats; preserve free-participant, volunteer-without-price-tier, and both paid-link navigation branches; and prove local waiver/native-disabled paths do not consume an attempt. | This browser marker is not a server boundary, durable idempotency key, provider dispatch/result record, business-state lock, payment proof, or reconciliation. It never releases during the mounted page visit because the admitted result must navigate or enter #357's terminal unknown-result state. Existing form controls other than the submit button remain editable. Reload, a remount, another tab/device, a script, or a direct service caller can bypass it. A same-mounted route change instead stays locked, and this slice does not fence an older pending result from that changed route. Complete PAY-002C/D persistence-first deterministic commands, durable replay/reconciliation, protected deployment, and exact website/Firebase/Stripe/live proof. | diff --git a/SYSTEM_DESIGN.md b/SYSTEM_DESIGN.md index 4933c27c..6add0c01 100644 --- a/SYSTEM_DESIGN.md +++ b/SYSTEM_DESIGN.md @@ -86,12 +86,14 @@ Text alternative: browsers can use the React app, Auth, Firestore, Functions, St | Operational data | Cloud Firestore | `src/services`, `firestore.rules`, `firestore.indexes.json` | Appropriate for current scale; counters and state transitions require transactional design. | | Server API | First-generation Firebase callable/HTTP/trigger functions | `functions/` | Prototype covers most workflows; validation, idempotency, and isolation are incomplete. | | Payments | Stripe Checkout Sessions, Payment Links, refunds, signed webhook | `functions/createCheckoutSession.js`, `createMerchCheckout.js`, `stripeWebhook.js` | Not ready for live payments until P0 issues are complete. | -| Hosting and release | Netlify currently answers `runmprc.com`. GitHub Pages still reports the same custom domain, so its default URL redirects to the Netlify-served name instead of providing an independent copy. #135 source stops ordinary automatic releases, pauses Git-triggered Netlify production builds, and removes the Pages CNAME from future protected artifacts. After an overbroad WEB-002A artifact was rolled back, [#473](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/473) used one bounded replacement exception: exact merge `9ad6837` published source `39ab864`, tree `d76b496`, 60 files, and artifact digest `07b10c7d…` as deploy `6a6dc9ea588b0c0008036312`. Every mismatch failed closed, and the manifest is inactive again. | `netlify.toml`, `config/netlify-production-release.json`, `scripts/netlify-release-policy.js`, `.github/workflows/deploy.yml`, `public/404.html` | **LIVE FRONTEND CONTAINMENT.** Shop is the static pickup catalog; Events and Calendar disclose no raw provider error. Event records remain unavailable because no Firebase or provider repair was deployed. This does not complete the general protected host under #133/#136/WEB-001. | +| Hosting and release | Netlify currently answers `runmprc.com`. GitHub Pages still reports the same custom domain, so its default URL redirects to the Netlify-served name instead of providing an independent copy. #135 source stops ordinary automatic releases, pauses Git-triggered Netlify production builds, and removes the Pages CNAME from future protected artifacts. After an overbroad WEB-002A artifact was rolled back, [#473](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/473) used one bounded replacement exception: exact merge `9ad6837` published source `39ab864`, tree `d76b496`, 60 files, and artifact digest `07b10c7d…` as deploy `6a6dc9ea588b0c0008036312`. Every mismatch failed closed, and the manifest became inactive again. WEB-002C [#623](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/623) is an active one-shot under review for frozen inert source `c2d87d1`, tree `411aa6ec`, 62 files, and digest `d837272a…`, with exact expected first parent `0193533` and the #473 deploy as rollback. | `netlify.toml`, `config/netlify-production-release.json`, `scripts/netlify-release-policy.js`, `.github/workflows/deploy.yml`, `public/404.html` | **LIVE FRONTEND CONTAINMENT.** Until every #623 preview, merge, readback, and repause gate passes, production remains #473 deploy `6a6dc9ea588b0c0008036312` from source `39ab864`. Shop is the static pickup catalog; Events and Calendar disclose no raw provider error. Event records remain unavailable because no Firebase or provider repair was deployed. This does not complete the general protected host under #133/#136/WEB-001. | | Email | Firestore `mail` outbox designed for the Firebase Trigger Email extension | `functions/sendConfirmationEmail.js` | Extension/provider deployment is unverified; outbox creation is not transactionally idempotent and HTML needs escaping. | | Observability | Optional Sentry; Firebase Analytics configuration remains but its runtime is not initialized by #139 source | `src/services/monitoring`, `src/services/analytics` | #134 source bounds Sentry payloads. #139 source removes every application runtime Firebase Analytics import, initialization, and emission while preserving no-op call compatibility. Website publication, provider collection/cookies and historical data, consent, retention, access, deletion, and vendor configuration remain unverified under #110/#111. | | Third-party fitness | Strava OAuth tokens and statistics | `functions/strava.js`, `src/services/strava` | Functional prototype. The #100 source Rules deny browser token access, but Firebase deployment is unproven and transactional refresh, scopes/revocation, IAM/encryption decision, and audit remain OAUTH-001. | -The former workflow automatically published Pages before attempting Firebase and could finish green after skipping Firebase. #135 replaces that source path with a manual exact-current-commit request, exact latest CI checks, one fixed backend plan, protected short-lived identity wiring, provider readback, and Firebase-before-Pages publication. Missing authority or failed/partial verification is red. Ordinary Git-triggered Netlify production builds stop. Replacement #473 continued only for one production `main` merge whose first parent was exact `dee79511b6e371329aa129139729e112e7a51aad`, then independently fetched and verified source commit `39ab8649df411262c8109a3c81a57bc38f1e168b` and tree `d76b496cdc5e79015bc048cb961758abbe88b9ce` before matching the fixed 60-file digest. Review also compared executable code against live artifact source `ed1b0833`; provenance alone did not authorize accumulated source. The now-inactive manifest, a later merge, wrong branch/context/ref/tree/artifact, build-hook metadata, or any uncertainty stops. The pinned build received no React/provider or server credential environment, and the preview and production artifacts matched. Build hooks and a reusable protected Netlify publication path remain unverified. No source test clears the current Pages custom-domain claim, configures #133, or deploys #136; those remain separate provider states. The App Engine synchronization script is another surface that must be documented as active or retired. +The former workflow automatically published Pages before attempting Firebase and could finish green after skipping Firebase. #135 replaces that source path with a manual exact-current-commit request, exact latest CI checks, one fixed backend plan, protected short-lived identity wiring, provider readback, and Firebase-before-Pages publication. Missing authority or failed/partial verification is red. Ordinary Git-triggered Netlify production builds stop. Replacement #473 continued only for one production `main` merge whose first parent was exact `dee79511b6e371329aa129139729e112e7a51aad`, then independently fetched and verified source commit `39ab8649df411262c8109a3c81a57bc38f1e168b` and tree `d76b496cdc5e79015bc048cb961758abbe88b9ce` before matching the fixed 60-file digest. Review also compared executable code against live artifact source `ed1b0833`; provenance alone did not authorize accumulated source. Its preview and production artifacts matched, and the manifest was then made inactive. + +#623 now proposes the same fail-closed pattern for one inert member-directory interface artifact. The reviewed input must remain exact source `c2d87d1f69f15e128a0bc9b1b9f915b7c8417aec`, tree `411aa6ec9a9459f5d923030533ffc7c007fe6908`, 62 files, digest `d837272a1e5efc1575809e87f532276b38d1a63f1dd79ec1aef0533f6da8afb1`, and first parent `019353361210021483f23003e09ee6924b78e67c`. Current production remains #473 deploy `6a6dc9ea588b0c0008036312` from source `39ab8649df411262c8109a3c81a57bc38f1e168b`, which is also the rollback target. A later merge, wrong branch/context/ref/tree/artifact, mismatched marker, missing signed-out public proof, or any uncertainty stops. If publication succeeds, the manifest must be re-paused immediately and its production attempt plus the retained live deploy read back separately. The release changes no Firebase, outside-provider configuration, sign-in state, or production data. Build hooks and a reusable protected Netlify publication path remain unverified. No source test clears the current Pages custom-domain claim, configures #133, or deploys #136; those remain separate provider states. The App Engine synchronization script is another surface that must be documented as active or retired. ```mermaid flowchart TD @@ -107,13 +109,16 @@ flowchart TD Verify -- "No" --> Stop Verify -- "Yes" --> Pages["Publish Pages branch without a CNAME"] Merge -. "Ordinary Git production build paused" .-> Netlify["Netlify / runmprc.com\nreusable protected publication not available"] - Merge -. "Completed #473 exact release; manifest inactive" .-> WebGate{"Temporary authority active?"} + Merge -. "#623 only: exact-parent one-shot under review" .-> WebGate{"Temporary authority and exact artifact valid?"} WebGate -- "No" --> Stop WebGate -- "Yes" --> WebOnly["Publish pinned web-only artifact"] WebOnly --> Netlify + Netlify --> WebReadback{"Exact marker and signed-out checks pass?"} + WebReadback -- "No" --> WebRollback["Restore recorded prior deploy"] + WebReadback -- "Yes" --> WebRepause["Disable temporary authority and verify no replacement"] ``` -Text alternative: ordinary merges run CI and do not publish Netlify; the completed #473 exception is inactive, while the separate protected workflow still requires approval and verified Firebase before publishing its Pages copy. +Text alternative: ordinary merges run CI and do not publish Netlify. Production remains the completed #473 artifact unless the one exact #623 parent, frozen source, artifact, and marker all pass. #623 then requires signed-out public readback; failure restores the recorded prior deploy, while success immediately disables the temporary authority and verifies that the production artifact was not replaced. The separate protected workflow still requires approval and verified Firebase before publishing its Pages copy. ### GitHub Pages callback handoff @@ -683,6 +688,8 @@ The preserved connected branch sends nothing while the person types. Search happ The backend source still has no production index, Rules, Function, privacy-notice, retention/backup approval, or live proof. Synthetic local artifacts and tests prove that the protected #621 layouts are disabled and their default branches make zero directory calls. Publishing and anonymously reading back the exact frontend may prove only its revision, normal sign-in and administrator guards, and absence of a public member-directory request. [#507](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/507) must complete the #110 policy decision, scoped authorization, #133 protected short-lived authority, any required index and backend-first deployment/readback (including the trigger retry policy), isolated synthetic staging checks, and backup-officer procedure before a separately reviewed source change turns availability on. Only then may the connected website be published and verified on `runmprc.com`. #507 must also own an idempotent, dry-runnable projection repair/backfill for any later schema/normalization upgrade, or prove no older preference data exists for the initial target. A green source build, disabled preview, merge, or frontend publication is not authorization to search a real name or inspect a real photo. +WEB-002C [#623](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/623) freezes only that inert interface into source `c2d87d1f69f15e128a0bc9b1b9f915b7c8417aec`, tree `411aa6ec9a9459f5d923030533ffc7c007fe6908`, and a 62-file artifact with digest `d837272a1e5efc1575809e87f532276b38d1a63f1dd79ec1aef0533f6da8afb1`. Its one-shot Netlify release is under review, not published. Production still serves #473 deploy `6a6dc9ea588b0c0008036312` from source `39ab8649df411262c8109a3c81a57bc38f1e168b`. Protected-layout proof remains synthetic. Any later public proof must stay signed out and is limited to the exact revision, normal sign-in and administrator guards, and absence of a member-directory request. #623 has no Firebase, provider, account, sign-in, or production-data authority, and #507 remains **NOT AVAILABLE YET**. + ### 8.0a Provider-neutral membership authority and entitlement — SOURCE ONLY, UNUSED MEMBERS-IDENTITY-001A [#208](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/208), with its command-order correction in MEMBERS-IDENTITY-001H [#451](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/451), defines one unused pure contract that keeps a stable MPRC membership separate from the Firebase account used to sign in. A membership record can exist and receive monotonic term decisions without a UID. Such a record grants no website entitlement. Google, WhatsApp, Strava, email equality, a profile role, and any browser field remain projections or inputs to future reviewed workflows; none is membership authority. diff --git a/config/netlify-production-release.json b/config/netlify-production-release.json index f4cf80a8..3c65d8eb 100644 --- a/config/netlify-production-release.json +++ b/config/netlify-production-release.json @@ -1,16 +1,16 @@ { "schemaVersion": 1, - "active": false, - "releaseId": "WEB-002A-CONTAINMENT-2026-08-01", - "issueNumber": 473, - "previewBranch": "codex/issue-473-permissions-containment-release", - "expectedProductionParent": "dee79511b6e371329aa129139729e112e7a51aad", + "active": true, + "releaseId": "WEB-002C-MEMBER-DIRECTORY-PREVIEW-2026-08-13", + "issueNumber": 623, + "previewBranch": "codex/issue-623-netlify-release", + "expectedProductionParent": "019353361210021483f23003e09ee6924b78e67c", "sourceRepository": "https://github.com/Run-MPRC/Run-MPRC.github.io.git", - "sourceRef": "refs/heads/codex/netlify-source-473-permissions-containment-v2", - "sourceCommit": "39ab8649df411262c8109a3c81a57bc38f1e168b", - "sourceTree": "d76b496cdc5e79015bc048cb961758abbe88b9ce", - "previousSourceCommit": "ed1b0833f25822cee80c99ded8753722b5608a3f", - "rollbackDeployId": "6a6dc219a8136300081811db", - "expectedSiteFileCount": 60, - "expectedSiteFilesSha256": "07b10c7d5ff176a1ad893d7549b07042312df35f4a4126e8765824ca94eaefb8" + "sourceRef": "refs/heads/codex/netlify-source-623-member-directory-preview", + "sourceCommit": "c2d87d1f69f15e128a0bc9b1b9f915b7c8417aec", + "sourceTree": "411aa6ec9a9459f5d923030533ffc7c007fe6908", + "previousSourceCommit": "39ab8649df411262c8109a3c81a57bc38f1e168b", + "rollbackDeployId": "6a6dc9ea588b0c0008036312", + "expectedSiteFileCount": 62, + "expectedSiteFilesSha256": "d837272a1e5efc1575809e87f532276b38d1a63f1dd79ec1aef0533f6da8afb1" } diff --git a/docs/officers/EVENTS_SHOP_MEMBERS.md b/docs/officers/EVENTS_SHOP_MEMBERS.md index 9833fd61..1b5a3d39 100644 --- a/docs/officers/EVENTS_SHOP_MEMBERS.md +++ b/docs/officers/EVENTS_SHOP_MEMBERS.md @@ -2416,30 +2416,34 @@ Officer review steps after every prerequisite has proof: ## Optional profile photo and officer people finder — INERT FRONTEND PREVIEW; BACKEND NOT LIVE -**Purpose:** let officers review the future profile-photo, independent finder-choice, and People finder layouts without connecting the private backend. The current #621 frontend default is an inert preview: every related control is disabled; it reads no saved photo or setting, accepts or uploads no photo, searches no name, and saves nothing. After a later approved connection, a signed-in person could choose one private profile thumbnail and, separately, choose whether the People finder may show their display name and thumbnail. A properly authorized officer could search by name and compare voluntary thumbnails visually. The system will not search a photo or recognize a face. +**Purpose:** let officers review the future profile-photo, independent finder-choice, and People finder layouts without connecting the private backend. The #621 frontend source default is an inert preview: every related control is disabled; it reads no saved photo or setting, accepts or uploads no photo, searches no name, and saves nothing. #623 freezes only that inert interface over the current live source for one bounded Netlify release. That release is under review and not published. Production still serves #473 deploy `6a6dc9ea588b0c0008036312` from source `39ab8649df411262c8109a3c81a57bc38f1e168b`. After a later approved connection, a signed-in person could choose one private profile thumbnail and, separately, choose whether the People finder may show their display name and thumbnail. A properly authorized officer could search by name and compare voluntary thumbnails visually. The system will not search a photo or recognize a face. **Approver:** membership lead, privacy owner, and platform/security owner. The privacy owner must approve the final notice and backup/removal wording before connected publication. -**Prerequisites:** parent [#504](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/504), account source child [#505](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/505), search source child [#506](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/506), and frontend-preview child [#621](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/621) are reviewed. Review the protected disabled layouts only through synthetic local artifacts. After the exact #621 frontend revision is published and read back, inspect only its signed-out public guards. Use only made-up accounts, made-up names, and generated non-face images for the preserved connected-source tests. Protected release [#507](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/507), the approved #110 privacy entry, scoped authorization, #133 protected release authority, and isolated staging are still required before any backend connection or live use. +**Prerequisites:** parent [#504](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/504), account source child [#505](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/505), search source child [#506](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/506), and frontend-preview child [#621](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/621) are reviewed. #623 must have its separately reviewed exact release record before publication. Review the protected disabled layouts only through synthetic local artifacts. If the exact #623 frontend artifact later publishes and is read back, inspect only its signed-out public revision and guards. Use only made-up accounts, made-up names, and generated non-face images for the preserved connected-source tests. Protected release [#507](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/507), the approved #110 privacy entry, scoped authorization, #133 protected release authority, and isolated staging are still required before any backend connection or live use. ```mermaid flowchart TD - Person["Signed-in person's My Account preview"] --> Account["Photo and separate finder-choice controls disabled"] - Guard["Existing administrator guard"] --> Officer["People finder preview"] + Synthetic["Synthetic local protected-layout proof"] --> Person["Signed-in person's My Account preview"] + Synthetic --> Guard["Existing administrator guard"] + Person --> Account["Photo and separate finder-choice controls disabled"] + Guard --> Officer["People finder preview"] Officer --> Search["Name field and Search button disabled"] Account --> None["No directory name or photo read, upload, remove, search, or save"] Search --> None + Release["#623 exact inert artifact under review"] --> Public["Future signed-out revision and guard readback only"] + Public -. "does not prove protected layout" .-> Synthetic Later["Later #507 reviewed source flip"] --> Gates["Privacy, scoped authorization, staging, and backend-first readback"] Gates --> Connected["Future optional name search with voluntary thumbnails"] Connected -. "never photo search or proof" .-> Official["Membership, role, payment, or official records"] ``` -Text alternative: the current signed-in Account preview and administrator-guarded People finder show their future layouts with every directory control disabled. Their directory branches read, accept, search, and save nothing; the administrator guard still uses the normal account and role context. Only a later #507 source flip after privacy, authorization, staging, and backend-first readback could connect the optional name search and voluntary thumbnails. It will not search a photo, and a result will not prove or change an official record. +Text alternative: synthetic local artifacts prove the signed-in Account preview and administrator-guarded People finder with every directory control disabled. The #623 artifact is under review; if published, signed-out public checks prove only its exact revision, normal guards, and absence of a directory request, not the protected layouts. Only a later #507 source flip after privacy, authorization, staging, and backend-first readback could connect the optional name search and voluntary thumbnails. It will not search a photo, and a result will not prove or change an official record. Officer review steps for the #621 frontend preview: 1. Keep the profile photo and People finder backend marked **NOT AVAILABLE YET**. -2. Ask the platform owner for the exact #621 pull request, commit, and frontend test record. +2. Ask the platform owner for the exact #621 source pull request, commit, and frontend test record. 3. Confirm the source-controlled availability boundary defaults unavailable. 4. Confirm the default My Account path mounts no connected profile component. 5. Ask the platform owner for dated synthetic local screenshots of both protected layouts at desktop and 320-pixel widths. @@ -2460,13 +2464,17 @@ Officer review steps for the #621 frontend preview: 20. Confirm the copy says a future result will not prove membership. 21. Confirm the copy says there is no photo query or facial recognition. 22. Confirm synthetic tests still cover the preserved connected branches through an explicit test seam. -23. After publication, ask the platform owner for the exact frontend hosting record. -24. Ask the platform owner for the matching public revision readback. -25. While signed out, open `/account` and confirm the normal sign-in boundary remains. -26. While signed out, open `/admin/member-directory` and confirm the administrator boundary remains. -27. Ask the platform owner for the anonymous public network record. -28. Confirm that record contains no member-directory callable request. -29. Stop. Do not sign in to production, choose a file, enter a name, call Firebase directly, or use production member data. +23. Ask whether #623 is still under review or has exact publication proof. +24. If it remains under review, record **not published** and stop the public checks. +25. If it published, ask the platform owner for release ID `WEB-002C-MEMBER-DIRECTORY-PREVIEW-2026-08-13`, source `c2d87d1f69f15e128a0bc9b1b9f915b7c8417aec`, tree `411aa6ec9a9459f5d923030533ffc7c007fe6908`, 62 files, digest `d837272a1e5efc1575809e87f532276b38d1a63f1dd79ec1aef0533f6da8afb1`, and the exact public marker. +26. Confirm the release record says no Firebase, provider, account, sign-in, or production-data action occurred. +27. Ask the platform owner for the matching signed-out public revision readback. +28. While signed out, open `/account` and confirm the normal sign-in boundary remains. +29. While signed out, open `/admin/member-directory` and confirm the administrator boundary remains. +30. Ask the platform owner for the anonymous public network record. +31. Confirm that record contains no member-directory callable request. +32. Confirm the manifest was re-paused and its attempt did not replace the verified deploy. +33. Stop. Do not sign in to production, choose a file, enter a name, call Firebase directly, or use production member data. Officer review steps for the preserved #505 connected source only: @@ -2525,11 +2533,11 @@ Officer review steps for the preserved #506 connected source only: 39. Confirm loading, no-result, fixed-error, and missing-photo states remain readable. 40. Stop. Do not enable connected production Account or Admin controls, search a real name, inspect a real profile, or edit Firebase. -**Expected result:** the frontend defaults to a visibly disabled preview that makes zero directory calls, accepts no file or name, and shows no person. Separate synthetic tests still prove the preserved default-off account controls and bounded officer name search whose future results would be rechecked against current private state. The backend and connected behavior remain unavailable. There is no public directory, official roster, public photo URL, Firebase Storage object, photo-as-query path, face recognition, similarity score, embedding, biometric template, export, result total, or pagination. +**Expected result:** the frontend artifact defaults to a visibly disabled preview that makes zero directory calls, accepts no file or name, and shows no person. Before exact publication proof, production remains the #473 deploy. Separate synthetic tests prove the protected disabled layouts and preserved connected source. Any later signed-out public readback proves only the exact revision, normal guards, and absence of a member-directory request. The backend and connected behavior remain unavailable. There is no public directory, official roster, public photo URL, Firebase Storage object, photo-as-query path, face recognition, similarity score, embedding, biometric template, export, result total, or pagination. **Stop conditions:** an enabled preview control; a preview that reads saved directory state, accepts a file or finder name, creates a directory request number, calls a directory service, or shows a sample or result card; a real person, name, photo, member record, production sign-in, direct production Firebase access, production data change, or member-directory callable request; a public/permanent photo URL; an upload that silently opts in; a result from a search transaction ordered after completed opt-out; a notice that fails to explain that an earlier-committed response may still arrive; raw image, name, query, result identity, or provider detail in a log, issue, screenshot, message, email, or AI tool; a request for photo search, face recognition, similarity matching, or biometric processing; missing privacy approval for connected publication; an unreviewed availability flip; or a claim that source, tests, merge, frontend publication, or preview means the backend feature is live. -**Success proof:** record the exact #505, #506, and #621 pull requests and merge commits; green focused and full checks; dependency review; independent privacy/security and backup-officer reviews; and explicit statements that Firebase, providers, and production data were unchanged. Record the exact frontend publication and `runmprc.com` readback if they occurred, or the exact external blocker if they did not. Synthetic artifacts and tests prove only the disabled protected layouts and default zero-directory-call branches. Publication and anonymous readback prove only the exact revision, signed-out guards, and absence of a public member-directory request. Record separately that the privacy notice, backend deployment, connected behavior, and live directory were not performed. Final connected live proof belongs to #507. +**Success proof:** record the exact #505, #506, and #621 pull requests and merge commits; green focused and full checks; dependency review; independent privacy/security and backup-officer reviews; and explicit statements that Firebase, providers, accounts/sign-in, and production data were unchanged. While #623 is under review, record the exact source, tree, count, digest, expected parent, and current #473 production/rollback deploy, then state **not published**. If #623 later publishes, record the exact release merge, deploy, public marker, signed-out guards, anonymous no-directory-request result, repause attempt, and retained-deploy readback. Synthetic artifacts and tests prove only the disabled protected layouts and default zero-directory-call branches. Publication and anonymous readback prove only the exact revision, signed-out guards, and absence of a public member-directory request. Record separately that the privacy notice, backend deployment, connected behavior, and live directory were not performed. Final connected live proof belongs to #507. **Undo:** before or after frontend-preview publication, use one reviewed frontend revert or safe roll-forward and read back the exact disabled state. The #621 preview changes no Firebase record to undo. After a future approved backend release, use the documented backend-first release path and verify opt-out/removal with a made-up account. Never undo by deleting or editing a real account or Firebase record manually. diff --git a/docs/officers/PUBLISH_AND_CHECK.md b/docs/officers/PUBLISH_AND_CHECK.md index fb8e5913..fdef2dd6 100644 --- a/docs/officers/PUBLISH_AND_CHECK.md +++ b/docs/officers/PUBLISH_AND_CHECK.md @@ -10,7 +10,7 @@ **Protected release status:** **NOT AVAILABLE YET.** Issue #135 provides the fail-closed source gate. Issue #133 must still configure protected `staging` and `production` environments, their named reviewers, and a short-lived cloud identity. Public browser build values must be named repository or organization variables because artifact preparation has no protected-environment access; #133/#136 must record and verify them separately. Do not add a long-lived Firebase key as a shortcut. -**Live Netlify publication status:** a reusable protected release is **NOT AVAILABLE YET**. Ordinary Git-triggered production builds are paused by repository configuration. An overbroad #473 artifact was published and then rolled back on 2026-08-01. Bounded replacement deploy `6a6dc9ea588b0c0008036312`, exact source `39ab8649df411262c8109a3c81a57bc38f1e168b`, is production now. Shop is the static catalog; Events and Calendar show a fixed retry-later notice instead of a raw provider error. Event records remain unavailable because this did not deploy Firebase. The temporary manifest is inactive again. GitHub Pages currently still claims the same custom domain; future source omits that claim, but #136/WEB-001 must publish and verify its removal. +**Live Netlify publication status:** a reusable protected release is **NOT AVAILABLE YET**. Ordinary Git-triggered production builds are paused by repository configuration. An overbroad #473 artifact was published and then rolled back on 2026-08-01. Bounded replacement deploy `6a6dc9ea588b0c0008036312`, exact source `39ab8649df411262c8109a3c81a57bc38f1e168b`, is production now. Shop is the static catalog; Events and Calendar show a fixed retry-later notice instead of a raw provider error. Event records remain unavailable because this did not deploy Firebase. #623 is a separate one-shot release under review for one frozen inert member-directory interface artifact. It is not published or verified, and the #473 deploy is its rollback target. GitHub Pages currently still claims the same custom domain; future source omits that claim, but #136/WEB-001 must publish and verify its removal. ## The release gate @@ -57,7 +57,7 @@ As of **2026-07-13**, with the internal tooling note below checked from source o - `runmprc.com` is served by Netlify, not GitHub Pages. - GitHub Pages currently reports `runmprc.com` as its custom domain and redirects its normal address there. It is not an independently reachable copy today. - Future source stops writing that Pages domain claim. Only provider readback after #136/WEB-001 can prove it cleared. -- Ordinary Git-triggered Netlify production builds are paused. The completed #473 exception used one exact two-parent merge and pinned source/tree/artifact; its manifest is inactive and its release source is retired. +- Ordinary Git-triggered Netlify production builds are paused. The completed #473 exception used one exact two-parent merge and pinned source/tree/artifact; its release source is retired. #623 temporarily arms a new exact-parent one-shot under review. It must publish only its frozen artifact, pass signed-out readback, and be re-paused immediately. - Live race signup, merchandise payments, and refunds remain unavailable. - CONFIG-001B1 [#151](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/151) adds source enforcement for a server-only commerce pause. It is not in the fixed profile-recovery release plan, is not deployed, and has no approved officer control. A future reviewed plan must deploy the complete guarded Function set with the deploy ceiling and every runtime/resource flag off, then prove signed webhooks still work. Do not widen the current plan by hand. @@ -149,7 +149,7 @@ If a member or officer sees **Server configuration is unavailable**: 9. Confirm `Test artifact scrubber` is green. It proves the deliberate made-up output scan ran. It does not prove a report was uploaded or a live service changed. 10. Confirm the Functions and Firestore Rules jobs are green. 11. Use a preview only for public, read-only pages. -12. Do not sign in, open private/admin pages, submit forms, or test signup, checkout, refund, email, or Strava in a preview. +12. Do not sign in, pass a private or administrator guard, submit forms, or test signup, checkout, refund, email, or Strava in a preview. A separately reviewed exact-artifact procedure may check only the signed-out guard. 13. Confirm the officer guide and undo note are present. 14. Approve or reject the merge. Do not describe merge approval as release approval. @@ -163,6 +163,74 @@ If a member or officer sees **Server configuration is unavailable**: 6. Do not expect GitHub Pages, Firebase, Netlify, or `runmprc.com` to change from a merge unless a separate exact temporary release is explicitly armed and reviewed. 7. For any other merge, if Netlify unexpectedly publishes, stop and treat it as a hosting incident. +## Temporary #623 inert member-directory interface release — UNDER REVIEW; NOT PUBLISHED + +**Purpose:** publish only the visibly disabled My Account profile-photo/finder-choice interface and the administrator-guarded People finder layout. This is an interface preview. It does not connect the private backend, read or save a setting, accept or upload a photo, search a name, or show a person. + +**Approver:** Dave Liu as platform owner, plus the privacy/security reviewer. This is not an officer-operated control. + +**Prerequisites:** approved issue [#623](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/623) with no newer blocker; exact release ID `WEB-002C-MEMBER-DIRECTORY-PREVIEW-2026-08-13`; green exact-head checks; successful pinned Deploy Preview; source commit `c2d87d1f69f15e128a0bc9b1b9f915b7c8417aec`; source tree `411aa6ec9a9459f5d923030533ffc7c007fe6908`; 62-file digest `d837272a1e5efc1575809e87f532276b38d1a63f1dd79ec1aef0533f6da8afb1`; exact first parent `019353361210021483f23003e09ee6924b78e67c`; current production and rollback deploy `6a6dc9ea588b0c0008036312`; current live source `39ab8649df411262c8109a3c81a57bc38f1e168b`; release source `codex/netlify-source-623-member-directory-preview`; rollback source `codex/netlify-source-623-rollback`; an executable diff proving only the inert interface was added to the live source; synthetic protected-layout proof; a prepared manifest-disable change; a named signed-out public observer; and no other `main` merge until verification and repause finish. + +```mermaid +flowchart TD + Synthetic["Synthetic local protected-layout proof"] --> Preview["Pinned #623 Deploy Preview"] + Preview --> Match{"Marker, source, tree, count, and digest match?"} + Match -- "No" --> Keep["Stop — keep #473 deploy live"] + Match -- "Yes" --> Merge["Exact-parent merge commit"] + Merge --> Public["Signed-out public revision and guard checks"] + Public --> Good{"Exact artifact and no directory request?"} + Good -- "No" --> Rollback["Restore #473 deploy"] + Good -- "Yes" --> Repause["Disable manifest and verify no replacement"] + Repause --> Retire["Retire release source and record proof"] +``` + +In words: synthetic local artifacts prove the protected disabled layouts. The release preview must match the exact frozen artifact. Only the exact-parent merge may publish it. Signed-out public checks then prove only the revision, normal guards, and absence of a directory request. A mismatch leaves or restores the current #473 deploy; a success is immediately re-paused. + +1. Confirm the release pull request targets `main`. +2. Confirm its exact head and every required check are current and green. +3. Confirm the frozen source commit, tree, file count, and digest match the prerequisites. +4. Confirm the executable delta from live source `39ab8649df411262c8109a3c81a57bc38f1e168b` contains only the inert Account and People finder interface. +5. Confirm synthetic local evidence shows both protected layouts at desktop and 320-pixel widths. +6. Confirm that evidence uses only a made-up account and contains no real name or photo. +7. Confirm every related file, checkbox, name, and Search control is disabled. +8. Confirm no preview branch reads saved directory state, creates a request, calls a directory service, or shows a person. +9. Open the Deploy Preview's `/.well-known/run-mprc-release.json` marker. +10. Confirm the marker's control commit is the exact pull-request head. +11. Confirm the marker's source, tree, previous source, rollback deploy, count, and digest match the prerequisites. +12. Confirm the preview marker uses HTTPS, JSON content type, `Cache-Control: no-store`, `X-Content-Type-Options: nosniff`, and HSTS. +13. Confirm the preview has `X-Robots-Tag: noindex`. +14. Stay signed out and open `/account`. +15. Confirm the normal sign-in boundary remains. +16. Stay signed out and open `/admin/member-directory`. +17. Confirm the normal administrator boundary remains. +18. Confirm the anonymous preview network record contains no member-directory callable request. +19. Confirm the prepared manifest-disable change and rollback ref are ready. +20. Re-read the issue and pull-request comments. +21. Stop for any unresolved blocker posted after the last review. +22. Confirm `main` is still exact commit `019353361210021483f23003e09ee6924b78e67c`. +23. Merge with a merge commit. Do not squash or rebase. +24. Confirm the production attempt uses that exact two-parent merge. +25. Read the live public marker. +26. Confirm its control commit is the exact release merge. +27. Confirm its stable source, tree, previous source, rollback deploy, count, and digest equal the preview. +28. Stay signed out and repeat the `/account` and `/admin/member-directory` guard checks on `runmprc.com` at phone and computer widths. +29. Confirm the anonymous production network record contains no member-directory callable request. +30. Record the deploy, release merge, source, tree, count, digest, marker, guards, widths, network result, and check time. +31. Merge the prepared manifest-disable change immediately. +32. Confirm its Netlify attempt does not replace the verified deploy. +33. Read the public marker again and confirm the verified deploy remains live. +34. Retire the #623 release source and verify it is absent. + +**Expected result:** if every gate passes, Netlify serves exactly the frozen inert interface. My Account remains behind sign-in, the People finder remains behind the administrator guard, and no member-directory request is available to a signed-out observer. Protected layouts remain synthetic proof only. The release does not deploy Firebase, Rules, Functions, or indexes; configure a provider; change sign-in, accounts, or production data; or make the backend available. Connected behavior remains **NOT AVAILABLE YET** under #507. + +**Stop conditions:** stop if `main` advances; a branch, context, source, tree, count, digest, parent, marker, page, guard, network record, or repause result differs; the preview is missing; rollback is unavailable; a protected control is enabled; a public check asks for sign-in or private data; a real name or photo appears; a member-directory request is made; another production attempt starts; or any blocker remains open. + +**Success proof:** keep the public deploy and marker links, exact hashes, check time, phone/computer widths, signed-out guard results, anonymous no-directory-request record, repause attempt, retained-deploy readback, and one redacted public screenshot if it contains no private content. Record Firebase, provider configuration, account/sign-in changes, and production-data changes as not performed. Do not call synthetic protected-layout proof public production proof. + +**Undo:** before publication, leave deploy `6a6dc9ea588b0c0008036312` live. If the wrong result publishes, ask the Netlify team owner to restore that same deploy. If provider restore is unavailable, use only the reviewed exact-parent rollback pinned to source `39ab8649df411262c8109a3c81a57bc38f1e168b`, then rerun its exact preview and checks. Disabling the manifest alone does not roll back an already published deploy. + +**Escalation:** platform owner first; privacy/security owner second; membership lead if the layout or promise is wrong. Use the private incident path if a real name, photo, account, or directory request appears. + ## Temporary #473 permissions-containment release — COMPLETED 2026-08-01 **Completed status:** merge `9ad6837756cdd409d296009fde5082eeeae5c059` published Netlify deploy `6a6dc9ea588b0c0008036312`. The public marker matched source `39ab8649df411262c8109a3c81a57bc38f1e168b`, tree `d76b496cdc5e79015bc048cb961758abbe88b9ce`, 60 files, and digest `07b10c7d5ff176a1ad893d7549b07042312df35f4a4126e8765824ca94eaefb8`. Preview and live route/header/robots/browser checks passed. Shop, Events, Calendar, and signed-out Account were readable; no raw provider error appeared. Final control merge `cb6a8f0a418fc14b448bce5ded71d68520415c92` made the temporary manifest inactive; Netlify attempt `6a6dcdd47bc81e000859a249` stopped unpublished, exact-main CI run `30696264830` passed all five jobs, and readback left the same bounded deploy and marker live. Both temporary release-source refs are absent; exact rollback source `ed1b0833f25822cee80c99ded8753722b5608a3f` remains. Event records remain unavailable because Firebase was unchanged. @@ -308,7 +376,7 @@ Do not use this section until #133 records that both GitHub environments are pro ## Verify every affected surface 1. Record whether the GitHub Pages branch published and whether provider readback shows its old `runmprc.com` claim is gone. -2. Ask the Netlify owner which commit, if any, Netlify published. For #457, also read the public `/.well-known/run-mprc-release.json` marker and verify its source commit/tree. +2. Ask the Netlify owner which commit, if any, Netlify published. For any reviewed exact-artifact Netlify exception, also read the public `/.well-known/run-mprc-release.json` marker and verify its source commit and tree. 3. Open [runmprc.com](https://runmprc.com) in a private window. 4. Visit the exact changed public page. 5. Check one phone-sized view. diff --git a/docs/officers/README.md b/docs/officers/README.md index c52d3f9f..e7f3dece 100644 --- a/docs/officers/README.md +++ b/docs/officers/README.md @@ -63,13 +63,13 @@ In words: approve the merge, request one exact release, and approve its protecte Never shorten several of these states to “done.” -Independent officer publishing to the live Netlify host is **NOT AVAILABLE YET**. An overbroad #473 artifact was published and rolled back on 2026-08-01. The bounded replacement is now live as deploy `6a6dc9ea588b0c0008036312`: Shop is the static pickup catalog, and Events/Calendar show a fixed retry-later notice instead of a raw provider error. Event records remain unavailable. The temporary manifest is inactive again; this was not a reusable officer control. Use a platform maintainer until the Netlify connection and rollback path are documented and tested. +Independent officer publishing to the live Netlify host is **NOT AVAILABLE YET**. An overbroad #473 artifact was published and rolled back on 2026-08-01. The bounded replacement remains live as deploy `6a6dc9ea588b0c0008036312` from source `39ab8649df411262c8109a3c81a57bc38f1e168b`: Shop is the static pickup catalog, and Events/Calendar show a fixed retry-later notice instead of a raw provider error. Event records remain unavailable. #623 is one separate, exact-artifact release under review for the inert member-directory interface; it has not published or been verified, and the #473 deploy is its rollback target. This is not a reusable officer control. Use a platform maintainer until the Netlify connection and rollback path are documented and tested. The protected GitHub release is also **NOT AVAILABLE YET** until #133 configures the environment approvers and short-lived cloud identity. Missing authority stops the release with a red failure before Firebase or website publication. GitHub Pages currently still claims `runmprc.com`, so its normal address redirects to the Netlify-served name. The source stops adding that claim, but #136/WEB-001 must publish and verify the provider setting before officers call Pages an independent copy. -The optional profile-photo and officer People-finder functions are **NOT AVAILABLE YET**. #621 makes the frontend default an inert preview. My Account shows the future photo and independent finder-choice controls disabled. The administrator-guarded People finder shows its name field and Search button disabled. Neither preview reads saved state, accepts a file or name, creates a request, calls the directory services, or shows people. Officers inspect those protected disabled layouts only in synthetic local artifacts. After publication, signed-out public review proves only the exact revision, normal sign-in and administrator guards, and absence of a member-directory network request. Follow [Events, shop, members, and money](./EVENTS_SHOP_MEMBERS.md), and do not use a real name or photo. #507 must later prove privacy approval, scoped authorization, isolated staging, backend-first deployment and readback, and a separate reviewed source flip before connected use. The future connected feature is not the official roster and performs no photo query or facial recognition. +The optional profile-photo and officer People-finder functions are **NOT AVAILABLE YET**. #621 makes the frontend source default an inert preview. My Account shows the future photo and independent finder-choice controls disabled. The administrator-guarded People finder shows its name field and Search button disabled. Neither preview reads saved state, accepts a file or name, creates a request, calls the directory services, or shows people. #623 freezes only that inert interface for one exact Netlify release, but it remains under review and is not production. Officers inspect the protected disabled layouts only in synthetic local artifacts. If publication later passes, signed-out public review proves only the exact revision, normal sign-in and administrator guards, and absence of a member-directory network request. Follow [Events, shop, members, and money](./EVENTS_SHOP_MEMBERS.md), and do not sign in to production or use a real name or photo. #623 changes no Firebase, provider, account, sign-in, or production data. #507 must later prove privacy approval, scoped authorization, isolated staging, backend-first deployment and readback, and a separate reviewed source flip before connected use. The future connected feature is not the official roster and performs no photo query or facial recognition. ## Current safety warning diff --git a/netlify.toml b/netlify.toml index c6139a16..cf5b3eb7 100644 --- a/netlify.toml +++ b/netlify.toml @@ -16,8 +16,8 @@ command = "node ./scripts/netlify-release-build.js" [context.deploy-preview] - # The temporary production release manifest is inactive. Deploy Previews use - # the ordinary checked-out preview tree; production remains paused. + # The #623 control-branch preview verifies its exact pinned source. Other + # previews use their checked-out tree; production still needs the exact gate. command = "node ./scripts/netlify-release-build.js --preview" [[headers]] diff --git a/tests/release-workflow.test.js b/tests/release-workflow.test.js index 45bae77a..e18e38e3 100644 --- a/tests/release-workflow.test.js +++ b/tests/release-workflow.test.js @@ -226,69 +226,68 @@ test('Netlify production is an exact-artifact release while previews remain avai }); }); -test('Netlify manifest retains the bounded containment provenance and is paused', () => { +test('Netlify manifest pins the active bounded #623 interface release', () => { const loaded = loadManifest(NETLIFY_MANIFEST_PATH); assert.equal(loaded.ok, true); - assert.equal(loaded.manifest.active, false); - assert.equal(loaded.manifest.releaseId, 'WEB-002A-CONTAINMENT-2026-08-01'); - assert.equal(loaded.manifest.issueNumber, 473); + assert.equal(loaded.manifest.active, true); + assert.equal( + loaded.manifest.releaseId, + 'WEB-002C-MEMBER-DIRECTORY-PREVIEW-2026-08-13', + ); + assert.equal(loaded.manifest.issueNumber, 623); assert.equal( loaded.manifest.expectedProductionParent, - 'dee79511b6e371329aa129139729e112e7a51aad', + '019353361210021483f23003e09ee6924b78e67c', ); assert.equal( loaded.manifest.sourceCommit, - '39ab8649df411262c8109a3c81a57bc38f1e168b', + 'c2d87d1f69f15e128a0bc9b1b9f915b7c8417aec', ); assert.equal( loaded.manifest.sourceTree, - 'd76b496cdc5e79015bc048cb961758abbe88b9ce', + '411aa6ec9a9459f5d923030533ffc7c007fe6908', ); assert.equal( loaded.manifest.previousSourceCommit, - 'ed1b0833f25822cee80c99ded8753722b5608a3f', + '39ab8649df411262c8109a3c81a57bc38f1e168b', ); assert.equal( loaded.manifest.rollbackDeployId, - '6a6dc219a8136300081811db', + '6a6dc9ea588b0c0008036312', ); assert.equal( loaded.manifest.sourceRef, - 'refs/heads/codex/netlify-source-473-permissions-containment-v2', + 'refs/heads/codex/netlify-source-623-member-directory-preview', ); assert.equal( loaded.manifest.previewBranch, - 'codex/issue-473-permissions-containment-release', + 'codex/issue-623-netlify-release', ); - assert.equal(loaded.manifest.expectedSiteFileCount, 60); + assert.equal(loaded.manifest.expectedSiteFileCount, 62); assert.equal( loaded.manifest.expectedSiteFilesSha256, - '07b10c7d5ff176a1ad893d7549b07042312df35f4a4126e8765824ca94eaefb8', + 'd837272a1e5efc1575809e87f532276b38d1a63f1dd79ec1aef0533f6da8afb1', ); }); -test('paused #473 authority agrees with the final incident and live record', () => { +test('completed #473 records remain the current live predecessor truth', () => { assert.match( netlifyConfig, - /temporary production release manifest is inactive/i, + /#623 control-branch preview verifies its exact pinned source/i, ); assert.match( netlifyConfig, - /Deploy Previews use[\s\S]{0,20}ordinary checked-out preview tree/i, - ); - assert.doesNotMatch( - netlifyConfig, - /(?:active release-control PR|still verifies the pinned artifact)/i, + /Other[\s\S]{0,20}previews use their checked-out tree/i, ); const expectedTruth = new Map([ [ 'IMPLEMENTATION_PLAN.md', - /bounded replacement \[#473\][^\n]*deploy `6a6dc9ea588b0c0008036312`[^\n]*manifest is inactive again/i, + /bounded replacement \[#473\][^\n]*deploy `6a6dc9ea588b0c0008036312`[^\n]*temporary manifest became inactive again/i, ], [ 'OFFICER_START_HERE.md', - /bounded replacement is now live as Netlify deploy `6a6dc9ea588b0c0008036312`[^\n]*manifest is inactive again/i, + /bounded replacement is still live as Netlify deploy `6a6dc9ea588b0c0008036312`[^\n]*#623 is a separate one-shot release under review/i, ], [ 'OPERATIONS_RUNBOOK.md', @@ -304,7 +303,7 @@ test('paused #473 authority agrees with the final incident and live record', () ], [ 'SYSTEM_DESIGN.md', - /used one bounded replacement exception[^\n]*6a6dc9ea588b0c0008036312[^\n]*manifest is inactive again/i, + /used one bounded replacement exception[^\n]*6a6dc9ea588b0c0008036312[^\n]*manifest became inactive again/i, ], [ 'docs/officers/ACCESS_CONTINUITY.md', @@ -320,7 +319,7 @@ test('paused #473 authority agrees with the final incident and live record', () ], [ 'docs/officers/README.md', - /bounded replacement is now live as deploy `6a6dc9ea588b0c0008036312`[^\n]*manifest is inactive again/i, + /bounded replacement remains live as deploy `6a6dc9ea588b0c0008036312`[^\n]*#623 is one separate[^\n]*release under review/i, ], [ 'docs/officers/REQUEST_A_CHANGE.md', @@ -352,6 +351,33 @@ test('paused #473 authority agrees with the final incident and live record', () }); }); + const pendingTruth = [ + 'IMPLEMENTATION_PLAN.md', + 'OFFICER_START_HERE.md', + 'OPERATIONS_RUNBOOK.md', + 'SECURITY.md', + 'SYSTEM_DESIGN.md', + 'docs/officers/EVENTS_SHOP_MEMBERS.md', + 'docs/officers/PUBLISH_AND_CHECK.md', + 'docs/officers/README.md', + ].map((relativePath) => finalReleaseTruth.get(relativePath)); + pendingTruth.forEach((record) => { + assert.match(record, /#623/); + assert.match(record, /(?:under review|not published)/i); + assert.match(record, /6a6dc9ea588b0c0008036312/); + }); + [ + 'c2d87d1f69f15e128a0bc9b1b9f915b7c8417aec', + '411aa6ec9a9459f5d923030533ffc7c007fe6908', + 'd837272a1e5efc1575809e87f532276b38d1a63f1dd79ec1aef0533f6da8afb1', + '019353361210021483f23003e09ee6924b78e67c', + ].forEach((identifier) => { + [ + finalReleaseTruth.get('OPERATIONS_RUNBOOK.md'), + finalReleaseTruth.get('docs/officers/PUBLISH_AND_CHECK.md'), + ].forEach((record) => assert.match(record, new RegExp(identifier))); + }); + const canonicalRecords = [ finalReleaseTruth.get('OPERATIONS_RUNBOOK.md'), finalReleaseTruth.get('docs/officers/PUBLISH_AND_CHECK.md'),