From 3bb6d61843aa88cb54f377db640c4a9385a9dd69 Mon Sep 17 00:00:00 2001 From: Jakub Zych Date: Thu, 15 Oct 2026 22:40:30 +0200 Subject: [PATCH] docs(19.3-06): progress record after Task 4 --- .../19.3-06-PROGRESS.md | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 .planning/phases/19.3-framework-transport-and-infrastructure-for-wavepath/19.3-06-PROGRESS.md diff --git a/.planning/phases/19.3-framework-transport-and-infrastructure-for-wavepath/19.3-06-PROGRESS.md b/.planning/phases/19.3-framework-transport-and-infrastructure-for-wavepath/19.3-06-PROGRESS.md new file mode 100644 index 0000000..10ee3da --- /dev/null +++ b/.planning/phases/19.3-framework-transport-and-infrastructure-for-wavepath/19.3-06-PROGRESS.md @@ -0,0 +1,69 @@ +# 19.3-06 progress record after Task 4 + +Mid-plan record of plan 19.3-06 (gap closure CR-01, WR-01..WR-09), superseded by 19.3-06-SUMMARY.md once that file exists. It is not a SUMMARY: Tasks 5-7 are still to run. + +next: Task 5 + +Recorded: 2026-10-15. Plan start: 2026-10-15T20:20:38Z. Ledger base (summercms.go HEAD before the plan): e9ad640. + +## Completed Tasks + +| Task | Name | Commit (repository, hash, subject) | Key files | +|------|------|------------------------------------|-----------| +| 1 | Tracer: cors:<profile> headers on every refusal (CR-01) | summercms.go 0723536 `fix(19.3-06): cors: headers on every response of the route (CR-01)` | modules/surf/router.go, modules/surf/cors.go, modules/surf/cors_profile_test.go, modules/surf/README.md, docs/services/routing.md, docs/architecture/request-lifecycle.md, scripts/check-phase19.3.sh | +| 2 | Credentialed CORS origin rules (WR-04) | summercms.go e32940d `fix(19.3-06): credentialed CORS refuses wildcard-host and unanchored origins (WR-04)` | modules/surf/cors.go, modules/surf/cors_profile_test.go, modules/surf/README.md, docs/services/routing.md | +| 2 | Production cookie Secure rule (WR-05) | summercms.go 4dae833 `fix(19.3-06): production refuses an inferred insecure token cookie (WR-05)` | modules/bouncer/cookie.go, modules/bouncer/cookie_test.go, modules/bouncer/README.md, docs/services/authentication.md | +| 2 | Zero CookieConfig defaults (WR-09) | summercms.go 85d8b6a `fix(19.3-06): zero CookieConfig writes SameSite=Lax and Secure (WR-09)` | modules/bouncer/cookie.go, modules/bouncer/cookie_test.go, modules/bouncer/README.md, docs/services/authentication.md | +| 3 | Nonce-only api-docs CSP (WR-06) | summercms.go f705343 `fix(19.3-06): api-docs CSP allows only nonce-carrying scripts (WR-06)` | modules/surf/apidocs.go, modules/surf/apidocs_test.go, modules/surf/README.md, docs/services/routing.md | +| 3 | Protected uploads refused with public_url (WR-07) | summercms.go caa5b5d `fix(19.3-06): refuse protected uploads into a public_url bucket (WR-07)` | modules/lagoon/attach/store.go, modules/lagoon/attach/bucket.go, modules/lagoon/attach/url_test.go, modules/lagoon/README.md, docs/services/storage.md | +| 3 | Turnstile boot warning (WR-08) | summercms.go d0a580c `fix(19.3-06): warn at boot when turnstile routes have no secret (WR-08)` | modules/surf/router.go, modules/surf/cors_profile_test.go, modules/surf/README.md, docs/services/authentication.md | +| 4 | Purge without redirects (WR-02) | summercms.go 50a584c `fix(19.3-06): site-cache purge never follows redirects (WR-02)` | modules/cooler/sitecache.go, modules/cooler/sitecache_test.go, modules/cooler/README.md, docs/services/caching.md | +| 4 | Queued, coalesced purges and Wait (WR-01) | summercms.go db6a7f5 `fix(19.3-06): queue site-cache purges after commit and coalesce them (WR-01)` | modules/cooler/sitecache.go, modules/cooler/cooler.go, modules/cooler/sitecache_test.go, modules/cooler/example_test.go, modules/cooler/README.md, docs/services/caching.md | +| 4 | FAQ plugin test waits (WR-01) | sm-faq-plugin e52b286 `test(19.3-06): wait for the queued site-cache purge before asserting (WR-01)` | site_cache_admin_test.go | +| 4 | Menu plugin test waits (WR-01) | sm-menu-plugin 4b42978 `test(19.3-06): wait for the queued site-cache purge before asserting (WR-01)` | site_cache_admin_test.go | +| 4 | Journal plugin test waits (WR-01) | sm-journal-plugin 4f633f0 `test(19.3-06): wait for the queued site-cache purge before asserting (WR-01)` | site_cache_admin_test.go, classes/site_cache_test.go | + +## Heads after Task 4 + +| Repository | HEAD | +|------------|------| +| summercms.go | db6a7f5 (this record's commit follows it) | +| sm-faq-plugin | e52b286 (planning head fa90e83) | +| sm-menu-plugin | 4b42978 (planning head fe27b9a) | +| sm-journal-plugin | 4f633f0 (planning head a99e9a1) | +| vue-faq-layer | 96b6bc0 (planning-time head, v0.2.0) | +| vue-menu-layer | dd0ee35 (planning-time head, v0.2.0) | +| vue-journal-layer | 5936f66 (planning-time head, v0.4.0) | + +Nothing pushed or tagged in any repository. + +## Verify results + +- Task 1: PASS. `go vet ./...`, surf short suite, the five named CORS tests (5 top-level PASS), guard_403, turnstile_422 and turnstile_503 (3 subtest PASS), `check-phase19.3.sh go` passed, TestDocsTree ok. Both new tests failed against the old router.go (checked by restoring it locally, then the new file back). Tracer gate: automated verify re-run green, expansion continued. +- Task 2: PASS. Surf and bouncer short suites, 7 top-level PASS, TestDocsTree ok. +- Task 3: PASS. Vet in both tag modes, surf and lagoon short suites, 6 top-level PASS, TestDocsTree ok. +- Task 4: PASS. `go test -short -race ./modules/cooler` ok, 5 top-level PASS, `check-phase19.3.sh plugins` passed, `check-phase19.3.sh docker` RUN and passed (Docker answered for user jin; framework cooler, cabana, attach suites and the FAQ, menu and Journal admin purge suites). + +## Notes for the SUMMARY + +Deviations: +- [Rule 2 - docs] Task 1 also updated docs/architecture/request-lifecycle.md ("The wrapping order" list gains the CORS profile step), since the list would otherwise be wrong after the change. Same commit as CR-01. +- [Rule 1 - test] Task 2 (WR-05): TestLoadCookieConfigDefaults had production rows with an http and an empty app.url expecting Secure false without error; they now run in development (the production case is an error by design and is covered in TestLoadCookieConfigErrors). +- [Rule 1 - test] Task 4 (WR-01): ExampleSiteCacheFromApp printed the purge from the receiver synchronously; it now calls `cooler.SiteCacheFromApp(app).Wait` after the save, and the `purge` doc comment of the example controller says the purge is queued. docs/services/caching.md's src= copy was refreshed with `summer docs:sync`. +- [Rule 1 - test] Task 4: "a slow receiver is abandoned and the save stays committed" now asserts the save returns within 500 ms (it no longer waits for the 2 s purge) and waits for the queue before reading the log. + +Decisions taken while executing: +- CR-01: `Router.wrap` resolves the profile through `routeCORSProfile` (two profiles stay a boot error), skips the `cors:` entry in its loop after the unknown-profile check, and wraps the recovered handler in `profileMiddleware` just inside `recordPattern`. `bufferedResponse.commit` merges each comma-separated `Vary` value through `addVary`. +- WR-04: anchoring is checked on the parsed expression (`regexp/syntax`): every alternative must begin with `^` and end with `$`, so `^a|b$` is refused as unanchored. Wildcard entries must be `://*.` with one `*`. Probe origins: https://cors-probe.invalid, http://cors-probe.invalid, https://cors-probe.com, null. +- WR-05: the explicit-false message stays; the inferred case says "auth.cookie.secure resolves to false in the production environment" and names app.url. +- WR-09: the zero-config Secure is decided before Name and Path are filled; any unset SameSite becomes Lax. +- WR-07: the check sits in `attach.Store` right after the argument checks, before the extension check and before the body is read. +- WR-08: the warning goes through `accessLogger(app)` (the published `*slog.Logger`, else `slog.Default`) with attributes `routes` and `production`. +- WR-01: `installSiteCache` returns the purger in effect, also one a plugin published before Install (the Journal alias keys), so `Handle.Close` drains whichever purger the app uses. Queued sends use a background context bounded by `SiteCachePurgeTimeout`. Log lines add `coalesced` only when above zero. `Purge` tolerates a nil client field (falls back to a no-redirect client). +- Plugin tests: `purgeReceiver` gains a `drain` hook set by `drainFrom(t, app)`; `take` (and so `expectOnePurge` and `expectNone`) waits on `cooler.SiteCacheFromApp(app).Wait` with a 5 s context first. In TestCategoriesAdminFrameworkKeysWin both receivers drain the app's purger. + +What the admin shows for a refused protected upload (Task 3, WR-07): cabana's `writeFileError` has no branch for `attach.ErrProtectedNeedsPrivateBucket`, so it reaches the default path: `logFileFailure` logs the error (its message names storage.uploads.public_url) and the client gets the generic opaque 500 error body. cabana is unchanged, as planned. + +Acceptance notes: +- The `git log --oneline --grep='(WR-0N)$'` checks of Tasks 2 and 3 print 4 and 5 lines rather than 3: commits of earlier phases (02) also end with `(WR-04)` and `(WR-07)`. The three 19.3-06 commits per task are present. +- `grep -n 'cooler.SiteCachePurger.Wait' modules/cooler/README.md` prints two lines (the Features bullet and the API row); Task 5's cooler README edit rewords the bullet so only the API row names it.