diff --git a/.planning/PROJECT.md b/.planning/PROJECT.md index 4103e3e6..68849129 100644 --- a/.planning/PROJECT.md +++ b/.planning/PROJECT.md @@ -139,6 +139,11 @@ Quality - **Commits**: no co-author tags; one logical change per commit; planning docs and code in separate commits - **Core plugin contracts**: the PHP user, blog, pages and payment plugins are shared across many projects; the Go ports must preserve their contracts and the PHP originals are not changed as part of this project +Validated in Phase 18.1: Payment Gateway Subscriptions + +- [x] Generic subscriptions core (`sm-paymentgatewaysubscriptions-plugin`, pgsub v0.1.1) and pgstripe's subscription gateway, signed webhook and claim-first River job (pgstripe v0.1.1) on framework v0.2.2; admin plans/subscriptions screens, headless customer API and the layer's Pg subscription components +- [x] Live Stripe test-mode UAT 8/8 on a `summer build` demo app: checkout, single `activated` event, 409 on a second subscription, portal and site cancellation, admin sync and reorder; security audit 51 threats, 0 open (`18.1-SECURITY.md`) + ## Key Decisions | Decision | Rationale | Outcome | @@ -166,6 +171,7 @@ Quality | The local dev stack is the Nuxt app proxied to the Go binary, guarded fail-closed (`fonoteka.go/scripts/dev-stack.sh`), with dev-only seed and marker route gated on `SUMMER_ENV=local` only | Proves the port in daily use before cutover without touching the production origin; `dev` is refused because shared servers often use that name (review WR-05) | ✓ Good (Phase 14.2, 2026-10-12; gate e2e 17/0/0/0, manual UAT 1-11) | | Framework serves public uploads itself (`storage.uploads.serve`, opt-in) with nosniff, a sandboxing CSP and attachment disposition for non-images | Local runs need WinterCMS upload URLs without nginx; the hardening keeps uploaded HTML or SVG from running on the app origin (review WR-03) | ✓ Good (Phase 14.2) | | The opt-in request log (`http.access_log`) records method, matched pattern, status and duration, never the URL path | Share and invitation tokens travel in paths (review WR-01); an unported route shows as a 404 with an empty pattern | ✓ Good (Phase 14.2) | +| Subscription state comes from Stripe, not the event snapshot (Phase 18.1, D-21 amended) | Out-of-order webhooks regressed rows; the job re-retrieves the subscription under a per-subscription advisory lock, and a portal `cancel_at` counts as cancel at period end | ✓ Good (Phase 18.1 UAT, 2026-10-16) | ## Evolution @@ -185,4 +191,4 @@ This document evolves at phase transitions and milestone boundaries. 4. Update Context with current state --- -*Last updated: 2026-10-12 after Phase 14.2 completion* +*Last updated: 2026-10-16 after Phase 18.1 completion* diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index d9d6eda2..a4bf73ea 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -1156,7 +1156,7 @@ Plans: 4. A host test proves checkout, activation, `invoice.paid`, `past_due` with its grace period, cancel and duplicate-event idempotency against a Stripe fake. 5. The new code has unit tests, delivered in the phase's last plan. -**Plans:** 9/9 plans executed +**Plans:** 9/9 plans complete Plans: **Wave 1** @@ -1385,7 +1385,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 → | 16.3. Backend admin personal API tokens with scopes (INSERTED) | 4/4 | Complete | 2026-10-12 | | 17. Blog companions | 6/6 | Complete | 2026-10-13 | | 18. Payment Gateway | 8/8 | Complete | 2026-10-14 | -| 18.1. Payment Gateway Subscriptions | 9/9 | In Progress| | +| 18.1. Payment Gateway Subscriptions | 9/9 | Complete | 2026-10-16 | | 19. Chat and ChatChannels | 0/TBD | Not started | - | | 19.1. Chat satellites | 0/TBD | Not started | - | | 19.2. Chat polish against WavePath clients | 0/TBD | Not started | - | diff --git a/.planning/STATE.md b/.planning/STATE.md index 1fd26234..d4d24c4a 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -1,19 +1,20 @@ --- msd_state_version: "1.0" milestone: v1.0 -current_phase: "19.3" -current_phase_name: Framework transport and infrastructure for WavePath (INSERTED) -status: Phase 19.3 plans complete; release and whole-phase docs pending in the phase 19.3 main session -stopped_at: Completed 19.3-06-PLAN.md (gap closure; release handed off to the phase 19.3 main session) -last_updated: "2026-10-16T09:58:01.923Z" +current_phase: 19 +current_phase_name: Chat and ChatChannels +status: planning +stopped_at: Phase 18.1 complete, ready to plan Phase 19 +last_updated: "2026-10-16T19:08:06.609Z" last_activity: 2026-10-16 -last_activity_desc: "Completed quick task 261016-0fm: Admin minors for v0.2.1" -state_head: 66fd4ae4b702373adc818324270337796f32d71a +last_activity_desc: Phase 18.1 complete, transitioned to Phase 19 +state_head: c02f2724aeb21135f9077b8037bd72871e74a5e8 progress: total_phases: 40 - completed_phases: 31 + completed_phases: 32 total_plans: 191 - completed_plans: 188 + completed_plans: 191 + percent: 80 gsd_state_version: "1.0" milestone_name: milestone --- @@ -25,22 +26,22 @@ milestone_name: milestone See: .planning/PROJECT.md (updated 2026-10-12) **Core value:** An existing WinterCMS-shaped app can be ported plugin by plugin to a single Go binary without its frontend noticing: the PHP version's API contract is the acceptance test. -**Current focus:** Phase 19.3 — Framework transport and infrastructure for WavePath (INSERTED) +**Current focus:** Phase 19 — Chat and ChatChannels (Phase 19.3 is being worked on in its own session) ## Current Position -Phase: 19.3 (Framework transport and infrastructure for WavePath (INSERTED)) — EXECUTING -Plan: 6 of 6 -Status: Phase 19.3 plans complete; release and whole-phase docs pending in the phase 19.3 main session -Last activity: 2026-10-16 - Completed quick task 261016-0fm: Admin minors for v0.2.1 +Phase: 19 — Chat and ChatChannels +Plan: Not started +Status: Ready to plan +Last activity: 2026-10-16 — Phase 18.1 complete, transitioned to Phase 19 -Progress: [████░░░░░░] 44% +Progress: [████████░░] 80% ## Performance Metrics **Velocity:** -- Total plans completed: 79 +- Total plans completed: 88 - Average duration: 21 min - Total execution time: 104 min @@ -62,6 +63,7 @@ Progress: [████░░░░░░] 44% | 16.2 | 7 | - | - | | 16.3 | 4 | - | - | | 14.2 | 4 | - | - | +| 18.1 | 9 | - | - | **Recent Trend:** @@ -245,6 +247,9 @@ Progress: [████░░░░░░] 44% | Phase 19.3 P04 | 60min | 5 tasks | 14 files | | Phase 19.3 P05 | 49min | 7 tasks | 38 files | | Phase 19.3 P06 | 40min | 6 tasks | 24 files | +| Phase 18.1 P07 | 20 min | 3 tasks | 18 files | +| Phase 18.1 P08 | 24 min | 4 tasks | 30 files | +| Phase 18.1 P09 | 60min | 5 tasks | 27 files | ## Accumulated Context @@ -830,6 +835,14 @@ Recent decisions affecting current work: - [Phase 19.3]: Phase 19.3 released as summercms v0.1.27 (Task 7 reply: release); sm-faq-plugin, sm-menu-plugin and sm-journal-plugin require v0.1.27 on origin; app plugin submodule pointers not bumped yet - [Phase 19.3]: Phase 19.3 gate scripts/check-phase19.3.sh is fail-closed: a skipped, missing or red required test fails the stage; miniredis is test-only - [Phase 19.3]: 19.3-06: release of v0.1.28 (tag, plugin require bumps, layer tags v0.3.0/v0.3.0/v0.5.0, pushes) handed off to the phase 19.3 main session with the whole-phase docs update; gap closure pushed and tagged nothing +- [Phase 18.1]: 18.1-07: D-21 superseded for event ordering (user decision 2026-10-14): the Stripe webhook job applies the subscription Stripe holds (re-retrieved outside any transaction, serialised by a per-subscription pg_advisory_lock via classes.WithSessionLock/SubscriptionLockKey); a Stripe 404 falls back to the event snapshot +- [Phase 18.1]: 18.1-07: ApplyDeleted on an unstored subscription returns classes.ErrSubscriptionNotStored so the job rolls its marker back and River retries the deletion +- [Phase 18.1]: 18.1-07: plan resolution order is exact price id, then stripe_product_id, then plan_code (any plan, active or not); contracts.Update gains ProductID and PlanCode +- [Phase 18.1]: 18.1-07: Service.SyncSubscription writes only gateway-owned columns of a FOR UPDATE re-read row (plus the plan via the optional contracts.SubscriptionRetriever), never the caller's whole row +- [Phase 18.1]: 18.1-08: checkouts bill only the plan's own Stripe price per currency (SubscriptionPlan.CheckoutPriceID, no cross-currency fallback); the yen is unsupported +- [Phase 18.1]: 18.1-08: customer checkouts go through Service.CheckoutGuarded (per-subscriber advisory lock, 409 for any open subscription) plus Stripe idempotency keys (10-minute window, parameter hash); users.stripe_customer_id is first-write-wins +- [Phase 18.1]: 18.1-08: framework pact.AdminBulkAction.Detached (opt-in, json:"-") runs a bulk action outside the cabana transaction; plan sync uses it; needs framework tag v0.1.27 at plan 09 +- [Phase 18.1]: 18.1-09: framework release tag is v0.2.1 (v0.1.27 and v0.2.0 already on origin); first plugin tags pgsub v0.1.0 and pgstripe v0.1.0, pgstripe requires pgsub v0.1.0, replace lines kept ### Pending Todos @@ -912,6 +925,6 @@ Items acknowledged and carried forward from previous milestone close: ## Session Continuity -Last session: 2026-10-15T21:01:19.601Z -Stopped at: Completed 19.3-06-PLAN.md (gap closure; release handed off to the phase 19.3 main session) +Last session: 2026-10-16T11:56:16.182Z +Stopped at: Phase 18.1 complete, ready to plan Phase 19 Resume file: None diff --git a/.planning/state.json b/.planning/state.json index 4743cbf3..caaf3626 100644 --- a/.planning/state.json +++ b/.planning/state.json @@ -53,35 +53,55 @@ "name": "Admin Vue SPA", "status": "complete" }, + { + "number": "10.1", + "name": "Runtime admin extension point (INSERTED)", + "status": "complete" + }, + { + "number": "10.2", + "name": "Nest framework packages under modules and write run docs (INSERTED)", + "status": "complete" + }, { "number": "11", "name": "Jobs, realtime and search infrastructure", - "status": "in_progress" + "status": "complete" }, { "number": "11.1", "name": "SummerCMS documentation for humans and AI agents", - "status": "in_progress" + "status": "complete" }, { "number": "11.2", "name": "summercms.io Alpha 0.1 landing page on SummerCMS", - "status": "in_progress" + "status": "complete" }, { "number": "12", "name": "Płytarium API — Collections and Albums", "status": "complete" }, + { + "number": "12.1", + "name": "User plugin admin screens (INSERTED)", + "status": "complete" + }, + { + "number": "12.2", + "name": "Admin form fields: date, file upload, relation editing with deferred binding (INSERTED)", + "status": "complete" + }, { "number": "13", "name": "Płytarium API — wishlist, notifications, CSV, credentials, public routes", - "status": "in_progress" + "status": "complete" }, { "number": "14", "name": "Domain jobs and external integrations", - "status": "in_progress" + "status": "complete" }, { "number": "14.1", @@ -93,10 +113,15 @@ "name": "Local Fonoteka frontend on local SummerCMS backend (INSERTED)", "status": "complete" }, + { + "number": "14.2.1", + "name": "Translate plugin (INSERTED)", + "status": "complete" + }, { "number": "14.3", "name": "One-command app scaffolding (INSERTED)", - "status": "in_progress" + "status": "complete" }, { "number": "15", @@ -106,22 +131,37 @@ { "number": "16", "name": "grzybyfunkcjonalne.pl on reusable blog views", - "status": "in_progress" + "status": "complete" + }, + { + "number": "16.1", + "name": "Journal admin urgent fixes (INSERTED)", + "status": "complete" + }, + { + "number": "16.2", + "name": "Plugin-provided field types and the Blog Editor formwidget (INSERTED)", + "status": "complete" + }, + { + "number": "16.3", + "name": "Backend admin personal API tokens with scopes (INSERTED)", + "status": "complete" }, { "number": "17", "name": "Blog companions", - "status": "in_progress" + "status": "complete" }, { "number": "18", "name": "Payment Gateway", - "status": "in_progress" + "status": "complete" }, { "number": "18.1", "name": "Payment Gateway Subscriptions", - "status": "pending" + "status": "complete" }, { "number": "19", @@ -141,7 +181,7 @@ { "number": "19.3", "name": "Framework transport and infrastructure for WavePath", - "status": "pending" + "status": "in_progress" }, { "number": "19.4", @@ -166,8 +206,8 @@ ], "next": { "command": "/msd:progress --next", - "label": "Advance to the next step", - "reason": "Phase 18 of 40 · executing" + "label": "Advance to the next step (plan phase 19)", + "reason": "Phase 19 of 40 — needs a plan" }, - "updated_at": "2026-10-13T22:19:45.651Z" + "updated_at": "2026-10-16T19:08:06.654Z" }