From f64ae79e1dfe8d4a67071529bf43ae63acfb9f93 Mon Sep 17 00:00:00 2001 From: Jakub Zych Date: Fri, 16 Oct 2026 13:53:57 +0200 Subject: [PATCH] docs(18.1-09): complete gap-closure tests, phase gate and release plan --- .../18.1-09-SUMMARY.md | 316 ++++++++++++++++++ 1 file changed, 316 insertions(+) create mode 100644 .planning/phases/18.1-payment-gateway-subscriptions/18.1-09-SUMMARY.md diff --git a/.planning/phases/18.1-payment-gateway-subscriptions/18.1-09-SUMMARY.md b/.planning/phases/18.1-payment-gateway-subscriptions/18.1-09-SUMMARY.md new file mode 100644 index 0000000..0b858ee --- /dev/null +++ b/.planning/phases/18.1-payment-gateway-subscriptions/18.1-09-SUMMARY.md @@ -0,0 +1,316 @@ +--- +phase: 18.1-payment-gateway-subscriptions +plan: 09 +subsystem: payments +tags: [stripe, subscriptions, testing, postgres, testcontainers, phase-gate, bfcache, release, gap-closure] +status: complete + +requires: + - phase: 18.1-payment-gateway-subscriptions + provides: "plan 07 (CR-01 fresh-state webhook job, WithSessionLock, SubscriptionRetriever, plan resolution, WR-06 sync), plan 08 (CR-02 strict price, WR-02/WR-03 idempotency, guard and lock, WR-01 Detached bulk action, WR-07 page restore), plan 06 (scripts/check-phase18.1.sh, release order)" +provides: + - "Named tests for every gap-closure fix of plans 07 and 08 in four repositories (framework cabana, pgsub, pgstripe, layer)" + - "scripts/check-phase18.1.sh requires the new names per stage and package; layers minimum raised to 43" + - "18.1-VALIDATION.md rows for every task of plans 07 to 09 (validated, nyquist_compliant)" + - "Layer package.json 0.2.1" + - "Release per the reply push-all-with-plugin-tags: framework v0.2.1, pgsub v0.1.0, pgstripe v0.1.0, layer v0.2.1" +affects: [18.1-verify-work, pgsub-consumers, pgstripe-consumers, layer-consumers, cabana-consumers] + +actuals: + tokens: 25537 # chars/4 over the added lines of the realized diff (4 repos, tasks 1-4) + tasks: 5 + commits: 12 # MEASURED per repo before this SUMMARY commit: summercms.go 7 + pgsub 1 + pgstripe 2 + layer 2 +commits: 12 +plan_head_before: "summercms.go f82ac0f2f5c322db3913f022d83fc45088b29a77; pgsub b6738d3f0246e586268caeb219e53ff6991790b2; pgstripe c9bd95308531320ea879a41e8e00da6a7b670d7b; layer 34cf926c4558446cd96c74fd94306b7727eb50ec" +plan_head_after: "summercms.go 03b8cc2; pgsub 2e21084; pgstripe cc80ea7; layer 22368c5 (before this SUMMARY commit and the release commits)" + +tech-stack: + added: [] + patterns: + - "Stripe fake failure switches (setFailRetrieve) and recorded request headers for idempotency assertions" + - "Open-transaction probe in a fake gateway hook (pg_stat_activity idle in transaction) to prove a provider call runs outside a transaction" + - "Row-lock probe from a second connection (SELECT ... FOR UPDATE NOWAIT) to prove a detached bulk action holds no lock" + +key-files: + created: + - modules/cabana/bulk_detached_test.go + - ../sm-pgstripe-plugin/subscriptions/ordering_test.go + - ../sm-pgstripe-plugin/subscriptions/gateway_hardening_test.go + - ../sm-paymentgatewaysubscriptions-plugin/models/checkout_price_test.go + - ../sm-paymentgatewaysubscriptions-plugin/plan_resolution_test.go + - ../sm-paymentgatewaysubscriptions-plugin/sync_subscription_test.go + - ../sm-paymentgatewaysubscriptions-plugin/checkout_guard_test.go + - ../sm-paymentgatewaysubscriptions-plugin/admin_sync_detached_test.go + modified: + - scripts/check-phase18.1.sh + - scripts/check-phase10.sh + - modules/cabana/README.md + - ../sm-pgstripe-plugin/subscriptions/stripefake_test.go + - ../sm-pgstripe-plugin/subscriptions/decode_test.go + - ../sm-pgstripe-plugin/subscriptions/README.md + - ../sm-paymentgatewaysubscriptions-plugin/fixture_plugin_test.go + - ../sm-paymentgatewaysubscriptions-plugin/README.md + - ../vue-paymentgateway-layer/tests/subscribe.test.ts + - ../vue-paymentgateway-layer/tests/subscriptionComponents.test.ts + - ../vue-paymentgateway-layer/package.json + - ../vue-paymentgateway-layer/README.md + - .planning/phases/18.1-payment-gateway-subscriptions/18.1-VALIDATION.md + - .planning/phases/18.1-payment-gateway-subscriptions/deferred-items.md + +key-decisions: + - "The framework release tag is v0.2.1, not the v0.1.27 the plan proposed: v0.1.27 and v0.2.0 already exist on origin (other sessions' releases), and tags are immovable" + - "First plugin tags v0.1.0 on pgsub and pgstripe; pgstripe requires pgsub v0.1.0; replace lines stay (sm-paymentgateway-plugin is untagged, IN-08)" + - "TestDetachedBulkAction is named outside ^TestBulkAction so plan 08's count of 13 stays meaningful" + +patterns-established: + - "A gap closure's last plan names one test per review finding and the phase gate requires each by stage and package" + +requirements-completed: [SC-5, D-22, CR-01, CR-02, WR-01, WR-02, WR-03, WR-04, WR-06, WR-07] + +coverage: + - id: D1 + description: "CR-01: the three review sequences (checkout race in either order and concurrent, deleted before created, stale cancel flag) plus retrieval failure retried and no Stripe call for duplicates or other apps" + requirement: CR-01 + verification: + - kind: integration + ref: "sm-pgstripe-plugin/subscriptions/ordering_test.go#TestEventOrdering" + status: pass + - kind: integration + ref: "sm-paymentgatewaysubscriptions-plugin/plan_resolution_test.go#TestApplyDeletedNotStored" + status: pass + human_judgment: false + - id: D2 + description: "CR-02: strict per-currency checkout price (models, gateway, API 422 before any gateway call)" + requirement: CR-02 + verification: + - kind: unit + ref: "sm-paymentgatewaysubscriptions-plugin/models/checkout_price_test.go#TestCheckoutPriceID" + status: pass + - kind: integration + ref: "sm-pgstripe-plugin/subscriptions/gateway_hardening_test.go#TestGatewayStrictPrice" + status: pass + - kind: integration + ref: "sm-paymentgatewaysubscriptions-plugin/checkout_guard_test.go#TestCustomerAPIGuards" + status: pass + human_judgment: false + - id: D3 + description: "WR-02/WR-03: first-write-wins customer id, guarded checkout (open statuses refused, concurrent checkouts of one subscriber never overlap at the gateway), idempotency keys" + requirement: WR-03 + verification: + - kind: integration + ref: "sm-paymentgatewaysubscriptions-plugin/checkout_guard_test.go#TestCheckoutGuarded" + status: pass + - kind: integration + ref: "sm-paymentgatewaysubscriptions-plugin/checkout_guard_test.go#TestCustomerIDFirstWriteWins" + status: pass + - kind: integration + ref: "sm-pgstripe-plugin/subscriptions/gateway_hardening_test.go#TestGatewayIdempotencyKeys" + status: pass + - kind: unit + ref: "sm-paymentgatewaysubscriptions-plugin/models/checkout_price_test.go#TestTerminalStatuses" + status: pass + human_judgment: false + - id: D4 + description: "WR-01: detached admin bulk actions (framework) and the plan sync actions run without a transaction or row lock across gateway calls" + requirement: WR-01 + verification: + - kind: integration + ref: "summercms.go/modules/cabana/bulk_detached_test.go#TestDetachedBulkAction" + status: pass + - kind: integration + ref: "sm-paymentgatewaysubscriptions-plugin/admin_sync_detached_test.go#TestAdminSyncDetached" + status: pass + human_judgment: false + - id: D5 + description: "WR-04/WR-06: plan resolution by price, stripe_prices, product and plan_code; fresh sync writes gateway-owned columns only under the lock" + requirement: WR-06 + verification: + - kind: integration + ref: "sm-paymentgatewaysubscriptions-plugin/plan_resolution_test.go#TestPlanResolution" + status: pass + - kind: integration + ref: "sm-paymentgatewaysubscriptions-plugin/sync_subscription_test.go#TestSyncSubscriptionFresh" + status: pass + - kind: integration + ref: "sm-pgstripe-plugin/subscriptions/gateway_hardening_test.go#TestRetrieveSubscription" + status: pass + - kind: unit + ref: "sm-pgstripe-plugin/subscriptions/decode_test.go#TestDecodeSubscription" + status: pass + human_judgment: false + - id: D6 + description: "WR-07: back/forward-cache restore of the subscribe and manage flows, guarded navigation, component wiring, the already-subscribed copy" + requirement: WR-07 + verification: + - kind: unit + ref: "vue-paymentgateway-layer/tests/subscribe.test.ts#subscriptions: a persisted pageshow restores the subscribe flow after a redirect" + status: pass + - kind: unit + ref: "vue-paymentgateway-layer/tests/subscriptionComponents.test.ts#subscriptions: the three components wire watchPageRestore on mount and remove it on unmount" + status: pass + human_judgment: false + - id: D7 + description: "Live Stripe test-mode checkout (created and updated in either order), portal cancel_at, and the browser pass including Back from Checkout" + verification: [] + human_judgment: true + rationale: "Manual-only rows kept in 18.1-VALIDATION.md; they need a real Stripe test account and a browser" + +duration: "plan start 2026-10-16T10:55:35Z (tasks 1-4 by the first executor, Task 5 by a continuation executor)" +completed: 2026-10-16 +--- + +# Phase 18.1 Plan 09: Gap-closure tests, phase gate and release Summary + +**Named Postgres, Docker-free and TAP tests pin every fix of plans 07 and 08 (CR-01 ordering, CR-02 strict price, WR-01 detached bulk actions, WR-02/03 guard and idempotency, WR-04/06 plan resolution and fresh sync, WR-07 page restore). The phase gate requires each test by name, and the gap closure was released as framework v0.2.1, pgsub v0.1.0, pgstripe v0.1.0 and layer v0.2.1.** + +## Commits per repository (tasks 1 to 4) + +| Task | summercms.go | pgsub | pgstripe | layer | +|------|--------------|-------|----------|-------| +| 1 CR-01 ordering suite (tracer) | `0fc909d` gate | | `768447b` | | +| 2 pgsub coverage | `5a446d6` gate | `2e21084` | | | +| 3 pgstripe and framework coverage | `fa91e2a` cabana test, `b6e6736` gate | | `cc80ea7` | | +| 4 layer coverage, 0.2.1, gate, VALIDATION | `3cb2252` gate, `48e2642` check-phase10 fix, `03b8cc2` VALIDATION and deferred items | | | `c535975` specs, `22368c5` 0.2.1 | + +The tracer gate after Task 1 re-ran the postgres stage end to end and it passed. A mutation check (applying the event snapshot again) turned 5 of the 7 `TestEventOrdering` subtests red. + +## Tests added + +**Framework (cabana)** +- `TestDetachedBulkAction` (Postgres): + - a detached Run sees no transaction (`TxFromContext` false); + - another connection's `SELECT ... FOR UPDATE NOWAIT` on a selected row succeeds during Run; + - the empty, absent and partial-selection rules (affected 0 without Run, 409); + - a `ValidationError` answers 422, and a row written before it stays written; + - the same action without `Detached` still holds the transaction and the lock. + +**pgsub** +- Docker-free: `TestCheckoutPriceID` (CR-02), `TestTerminalStatuses` (WR-03). +- Postgres: + - `TestPlanResolution` (WR-04): price, stripe_prices, product, plan_code (inactive plan included), the unresolved error, an existing row keeps its plan; + - `TestApplyDeletedNotStored` (CR-01): the sentinel, nil row, no event; + - `TestSyncSubscriptionFresh` (WR-06): retriever and legacy paths, gateway-owned columns only, no events, waits for a held `SubscriptionLockKey`; + - `TestCheckoutGuarded` (WR-03): the six open statuses refused without a gateway call, canceled and incomplete_expired allowed, concurrent checkouts of one subscriber never overlap, two subscribers in parallel, one customer; + - `TestCustomerIDFirstWriteWins` (WR-02); + - `TestCustomerAPIGuards` (WR-03, CR-02): 409 for past_due, unpaid, incomplete and paused with the check order unchanged, 422 for a currency without a price; + - `TestAdminSyncDetached` (WR-01): no gateway call while the session is idle in transaction, a failed save is a failure line, ids saved after the request context was cancelled. + +**pgstripe** +- `TestEventOrdering` (Postgres, CR-01). Subtests: + - `checkout_race_updated_first` + - `checkout_race_concurrent` + - `deleted_before_created` + - `stale_cancel_flag_no_reactivated` + - `retrieve_error_retries` + - `duplicate_makes_no_stripe_call` + - `other_app_makes_no_stripe_call` +- Gateway tests (Postgres, for the settings): + - `TestGatewayStrictPrice` (CR-02); + - `TestGatewayIdempotencyKeys` (WR-02, WR-03); + - `TestRetrieveSubscription` (CR-01, WR-06). +- `TestDecodeSubscription` gained the price's product (string and expanded) and `plan_code`. Job arguments still hold no personal data. +- Fake: `setFailRetrieve(bool)`, and `fakeRequest` now keeps the `Idempotency-Key` header. + +**Layer** +- Seven new `subscriptions:` specs, which raises the count from 36 to 43: + - a persisted pageshow restores the subscribe flow; + - a non-persisted pageshow changes nothing; + - `watchPageRestore`'s remover and its server no-op; + - a throwing navigation re-enables the button; + - the manage flow restores; + - the components' mount and unmount wiring; + - the already-subscribed copy in en and pl. +- `package.json` is 0.2.1. `pnpm-lock.yaml` is unchanged. + +## Gate result + +The Task 5 sheet run of `PHASE181_REQUIRE_POSTGRES=1 bash scripts/check-phase18.1.sh --all` (clean trees, then `--all`) printed `final-push-sheet-ok`: + +| Stage | Result | +|-------|--------| +| self-test | PASS | +| go | 5 required framework tests | +| plugins | pgsub models 5, contracts 1, root 5; pgstripe root 4, subscriptions 3; vet and `-short` green | +| admin | vue-tsc; vitest 1429 tests in 93 files | +| layers | 112 TAP tests, 0 failures; 43 named `subscriptions:` (minimum 43) | +| docs | TestDocsTree, docs:build, OpenAPI check, dist check, phase 10 hygiene | +| postgres | framework 4; pgsub 21; pgstripe root 4, subscriptions 12 | + +No suite was NOT RUN. + +## VALIDATION + +`18.1-VALIDATION.md` stays `status: validated`, `nyquist_compliant: true` and `wave_0_complete: true`, revalidated 2026-10-16. It has one row per task of plans 07, 08 and 09. The manual-only rows remain: +- a live test-mode checkout, now also watching created and updated in either order; +- the portal `cancel_at` check; +- the browser pass, now including Back from Checkout. + +## Task 5: release decision + +**Reply (verbatim):** "push-all-with-plugin-tags, v0.2.1" + +The plan proposed framework tag `v0.1.27`. Since the plan was written, `v0.1.27` and `v0.2.0` were created on origin by other sessions, so the framework tag is `v0.2.1`. Tags are immovable, so the existing tags are left untouched. The plugin requires are raised to `v0.2.1` accordingly. + +**Push sheet.** Every repository was 0 commits behind its origin after `git fetch origin --tags`. Unpushed commits: +- summercms.go master: 27 ahead before this SUMMARY. + - This phase's commits: plans 07 to 09. + - Other sessions' committed work, approved for this push: quick-261016-0fm (admin theme and locale preferences, the dist rebuild for v0.2.1), quick-261015-w7f (docs since v0.1.14, deployed on v0.2.0), the fast task "summercms.io Alpha 0.2 copy", and `9921768` docs(19.3-05). +- pgsub main: 7 ahead (plans 07 to 09). +- pgstripe main: 8 ahead (plans 07 to 09). +- layer main: 3 ahead (plans 08 and 09). + +**Order** (plan 06's order, with plugin tags): +1. framework master, then annotated `v0.2.1` on this SUMMARY's commit; +2. pgsub: require bump to summercms `v0.2.1` (its own commit), main, annotated `v0.1.0`; +3. pgstripe: require bump to summercms `v0.2.1` and pgsub `v0.1.0` (its own commit), main, annotated `v0.1.0`; +4. layer: main, annotated `v0.2.1` on `22368c5`. + +Replace lines stay in both plugins. Nothing is force-pushed. The pushed hashes and tag objects are recorded in the release record below, which is added after the pushes. + +**Open item:** the layer's `scripts/check-git-install.sh` still targets `vue-journal-layer`, so an install check of layer `v0.2.1` needs that script fixed first. See `deferred-items.md`. + +## Decisions Made + +- Release per the reply `push-all-with-plugin-tags, v0.2.1`. The framework tag is `v0.2.1` instead of `v0.1.27`, because `v0.1.27` and `v0.2.0` already exist on origin. +- New test files per finding group, so gate names map one to one to review findings. +- The layer component specs render on the server, so the mount and unmount wiring is asserted from the component sources. + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 3 - Blocking] The phase 10 hygiene allow-list missed the admin theme and locale cache** +- **Found during:** Task 4 (`check-phase18.1.sh docs` failed). +- **Issue:** `usePreferences.ts` from quick task 261016-0fm caches the theme and locale codes in localStorage. That is an accepted threat of that task (T-261016-0fm-08), but the `check-phase10.sh` hygiene allow-list was not updated, so the docs stage failed. Tokens stay forbidden. +- **Fix:** the allow-list now accepts the two preference keys. +- **Committed in:** `48e2642`. + +### Adaptations + +1. **The fixture `fakeGateway` gained an `onSync` hook** (pgsub `fixture_plugin_test.go`). `TestAdminSyncDetached` uses it as the open-transaction probe at the moment of the gateway call. +2. **`TestDetachedBulkAction` uses test-only actions on the existing roster fixture**, through its wrap hook. No production fixture changed, and the test names no application or provider. +3. **pnpm store-dir mismatch after the 0.2.1 bump.** The layer's `pnpm test` aborted without a TTY (`ERR_PNPM_ABORTED_REMOVE_MODULES_DIR_NO_TTY`). `pnpm install --frozen-lockfile --offline --store-dir /media/nvme/dev/golem15/summercms.io/summercms/.pnpm-store` fixed it with the lockfile sha256 unchanged and no package added. It is recorded in `deferred-items.md`. +4. **Pre-existing gofmt drift in `modules/cabana` token files.** These are committed files from another session and are not touched here. They are logged in `deferred-items.md`; vet and the tests pass. +5. **Framework tag `v0.2.1` instead of `v0.1.27`**, per the human's reply (see Task 5). +6. **Most coverage tasks had no failing test first.** They test behaviour plans 07 and 08 already shipped, and no production bug surfaced, so no `fix(18.1-09)` commit exists. The plan is `type: execute`, so the plan-level TDD gate does not apply. + +**Total deviations:** 1 auto-fixed (Rule 3) and 6 adaptations. +**Impact on plan:** test-side plumbing and one gate script allow-list. No scope or dependency was added. + +## Deferred items + +| Item | Status | +|------|--------| +| Layer `scripts/check-git-install.sh` targets `vue-journal-layer` | open (blocks an install check of layer v0.2.1) | +| Review findings outside the gap closure (WR-05, IN-01 to IN-08) | open | +| gofmt drift in `modules/cabana` token files | open (another session's files) | +| pnpm store-dir mismatch in the layer | resolved for the gate; seen again and fixed locally | + +## Known Stubs + +None. This plan adds tests, gate entries and a version string only. + +## Self-Check: PASSED + +- All created test files exist in their repositories (`bulk_detached_test.go`, `ordering_test.go`, `gateway_hardening_test.go`, `checkout_price_test.go`, `plan_resolution_test.go`, `sync_subscription_test.go`, `checkout_guard_test.go`, `admin_sync_detached_test.go`). +- The commits `0fc909d`, `5a446d6`, `fa91e2a`, `b6e6736`, `3cb2252`, `48e2642`, `03b8cc2`, `2e21084`, `768447b`, `cc80ea7`, `c535975` and `22368c5` exist in their repositories.