Files
summercms/.planning/STATE.md

996 lines
133 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
msd_state_version: "1.0"
milestone: v1.0
current_phase: 19.4.1
current_phase_name: Early public demo on demo.summercms.io
status: Ready for verification — 6/6 plans executed, UAT in progress (19.3-UAT.md)
stopped_at: Phase 19.4.1 context gathered
last_updated: "2026-10-20T01:33:22.399Z"
last_activity: 2026-10-20
last_activity_desc: "Completed quick task 261020-42e: branded HTML error pages for browsers"
state_head: 2b5f0b2c92e0bc38070c7f4a8870f64e2c430bb8
progress:
total_phases: 43
completed_phases: 35
total_plans: 217
completed_plans: 208
gsd_state_version: "1.0"
milestone_name: milestone
---
# Project State
## Project Reference
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 (UAT in progress)
## Current Position
Phase: 19.4.1 (Early public demo on demo.summercms.io) — READY TO EXECUTE
Plan: 6 of 6
Status: Ready for verification — 6/6 plans executed, UAT in progress (19.3-UAT.md)
Last activity: 2026-10-20 - Completed quick task 261020-42e: branded HTML error pages for browsers
Progress: [████████░░] 83%
## Performance Metrics
**Velocity:**
- Total plans completed: 105
- Average duration: 21 min
- Total execution time: 104 min
**By Phase:**
| Phase | Plans | Total | Avg/Plan |
|-------|-------|-------|----------|
| 01 | 4 | - | - |
| 02 | 5 | - | - |
| 03 | 4 | - | - |
| 04 | 4 | - | - |
| 05 | 6 | - | - |
| 06 | 14 | - | - |
| 7 | 8 | - | - |
| 08 | 10 | - | - |
| 10 | 5 | - | - |
| 10.2 | 2 | - | - |
| 14.1 | 2 | - | - |
| 16.2 | 7 | - | - |
| 16.3 | 4 | - | - |
| 14.2 | 4 | - | - |
| 18.1 | 9 | - | - |
| 19 | 7 | - | - |
| 19.1 | 6 | - | - |
| 19.4 | 4 | - | - |
**Recent Trend:**
- Last 5 plans: 02-01 7 min, 02-02 12 min, 02-03 61 min, 02-04 9 min, 02-05 15 min
- Trend: -
*Updated after each plan completion*
| Phase 03 P03-01 | 17 min | 2 tasks | 52 files |
| Phase 03 P03-02 | 5 min | 2 tasks | 8 files |
| Phase 03 P03-03 | 8 min | 2 tasks | 17 files |
| Phase 03 P03-04 | 15 min | 2 tasks | 15 files |
| Phase 04 P01 | 11 min | 3 tasks | 14 files |
| Phase 04 P04-02 | 10 min | 3 tasks | 11 files |
| Phase 04 P04-03 | 6 min | 3 tasks | 15 files |
| Phase 04 P04-04 | 8 min | 3 tasks | 9 files |
| Phase 05 P01 | 16 min | 4 tasks | 37 files |
| Phase 05 P02 | 27 min | 3 tasks | 32 files |
| Phase 05-data-layer-full-fidelity P03 | 15 min | 3 tasks | 28 files |
| Phase 05 P04 | 18 min | 3 tasks | 18 files |
| Phase 05 P05 | 18 min | 3 tasks | 18 files |
| Phase 05 P06 | 23 min | 3 tasks | 12 files |
| Phase 06 P01 | 25 min | 3 tasks | 26 files |
| Phase 06 P02 | 14 min | 3 tasks | 16 files |
| Phase 06 P03 | 20 min | 3 tasks | 24 files |
| Phase 06 P05 | 13 min | 3 tasks | 13 files |
| Phase 06 P06 | 1h 29m | 2 tasks | 3 files |
| Phase 06 P07 | 12 min | 1 tasks | 4 files |
| Phase 06 P08 | 1h 20m | 1 tasks | 3 files |
| Phase 06 P09 | 4 min | 1 tasks | 2 files |
| Phase 06 P10 | 3h 15m | 1 tasks | 2 files |
| Phase 06 P11 | 12h 30m | 1 tasks | 1 files |
| Phase 07 P01 | 12 min | 3 tasks | 20 files |
| Phase 07 P02 | 83m | 3 tasks | 22 files |
| Phase 07 P03 | 95m | 3 tasks | 28 files |
| Phase 07 P04 | 75m | 3 tasks | 16 files |
| Phase 07 P05 | 45 | 3 tasks | 45 files |
| Phase 07 P06 | 40 min | 3 tasks | 8 files |
| Phase 07 P07 | 3h 15m | 3 tasks | 28 files |
| Phase 07-user-plugin-and-authentication P07 | 3h 15m | 3 tasks | 28 files |
| Phase 07-user-plugin-and-authentication P08 | 25min | 3 tasks | 6 files |
| Phase 08 P01 | 25min | 2 tasks | 6 files |
| Phase 08 P02 | 30min | 3 tasks | 14 files |
| Phase 08 P03 | 20min | 2 tasks | 6 files |
| Phase 08 P04 | 15min | 2 tasks | 5 files |
| Phase 08 P05 | 55min | 3 tasks | 12 files |
| Phase 08 P06 | 50min | 3 tasks | 10 files |
| Phase 08 P07 | 35min | 2 tasks | 9 files |
| Phase 08 P08 | 20min | 2 tasks | 4 files |
| Phase 08 P09 | 55min | 3 tasks | 25 files |
| Phase 08 P10 | 55min | 3 tasks | 17 files |
**Per-Plan Metrics:**
| Plan | Duration | Tasks | Files |
|------|----------|-------|-------|
| Phase 09 P01 | 26min | 2 tasks | 26 files |
| Phase 09 P02 | 22 min | 3 tasks | 14 files |
| Phase 09 P03 | 25min | 3 tasks | 10 files |
| Phase 09 P04 | 36min | 3 tasks | 13 files |
| Phase 09 P05 | 25min | 3 tasks | 8 files |
| Phase 09 P06 | 29min | 3 tasks | 15 files |
| Phase 09 P07 | 2h20m | 2 tasks | 9 files |
| Phase 09 P08 | 2h28m | 2 tasks | 4 files |
| Phase 09 P09 | 11h 48m | 2 tasks | 9 files |
| Phase 09 P10 | 2h 20m | 3 tasks | 17 files |
| Phase 09 P11 | 1h 15m | 3 tasks | 21 files |
| Phase 10 P01 | 31min | 3 tasks | 111 files |
| Phase 10 P02 | 50min | 3 tasks | 63 files |
| Phase 10 P03 | 23 min | 3 tasks | 64 files |
| Phase 10 P04 | 21min | 2 tasks | 42 files |
| Phase 10 P05 | 53 min | 3 tasks | 76 files |
| Phase 10.2 P01 | 10h56m | 3 tasks | 404 files |
| Phase 10.2 P02 | 14m | 3 tasks | 20 files |
| Phase 10.1 P01 | 25min | 3 tasks | 33 files |
| Phase 10.1 P02 | 17 min | 3 tasks | 29 files |
| Phase 10.1 P03 | 11min | 3 tasks | 16 files |
| Phase 10.1 P04 | 42min | 3 tasks | 32 files |
| Phase 11 P01 | 37 min | 3 tasks | 50 files |
| Phase 11 P02 | 385 min | 2 tasks | 21 files |
| Phase 11 P03 | 12h 45m | 3 tasks | 31 files |
| Phase 11 P05 | 24 min | 2 tasks | 14 files |
| Phase 11 P06 | 23 min | 3 tasks | 39 files |
| Phase 11 P04 | 17 min | 2 tasks | 19 files |
| Phase 11 P07 | 59min | 3 tasks | 40 files |
| Phase 11 P08 | unknown (manual close-out) | 3 tasks | 13 files |
| Phase 11.1 P01 | 15min | 3 tasks | 18 files |
| Phase 11.1 P02 | 30min | 3 tasks | 53 files |
| Phase 11.1 P03 | 15min | 3 tasks | 28 files |
| Phase 11.1 P04 | 47min | 3 tasks | 68 files |
| Phase 11.1 P05 | 18min | 3 tasks | 37 files |
| Phase 11.1 P06 | 29min | 3 tasks | 168 files |
| Phase 11.1 P07 | 33 min | 5 tasks | 64 files |
| Phase 11.2 P01 | 12 min | 3 tasks | 35 files |
| Phase 11.2 P02 | 22min | 5 tasks | 36 files |
| Phase 11.2 P03 | 13 min | 3 tasks | 11 files |
| Phase 12 P01 | 39 min | 4 tasks | 57 files |
| Phase 12 P02 | 46 min | 3 tasks | 119 files |
| Phase 12 P03 | 36 min | 3 tasks | 72 files |
| Phase 12 P04 | 82 min | 3 tasks | 226 files |
| Phase 12 P05 | 2h15m | 3 tasks | 85 files |
| Phase 12.2 P01 | 20min | 3 tasks | 32 files |
| Phase 12.2 P02 | 28min | 3 tasks | 29 files |
| Phase 12.2 P03 | 45min | 3 tasks | 26 files |
| Phase 12.2 P04 | 2h | 4 tasks | 31 files |
| Phase 13 P01 | 29 min | 4 tasks | 31 files |
| Phase 13 P02 | 42min | 3 tasks | 30 files |
| Phase 13 P03 | 51 min | 4 tasks | 132 files |
| Phase 13 P04 | 80min | 3 tasks | 141 files |
| Phase 13 P05 | 34 min | 3 tasks | 53 files |
| Phase 13 P06 | 70 min | 3 tasks | 59 files |
| Phase 14 P01 | 25min | 5 tasks | 42 files |
| Phase 14 P02 | 80 min | 4 tasks | 81 files |
| Phase 14 P03 | 54min | 3 tasks | 122 files |
| Phase 14 P04 | 69 min | 4 tasks | 144 files |
| Phase 14 P05 | 100min | 3 tasks | 93 files |
| Phase 14 P06 | 132 min | 4 tasks | 26 files |
| Phase 12.1 P01 | 38min | 4 tasks | 61 files |
| Phase 12.1 P02 | 12h 43m | 6 tasks | 77 files |
| Phase 12.1 P03 | 46min | 4 tasks | 40 files |
| Phase 12.1 P04 | 32min | 4 tasks | 35 files |
| Phase 12.1 P05 | 103min | 3 tasks | 56 files |
| Phase 14.1 P01 | 90 | 4 tasks | 20 files |
| Phase 14.1 P02 | 8 min | 3 tasks | 3 files |
| Phase 14.2.1 P01 | 16 min | 3 tasks | 23 files |
| Phase 14.2.1 P02 | 15 min | 3 tasks | 21 files |
| Phase 14.2.1 P03 | 5 min | 3 tasks | 40 files |
| Phase 14.2.1 P04 | 51 min | 3 tasks | 22 files |
| Phase 14.2.1 P05 | 11 | 3 tasks | 24 files |
| Phase 14.2.1 P06 | 41min | 3 tasks | 15 files |
| Phase 15 P15-01 | 11 | 3 tasks | 31 files |
| Phase 15 P15-02 | 75 min | 3 tasks | 39 files |
| Phase 15 P15-03 | 80 min | 3 tasks | 22 files |
| Phase 14.3 P01 | 13 min | 3 tasks | 17 files |
| Phase 14.3 P02 | 11 min | 3 tasks | 11 files |
| Phase 14.3 P03 | 9min | 3 tasks | 13 files |
| Phase 14.3 P04 | 19min | 3 tasks | 32 files |
| Phase 16 P01 | 1h 34m | 4 tasks | 55 files |
| Phase 16 P02 | 16 min | 3 tasks | 44 files |
| Phase 16 P03 | 22 min | 3 tasks | 32 files |
| Phase 16 P04 | 40min | 3 tasks | 10 files |
| Phase 16 P05 | 19 min | 3 tasks | 27 files |
| Phase 16.1 P01 | 11 min | 4 tasks | 20 files |
| Phase 16.1 P02 | 1h 26m | 3 tasks | 4 files |
| Phase 16.1 P03 | 12 min | 3 tasks | 8 files |
| Phase 16.2 P01 | 34min | 4 tasks | 51 files |
| Phase 16.2 P02 | 25min | 3 tasks | 24 files |
| Phase 16.2 P03 | 13min | 3 tasks | 21 files |
| Phase 16.2 P04 | 50 min | 3 tasks | 15 files |
| Phase 16.2 P05 | 25 min | 3 tasks | 7 files |
| Phase 16.2 P06 | 43min | 3 tasks | - files |
| Phase 16.3 P01 | 95 | 4 tasks | 43 files |
| Phase 16.3 P02 | 9 min | 3 tasks | 17 files |
| Phase 16.3 P03 | 658 min | 4 tasks | 12 files |
| Phase 16.3 P04 | 30 min | 3 tasks | 16 files |
| Phase 14.2 P01 | 16 min | 3 tasks | 18 files |
| Phase 14.2 P02 | 27min | 4 tasks | 7 files |
| Phase 14.2 P03 | about 4h | 4 tasks | 23 files |
| Phase 14.2 P04 | 60min | 3 tasks | 11 files |
| Phase 17 P01 | 41 min | 4 tasks | 55 files |
| Phase 17 P02 | 50min | 6 tasks | 44 files |
| Phase 17 P03 | 38 min | 4 tasks | 53 files |
| Phase 17 P04 | 32min | 5 tasks | 70 files |
| Phase 17 P05 | 93min | 5 tasks | 103 files |
| Phase 17 P06 | 53 min | 4 tasks | 50 files |
| Phase 18 P01 | 43min | 4 tasks | 59 files |
| Phase 18 P02 | 75min | 4 tasks | 84 files |
| Phase 18 P03 | 45min | 3 tasks | 75 files |
| Phase 18 P04 | 40 min | 3 tasks | 78 files |
| Phase 18 P05 | 20min | 3 tasks | 31 files |
| Phase 18 P06 | 70min | 6 tasks | 52 files |
| Phase 18 P07 | 75 min | 5 tasks | 62 files |
| Phase 18 P08 | 20min | 4 tasks | 7 files |
| Phase 18.1 P01 | 31min | 5 tasks | 37 files |
| Phase 18.1 P02 | 45min | 5 tasks | 30 files |
| Phase 18.1 P03 | 21 min | 4 tasks | 27 files |
| Phase 18.1 P04 | 22 min | 5 tasks | 36 files |
| Phase 18.1 P05 | 20 min | 3 tasks | 20 files |
| Phase 18.1 P06 | 50min | 5 tasks | 39 files |
| Phase 19.3 P01 | 14 min | 2 tasks | 20 files |
| Phase 19.3 P02 | 15min | 4 tasks | 11 files |
| Phase 19.3 P03 | 10 min | 3 tasks | 20 files |
| 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 |
| Phase 19 P01 | 80min | 3 tasks | 34 files |
| Phase 19.1 P01 | 12min | 3 tasks | 37 files |
| Phase 19.1 P19.1-03 | 7 min | 3 tasks | 32 files |
| Phase 19.1-chat-satellites P19.1-04 | 11 | 4 tasks | 35 files |
| Phase 19.1-chat-satellites P19.1-05 | 10min | 4 tasks | 37 files |
| Phase 19.1 P19.1-02 | 9 min | 4 tasks | 36 files |
| Phase 19.1 P19.1-06 | 16 | 3 tasks | 46 files |
| Phase 19.4 P01 | 28 min | 4 tasks | 34 files |
| Phase 19.4 P02 | 50min | 5 tasks | 54 files |
| Phase 19.4 P03 | 71 min | 7 tasks | 61 files |
| Phase 19.4 P04 | 65 min | 5 tasks | 42 files |
## Accumulated Context
### Roadmap Evolution
- Phase 2 edited: edited fields: depends_on (Phase 1), goal (summer parity:* on bonfire, no longer a parallel workstream)
- Phase 10.1 inserted after Phase 10: Runtime admin extension point (URGENT)
- Phase 10.2 inserted after Phase 10: Nest framework packages under modules and write run docs (URGENT)
- Phase 11.1 inserted after Phase 11: SummerCMS documentation for humans and AI agents, modelled on wintercms.com/docs; after core framework, before Płytarium API port
- Phase 11.2 inserted after Phase 11.1: Ready to share: summercms.io website (sm-summercmsio-app + vue-summercmsio-app), sm-newsletter-plugin stub with double opt-in, 11.1 docs at /docs
- Phase 11.2 edited: edited fields: title, goal, repos, design source, success_criteria — reshaped into the Alpha 0.1 landing page (Nuxt 4 on SummerCMS, nginx + supervisor deploy); newsletter moved to 11.3
- Phase 11.3 inserted after Phase 11.2: Newsletter plugin and signup on summercms.io (split out of the original 11.2)
- Phase 11.3 deferred to the backlog as Phase 999.1 (2026-10-02): the Journal plugin and delivering Płytarium take priority
- Phase 12.1 inserted after Phase 12: User plugin admin screens (sm-user-plugin): Users, User Groups, Organisations admin, before Phase 15 cutover
- Phase 12.2 inserted after Phase 12: Admin form fields: date picker, file upload and relation-manager create/update/delete with Winter-like deferred binding (RelationController parity); framework only, ships as tag v0.1.1; blocks a downstream project on v0.1 (URGENT)
- Phase 14.1 inserted after Phase 14: OAuth identities (GET/DELETE oauth-identities) and GET /api/v1/fonoteka/me: the 3 orphan pending routes (14-CONTEXT D-09), so Phase 15 can reach all routes green (URGENT)
- Phase 14.2 inserted after Phase 14.1: Local vue-fonoteka-app against local fonoteka.go SummerCMS backend (dress rehearsal before production cutover) (URGENT)
- Phase 15 added: Journal plugin port (sm-journal-plugin); former Phase 15 Cutover moved to Phase 20
- Phase 16 added: grzybyfunkcjonalne.pl on reusable blog views
- Phase 20 edited: Płytarium production cutover moved here from Phase 15
- Phase 14.2.1 inserted after Phase 14.2: sm-translate-plugin so Journal can keep translatable fields (URGENT)
- Phase 14.3 inserted after Phase 14.2.1: One-command app scaffolding: summer new + summercms.io/new bootstrap script; ships as v0.1.13 (URGENT)
- Phase 18.1 inserted after Phase 18: Payment Gateway Subscriptions (split from Phase 18 per 18-CONTEXT D-01)
- Phase 16.1 inserted after Phase 16: Journal admin urgent fixes: relation search, list state, post form content and excerpt (found on UAT) (URGENT)
- Phase 16.2 inserted after Phase 16.1: Plugin-provided field types and the Blog Editor formwidget
- Phase 16.3 inserted after Phase 16: Backend admin personal API tokens with user-selected scopes (Journal agent writes as backend author); successor of Apparatus PersonalApiToken (URGENT)
- Phase 19.2 inserted after Phase 19.1: Chat polish against WavePath clients (WavePath framework asks, 2026-10-11)
- Phase 19.3 inserted after Phase 19.2: Framework transport and infrastructure for WavePath (WavePath framework asks, 2026-10-11)
- Phase 19.4 inserted after Phase 19.3: OIDC provider on wristband (WavePath framework asks, 2026-10-11)
- Phase 19.5 inserted after Phase 19.4: Social login in sm-user-plugin (WavePath framework asks, 2026-10-11)
- Phase 19.6 inserted after Phase 19.5: Two-factor auth and device flow in sm-user-plugin (WavePath framework asks, 2026-10-11)
- Phase 19.7 inserted after Phase 19.6: Generic MCP module: opt-in endpoint, plugin-declared tools, one connector per person (from /msd-explore 2026-10-17)
- Phase 20 reordered: Roadmap reordered (/msd-explore 2026-10-18): new Phase 20 Demo instance with every shared plugin; Płytarium cutover moved 20 → 30; Chat polish against WavePath clients moved 19.2 → 31; 21–29 added as their goals firm up
- Phase 19.4.1 inserted after Phase 19.4: Early public demo on demo.summercms.io: today's 16 shared plugins on rome, demo accounts, journal/presentations/quizzes/OIDC pages, backend by request; Phase 20 narrows to the chat family (URGENT)
### Decisions
Decisions are logged in PROJECT.md Key Decisions table.
Recent decisions affecting current work:
- [Phase 15]: 15-03: `/_journal/api/v1` public GETs; writes require backend-audience JWT and PHP `{error}` strings; JOURNAL-005 draft 404; Typesense Gate off by default (D-12/D-14/D-15/D-17). Do not add Journal to fonoteka.go tide (D-16).
- [Phase 14.2.1]: D-10 Go tables are `golem15_translate_*` (execute Task 2 `golem15-prefix`, 2026-10-06). PHP pin still uses `winter_translate_*`; do not copy those names into Go DDL.
- [Phase 14.1]: 14.1-01 Task 1 `ship-shared-full` — create `golem15_user_oauth_identities` in the shared sm-user-plugin with the full PHP column set (locked D-01 and D-07).
- [Phase 14.1]: Identity Index/Destroy are host-mounted on the JWT group; the user plugin does not register them under `/_user`. `/me` unrestricted `collection_ids` is JSON null (D-09); `scopes` stay `[]`.
- [Phase 14.1]: Corpus is 175/175/0 with zero pending routes. Live Nuxt Connected accounts and fonoteka-mcp `me()` stay Phase 15 UAT.
- Roadmap: gormigrate (not goose) is the migration tool, per STACK.md's more recent reasoning — ARCHITECTURE.md/PITFALLS.md text still says goose in places; treat gormigrate as authoritative when phases 3 and 5 are planned.
- Roadmap: two repos from day one — `summercms.go` (framework, no app knowledge) and `fonoteka.go` (sibling app repo, go.work workspace of plugins). Every phase states which repo(s) it writes to.
- Roadmap: the parity harness (Phase 2) and the first vertical slice (Phase 3) are sequenced immediately after kernel foundation, ahead of any further kernel broadening, to avoid the documented Scala-era bottom-up-kernel failure mode.
- [Phase 02]: tide is the framework-owned parity library and does not import Fonoteka — Phase 2 CONTEXT.md discretion; summercms.go must stay app-agnostic
- [Phase 02]: goccy/go-yaml v1.19.2 decodes fixtures with DisallowUnknownField; SaveFlow uses a dedicated encoder so nested literal bodies keep indent — goccy BytesMarshaler re-emitted |- scalars without nested indent; decode still uses the verified library
- [Phase 02]: JSON diffs ignore object key order and fail missing keys or token-type changes at $.path; non-JSON compares bytes at offset — D-13: structural JSON compare with UseNumber, exact bytes for non-JSON
- [Phase 02]: Proxy bind and upstream must be loopback HTTP; incoming Host/URL never selects the origin — T-02-01: pin PHP upstream, ignore client destination, cap bodies
- [Phase 02]: Capture rules and a private 0600 --vars store drive {{name}} substitution; unclassified credential shapes fail fixture writes — D-07 D-11 and T-02-02: never commit live JWT, inv_ tokens, OAuth codes or PKCE verifiers
- [Phase 02]: Carbon *_at values must match +00:00 before masking; Z, string ids, null vs [] and missing keys fail at $.path — D-13 D-15: assert shape before mask so parity classes stay visible
- [Phase 02]: 154 is the app manifest validated route count, not a framework constant; --next-batch above 15 is refused — D-16 and the 15-route resume workflow; keep tide generic
- [Phase 02]: Isolated PHP uses a parity-named SQLite file on 127.0.0.1:8423; record/reset refuse any other DB — T-02-05
- [Phase 02]: Client OAuth replay merges `/tmp/summercms-parity/pkce.vars` after seed; the verifier is not in git — D-07 D-11
- [Phase 02]: newTarget and seedHooks live in the app test package so Phase 3 can swap the synthetic handler for the real app and add a temporary genres SQL hook until POST genres is ported — D-10 D-12: framework tide stays app-agnostic; the handler/seed-hook seam is app-owned
- [Phase 02]: Pending never equals passing: TestParityCorpus reports recorded 154/154 passing 0 pending 154 and does not replay PHP fixtures against the synthetic handler — D-16: unported PHP routes must never count as a Go pass
- [Phase 02]: Unavailable Docker fails TestMain; testing.Short skips the container so the fast loop stays fast — QA-03 and D-12: no false green skip when Postgres cannot start
- [Phase 02]: Fresh PHP self-replay uses disposable MariaDB fonoteka_parity_* plus process-local hex credentials, never the developer DB or caller-supplied PHP_PARITY_TARGET — T-02-01 T-02-05: check-phase2.sh --fresh-php owns the origin and rejects PHP_PARITY_TARGET
- [Phase 02]: Client flows run on a second winter:up after dropping tables so they are not replayed after the mutating 154-route suite — D-16 and 02-03 seed-then-clients: keep route replay and Nuxt/MCP replay on disjoint schemas
- [Phase 02]: Capture-by-reference mismatch is a two-step share:item flow with a live token change on the second /show — D-11 D-13: contract tests exercise RecordFlow/ReplayFlow public APIs, not private helpers
- [Phase 03]: GORM and app services share one pgx-stdlib *sql.DB; the River LISTEN/NOTIFY pool is a Phase 11 seam and is not created in lagoon.Open — DATA-01: one shared pool now; dual-driver River listener deferred
- [Phase 03]: Generated app main stays framework-generic (lagoon.RuntimeCommands + surf.ServeCommand); fonoteka.go/app.Handler is the in-process boot seam for parity tests — summer build cannot import the app package; CLI serve and tests still assemble the same surf router
- [Phase 03]: Empty golem15.user.jwt.secret fails Boot; tests use a fixed test-only HS256 secret and do not issue tokens through a production API — D-11: missing secret must not fall back; token minting stays out of Phase 3
- [Phase 03]: lagoon.OrderBy takes a caller allow-list so the framework never hardcodes Fonoteka table names; the handler passes PHP PolishOrder::ALLOWED_COLUMNS — summercms.go must stay app-agnostic; PolishOrder columns live at the Fonoteka call site
- [Phase 03]: Duplicate non_empty query keys last-win, matching PHP parse_str; invalid then 1 is accepted, 1 then invalid is 422 — PHP parse_str last-wins confirmed with php -r; Go uses vals[len(vals)-1]
- [Phase 03]: Invalid stored context is rewritten to the lowest-ID accessible kind=collection row; auto-provisioning stays out of this slice — Plan 03-02 ports only the JWT default resolve path; CollectionProvisioner is Phase 12
- [Phase 03]: Route constraints compile regex and enum allow-lists at registration; request path text is only matched — D-15 T-03-07: PHP ->where() maps onto surf.Where/WhereIn; malformed and unknown IDs share a 404
- [Phase 03]: A ported route with a trusted seed_hook skips the global PHP bootstrap replay — D-20: unported register/login and POST genres; the hook mints a test-only JWT
- [Phase 03]: Corpus passing increments only after the ported subtest succeeds; pending never counts as passing — QA-04 T-03-06: 154 recorded, 1 passing, 153 pending
- [Phase 03]: Colliding fixture IDs are derived from CanonicalGenres seed order (rock=1, electronic=2, jazz=4) — D-20: set id:token, id:wishlist-album, id:genre from PHP seed order, not user input
- [Phase 03]: Phase 3 gate inlines TestParitySynthetic and CLI record/replay; PHP --fresh-php stays the Phase 2 sign-off because four wishlist album_count routes currently fail on live PHP — check-phase2.sh --fresh-php reports 150/154 on wishlist album_count expected 1 vs live 2. Phase 3 did not change PHP, tide, or those fixtures, so the Phase 3 gate must not fail on that drift.
- [Phase 03]: testcontainers-go v0.44.0 is the STACK-named test dependency for framework lagoon isolation tests, matching the app TestMain — DATA-01 isolation tests need a real ICU pl-PL Postgres. The app already used testcontainers; the framework module now pins the same STACK versions so lagoon tests do not share the app TestMain.
- [Phase 03]: High-severity T-03-01 through T-03-04 and T-03-06 are closed with failing-when-broken tests; token issuing remains test-only until Phase 7 — Roadmap required a security review of the JWT guard. 03-SECURITY-REVIEW.md maps each threat to a passing test; minting tokens through a production API is out of this slice.
- [Phase 04]: registry.gen.go is regenerated by scanning Code generated by summer make files, not by rewriting plugin.go — D-12: handwritten plugin.go stays byte-identical; existing plugins get a one-time accessor hint
- [Phase 04]: make:model table names are vendor_plugin_plural_snake with explicit gormigrate CREATE/DROP TABLE — D-13: timestamps on the model; --no-migration omits only the create-table file
- [Phase 04]: models sibling-import check runs in build.App before go build and reports plugin ID, file, and import — D-11: limited to classes/controllers/console/jobs/middleware/updates of the same plugin module
- [Phase 04]: go-i18n is used only to choose a CLDR category label; YAML message text never enters its template engine — D-03: PHP :name placeholders must remain literal
- [Phase 04]: A per-locale i18n.Bundle is cached so matcher/plural rules follow the requested tag, not the English default bundle — A single English default bundle made Polish few/many look invalid
- [Phase 04]: Fallback order is requested, parent, app.fallback_locale, then the raw key; framework defaults remain en/en — D-04 D-05: no Polish framework default and no extra core-locale step
- [Phase 04]: Goldmark v1.8.6 is used without html.WithUnsafe; final HTML is rejected if script/iframe, event handlers, or javascript/vbscript/data schemes remain — D-07 and T-04-08: Goldmark default rejects raw HTML; a second pass keeps layout HTML trusted only as template.HTML
- [Phase 04]: postcard.Mailer is published after Register and before Boot; each HasMailTemplates catalog is validated at that plugin Boot transition — D-20 D-21: plugin Boot can Lookup the mailer; missing files fail as party: boot <plugin ID> with the dotted name
- [Phase 04]: mail.smtp.tls defaults to mandatory STARTTLS; none/notls is opt-in for Mailpit and is never inferred — D-18 T-04-10: no silent production TLS downgrade; Mailpit needs explicit NoTLS
- [Phase 04]: Mailpit image is axllent/mailpit:v1.31.1; receipt is polled from /api/v1/messages then /api/v1/message/{ID} — D-19: a successful Send claim requires observed receipt through a separate HTTP API; pin a released Mailpit tag
- [Phase 04]: fstest.MapFS WalkDir cannot host '..' keys; malformed lang paths are extra-segment and wrong-suffix files — MapFS Open/WalkDir follows .. into an infinite directory loop; parseLangPath still rejects cleaned traversal
- [Phase 05]: Models-leaf rule holds on keios.eu user, jz chat, and pxpx checkout; contracts/VOs/jobs/broadcasting stay inside the cast-or-hook conversion treatments
- [Phase 05]: plugin.go Models()/Migrations() return registry All(); Boot calls classes.RegisterHooks when *gorm.DB is published — later Phase 5 plans add files, not edit plugin.go
- [Phase 05]: lagoon.Fill matches gorm column tags against the caller allow-list and logs dropped keys once per type+key in non-production — D-05 D-06
- [Phase 05]: Lifecycle Has* interfaces use GORM-native signatures; WithSoftDeleteCascade does not open a new transaction — DATA-03 primitive
- [Phase 05]: Paginate coerces nil data to []; RegisterJoinTable fails loud on nil db — DATA-10 and pivot-write contract
- [Phase 05]: DATA-09 migration count is not an acceptance number (D-01); HTTP DTO fuzz moves to Phase 12 (D-07)
- [Phase 05]: Jsonable payload field is Data, not Value, because driver.Valuer.Value() collides under go vet — go vet rejects a field and method both named Value; type Jsonable[T] T is illegal
- [Phase 05]: KeepMarketPriceSource is a gorm-ignored Album flag set by SaveAlbum when requested contains market_price_source — GORM has no Eloquent isDirty; the flag preserves stampMarketPrice source provenance
- [Phase 05]: CollectionFillFields is name+description; PHP has no CollectionWriteService — D-05 service list is a subset of Fillable excluding owner_id
- [Phase 05]: Various Artists natural key is name_key='various artists' (PHP seed), slug various-artists — seed_genre_and_various_artist_taxonomy.php uses a spaced name_key
- [Phase 05]: go-playground/validator v10.30.4 is the STACK-named rule engine behind lagoon.Validate — STACK.md already named this library; lagoon.Validate translates Laravel rule strings onto Var()
- [Phase 05]: Column keys are HKDF-SHA256 derived via Go 1.27 crypto/hkdf with info summercms.lagoon.encrypted.v1; no golang.org/x/crypto — D-11 stdlib-first: Go 1.24+ ships HKDF; CLAUDE.md forbids a new dependency here
- [Phase 05]: Ciphertext is 1-byte format/key-id | 12-byte nonce | GCM seal, stored base64 in text columns — D-12 versioned ciphertext plus app.previous_keys decrypt-only fallback
- [Phase 05]: OpenFromApp calls LoadAppKey+PublishEncryptionKeys once per boot; empty/short/undecodable app.key fails with SUMMER_APP__KEY — D-11 fail-loud, no default key; Scan/Value must not re-read config per row
- [Phase 05]: golem15_user_organisations is owned by the user plugin; Phase 5 ships no users-table ALTER (D-03 deviation, Phase 7 AUTH-02) — User-confirmed at plan time: organisations are an FK target only this phase
- [Phase 05]: DecryptLaravelPayload is cutover-import-only and is never called from Encrypted Scan/Value — D-10 live path is AES-256-GCM only; Laravel CBC is Phase 15 import
- [Phase 05]: Blob keys are partition+disk_name (no public/protected prefix); fileblob roots at storage/app/uploads — StaticHandler reconstructs keys from PartitionDirectory+disk_name; cutover can point bucket_url at uploads/public
- [Phase 05]: DeleteForOwner afterCommit is an in-tx key collector; DeleteKeys removes originals and thumb_<id>_ prefixes after commit — GORM has no post-commit hook; a rollback must not have already deleted bytes
- [Phase 05]: Album/Collection MorphName returns the PHP class string and does not import attach (models stay a leaf) — models-leaf rule; interface satisfaction is implicit
- [Phase 05]: Settings is golem15_fonoteka_settings singleton with typed search_use_typesense BOOLEAN (RESEARCH Open Question 2) — PHP SettingsModel over system_settings is not ported; dedicated typed table is the user-resolved storage
- [Phase 05]: notifications/wishlist_subscriptions/wishlist_digest_queue have no FK on user_id/collection_id, matching PHP (Open Question 3 / T-05-18) — PHP migrations omit ->foreign() on these tables; D-02 matches actual constraints rather than fixing them
- [Phase 05]: D-02 allow-list is settings table, users column-count (no widen_users), and the shipped extra oauth_refresh_tokens.user_id FK — Intended gaps only: Open Question 2, D-03/AUTH-02, and P3 D-17 freeze of 05-03 extra FK
- [Phase 05]: DATA-11 fixture plugin is test-only: no process-wide Register, not in app.PluginIDs or plugins.gen.go — CONTEXT.md discretion: fixture plugin in tests is acceptable; production binary must not load it
- [Phase 05]: Hidden-marshal registry walk lives in fonoteka.go/classes because summercms.go must not import the app — CLAUDE.md two-repo rule; plan allowed the parity/classes fallback
- [Phase 05]: Credential fuzz excludes owner FKs from the working allow-list, matching D-05 two-layer even without a write-service file — Fillable() includes user_id/organisation_id; acceptance requires the owner FK never change
- [Phase 05]: classes TestMain is the real-Postgres harness; parity activateAppPlugins/parityDB cannot be imported from package main — Same ICU pl-PL migrate-both-plugins shape without crossing the app/framework test boundary
- [Phase 06]: Parameterized middleware is a surf factory (strings.Cut on first ':'), not a fixed name table (D-05)
- [Phase 06]: TokenGuard implements CredentialGuard only; InvScope owns PHP TokenScope 401/403 bodies (D-08)
- [Phase 06]: NewJWTGuard reuses bearerToken/Verify/write401 so Registry.Middleware(jwt) is byte-identical to bouncer.Middleware (D-10)
- [Phase 06]: oauth is not registered this plan; only jwt and inv_token (D-09)
- [Phase 06]: Personal-token genres fixture body matches isolated seedGenres; CORS * deferred to D-18
- [Phase 06]: Concrete limiter is FixedWindowLimiter; surf.Limiter interface remains the unused Phase 3 seam — D-03 D-05 naming collision with pre-existing Limiter interface
- [Phase 06]: Trusted-proxy list is a NewFixedWindowLimiter constructor argument, never a setter — Named-bucket Key closures and inline N,M must share one trusted list
- [Phase 06]: fonoteka-* buckets live on the app plugin via surf.BucketProvider, not hardcoded in surf — summercms.go must stay Płytarium-agnostic
- [Phase 06]: Empty PHP group builders plus test-only boot-probe routes prove middleware strings resolve without 501 shells — D-15: no 501 shells; wrap() only sees routes
- [Phase 06]: php_parity.sh pins APP_DEBUG=false; three existing HTML exception fixtures need re-recording — T-06-09 production-shaped error bodies
- [Phase 06]: HasHouseMiddleware is the only plugin-facing house-tag path; Assemble/BuildRouter is the sole RegisterHouseMiddleware caller (D-16)
- [Phase 06]: Production body limits are 134217728/134217728 (128MiB), operator-confirmed 2026-09-19 from nginx client_max_body_size=128M and php.ini post_max_size=128M/upload_max_filesize=128M (D-18, T-06-13)
- [Phase 06]: CORS path globs compile as Laravel nested * because Go path.Match would miss /api/v1/fonoteka/genres (Pitfall 10)
- [Phase 06]: swag v1 Swagger 2 is converted by a local swagger2openapi helper to OpenAPI 3 for openapi-typescript 7; Phase 10 wires types into the admin SPA
- [Phase 06]: Full route-table isolation uses surf.BuildRouter of the real plugins; app.Handler returns http.Handler and cannot call Routes() — app.Handler assembles an http.Handler; Routes() is on *surf.Router
- [Phase 06]: T-06-05 remains accept as originating 06-01 (the 06-05 plan three-accepts list omitted it) — Originating plan disposition is copied verbatim into 06-SECURITY-REVIEW.md
- [Phase 06]: PublicOnlyMode any-host-when-public is proven via skipReservedCheck httptest, not a live public IP dial — Unit tests must not require outbound network
- [Phase 06]: Keep inv_token outermost so valid credentials populate bouncer.Credential before the limiter selects tok:<id>. — The named bucket must retain per-token isolation for valid credentials instead of collapsing them onto the IP fallback.
- [Phase 06]: Place throttle:fonoteka-api-token before inv.scope:read in the personal-token middleware declaration. — Missing and invalid credentials must consume the 60/minute per-IP deny-path budget before InvScope returns its PHP-compatible 401 response.
- [Phase 06]: Replace the split limiter store protocol with one atomic Attempt operation. — Expiry, threshold comparison, admitted increment, and retry duration must share one mutex critical section so concurrent callers cannot bypass Max.
- [Phase 06]: Use one inline:domainless namespace plus trusted-proxy ClientIP for every anonymous inline throttle. — Host and inline throttle text must not let anonymous callers rotate rate-limit buckets; authenticated requests retain u:<principal id> isolation.
- [Phase 06]: isReservedOrPrivate owns Addr.Unmap and recursively applies the ordinary IPv4 table to supported transition embeddings — Direct helper callers and the production dial hook must share one normalization and private/reserved policy.
- [Phase 06]: A 64:ff9b:1::/48 address with a non-zero RFC 6052 u octet fails closed — Malformed local-use NAT64 must not fall through as apparently public native IPv6.
- [Phase 06]: Use one unexported bufferedResponse for house and raw recovery — A shared transactional writer keeps panic-before-write and panic-after-write behavior identical while preserving raw versus house fallback bodies.
- [Phase 06]: Keep bufferedResponse Flush as a no-op — Recovery must never unwrap, hijack, flush, or otherwise expose the destination writer before handler success.
- [Phase 06]: Success commit replaces only route-owned header keys — Unrelated headers already placed on the destination by outer wrappers such as path-scoped CORS must survive.
- [Phase 06]: Use wire.WriteJSON for both InvScope denial branches — The shared writer is the established PHP-compatible no-newline serialization path.
- [Phase 06]: Assert exact denial bytes before JSON shape checks — Whitespace normalization would hide response-contract regressions.
- [Phase 06]: Retain all four earlier accepted risks unchanged; T-06-23 through T-06-27 are mitigated, not accepted or deferred. — Both repositories' authoritative race and vet gates passed, and each new threat has concrete source and named regression evidence.
- [Phase 06]: Anonymous inline limiter identity is documented only as inline:domainless|<ClientIP>, excluding policy text and request or forwarded Host inputs. — The production resolver and three executed regressions prove Host rotation and inline-parameter changes cannot create fresh anonymous budgets while authenticated principals keep isolated u:<id> keys.
- [Phase 07]: Blacklist storage expiry follows PHP jwt-auth (later of exp and iat+refreshTTL, plus one minute). — Using the raw access exp would drop a logged-out token that is still inside the refresh window.
- [Phase 07]: user_throttle and jwt_blacklist are allowed schema diffs — The frozen PHP snapshot predates the user plugin. jwt_blacklist is also the production PHP path: logout calls JWTAuth::invalidate(true) and blacklist_enabled defaults true. The earlier "PHP does not blacklist" note was a harness artifact of CACHE_DRIVER=array.
- [Phase 07]: Fetch after logout stays 401 in Go — Re-recorded PHP with file cache still returned 200 on a reused token (show_black_list_exception default 0). That case is kept on disk but is not a ported corpus case.
- [Phase 07]: Already-activated activate/activate-by-code is Winter 500 HTML — User::attemptActivation throws when the user is already active; Go keys that path on IsAlreadyActivated, not wrong-code.
- [Phase 07]: Both CLI serve and in-process Handler must publish *blob.Bucket; unit tests that call attach.Publish themselves cannot stand in for boot — Phase 5 shipped OpenBucket/Publish but never wired them. Phase 7 added HTTP avatar. UAT and corpus replay boot via Handler, so serve-only publish would leave the same 500.
- [Phase 07]: Assembled Handler avatar coverage lives in parity/ next to newConfiguredTarget, not in the user plugin package — The user plugin cannot import app.Handler without a reverse import. newConfiguredTarget is the same boot as replay.
- [Phase 08]: wristband.Options exposes only the four PHP-configurable metadata fields (service_documentation path, scopes_supported, token_endpoint_auth_methods_supported, authorization_response_iss_parameter_supported); response_types/grant_types/code_challenge_methods stay fixed protocol constants per D-06
- [Phase 08]: Plugin.Boot always constructs wristband.Server even with an empty app.url rather than failing loud, to avoid breaking the many existing fonoteka tests that boot without app.url configured; production must set app.url
- [Phase 08]: D-10 (oauth guard retirement) is recorded via TestOAuthMetadataRouteIsolation rather than editing Phase 6 historical docs, per 08-PATTERNS.md guidance to prefer a supersession note
- [Phase ?]: [Phase 08 P02]: Corrective migration named 21_oauth_schema_correction.go (not 12_): Go init()-order/gormigrate slice position determines RollbackLast() target, not the migration's numeric ID, so the file must sort after 20_remaining.go
- [Phase ?]: [Phase 08 P02]: Schema-correction rollback refuses (changes nothing) rather than deleting/coercing rows when any row depends on a nullable lifecycle column
- [Phase ?]: [Phase 08 P02]: wristband.Tx ships the full ClientStore/AuthCodeStore/RefreshTokenStore/AccessTokenIssuer surface now so 08-03/08-04 reuse the same transaction seam without another interface change; expiry-sweep methods are deferred to 08-06
- [Phase ?]: [Phase 08 P02]: Register strips C0/DEL control characters from client_name at DCR capture time (PHP only strips at ConnectedApp/Consent display time) per 08-CONTEXT.md discretion
- [Phase ?]: [Phase 08 P02]: OAuth config keys live under golem15.fonoteka.oauth.* (bare plugin ID, matching compass.MergePlugin), not plugins.golem15.fonoteka.oauth.*
- [Phase ?]: [Phase 08 P02]: AUTH-05/AUTH-06/AUTH-07 remain Pending in REQUIREMENTS.md: this plan ships DCR only, not the full authorize/token/consent surface
- [Phase 08]: [Phase 08 P03]: Options gained Resource and PendingRequestTTL (not in the plan's file list for server.go) following the 08-02 precedent of extending Options for deployment-configurable values
- [Phase 08]: [Phase 08 P03]: authorize's allowed-scope set is a package-level authorizeAllowedScopes constant, not Options.ScopesSupported (RFC 8414 metadata field) -- conceptually distinct config surfaces matching PHP's own separation
- [Phase 08]: [Phase 08 P03]: Client lookup and pending-row creation each open their own WithinTx call, matching PHP's lack of a wrapping transaction around authorize; only DCR and later code-exchange/refresh-rotation need single-transaction atomicity
- [Phase 08]: [Phase 08 P03]: AUTH-05/AUTH-06/AUTH-07 remain Pending in REQUIREMENTS.md, continuing 08-01/08-02's decision: this plan ships authorize only, not the full RFC surface
- [Phase 08]: [Phase 08 P04]: Token dispatch accepts grant_type=refresh_token per PHP's exact validity check but rotateRefreshToken always returns invalid_grant in this plan's scope; full rotation/lineage-kill (T-08-REFRESH-REPLAY) is 08-06's job per ROADMAP.md Wave 6
- [Phase 08]: [Phase 08 P04]: No routes.go/plugin.go changes -- POST /oauth/mcp/token is not mounted on the assembled app this plan; mounting happens once 08-05 wires consent and produces a real issued code
- [Phase 08]: [Phase 08 P04]: MintablePrefix changed from const to var (D-11 groundwork) with no config wiring added yet; default stays byte-identical inv_
- [Phase 08]: [Phase 08 P05] AuthCodeStore.MarkIssued gained scopes/collectionIDs/expiresAt params; ClientStore gained MarkConsented — PHP issueCode overwrites six columns in one UPDATE, not just code_hash/user_id; the narrower 08-02 signature could not persist consent's granted scopes or server-resolved collection pin
- [Phase 08]: [Phase 08 P05] wristband.Options gained CodeTTL (600s default), wired from the previously-unconsumed code_ttl_seconds config key — PHP OAuthCodeManager::CODE_TTL_SECONDS is a distinct constant from PENDING_TTL_SECONDS; IssueCode needs a fresh expiry from consent time
- [Phase 08]: [Phase 08 P05] Consent scope ordering follows auth.MintableScopes's declared read/write/ai order, not PHP array_intersect's incidental first-array order — 08-UI-SPEC.md explicitly documents canonical read -> write -> ai order as the fixed contract
- [Phase 08]: [Phase 08 P05] POST /oauth/mcp/token mounted in this plan, closing 08-04's deliberate D-09 deferral — Only once consent produces a real issued code does an end-to-end /token call through the real route have anything to exchange
- [Phase 08]: [Phase 08 P05] AUTH-05/06/07 remain Pending in REQUIREMENTS.md — Refresh rotation (08-06) and connected-apps list/revoke are still outstanding pieces of those requirements; this plan ships consent plus the token mount only
- [Phase 08 P06]: rotateRefreshToken commits lineage-kill revocation inside WithinTx and returns nil (success) on replay, mapping a captured replayed flag to invalid_grant outside the transaction -- mirrors PHP rotateRefresh's own commit-then-throw shape
- [Phase 08 P06]: RevokeLineage walks forward only through RotatedToID; sufficient for both replay-kill and connected-app-revoke because every normal rotation already revokes its own predecessor's access token and connected-app revoke always starts from the terminal row
- [Phase 08 P06]: Fixed RevokeLineage (GORM adapter and in-memory test double) to also revoke each visited row's linked access token -- the 08-02-era method only stamped the refresh row itself, leaving a replayed lineage's live access token usable
- [Phase 08 P06]: AUTH-05/06/07 remain Pending in REQUIREMENTS.md: refresh-rotation and connected-apps pieces are done, but AUTH-05/07's unchanged-fonoteka-mcp clause needs 08-08/08-09, and AUTH-06's CSRF/rate-limit/cache-header claims are reconciled by 08-10's security review
- [Phase ?]: [Phase 08 P07]: bonfire.Flag.Repeatable / Input.Flags(name) is the new shape for PHP-parity =* console options (Cobra StringSlice), with no change to existing scalar/bare Flag callers
- [Phase ?]: [Phase 08 P07]: fonoteka:oauth-client create reuses wristband.Tx.CreateWithCap(ctx, rec, math.MaxInt32) instead of adding an uncapped Create method -- operator-issued clients are never subject to DCR's 200-client cap
- [Phase ?]: [Phase 08 P07]: list/update operate directly on models.OAuthClient via *gorm.DB rather than extending wristband.ClientStore -- only client issuance needs the shared wristband hash/validation path (T-08-SECRET-TIMING)
- [Phase ?]: [Phase 08 P07]: Fixed clientRecordToModel to persist ScopeCeiling, a mapping gap silent since 08-02 because DCR never sets a ceiling
- [Phase ?]: [Phase 08 P07]: AUTH-05/AUTH-07 remain Pending in REQUIREMENTS.md -- both requirements' full text still needs 08-08/08-09's unchanged fonoteka-mcp install/auth flow proof
- [Phase ?]: [Phase 08 P08]: me_token_controller.go was created in the Task 1 RED commit as a compiling 501 stub (Rule 3), following the 08-04/08-06/08-07 precedent -- controllers/api must compile with its new test file while the route stays unmounted
- [Phase ?]: [Phase 08 P08]: scopes/collection_ids on GET /api/v1/fonoteka/me always serialize via wire.Slice (nil -> []), a deliberate divergence from PHP's collection_ids null-means-unrestricted semantics, accepted because fonoteka-mcp's TS MeResponse type never reads collection_ids
- [Phase ?]: [Phase 08 P08]: AUTH-07 stays Pending in REQUIREMENTS.md -- this plan ships the /me prerequisite but the requirement's 'fonoteka-mcp completes its install and auth flow unchanged' clause needs 08-09's real MCP gate
- [Phase ?]: [Phase 08 P09]: Lifecycle fixture recorded via a one-time Go program calling tide.RecordFlow directly against isolated PHP (deleted after use), not via Playwright/capture_clients.mjs -- login and consent are plain JWT API calls with no browser dependency
- [Phase ?]: [Phase 08 P09]: wristband's explicit no-store Cache-Control becomes no-store, private and unheadered JSON errors default to no-cache, private -- confirmed by live PHP recording, superseding the earlier 08-CONTEXT.md assumption of bare no-store
- [Phase ?]: [Phase 08 P09]: wristband redirects now emit Symfony's exact HTML redirect body (wristband/redirect_html.go) with PHP htmlspecialchars(ENT_QUOTES) escaping -- Go's bare 302 never matched real PHP
- [Phase ?]: [Phase 08 P09]: {request_id} route constraint is now upper-bound only ([A-Za-z0-9_-]{1,128}) -- PHP applies no router-level shape constraint and the 16-char lower bound was rejecting a valid recorded 404 test case at the router
- [Phase ?]: [Phase 08 P09]: OAuthConsentController's basic-validation failure now writes Winter's generic production 500 HTML page (not a clean 422) -- PHP's bare $request->validate() on this route is never caught by a JSON exception renderer, confirmed live with APP_DEBUG=false
- [Phase ?]: [Phase 08 P09]: All nine OAuth manifest routes are status: ported with seed_hook: genres; scripts/check-phase8.sh's stage bodies are fully implemented but never executed by this plan -- 08-10 Task 3 is the sole execution site
- [Phase ?]: [Phase 08 P09]: AUTH-05/AUTH-06/AUTH-07 remain Pending in REQUIREMENTS.md -- this plan proves byte parity and builds the real-MCP gate machinery, but only 08-10's actual gate execution can prove the unchanged-fonoteka-mcp clause
- [Phase 08]: Security review self-performed by the 08-10 executor (no Task/Agent spawner available), disclosed in 08-SECURITY-REVIEW.md's frontmatter and Reviewer Note — Per 08-CONTEXT.md D-04's documented fallback; every cited file:TestName was individually re-run, not inherited unverified
- [Phase 08]: Checkpoint decision: approved closing Phase 8 with the Playwright UI matrix gap (check-phase8-ui.mjs:458) carried forward as a named follow-up — Every other scripts/check-phase8.sh stage ran green in the sole full gate execution; the Playwright matrix was a deliberate, never-authored fatal() left by 08-05 as 08-10's seam
- [Phase 08]: AUTH-05, AUTH-06 and AUTH-07 marked complete in REQUIREMENTS.md — Each requirement's exact text is satisfied by the delivered backend/gate evidence; none mandates a Playwright-verified browser regression suite, so the carried-forward UI gap does not block completion
- [Phase 09]: Frontend verification accepts PHP tokens that omit aud and rejects any other explicit audience
- [Phase 09]: Backend JWTs require aud=backend, use admin.jwt.secret, and omit the PHP user prv hash
- [Phase 09]: Cabana mounts from BuildRouter only when a plugin registers admin controllers; an empty admin.jwt.secret fails that assembly
- [Phase 09]: Admin jti rows live in backend_jwt_blacklist and cabana does not republish the frontend BlacklistStore — Refresh and logout must not revoke frontend tokens or be revoked by them.
- [Phase 09]: backend_user_roles.code is indexed and not unique so Winter rows can repeat a code — admin:create rejects zero or many matches instead of a unique constraint the cutover table does not have.
- [Phase 09]: tokens_valid_after is a nullable additive column used to revoke admin JWTs on password reset — The guard already honors Principal.TokensValidAfter and Winter's required columns stay unchanged.
- [Phase 09]: Cached form schemas keep source phrase keys; each response localizes a copy and records meta.locale
- [Phase 09]: Absent config_form.yaml does not fail activation, so the 09-01 Genre list controller still boots
- [Phase 09]: YAML option keys keep their JSON scalar type; method options call DropdownOptions with the exact field name
- [Phase 09]: make:admin-controller writes controllers/name config_form and config_list pointing at models/name fields and columns
- [Phase 09]: Omitted sortable defaults to true, except relation columns, which stay unsortable unless columns.yaml sets sortable
- [Phase 09]: perPageOptions keep YAML order and must include recordsPerPage
- [Phase 09]: list_toolbar, recordUrl, and showCheckboxes compile to create, update, and delete
- [Phase 09]: A conditions key is a boot error; a model scope must be listed by FilterScopes()
- [Phase 09]: Admin list meta uses D-11 page, while lagoon.Paginate still computes last_page
- [Phase 09]: Writable admin fields are bound to gorm columns at activation; id, timestamps, scope, and system flags are never fillable
- [Phase 09]: Show and update use one not-found body for missing and out-of-scope rows; delete of an absent row is deleted 0 and does not run hooks
- [Phase 09]: A bulk selection that matches no scoped row is a no-op; a mixed present and absent selection is a 409 and rolls back
- [Phase 09]: Controller hook failures return an opaque lifecycle error and do not echo the hook text
- [Phase 09]: Album admin D-14 stores collection_id of the one active frontend user matched by normalized backend email; no album user_id column was added
- [Phase 09]: Winter relation keys match exported Go fields case-insensitively and the served JSON keeps the YAML spelling
- [Phase 09]: Required relation fields stay on the admin form schema and are not save-time column rules
- [Phase 09]: Genre and style admin option values are decimal strings because pact.Option.Value is a string
- [Phase 09]: Artists uses only the shared cabana list and CRUD engines; the controller contains identity, asset paths, model factory, and permission declaration only
- [Phase 09]: Artist name_key and slug remain model-owned lifecycle fields and are not exposed by the admin writable projection
- [Phase 09]: The Phase 9 Genre tracer remains the one production controller; form capability was added without a parallel route or identity
- [Phase 09]: The tracer list and columns were already byte-equivalent to the tracked Winter source, so Task 2 added behavioral proof rather than rewriting them
- [Phase 09]: The tracked Style source declares no dropdown provider or filter, so production YAML stays exact while isolated compiler fixtures prove typed scalars and provider rejection
- [Phase 09]: Style slug retains the tracked update-only readonly schema and is generated on create by the model lifecycle
- [Phase 09]: Style equal-value list ordering is explicitly stabilized by the shared primary-key tie-breaker
- [Phase 09]: Collection-specific pivot identifiers and stamps remain plugin-owned behind a typed relation contract. — Cabana can validate and execute relations generically without hardcoding Fonoteka tables, columns, roles, or payload behavior.
- [Phase 10]: Admin prefix is backend.uri (default /backend); admin API at {prefix}/api/v1, embedded SPA at {prefix}; fonoteka uses /plytadmin
- [Phase 10]: Admin SPA uses the HttpOnly summer_admin cookie selected by X-Requested-With; cookie-only POST/PUT/DELETE without the header get 403 forbidden; Bearer bodies unchanged
- [Phase 10]: Framework owns admin/openapi/admin.json (swag -> swagger2openapi -> openapi-typescript); fonoteka docs/openapi.json no longer lists admin paths
- [Phase 10]: npm package gate approved: 17 exact pins in admin/package.json; any new package or version needs a checkpoint
- [Phase 10]: 10-02: Relation lists, field options and filter options share one mounted six-segment GET pattern dispatched by nestedGet (ServeMux cannot register them side by side); documented paths unchanged
- [Phase 10]: 10-02: Record responses carry relation values in data and labels in meta.labels; a belongsTo on a protected fill key is read-only (Collections owner)
- [Phase 10]: 10-02: Message key validation checks declared keys and framework defaults only; defaults derived from Winter prompt, noRecordsMessage or form name may be literal text
- [Phase 10]: 10-02: Filter options path {scope} is the filter name; the model's FilterOptions receives the scope method name
- [Phase 10]: 10-02: Tailwind excludes generated schema.d.ts and openapi/ from the class scan so API changes do not churn boardwalk/dist
- [Phase 10]: Admin OpenAPI declares write bodies (AdminRecord, AdminIDsRequest) and the list filter query as a deepObject so the SPA sends typed payloads
- [Phase 10]: A form field without span fills the whole row (Winter default); auto and row take the next free slot
- [Phase 10]: The save body skips read-only fields and unregistered types (relation-manager, unknown); Winter attributes pass through an allowlist
- [Phase 10]: aria-sort reflects only the explicit URL sort; defaultSort is applied server-side without a header indicator
- [Phase 10]: relation-manager is record-bound: dropped on create even without context: update and never sent in the save body
- [Phase 10]: Admin OpenAPI declares search, sort, dir, page and per_page on the linked and candidate relation routes
- [Phase 10]: Collapsed rail: Enter or ArrowDown opens the SectionFlyout and moves focus into it; click still navigates; expand button only for a manual collapse
- [Phase 10]: logout(router) always clears user, navigation and settings and ends on the login route, even when POST /auth/logout fails
- [Phase 10]: Phase 10 gate allow-lists exactly the two pre-existing fonoteka parity failures by package and test name and refuses once either passes
- [Phase 10]: Field controls import components/form/control.ts; registry.ts re-exports it (breaks a registry/control import cycle)
- [Phase 10]: Tailwind skips admin/tests so test code never changes boardwalk/dist
- [Phase 10]: Framework tests use neutral acme names; the hygiene stage refuses application names in summercms.go admin, boardwalk, cabana and phrasebook
- [Phase 10.2]: Kept framework onboarding in the root README and application setup in fonoteka.go, preserving the two-repository boundary.
- [Phase 10.2]: Made the gate scan tracked Go, template, and shell files in both repositories while excluding itself and planning artifacts.
- [Phase 10.1]: 10.1-01: create and delete are reserved names in the single controller action namespace shared by toolbar.buttons and widget action: keys
- [Phase 10.1]: 10.1-01: toolbar action body must be exactly {}; record_id or values is a 422
- [Phase 10.1]: 10.1-01: partial view-model guard also refuses html/template trusted content types; href/src with whitespace or control characters are dropped
- [Phase 10.1]: 10.1-02: a widget posts only its fill keys' current values; loadControllerAssets resolves with failed script URLs so a widget fails fast instead of waiting 5000 ms
- [Phase 10.1]: 10.1-02: plugin stylesheet links are keyed by controller and URL and toggled on every list and form open
- [Phase 10.1]: 10.1-02: the client partial allowlist drops the server's removed tags with their subtree and unwraps other unknown tags
- [Phase 10.1]: 10.1-03: lagoon.Fill converts json.Number into integer, unsigned and float fields; a fraction or overflow into an integer field is an error
- [Phase 10.1]: 10.1-03: Discogs lookup and sync admin actions are D-02 stubs reusing golem15.fonoteka.access_albums; Phase 14 replaces them
- [Phase 11]: 11-01: conga job registration closes only while a worker client runs; the insert-only client has no Workers bundle
- [Phase 11]: 11-01: a conga worker's lifetime is Worker.Stop, not the start ctx (River Start on context.WithoutCancel), so SIGTERM stops serve and queue:work gracefully
- [Phase 11]: 11-01: an app with no jobs gets an internal summercms_conga_idle worker because River refuses to start without workers
- [Phase 11]: 11-01: lagoon.OnDatabase treats a published *gorm.DB as ready and derives the pool; fonoteka GORM hooks now register under serve
- [Phase 11]: 11-01: River v7 tables are river_job, river_leader, river_migration, river_notification, river_queue (fonoteka schema allow-list)
- [Phase 11]: 11-03: broadcast job args carry the payload as a JSON string because River's JSONB args column reorders object keys
- [Phase 11]: 11-03: no broadcast job is enqueued (and Emit is a no-op) for the null driver or a driver whose Enabled() is false (Centrifugo without an API key)
- [Phase 11]: 11-03: the fonoteka test harness boots realtime.driver centrifugo so assembled route tables always carry the realtime routes
- [Phase 11]: 11-03: Phase 12 album store/update/bulk must use WithoutBroadcasting[models.Album] plus Service.Emit (the in-tx automatic payload can carry stale artists)
- [Phase 11]: 11-05: beachcomber sync callbacks are pinned After(gorm:after_*) and Before(gorm:commit_or_rollback_transaction); GORM appends After-only callbacks past the commit and past lagoon:after_commit
- [Phase 11]: 11-05: code on a GORM callback's handle builds a clean session (Session NewDB+Context, Clauses(), Session NewDB); Session with a Context clones the write statement and a later WithContext queries through the written model
- [Phase 11]: 11-05: search index name is search.prefix + SearchableAs(); SearchIDs returns candidates only and Phase 12 must re-gate in SQL
- [Phase 11]: 11-05: sync is inline after commit on context.WithoutCancel, bounded by the Typesense timeout; errors are typesense.StatusError without bodies and are logged, never returned to the write
- [Phase 11]: flare VAPID driver never follows redirects and sends only to https hosts on push.allowed_hosts (checked before dialing)
- [Phase 11]: compass Persist now merges runtime values over the existing overrides.yaml so websockets:generate-vapid-keys --update cannot wipe other persisted overrides
- [Phase 11]: websockets:health probes the Centrifugo info API (PHP only read local config); an error body fails the check
- [Phase 11]: Phase 11 removal checks are scripted: check-phase11.sh --removal applies anchor-exact mutations, requires the named test to fail on an assertion and restores the file byte for byte (cmp); kept out of --all because it edits tracked source
- [Phase 11]: Savepoint helpers (beachcomber, lighthouse) roll back when RELEASE fails, because a swallowed read error still aborts the Postgres transaction
- [Phase 11]: lagoon after-commit callbacks always receive a clean statement on the write's connection; side-effect GORM callbacks declare Before(gorm:commit_or_rollback_transaction)
- [Phase 11]: Phase gates accept a pending skip only by exact test name and pending text (TestBroadcastGoldens/created and /updated until Phase 12), in check-phase11.sh and check-phase10.1.sh
- [Phase 11]: Phase 11-08: lagoon.AfterCommit inside a foreign plain GORM transaction warns and skips the callback instead of running it immediately; Cabana writes and SaveAlbum run in lagoon.Transaction — Lagoon cannot observe a foreign commit; running early sent uncommitted, pre-pivot or rolled-back Album state to Typesense (CR-01)
- [Phase 11]: Phase 11-08: a nested lagoon.Transaction given a root handle returns an error (WR-03) — Treating it as a savepoint would commit independently while its callbacks waited on the parent buffer
- [Phase 11.1]: 11.1-02: The identifier index resolves promoted members itself; go doc does not resolve embedding and stays only as the fallback
- [Phase 11.1]: 11.1-02: docsCommands() collects app command names from the real constructors on backpack.New(&compass.Config{}); TestDocsCommandsMirrorGeneratedMain keeps it in step with internal/build
- [Phase 11.1]: 11.1-02: chroma/v2 v2.27.0 is the only new direct dependency (D-15); tokens map onto tok-* classes without chroma's HTML formatter
- [Phase 11.1]: 11.1-03: the Plugin.php-in-Go section shows modules/party/example_plugin_test.go as a whole-file src= fence because Go methods cannot live in an Example body
- [Phase 11.1]: 11.1-03: docs pages never carry hand-written Go fences; where no runnable Example fits, the page uses prose
- [Phase 11.1]: 11.1-03: bonfire has no duplicate command name check; documented as is and logged as todo bonfire-duplicate-command-names
- [Phase 11.1]: 11.1-04: The transactions page follows lagoon at HEAD: nested lagoon.Transaction needs the outer tx, AfterCommit in a plain GORM transaction warns and skips
- [Phase 11.1]: 11.1-04: Docs examples never register global drivers or engines; toy engines use test-only hooks and driver examples live in the driver package
- [Phase 11.1]: 11.1-04: Database-bound docs snippets run as TestDocs* regions on each module's Postgres harness through export_docs_test.go wrappers
- [Phase 11.1]: 11.1-05: acme.blog walkthrough lives in the root module (docs/examples/blog) and is pinned to make:* output by TestScaffoldLayout
- [Phase 11.1]: 11.1-05: console.PublishCommand takes a withDB parameter because generated zero-argument commands cannot reach the app; Plugin.Commands appends it
- [Phase 11.1]: 11.1-05: walkthrough form omits published_at (cabana has no date field type); blog:publish sets it and the list shows it
- [Phase 11.1]: 11.1-06: violation cases are overlays on testdata/clean with a want.txt naming the one problem each plant must produce
- [Phase 11.1]: 11.1-06: check-phase11.1.sh --named requires every named test to PASS; module TestDocs* and output Examples are derived from source
- [Phase 11.1]: 11.1-06: CRLF and BOM docs pages stay refused, with a message naming the encoding
- [Phase 11.1]: A src= fence that is not a top-level block of a docs page is refused and docs:sync does not rewrite it.
- [Phase 11.1]: Module README src= fences are refused and never captioned; Go fences without src= in a README stay legal.
- [Phase 11.1]: The identifier fallback runs go doc -c, so a span must match the declaration's case.
- [Phase 11.1]: Shell command words follow cobra stripFlags and include env prefixes, go run ./cmd/summer and bin/{app}.
- [Phase 11.2]: 11.2-01: Header at phone widths hides the empty links wrapper at <=720px and tightens gap/padding at <=420px; the handoff spacing overflowed 375px
- [Phase 11.2]: 11.2-01: Page title 'SummerCMS: A new dawn in content management' with titleTemplate %s; header sun is the unmasked original scaled 1.12, hero sun 136px
- [Phase 11.2]: 11.2-01: Copy button falls back to a hidden textarea + execCommand when the Clipboard API is missing; a failed copy leaves the label unchanged
- [Phase 11.2]: 11.2-02: D-42 resolved as defer-tag; no v0.1.0 tag in summercms.go, release path proven on a scratch clone with a local tag, and creating + pushing v0.1.0 and pushing master is a DEPLOY.md launch step
- [Phase 11.2]: 11.2-02: the terminal check's clone override clones into the page's directory (git clone <override> summercms) so the page's cd summercms works with a local path
- [Phase 11.2]: 11.2-03: the gate's --built stage builds in release mode only when the framework checkout build.sh uses has v0.1.0, else a dev build
- [Phase 11.2]: 11.2-03: plugin route coexistence is tested with the plugin's real patterns from surf.BuildRouter(...).Routes() beside cabana's admin patterns on one ServeMux
- [Phase 12]: 12-01: lagoon.ValidateRequest follows Laravel exactly, including that a wildcard rule with nothing to expand adds no attribute; Go expectations are cross-checked against the real Winter validator from the PHP vendor tree
- [Phase 12]: 12-01: lagoon.ErrorKeys(errs, rules) rebuilds the PHP message-bag key order from the rule table, since a Go map carries none
- [Phase 12]: 12-01: exists:table,column compares CAST(column AS TEXT) with the PHP string form of the value, so bad input is a 422, never a Postgres error
- [Phase 12]: 12-01: beachcomber.PageSearcher is optional and SearchPage falls back to SearchIDs, so Engine and existing fakes stay unchanged; Typesense pages are capped at 250
- [Phase 12]: 12-01: sm-user-plugin user groups are additive (migration 202610020001_create_user_groups, User.Groups never serialized); commits are local because the orchestrator forbade pushing
- [Phase 12]: 12-02: Collection strings are trimmed on save (Collection.BeforeSave) because Winter models trim every string attribute; recordings store ' New Shelf ' as 'New Shelf'
- [Phase 12]: 12-02: ProvisionCollection and the resolve fallback have no kind filter, as PHP; a lower-id wishlist can become the active context
- [Phase 12]: 12-02: Parity cases that mutate state are recorded from a fresh PHP tinker reset (parity/fonoteka_reset.php) and replayed from a fresh fonoteka seed per case; id sequences lifted to 8 digits
- [Phase 12]: 12-02: The backend admin album binding keeps a non-provisioning resolver; only API callers provision
- [Phase 12]: 12-02: Winter HttpException pages are embedded as winter_<status>.html with only the app.url stylesheet origin templated
- [Phase 12]: Invitation ValidationExceptions (store required|email, normalizeEmail) answer Winter's 500 error page, as recorded PHP does; the store runs a Laravel RFCValidation email port before normalizeEmail
- [Phase 12]: Invitation mail is a conga Enqueue River job on the invite transaction with the token as lagoon.Encrypted ciphertext; the worker skips superseded or non-pending invitations
- [Phase 12]: Every resolver caller maps errors through writeResolveError (404 page, 409 pending-invitation page, else opaque 500)
- [Phase 12]: Route parity cases needing an invitation token seed it on both sides as secret:invite; flows read a PHP-minted token from the log mailer (php_parity.sh serve-mail, invite-token)
- [Phase 12]: 12-04: slugs and artist name keys port Laravel Str::slug/Str::ascii (czeslaw-niemen, rb), non-Latin scripts dropped
- [Phase 12]: 12-04: album search engine ids are re-gated by access and active collection only, as Scout's query callback; filters stay engine-side
- [Phase 12]: 12-04: sync versions compare at whole seconds so PHP's base64 cursor round-trips; albums/value prints exact sums like sprintf('%.2f')
- [Phase 12]: 12-04: GORM callback handles are used through a NewDB session (cleanSession); WithContext keeps the write's bind variables
- [Phase 12]: 12-04: parity album state uses fixed ids/timestamps on both sides; recorded query strings are restored from the spec
- [Phase 12]: 12-05: attach Thumb stores WinterCMS's broken-image picture for an unusable original instead of erroring (T-12-16)
- [Phase 12]: 12-05: check-phase12.sh --removal runs separately from --all because it edits tracked source; all 25 RC rows recorded in 12-SECURITY-REVIEW.md
- [Phase 12]: 12-05 complete (5/5 plans): Phase 12 ready for /gsd-verify-work 12; sm-user-plugin master is 3 commits ahead of origin and needs a push
- [Phase 12.2]: 12.2-01: Fill's TextUnmarshaler fallback skips sql.Scanner types except lagoon.Date/TimeOfDay, so values that filled through Scan keep that path
- [Phase 12.2]: 12.2-01: empty text fills a zero lagoon.Date/TimeOfDay (NULL); required rejects zero dates and times
- [Phase 12.2]: 12.2-01: PurgeDeferred keeps created-child bindings whose slave type no plugin model resolves (Skipped) instead of deleting blind
- [Phase 12.2]: 12.2-01: framework schedule entry summercms.lagoon[0]:deferred:purge at database.deferred_bindings.purge_at (default 03:00, empty disables)
- [Phase 12.2]: 12.2-02: file routes on id 0 answer 404 when the controller does not declare create, as for a missing session key
- [Phase 12.2]: 12.2-02: the save consumes a file bind whose row is gone or already attached, and an unbind of a file not attached to the owner
- [Phase 12.2]: 12.2-02: preview thumbnails use imageWidth x imageHeight (one given means square), else 240 x 240 in thumbOptions.mode or crop
- [Phase 12.2]: 12.2-02: a fileupload field on a settings form fails boot; a settings datepicker gets the D-19 type check
- [Phase 12.2]: 12.2-02: swagger2openapi folds formData into a multipart requestBody and maps {file} responses to binary
- [Phase 12.2]: 12.2-03: relation forms compile into their own CompiledController (relationModelController) so child saves reuse the controller fill, validate, file and date pipelines
- [Phase 12.2]: 12.2-03: at commit, a bind of a child the session created attaches directly; a bind of an existing record re-runs the full link eligibility, and an ineligible one is a 422 on the relation-manager field
- [Phase 12.2]: 12.2-03: pivot values fill through the pivot form's writable fields only; unknown pivot keys are 422 on link and on PUT .../pivot/{child}
- [Phase 12.2]: 12.2-03: child file routes use X-Child-Session-Key; a saved child needs update to write files (403), child 0 needs create (404 otherwise)
- [Phase 12.2]: 12.2-04: the user approved @internationalized/date 3.12.4 as an exact-pinned direct admin dependency
- [Phase 12.2]: 12.2-04: by user decision, existing belongsToMany relation managers stay deferrable and appear on the create screen unless the relation-manager field has context: update; the create-screen deferral UI is not opt-in
- [Phase 12.2]: 12.2-04: the registry resolves RelationManager lazily because child forms close an import cycle with it
- [Phase 12.2]: 12.2-04: a datetime is emitted as UTC YYYY-MM-DDTHH:MM:SSZ without milliseconds; a stored datetime without a zone is read as UTC
- [Phase 13]: 13-01: River rejects dotted queue names, so the D-03 workerless queues are fonoteka_csv_import, fonoteka_csv_match and fonoteka_wishlist_digest; the dotted PHP names stay the summer_jobs labels and the kinds are unchanged (pending user confirmation before 13-03/13-04 dispatch)
- [Phase 13]: 13-01: conga refuses an unregistered job kind's empty or served queue only while a worker runs; unregistered kinds always insert through the insert-only River client
- [Phase 13]: 13-01: surf registers ServeMux-conflicting, constraint-separated routes as overlap families under one method-less pattern, tried in registration order; same-shape, multi-segment and shadowed members stay boot errors
- [Phase 13]: 13-02: the Discogs store follows PHP's rules (token nullable|string|regex, shared nullable|boolean); a manager's not-shared store deletes the org Discogs credential
- [Phase 13]: 13-02: test secrets, onboarding passwords and an unknown invite token are {{secret:...}} vars set by both seeds, so fixtures hold no literal
- [Phase 13]: 13-02: RegisterEvent.Payload is the register input minus password keys (no IP columns); onboarding routes replay on their own databases for the empty state
- [Phase 13]: 13-03: the wishlist item-added branch extends albumAddedCallback; the digest upsert is ON CONFLICT ... RETURNING (xmax = 0) and only the row-creating insert dispatches the 1800 s wishlist_digest job (no worker until Phase 14)
- [Phase 13]: 13-03: job queues called inside GORM callbacks get jobDB (a fresh statement); a bare cleanSession handle replays the triggering insert under WithContext+Create
- [Phase 13]: 13-03: the purchase emits the moved album's updated event before the bells (PHP sync order); multi-publication goldens compare order-insensitively
- [Phase 13]: 13-04: PHP decodes ISO-8859-2 CSV uploads as Windows-1250 (iconv //IGNORE never fails); the Go parser keeps that, so letters that differ between the encodings import wrongly in both
- [Phase 13]: 13-04: ReleaseFetcher returns the mapped Discogs draft; the Phase 13 default fails (discogs_unavailable), Phase 14 installs the real one with SetReleaseFetcher
- [Phase 13]: 13-04: CSV uploads live in the private bucket golem15.fonoteka.csv.bucket_url (default file://./storage/app) under fonoteka-csv/<uid>/<uuid>.csv
- [Phase 13]: 13-05: the pubfail counter is in memory and one per app (Go process); PHP shared it through the host file cache, so several Go instances multiply the guessing budget
- [Phase 13]: 13-05: PubfailCounter.Begin reserves a failure slot so the check and the count are serialized; concurrent failures never pass 10 per window (PHP can overshoot)
- [Phase 13]: 13-05: public facets order artists and genres by name COLLATE C (the recording SQLite order) with the id breaking ties
- [Phase 13]: 13-05: the public search is SearchAlbums with AlbumSearchParams.Public (TEXT_FIELDS_PUBLIC, no ratings join, collection-only re-gate); authenticated callers unchanged
- [Phase 13]: 13-06: the user plugin controllers floor is its pre-phase 68.8% (now 70.6%); T-13-SC reviewed at its strictest declaration (medium, mitigate) with golang.org/x/text pinned at v0.42.0 by the gate
- [Phase 13]: 13-06: tests that queue River jobs run on dedicated databases; a process booting several apps boots the job-writing one last (process-wide job dispatcher, deferred)
- [Phase 14]: 14-01: fetchguard.Fetch ignores TrustedMode; only Client honours it
- [Phase 14]: 14-01: upstream sidecars refuse unmasked Authorization/X-Api-Key; multipart file parts stored as sha256 only
- [Phase 14]: 14-01: generated mains call sunscreen.InstallDefault(os.Stderr) before compass.Load; sunscreen also redacts dashed sk- keys
- [Phase 14]: 14-01: INTG-01/INTG-02/API-08/SRCH-02 stay Pending until 14-02..14-06 deliver them
- [Phase 14]: 14-02: ReleaseFetcher.FetchRelease takes the request's db and config and returns *csv.Map (stateless fetcher, no app captured at Boot)
- [Phase 14]: 14-02: Discogs pick cases use a separate csv-discogs seed state so existing csv fixtures keep alice without a token
- [Phase 14]: 14-02: every parity case replays through an upstream fake; no sidecar means an empty fake, so unrecorded vendor calls fail
- [Phase 14]: 14-02: digest worker mails only when its DELETE removed the queue row (once per window under races)
- [Phase 14]: 14-02: reindex prefixes both the album and the legacy items index with search.prefix
- [Phase 14]: 14-02: INTG-01 stays Pending until 14-03 mounts the Discogs routes
- [Phase 14]: Parity over plan wording: a second identical apply re-imports covers and dry_run writes the covers and non-draft catalog fields, as PHP does
- [Phase 14]: apply-release runs without an enclosing transaction, as PHP, so cover downloads never hold the album row lock
- [Phase 14]: Discogs-route covers go through classes.NewGuardedCoverImporter (fetchguard.Client, GuzzleHttp/7 UA) so the replay answers them; Phase 12 cover imports deferred
- [Phase 14]: discogs is an additive parity seed extra; A Love Supreme carries discogs_id 2002 with the album state
- [Phase 14]: sm-golem-plugin is a shared core plugin repo mounted at plugins/golem15/golem; app tests are made bump-neutral first so pointer bumps stay gitlink-only (14-04)
- [Phase 14]: ai-credential/test tests the caller's own stored key only, as PHP UserAiConfig (14-04)
- [Phase 14]: AdminVisionModel(ctx, db) is a stateless seam fed by the golem vision model; only the admin tier sets AIConfig.Trusted (14-04)
- [Phase 14]: User and org AI base_url overrides pass security.AssertSafeURL at resolve time; a refusal is Winter's 500 page (14-04)
- [Phase 14]: fetchguard.IsPrivateAddr exported for resolve-time address checks (14-04)
- [Phase 14]: 14-05: surf answers OPTIONS on CORS paths with Laravel HandleCors headers (Cache-Control always; preflight echoes the requested method and headers, Vary, PHP's default Content-Type)
- [Phase 14]: 14-05: the feedback settings screen's code is feedback (cabana keys settings screens by code across plugins), not WinterCMS's settings
- [Phase 14]: 14-05: the G15Office job is a conga Dispatch with a summer_jobs row labelled with the PHP job class, 3 attempts on queue feedback, on a fetchguard AllowHostsMode client for the base URL's host (https only)
- [Phase 14]: 14-05: shared-plugin vendor sidecars are replayed from the plugin's own testdata copy; the application keeps the recorded original with a rows golden and a byte-equality test
- [Phase 14]: 14-06: the sm-feedback-plugin routes are pinned and fuzzed by the plugin's own tests, so the fonoteka plugin does not require the shared plugin module
- [Phase 14]: 14-06: every mitigated threat has a removal row (43); --evidence requires one per mitigated threat
- [Phase 14]: 14-06: two Phase 14 functions are exempt from the 80% function floor with reasons (cover_importer fetch, validateDiscogsInput); internal/pgtest exempt from the package floor
- [Phase 12.1]: 12.1-01: bulk, record and toolbar/widget actions are three namespaces per controller; create and delete are reserved in each
- [Phase 12.1]: 12.1-01: CRUDService.BulkAction and RecordAction take the action name and resolve declared-and-registered themselves
- [Phase 12.1]: 12.1-01: a 403 on save is the forbidden banner (not a toast) for every 403, plugin refusal or framework denial
- [Phase 12.1]: 12.1-01: plugin writes to a soft-deleted record must use tx.Unscoped(); cabana's update write does
- [Phase 12.1]: 12.1-01: RecordActions.vue is unmounted until 12.1-02 adds the preview footer; it emits done with the toast text for its host to show
- [Phase 12.1]: 12.1-02: v0.1.3 is an annotated local tag on df5cace (user chose tag-local); pushing master and v0.1.3 is a pending release step, and the sm-user-plugin push of plan 05 waits for it
- [Phase 12.1]: 12.1-02: a form with recordActions must declare preview:; preview: {} enables the screen without a status hint and a null preview: stops boot
- [Phase 12.1]: 12.1-02: password, permissioneditor and preset are refused on settings forms and relation forms; a type: password field must be listed in FormVirtualFields
- [Phase 12.1]: 12.1-02: relation locks are enforced on form create and update only; relation-manager link and unlink routes do not ask RelationLockProvider (open for plan 05)
- [Phase 12.1]: 12.1-02: the SPA sends a permissioneditor field on every update reduced to offered codes; a locked code with a stored value outside the mode's set makes every save a 403 (open for plan 05)
- [Phase 12.1]: 12.1-02: pact.FilterOptions keeps its signature and is asked on the admin controller before the model
- [Phase 12.1]: Users admin controller and its guard live in sm-user-plugin: a privileged-group member's email, password and permanent delete need golem15.users.manage_privileged_groups; refusals are cabana.ForbiddenError (D-30)
- [Phase 12.1]: HasPermission ports Winter line by line: a leading asterisk is a wildcard on the asked code only; PermissionSet.Scan fails on content that is not a JSON object rather than read it as empty
- [Phase 12.1]: A user created in the admin gets has_self_set_password true and stays not activated; the invitation carries the login and the activation link, never the password
- [Phase 12.1]: Plugin admin tests boot the plugin alone through newAdminEnv; mail is read from the log driver because the mailer service cannot be replaced after party.Activate
- [Phase 12.1]: 12.1-04: the user form's groups field is the only writer of users_groups; privileged groups are locked through cabana.RelationLockProvider and need golem15.users.manage_privileged_groups on create and update
- [Phase 12.1]: 12.1-04: creating a group with a privileged code, changing a code to or from one and deleting a privileged group need the extra permission; name, description and frontend permissions of a privileged group need only access_groups
- [Phase 12.1]: 12.1-04: Organisation gained the Go relation field Members (no column) because the relation manager requires the relation on the owner model
- [Phase 12.1]: 12.1-04: the organisation members manager links only users without an organisation; a user moves by unlink then link, or through the user form's organisation field
- [Phase 12.1]: 12.1-04: nothing was pushed; the fonoteka.go pointer names an unpublished plugin commit, so the order is framework tag, then sm-user-plugin (plan 05 Task 3), then fonoteka.go
- [Phase 14.1]: sessionApp does not phrasebook.Activate; EN/PL 409 tests activate catalogs and set locale with towel.WithLocale — sessionApp Boots the plugin only; Get would return the raw last_method_blocked key
- [Phase 14.1]: Unique (provider, provider_id) is table-global; EN/PL 409 seeds use provider_id fb-only-<locale> — oauth_identities_provider_identity_unique is not per-user
- [Phase 14.1]: Task 3 needed no parity_test.go change: 14.1-01 already set expectedPortedRoutes 175 and rewrote assertPortedMismatch — Corpus already recorded 175/175 passing 175 failing 0 unrecorded 0 pending 0
- [Phase 14.2.1]: Task 2 locked golem15-prefix: Go tables are golem15_translate_*; PHP pin keeps winter_translate_* out of Go DDL.
- [Phase 14.2.1]: Sibling plugin go.mod replace is ../summercms.go so isolated D-16 go test passes; host mounts replace via go.work.
- [Phase 14.2.1]: MorphName is an import-stable string, never reflect.Type.String(); Translated applies D-11 fallback and TranslatedExact does not.
- [Phase 14.2.1]: Locales is HasAdminControllers plus HasNavigation (not HasSettings); Fillable excludes is_default/sort_order; makeDefault uses GORM Updates maps.
- [Phase 14.2.1]: Task 2 locked grzyby-submodules: proof host is sm-grzybyfunkcjonalne-app with plugins/golem15/user and plugins/golem15/translate; host go.work replace points at ../summercms.go.
- [Phase 14.2.1]: 14.2.1-04 security review was executor-self-performed (no gsd-security-auditor in this Cursor session); every cited TestName was re-run. Full cabana `-race` needs `-timeout 30m`. WhereIndex falls back to the host column when no index row matches (PHP transWhere).
- [Phase 14.2.1]: writer-exact: add TranslatedExact to cabana.TranslationWriter; AdminWriter delegates to classes.TranslatedExact
- [Phase 14.2.1]: RelationService.writer stays unexported; relations() Lookup matches crud(); no new HTTP route — WR-02 closes on existing child routes; cabana must not export a plugin-facing writer field
- [Phase 15]: Go schema is squashed to golem15_journal_* from PHP pin 02110eb1; rainlab-era names are not created — D-04 / D-01 squash; execute verified PHP pin HEAD
- [Phase 15]: translate replace stays in journal go.work, not go.mod, so the host workspace module path does not conflict — Host use ./plugins/golem15/translate vs sibling-relative replace
- [Phase 15]: Uncategorized is seeded (A4 PHP seed_all_tables still inserts it); first post is not seeded — Execute-time read of seed_all_tables.php at pin 02110eb1
- [Phase 15]: slug is in Translatable() as well as TranslatableIndexes() because SetTranslated requires a declared translatable field — PHP Post.php translatable includes slug with index
- [Phase 15]: D-11 is a no-op — post content is mlmarkdown; FormatHTML lives in the plugin (goldmark Footnote+Table+WithAttribute + rejectUnsafe); cabana.RenderMarkdown is not edited
- [Phase 15]: preset is valid only on type: text; post/category mltext slugs omit preset; tag slug stays type: text with preset
- [Phase 15]: journal:export-posts/import-posts are JSON-per-slug files matching PHP ExportPosts/ImportPosts (--path, --dry-run, --force), not a Winter CSV framework
- [Phase 15]: Posts toolbar extra buttons are HasAdminActions names exportPosts/importPosts with golem15.journal.access_import_export; create/delete stay reserved
- [Phase 15]: NavigationItem has no IconSvg; journal-icon.svg is go:embed in AdminFS; user_id is WritableForeignKey and FormBeforeCreate stamps it when empty
- [Phase 15]: 15-04 security review was executor-self-performed (no gsd-security-auditor in this Cursor session); every cited TestName was re-run. Wave-3 HTTP tests stay at plugin root; controllers/api holds clampPerPage/media path units plus EndToEnd stubs.
- [Phase 15]: Host journal gitlink is a nested clone; bump via file:// fetch with protocol.file.allow=always. TestSearchGateOff must not skip.
- [Phase 14.3]: bonfire accepts new as a bare command name for summer new
- [Phase 14.3]: summer new compose.yaml publishes postgres:16 on 127.0.0.1:54320 only (user/password summer)
- [Phase 14.3]: summer new falls back to SummerCMS <summer@localhost> only for missing git identity keys
- [Phase 14.3]: plugin:add writes go.work use paths relative to the go.work directory
- [Phase 14.3]: Bootstrap script lives in modules/sunrise (new.sh + Script()); served by the site plugin at GET /new as text/plain
- [Phase 14.3]: new.sh installs summer with GOWORK=off and never deletes, resets or force-fetches; refusals for branch, dirty, moved-tag and non-fast-forward checkouts
- [Phase 14.3]: 14.3-03: the gated terminal check stops summer serve's app binary (pkill -P) before summer, since summer serve has no signal handling
- [Phase 14.3]: Phase 14.3 gate is scripts/check-phase14.3.sh --all: one process per stage (unit, generator, bootstrap, terminal, acceptance, security), a named PASS line per required test, goldens unchanged by the run
- [Phase 14.3]: summer new goldens live in internal/build/testdata/new and new-no-plugin and are rewritten only with go test -update; the golden app key is base64 of bytes 0x00..0x1f
- [Phase 14.3]: Tests that commit in temp git repos disable detached auto maintenance (maintenance.auto=false, gc.auto=0) to avoid cleanup races with git 2.55
- [Phase 16]: vue-journal-layer pages gate and slim API rows in useAsyncData transforms (toPostView/toListPost) so the SSR payload never carries unsafe HTML, raw content or translations
- [Phase 16]: Journal SSR, sitemap and RSS fetches forward x-forwarded-for with the Nuxt peer address appended
- [Phase 16]: vue-grzybyfunkcjonalne-app is a private repo checked over SSH; only vue-journal-layer must be public (D-04)
- [Phase 16]: Journal author links use author.login (only key on post rows); the author page canonicalises to the endpoint slug
- [Phase 16]: Under routeRules swr the site app records the visitor address in event.context (Nitro request hook) and overrides useJournalApi, because Nitro's cached handler strips request headers; the proper fix belongs in vue-journal-layer
- [Phase 16]: Site page URLs answer errors with the HTML error page even for fetch-like requests (server/plugins/html-errors.ts); /api/, /_ and .json paths keep Nuxt JSON errors
- [Phase 16]: Site rich-text copy stays in pl.json (i18n compilation.strictMessage false); contact data lives in app/data/contact.ts
- [Phase 16]: Plugin commands in a summer-build host open and publish the database themselves (lookupDB via lagoon); the journal import runs as grzyby:import-posts because the generated main.go publishes no DB for plugin commands
- [Phase 16]: migrate.sh imports from a copy of the PHP export with empty translations [] arrays normalized to {}; the importer fix is deferred to sm-journal-plugin
- [Phase 16]: The URL rehearsal gives each URL its own X-Forwarded-For visitor (203.0.113.N) so ~90 SSR pages stay under Go's 120/min per-visitor journal limit
- [Phase 16]: The staging nginx image location declares its own types map and matches extensions case-insensitively so images keep an image type under nosniff
- [Phase 16]: 16-04: staging keeps the UAT basic-auth gate (off for loopback via nginx geo); smoke and curl checks run on the server
- [Phase 16]: 16-04: staging host is uat.grzybyfunkcjonalne.pl; first-journal-post skipped in the import so Go serves the 62 live posts
- [Phase 16]: 16-04: fileblob uploads bucket uses no_tmp_dir=true because /tmp on the server is tmpfs
- [Phase 16]: No layer util needed a fix in 16-05, so vue-journal-layer gets no v0.1.1 tag; the app keeps v0.1.0
- [Phase 16]: The malformed-GA-id SSR test asserts the id appears only as the JSON string in window.__NUXT__.config, since Nuxt serializes public runtime config
- [Phase 16]: The grzyby console test harness creates an ICU pl-PL database via GRZYBY_TEST_ADMIN_DSN or testcontainers and fails, not skips, without one outside -short
- [Phase 16.1]: Relation list search uses correlated EXISTS for every relation type (no outer JOIN); to-many sort uses a correlated string_agg subquery; names come from GORM's parsed relationship
- [Phase 16.1]: meta.enabledLocales lists the default content locale first, then the writer's order
- [Phase 16.1]: Journal FormatHTML renders raw HTML (goldmark WithUnsafe) behind the unchanged rejectUnsafe gate
- [Phase 16.1]: List state: URL plus per-list localStorage key summer-admin.list:<controller>, restored before the first list request, cleared on logout
- [Phase 16.1]: 16.1-02: summercms v0.1.16 tagged on d77393b after the human chose docker-first; journal a675f2f and host cac2ee7 pushed; UAT redeployed
- [Phase 16.1]: 16.1-02: UAT Go log checks read /var/log/supervisor/grzybyfunkcjonalne-go.log directly; supervisorctl tail fails on ANSI escapes in GORM log lines and makes grep-count checks vacuous
- [Phase 16.1]: 16.1-02: D-01/D-03 end-of-phase UAT checks are human-reported but not independently confirmed; queued for /msd-verify-work
- [Phase 16.1]: FormatHTML refuse tests require an error and empty result for every unsafe input: with html.WithUnsafe the gate is the only XSS defence
- [Phase 16.2]: pact.FormFieldType.ListValue is func(ctx context.Context, value any) string (human adjust at the 16.2-01 checkpoint); plans 03, 05 and 06 use the ctx form
- [Phase 16.2]: Plugin field type registry is built per Activate by compileFieldTypes and passed down the compile functions; existing compile functions stay nil-registry wrappers
- [Phase 16.2]: Only pact.FormFieldError from a plugin field type's Decode or Upload is a 422; every other plugin error, Load included, is the opaque 500
- [Phase 16.2]: Untouched plugin field values are skipped on update by canonical encoding/json comparison with the served value (stored value through Load), per field and locale
- [Phase 16.2]: TipTap 3.31.4 pinned exactly for all five editor packages after the human package check (packages approved)
- [Phase 16.2]: Blog Editor bundle built through the Vite application pipeline (single ES entry, fixed names, exports kept) because library mode never minifies ES whitespace
- [Phase 16.2]: Blog Editor link parsing, rendering and commands use isPolicyUrl (http, https, mailto, relative); on* attributes are never captured; external values load outside the undo history
- [Phase 16.2]: Uncaptioned editor images are inline (<p><img></p>); captioned ones are figure with figureImage and figcaption
- [Phase 16.2]: Plan 03: the Journal HTML policy needed exactly the four editor-output rules (figure data-embed/data-id, img alt, ol start); the corpus showed no fifth
- [Phase 16.2]: Plan 03: editor uploads must sniff as a JPEG/PNG/GIF/WebP type matching the extension; uniqueMediaKey errors instead of overwriting when all numbered names are taken
- [Phase 16.2]: Plan 03: admin saves of post content write the Decode output to content and content_html per written locale (FormBefore*/FormAfter* hooks), no goldmark; category 422 gate retired
- [Phase 16.2]: 16.2-04: vue-journal-layer mirrors exactly the four Go editor-output rules; embeds expand only at the v-html sinks, after sanitizeJournalHtml
- [Phase 16.2]: 16.2-04: vue-journal-layer v0.2.0 published (tag 042b65d0 -> 759e684e, human reply "push"); vue-grzybyfunkcjonalne-app main 465286a pins it; a defect gets v0.2.1
- [Phase 16.2]: 16.2-05: summercms v0.1.18 released (human: release v0.1.18); a later defect gets v0.1.19
- [Phase 16.2]: 16.2-05: UAT Nuxt rollback copy is nuxt/.output.prev; the site-ok log check reads /var/log/supervisor/grzybyfunkcjonalne-go.log from the restart River anchor (supervisorctl tail fails on UAT)
- [Phase 16.3]: D-03 literal: one method-allowed scope must match every required code; ScopeLimit keeps per-scope pattern slices — Locked CONTEXT reading; multi-code lists are stricter than a union
- [Phase 16.3]: CLI uses party.Activated at Run time; cabana.RuntimeCommands(app) signature unchanged (Option A) — User-confirmed Option A so host apps only bump versions
- [Phase 16.3]: ResolveBackend is Bearer-only (cookie-less JWT guard) — Plugin write routes have no CSRF; cookie auth must not drive them
- [Phase 16.3]: 16.3-02: ApiTokensPanel + ApiTokenCreateDialog on /account; plaintext dialog-local only; shared ConfirmDialog for revoke and close-without-copy — UI-SPEC component split
- [Phase 16.3]: 16.3-02: expired from API drives Expired chip; dates use CellValue local YYYY-MM-DD / YYYY-MM-DD HH:mm — Plan decisions; no new date format
- [Phase 16.3]: Released summercms v0.1.21 after human resume release v0.1.21; Journal requires it; grzybyfunkcjonalne journal gitlink bumped; summercmsio not bumped
- [Phase 16.3]: Journal API uses cabana.ResolveBackend with cover golem15.journal.*; ErrTokenScope maps to PHP Insufficient permissions
- [Phase 16.3]: 16.3-04: full unit coverage + scripts/check-phase16.3.sh; postgres PASS on Docker host; nyquist_compliant true — Lean rule 3 phase-ending plan
- [Phase 16.3]: Public Journal Show under optional-auth needs published_at in the past, not only published:true — TestJournalTokens fixture
- [Phase 14.2]: storage.uploads.serve mounts attach.StaticHandlerPublic from surf (owner summercms.surf), resolving bucket and DB per request, with nosniff; default off
- [Phase 14.2]: http.access_log logs one 'http request' record (method, pattern, status, duration_ms; path only when unmatched); default off
- [Phase 14.2]: fonoteka console commands use commandDatabase/commandBucket: published handle, else open+publish; closers close what they opened
- [Phase 14.2]: fonoteka:dev-seed allowed only for SUMMER_ENV local or dev; prints lines after commit; secret shown only on mint
- [Phase 14.2]: dev-stack.sh runs Go processes with TMPDIR=tmp/dev-stack/go-tmp because fileblob renames uploads in from os.TempDir() and fails across devices when /tmp is a tmpfs (deployment fix deferred to Phase 20)
- [Phase 14.2]: dev-stack.sh parses the env file as KEY=value data instead of sourcing it; pnpm dev gets only the NUXT_* values, not the SUMMER_* secrets
- [Phase 14.2]: dev-stack.sh e2e requires a running stack and the guard before Playwright; e2e-verdict refuses skips, flaky, unexpected and zero-expected reports
- [Phase 14.2]: Album model rules see nullable text columns as strings, so an empty format passes like PHP (14.2-03)
- [Phase 14.2]: A 5xx written after the client cancelled the request is logged as 499 by the surf access log (14.2-03)
- [Phase 14.2]: dev-seed creates seeded albums with artist rows and backfills them on rerun (14.2-03)
- [Phase 14.2]: Item 11 PHP cross-check passed by classification: GD-undecodable fixture and missing PHP Centrifugo config, no contract difference (14.2-03)
- [Phase 14.2]: The surf uploads route owns storage.uploads.public_path_prefix: BuildRouter fails when a plugin route sits at or under it
- [Phase 14.2]: check-phase14.2.sh --contracts blocks on the phase's own contract test and only reports the whole pnpm test:contracts suite (missing-catalog.test.mjs predates the phase)
- [Phase 14.2]: check-phase14.2.sh leaves TMPDIR to the caller: a TMPDIR inside a go.work tree makes internal/build scaffold tests edit that go.work
- [Phase 17]: 17-01: menu item type registry is modules/signpost (party activates it before Boot); host URL overrides at menu_items.patterns (YAML only); list types use PatternFrom (one level) so a record type's host override reaches list children
- [Phase 17]: 17-01: framework sanitizer modules/seaglass on bluemonday v1.0.27 (Journal policy without Blog Editor rules, plus tel:); D-10 Journal clause dropped, category content stays blogeditor
- [Phase 17]: 17-01: dependent dropdown options for a repeater row are addressed as {field}=repeater.nested; sibling values reach DependentOptions as strings (nil when empty) on the route and the save recheck
- [Phase 17]: 17-01: reorder saves write parent and sort columns via UpdateColumns (no model hooks); the partition is a belongsTo relation field of the form; stale sets answer 409 reorder_stale
- [Phase 17]: 17-02: framework released as v0.1.23 (annotated, ae75a92 on e2a9529); plan 05 bumps plugin and host requires to it
- [Phase 17]: 17-02: read-only richeditor HTML goes through a client display allowlist because stored values are not sanitized on read
- [Phase 17]: 17-02: repeater rows use a plain aria-expanded disclosure instead of Reka Collapsible so 422 focus reaches collapsed rows
- [Phase 17]: 17-03: Sitemap definition theme key is the constant default; imports always write it and report the PHP theme
- [Phase 17]: 17-03: Journal URL placeholders follow PHP getHeadlessUrlParams order (id, slug, category, year, month, day); translated slugs use PHP fallback via one batched attribute read
- [Phase 17]: 17-03: journal-category nesting returns the subtree per the plan truth; frozen PHP returned every category (deferred item for plan 05 parity)
- [Phase 17]: 17-03: Host tier override lives in config/golem15.yaml under sitemap.priority_tiers and replaces the default list
- [Phase 17]: 17-04: D-16/D-17 API shapes approved verbatim ("approve"); /_menu/api/v1/menus/{code} and /_faq/api/v1/faqs/{slug} are now one-way
- [Phase 17]: 17-04: menu list vs single registry type decided by the resolved item having no URL (pact.ResolvedMenuItem contract); generated children follow stored children
- [Phase 17]: 17-04: public API path params validated in handlers (not route Where) so every miss is the JSON 404
- [Phase 17]: 17-04: FAQ answers and description re-sanitized with seaglass on API output; menu URLs pass AllowedURL on save and on output
- [Phase 17]: 17-05: grzybyfunkcjonalne menu code main-menu; Winter menu types map by the built-in link->url and blog_categories->all-journal-categories, no --map; six orphan link entries dropped
- [Phase 17]: 17-05: sitemap:import-winter --page-url copies a Winter cms-page item as a url item (blog=/blog on grzybyfunkcjonalne); the PHP site had no sitemap settings, plugin defaults apply
- [Phase 17]: 17-05: UAT remote checks run on the server through nginx with curl --resolve to loopback; production deploy and summercms.go push not approved
- [Phase 17]: 17-06: cabana singleton route reruns its transaction once on a Postgres unique violation from SingletonRecord (first-open race); fix 6312929 needs the next framework tag
- [Phase 17]: 17-06: scripts/check-phase17.sh is the Phase 17 gate; every Phase 17 test name sits in one stage list (go, plugins, admin, layers, postgres)
- [Phase 17]: 17-06: protocol-relative menu URLs stay anchors (D-16 rule) in the server and layer; the plan's text-without-href wording is a deferred question
- [Phase 18]: Phase 18 plan 01 release gate: framework tag v0.1.25 held until the phase gate after plan 07 (human reply: hold); nothing pushed or tagged, plans 02 to 06 build against the checkout through go.work replace — One tag for the whole phase with the unit tests of plan 07 included; the plugin plans use go.work replace and need no published version, so holding costs only a later bump
- [Phase 18]: Money is a decimal string on the wire and in the SPA (never a float, never rounded or truncated); MoneyField emits the raw compact text for a malformed or too-precise draft so the server 422 stays the gate — D-07 and D-10 forbid rounding and truncation; emitting the last valid prefix on a bad draft would let Save write a truncated amount
- [Phase 18]: Settings secrets are write-only: the GET omits the value and reports meta.secrets.<field> as stored, env or empty; on PUT an absent key or empty string keeps the stored value and JSON null clears it; meta is omitted when there is nothing to report so existing payloads stay byte-identical — The value must never leave the server (D-13, T-18-03); omitempty on meta and danger keeps the Phase 10 conformance fixtures and the PHP-shaped payloads unchanged
- [Phase 18]: 18-02: cart/details v2 row format is v2-research (long keys, top-level v:2); legacy JMS rows decoded in place, never rewritten
- [Phase 18]: 18-02: notification route verifies (VerifyNotification on the capped raw body) before any database lookup; operators.PaymentContext carries Tx for in-transaction transitions
- [Phase 18]: 18-02: NBP uses Table A of the day before with a 5-day fallback (PHP semantics); Fixer keeps the PHP endpoint with optional fixer_access_key
- [Phase 18]: Admin amounts use money.Stored (struct around Amount4) because cabana money fields/columns accept only string or struct Scanner/Valuer columns
- [Phase 18]: Quote cart widget runs one cabana action (quote_cart) with payload operations; refusals answer 403 ForbiddenError so the admin toasts the localized reason
- [Phase 18]: Paymentgateway admin copy is complete in lang/{en,pl}; plan 04 settings/mailer keys listed in 18-03-SUMMARY
- [Phase 18]: 18-04: cabana settings forms serve method-backed dropdowns from the settings model (pact.DropdownOptionsProvider); framework tag still held to the phase gate
- [Phase 18]: 18-04: default_currency validated as required|in:<ISO codes> plus a BeforeValidate 422 settings.default_currency_required (lagoon has no string/size rule)
- [Phase 18]: 18-04: order mails only with orders on, payment mails (customer and staff) only with orders off, as PHP registerSubscribers; mails sent via lagoon.AfterCommit, events.MailSender seam for apps and tests
- [Phase 18]: 18-05: Stripe operator defaults to hosted Checkout; the Details parameter stripe_checkout_mode overrides the checkout_mode setting per order
- [Phase 18]: 18-05: PaymentIntent created without a payment method answers the requires_action bundle for requires_payment_method; only a confirmed payment method ending there is a decline
- [Phase 18]: 18-05: Stripe webhooks are verified by signature only (webhook.ValidatePayload) with a minimal local event decode, so the endpoint API version is irrelevant
- [Phase 18]: 18-05: Authentic notifications for no payment of this application answer 200 ignored via the new paymentgateway sentinel operators.ErrNotificationIgnored
- [Phase 18]: 18-05: All four Stripe secrets fall back to golem15.pgstripe.<column> config keys (SUMMER_GOLEM15__PGSTRIPE__*) while their encrypted column is empty
- [Phase 18]: 18-06: customer API contract api-research (one-way): twelve endpoints under /_paymentgateway/api/v1, money {amount, amount_minor, currency}, 404 for unknown and forbidden orders, 409 Order state changed
- [Phase 18]: 18-06: customer cancel in AwaitingPayment uses the PHP Cancel transition after canceling open cash and bank transfer payments; process rolls back on an operator error so the order stays Placed
- [Phase 18]: 18-06: GET /orders behind api.RequireJWT (jwt guard, Bearer only); layer auth hook is the Nuxt provide paymentGatewayAuth
- [Phase 18]: 18-06: fixtures: none for shop-type legacy rows (human); three repos published, vue-paymentgateway-layer tagged v0.1.0
- [Phase 18]: 18-07 push-all: four repositories pushed and the framework tagged v0.1.25 (held since 18-01)
- [Phase 18]: 18-07 money fields accept trailing zeros beyond the scale (fix eb60c32), published in v0.1.25
- [Phase 18]: 18-07 fixtures none: synthetic legacy rows stand in for D-11; TestLegacyFixtures picks up shop-export rows later
- [Phase 18]: 18-07 the two pgstripe reliability tests are 18.1; replay_and_race is Phase 18 idempotency evidence
- [Phase 18]: 18-08: payment_intent.payment_failed is not terminal: the Stripe operator logs one Warn line and leaves the payment Sent; only checkout.session.async_payment_failed rejects; checkout.session.expired and payment_intent.canceled stay 200 no-ops closed by pg:cancel-sent-payments — Stripe sends the event on every declined attempt and the customer may retry on the same Checkout page or client secret; Reject is final, and PHP never moved a payment from a notification except to Accept (gap 1 of 18-VERIFICATION, A-CR-01)
- [Phase 18]: 18-08: a paid signal the payment machine refuses is logged at Error level (refuses Accept) for never-accepted payments; Accepted and Refunded are silent; no payment event, automatic refund or data write — The log line is the alert hook; a new event would be a public contract on a shared plugin with no listener, and a refund moves money (A-CR-02 logging half; session expiry on cancel stays open)
- [Phase 18]: 18-08 push: sm-pgstripe-plugin main pushed to 0313820 after the human reply push; no tag, summercms.go not pushed — Task 4 checkpoint:decision; no application mounts the plugin yet, so no pointer bump
- [Phase 18.1]: 18.1-01 release gate: human replied "hold"; framework tag v0.1.26 held until plan 06, nothing pushed (22 commits origin/master..master local)
- [Phase 18.1]: 18.1-01: flat reorder inferred WinterCMS-Sortable style (no parentColumn key, no parent_id column); flat flag omitempty keeps tree payloads byte-identical
- [Phase 18.1]: 18.1-01: required taglist is checked on its normalised tags; generic required rule skipped for taglist as for repeater
- [Phase 18.1]: 18.1-02: baseline JSON columns (stripe_prices, currency_prices, features) are JSONB (human: baseline-jsonb); lagoon.Jsonable works unchanged
- [Phase 18.1]: 18.1-02: pgsub gateways never write the DB; the service stores the customer id in its own short transaction and writes cancel_at_period_end after a successful cancel at period end
- [Phase 18.1]: 18.1-02: sm-paymentgatewaysubscriptions-plugin created with tea and main pushed at aad4647 (human: create-and-push)
- [Phase 18.1]: 18.1-03: subscription webhook answers keep PHP plain-text bodies (reply php-plain-text), text/plain without trailing newline
- [Phase 18.1]: 18.1-03: Stripe plan sync looks prices up with Prices List (lookup_keys, active) instead of PHP Prices Search
- [Phase 18.1]: 18.1-03: golem15.pgstripesubscriptions is its own plugin in sm-pgstripe-plugin/subscriptions; the root package never imports it (payments-only builds link no pgsub)
- [Phase 18.1]: 18.1-03: pgstripe:import-winter --webhook-secret-target defaults to payments (Phase 18 mapping); ReadWinterSettingsFor is additive
- [Phase 18.1]: Customer API contract confirmed as api-research (18.1-04 Task 4): money objects per currency, 404 Subscription not found when the user has none, latest subscription with has_access and in_grace_period
- [Phase 18.1]: pgsub plan sync admin actions never fail after the first gateway call: per-plan failures are code: message lines in the result, so provider ids saved in the bulk transaction survive
- [Phase 18.1]: Customer API redirect targets come only from api.success_url, api.cancel_url and api.portal_return_url, resolved at boot (absolute http(s), a path joined to app.url, or empty = app.url); malformed values fail boot naming the key
- [Phase 18.1]: Layer release 18.1-05: human reply "hold"; vue-paymentgateway-layer main 3dea9cd unpushed, no v0.2.0 tag; plan 06 publishes
- [Phase 18.1]: Layer subscription redirects follow only absolute http(s) checkout_url/portal_url from the API answer
- [Phase 18.1]: PgPlans shows the chosen currency price or the plan first price; subscribe sends the currency of the price shown
- [Phase 18.1]: Phase 18.1 published on push-all: summercms v0.1.26 (taglist, flat reorder, localized reorder.flat fix), vue-paymentgateway-layer v0.2.0, pgsub and pgstripe require summercms v0.1.26
- [Phase 18.1]: scripts/check-phase18.1.sh is the phase gate; it never installs packages and fails loudly on a pnpm store mismatch; the layer check-git-install.sh stays open
- [Phase 19.3]: 19.3-01: cooler memory cache driver is otter v2 (bounded); stdlib map rejected as unbounded
- [Phase 19.3]: 19.3-01: unreachable Redis at start-up is a warning; missing/malformed redis.url with a Redis driver is a boot error naming the key
- [Phase 19.3]: 19.3-01: cooler key prefix = cache.prefix or slug(app.name)+':' (default summer:); cache keys <prefix>cache:, limiter keys <prefix>rl:
- [Phase 19.3]: 19.3-01: Remember degrades on cache errors; Redis Flush is SCAN MATCH <prefix>cache:* + UNLINK, never FLUSHDB
- [Phase 19.3]: 19.3-02: bouncer cookie transport reads auth.cookie.* through CookieConfigSource (compass satisfies it, bouncer imports no framework module); TokenCookie Max-Age is the guard's refresh window
- [Phase 19.3]: 19.3-02: credentialed CORS with a * origin, uncompilable or PHP-delimited origin patterns fail boot; dynamic CORS answers echo the exact origin with Vary: Origin; the non-credentialed * answer is unchanged
- [Phase 19.3]: 19.3-02: route-group CORS uses cors:<profile> from http.cors_profiles.<name>; the router registers OPTIONS preflight routes (owner surf) and refuses overlap with http.cors.paths
- [Phase 19.3]: 19.3-02: Turnstile open/closed follows PHP; with a secret, a transport error or timeout is 422 in both modes and a missing token fails without a siteverify call; a published *bouncer.Turnstile or bouncer.TurnstileWriter overrides the defaults
- [Phase 19.3]: 19.3-02: /api-docs is opt-in (http.api_docs.enabled), spec from exactly one surf.APIDocsProvider converted at boot, swagger-ui-dist pinned to 5.33.1 with sha384 SRI and a nonce CSP
- [Phase 19.3]: 19.3-03: S3 driver opt-in via summer.yaml build.tags summercms_s3 (attach.S3BuildTag); untagged binaries refuse s3:// at boot naming the tag
- [Phase 19.3]: 19.3-03: attach.DiskExtension derives the stored extension from the sniffed type (fixed table, script-capable client extensions become txt/bin); file_name keeps the client name
- [Phase 19.3]: 19.3-03: storage.uploads.public_url applies to every attach URL; start-up line and docs require a public-files-only bucket
- [Phase 19.3]: Site-cache purge is a cooler service (site_cache.purge_url, site_cache.purge_token); failures never fail a save
- [Phase 19.3]: pact.ReorderAfterSave runs inside cabana's reorder transaction; reorderPost uses lagoon.Transaction so AfterCommit fires
- [Phase 19.3]: Journal golem15.journal.site_cache.* kept as deprecated aliases for one release; framework keys win
- [Phase 19.3]: 19.3-04 Task 4 reply push-all: plugins, layers, summercms master pushed; layer tags vue-faq/menu v0.2.0, vue-journal v0.4.0; no framework tag
- [Phase 19.3]: 19.3-05: phase gate scripts/check-phase19.3.sh requires every new test by name (go test -json / TAP detectors); --all green incl. Docker
- [Phase 19.3]: 19.3-05: miniredis v2.40.0 test-only in modules/cooler (Task 4 reply: approved)
- [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
- [Phase 19]: 19-01: Task 1 reply proceed-and-push; sm-chat-plugin created and main pushed at b93cae7
- [Phase 19]: 19-01: D-01 names, D-04 baseline 202610160001_create_golem15_chat_baseline, D-12 rewrite-as-both locked
- [Phase 19.1]: Task 1 reply verbatim: proceed-and-push
- [Phase 19.1]: Locked D-01 names, D-02 baseline 202610180001_create_golem15_userfriends_baseline, D-05 prefix /_userfriends/api/v1
- [Phase 19.1]: tea repos create --owner golem15 --name sm-userfriends-plugin succeeded; main pushed at 2480ff0 (never force). summercms.go was not pushed.
- [Phase 19.1]: Task 1 reply verbatim: proceed. Locked D-02 baseline 202610180001_create_golem15_chatbots_baseline and D-05 prefix /_chatbots/api/v1; remote follows plan 01 proceed-and-push.
- [Phase 19.1]: tea repos create --owner golem15 --name sm-chatbots-plugin succeeded; main pushed at c7d9108 (never force). summercms.go was not pushed.
- [Phase 19.1-chat-satellites]: Task 2 reply verbatim: proceed
- [Phase 19.1-chat-satellites]: Locked D-02 baseline id 202610180001_create_golem15_herald_baseline and D-05 prefix /_herald/api/v1; remote policy follows plan 01 proceed-and-push
- [Phase 19.1-chat-satellites]: tea repos create --owner golem15 --name sm-herald-plugin succeeded; main pushed at 46af07a (never force). summercms.go was not pushed.
- [Phase 19.1]: Task 2 reply verbatim: proceed — Human locked D-02 baseline id and D-05 prefix; Task 2 was not re-asked.
- [Phase 19.1]: Locked D-02 baseline id 202610180001_create_golem15_chatvideo_baseline and D-05 prefix /_chatvideo/api/v1; remote policy follows plan 01 proceed-and-push — These names land in adopting apps and published clients.
- [Phase 19.1]: tea repos create --owner golem15 --name sm-chatvideo-plugin succeeded; main pushed at 38179b5 (never force). summercms.go was not pushed. — Remote policy is proceed-and-push after Task 4 green.
- [Phase 19.1]: After APIs exist, execute should run the security-review agent (CONTEXT). This execute did not spawn it. — CONTEXT discretion; planner and execute both refuse to spawn the reviewer.
- [Phase 19.1]: Task 2 reply verbatim: proceed — Human locked D-02 baseline id 202610180001_create_golem15_forum_baseline and D-05 prefix /_forum/api/v1 before the sibling repo existed. Remote policy follows plan 01 proceed-and-push.
- [Phase 19.1]: Locked D-02 baseline id 202610180001_create_golem15_forum_baseline and D-05 prefix /_forum/api/v1; remote policy follows plan 01 proceed-and-push — Continuation recorded the lock from the human proceed reply; Task 1 and Task 2 were not re-asked.
- [Phase 19.1]: tea repos create --owner golem15 --name sm-forum-plugin succeeded; main pushed at 6a40030 (never force). summercms.go was not pushed. — Plan 01 remote policy proceed-and-push; tea-create only after Task 4 green.
- [Phase 19.1]: Goldens from PHP fixtures; live capture NOT RUN — No live PHP Wavepath backend was reachable.
- [Phase 19.1]: Execute should now run the security-review agent; this execute did not spawn it — Five APIs, push sender, token issuer, sanitizer boundary, and GDPR cascades now exist.
- [Phase 19.1]: Gate named-short vs postgres split follows the skip detector — TestHTTPTracer and TestReconcileEmptyRoom skip under -short; a skip is refuse.
- [Phase 19.4]: D-16: extend the hand-written wristband with an OIDC flavour; golang-jwt/jwt/v5 v5.3.1 signs id_tokens via MapClaims; stdlib JWKs; zitadel/oidc v3 rejected; go-oidc and x/oauth2 test-only in sm-oauthserver-plugin
- [Phase 19.4]: 19.4-01: OIDC redirects send Cache-Control no-store, private (plan) while PHP sends no-cache, private; plan 03 tide replay settles it
- [Phase 19.4]: 19.4-01: wristband DefaultOptions().Resource is empty; host applications set their MCP resource explicitly (fonoteka 687e25a)
- [Phase 19.4]: 19.4-02: go-oidc v3.21.0 and x/oauth2 v0.37.0 approved as test-only requirements of sm-oauthserver-plugin (TestRelyingPartyInterop, RS256 and ES256)
- [Phase 19.4]: 19.4-02: first signing key via INSERT ... ON CONFLICT (active) WHERE active DO NOTHING with a silent GORM logger so key ciphertext never reaches the log
- [Phase 19.4]: 19.4-02: plugin-owned oauthserver.body.limit middleware on the token routes because surf ignores body.limit on GroupRaw routes
- [Phase 19.4]: 19.4-02: SanitizeCustomCSS repeats tag-opener removal until stable (PHP single pass is bypassable)
- [Phase 19.4]: 19.4-03: skip-parity at Task 5; per the user's D-14 amendment the PHP OIDC recording is a preliminary task of the WavePath cutover phase (todo 2026-10-19-record-php-oidc-parity-against-wavepath); the phase verdict is not held at human_needed
- [Phase 19.4]: 19.4-03: admin redirect URIs are checked on save with wristband.RejectRedirectURI; http loopback only on development clients
- [Phase 19.4]: 19.4-03: lagoon has no string/url/regex rules, so branding formats are checked in the branding controller and Rules() use only nullable|max:N
- [Phase 19.4]: 19.4-03: security review pinned every T-19.4 mitigation with a test (summercms.go eaa545c, plugin b4223be, f8eaf70); T-19.4-36..39 accepted
- [Phase 19.4]: Phase gate stages split by Docker need; the application's unrelated admin route test stays outside the gate (deferred-items)
- [Phase 19.4]: PHP method map: 171 methods, 146 ported, 25 adapted with reasons, none not-applicable
- [Phase 19.4]: Parity NOT RUN points to the WavePath cutover todo and does not hold the verdict (D-14 amended); PHASE194_REQUIRE_PARITY=1 fails
### Pending Todos
- [2026-10-01] [docs] Benchmark Płytarium on WinterCMS vs SummerCMS and publish a measured table — [todo file](.planning/todos/pending/2026-10-01-benchmark-the-application-on-wintercms-vs-summercms.md)
- [2026-10-07] [sm-translate-plugin] Translate Messages as a Nuxt i18n JSON editor — [todo file](.planning/todos/pending/2026-10-07-translate-messages-as-nuxt-i18n-json-editor.md)
- [2026-10-10] [framework] Prompt open admin and site tabs to reload when a new version is deployed — [todo file](.planning/todos/pending/2026-10-11-reload-open-apps-when-a-new-version-is-deployed.md) — Needs TBD. Ideas:.
- [2026-10-11] [lagoon/attach, fonoteka.go uploads] Uploads keep the client file extension, so a valid image can be served as HTML (stored XSS) — [todo file](.planning/todos/pending/2026-10-11-uploads-keep-client-extension-stored-xss.md)
- [unknown] [sm-user-plugin / cabana] Verified link between a frontend user and a backend user — [todo file](.planning/todos/pending/2026-10-17-frontend-backend-account-link.md)
- [unknown] [sm-user-plugin / pact] Plugins register their frontend permission codes in Go — [todo file](.planning/todos/pending/2026-10-17-plugin-registered-frontend-permissions.md)
- [2026-10-19] [sm-oauthserver-plugin] Record PHP OIDC parity against WavePath before the OIDC cutover — [todo file](.planning/todos/pending/2026-10-19-record-php-oidc-parity-against-wavepath.md)
- [unknown] [cabana / admin] Audit-log admin action runs with the admin id (toolbar actions) — [todo file](.planning/todos/pending/admin-action-audit-log.md)
- [unknown] [summercms.go bonfire] Reject duplicate console command names in bonfire.NewRoot — [todo file](.planning/todos/pending/bonfire-duplicate-command-names.md)
- [unknown] [sm-faq-plugin] Port the FAQ plugin as sm-faq-plugin so FAQs become editable — [todo file](.planning/todos/pending/faq-plugin-port.md)
- [2026-10-09] [plugins] Expose author subtitle, description and avatar on the Journal author API — [todo file](.planning/todos/pending/journal-author-profile-fields.md)
- [unknown] [summercms.go lagoon] lagoon README registers an after-commit GORM callback that can sort after the flush — [todo file](.planning/todos/pending/lagoon-readme-after-commit-callback-order.md)
- [unknown] [sm-user-plugin mail] Port MailBlocker and the block-mail checkbox with the mailing work — [todo file](.planning/todos/pending/mailblocker-with-mailing.md)
- [unknown] [sm-menu-plugin] Port the menu plugin as sm-menu-plugin so blog menus become editable — [todo file](.planning/todos/pending/menu-plugin-port.md)
- [unknown] [summercms.go/modules/*/README.md, internal/docsite] Convert module README Go code blocks to compiled src= references — [todo file](.planning/todos/pending/readme-go-fences-src.md)
- [unknown] [../fonoteka.go/README.md] Refresh fonoteka.go README — short status plus migrate/serve/login — [todo file](.planning/todos/pending/refresh-fonoteka-readme.md)
- [unknown] [summercms.go internal/build] make:admin-controller output does not boot and names the model after the controller — [todo file](.planning/todos/pending/scaffold-admin-controller-incomplete.md)
- [unknown] [summercms.go internal/build] Scaffolded files carry a DO NOT EDIT header, and make:command cannot reach the application — [todo file](.planning/todos/pending/scaffold-generated-header-and-command-deps.md)
- [unknown] [summercms.go internal/build] make:model and make:migration in the same second produce migrations that run out of order — [todo file](.planning/todos/pending/scaffold-same-second-migration-order.md)
- [2026-10-12] [framework] Purge the Nuxt site cache on menu and FAQ admin saves (framework-level purge service) — [todo file](.planning/todos/pending/site-cache-purge-for-menu-and-faq.md)
- [unknown] [sm-sitemap-plugin] Port the sitemap plugin 1:1 as sm-sitemap-plugin for the next blog project — [todo file](.planning/todos/pending/sitemap-plugin-port.md)
- [unknown] [sm-sitemap-plugin] Sitemap - replace the browser XSLT view before Chromium drops XSLT — [todo file](.planning/todos/pending/sitemap-xslt-browser-deprecation.md)
- [unknown] [sm-user-plugin] Let the sm-user-plugin console commands open their own database — [todo file](.planning/todos/pending/user-plugin-commands-open-database.md)
### Blockers/Concerns
- ~~Phase 8 (OAuth2.1) pre-planning check of the PHP OAuth server for `ClientCredentialsStorage`/`TokenExchangeStorage`~~ — resolved 2026-09-23 during Phase 8 discussion/research: the PHP server is hand-rolled and supports only `authorization_code`/`refresh_token`, so neither interface is needed (see 08-CONTEXT.md, 08-RESEARCH.md).
- Phase 9 (admin schema pipeline / relation manager) is the least-precedented design surface in the research — plan with `--research-phase`.
- Phase 11 (River dual-driver split) is documented but unverified against a real build — plan with `--research-phase` and budget a timed-latency test.
- Phase 8 UI gate: scripts/check-phase8-ui.mjs --final-gate's real Playwright browser matrix (32 UI-SPEC scenarios) is unimplemented (deliberate fatal() at check-phase8-ui.mjs:458, never authored by 08-05); stage_ui_harness must keep failing closed until a Playwright spec is authored. User approved Phase 8 closure on 2026-09-24 with this gap carried forward -- see 08-10-SUMMARY.md and .planning/phases/08-oauth2-1-authorization-server/deferred-items.md.
- pending: push sm-user-plugin — 12.1-05 Task 3 condition (c) failed (`git ls-remote --tags origin v0.1.3` empty). Push summercms.go master and annotated tag v0.1.3 (local on df5cace) first, then the plugin, then fonoteka.go. Recorded in 12.1-05-SUMMARY.md.
- 16.2-05 open UAT items: re-save HTML post 1 untouched (D-03 evidence vacuous), D-19 category edit check, root vhost install (frame-src), unpublish/delete test post 105
- Phase 19.4: review warnings WR-01..06 are open (19.4-REVIEW-DISPOSITION.md); WR-01 (a replayed code does not revoke rotated refresh tokens) must be fixed before any client enables refresh. Release still pending: tag summercms, bump sm-oauthserver-plugin go.mod to sm-user-plugin v0.1.1 / summercms v0.2.4.
### Quick Tasks Completed
| # | Description | Date | Commit | Directory |
|---|-------------|------|--------|-----------|
| 260927-q23 | fix CR-01: /auth/refresh must enforce tokens_valid_after and is_activated | 2026-09-27 | be4a923 | [260927-q23-fix-cr-01-auth-refresh-must-enforce-toke](./quick/260927-q23-fix-cr-01-auth-refresh-must-enforce-toke/) |
| 261001-qoa | Add a docs page on performance and scaling compared to PHP/WinterCMS | 2026-10-01 | 4103d95 | [261001-qoa-add-a-docs-page-on-performance-and-scali](./quick/261001-qoa-add-a-docs-page-on-performance-and-scali/) |
| 261002-esz | Extract golem15.user into its own repo sm-user-plugin, mounted back as a submodule | 2026-10-02 | 18576db | [261002-esz-extract-golem15-user-plugin-from-fonotek](./quick/261002-esz-extract-golem15-user-plugin-from-fonotek/) |
| 2 | make admin SPA edit/create and settings forms full width | 2026-09-27 | 2585671 | — |
| 260928-lf2 | Rewrite module READMEs and root README as professional app-agnostic docs | 2026-09-28 | fafb12f | [260928-lf2-rewrite-module-readmes-and-root-readme-a](./quick/260928-lf2-rewrite-module-readmes-and-root-readme-a/) |
| 261001-ddh | Replace lagoon hardcoded ICU pl-PL database locale with per-query COLLATE option | 2026-10-01 | 037dc53 | [261001-ddh-replace-lagoon-hardcoded-icu-pl-pl-datab](./quick/261001-ddh-replace-lagoon-hardcoded-icu-pl-pl-datab/) |
| 261004-rou | Move API-token ownership from Fonoteka to sm-user-plugin | 2026-10-04 | d1abcab | [261004-rou-move-user-api-tokens-from-fonoteka-plugi](./quick/261004-rou-move-user-api-tokens-from-fonoteka-plugi/) |
| 261005-qvk | Map Winter icon-* navigation names onto lucide so BM Studies and Quizzes icons render | 2026-10-05 | a00dafa | [261005-qvk-fix-icons-sm-bm-plugin-tried-to-apply-ic](./quick/261005-qvk-fix-icons-sm-bm-plugin-tried-to-apply-ic/) |
| 261006-eyj | Widget form field payload request and structured data response channel | 2026-10-06 | e723c39 | [261006-eyj-widget-field-payload-and-data-channel](./quick/261006-eyj-widget-field-payload-and-data-channel/) |
| 261006-s0v | Remove copy-from locale UI from ML text and markdown admin fields | 2026-10-06 | 2f03128 | [261006-s0v-remove-copy-from-locale-ui-from-ml-text-](./quick/261006-s0v-remove-copy-from-locale-ui-from-ml-text-/) |
| 261006-seq | Fix nullable pointer scalars rendering as <nil>/address in cabana ML form fields | 2026-10-06 | 90b87d6 | [261006-seq-fix-nullable-pointer-scalars-rendering-a](./quick/261006-seq-fix-nullable-pointer-scalars-rendering-a/) |
| 261006-sne | Render Markdown field Preview via sanitized cabana.RenderMarkdown | 2026-10-06 | a0116df | [261006-sne-render-markdown-field-preview-via-saniti](./quick/261006-sne-render-markdown-field-preview-via-saniti/) |
| 261006-t5r | Make SPA slug preset follow mltext source fields | 2026-10-06 | b5d8506 | [261006-t5r-make-spa-slug-preset-follow-mltext-sourc](./quick/261006-t5r-make-spa-slug-preset-follow-mltext-sourc/) |
| 261006-tnp | Add mltextarea field type | 2026-10-06 | 3596df4 | [261006-tnp-add-mltextarea-field-type](./quick/261006-tnp-add-mltextarea-field-type/) |
| 15 | Commit accumulated planning documents properly | 2026-10-06 | c05ad09 | — |
| 16 | Default icon shouldn't be a square but a sun. | 2026-10-06 | 4a94380 | — |
| 261007-p4y | Switch toggle pixel sizing; settings entries linking to admin controllers; translate Locales moved to Settings | 2026-10-07 | 0b09acd | [261007-p4y-toggle-proportions-and-settings-controll](./quick/261007-p4y-toggle-proportions-and-settings-controll/) |
| 18 | cabana list: join to-many relation column values with ", " | 2026-10-07 | fa25b4e | — |
| 261007-qgx | Admin record heading, crumb and title from the first text or mltext field (default locale) | 2026-10-07 | a714bb4 | [261007-qgx-admin-record-heading-uses-translatable-m](./quick/261007-qgx-admin-record-heading-uses-translatable-m/) |
| 261008-k7s | Bootstrap script PATH hint per login shell (fish/zsh/bash) | 2026-10-08 | ddf1178 | [261008-k7s-bootstrap-script-path-hint-per-shell](./quick/261008-k7s-bootstrap-script-path-hint-per-shell/) |
| 261008-kdf | summer delegates pass flags through, admin/route/key delegates, signal forwarding | 2026-10-08 | 50c8cee | [261008-kdf-delegated-summer-commands-pass-flags-thr](./quick/261008-kdf-delegated-summer-commands-pass-flags-thr/) |
| 261008-kdh | serve prints URL and admin status; summer new apps admin-ready | 2026-10-08 | f70d80e | [261008-kdh-serve-startup-and-admin-feedback-admin-r](./quick/261008-kdh-serve-startup-and-admin-feedback-admin-r/) |
| 261008-o6k | summercms.io/update: update one application in place (framework checkout, CLI, .env admin secret, build) | 2026-10-08 | 9b3803c | [261008-o6k-bootstrap-upgrade-mode-for-existing-chec](./quick/261008-o6k-bootstrap-upgrade-mode-for-existing-chec/) |
| 261009-i6e | add the anonymous author endpoint to sm-journal-plugin (D-20) | 2026-10-09 | sm-journal-plugin 908ade9, 57a347c | [261009-i6e-add-the-anonymous-author-endpoint-to-sm-](./quick/261009-i6e-add-the-anonymous-author-endpoint-to-sm-/) |
| 261009-mgk | add the category content field to sm-journal-plugin (D-21) | 2026-10-09 | sm-journal-plugin 740dcd0, f22d089, 3d533d4 | [261009-mgk-add-the-category-content-field-to-sm-jou](./quick/261009-mgk-add-the-category-content-field-to-sm-jou/) |
| 261009-mys | Add subtitle and avatar to backend users and a My account page (avatar menu, like WinterCMS) | 2026-10-09 | 143eca5, 64d21c8, 4f1cda3, 89611ea | [261009-mys-add-subtitle-and-avatar-to-backend-users](./quick/261009-mys-add-subtitle-and-avatar-to-backend-users/) |
| 261010-iwu | Blog Editor live preview pane: toggleable, sandboxed iframe, embed thumbnail cards (16.2 follow-up) | 2026-10-10 | sm-journal-plugin df67939, c1f6a83, 2c78211 | [261010-iwu-blog-editor-live-preview-pane-toggleable](./quick/261010-iwu-blog-editor-live-preview-pane-toggleable/) |
| 28 | Admin navbar View site link (admin origin root, new tab) | 2026-10-10 | 85fe541 | — |
| 261011-joh | sieve: generic collection helpers module (Map, TryMap, Filter, Reduce, KeyBy, GroupBy, Uniq, UniqBy, Partition, Find, Set) | 2026-10-11 | 6933004, a0bf8ec | [261011-joh-sieve-generic-collection-helpers-module](./quick/261011-joh-sieve-generic-collection-helpers-module/) |
| 30 | go test -short runs without Docker: TestMain flag parsing fix in 4 modules, cabana list fixture skip | 2026-10-11 | 7ef2e4f, c85cb64 | — |
| 261011-kzi | Fix Phase 09 gaps: fonoteka phase09AdminRoutes inventory + per-guard cabana last_used_at throttle | 2026-10-11 | b45af8e | [261011-kzi-fix-phase-09-gaps-add-the-10-post-phase-](./quick/261011-kzi-fix-phase-09-gaps-add-the-10-post-phase-/) |
| 261011-mag | fix check-phase10.sh hygiene gaps from 10-VERIFICATION.md | 2026-10-11 | 1541cd4 | [261011-mag-fix-check-phase10-sh-hygiene-gaps-from-1](./quick/261011-mag-fix-check-phase10-sh-hygiene-gaps-from-1/) |
| 261012-s3g | Fix Phase 10 security blockers: DOMPurify richeditor display sanitizer (T-10-16), check-phase10 hygiene green (T-10-24/25), CSP form-action/frame-src; --all still red on fonoteka.go TestPhase09SecurityRoutes | 2026-10-12 | 708b3ec | [261012-s3g-fix-phase-10-security-blockers-t-10-16-d](./quick/261012-s3g-fix-phase-10-security-blockers-t-10-16-d/) |
| 261013-0wa | Close T-17-15: validate sitemap frontend_url and escape it in Rewrite | 2026-10-12 | 6e5a8e7 | [261013-0wa-close-t-17-15-validate-sitemap-frontend-](./quick/261013-0wa-close-t-17-15-validate-sitemap-frontend-/) |
| 261015-w7f | Framework docs since v0.1.14 + Upgrading to 0.2, release v0.2.0 (plugins, layers), deploy summercms.io | 2026-10-15 | a204d38 | [261015-w7f-update-framework-docs-for-phase-19-3-rel](./quick/261015-w7f-update-framework-docs-for-phase-19-3-rel/) |
| 36 | summercms.io copy Alpha 0.1 → 0.2 (vue b5d6dbb, app 6a13a87), deployed | 2026-10-15 | 853ea1f | — |
| 261016-0fm | Admin minors for v0.2.1: theme switch, language switch, settings nav on form pages | 2026-10-16 | 66fd4ae | [261016-0fm-admin-minors-for-v0-2-1-theme-switch-lan](./quick/261016-0fm-admin-minors-for-v0-2-1-theme-switch-lan/) |
| 261016-s8x | PaymentGateway: banner bottom margin and hide disabled sections from admin navigation | 2026-10-16 | ab9f385 | [261016-s8x-paymentgateway-banner-bottom-margin-and-](./quick/261016-s8x-paymentgateway-banner-bottom-margin-and-/) |
| 261018-k9s | add the D-17 module in summercms.go: modules/breakwater Render(markdown)/Purify(html) with the forum bluemonday policy | 2026-10-18 | 98bc6de | [261018-k9s-add-the-d-17-module-in-summercms-go](./quick/261018-k9s-add-the-d-17-module-in-summercms-go/) |
| 40 | Goldens secret scan in check-phase19.1.sh (closes T-19.1-12) | 2026-10-18 | 8643195 | — |
| 261018-qbs | Fix 19.1 review WR-04 (chatbots dispatch checks IsAvailable) and WR-05 (chatvideo user.Deleting heir + LiveKit cleanup); extending.md optional-dependency go.mod note | 2026-10-18 | 97daec5 | [261018-qbs-fix-19-1-review-findings-wr-04-chatbots-](./quick/261018-qbs-fix-19-1-review-findings-wr-04-chatbots-/) |
| 261019-05g | Phase 17 verify-work unblock (COVERAGE.md reason, goal as user story, sm-sitemap-plugin requires v0.2.0) and console docs example of the application binary's --help | 2026-10-19 | 86c5a80 | [261019-05g-phase-17-verify-work-unblock-coverage-md](./quick/261019-05g-phase-17-verify-work-unblock-coverage-md/) |
| 261019-hgw | Translate hook ValidationError phrase keys in the request locale | 2026-10-19 | ce8d7ef | [261019-hgw-translate-hook-validationerror-phrase-ke](./quick/261019-hgw-translate-hook-validationerror-phrase-ke/) |
| 261020-13e | Admin image previews of public files fall back to the authenticated thumb route | 2026-10-19 | 1ab7db4 | [261020-13e-admin-file-upload-widget-image-preview-a](./quick/261020-13e-admin-file-upload-widget-image-preview-a/) |
| 261020-42e | branded HTML error pages for browsers | 2026-10-20 | a34c251 | [261020-42e-branded-html-error-pages-for-browsers-un](./quick/261020-42e-branded-html-error-pages-for-browsers-un/) |
## Deferred Items
Items acknowledged and carried forward from previous milestone close:
| Category | Item | Status | Deferred At |
|----------|------|--------|-------------|
| Phase 8 UI gate | `scripts/check-phase8-ui.mjs --final-gate`'s real Playwright browser matrix (32 UI-SPEC scenarios) is unimplemented -- deliberate `fatal()` at `scripts/check-phase8-ui.mjs:458`, never authored by 08-05. `stage_ui_harness` must keep failing closed until a Playwright config/spec outside the Nuxt checkout is authored (see .planning/phases/08-oauth2-1-authorization-server/deferred-items.md). Every other Phase 8 gate stage (real unchanged fonoteka-mcp lifecycle, both repos' vet/test/race, 169/169 parity corpus, secret scan, security review 11/11 closed, return-path 6/6, i18n 74 keys) is green. | Open — carried forward, user-approved | 08-10, 2026-09-24 |
## Session Continuity
Last session: 2026-10-20T00:18:01.483Z
Stopped at: Phase 19.4.1 context gathered
Resume file: .planning/phases/19.4.1-early-public-demo-on-demo-summercms-io/19.4.1-CONTEXT.md