From 03b8cc277049cb9f2b29a34b8c4cc01b2988105b Mon Sep 17 00:00:00 2001 From: Jakub Zych Date: Fri, 16 Oct 2026 13:44:55 +0200 Subject: [PATCH] docs(18.1-09): VALIDATION rows for plans 07 to 09 and the deferred review findings --- .../18.1-VALIDATION.md | 33 +++++++++++++++---- .../deferred-items.md | 22 +++++++++++++ 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/.planning/phases/18.1-payment-gateway-subscriptions/18.1-VALIDATION.md b/.planning/phases/18.1-payment-gateway-subscriptions/18.1-VALIDATION.md index 06aff51..0da746d 100644 --- a/.planning/phases/18.1-payment-gateway-subscriptions/18.1-VALIDATION.md +++ b/.planning/phases/18.1-payment-gateway-subscriptions/18.1-VALIDATION.md @@ -8,6 +8,7 @@ nyquist_compliant: true wave_0_complete: true created: "2026-10-14" validated: "2026-10-14" +revalidated: "2026-10-16" --- # Phase 18.1 — Validation Strategy @@ -40,7 +41,7 @@ validated: "2026-10-14" ## Per-Task Verification Map -Every task of plans 01 to 06, with the command that proves it (run from the summercms.go root; sibling repositories through `go -C ../` or `pnpm --dir ../`). Docker rows ran on a Docker host. "File exists" names the plan and task that created the test. Status is the result of the 18.1-06 gate run (`PHASE181_REQUIRE_POSTGRES=1 bash scripts/check-phase18.1.sh --all`) unless noted. +Every task of plans 01 to 09, with the command that proves it (run from the summercms.go root; sibling repositories through `go -C ../` or `pnpm --dir ../`). Docker rows ran on a Docker host. "File exists" names the plan and task that created the test. Status is the result of the 18.1-06 gate run (`PHASE181_REQUIRE_POSTGRES=1 bash scripts/check-phase18.1.sh --all`) for plans 01 to 06 and of the 18.1-09 gate run (the same command, 2026-10-16, Docker host) for plans 07 to 09, unless noted. | Task | Req | Behaviour | Type | Automated command | File exists | Status | |------|-----|-----------|------|-------------------|-------------|--------| @@ -70,7 +71,19 @@ Every task of plans 01 to 06, with the command that proves it (run from the summ | 18.1-06 T2 | SC-5, D-17, D-14 | framework and layer coverage; the `reorder.flat` defect fixed | gate | `bash scripts/check-phase18.1.sh go && bash scripts/check-phase18.1.sh admin && bash scripts/check-phase18.1.sh layers` | ✅ (06 T2) | ✅ green | | 18.1-06 T3 | SC-5, D-03 to D-16, D-21 | pgsub coverage | gate | `bash scripts/check-phase18.1.sh plugins && bash scripts/check-phase18.1.sh postgres` | ✅ (06 T3) | ✅ green | | 18.1-06 T4 | SC-5, D-11, D-12, D-15, D-18, D-19 | pgstripe coverage and the PHP map | gate | `PHASE181_REQUIRE_POSTGRES=1 bash scripts/check-phase18.1.sh --all` | ✅ (06 T4) | ✅ green | -| 18.1-06 T5 | D-22 | final push and tag decision | checkpoint | n/a (awaiting the human reply) | n/a | ⬜ pending | +| 18.1-06 T5 | D-22 | final push and tag decision | checkpoint | n/a (reply "push-all": framework v0.1.26, layer v0.2.0) | n/a | ✅ recorded | +| 18.1-07 T1 | CR-01, D-11 | webhook job applies the subscription Stripe holds, under the per-subscription session lock, claim-first kept | Docker | `go -C ../sm-pgstripe-plugin test -count=1 -run '^(TestEventOrdering\|TestFreshStateSmoke\|TestRetrieveSubscription)$' ./subscriptions` | ✅ (07 T1, 09 T1, 09 T3) | ✅ green | +| 18.1-07 T2 | CR-01, WR-04 | a deletion before its subscription is stored is retried; plan resolution by price, product, plan code | unit + Docker | `go -C ../sm-paymentgatewaysubscriptions-plugin test -count=1 -run '^(TestPlanResolution\|TestApplyDeletedNotStored)$' .`; `go -C ../sm-pgstripe-plugin test -count=1 -short -run '^TestDecodeSubscription$' ./subscriptions` | ✅ (09 T2, 09 T3) | ✅ green | +| 18.1-07 T3 | WR-06 | SyncSubscription writes only gateway-owned columns of a locked re-read row and refreshes the plan | Docker | `go -C ../sm-paymentgatewaysubscriptions-plugin test -count=1 -run '^TestSyncSubscriptionFresh$' .` | ✅ (09 T2) | ✅ green | +| 18.1-08 T1 | CR-02 | strict checkout price: customer API 422, gateway ErrNoPriceForCurrency without a Stripe call, yen refused | unit + Docker | `go -C ../sm-paymentgatewaysubscriptions-plugin test -count=1 -short -run '^TestCheckoutPriceID$' ./models`; `go -C ../sm-pgstripe-plugin test -count=1 -run '^TestGatewayStrictPrice$' ./subscriptions`; `go -C ../sm-paymentgatewaysubscriptions-plugin test -count=1 -run '^TestCustomerAPIGuards$' .` | ✅ (09 T2, 09 T3) | ✅ green | +| 18.1-08 T2 | WR-02, WR-03 | idempotency keys, first-write-wins customer id, guarded checkout under the subscriber lock, 409 for any open subscription | unit + Docker | `go -C ../sm-paymentgatewaysubscriptions-plugin test -count=1 -short -run '^TestTerminalStatuses$' ./models`; `go -C ../sm-paymentgatewaysubscriptions-plugin test -count=1 -run '^(TestCheckoutGuarded\|TestCustomerIDFirstWriteWins\|TestCustomerAPIGuards)$' .`; `go -C ../sm-pgstripe-plugin test -count=1 -run '^TestGatewayIdempotencyKeys$' ./subscriptions` | ✅ (09 T2, 09 T3) | ✅ green | +| 18.1-08 T3 | WR-01 | `pact.AdminBulkAction.Detached`; plan sync holds no transaction or row lock across Stripe calls | Docker | `go test -count=1 -run '^TestDetachedBulkAction$' ./modules/cabana`; `go -C ../sm-paymentgatewaysubscriptions-plugin test -count=1 -run '^TestAdminSyncDetached$' .` | ✅ (09 T2, 09 T3) | ✅ green | +| 18.1-08 T4 | WR-07, WR-03 | the layer's buttons recover after Back from Stripe; 409 copy fits any open subscription | node | `pnpm --dir ../vue-paymentgateway-layer test` (seven `subscriptions:` specs of 18.1-09 in `tests/subscribe.test.ts`, `tests/subscriptionComponents.test.ts`) | ✅ (09 T4) | ✅ green | +| 18.1-09 T1 | SC-5, CR-01 | tracer: the CR-01 ordering suite required by name in the postgres stage | gate | `bash scripts/check-phase18.1.sh --self-test && bash scripts/check-phase18.1.sh postgres` | ✅ (09 T1) | ✅ green | +| 18.1-09 T2 | SC-5, CR-01, CR-02, WR-01 to WR-04, WR-06 | pgsub gap-closure coverage | gate | `bash scripts/check-phase18.1.sh plugins && bash scripts/check-phase18.1.sh postgres` | ✅ (09 T2) | ✅ green | +| 18.1-09 T3 | SC-5, CR-01, CR-02, WR-01 to WR-04 | pgstripe and framework coverage | gate | `bash scripts/check-phase18.1.sh go && bash scripts/check-phase18.1.sh plugins && bash scripts/check-phase18.1.sh docs && bash scripts/check-phase18.1.sh postgres` | ✅ (09 T3) | ✅ green | +| 18.1-09 T4 | SC-5, WR-07 | layer coverage (43 `subscriptions:` passes, minimum 43), version 0.2.1, the full gate | gate | `PHASE181_REQUIRE_POSTGRES=1 bash scripts/check-phase18.1.sh --all` | ✅ (09 T4) | ✅ green | +| 18.1-09 T5 | D-22 | final push and tag decision for the gap closure (framework v0.1.27, layer v0.2.1, optional plugin v0.1.0) | checkpoint | n/a (awaiting the human reply) | n/a | ⬜ pending | The success-criterion view (seeded by the planner) maps onto the rows above: @@ -81,6 +94,13 @@ The success-criterion view (seeded by the planner) maps onto the rows above: | SC-3 | admin screens and actions; customer API; layer client and components | `TestAdminPlansSmoke`, `TestAdminSubscriptionsSmoke`, `TestAdminSyncActions`, `TestCustomerAPISmoke`, `TestCustomerAPIAuthz`, the layer's `subscriptions:` specs | | SC-4 | the proof host flow incl. a real conga worker | `TestHostSubscriptionFlow` | | SC-5 | the PHP test map and the gate | `TestStripeSubscriptionReliability`, `TestSubscriptionAuthentication`, `TestStripeSubscriptionSecrets`, `TestSDKPin`; `scripts/check-phase18.1.sh --all` | +| CR-01 | stale events cannot regress a row; deletion before creation retried | `TestEventOrdering`, `TestFreshStateSmoke`, `TestApplyDeletedNotStored`, `TestRetrieveSubscription` | +| CR-02 | no checkout billed at another currency's price | `TestCheckoutPriceID`, `TestGatewayStrictPrice`, `TestCustomerAPIGuards` | +| WR-01 | no transaction or row lock across Stripe calls in the admin sync | `TestDetachedBulkAction`, `TestAdminSyncDetached` | +| WR-02, WR-03 | one customer and one checkout per subscriber; any open subscription blocks checkout | `TestGatewayIdempotencyKeys`, `TestCustomerIDFirstWriteWins`, `TestCheckoutGuarded`, `TestTerminalStatuses`, `TestCustomerAPIGuards` | +| WR-04 | a re-priced or renamed plan still stores the subscription | `TestPlanResolution`, `TestDecodeSubscription` | +| WR-06 | no lost update in SyncSubscription | `TestSyncSubscriptionFresh` | +| WR-07 | the layer's buttons recover after Back | the seven 18.1-09 `subscriptions:` specs | *Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky* @@ -91,7 +111,8 @@ The success-criterion view (seeded by the planner) maps onto the rows above: - [x] `sm-paymentgatewaysubscriptions-plugin/testmain_test.go` + harness (18.1-02) - [x] Subscription-endpoint Stripe fake in `sm-pgstripe-plugin/subscriptions` (18.1-03; price failure switch added in 18.1-06) - [x] Webhook event-body builders for both API shapes (`subscriptions/eventbody_test.go`, 18.1-03) -- [x] `scripts/check-phase18.1.sh` (18.1-06) +- [x] `scripts/check-phase18.1.sh` (18.1-06; gap-closure names and the layers minimum 43 added in 18.1-09) +- [x] Stripe fake retrieval failure switch `setFailRetrieve` and the `Idempotency-Key` capture (18.1-08, 18.1-09) --- @@ -100,8 +121,8 @@ The success-criterion view (seeded by the planner) maps onto the rows above: | Behavior | Requirement | Why Manual | Test Instructions | |----------|-------------|------------|-------------------| | Billing-portal cancellation arrives as `cancel_at_period_end` or `cancel_at` | SC-2, D-21 | Depends on live Stripe portal behaviour on the account's API version | Stripe test mode + `stripe listen` forwarding to the subscriptions URL; cancel through the portal; inspect the delivered `customer.subscription.updated` | -| Real Stripe test-mode checkout end to end | SC-4 | The fake cannot prove Stripe's hosted checkout | Test-mode keys in settings, run checkout from the layer playground, confirm activation and the `activated` event | -| Admin screens and layer components in a real browser | SC-3, D-17 | vitest and SSR specs do not drive a browser | Try the taglist chips, the flat drag reorder, the sync toasts, the plans page, Checkout and portal redirects and the cancel dialog once | +| Real Stripe test-mode checkout end to end | SC-4, CR-01 | The fake cannot prove Stripe's hosted checkout or its real event order | Test-mode keys in settings, run checkout from the layer playground, confirm activation and the `activated` event; watch that `customer.subscription.created` and `.updated` arrive in either order without the row regressing | +| Admin screens and layer components in a real browser | SC-3, D-17, WR-07 | vitest and SSR specs do not drive a browser or its back/forward cache | Try the taglist chips, the flat drag reorder, the sync toasts, the plans page, Checkout and portal redirects and the cancel dialog once, and press Back from Checkout and from the portal: the buttons are enabled again | --- @@ -114,4 +135,4 @@ The success-criterion view (seeded by the planner) maps onto the rows above: - [x] Feedback latency < 60s for the quick run - [x] `nyquist_compliant: true` set in frontmatter -**Approval:** validated 2026-10-14 by the 18.1-06 gate run (`PHASE181_REQUIRE_POSTGRES=1 bash scripts/check-phase18.1.sh --all`, postgres run on a Docker host) +**Approval:** validated 2026-10-14 by the 18.1-06 gate run (`PHASE181_REQUIRE_POSTGRES=1 bash scripts/check-phase18.1.sh --all`, postgres run on a Docker host); revalidated 2026-10-16 for the gap closure (plans 07 to 09) by the 18.1-09 gate run, the same command on a Docker host, every stage PASS diff --git a/.planning/phases/18.1-payment-gateway-subscriptions/deferred-items.md b/.planning/phases/18.1-payment-gateway-subscriptions/deferred-items.md index af4c18e..f253f4a 100644 --- a/.planning/phases/18.1-payment-gateway-subscriptions/deferred-items.md +++ b/.planning/phases/18.1-payment-gateway-subscriptions/deferred-items.md @@ -10,6 +10,7 @@ **Fix:** copy `Flat` into the localized `ListReorder` and add the assertion to `reorder_flat_test.go`'s HTTP path; a framework change for plan 18.1-06 or a quick task (out of scope for the plugin repository plan 04 commits to). - The layer's `scripts/check-git-install.sh` targets `vue-journal-layer` status: open + **Plan 18.1-09:** still open. The layer's v0.2.1 release (plan 18.1-09 Task 5) has the same limit: an install check of `v0.2.1` needs this script fixed first. **Plan 18.1-06:** left open. Neither the phase gate (`scripts/check-phase18.1.sh`) nor the plan's release task runs it, so the release does not depend on it. It is listed on the final push sheet: after a `v0.2.0` tag, an install check of the tag needs this script fixed first (a quick task against the layer). **Found during:** plan 18.1-05 Task 3 (release sheet). **What:** `../vue-paymentgateway-layer/scripts/check-git-install.sh`, shipped since v0.1.0 and run by `pnpm run check:git-install `, was copied from the journal layer. `REPO_URL`, the scratch app's dependency name, `extends` and the messages (12 "journal" references) point at `vue-journal-layer`, and it expects `testing/expect-acme.txt`, which the payment gateway layer does not have. Running `pnpm run check:git-install v0.2.0` would check the wrong repository. @@ -19,4 +20,25 @@ **Resolved by:** plan 18.1-06. `scripts/check-phase18.1.sh layers` (and `--all`) ran the layer suite green from a clean shell with the lockfile unchanged. The gate never installs: if pnpm aborts with `ERR_PNPM_ABORTED_REMOVE_MODULES_DIR_NO_TTY`, the layer stage fails and prints the offline, frozen-lockfile recovery command. **Found during:** plan 18.1-05 Task 2 verify. **What:** the layer's `node_modules` was installed against the store `/media/nvme/dev/golem15/summercms.io/summercms/.pnpm-store/v11`, but pnpm 11.3.0 now resolves `/media/nvme/.pnpm-store/v11`. After any `package.json` change, `pnpm test` runs `pnpm install` first and aborts without a TTY (`ERR_PNPM_ABORTED_REMOVE_MODULES_DIR_NO_TTY`). Plan 05 fixed the local state with `pnpm install --frozen-lockfile --offline --store-dir /media/nvme/dev/golem15/summercms.io/summercms/.pnpm-store`, which left the lockfile unchanged and added no package. + **Seen again:** plan 18.1-09 Task 4, after the `package.json` 0.2.1 bump; the same offline, frozen-lockfile install fixed it (lockfile sha256 unchanged, no package added) and the layers stage then passed. **Fix:** plan 18.1-06's `scripts/check-phase18.1.sh` (and any other checkout) may hit the same thing. Either pin `store-dir` in the layer's `pnpm-workspace.yaml`, or run the frozen offline install before the layer stage. Close this item once the check script runs the layer suite green from a clean shell. +- Review findings outside the gap closure + status: open + **Source:** `18.1-REVIEW.md` (the gap closure, plans 18.1-07 to 18.1-09, fixed CR-01, CR-02, WR-01 to WR-04, WR-06 and WR-07 only). + **Recorded by:** plan 18.1-09 Task 4. + **Open findings:** + - WR-05: the grace period counts from the already advanced period end, so a past_due subscription keeps access for about a full unpaid period plus `grace_period_days`. Kept PHP-identical per the user's scope for the gap closure. + - IN-01: the per-user throttle key of the customer API never applies. + - IN-02: the `metadata_app` filter lets untagged events from other applications through. + - IN-03: the admin sync result shows the raw Stripe error text. + - IN-04: the plans table decides "free" differently from the server. + - IN-05: TaglistField leaves a typed separator in the input when the draft is empty. + - IN-06: the taglist required message is not localized; `customTags: false` without options is accepted at boot. + - IN-07: test-mode and live-mode Stripe customer ids share `users.stripe_customer_id`. + - IN-08: repository hygiene of the new plugin module (among others, sm-paymentgateway-plugin has no tag, so consumers keep its `replace`). + **Fix:** a later phase or quick tasks, one per finding. +- Pre-existing gofmt drift in `modules/cabana` token files + status: open + **Found during:** plan 18.1-09 Task 3. + **What:** `gofmt -l modules/cabana` lists `account_tokens.go`, `account_tokens_test.go`, `token_postgres_smoke_test.go`, `token_routes_test.go` and `token_scopes_test.go`. They are committed and outside this phase; `go vet` and the tests pass. + **Fix:** a `gofmt -w` of those files in the session that owns them.