diff --git a/GITHUB_ISSUES.md b/GITHUB_ISSUES.md index 70ee857..70c727e 100644 --- a/GITHUB_ISSUES.md +++ b/GITHUB_ISSUES.md @@ -1,6 +1,6 @@ # GitHub Issue Backlog: Secure Commerce Program -**COMMUNITY-002 / #707:** one staging-only service/UI for pending membership requests/withdrawal, paid-term cancellation intent and free-event signup/cancellation. Dependencies: #701 hosted profiles/Auth/App Check, #705 approved paid-term policy and existing pure authority; paid activation/dues/production work stays separate. UID-only additive server collections, native identity/attestation, transaction/revision/saved-command safety and bounded reads; no payment, role, email, schedule, migration or production fallback. Officer impact: disposable staging review only. Officer documentation: STAGING_COMMUNITY plus entrypoints/maps/cost guide. Deployment evidence: source/emulator preparation only until native hosted acceptance is recorded. #114 and production launch remain open. +**COMMUNITY-002 / #707:** one staging-only service/UI for pending membership requests/withdrawal, paid-term cancellation intent and free-event signup/cancellation. Dependencies: #701 hosted profiles/Auth/App Check, #705 approved paid-term policy and existing pure authority; paid activation/dues/production work stays separate. UID-only additive server collections, native identity/attestation, transaction/revision/saved-command safety and bounded reads; no payment, role, email, schedule, migration or production fallback. Officer impact: disposable staging review only. Officer documentation: STAGING_COMMUNITY plus entrypoints/maps/cost guide. Deployment evidence: version1 native archive/spec/IAM, website60435c8,38 genuine hosted checks and all four page actions verified on 2026-10-11; #707 records final website/visual/cleanup proof. #114 and production launch remain open. **MEMBERS-CANCEL-001A / #705 (2026-10-11):** owner-approved cancellation retains membership access through the already-paid annual term and does not issue an automatic refund. Scope: versioned backend source policy, existing authority expiry/ownership/suspension regressions, and officer handoff. No cancellation endpoint, persistence, claim, Rule, IAM, website, provider or deployment change. #114 and hosted membership/event signup/cancellation remain open. Officer impact: the rule is recorded; no new member control is available. Officer documentation: entrypoint and Events/Shop/Members guide. Deployment evidence: source/local checks only until native issue records otherwise. diff --git a/OFFICER_START_HERE.md b/OFFICER_START_HERE.md index b5a0262..19d0237 100644 --- a/OFFICER_START_HERE.md +++ b/OFFICER_START_HERE.md @@ -16,7 +16,7 @@ Attach the exact wording, public link, or approved photo when you have it. The A ## Choose the closest task -**Staging membership/free events — #707:** one isolated service and screen are being prepared for pending membership signup/withdrawal, paid-term cancellation requests and free-event signup/cancellation. No paid membership activation or real payment is included. Hosted acceptance is not verified yet. Follow the [staging-only review](./docs/officers/STAGING_COMMUNITY.md); production remains **NOT AVAILABLE YET**. +**Staging membership/free events — #707:** one isolated service and screen passed hosted pending membership signup/withdrawal, synthetic paid-term cancellation and free-event signup/cancellation checks on 2026-10-11. No paid membership activation or real payment is included. Follow the [staging-only review](./docs/officers/STAGING_COMMUNITY.md); ask the specialist for disposable fixtures. The issue records the current website, visual review and cleanup; production remains **NOT AVAILABLE YET**. **Membership cancellation decision, 2026-10-11:** Dave approved keeping access through the already-paid annual term, without an automatic refund. [#705](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/705) records the rule and checks the existing term-end logic only. A member cancellation control is **NOT AVAILABLE YET**; do not edit a member record or use race cancellation as a workaround. See [the membership policy handoff](./docs/officers/EVENTS_SHOP_MEMBERS.md#membership-cancellation--policy-approved-not-available-yet). diff --git a/OPERATIONS_RUNBOOK.md b/OPERATIONS_RUNBOOK.md index 5cc474d..9eac528 100644 --- a/OPERATIONS_RUNBOOK.md +++ b/OPERATIONS_RUNBOOK.md @@ -830,6 +830,8 @@ The current protected release plan names only the profile-recovery Functions. It For #707, only the club owner may use the fixed `scripts/staging-community-release.js` after reviewed exact-main source/six successful jobs. It deploys one isolated Function through the existing explicit builder, with a separate four-permission runtime and native archive/resource readback. This is a separately approved staging owner route, not a widening of the protected profile workflow or a reusable production release. The profile path may inspect the one known community service but still deploys only its original pair. The specialist supplies/removes UID-only synthetic fixtures, tests actual Auth/App Check plus retry/ownership/capacity/term-boundary behavior, then records staged Hosting and cleanup evidence. Officers use [the no-terminal staging review](./docs/officers/STAGING_COMMUNITY.md). Never run against production or a payment provider; future release cleanup/rollback uses a separate reviewed owner action, not direct member edits. +On 2026-10-11, #707 verified `communityActions` version1 from `7e3dc451368c70e4b63805927b12d413e8739b56`; its81,228-byte native archive exactly matches SHA256 `1b78f38d8b5563e9e88482f68d5f57c3249c49c43c69b49eed3efbcd5fa7e148`. Profile versions2/1 and their labels remained unchanged. First Hosting60435c8 passed38 genuine browser boundary/lifecycle checks and all four page actions. Hosting uses an email-verified club-owner short-lived token only in process memory, with explicit staging quota and no ADC/login/key fallback. A script/docs-only newer main may reuse the Function only with ancestor provenance, exact current bundle and native archive readback; no extra Function build is required. The final issue receipt separately records current Hosting, visual review and fixture cleanup; production/real payments remain excluded. + For future membership cancellation acceptance, use a disposable account and a synthetic approved term. Prove access remains just before the original term end and is denied at/after it; the term and payment evidence must not change. Also prove ownership denials, repeated/lost-response recovery, and that suspension is not undone. #705 currently supplies only the approved policy and existing-authority unit regressions, not the cancellation request or hosted proof. Do not test with production members or real payments. Before every major payment release: diff --git a/SECURITY.md b/SECURITY.md index 8a3e438..f6e10f2 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -8,7 +8,7 @@ This is both the repository security policy and the current engineering risk reg ## 1. Production readiness decision -**#707 isolated staging boundary:** one new service requires native Auth/App Check and an exact caller-account match; mutations also require verified email. It accepts closed bounded inputs and server-owned free-event/waiver/capacity state, saves command results transactionally and fences stale revisions. Browser access to new collections remains denied, including admins. The separate staging runtime has only transaction/get/create/update Firestore permissions; IAM is project-scoped, not collection-scoped. It has no Auth mutation, delete, secret, key, payment or mail permission. No claims, real membership/payment data, production fallback, raw error/body logging or legacy schema migration. Existing two profile Functions and their runtime IAM are unchanged. Hosted enforcement must be separately verified before claiming this source is live. +**#707 isolated staging boundary:** one new service requires native Auth/App Check and an exact caller-account match; mutations also require verified email. It accepts closed bounded inputs and server-owned free-event/waiver/capacity state, saves command results transactionally and fences stale revisions. Browser access to new collections remains denied, including admins. The separate staging runtime has only transaction/get/create/update Firestore permissions; IAM is project-scoped, not collection-scoped. It has no Auth mutation, delete, secret, key, payment or mail permission. No claims, real membership/payment data, production fallback, raw error/body logging or legacy schema migration. Existing two profile Functions and their runtime IAM are unchanged. Native archive/spec/IAM and38 genuine hosted ownership/attestation/lifecycle checks were verified on 2026-10-11; this proves only the staged boundary, not production, financial operations or a spending cap. The public informational website can continue operating with normal care. **Live race or merchandise payments should remain disabled until every P0 launch blocker below is closed and verified in a staging dress rehearsal.** The existing payment implementation is a useful prototype, but several correctness defects could mark unpaid transactions paid, lose payment callbacks, oversell race capacity, fail to reconcile late-payment links, or allow overly broad administrative access. diff --git a/SYSTEM_DESIGN.md b/SYSTEM_DESIGN.md index fa4e76a..0298ee5 100644 --- a/SYSTEM_DESIGN.md +++ b/SYSTEM_DESIGN.md @@ -10,7 +10,7 @@ The repository already contains a substantial prototype: a public React site, Fi ## 1. Goals and non-goals -**Isolated nonfinancial lifecycle — #707, source prepared:** `communityOnly.js` exports only `communityActions`, outside the prototype index. Native Auth/App Check, exact account binding and verified email protect mutations. UID-only pending membership requests never grant dues/roles; cancellation preserves an approved synthetic authority record and its original expiry. Server-owned zero-price events/waiver snapshots and UID registrations share one capacity transaction. Per-account command results/revision fences prevent old cancellation retries changing a later signup. New `communityMemberships`, `communityEvents`, `communityCommands`, `communityLimits` and `communityConfig` collections are server-only; no migration or legacy commerce adoption. A separate runtime has exactly Firestore transaction/get/create/update permissions at staging-project scope. UI and server fail closed outside staging. The existing pure authority is now consumed only by this isolated staging service; earlier unused-runtime statements below remain historical outside this scope. No role/dues activation, Stripe/mail, officer admin endpoint, schedule or production release is included. Hosted acceptance remains unverified; see the [officer staging guide](./docs/officers/STAGING_COMMUNITY.md). +**Isolated nonfinancial lifecycle — #707, staging only:** `communityOnly.js` exports only `communityActions`, outside the prototype index. Native Auth/App Check, exact account binding and verified email protect mutations. UID-only pending membership requests never grant dues/roles; cancellation preserves an approved synthetic authority record and its original expiry. Server-owned zero-price events/waiver snapshots and UID registrations share one capacity transaction. Per-account command results/revision fences prevent old cancellation retries changing a later signup. New `communityMemberships`, `communityEvents`, `communityCommands`, `communityLimits` and `communityConfig` collections are server-only; no migration or legacy commerce adoption. A separate runtime has exactly Firestore transaction/get/create/update permissions at staging-project scope. UI and server fail closed outside staging. The existing pure authority is now consumed only by this isolated staging service; earlier unused-runtime statements below remain historical outside this scope. No role/dues activation, Stripe/mail, officer admin endpoint, schedule or production release is included. First native hosted acceptance was verified on 2026-10-11; see the [officer staging guide](./docs/officers/STAGING_COMMUNITY.md) and #707 for current website/cleanup proof, not production availability. **Engineering staging checkpoint, 2026-10-11 — #701:** only signup-profile creation and profile recovery are hosted and verified on Node 22. A profile-only entrypoint, explicit runtime/build accounts, scoped IAM and protected keyless deployment exclude commerce/mail/directory/Strava and website publication. Actual source archives/resource limits, genuine App Check/Auth, synthetic signup/recovery/denials/retries/concurrency and owned cleanup passed. Auth count returned to zero; no migration or production data/payment change. This supersedes source-only runtime/profile wording below only for isolated staging, not production or the target commerce platform. See the [dated officer map](./docs/officers/SYSTEM_MAPS.md#scoped-engineering-profile-release--701) and [acceptance proof](./docs/officers/LOW_COST_COMMUNITY.md#engineering-staging-profiles--701). diff --git a/docs/officers/EVENTS_SHOP_MEMBERS.md b/docs/officers/EVENTS_SHOP_MEMBERS.md index 0d572f2..47f21c8 100644 --- a/docs/officers/EVENTS_SHOP_MEMBERS.md +++ b/docs/officers/EVENTS_SHOP_MEMBERS.md @@ -12,7 +12,7 @@ There is currently no proven no-code switch that safely stops all new Stripe pay ## Membership cancellation — POLICY APPROVED, NOT AVAILABLE YET -[#707](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/707) prepares the separate [staging-only request and free-event review](./STAGING_COMMUNITY.md). This does not launch production membership, grant a paid term, or repair paid race cancellation. Hosted evidence is still required. +[#707](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/707) verified the separate [staging-only request and free-event review](./STAGING_COMMUNITY.md) on 2026-10-11. Fresh disposable fixtures still require the specialist. This does not launch production membership, grant a paid term, or repair paid race cancellation. Dave approved this rule on October 11, 2026: cancelling membership keeps access through the already-paid annual term. Cancellation does not issue a refund. It does not extend the term or restore suspended access. Refunds and race cancellation remain separate. diff --git a/docs/officers/LOW_COST_COMMUNITY.md b/docs/officers/LOW_COST_COMMUNITY.md index 67624c5..0f62576 100644 --- a/docs/officers/LOW_COST_COMMUNITY.md +++ b/docs/officers/LOW_COST_COMMUNITY.md @@ -8,7 +8,7 @@ ## What we are prioritizing -[#707](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/707) adds one staging-only nonfinancial service, with the same min0/max2/256MB/30s limits and no polling, mail worker, subscription or scheduled job. Existing profile IAM stays unchanged; the new runtime has four Firestore permissions at staging-project scope. See the [staging scope, proof and recovery guide](./STAGING_COMMUNITY.md). This source preparation is not deployment proof or a cost guarantee. +[#707](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/707) verified one staging-only nonfinancial service on 2026-10-11, with the same min0/max2/256MB/30s limits and no polling, mail worker, subscription or scheduled job. Existing profile IAM stays unchanged; the new runtime has four Firestore permissions at staging-project scope. See the [staging scope, proof and recovery guide](./STAGING_COMMUNITY.md). Hosted acceptance is not a cost guarantee or a production launch. - Clear information about regular runs, meeting places, joining, and contacts. - A useful events/calendar experience on phones as well as computers. diff --git a/docs/officers/STAGING_COMMUNITY.md b/docs/officers/STAGING_COMMUNITY.md index e4ac0fd..4441c28 100644 --- a/docs/officers/STAGING_COMMUNITY.md +++ b/docs/officers/STAGING_COMMUNITY.md @@ -1,6 +1,6 @@ # Membership and free events — staging only -**Status:** SOURCE PREPARED; hosted deployment and acceptance are not verified yet under [#707](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/707). Production is **NOT AVAILABLE YET**. +**Status:** STAGING ONLY — the Function and first website release were verified on 2026-10-11 under [#707](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/707). Pending membership requests/withdrawal, synthetic paid-term cancellation, free-event signup/cancellation, native Auth/App Check denials, retries and capacity passed hosted checks. Production is **NOT AVAILABLE YET**. The issue records the latest website source, visual review and disposable-data cleanup separately. **Purpose:** test membership requests and free-event signup/cancellation without real members or payments. @@ -16,7 +16,7 @@ - Free-event signup: confirms one place and records acceptance of the displayed test waiver. - Cancel event signup: releases that place once. Old retries cannot cancel a later signup. -Officer review after the specialist confirms deployment: +Officer review after the specialist supplies fresh disposable fixtures: 1. Open the specialist-supplied staging `/community` link. 2. Confirm the page says **Staging test only**. @@ -41,6 +41,8 @@ Officer review after the specialist confirms deployment: **Success proof:** the specialist records exact source/CI, native Function archive and resource/IAM readback, staging Hosting release, genuine Auth/App Check denials, capacity/retry/ownership tests, and disposable-data cleanup separately. Local tests or a merge alone are not hosted proof. The term-end check uses synthetic evidence, not a real payment. +The first verified backend is version 1 from `7e3dc451368c70e4b63805927b12d413e8739b56`. Its native archive is 81,228 bytes with SHA-256 `1b78f38d8b5563e9e88482f68d5f57c3249c49c43c69b49eed3efbcd5fa7e148`. The first verified website is `60435c8fe0846625f22067b0d9b2e76171317d2b`. Thirty-eight genuine hosted boundary/lifecycle checks passed, followed by all four page actions. Twenty distinct emulator accounts and two disposable hosted accounts separately proved final-seat safety. Cancellation preserved the synthetic term evidence and access; the real server clock then ended access. No custom role claims, charges, refunds or emails were created. These are dated staging proofs, not production availability or actual payment proof. + **Undo:** stop the staging review. Ask Dave and the platform specialist to use the reviewed staging rollback or remove the one staged community service. Do not edit a member record, role, payment or production setting yourself. The specialist's Hosting release uses Dave's existing club login for a short-lived credential kept in memory. It does not require a new saved Firebase CLI login or key file. A later documentation or release-script fix may reuse the Function only after its downloaded source exactly matches the reviewed bundle; it does not silently deploy more backend code. diff --git a/docs/officers/SYSTEM_MAPS.md b/docs/officers/SYSTEM_MAPS.md index f95c3f8..b1cf8ae 100644 --- a/docs/officers/SYSTEM_MAPS.md +++ b/docs/officers/SYSTEM_MAPS.md @@ -2,11 +2,13 @@ These diagrams show where pages, information, and deployments go. Each diagram includes a one-sentence text version. -## Staging community lifecycle — #707, source prepared +## Staging community lifecycle — #707, first hosted release verified 2026-10-11 ```mermaid flowchart LR Account["Staging My Account"] --> Page["/community; production disabled"] + Page --> Login["Signed out: sign in; return to /community"] + Login --> Identity Page --> Identity["Native Auth + App Check; own account only"] Identity --> Service["One communityActions Function; min0/max2"] Service --> Requests["Pending membership / cancellation request; no paid grant"] @@ -16,7 +18,7 @@ flowchart LR Browser["Browser member/admin"] -. "Denied direct access" .-> Requests ``` -Text alternative: a staging-only account link opens one authenticated service that stores member requests, transactionally manages free-event seats and saves retry results, while preserving the original term and denying direct browser writes. Hosting/provider proof is not yet recorded; no production, payment, mail or role-grant path is added. +Text alternative: a staging-only account link opens one authenticated service that stores member requests, transactionally manages free-event seats and saves retry results, while preserving the original term and denying direct browser writes; signed-out visitors return after sign-in. First Hosting/provider acceptance is recorded in the staging guide and #707; no production, payment, mail or role-grant path is added. ## Engineering staging billing — checked 2026-10-11 diff --git a/src/pages/community/Community.css b/src/pages/community/Community.css new file mode 100644 index 0000000..198785d --- /dev/null +++ b/src/pages/community/Community.css @@ -0,0 +1,54 @@ +.community-staging { + box-sizing: border-box; + width: calc(100% - 2.5rem); + max-width: 800px; + margin-inline: auto; + padding-block: 2rem; + overflow-wrap: anywhere; +} + +.community-staging h1 { margin-bottom: 1rem; } +.community-staging h2 { margin-bottom: 0.75rem; } +.community-staging p { margin-block: 0.75rem; } +.community-staging a { text-decoration: underline; } + +.community-staging [role="note"] { + padding: 1rem; + border-left: 4px solid var(--color-secondary); + background: var(--color-gray-500); +} + +.community-staging section { + margin-top: 1.5rem; + padding: 1.25rem; + border: 1px solid #77759e; + border-radius: 0.75rem; +} + +.community-staging button { + min-height: 44px; + max-width: 100%; + margin: 0.25rem 0.25rem 0.25rem 0; + padding: 0.6rem 1rem; + border: 1px solid #a9bfff; + border-radius: 0.4rem; + background: var(--color-primary); + color: #fff; + font: inherit; + cursor: pointer; +} + +.community-staging button:disabled { + background: #32314e; + color: #c7c6d7; + border-color: #77759e; + cursor: not-allowed; +} + +.community-staging label { display: flex; gap: 0.75rem; align-items: center; min-height: 44px; } +.community-staging input[type="checkbox"] { width: 20px; min-width: 20px; height: 20px; } + +@media (max-width: 480px) { + .community-staging section { padding: 1rem; } + .community-staging button { width: 100%; margin-inline: 0; } +} diff --git a/src/pages/community/Community.test.tsx b/src/pages/community/Community.test.tsx index 8f8e33c..cf0f237 100644 --- a/src/pages/community/Community.test.tsx +++ b/src/pages/community/Community.test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { MemoryRouter } from 'react-router-dom'; +import { MemoryRouter, useLocation } from 'react-router-dom'; import { HelmetProvider } from 'react-helmet-async'; import { act, fireEvent, render, screen, waitFor @@ -42,6 +42,25 @@ test('signed-out page makes no backend call', () => { expect(screen.getByRole('link', { name: 'Sign in to continue' })).toBeInTheDocument(); expect(call).not.toHaveBeenCalled(); }); +test('signed-out staging notice stays visible and sign-in returns to community', () => { + mockUser = null; + function LocationProbe() { + const location = useLocation(); + return {`${location.pathname}:${location.state?.from || ''}`}; + } + render( + + + + + + , + ); + expect(screen.getByRole('note')).toHaveTextContent('Staging test only'); + fireEvent.click(screen.getByRole('link', { name: 'Sign in to continue' })); + expect(screen.getByTestId('return-path')).toHaveTextContent('/login:/community'); + expect(call).not.toHaveBeenCalled(); +}); test('pending membership request uses the signed-in account and rereads status', async () => { render(show()); await screen.findByRole('button', { name: 'Request membership' }); call.mockResolvedValueOnce({ membership: { revision: 1, applicationStatus: 'pending' } }).mockResolvedValueOnce(state('pending', 1)); diff --git a/src/pages/community/Community.tsx b/src/pages/community/Community.tsx index 26dea04..15681e4 100644 --- a/src/pages/community/Community.tsx +++ b/src/pages/community/Community.tsx @@ -6,6 +6,7 @@ import { callCommunity, COMMUNITY_ENABLED, CommunityRequest, CommunityStatus } from '../../services/community/communityService'; import SEO from '../../components/SEO'; +import './Community.css'; export default function Community() { const { services } = useServiceLocator(); @@ -74,22 +75,24 @@ export default function Community() { } if (!enabled) { return ( -
+

Community signup is not available here

My Account
); } - if (isLoading) return

Loading account…

; + if (isLoading) return

Loading account…

; if (!uid) { return ( -
+
+

Membership and free events

- Sign in to continue +

Staging test only. Use made-up accounts. No real membership, race entry or payment is created.

+ Sign in to continue
); } const verified = services?.firebaseResources.auth.currentUser?.emailVerified === true; const blocked = busy || pending.current !== null || !verified; return ( -
+

Membership and free events

@@ -127,6 +130,7 @@ export default function Community() { && (

Access continues through the paid term ending + {' '} {new Date(status.membership.accessThroughMs).toLocaleString()} .