From 6f9f2e20a42996474165747c090e87ca29a7bfa1 Mon Sep 17 00:00:00 2001 From: Jakub Zych Date: Thu, 15 Oct 2026 19:17:00 +0200 Subject: [PATCH] docs(19.3-04): complete framework site-cache purge plan - cooler.SiteCachePurger, pact.ReorderAfterSave, FAQ/menu/Journal adoption, shared Nuxt purge route - Task 4 reply push-all: plugins, layers and summercms master pushed; layer tags v0.2.0, v0.2.0, v0.4.0 --- .../19.3-04-SUMMARY.md | 227 ++++++++++++++++++ .../deferred-items.md | 6 + 2 files changed, 233 insertions(+) create mode 100644 .planning/phases/19.3-framework-transport-and-infrastructure-for-wavepath/19.3-04-SUMMARY.md diff --git a/.planning/phases/19.3-framework-transport-and-infrastructure-for-wavepath/19.3-04-SUMMARY.md b/.planning/phases/19.3-framework-transport-and-infrastructure-for-wavepath/19.3-04-SUMMARY.md new file mode 100644 index 0000000..ac3e3a4 --- /dev/null +++ b/.planning/phases/19.3-framework-transport-and-infrastructure-for-wavepath/19.3-04-SUMMARY.md @@ -0,0 +1,227 @@ +--- +phase: 19.3-framework-transport-and-infrastructure-for-wavepath +plan: 04 +subsystem: caching +tags: [cooler, site-cache, purge, cabana, reorder, pact, faq, menu, journal, nuxt-layers, nitro] +status: complete + +requires: + - phase: 19.3-01 + provides: "modules/cooler (cache module) that now also holds the site-cache purge service" +provides: + - "cooler.SiteCachePurger with framework keys site_cache.purge_url and site_cache.purge_token, published by cooler.Install; cooler.SiteCacheFromApp; cooler.PurgeSiteCacheAfterCommit" + - "pact.ReorderAfterSave(ctx, partition *uint64) error, called by cabana's reorder inside its transaction" + - "FAQ, menu and menu-entry admin saves, deletes and reorders purge the host site's page cache after commit" + - "Journal purges through cooler; golem15.journal.site_cache.* and classes.SiteCachePurger/NewSiteCachePurger/PurgeSiteCacheAfterCommit kept as aliases for one release" + - "Shared Nitro route POST /api/__site__/cache/purge (runtimeConfig siteCache.purgeToken) in vue-faq-layer v0.2.0, vue-menu-layer v0.2.0 and vue-journal-layer v0.4.0" +affects: [19.3-05, sm-grzybyfunkcjonalne-app deployment, sm-summercmsio-app journal pointer] + +actuals: + tokens: 56800 + tasks: 5 + commits: 2 +plan_head_before: 1d8e50a1ee707b1f844c6fbe47bfe1597d8dc2e2 +plan_head_after: af9dad634cd4ff90bef1cd1a042b18d7dca97e18 + +tech-stack: + added: [] + patterns: + - "Framework-owned after-commit side effect: hooks take the save transaction from cabana.TxFromContext and register through lagoon.AfterCommit; failures are logged and swallowed" + - "Lazy service lookup (SiteCacheFromApp) so commands and tests that do not run serve still get the same purger" + - "Plugin-key alias path: framework keys win; legacy plugin keys publish the framework service with a deprecation warning" + - "Byte-identical shared files across layers, pinned by sha256 in each layer's spec" + +key-files: + created: + - modules/cooler/sitecache.go + - modules/cooler/sitecache_test.go + - ../sm-faq-plugin/controllers/site_cache.go + - ../sm-faq-plugin/site_cache_admin_test.go + - ../sm-menu-plugin/controllers/site_cache.go + - ../sm-menu-plugin/site_cache_admin_test.go + - ../vue-faq-layer/server/lib/siteCachePurge.ts + - ../vue-faq-layer/server/api/__site__/cache/purge.post.ts + - ../vue-faq-layer/tests/sitePurge.test.ts + - ../vue-menu-layer/server/lib/siteCachePurge.ts + - ../vue-menu-layer/server/api/__site__/cache/purge.post.ts + - ../vue-menu-layer/tests/sitePurge.test.ts + - ../vue-journal-layer/server/lib/siteCachePurge.ts + - ../vue-journal-layer/server/api/__site__/cache/purge.post.ts + - ../vue-journal-layer/tests/sitePurge.test.ts + modified: + - modules/cooler/cooler.go + - modules/cooler/example_test.go + - modules/cooler/README.md + - modules/pact/capabilities.go + - modules/pact/README.md + - modules/cabana/reorder.go + - modules/cabana/README.md + - README.md + - docs/services/caching.md + - docs/backend/admin-controllers.md + - docs/backend/lists-and-filters.md + - docs/setup/configuration.md + - ../sm-faq-plugin/controllers/faqs.go + - ../sm-menu-plugin/controllers/admin_registry.go + - ../sm-menu-plugin/controllers/menus.go + - ../sm-menu-plugin/controllers/menuentries.go + - ../sm-journal-plugin/plugin.go + - ../sm-journal-plugin/classes/site_cache.go + - ../sm-journal-plugin/controllers/site_cache.go + - ../sm-grzybyfunkcjonalne-app/go.mod + - ../sm-grzybyfunkcjonalne-app/go.sum + - ../sm-grzybyfunkcjonalne-app/DEPLOY.md + - ../sm-grzybyfunkcjonalne-app/vue-grzybyfunkcjonalne-app/package.json + - ../sm-grzybyfunkcjonalne-app/vue-grzybyfunkcjonalne-app/pnpm-lock.yaml + +key-decisions: + - "Site-cache purge is a cooler service with framework keys site_cache.purge_url and site_cache.purge_token (http only to loopback, token at least 32 characters, 2 s timeout, after commit only, failures never fail a save)" + - "pact.ReorderAfterSave(ctx, partition *uint64) runs inside cabana's reorder transaction; cabana's reorderPost now uses lagoon.Transaction so AfterCommit callbacks registered by the hook fire" + - "Purge kinds: faq (slug), menu (code), menu_entry (id, or partition: on reorder); Journal keeps post and category" + - "Journal alias keys golem15.journal.site_cache.* publish the cooler purger with a deprecation warning when the framework keys are unset; framework keys win" + - "Layer releases are additive minors: vue-faq-layer v0.2.0, vue-menu-layer v0.2.0, vue-journal-layer v0.4.0; plugins get no tag" + - "Task 4 reply push-all: summercms master, three plugin mains and three layer mains pushed, layer tags created; no framework tag (plan 05 owns it)" + +requirements-completed: [SC-4, D-10, D-11, D-19] + +duration: 60min +completed: 2026-10-15 +--- + +# Phase 19.3 Plan 04: Framework site-cache purge Summary + +**The site-cache purge moved from the Journal plugin into cooler (keys `site_cache.purge_url` and `site_cache.purge_token`). Cabana gained a `pact.ReorderAfterSave` hook. FAQ, menu and Journal admin saves, deletes and reorders now purge the page cache after commit, through a shared `POST /api/__site__/cache/purge` route that ships in all three Nuxt layers. The plugins and layers are pushed and tagged, and the app's pointers are bumped locally.** + +## Performance + +- **Duration:** about 60 min (2026-10-15T16:16:40Z to 17:16Z, across two executor runs split at the Task 4 checkpoint) +- **Tasks:** 5 of 5 +- **Files modified:** 14 framework, 4 FAQ, 6 menu, 6 Journal, 6 per layer, 7 app (incl. submodule pointers), 2 vue app + +## Accomplishments + +- `cooler.SiteCachePurger` is now a framework service. It is validated and published at boot, so a bad `site_cache` config fails boot. `SiteCacheFromApp` gives commands and tests the same purger lazily. `PurgeSiteCacheAfterCommit` sends one bearer POST of `{kind, slug}` per committed save. +- `pact.ReorderAfterSave` is called inside cabana's reorder transaction after the new order is written. A hook error rolls the reorder back. Controllers without the hook behave as before. +- FAQ create, update and delete purge, and so do menu create, update and delete and menu-entry create, update, delete and reorder. The existing locale and placement checks still run first. +- Journal moved onto cooler. Its old keys and exported names still work as aliases, with a deprecation warning. +- The shared Nitro route and helper are byte-identical in all three layers, pinned by sha256 in each layer's spec. Each layer has its own `'/api/__site__/**': { cache: false }` rule. vue-journal-layer keeps `/api/__journal__/cache/purge` and `journal.cachePurgeToken` as aliases. +- The plugins, layers and summercms master are pushed, and the layer tags are on origin. sm-grzybyfunkcjonalne-app now runs the adopted plugins and layer versions. Its deployment config is unchanged because the Journal alias path keeps it working. + +## Task Commits + +| Task | Name | Repository: commit | +|------|------|--------------------| +| 1 (tracer) | FAQ save purges the site cache through cooler and the shared Nuxt route | summercms.go 0bc390f, sm-faq-plugin c60ae6e, vue-faq-layer e26f22e | +| 2 | pact.ReorderAfterSave hook; menu plugin purges; vue-menu-layer route | summercms.go af9dad6, sm-menu-plugin 559d96b, vue-menu-layer 3bc7729 | +| 3 | Journal moves onto cooler with alias keys; vue-journal-layer route | sm-journal-plugin d7943fb, vue-journal-layer 3e0b377 | +| 4 | Push decision (checkpoint:decision, blocking-human) | pushes and tags below, no commits | +| 5 | App picks up the purge | sm-grzybyfunkcjonalne-app f9cafe2 (go mod tidy), d604509 (journal/menu/faq pointers), 85939c3 (vue app pointer), 129c0df (DEPLOY.md, deviation); vue-grzybyfunkcjonalne-app 0ee97d0 (layer tags) | + +Framework commits measured from the ledger: `git rev-list --count 1d8e50a..HEAD` = 2. + +## Task 4: push decision + +**Reply (verbatim):** push-all + +Before pushing, I re-ran `git fetch origin --tags` in all seven repositories. None was behind origin. `git ls-remote --tags origin` showed no v0.2.0 on vue-faq-layer or vue-menu-layer and no v0.4.0 on vue-journal-layer, and none of these tags existed locally. The previous run had already printed push-gate-ok (all plugin and layer suites green, Docker tests run). + +Pushed (no force, no framework tag): + +| Repository | Branch | Range pushed | +|------------|--------|--------------| +| summercms.go | master | a9dd565..af9dad6 (also carries plans 19.3-01, -02 and -03 and the phase planning commits) | +| sm-faq-plugin | main | 919cf06..c60ae6e | +| sm-menu-plugin | main | 9c0f833..559d96b | +| sm-journal-plugin | main | 31d901c..d7943fb | +| vue-faq-layer | main | 321bc90..e26f22e | +| vue-menu-layer | main | 2deac88..3bc7729 | +| vue-journal-layer | main | 25e9bfe..3e0b377 | + +Annotated tags created and pushed: + +| Repository | Tag | Tag object | Commit | +|------------|-----|------------|--------| +| vue-faq-layer | v0.2.0 | 8acbde2 | e26f22e | +| vue-menu-layer | v0.2.0 | a6942aa | 3bc7729 | +| vue-journal-layer | v0.4.0 | 9213af5 | 3e0b377 | + +Checked after the push: each plugin's `main` equals `origin/main`, summercms `master` equals `origin/master`, and `ls-remote` prints each tag. The latest framework tag on origin is still v0.1.9 in the tag listing; no new framework tag was made. + +**Not pushed:** sm-grzybyfunkcjonalne-app (4 commits) and vue-grzybyfunkcjonalne-app (1 commit) stay local. Plan 05's release decision covers those pushes. Nothing was deployed. + +## Identifiers and keys added + +- **cooler:** `SiteCachePurger`, `NewSiteCachePurger`, `NewSiteCachePurgerFor`, `SiteCachePurger.Purge`, `SiteCachePurger.Endpoint`, `PurgeSiteCacheAfterCommit`, `SiteCacheFromApp`, `SiteCachePurgeURLKey`, `SiteCachePurgeTokenKey`, `SiteCachePurgeTimeout`, `SiteCachePurgeMinTokenLength` +- **pact:** `ReorderAfterSave` +- **Config keys:** `site_cache.purge_url` (SUMMER_SITE_CACHE__PURGE_URL), `site_cache.purge_token` (SUMMER_SITE_CACHE__PURGE_TOKEN) +- **Layers:** runtimeConfig `siteCache.purgeToken` (NUXT_SITE_CACHE_PURGE_TOKEN); route `POST /api/__site__/cache/purge`; `server/lib/siteCachePurge.ts` exports `MIN_SITE_PURGE_TOKEN_LENGTH`, `SITE_ROUTE_CACHE_BASE`, `authorizeSitePurge`, `purgeSiteRouteCache`, `parseSitePurgeEvent` +- **Purge kinds:** `faq`, `menu`, `menu_entry` (slug `partition:` on reorder), Journal `post` and `category` + +**Shared-file sha256 pins (identical in all three layers):** +- `server/lib/siteCachePurge.ts`: `7c34121ae06cf011b30a1dc5d787a94657b71b8768bed3a200be0f41566001b8` +- `server/api/__site__/cache/purge.post.ts`: `c459012280cdf59551e57f0440fcaeb24566a5ba6e3dccf31133e67a683f1c06` + +## Tests + +- Docker-backed tests **ran** (not skipped): `TestFaqAdminPurgesSiteCache`, `TestMenuAdminPurgesSiteCache` and the Journal admin purge test on Postgres. All suites were green. +- Extra tests: `TestSiteCacheFrameworkKeysWin` (Journal) and `TestSiteCachePurgeConfig` subtests in cooler. +- Task 5: the app passed `go build ./...` and `go test -short ./...`. In the vue app, `pnpm run build` succeeded, `pnpm test` passed 27/27 and `smoke:ssr` passed 38 checks. The built Nitro config carries `"/api/__site__/**": { "cache": false }` and the `/api/__site__/cache/purge` handler. The Task 5 automated verify printed TASK5-VERIFY-OK, and `grep -c '#v0.2.0'` returned 2. + +## Decisions Made + +See key-decisions in the frontmatter. Task 4 reply: push-all. + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 3 - Blocking] cabana reorder runs in lagoon.Transaction** +- **Found during:** Task 2 +- **Issue:** `reorderPost` in `modules/cabana/reorder.go` used a plain GORM Transaction, so `lagoon.AfterCommit` skipped and a purge registered from `ReorderAfterSave` would never be sent. +- **Fix:** It now runs in `lagoon.Transaction`. The existing reorder tests pass. +- **Commit:** summercms.go af9dad6 + +**2. [Docs checker] cooler example and configuration row** +- **Found during:** Task 1 +- **Issue:** The docs checker needed real snippets for caching.md. +- **Fix:** `modules/cooler/example_test.go` gained `ExampleSiteCacheFromApp` and a neutral `articlesController`, and caching.md pulls them via `src=`. A `docs/setup/configuration.md` row was added for the new keys, which was not in files_modified. +- **Commit:** summercms.go 0bc390f + +**3. [Commit order] af9dad6 amended after the menu plugin commit** +- **Found during:** Task 2 +- **Issue:** The acceptance grep wants exactly one `pact.ReorderAfterSave` line in reorder.go, but one comment repeated the name. +- **Fix:** The comment was reworded and af9dad6 amended. This happened before any push. + +**4. [Rule 2 - Docs correctness] sm-grzybyfunkcjonalne-app DEPLOY.md** +- **Found during:** Task 5 +- **Issue:** After the pointer bump, DEPLOY.md still said menu and FAQ edits do not purge the cache. It also named the old `journal: site cache purged` log line, which is now `cooler: site cache purged`. +- **Fix:** Three sentences were updated: which saves purge, the cooler log lines, and the boot-time deprecation warning for the journal keys, which keep working. `scripts/check-deploy.sh` passes. +- **Files modified:** ../sm-grzybyfunkcjonalne-app/DEPLOY.md +- **Commit:** sm-grzybyfunkcjonalne-app 129c0df (separate docs commit after the pointer bumps) + +**5. [Rule 3 - Blocking] pnpm install without a TTY** +- **Found during:** Task 5 +- **Issue:** `pnpm install` aborted with ERR_PNPM_ABORTED_REMOVE_MODULES_DIR_NO_TTY. +- **Fix:** It was re-run with `--config.confirmModulesPurge=false`, which rebuilds the local node_modules only. The lockfile diff touches only the three layer entries. + +## Issues Encountered + +None beyond the deviations. Pre-existing untracked files were left alone: sm-grzybyfunkcjonalne-app `.gsd/` and its nested `sm-grzybyfunkcjonalne-app` entry, and sm-journal-plugin `go.work.sum`. + +## Deferred Items + +Logged in `deferred-items.md`: +- Pre-existing gofmt findings (missing trailing newlines) in `modules/cabana/account_tokens.go`, `account_tokens_test.go`, `token_postgres_smoke_test.go`, `token_routes_test.go`, `token_scopes_test.go` and sm-journal-plugin `classes/blog_editor_test.go`. + +## Follow-ups + +- Move the grzybyfunkcjonalne deployment to `site_cache.purge_url`/`site_cache.purge_token` and `NUXT_SITE_CACHE_PURGE_TOKEN` (route `/api/__site__/cache/purge`) before the Journal alias keys are removed. Until then the binary logs a deprecation warning at boot. +- Bump the Journal pointer in sm-summercmsio-app, which is 45 commits behind main. +- Push sm-grzybyfunkcjonalne-app (f9cafe2, d604509, 85939c3, 129c0df) and vue-grzybyfunkcjonalne-app (0ee97d0) under plan 05's release decision. +- Manual check from 19.3-VALIDATION.md: save a menu entry in a running admin and see the page update without waiting for the SWR window. + +## Next Phase Readiness + +Plan 05 (unit tests and the framework release decision) can start. summercms master on origin already carries the cooler purge service that the pushed plugins import. + +## Self-Check: PASSED diff --git a/.planning/phases/19.3-framework-transport-and-infrastructure-for-wavepath/deferred-items.md b/.planning/phases/19.3-framework-transport-and-infrastructure-for-wavepath/deferred-items.md index e22bd1d..cb0dac8 100644 --- a/.planning/phases/19.3-framework-transport-and-infrastructure-for-wavepath/deferred-items.md +++ b/.planning/phases/19.3-framework-transport-and-infrastructure-for-wavepath/deferred-items.md @@ -6,3 +6,9 @@ Out-of-scope findings logged by executors. Not fixed in the plan that found them - `internal/build/registry.go` is not gofmt-clean (const block alignment, `registryFileName` / `generatedMark`). Pre-existing, untouched by 19.3-03; `go vet` is unaffected. - The 19.3-03 Task 3 acceptance grep `grep -rniE 'wavepath|fonoteka|grzyby' modules/lagoon ...` matches pre-existing test files outside the plan's scope: `modules/lagoon/hidden_marshal_test.go` (comments naming the consumer app), `modules/lagoon/migrations_test.go` (`golem15.fonoteka` as a sample plugin ID) and `modules/lagoon/attach/lifecycle_test.go` (`Golem15\Fonoteka\Models\Album` morph type). CLAUDE.md says framework code never names a consuming application; a follow-up quick task could switch these to neutral names (`acme`). 19.3-03 added no such names. + +## From 19.3-04 + +- Pre-existing gofmt findings (missing trailing newlines), untouched by 19.3-04: `modules/cabana/account_tokens.go`, `modules/cabana/account_tokens_test.go`, `modules/cabana/token_postgres_smoke_test.go`, `modules/cabana/token_routes_test.go`, `modules/cabana/token_scopes_test.go`, and in the Journal plugin `classes/blog_editor_test.go`. `go vet` and the suites are unaffected. +- Deployment follow-up: move the grzybyfunkcjonalne deployment from `golem15.journal.site_cache.*` and `NUXT_JOURNAL_CACHE_PURGE_TOKEN` to `site_cache.purge_url`/`site_cache.purge_token` and `NUXT_SITE_CACHE_PURGE_TOKEN` (route `/api/__site__/cache/purge`) before the Journal alias keys are removed. +- `sm-summercmsio-app` mounts the Journal plugin 45 commits behind main; bump its pointer in a separate follow-up.