Files
summercms/.planning/ROADMAP.md

50 KiB
Raw Blame History

Roadmap: SummerCMS (Go)

Overview

v1 ports the Płytarium (fonoteka) headless PHP backend to a single Go binary without vue-fonoteka-app or fonoteka-mcp noticing. The journey starts with the smallest possible kernel plus a day-one parity harness, proves the whole stack on one real endpoint (GET /_fonoteka/api/v1/genres) before any further kernel design, then broadens outward: full data-layer fidelity, the three-auth-group HTTP layer, the user plugin, OAuth2.1 for MCP/ChatGPT, the admin schema pipeline and its Vue SPA, jobs/realtime/search infrastructure, the 154-route API surface (split into two delivery slices), domain jobs and external integrations, and finally a cutover phase where the parity harness is green on all 154 routes and both real clients run unchanged against the Go backend.

Phases

Phase Numbering:

  • Integer phases (1, 2, 3): Planned milestone work
  • Decimal phases (2.1, 2.2): Urgent insertions (marked with INSERTED)

Decimal phases appear between their surrounding integers in numeric order.

  • Phase 1: Framework kernel foundation - Config, plugin registry, event bus, container and CLI skeleton boot the summer binary (completed 2026-09-16)
  • Phase 2: API parity harness bootstrap - Fixture recorder + replay-and-diff harness against the live PHP backend, built on the Phase 1 command kernel (completed 2026-09-17)
  • Phase 3: First vertical slice — genres end to end - GET /_fonoteka/api/v1/genres passes the parity diff through every layer (completed 2026-09-17)
  • Phase 4: CLI scaffolding, i18n and mail - Scaffolding commands, translated/pluralized strings, mail templates (completed 2026-09-18)
  • Phase 5: Data layer full fidelity - All 25 models and their squashed migrations with fillable/hidden/cast/soft-delete discipline (completed 2026-09-18)
  • Phase 6: HTTP routing, auth groups and rate limiting - Three auth groups, named rate buckets, OAuth-safe middleware structure (completed 2026-09-21)
  • Phase 7: User plugin and authentication - Registration, login, JWT, organizations, personal tokens, must-change-password (completed 2026-09-22)
  • Phase 8: OAuth2.1 authorization server - direct standard-library wristband server for fonoteka-mcp and the ChatGPT connector (completed 2026-09-23)
  • Phase 9: Backend admin authentication and schema pipeline - Admin roles, fields.yaml/columns.yaml, relation manager
  • Phase 10: Admin Vue SPA - Login, navigation, lists, forms and relation manager for five controllers (completed 2026-09-27)
  • Phase 11: Jobs, realtime and search infrastructure - River, Centrifugo and Typesense sync brought up before the API phases that need them
  • Phase 12: Płytarium API — Collections and Albums - Core content endpoints ported with byte-level parity
  • Phase 13: Płytarium API — wishlist, notifications, CSV, credentials, public routes - Remaining core API surface
  • Phase 14: Domain jobs and external integrations - CSV/Discogs jobs, wishlist digest, reindex, Discogs client, AI recognition, feedback, sitemap
  • Phase 15: Cutover - Parity harness green on all 154 routes, both real clients run unchanged

Phase Details

Phase 1: Framework kernel foundation

Goal: The summer binary boots from layered YAML config, plugins self-register through one required interface plus optional capability interfaces, a typed event bus and service registry are available, and a CLI command framework with a dev watch loop exists — built only as far as the first vertical slice will need it, per the interleaved-not-sequential kernel approach. Mode: mvp Depends on: Nothing (first phase) Repos: summercms.go Requirements: KERN-01, KERN-02, KERN-03, KERN-04, KERN-05, KERN-06, KERN-07, KERN-08, KERN-09, CLI-01 Success Criteria (what must be TRUE):

  1. summer build regenerates the blank-import plugin list and produces a binary that boots from a layered YAML config (base files, env overlay directory, per-plugin namespace, environment variables) with dot-path access.
  2. A throwaway plugin registered via the Plugin interface has its Register phase run for all plugins before any Boot phase runs, in Requires()-topological order, verified by a test with reordered input.
  3. A plugin can type-assert an optional capability interface (e.g. HasModels) and skip integration with another plugin that isn't registered, with no hard import.
  4. The typed event bus supports fire-and-forget, fire-and-collect, and fire-until-handled dispatch, exercised by unit tests; per-request state travels only through context.Context, never a package-level global.
  5. The summer CLI discovers a registered command and renders rich output (spinner, progress, table, prompts) with non-TTY degradation, and a dev watch loop rebuilds and restarts the binary on source change.

Plans: 4 plans

Plans: Wave 1

  • 01-01-PLAN.md — Build and boot the separate hello app through the shared command kernel

Wave 2 (blocked on Wave 1 completion)

  • 01-02-PLAN.md — Add layered config, optional plugin composition, services and typed events

Wave 3 (blocked on Wave 2 completion)

  • 01-03-PLAN.md — Add plugin scaffolding, rich output and the watch rebuild loop

Wave 4 (blocked on Wave 3 completion)

  • 01-04-PLAN.md — Complete unit, integration and race-enabled verification

Phase 2: API parity harness bootstrap

Goal: A fixture recorder captures request/response pairs from the running PHP backend — including real Nuxt and MCP flows — and a replay-and-diff harness with a normalizer for nondeterministic fields can run against any HTTP backend. The recorder and replayer are summer parity:* commands on the Phase 1 command kernel (bonfire); recording itself only needs a running PHP backend, but the tooling waits for Phase 1. Mode: mvp Depends on: Phase 1 Repos: summercms.go, fonoteka.go Requirements: QA-01, QA-02, QA-03 Success Criteria (what must be TRUE):

  1. The fixture recorder captures a request/response pair from the live PHP backend for at least one route and stores it as a replayable fixture.
  2. The replay-and-diff harness runs a recorded fixture against an arbitrary httptest.Server backend and reports a byte-level diff.
  3. The harness's normalizer and assertions explicitly catch the parity classes named in research (nil vs [], date format, tri-state booleans, envelope/conditional keys) on a synthetic test case, not just status codes.
  4. go vet and go test ./... are green, and the harness's own integration tests run against testcontainers Postgres.

Plans: 5 plans

Plans: Wave 1

  • 02-01-PLAN.md — Record and replay one fixture through the summer CLI

Wave 2 (blocked on Wave 1 completion)

  • 02-02-PLAN.md — Capture safe proxy sessions and manifest routes with strict diffs

Wave 3 (blocked on Wave 2 completion)

  • 02-03-PLAN.md — Record all 154 PHP routes and real Nuxt/MCP flows

Wave 4 (blocked on Wave 3 completion)

  • 02-04-PLAN.md — Wire honest Go replay with testcontainers Postgres

Wave 5 (blocked on Waves 1–4 completion)

  • 02-05-PLAN.md — Complete contract, security and integration tests

Phase 3: First vertical slice — genres end to end

Goal: GET /_fonoteka/api/v1/genres passes the parity diff end to end (config → DB → plugin registry → JWT-guarded routing → GORM → JSON), proving every kernel layer together before any further kernel abstraction. Touches the JWT auth guard on the request path — treat as security-load-bearing and apply the security-review agent even though the guard is a throwaway seeded-token implementation at this stage. Mode: mvp Depends on: Phase 1, Phase 2 Repos: summercms.go, fonoteka.go Requirements: DATA-01, DATA-02, HTTP-01, HTTP-02, QA-04 Success Criteria (what must be TRUE):

  1. GORM connects to Postgres over one shared *sql.DB (pgx stdlib); a gormigrate migration set runs up and down for a single plugin with per-plugin version tracking, and AutoMigrate is not the schema source.
  2. A plugin registers a route group on net/http ServeMux with typed params, and a request is served through the full middleware pipeline (recover, CORS, locale, auth group, must-change-password, org context, rate limit, handler).
  3. A JWT-guarded GET /_fonoteka/api/v1/genres route returns a real Genre row read from Postgres.
  4. The response is diffed byte-for-byte against a fixture recorded from the live PHP backend using the Phase 2 harness, and the diff is green.

Plans: 4 plans

Plans:

Wave 1

  • 03-01-PLAN.md — Boot the Postgres-backed JWT genre route through both plugins

Wave 2 (blocked on Wave 1 completion)

  • 03-02-PLAN.md — Port active-collection counts, validation and Polish ordering

Wave 3 (blocked on Wave 2 completion)

  • 03-03-PLAN.md — Add typed params, isolated rollback and the first real parity pass

Wave 4 (blocked on Wave 3 completion)

  • 03-04-PLAN.md — Complete unit, integration and security verification

Phase 4: CLI scaffolding, i18n and mail

Goal: Scaffolding commands generate compiling stubs for every plugin artifact type, and plugins can register translated, CLDR-pluralized, namespaced strings and mail templates rendered through a pluggable driver interface. Mode: mvp Depends on: Phase 1 Repos: summercms.go Requirements: CLI-02, I18N-01, I18N-03 Success Criteria (what must be TRUE):

  1. summer make:plugin, make:model, make:migration, make:command, make:job and make:admin-controller each generate stubs that compile and pass go vet.
  2. A translation key vendor.plugin::group.key resolves for pl and en, including a CLDR plural form, loaded from per-plugin per-locale YAML files with parameter substitution.
  3. A plugin registers a mail template and layout by dotted name with the per-locale suffix convention, and it renders via html/template through a driver interface (SMTP via go-mail) in a test send.

Plans: 4 plans

Plans:

Wave 1

  • 04-01-PLAN.md — Generate compiling plugin artifacts, registry and model import checks

Wave 2 (blocked on Wave 1 completion)

  • 04-02-PLAN.md — Load namespaced translations, plurals, parameters and locale fallbacks

Wave 3 (blocked on Wave 2 completion)

  • 04-03-PLAN.md — Register, render and send plugin mail through pluggable drivers

Wave 4 (blocked on Wave 3 completion)

  • 04-04-PLAN.md — Verify scaffolding, translation and mail with unit and SMTP integration tests

Phase 5: Data layer full fidelity

Goal: All 25 Płytarium models and their squashed migration set are ported with matching relations, casts, hooks, and the fillable/hidden/encrypted-cast mass-assignment and serialization discipline. This is security-load-bearing — mass-assignment boundaries and encrypted-at-rest credential casts are named security invariants in the PHP source — apply the security-review agent and the DTO-vs-model convention from the first model onward. Mode: mvp Depends on: Phase 1, Phase 3 Repos: summercms.go, fonoteka.go Requirements: DATA-03, DATA-04, DATA-05, DATA-06, DATA-07, DATA-08, DATA-09, DATA-10, DATA-11, CLI-03 Success Criteria (what must be TRUE):

  1. All 25 models exist with matching table names, columns, indexes and defaults, and every Go migration runs up and down individually; the final schema matches PHP's (migrations are squashed per final-state table, not a 1:1 port of PHP's 38 files — the historical migration count is not a target). summer migrate:rollback --plugin=fonoteka rolls back only that plugin's last migration.
  2. A many-to-many relation with pivot business columns (album_artists.sort_order, CollectionEditor.role/granted_at/granted_by) round-trips correctly for a 3+ artist album; belongsTo/hasOne/hasMany relations return ordered results.
  3. The fill boundary of the PHP write services (at minimum Album, Collection and the four credential models) is fuzzed against real Postgres with random extra and server-owned keys, asserting nothing outside the allow-list is persisted (service-level, this phase); a request-DTO-level fuzz over every write endpoint is Phase 12's criterion (the HTTP layer does not exist until Phase 6/12). Serialization honors the hidden deny-list with an explicit per-call override.
  4. The money cast round-trips the PHP ceiling and blank-string cases as a fixed 4-decimal JSON string (never float64), and an encrypted-at-rest credential column is AES-GCM encrypted at rest and hidden from serialization.
  5. Paginated responses use the exact {data, meta{current_page,last_page,per_page,total}} envelope with no links key; another plugin extends a model's lifecycle through the GORM callback registry and a companion migration without editing the owning plugin's file; soft-deletable + uniquely-keyed tables pass a delete-then-recreate test.

Plans: 11 plans

Plans: Wave 1

  • 05-01-PLAN.md — Verify models-leaf rule, restructure plugins into Winter layout, ship lagoon write/read-path primitives

Wave 2 (blocked on Wave 1 completion)

  • 05-02-PLAN.md — Album/Collection slice: migrations, models, casts, validation engine, write services

Wave 3 (blocked on Wave 2 completion)

  • 05-03-PLAN.md — Secrets slice: encrypted cast, key management, organisations, credentials/OAuth tables
  • 05-04-PLAN.md — Attachments: system_files, blob storage, thumbnails, delete lifecycle

Wave 4 (blocked on Wave 3 completion)

  • 05-05-PLAN.md — Remaining models, D-02 schema-diff proof, test-only DATA-11 fixture plugin, CLI-03 verification

Wave 5 (blocked on Wave 4 completion)

  • 05-06-PLAN.md — Fuzz tests, hidden-marshal coverage, attachment smoke test, security review

Phase 6: HTTP routing, auth groups and rate limiting

Goal: The three mutually exclusive auth groups (JWT, personal token, public/onboarding) share handlers with correct route subsets, named rate-limit buckets are ported 1:1, and OAuth/RFC routes are structurally exempted from any house envelope or error middleware. Security-load-bearing — auth guard registry, rate limiting and the SSRF-guarded outbound fetch helper all live here; apply the security-review agent. Mode: mvp Depends on: Phase 3, Phase 5 Repos: summercms.go, fonoteka.go Requirements: HTTP-03, HTTP-04, HTTP-05, HTTP-06, HTTP-07, HTTP-08, HTTP-09 Success Criteria (what must be TRUE):

  1. The same handler serves both a JWT-authenticated request under /_fonoteka/api/v1 and a personal-token request under /api/v1/fonoteka, with public/onboarding groups reachable without auth; unknown and malformed ids on ownership-scoped resources both return 404.
  2. All five named rate-limit buckets are enforced with the documented keys and limits, including a route stacking two limiters.
  3. Response conventions hold under test: empty arrays serialize as [], timestamps carry +00:00, tri-state booleans keep null, conditional keys are omitted not nulled; an OAuth-group route carries no house envelope or error middleware, verified by route-registration inspection.
  4. The guarded outbound fetch helper rejects a non-allow-listed host and enforces a byte cap and timeout on a user-supplied cover URL fetch (manual cover URL, Discogs cover).
  5. OpenAPI is generated from swaggo/swag annotations on handlers and openapi-typescript produces valid TypeScript types from it; CORS and JSON body-size limits match the PHP deployment.

Plans: 10 plans

Plans: Wave 1 (parallel)

  • 06-01-PLAN.md — Auth-groups slice: router verb/factory growth, bouncer guard registry, real inv_token guard + inv.scope, genres shared under both auth groups
  • 06-04-PLAN.md — SSRF-guarded outbound fetch helper (framework primitive, independent of the other three plans)

Wave 2 (blocked on 06-01)

  • 06-02-PLAN.md — Rate limiting: fixed-window Store/Limiter, trusted-proxy client IP, five fonoteka buckets, PublicShareHeaders, remaining route groups declared

Wave 3 (blocked on 06-02)

  • 06-03-PLAN.md — Contract surface: raw-group enforcement + route table + route:list, wire response helpers, swag/openapi-typescript pipeline, path-scoped CORS, body limits (blocking human-verify checkpoint for production body-size numbers), oauth group declared raw

Wave 4 (blocked on 06-01..06-04)

  • 06-05-PLAN.md — Full unit coverage across both repos, full route-table mutual-exclusivity test, 06-SECURITY-REVIEW.md

Wave 5 (gap closure; blocked on 06-05)

  • 06-06-PLAN.md — Repair personal-token middleware order and prove unauthenticated request 61 is rate-limited

Wave 6 (gap closure; parallel, blocked on 06-06)

  • 06-07-PLAN.md — Make limiter admission atomic and remove attacker-controlled Host from inline keys
  • 06-08-PLAN.md — Reject private IPv4 embedded in NAT64 and 6to4 dial addresses
  • 06-09-PLAN.md — Buffer route responses so partial-write panics yield clean raw/house 500s
  • 06-10-PLAN.md — Restore exact no-newline InvScope 401/403 wire bodies

Wave 7 (gap closure; blocked on 06-07..06-10)

  • 06-11-PLAN.md — Re-run Phase 6 security gates and refresh the stale threat verdict

Phase 7: User plugin and authentication

Goal: The user plugin is ported with registration, login, JWT issue/refresh, organizations, personal API tokens and the must-change-password lock. Security-load-bearing — password auth, token scope ceilings and the session lock all live here; apply the security-review agent. Mode: mvp Depends on: Phase 6 Repos: summercms.go, fonoteka.go Requirements: AUTH-01, AUTH-02, AUTH-03, AUTH-04, I18N-02 Success Criteria (what must be TRUE):

  1. A user can register, log in, log out, reset a forgotten password by email, verify email, and receive/refresh a JWT (golang-jwt) with the same claims and cookie behavior the Nuxt app expects.
  2. Organization fields appear on the user payload through a fire-and-collect event the fonoteka plugin populates, without the user plugin importing fonoteka.
  3. A personal API token is created with a read|write|ai scope ceiling, listed, and revoked; a scope-checking middleware rejects an out-of-scope request.
  4. The must-change-password flag returns 423 on the authenticated surface except the locale and password-change routes, and locale resolves per request from the user's persisted preferred_locale with header fallback even while the lock is active.

Plans: 8 plans

Plans: Wave 1

  • 07-01-PLAN.md — bouncer JWT lifecycle, password hashing, I18N-02 locale-from-principal, lagoon.Validate extensions

Wave 2 (blocked on 07-01)

  • 07-02-PLAN.md — User/Throttle schema and the core session loop: login/logout/fetch/refresh/register

Wave 3 (blocked on 07-02)

  • 07-03-PLAN.md — Account management: forgot/reset password, activation, update, change-password, avatar, mail
  • 07-04-PLAN.md — Personal API tokens (mint/list/revoke), me/locale, 423-exempt route-table proof

Wave 4 (blocked on 07-03, 07-04)

  • 07-05-PLAN.md — Parity evidence: record and replay the 15 /_user/api/v1 routes and the nuxt-auth flow

Wave 5 (blocked on 07-05)

  • 07-06-PLAN.md — Full unit coverage, 07-VALIDATION.md sign-off

Wave 6 (gap closure; blocked on 07-06)

  • 07-07-PLAN.md — Re-record the logout blacklist under a persistent PHP cache, fix the already-activated HTML-500 quirk, and flip all 15 /_user/api/v1 routes plus both nuxt flows to ported

Wave 7 (gap closure; blocked on 07-07 UAT)

  • 07-08-PLAN.md — Publish the uploads bucket on serve and Handler so assembled avatar POST is 200

Phase 8: OAuth2.1 authorization server

Goal: A direct standard-library OAuth2.1-style authorization server (wristband) implements the RFC 8414/6749/7591/8707 contract needed by fonoteka-mcp and the ChatGPT connector unchanged. The backend preserves its exact Basic invalid-client challenge and existing personal-token 401, while fonoteka-mcp retains ownership of RFC 9728 protected-resource metadata and its rich Bearer challenge. Security-load-bearing — bearer tokens, PKCE, replay-family revocation, and constant-time secret comparison all live here; apply the security-review agent. Mode: mvp Depends on: Phase 6, Phase 7 Repos: summercms.go, fonoteka.go Requirements: AUTH-05, AUTH-06, AUTH-07 Success Criteria (what must be TRUE):

  1. RFC 8414 metadata is served unenveloped at its well-known path; authorize with PKCE and a consent screen works, and the token endpoint issues/refreshes authorization_code and refresh_token flows, all as unwrapped RFC 6749 bodies with the PHP cache headers (Cache-Control: no-store, Pragma: no-cache).
  2. RFC 7591 dynamic client registration and RFC 8707 resource parameter tolerance both work against a real client registration call.
  3. The token endpoint returns exactly WWW-Authenticate: Basic realm="OAuth" on invalid_client, the backend personal-token 401 remains unchanged with no added challenge, and fonoteka-mcp's own rich Bearer challenge plus protected-resource metadata are verified through its actual discovery flow, not just a Go unit test.
  4. Connected apps can be listed and revoked; OAuthClient/OAuthAuthCode/OAuthRefreshToken models persist correctly; fonoteka-mcp completes its install and auth flow unchanged; client-secret comparison uses crypto/subtle.ConstantTimeCompare.

Plans: 10 plans Research flag: yes

Plans:

Wave 1

  • 08-01-PLAN.md — Mount exact connector-visible metadata and establish fail-closed RED verification

Wave 2 (blocked on 08-01)

  • 08-02-PLAN.md — Deliver persistent connector-visible DCR with corrected schema and transaction-scoped stores

Wave 3 (blocked on 08-02)

  • 08-03-PLAN.md — Create durable PKCE-bound authorize requests on the assembled raw route surface

Wave 4 (blocked on 08-03)

  • 08-04-PLAN.md — Implement atomic PKCE-bound authorization-code exchange

Wave 5 (blocked on 08-04)

  • 08-05-PLAN.md — Wire JWT consent and prove the unchanged Nuxt UI contract

Wave 6 (blocked on 08-05)

  • 08-06-PLAN.md — Rotate refresh grants, kill replayed lineages, and manage connected apps

Wave 7 (parallel; blocked on 08-06)

  • 08-07-PLAN.md — Provision confidential clients through the exact operator command
  • 08-08-PLAN.md — Serve the MCP personal-token bootstrap on the existing token surface

Wave 8 (blocked on 08-07 and 08-08)

  • 08-09-PLAN.md — Replay PHP OAuth flows and assemble the self-validating final unchanged-MCP gate

Wave 9 (blocked on 08-09; blocking security checkpoint)

  • 08-10-PLAN.md — Close 103-method coverage, independent security review, and the final fail-closed gate

Phase 9: Backend admin authentication and schema pipeline

Goal: Backend admin users with roles are separate from frontend users and gate navigation and controller access; fields.yaml/columns.yaml drive a JSON form/list schema, including a first-class relation-manager schema replacing the one partial field. Security-load-bearing — separate admin authentication and permissions registry live here; apply the security-review agent. This is also the least-precedented design surface in the research (one real relation-manager usage in the PHP source, no direct library equivalent). Mode: mvp Depends on: Phase 5, Phase 6 Repos: summercms.go, fonoteka.go Requirements: AUTH-08, ADMIN-01, ADMIN-02, ADMIN-03, ADMIN-04, ADMIN-05 Success Criteria (what must be TRUE):

  1. A backend admin user with a role logs in separately from frontend users, and navigation/controller access is gated by the permissions registry.
  2. fields.yaml for a real controller parses (goccy/go-yaml) into a JSON form schema covering text, textarea, checkbox, switch, dropdown (model-method options) and relation (nameFrom, emptyOption), with span/tabs/context/attributes layout hints.
  3. columns.yaml parses into a JSON list schema with searchable/sortable/relation columns and datetime/switch renderers.
  4. The relation-manager schema supports search/link/unlink/manage-or-view lists for Collections' editors tab, replacing the partial field entirely.
  5. Admin CRUD endpoints expose listExtendQuery/formExtendQuery/formBeforeCreate/formBeforeUpdate/relationExtendManageQuery hooks, bulk delete runs each record's lifecycle hooks, and the Settings model binds to a settings screen through the same schema pipeline.

Plans: 12/12 plans executed Research flag: yes

Plans:

Wave 1

  • 09-01-PLAN.md — Prove the architecture with one production end-to-end Genre list

Wave 2 (blocked on Wave 1 completion)

  • 09-02-PLAN.md — Complete backend identity lifecycle and operator provisioning

Wave 3 (blocked on Wave 2 completion)

  • 09-03-PLAN.md — Compile the typed form-schema contract and Winter scaffolding

Wave 4 (blocked on Wave 3 completion)

  • 09-04-PLAN.md — Compile the list contract and the allowlisted query engine

Wave 5 (blocked on Wave 4 completion)

  • 09-05-PLAN.md — Deliver schema-projected CRUD and transactional bulk deletion

Wave 6 (blocked on Wave 5 completion)

  • 09-06-PLAN.md — Port the Albums admin surface and the collection boundary

Wave 7 (blocked on Wave 6 completion)

  • 09-07-PLAN.md — Port the Artists backend controller
  • 09-08-PLAN.md — Complete the Genres controller with form/list parity
  • 09-09-PLAN.md — Port the Styles controller and typed provider/filter behavior
  • 09-10-PLAN.md — Deliver Collections and the typed relation manager

Wave 8 (blocked on Wave 7 completion)

  • 09-11-PLAN.md — Complete permissions, navigation, and singleton settings

Wave 9 (blocked on Wave 8 completion)

  • 09-12-PLAN.md — Close with security, PostgreSQL, OpenAPI, and acceptance gates

Phase 10: Admin Vue SPA

Goal: A minimal Vue 3 + TypeScript admin SPA renders login, permission-gated navigation, lists, forms and the relation manager for Albums, Artists, Collections, Genres and Styles, typed from the generated OpenAPI document. Mode: mvp Depends on: Phase 9 Repos: summercms.go, fonoteka.go Requirements: ADMIN-06 Success Criteria (what must be TRUE):

  1. An admin logs in through the SPA and sees only the navigation items their permissions allow.
  2. Each of the five controllers (Albums, Artists, Collections, Genres, Styles) renders a working list and form generated from its JSON schema.
  3. The Collections form's relation manager lets an admin search, link and unlink an editor.
  4. API calls in the SPA use TypeScript types generated from the OpenAPI document, with no hand-maintained duplicate type.

Plans: 5/5 plans complete UI hint: yes

Plans:

Wave 1

  • 10-01-PLAN.md — Tracer: backend.uri prefix, cookie+CSRF transport, embedded SPA (login, navigation, typed Genres list), framework admin OpenAPI pipeline, fonoteka lang/icons/Collections nav

Wave 2 (blocked on Wave 1 completion)

  • 10-02-PLAN.md — Backend contract: relation options and saves with labels, backend strings bundle and overrides, messages, declarative toolbar, filter options, fully typed OpenAPI with conformance

Wave 3 (blocked on Wave 2 completion)

  • 10-03-PLAN.md — SPA lists, forms, filter bar and settings screen for all five controllers

Wave 4 (blocked on Wave 3 completion)

  • 10-04-PLAN.md — Relation manager with picker modal, and shell polish (flyout, collapse, user menu, breadcrumbs, dark mode)

Wave 5 (blocked on Wave 4 completion)

  • 10-05-PLAN.md — Unit tests last: Vitest and Go coverage, assembled acceptance, check-phase10.sh gate and security evidence

Phase 10.2: Nest framework packages under modules and write run docs (INSERTED)

Goal: The 18 beach-named framework packages live under modules/<name>/ with the same names and a single root go.mod; importers in summercms.go, examples, and fonoteka.go use git.golem15.com/golem15/summercms/modules/<name>; each module has a short README; the root README is honest run/onboarding docs. Requirements: TBD Depends on: Phase 10 Repos: summercms.go, fonoteka.go Plans: 2/2 plans complete

Plans:

Wave 1

  • 10.2-01-PLAN.md — Nest the 18 beach packages under modules/ and rewrite every importer

Wave 2 (blocked on Wave 1 completion)

  • 10.2-02-PLAN.md — Per-module READMEs, root run docs, and the hygiene/unit-test gate

Phase 10.1: Runtime admin extension point (INSERTED)

Goal: A plugin extends the compiled admin SPA without a Node rebuild. Controllers declare their own JS/CSS, served same-origin from the plugin's embedded files; type: widget fields mount plugin custom elements whose actions the SPA posts; type: partial form fields and a list headerPartial render server-side through html/template and reach the page without any raw-HTML sink; and controllers register named toolbar actions. The framework contract is proven on a nameless fixture plugin, and the application proof is three Albums surfaces: a statistics strip above the list, a Discogs lookup widget on the form, and a Discogs sync toolbar action, both Discogs actions as stubs that Phase 14 replaces. Requirements: ADMIN-07 Depends on: Phase 10 Repos: summercms.go, fonoteka.go Success Criteria (what must be TRUE):

  1. A controller's declared JS/CSS is served from its plugin's embedded files under {backend.uri}/assets/{vendor}/{plugin}/… through an exact allowlist, loads only when that controller's list or form opens, and runs under CSP script-src 'self'; undeclared files and traversal attempts never leave the plugin tree.
  2. A type: widget field mounts the plugin's custom element; its event makes the SPA POST the declared action with the admin cookie and CSRF header, and only the field's declared fill keys are patched onto the unsaved form.
  3. A config_list.yaml headerPartial and a type: partial form field render server-side with html/template from a controller-supplied view model and reach the DOM only as an allowlisted node tree.
  4. A controller registers named toolbar actions listed in toolbar.buttons; a click POSTs the action and toasts while create and delete keep their behaviour; unknown YAML keys, missing templates, unregistered actions and unknown permissions fail boot.
  5. The Albums list shows a statistics strip scoped to the admin's collection, the Albums form shows a "Load from Discogs" widget whose stub fills Release year and Format, and a "Sync with Discogs" toolbar action toasts from its stub.

Plans: 4/4 plans executed

Plans:

Wave 1

  • 10.1-01-PLAN.md — Framework Go: pact capability interfaces, cabana widget/partial/toolbar/asset boot rules, cabana-owned action, partial and asset routes, sanitizer, OpenAPI (summercms.go)

Wave 2 (blocked on Wave 1 completion)

  • 10.1-02-PLAN.md — Framework SPA: plugin asset loader, WidgetField, PartialHost and PartialField, list-header slot, custom toolbar buttons, rebuilt dist (summercms.go/admin)

Wave 3 (blocked on Wave 2 completion)

  • 10.1-03-PLAN.md — Application: Albums stats strip, Discogs lookup widget stub, discogsSync toolbar stub, plugin assets and copy (fonoteka.go)

Wave 4 (blocked on Wave 3 completion)

  • 10.1-04-PLAN.md — Unit tests last: Go and Vitest coverage, Albums acceptance, check-phase10.1.sh gate, security review and validation evidence (both repos)

Phase 11: Jobs, realtime and search infrastructure

Goal: River jobs run on the correct dual-driver split, Centrifugo publishing and channel authorization match the existing server, and Typesense sync stays a re-gated pre-filter — all brought up before the API phases that depend on them (album search needs Typesense sync, CSV import needs River jobs, notifications need the realtime publisher). River's dual-driver split and the Centrifugo/Typesense contracts are the least-implemented-and-verified parts of this research pass. Mode: mvp Depends on: Phase 5, Phase 7 Repos: summercms.go, fonoteka.go Requirements: JOBS-01, CLI-04, CLI-06, RT-01, RT-02, RT-03, SRCH-01 Success Criteria (what must be TRUE):

  1. River runs on the shared *sql.DB (riverdatabasesql, transactional enqueue) with LISTEN/NOTIFY wake-ups on a small separate pgx pool (riverdatabasesql.NewWithPgxListener, one River client), verified by a timed test that job pickup is not poll-interval latency; job outcomes (complete, fail, skip) are queryable through a job manager service.
  2. The queue worker runs via summer queue:work and the scheduler runs recurring commands via summer schedule:run.
  3. The websockets plugin issues connection and subscription JWTs and publishes to the existing Centrifugo server with the same secret, claims and channel names, served at GET /api/realtime/token.
  4. A channel-namespace authorizer registry re-validates on every subscribe; a broadcastable model interface with bulk-write suppression emits exactly one summary event for a bulk operation.
  5. Typesense sync is scoped by collection_id behind a settings kill-switch and degrades gracefully without DB/config.

Plans: 8/8 plans executed Research flag: yes

Plans:

Wave 1

  • 11-01-PLAN.md — conga core: River v0.47.0 on NewWithPgxListener, summer_jobs + River v7 migrations, job manager, conga.Job[T], worker in serve, queue:work/queue:clear, lagoon OnDatabase and Transaction/AfterCommit seams, fonoteka allow-lists (summercms.go, fonoteka.go)

Wave 2 (blocked on Wave 1 completion)

  • 11-02-PLAN.md — Scheduler: pact.HasSchedule, Daily/Every, River periodic jobs, bonfire.Call, schedule:run (+ --once), fonoteka prune-notifications entry
  • 11-03-PLAN.md — Realtime: lighthouse + Centrifugo driver (token route, subscribe proxy, HTTP client, broadcasts with suppression), fonoteka authorizers, Mount, ws-api bucket, Album binding

Wave 3 (blocked on Wave 2 completion)

  • 11-05-PLAN.md — Search: beachcomber + Typesense engine, after-commit sync, fonoteka Album Searchable and settings kill-switch
  • 11-06-PLAN.md — tide Centrifugo recorder, PHP broadcast goldens (deleted + bulk proven; created/updated pending Phase 12), realtime routes recorded and replayed

Wave 4 (blocked on Wave 3 completion)

  • 11-04-PLAN.md — Web Push (flare VAPID driver, RFC 8291/8292) and websockets:health, websockets:generate-vapid-keys, websockets:test-push

Wave 5 (blocked on Wave 4 completion)

  • 11-07-PLAN.md — Unit tests last: full coverage, failing-when-broken T-11 evidence, check-phase11.sh gate, security review and validation map

Wave 6 (gap closure, blocked on Wave 5 completion)

  • 11-08-PLAN.md — CR-01: Cabana writes and SaveAlbum in lagoon.Transaction, lagoon.AfterCommit refuses foreign transactions, committed artist_ids regression tests (summercms.go, fonoteka.go)

Phase 11.1: SummerCMS documentation for humans and AI agents (INSERTED)

Goal: SummerCMS has a WinterCMS-style documentation set that serves both humans and AI agents. The Markdown source lives in summercms.go/docs/ and a summer CLI command builds it into a static site with sidebar navigation, search, llms.txt/llms-full.txt and a raw .md per page. Every code example compiles and is tested, and a "Coming from WinterCMS" map plus an acme/blog porting walkthrough cover the migration path. It documents the framework as it stands after Phase 11 and never names a consuming application. Requirements: DOCS-01, DOCS-02, DOCS-03, DOCS-04, DOCS-05, DOCS-06, DOCS-07, DOCS-08 Depends on: Phase 11 Repos: summercms.go Success Criteria (what must be TRUE):

  1. docs/ holds Markdown pages with frontmatter, grouped into Winter-mirroring sections (Setup, Architecture, Plugins, Backend, Database, Services, Console, API reference), and every framework module is reachable from the sidebar.
  2. A summer CLI command builds a self-contained static site with sidebar, on-page TOC, prev/next, client-side search and dark mode, using no Node toolchain. The only new dependency is goldmark unless research names another and it is approved.
  3. The build emits llms.txt, llms-full.txt and a clean .md for every page, and a test asserts all three stay in sync with the page tree.
  4. Every Go example in a docs/ page is compiled and run by go test ./... (Go code in ingested module READMEs is identifier-checked, not compiled — D-18). An identifier checker and an internal link/anchor checker also run there and fail on stale names or broken links.
  5. A "Coming from WinterCMS" concept map and an acme/blog porting walkthrough exist, and the walkthrough's code is verified under criterion 4.

Plans: 7/7 plans executed

Plans: Wave 1

  • 11.1-01-PLAN.md — Tracer: internal/docsite generator core to every output (html, .md, llms.txt, llms-full.txt, search index), README ingestion, src= snippets, summer docs:build and docs:sync

Wave 2 (blocked on Wave 1 completion)

  • 11.1-02-PLAN.md — Accuracy gates (identifiers, links/anchors, command names, forbidden names, go-fence policy), UI-SPEC theme with chroma/v2, search, dark mode, summer docs:serve, phase gate, CLAUDE.md D-13 rule

Wave 3 (blocked on Wave 2 completion)

  • 11.1-03-PLAN.md — Content A: Setup (incl. Coming from WinterCMS), Architecture, Plugins, Console, module Examples

Wave 4 (blocked on Wave 3 completion)

  • 11.1-04-PLAN.md — Content B: Database, Backend, Services (jobs, realtime, push, search, parity, transactions), Frontend and AJAX (not provided), module Examples

Wave 5 (blocked on Wave 4 completion)

  • 11.1-05-PLAN.md — acme/blog porting walkthrough under docs/examples/blog with Docker and scaffold-layout tests

Wave 6 (blocked on Wave 5 completion)

  • 11.1-06-PLAN.md — Unit tests last: planted-violation fixtures, internal/docsite coverage, SC1-SC5 acceptance, final gate, validated VALIDATION.md

Wave 7 (gap closure, blocked on Wave 6 completion)

  • 11.1-07-PLAN.md — Checkers fail closed: AST fence discovery (nested and README src= refused, captions only on verified fences, Go-lexer aliases need src=), go test roots and go/build membership for src= targets, go doc -c, env/flag/go run/bin command forms; planted fixtures and unit tests for every hole

Phase 11.2: summercms.io Alpha 0.1 landing page on SummerCMS (INSERTED)

Goal: summercms.io replaces its "Under construction" page with the Alpha 0.1 landing page from the claude.ai/design handoff. The page is a Nuxt 4 static site, English only and i18n-ready, embedded in and served by a SummerCMS binary that also serves the Phase 11.1 docs at /docs. It deploys behind nginx with supervisor using documented steps. This is a quick phase of 2-3 plans; newsletter signup moved to Phase 11.3. Requirements: TBD Depends on: Phase 11.1 Repos: three new repos under git.golem15.com/golem15/: (1) sm-summercmsio-app, the root app that builds the binary and wires the plugins; (2) vue-summercmsio-app, the Nuxt 4 site, held inside the root app; (3) sm-summercmsio-plugin, the site plugin that serves the embedded site at /, the docs at /docs and any site-specific data, the WinterCMS site-plugin pattern. summercms.go changes only if the site needs a framework feature that is missing. Naming convention: the same as OctoberCMS (oc-) and WinterCMS (wn-), with the sm- prefix: root app sm-<name>-app, Vue frontend vue-<name>-app, plugin sm-<name>-plugin, theme sm-<name>-theme. The Go package inside a plugin keeps its plain name (newsletter). The meta root summercms.io and the framework summercms.go keep their names. fonoteka.go becomes sm-fonoteka-app. Design source: .planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/design/ (handoff README.md, SummerCMS Landing.dc.html, assets/sun-crop.png). The design is high fidelity: colors, type, spacing and copy are final. The handoff supersedes 11.2-CONTEXT D-06 (English only for now), D-08, D-10 and D-11 (no signup, "Alpha 0.1 is out", Source links allowed). Success Criteria (what must be TRUE):

  1. nuxt generate in vue-summercmsio-app produces the landing page matching the handoff: sticky header with scroll-spy, hero, the Why, Features, From WinterCMS and Get started sections, a terminal card with a working Copy button, and a footer. It is responsive down to phone width, and the section links hide at 720px or less.
  2. One sm-summercmsio-app binary embeds the Nuxt output and the Phase 11.1 docs build. It serves / and /docs with indexable responses (no admin noindex/CSP) and with cache headers: immutable for hashed assets, no-cache for HTML.
  3. Every link on the page resolves, including the eight feature-tile /docs/... links and the WinterCMS concept-map link. They are verified against the built docs, not assumed.
  4. A scripted build (nuxt generate, then summer docs:build, then go build) and a DEPLOY.md in the root app cover building, uploading, a supervisor program config, and an nginx site config (TLS, proxy to the binary, gzip). Following it on a clean server brings the site up.
  5. The new Go code has unit tests, delivered in the phase's last plan.

Context: .planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/11.2-CONTEXT.md (D-01 to D-05, D-07, D-09 and D-12 still apply; see its supersession note)

Plans: 1/3 plans executed

Plans: Wave 1

  • 11.2-01-PLAN.md — vue-summercmsio-app: Nuxt 4 landing page matching the handoff (en-only i18n, @nuxt/fonts, @nuxtjs/seo with static OG, sun assets from logo.png, single-source terminal commands, Copy, scroll-spy, app config flags)

Wave 2 (blocked on Wave 1 completion)

  • 11.2-02-PLAN.md — Framework PG15 verification and site_url/site_label (D-25, D-41, D-46), v0.1.0 tag checkpoint (D-42), sm-summercmsio-plugin static serving at / and /docs (D-07, D-47), sm-summercmsio-app wiring, build and smoke scripts, link and terminal checks (SC3, D-40), DEPLOY.md with nginx, supervisor and rollback

Wave 3 (blocked on Wave 2 completion)

  • 11.2-03-PLAN.md — Unit tests last: plugin rule table at 90%+ coverage, site_url/site_label branches, terminal-check helpers, check-phase11.2.sh gate, validated VALIDATION.md

Phase 11.3: Newsletter plugin and signup on summercms.io (INSERTED)

Goal: Visitors to summercms.io subscribe for updates through the initial Go version of the Golem15 Newsletter plugin, which confirms each email by double opt-in. The signup widget is added to the Phase 11.2 landing page, and the site gains Polish alongside English. Requirements: TBD Depends on: Phase 11.2 Repos: sm-newsletter-plugin (new, under git.golem15.com/golem15/, Go package newsletter), plus changes to the Phase 11.2 repos (vue-summercmsio-app for the widget and Polish locale, sm-summercmsio-app to wire the plugin). summercms.go changes only if a framework feature is missing. Port source: Golem15.Newsletter, github.com/golem15com/wn-newsletter-plugin, checked out at /media/nvme/dev/golem15/horoskopia.eu/plugins/golem15/newsletter. The PHP plugin is work in progress. Its audience comes from registered users (it requires Golem15.User), and its only public routes are unsubscribe. It has no public subscriber model or signup widget, so this phase adds those in Go. The PHP original is not changed. Success Criteria (what must be TRUE):

  1. The newsletter plugin repo exists and ships an initial stub: a public signup endpoint (validated, honeypot and rate-limited, with required consent), double opt-in confirmation mail with a tokenized confirm link, a welcome mail, unsubscribe, and subscriber add, edit and delete in the admin SPA. Composing and sending newsletters is out of scope.
  2. The landing page has a signup widget that calls the plugin's API on the same origin and covers the pending, confirmed and error states, with localized result pages.
  3. The site is prerendered in English and Polish, and the confirmation and welcome mails follow the subscriber's locale.
  4. The new code has unit tests, delivered in the phase's last plan.

Context: decisions D-13 to D-22 in .planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/11.2-CONTEXT.md (carried over from the original 11.2 discussion).

Plans: 0 plans

Plans:

  • TBD (run /gsd-plan-phase 11.3 to break down)

Phase 12: Płytarium API — Collections and Albums

Goal: Collections and Albums endpoints are ported with byte-compatible request/response shapes, including active-context switching, editor invitations, ratings, reservations, cover handling and search. Mode: mvp Depends on: Phase 5, Phase 6, Phase 7, Phase 11 Repos: fonoteka.go Requirements: API-01, API-02 Success Criteria (what must be TRUE):

  1. Collections CRUD, the me/context active-collection switch (returning an opaque channel name), editor invitation/acceptance, share-link regeneration and public token views all pass the parity diff.
  2. Albums CRUD, ratings, reservations, photo upload, manual cover URL and Discogs cover price all pass the parity diff.
  3. Album search treats Typesense results as a pre-filter re-gated in SQL, verified by a security test that a stale/mis-scoped search document cannot leak an unauthorized result.
  4. Artists/genres/styles lookup endpoints used by the Albums UI pass the parity diff.
  5. A request-DTO-level fuzz over every write endpoint asserts unknown and server-owned keys are never persisted (inherits the HTTP half of Phase 5 criterion 3; the HTTP layer does not exist until Phase 6/12).

Plans: TBD

Phase 13: Płytarium API — wishlist, notifications, CSV, credentials, public routes

Goal: The remaining core API surface — wishlist, notifications, CSV import/export, per-user/org credentials, and onboarding/public/invitation routes — is ported with byte-compatible shapes and their own public rate-limit buckets. Mode: mvp Depends on: Phase 11, Phase 12 Repos: fonoteka.go Requirements: API-03, API-04, API-05, API-06, API-07 Success Criteria (what must be TRUE):

  1. Wishlist items, subscriptions, public-wishlist/{token} views and purchase/digest triggers pass the parity diff.
  2. Notifications list/mark-read/prune endpoints pass the parity diff (the realtime token endpoint itself is owned by the websockets plugin, ported in Phase 11).
  3. CSV import runs as a multi-step session (store, show/poll, mapping patch, per-row edit, commit, cancel) and CSV export works on both authenticated groups, all passing the parity diff.
  4. Per-user and per-org Discogs/AI credentials CRUD store encrypted values, honor the org-lock flag, and resolve env-to-org-to-user correctly.
  5. Onboarding, public and invitation-inspection routes are reachable without auth and enforce their own public rate-limit buckets.

Plans: TBD

Phase 14: Domain jobs and external integrations

Goal: The domain-specific River jobs (CSV import write, Discogs match, wishlist digest), the reindex command, the Discogs client and AI cover recognition are ported on top of the Phase 11 jobs/realtime/search infrastructure and the Phase 13 API surface they serve. Mode: mvp Depends on: Phase 11, Phase 13 Repos: fonoteka.go (plus summercms.go only if a framework helper is needed) Requirements: JOBS-02, JOBS-03, SRCH-02, INTG-01, INTG-02, API-08, CLI-05 Success Criteria (what must be TRUE):

  1. The CSV import write job and the self-redispatching Discogs match job (240s timeout, self-redispatching with a delay on a Discogs rate-limit error) both complete correctly.
  2. The wishlist digest job coalesces a 30-minute window and deletes its queue row on completion.
  3. The reindex command asserts zero collection_id-0 documents before and after and can drop the legacy index.
  4. The Discogs client enforces its proactive rate threshold, bounded wait budget, retry-after fallback and host-locked cover fetch.
  5. AI cover recognition works through both Anthropic and OpenAI-compatible adapters with per-credential model/base-URL overrides.
  6. Feedback submissions and sitemap output work; the ported oauth-client/prune-notifications/reindex commands all run correctly.

Plans: TBD

Phase 15: Cutover

Goal: The parity harness is green on all 154 routes and vue-fonoteka-app and fonoteka-mcp run unchanged against the Go backend in daily use — the project's definition of done. Mode: mvp Depends on: Phase 2, Phase 8, Phase 9, Phase 10, Phase 12, Phase 13, Phase 14 Repos: fonoteka.go, summercms.go Requirements: API-09, QA-05 Success Criteria (what must be TRUE):

  1. All 154 routes are registered on the correct auth groups with identical paths, methods and status codes.
  2. The parity harness runs green across recorded fixtures for all 154 routes.
  3. vue-fonoteka-app runs unchanged against the Go backend for a full manual session (browse, edit, upload, invite, OAuth-connect an MCP client).
  4. fonoteka-mcp completes its install/auth flow and a representative set of tool calls unchanged against the Go backend.

Plans: TBD

Progress

Execution Order: Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 → 9 → 10 → 11 → 12 → 13 → 14 → 15 (Phase 2 depends on Phase 1's command kernel; the two are no longer parallel.)

Phase Plans Complete Status Completed
1. Framework kernel foundation 4/4 Complete 2026-09-16
2. API parity harness bootstrap 5/5 Complete 2026-09-17
3. First vertical slice — genres end to end 4/4 Complete 2026-09-17
4. CLI scaffolding, i18n and mail 4/4 Complete 2026-09-18
5. Data layer full fidelity 6/6 Complete 2026-09-18
6. HTTP routing, auth groups and rate limiting 14/14 Complete 2026-09-21
7. User plugin and authentication 8/8 Complete 2026-09-23
8. OAuth2.1 authorization server 10/10 Complete 2026-09-23
9. Backend admin authentication and schema pipeline 12/12 In Progress
10. Admin Vue SPA 5/5 Complete 2026-09-27
11. Jobs, realtime and search infrastructure 8/8 In Progress
11.1. SummerCMS documentation for humans and AI agents 7/7 In Progress
11.2. summercms.io Alpha 0.1 landing page on SummerCMS 1/3 In Progress
11.3. Newsletter plugin and signup on summercms.io 0/TBD Not started -
12. Płytarium API — Collections and Albums 0/TBD Not started -
13. Płytarium API — wishlist, notifications, CSV, credentials, public routes 0/TBD Not started -
14. Domain jobs and external integrations 0/TBD Not started -
15. Cutover 0/TBD Not started -