1357 lines
103 KiB
Markdown
1357 lines
103 KiB
Markdown
# 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, a local Nuxt-on-Go dress rehearsal, the Journal plugin and a first blog (grzybyfunkcjonalne.pl) on reusable views, and finally production cutover of Płytarium where the parity harness is green on every manifest route 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.
|
||
|
||
- [x] **Phase 1: Framework kernel foundation** - Config, plugin registry, event bus, container and CLI skeleton boot the `summer` binary (completed 2026-09-16)
|
||
- [x] **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)
|
||
- [x] **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)
|
||
- [x] **Phase 4: CLI scaffolding, i18n and mail** - Scaffolding commands, translated/pluralized strings, mail templates (completed 2026-09-18)
|
||
- [x] **Phase 5: Data layer full fidelity** - All 25 models and their squashed migrations with fillable/hidden/cast/soft-delete discipline (completed 2026-09-18)
|
||
- [x] **Phase 6: HTTP routing, auth groups and rate limiting** - Three auth groups, named rate buckets, OAuth-safe middleware structure (completed 2026-09-21)
|
||
- [x] **Phase 7: User plugin and authentication** - Registration, login, JWT, organizations, personal tokens, must-change-password (completed 2026-09-22)
|
||
- [x] **Phase 8: OAuth2.1 authorization server** - direct standard-library `wristband` server for fonoteka-mcp and the ChatGPT connector (completed 2026-09-23)
|
||
- [x] **Phase 9: Backend admin authentication and schema pipeline** - Admin roles, fields.yaml/columns.yaml, relation manager (completed 2026-10-01)
|
||
- [x] **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
|
||
- [x] **Phase 12: Płytarium API — Collections and Albums** - Core content endpoints ported with byte-level parity (completed 2026-10-02)
|
||
- [ ] **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
|
||
- [x] **Phase 14.2: Local Fonoteka frontend on local SummerCMS backend** - vue-fonoteka-app locally against fonoteka.go (completed 2026-10-12)
|
||
- [ ] **Phase 15: Journal plugin** - Port Golem15.Journal to sm-journal-plugin
|
||
- [ ] **Phase 16: grzybyfunkcjonalne.pl on reusable blog views** - First blog on SummerCMS
|
||
- [ ] **Phase 17: Blog companions** - Port sitemap, menu and FAQ as shared plugins, with a framework menu-item type registry
|
||
- [ ] **Phase 18: Payment Gateway** - Port Golem15.PaymentGateway and its Stripe operator, with Horoskopia as the acceptance consumer
|
||
- [ ] **Phase 19: Chat and ChatChannels** - Port Golem15.Chat and Golem15.ChatChannels on lighthouse
|
||
- [ ] **Phase 19.1: Chat satellites** - Port chatbots, chatvideo, forum, herald and userfriends
|
||
- [ ] **Phase 19.2: Chat polish against WavePath clients** - Replay WavePath's chat traffic through `tide` against the ported chat plugins until green
|
||
- [ ] **Phase 19.3: Framework transport and infrastructure for WavePath** - Cookie refresh, credentialed CORS, cache and Redis rate limits, S3 option, OpenAPI page, Turnstile
|
||
- [ ] **Phase 19.4: OIDC provider on wristband** - Discovery, JWKS with key rotation, id_token, userinfo, consent and pairwise subjects
|
||
- [ ] **Phase 19.5: Social login in sm-user-plugin** - Google and Facebook login with the SPA one-time-code handoff
|
||
- [ ] **Phase 19.6: Two-factor auth and device flow in sm-user-plugin** - TOTP, WebAuthn, email and recovery codes, trusted devices, device authorization
|
||
- [ ] **Phase 20: Płytarium cutover** - Parity harness green, both real clients run unchanged in production
|
||
|
||
## 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**
|
||
|
||
- [x] 01-01-PLAN.md — Build and boot the separate hello app through the shared command kernel
|
||
|
||
**Wave 2** *(blocked on Wave 1 completion)*
|
||
|
||
- [x] 01-02-PLAN.md — Add layered config, optional plugin composition, services and typed events
|
||
|
||
**Wave 3** *(blocked on Wave 2 completion)*
|
||
|
||
- [x] 01-03-PLAN.md — Add plugin scaffolding, rich output and the watch rebuild loop
|
||
|
||
**Wave 4** *(blocked on Wave 3 completion)*
|
||
|
||
- [x] 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**
|
||
|
||
- [x] 02-01-PLAN.md — Record and replay one fixture through the `summer` CLI
|
||
|
||
**Wave 2** *(blocked on Wave 1 completion)*
|
||
|
||
- [x] 02-02-PLAN.md — Capture safe proxy sessions and manifest routes with strict diffs
|
||
|
||
**Wave 3** *(blocked on Wave 2 completion)*
|
||
|
||
- [x] 02-03-PLAN.md — Record all 154 PHP routes and real Nuxt/MCP flows
|
||
|
||
**Wave 4** *(blocked on Wave 3 completion)*
|
||
|
||
- [x] 02-04-PLAN.md — Wire honest Go replay with testcontainers Postgres
|
||
|
||
**Wave 5** *(blocked on Waves 1–4 completion)*
|
||
|
||
- [x] 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**
|
||
|
||
- [x] 03-01-PLAN.md — Boot the Postgres-backed JWT genre route through both plugins
|
||
|
||
**Wave 2** *(blocked on Wave 1 completion)*
|
||
|
||
- [x] 03-02-PLAN.md — Port active-collection counts, validation and Polish ordering
|
||
|
||
**Wave 3** *(blocked on Wave 2 completion)*
|
||
|
||
- [x] 03-03-PLAN.md — Add typed params, isolated rollback and the first real parity pass
|
||
|
||
**Wave 4** *(blocked on Wave 3 completion)*
|
||
|
||
- [x] 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**
|
||
|
||
- [x] 04-01-PLAN.md — Generate compiling plugin artifacts, registry and model import checks
|
||
|
||
**Wave 2** *(blocked on Wave 1 completion)*
|
||
|
||
- [x] 04-02-PLAN.md — Load namespaced translations, plurals, parameters and locale fallbacks
|
||
|
||
**Wave 3** *(blocked on Wave 2 completion)*
|
||
|
||
- [x] 04-03-PLAN.md — Register, render and send plugin mail through pluggable drivers
|
||
|
||
**Wave 4** *(blocked on Wave 3 completion)*
|
||
|
||
- [x] 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**
|
||
|
||
- [x] 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)*
|
||
|
||
- [x] 05-02-PLAN.md — Album/Collection slice: migrations, models, casts, validation engine, write services
|
||
|
||
**Wave 3** *(blocked on Wave 2 completion)*
|
||
|
||
- [x] 05-03-PLAN.md — Secrets slice: encrypted cast, key management, organisations, credentials/OAuth tables
|
||
- [x] 05-04-PLAN.md — Attachments: system_files, blob storage, thumbnails, delete lifecycle
|
||
|
||
**Wave 4** *(blocked on Wave 3 completion)*
|
||
|
||
- [x] 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)*
|
||
|
||
- [x] 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)*
|
||
|
||
- [x] 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
|
||
- [x] 06-04-PLAN.md — SSRF-guarded outbound fetch helper (framework primitive, independent of the other three plans)
|
||
|
||
**Wave 2** *(blocked on 06-01)*
|
||
|
||
- [x] 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)*
|
||
|
||
- [x] 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)*
|
||
|
||
- [x] 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)*
|
||
|
||
- [x] 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)*
|
||
|
||
- [x] 06-07-PLAN.md — Make limiter admission atomic and remove attacker-controlled Host from inline keys
|
||
- [x] 06-08-PLAN.md — Reject private IPv4 embedded in NAT64 and 6to4 dial addresses
|
||
- [x] 06-09-PLAN.md — Buffer route responses so partial-write panics yield clean raw/house 500s
|
||
- [x] 06-10-PLAN.md — Restore exact no-newline InvScope 401/403 wire bodies
|
||
|
||
**Wave 7** *(gap closure; blocked on 06-07..06-10)*
|
||
|
||
- [x] 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**
|
||
|
||
- [x] 07-01-PLAN.md — bouncer JWT lifecycle, password hashing, I18N-02 locale-from-principal, lagoon.Validate extensions
|
||
|
||
**Wave 2** *(blocked on 07-01)*
|
||
|
||
- [x] 07-02-PLAN.md — User/Throttle schema and the core session loop: login/logout/fetch/refresh/register
|
||
|
||
**Wave 3** *(blocked on 07-02)*
|
||
|
||
- [x] 07-03-PLAN.md — Account management: forgot/reset password, activation, update, change-password, avatar, mail
|
||
- [x] 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)*
|
||
|
||
- [x] 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)*
|
||
|
||
- [x] 07-06-PLAN.md — Full unit coverage, 07-VALIDATION.md sign-off
|
||
|
||
**Wave 6** *(gap closure; blocked on 07-06)*
|
||
|
||
- [x] 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)*
|
||
|
||
- [x] 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**
|
||
|
||
- [x] 08-01-PLAN.md — Mount exact connector-visible metadata and establish fail-closed RED verification
|
||
|
||
**Wave 2** *(blocked on 08-01)*
|
||
|
||
- [x] 08-02-PLAN.md — Deliver persistent connector-visible DCR with corrected schema and transaction-scoped stores
|
||
|
||
**Wave 3** *(blocked on 08-02)*
|
||
|
||
- [x] 08-03-PLAN.md — Create durable PKCE-bound authorize requests on the assembled raw route surface
|
||
|
||
**Wave 4** *(blocked on 08-03)*
|
||
|
||
- [x] 08-04-PLAN.md — Implement atomic PKCE-bound authorization-code exchange
|
||
|
||
**Wave 5** *(blocked on 08-04)*
|
||
|
||
- [x] 08-05-PLAN.md — Wire JWT consent and prove the unchanged Nuxt UI contract
|
||
|
||
**Wave 6** *(blocked on 08-05)*
|
||
|
||
- [x] 08-06-PLAN.md — Rotate refresh grants, kill replayed lineages, and manage connected apps
|
||
|
||
**Wave 7** *(parallel; blocked on 08-06)*
|
||
|
||
- [x] 08-07-PLAN.md — Provision confidential clients through the exact operator command
|
||
- [x] 08-08-PLAN.md — Serve the MCP personal-token bootstrap on the existing token surface
|
||
|
||
**Wave 8** *(blocked on 08-07 and 08-08)*
|
||
|
||
- [x] 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)*
|
||
|
||
- [x] 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 complete
|
||
**Research flag:** yes
|
||
|
||
Plans:
|
||
|
||
**Wave 1**
|
||
- [x] 09-01-PLAN.md — Prove the architecture with one production end-to-end Genre list
|
||
|
||
**Wave 2** *(blocked on Wave 1 completion)*
|
||
- [x] 09-02-PLAN.md — Complete backend identity lifecycle and operator provisioning
|
||
|
||
**Wave 3** *(blocked on Wave 2 completion)*
|
||
- [x] 09-03-PLAN.md — Compile the typed form-schema contract and Winter scaffolding
|
||
|
||
**Wave 4** *(blocked on Wave 3 completion)*
|
||
- [x] 09-04-PLAN.md — Compile the list contract and the allowlisted query engine
|
||
|
||
**Wave 5** *(blocked on Wave 4 completion)*
|
||
- [x] 09-05-PLAN.md — Deliver schema-projected CRUD and transactional bulk deletion
|
||
|
||
**Wave 6** *(blocked on Wave 5 completion)*
|
||
- [x] 09-06-PLAN.md — Port the Albums admin surface and the collection boundary
|
||
|
||
**Wave 7** *(blocked on Wave 6 completion)*
|
||
- [x] 09-07-PLAN.md — Port the Artists backend controller
|
||
- [x] 09-08-PLAN.md — Complete the Genres controller with form/list parity
|
||
- [x] 09-09-PLAN.md — Port the Styles controller and typed provider/filter behavior
|
||
- [x] 09-10-PLAN.md — Deliver Collections and the typed relation manager
|
||
|
||
**Wave 8** *(blocked on Wave 7 completion)*
|
||
- [x] 09-11-PLAN.md — Complete permissions, navigation, and singleton settings
|
||
|
||
**Wave 9** *(blocked on Wave 8 completion)*
|
||
- [x] 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**
|
||
- [x] 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)*
|
||
- [x] 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)*
|
||
- [x] 10-03-PLAN.md — SPA lists, forms, filter bar and settings screen for all five controllers
|
||
|
||
**Wave 4** *(blocked on Wave 3 completion)*
|
||
- [x] 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)*
|
||
- [x] 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**
|
||
- [x] 10.2-01-PLAN.md — Nest the 18 beach packages under modules/ and rewrite every importer
|
||
|
||
**Wave 2** *(blocked on Wave 1 completion)*
|
||
- [x] 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**
|
||
- [x] 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)*
|
||
- [x] 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)*
|
||
- [x] 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)*
|
||
- [x] 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**
|
||
- [x] 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)*
|
||
- [x] 11-02-PLAN.md — Scheduler: pact.HasSchedule, Daily/Every, River periodic jobs, bonfire.Call, schedule:run (+ --once), fonoteka prune-notifications entry
|
||
- [x] 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)*
|
||
- [x] 11-05-PLAN.md — Search: beachcomber + Typesense engine, after-commit sync, fonoteka Album Searchable and settings kill-switch
|
||
- [x] 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)*
|
||
- [x] 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)*
|
||
- [x] 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)*
|
||
- [x] 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**
|
||
- [x] 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)*
|
||
- [x] 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)*
|
||
- [x] 11.1-03-PLAN.md — Content A: Setup (incl. Coming from WinterCMS), Architecture, Plugins, Console, module Examples
|
||
|
||
**Wave 4** *(blocked on Wave 3 completion)*
|
||
- [x] 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)*
|
||
- [x] 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)*
|
||
- [x] 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)*
|
||
- [x] 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 the backlog (Phase 999.1).
|
||
**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:** 3/3 plans executed
|
||
|
||
Plans:
|
||
**Wave 1**
|
||
- [x] 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)*
|
||
- [x] 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)*
|
||
- [x] 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 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, manual cover URL and Discogs cover import, and search.
|
||
**Mode:** mvp
|
||
**Depends on**: Phase 5, Phase 6, Phase 7, Phase 11
|
||
**Repos:** fonoteka.go, sm-user-plugin (submodule at fonoteka.go/plugins/golem15/user)
|
||
**Requirements**: API-01, API-02
|
||
**Success Criteria** (what must be TRUE):
|
||
|
||
1. Collections CRUD with photos and image, the `collections/{id}/switch` and `me/context` flags, the opaque channel name from `GET realtime/channels`, editor invitation/acceptance and members, and the owner-only `collection/share` show/update/regenerate all pass the parity diff (anonymous public token views moved to Phase 13).
|
||
2. Albums CRUD, ratings, photo upload, manual cover URL and Discogs cover import on create/bulk (`cover_urls`), plus sync/stats/value/missing/bulk all pass the parity diff (reservations moved to Phase 13, the Discogs cover-price route to Phase 14).
|
||
3. Album search treats Typesense results as a pre-filter re-gated in SQL, and the search total is the re-gated SQL count over at most 1000 engine ids (Scout v10.25.0), verified by a security test that a stale/mis-scoped search document can neither leak an unauthorized result nor be counted in the total.
|
||
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**: 4/5 plans executed
|
||
|
||
Plans:
|
||
|
||
**Wave 1**
|
||
- [x] 12-01-PLAN.md — Framework gaps (summercms.go): Laravel-semantics request validator with pl/en catalogs, attach URL/webp, tide multipart and upload masks, beachcomber found/weights; user groups in the Go user plugin sm-user-plugin (D-25); ROADMAP/REQUIREMENTS rewording
|
||
|
||
**Wave 2** *(blocked on Wave 1 completion)*
|
||
- [x] 12-02-PLAN.md — Active context, collections and share: token-aware resolver, AccessibleBy, provisioning, gates, collection serializer, collections routes, me/context, realtime/channels, collection/share, per-route scopes and per-album delete (D-26)
|
||
|
||
**Wave 3** *(blocked on Wave 2 completion)*
|
||
- [x] 12-03-PLAN.md — Household and invitations: InvitationService, transactional mail job with encrypted token, notifications write path, members, accept, 409 guard, nuxt-collections flow
|
||
|
||
**Wave 4** *(blocked on Wave 3 completion)*
|
||
- [x] 12-04-PLAN.md — Albums, search and lookups: full album write path, covers through fetchguard, uploads, bulk/stats/value/missing/sync, Scout-exact search recount, lookups, nuxt-albums flow, created/updated goldens
|
||
|
||
**Wave 5** *(blocked on Wave 4 completion)*
|
||
- [x] 12-05-PLAN.md — Unit tests last: D-18 leak test with D-19 totals, route-table scope test, request-DTO fuzz, T-12 threat tests, coverage, check-phase12.sh, security review and validation sign-off
|
||
|
||
### Phase 12.1: User plugin admin screens (INSERTED)
|
||
|
||
**Goal:** Backend admins manage frontend users, user groups and organisations in the admin SPA without SQL, so the PHP backend is not needed for user administration after cutover. The Users, User Groups and Organisations screens of the PHP user plugin are ported to `golem15.user`, driven by its `fields.yaml`/`columns.yaml`.
|
||
**Mode:** mvp
|
||
**Requirements**: TBD
|
||
**Depends on:** Phase 12 (user groups tables and the `Groups` relation from 12-01)
|
||
**Repos:** `sm-user-plugin` (mounted in fonoteka.go at `plugins/golem15/user`); `summercms.go` only if the admin pipeline is missing a feature the screens need
|
||
**Ordering:** independent of Phase 13; must land before Phase 20 (Płytarium cutover)
|
||
**Success Criteria** (what must be TRUE):
|
||
|
||
1. Users, User Groups and Organisations each have a list (columns, search, filters as in the PHP `config_filter.yaml`) and a create/update form ported from the PHP model YAML, reachable from admin navigation and gated by backend permissions.
|
||
2. A user's groups are managed through a relation field on the user form and an organisation's members through a relation manager.
|
||
3. The user actions activate, unban, unsuspend and delete, plus the list bulk actions, behave as in PHP `Users.php`.
|
||
4. Threat T-12-18 is revisited: the admin form is the first writer of `users_groups`, and only a backend user holding the required permission can change group membership.
|
||
5. The new code has unit tests, delivered in the phase's last plan.
|
||
|
||
**Open questions (discuss-phase):** impersonate user in or out of scope (security-sensitive); a separate permission for granting the `admin` group (it makes a site admin); whether convert-guest is needed for the application's data.
|
||
**Plans:** 5/5 plans executed
|
||
|
||
Plans:
|
||
|
||
**Wave 1**
|
||
- [x] 12.1-01-PLAN.md — Framework actions (summercms.go): declared bulk actions, record actions, row state and `cabana.ForbiddenError` (403), with READMEs, docs, OpenAPI, TS types, `dist/` and the neutral `acme` fixture
|
||
|
||
**Wave 2** *(blocked on Wave 1 completion)*
|
||
- [x] 12.1-02-PLAN.md — Framework preview and form seams (summercms.go): preview context, `permissioneditor`, `password`, form virtual fields, per-operation rules, writable foreign key, locked relation options, `invisible` columns, `preset`; ends with the tag v0.1.3
|
||
|
||
**Wave 3** *(blocked on Wave 2 completion)*
|
||
- [x] 12.1-03-PLAN.md — Plugin foundation and the Users screen (sm-user-plugin): additive migrations, permissions, navigation, list, filters, preview, form, bulk and record actions, delete semantics, password and invite, avatar, frontend permission resolver, `last_seen`, and the takeover guard for members of privileged groups (D-30)
|
||
|
||
**Wave 4** *(blocked on Wave 3 completion)*
|
||
- [x] 12.1-04-PLAN.md — User Groups and Organisations screens, the `members` relation manager, the privileged-group guard on every `users_groups` write path (T-12-18), and the application's parity allow-list entry and submodule pointer on framework v0.1.3
|
||
|
||
**Wave 5** *(blocked on Wave 4 completion)*
|
||
- [x] 12.1-05-PLAN.md — Unit tests last: full coverage in both repos, `scripts/check-phase12.1.sh`, the security review and the validation sign-off, then the plugin push (only when v0.1.3 is on origin)
|
||
|
||
### Phase 12.2: Admin form fields: date, file upload, relation editing with deferred binding (INSERTED)
|
||
|
||
**Goal:** A plugin's admin forms cover the three gaps a downstream project on SummerCMS v0.1 hit: a date/datetime field, a file upload field, and creating, editing and deleting related records inside the parent form (WinterCMS RelationController parity). Uploads and related-record changes on a record that is not saved yet use Winter-like deferred binding: they are held against a session key and committed with the parent's first save, or discarded with it.
|
||
**Requirements**: TBD
|
||
**Depends on:** Phase 9 (cabana form schema and relation-manager), Phase 12 plan 12-01 (`lagoon/attach` public URLs, webp, image-dimension guard)
|
||
**Repos:** summercms.go only (`modules/cabana`, `modules/lagoon/attach`, the admin SPA); framework READMEs and `docs/` updated in the same change
|
||
**Release:** ships as tag v0.1.1
|
||
**Ordering:** urgent; independent of Phase 12.1 and Phase 13, and Phase 12.1's relation managers can use it
|
||
**Success Criteria** (what must be TRUE):
|
||
|
||
1. `fields.yaml` accepts `type: datepicker` with Winter's `mode` (date, datetime, time) and stores date, timestamp and time columns without a plugin defining its own date types.
|
||
2. `type: fileupload` handles `attachOne` and `attachMany` relations: upload with size and MIME limits from the field config, image preview and thumbnail, remove, and reorder for `attachMany`. It works on both saved and unsaved records.
|
||
3. The relation manager creates, updates and deletes `hasMany` (and `belongsToMany` with pivot) related records in a modal driven by the related model's `fields.yaml`, alongside the existing link and unlink. Every child endpoint is scoped to its parent record, so a child of another parent can be neither read nor changed.
|
||
4. Deferred binding: on an unsaved parent, uploads and related-record changes bind to a session key, are committed in the parent's create transaction, and are discarded with the session. Orphaned deferred bindings and their files are purged.
|
||
5. The new code has unit tests, delivered in the phase's last plan, and the docs checker passes.
|
||
|
||
**Plans:** 4/5 plans executed
|
||
|
||
Plans:
|
||
**Wave 1**
|
||
- [x] 12.2-01-PLAN.md — Foundations: deferred_bindings table and store, attach.Store with image guard, attach.Relation, lagoon.Date/TimeOfDay with Fill and required, deferred:purge with the daily framework schedule, pact relation hooks
|
||
|
||
**Wave 2** *(blocked on Wave 1 completion)*
|
||
- [x] 12.2-02-PLAN.md — cabana fileupload (seven file routes, protected download) and datepicker fields, session key, commit of file bindings in the save transaction, OpenAPI
|
||
|
||
**Wave 3** *(blocked on Wave 2 completion)*
|
||
- [x] 12.2-03-PLAN.md — cabana relation child CRUD (hasMany kind, manage/view/pivot forms, toolbar buttons), D-15 parent scoping, unsaved-parent deferral and relation commit, child file routes
|
||
|
||
**Wave 4** *(blocked on Wave 3 completion)*
|
||
- [x] 12.2-04-PLAN.md — Admin SPA: FileuploadField, DatepickerField (Reka), child and pivot modals, create-screen deferral, date/time cells, rebuilt dist
|
||
|
||
**Wave 5** *(blocked on Wave 4 completion)*
|
||
- [ ] 12.2-05-PLAN.md — Unit and security tests (D-15 suite, test map, SPA backstops), check-phase12.2.sh, security review, v0.1.1 tag checkpoint
|
||
|
||
**Cross-cutting constraints:**
|
||
- Calendar popover: Esc returns focus to the trigger and a disabled day cannot be selected
|
||
- Datetime round trip in a fixed non-UTC zone shows the local wall clock and emits the UTC string; ignoreTimezone emits the wall clock unchanged
|
||
- Protected thumbnails are requested with X-Session-Key, rendered from an object URL and the URL is revoked on unmount
|
||
- Keyboard reorder: ArrowUp/ArrowDown on a handle moves the item, keeps focus on its handle, announces fileupload.moved and sends one debounced reorder request
|
||
|
||
### 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; sm-user-plugin (submodule at fonoteka.go/plugins/golem15/user, D-09/D-11 additive exports); summercms.go (13-01 framework gaps)
|
||
**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, reservations (`wishlist/albums/{id}/reserve|reveal`) and purchase/digest triggers pass the parity diff. The wishlist Discogs `match`/`apply-release` routes move to Phase 14 (D-01), and the digest trigger queues its job here while the digest job body is Phase 14 (JOBS-03).
|
||
2. Notifications list, unread count and mark-read (one and all) pass the parity diff; pruning is the Phase 14 `fonoteka:prune-notifications` console command, not a route (D-06). 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. Commit and mapping enqueue the CSV import and match jobs, whose bodies are Phase 14 (JOBS-02).
|
||
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. The live `ai-credential/test` and `discogs-credential/test` routes move to Phase 14 (D-02).
|
||
5. Onboarding, public and invitation-inspection routes, including the anonymous collection views `public/{token}`, `public/{token}/albums` and `public/{token}/albums/{id}`, are reachable without auth and enforce their own public rate-limit buckets.
|
||
|
||
**Plans:** 6/6 plans executed
|
||
|
||
Plans:
|
||
|
||
**Wave 1**
|
||
- [x] 13-01-PLAN.md — Framework gaps (summercms.go): surf overlap dispatch, conga workerless job kinds, lagoon prohibited, tide date and publication masks; the pinned job contract; parity scaffolding (queue override, rows dump, share:wishlist, D-15, ported case-status check); ROADMAP/REQUIREMENTS rewording
|
||
|
||
**Wave 2** *(blocked on Wave 1 completion)*
|
||
- [x] 13-02-PLAN.md — Notifications, credentials CRUD with the AI/Discogs resolution order, onboarding bootstrap, invitation inspection, sm-user-plugin RegisterEvent.Payload and registration exports, the register listener, onboarding flow (14 routes)
|
||
|
||
**Wave 3** *(blocked on Wave 2 completion)*
|
||
- [x] 13-03-PLAN.md — Wishlist (30 routes): resolver, visibility, subscriptions, secret reservations, share/settings/household, purchase with transactional mail, item-added bell and digest queue, nuxt-wishlist and mcp-wishlist flows, publication goldens
|
||
|
||
**Wave 4** *(blocked on Wave 3 completion)*
|
||
- [x] 13-04-PLAN.md — CSV (8 routes): fputcsv export on both groups, parser package with PHP truth tables, private uploads, import session with commit CAS, queued import/match jobs, D-05 seam, nuxt-csv flow
|
||
|
||
**Wave 5** *(blocked on Wave 4 completion)*
|
||
- [x] 13-05-PLAN.md — Public views (6 routes): token resolution, pubfail lockout, public search and facets, public album field set, D-14 per-route buckets, header fix, public flows
|
||
|
||
**Wave 6** *(blocked on Wave 5 completion)*
|
||
- [x] 13-06-PLAN.md — Unit tests last: route-table test, request-DTO fuzz, T-13 threat tests, coverage, check-phase13.sh, security review and validation sign-off
|
||
|
||
### 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; summercms.go (14-01 framework helpers); sm-golem-plugin and sm-feedback-plugin (new core-plugin repos mounted as submodules)
|
||
**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, and the `albums/{id}/cover-price/discogs` route, the wishlist `wishlist/albums/{id}/match` and `apply-release` routes, the album `albums/match`, `albums/{id}/match`, `albums/{id}/apply-release` and `albums/import/discogs` routes, `discogs-credential/test` and the CSV row-edit Discogs pick (`selected_discogs_id`) pass the parity diff.
|
||
5. AI cover recognition works through both Anthropic and OpenAI-compatible adapters, hand-rolled over the guarded client, with per-credential model/base-URL overrides; `albums/recognize` on the JWT and personal-token groups and `ai-credential/test` pass the parity diff, and the AI resolver's admin tier uses the backend global vision model.
|
||
6. Feedback submissions, widget config and the hide preference work (sitemap dropped for this application, D-14); the oauth-client, prune-notifications and reindex commands all run correctly.
|
||
`oauth-identities` GET/DELETE and token `GET /api/v1/fonoteka/me` stay pending in this phase (D-09, todo orphan-pending-routes).
|
||
|
||
**Plans:** 6/6 plans executed
|
||
|
||
Plans:
|
||
|
||
**Wave 1**
|
||
- [x] 14-01-PLAN.md — Framework (summercms.go): fetchguard guarded client with TrustedMode and a code-only transport seam, tide upstream sidecars with an asserting replay fake and the `summer parity:upstream` recording proxy, the `sunscreen` redacting slog handler in generated mains, beachcomber IndexDropper/EnsureIndex; READMEs and docs; REQUIREMENTS/ROADMAP/PROJECT rewording (D-06, D-07, D-13, D-14)
|
||
|
||
**Wave 2** *(blocked on Wave 1 completion)*
|
||
- [x] 14-02-PLAN.md — Discogs client with the Postgres UNLOGGED limiter and domain classes, real ReleaseFetcher, WR-02 locks, CSV match/import workers with write-service CSV variants, digest worker and mail, prune-notifications and reindex commands, row-edit pick parity cases
|
||
|
||
**Wave 3** *(blocked on Wave 2 completion)*
|
||
- [x] 14-03-PLAN.md — Discogs routes: album and wishlist match/apply-release, albums/match, albums/import/discogs, token cover-price (AlbumCoverFetcher, host-locked), discogs-credential/test; inbound limiters; recordings with sidecars (165 ported)
|
||
|
||
**Wave 4** *(blocked on Wave 3 completion)*
|
||
- [x] 14-04-PLAN.md — sm-golem-plugin (models table, admin, importer, Anthropic/OpenAI-compatible adapters, AIService, SSRF guard) mounted as a submodule; admin vision tier; AlbumRecognitionService, recognize on both groups and ai-credential/test (168 ported)
|
||
|
||
**Wave 5** *(blocked on Wave 4 completion)*
|
||
- [x] 14-05-PLAN.md — sm-feedback-plugin: config, submit, preflight, me/hidden, getApiArray hook, embed.js, settings and submissions admin, importer, G15Office River job; feedback parity section (175 routes, 172 ported)
|
||
|
||
**Wave 6** *(blocked on Wave 5 completion)*
|
||
- [x] 14-06-PLAN.md — Unit tests last: PHP Discogs truth tables, full coverage, threat and edge tests, route table and fuzz, scripts/check-phase14.sh, security review, validation and requirement evidence
|
||
|
||
### Phase 14.1: OAuth identities and fonoteka me routes (INSERTED)
|
||
|
||
**Goal:** The three manifest routes no phase owned (14-CONTEXT D-09) are ported and pass the parity diff, so that no route is left `pending` before cutover. They are `GET /_fonoteka/api/v1/oauth-identities`, `DELETE /_fonoteka/api/v1/oauth-identities/{provider}` (social-login identities, deferred since Phase 7) and `GET /api/v1/fonoteka/me` (the full contract, not only the minimal Phase 8 D-20 version).
|
||
**Mode:** mvp
|
||
**Requirements**: TBD
|
||
**Depends on:** Phase 14
|
||
**Repos:** fonoteka.go (plus sm-user-plugin if the identities live in the user plugin)
|
||
**Success Criteria** (what must be TRUE):
|
||
|
||
1. `GET oauth-identities` lists the signed-in user's linked social identities exactly as PHP returns them.
|
||
2. `DELETE oauth-identities/{provider}` unlinks one identity with PHP's status codes and error shapes.
|
||
3. `GET /api/v1/fonoteka/me` matches the PHP response for fonoteka-mcp token callers.
|
||
4. All three manifest entries flip from `pending` to ported with recorded PHP cases, leaving zero pending routes.
|
||
|
||
**Plans:** 2/2 plans complete
|
||
|
||
Plans:
|
||
|
||
**Wave 1**
|
||
- [x] 14.1-01-PLAN.md — Model, migration, exported Index/Destroy, JWT mount, `/me` null, D-10 recording and manifest flip
|
||
|
||
**Wave 2** *(blocked on Wave 1 completion)*
|
||
- [x] 14.1-02-PLAN.md — Unit tests last: OAuthIdentityApiTest port, migration, MeToken null, secret-leak, corpus 175/175/0
|
||
|
||
### Phase 14.2: Local Fonoteka frontend on local SummerCMS backend (INSERTED)
|
||
|
||
**Goal:** A developer runs the existing `vue-fonoteka-app` locally against the local `fonoteka.go` SummerCMS backend (not PHP), logs in, and uses the real Nuxt flows so the port is proven in daily use before production cutover.
|
||
**Mode:** mvp
|
||
**Depends on:** Phase 14.1
|
||
**Repos:** `vue-fonoteka-app`, `fonoteka.go` (local API, CORS, cookies, env); `summercms.go` only if a framework helper is missing
|
||
**Requirements**: QA-05
|
||
**Success Criteria** (what must be TRUE):
|
||
|
||
1. Documented local run: Nuxt and the Go backend start together, with API base URL, cookies and CORS matching how the SPA talks to Winter today.
|
||
2. A signed-in session against Go covers the core Nuxt flows: browse collections and albums, search, edit, rate, upload a cover, wishlist add/remove.
|
||
3. Failures against Go are visible (no silent fall-through to PHP); the local stack does not require the production origin.
|
||
4. The new glue (env, proxy, CORS, run docs) has tests or a fail-closed check script, delivered in the phase's last plan.
|
||
|
||
**Plans:** 4/4 plans complete
|
||
|
||
Plans:
|
||
|
||
**Wave 1**
|
||
- [x] 14.2-01-PLAN.md — Go glue: `fonoteka:dev-seed`, plugin commands open their own DB (G1), dev-only marker route, framework `/storage` mount and opt-in access log with docs
|
||
|
||
**Wave 2** *(blocked on Wave 1 completion)*
|
||
- [x] 14.2-02-PLAN.md — `scripts/dev-stack.sh` (start, fail-closed guard, logs, e2e verdict), `.env.dev.example`, `.gitignore`, run docs
|
||
|
||
**Wave 3** *(blocked on Wave 2 completion)*
|
||
- [x] 14.2-03-PLAN.md — Nuxt `E2E_BACKEND=go` switch, realtime rewrite, edit/rate/cover, search and wishlist specs on Go and PHP, Centrifugo origin step, manual UAT session
|
||
|
||
**Wave 4** *(blocked on Wave 3 completion)*
|
||
- [x] 14.2-04-PLAN.md — Unit tests (seed, commands, marker, uploads mount, access log, switch contract) and `scripts/check-phase14.2.sh`
|
||
|
||
### Phase 14.2.1: Translate plugin (INSERTED)
|
||
|
||
**Goal:** Golem15.Translate is ported to Go as `sm-translate-plugin` so Journal (Phase 15) can keep translatable fields. Lean core only: Locale model and Locales admin, Translatable API, cabana `mltext`/`mlmarkdown`, and PHP Translator locale resolution (URL prefix / session / cookie / default).
|
||
**Requirements**: TBD
|
||
**Depends on:** Phase 14.2
|
||
**Repos:** `sm-translate-plugin` (new); `sm-grzybyfunkcjonalne-app` (proof host); `summercms.go` for ML field types and the surf locale seam
|
||
**Success Criteria** (what must be TRUE):
|
||
|
||
1. Plugin repo exists with `golem15_translate_*` schema (locales, attributes, indexes, empty messages table), Locale model, en/pl seed, and context-safe Translator.
|
||
2. Translatable API (`Translatable()`, `WithLocale`, get/set, default-locale fallback) and permissioned Locales admin work; a fixture model saves and reads `en`+`pl`.
|
||
3. Cabana `markdown`/`mltext`/`mlmarkdown` compose; nested locale writes are not dropped; docs, OpenAPI, TS types and `boardwalk` `dist/` update in the same change.
|
||
4. Proof host `sm-grzybyfunkcjonalne-app` boots with user+translate; unit tests are the last plan.
|
||
|
||
**Plans:** 6/6 plans executed
|
||
|
||
Plans:
|
||
|
||
**Wave 1**
|
||
- [x] 14.2.1-01-PLAN.md — Plugin repo, `golem15_translate_*` schema, Locale, en/pl seed, Translator, surf Resolver
|
||
|
||
**Wave 2** *(blocked on Wave 1 completion)*
|
||
- [x] 14.2.1-02-PLAN.md — Translatable API, Locales admin, fixture save/read
|
||
|
||
**Wave 3** *(blocked on Wave 2 completion)*
|
||
- [x] 14.2.1-03-PLAN.md — Cabana markdown/ML fields, nested save, docs/OpenAPI/dist, proof host boot
|
||
|
||
**Wave 4** *(blocked on Wave 3 completion)*
|
||
- [x] 14.2.1-04-PLAN.md — Unit/integration tests last, phase gate, security review
|
||
|
||
**Wave 5** *(gap closure, blocked on Wave 4 completion)*
|
||
- [x] 14.2.1-05-PLAN.md — Hydrate enabled locales on admin ML forms (CR-01)
|
||
|
||
**Wave 6** *(gap closure, blocked on Wave 5 completion)*
|
||
- [x] 14.2.1-06-PLAN.md — Lift ML maps on relation-child saves (WR-02); named tests last
|
||
|
||
**Cross-cutting constraints:**
|
||
- Nested locale writes are not dropped on the admin form path and on relation-child saves
|
||
- The SPA lists every enabled locale, switches all ML controls together, and sends `Record<string,string>`
|
||
|
||
### Phase 14.3: One-command app scaffolding: summer new and summercms.io/new bootstrap script (INSERTED)
|
||
|
||
**Goal:** Starting a SummerCMS app takes one command. `summer new <name>` writes a ready-to-run `sm-<name>-app` next to the framework checkout, and `curl -fsSL https://summercms.io/new | bash -s -- <name>` checks the prerequisites, fetches the framework, installs the `summer` CLI and runs it. Ships as framework tag `v0.1.13`.
|
||
**Requirements**: TBD
|
||
**Depends on:** Phase 14.2.1
|
||
**Repos:** `summercms.go` (generator, bootstrap script, docs); `sm-summercmsio-app` / `sm-summercmsio-plugin` (serve `/new` as `text/plain`)
|
||
**Success Criteria** (what must be TRUE):
|
||
|
||
1. `summer new <name>` validates the name (lower-case letters and digits, starting with a letter), refuses an existing directory, and writes `go.mod` (module `git.golem15.com/golem15/sm-<name>-app`, go 1.27.0, relative `replace` to the framework found via `--framework`, then sibling detection by module path, else a clear error), `summer.yaml`, a starter plugin `golem15.<name>` through the `make:plugin`/`plugin:add` code paths with `go.work` (optional via `--no-plugin` if an empty plugin list builds), `config/` complete for `serve` and `route:list` (incl. `http.yaml` `body_limits`, app key, storage bucket defaults), `.env.example` and `.env` with a fresh app key, `.gitignore`, `compose.yaml` (PostgreSQL 16 matching `.env`), `CLAUDE.md`/`AGENTS.md`, and a first git commit (skippable with `--no-git`).
|
||
2. The generated app works with no edits: `summer build`, `summer migrate` and `summer serve` succeed and a starter-plugin route answers over HTTP.
|
||
3. The bootstrap script (wrapped in `main()`) checks Go 1.27+ and Docker, clones or fast-forwards the framework, runs `go install ./cmd/summer` with a PATH hint, runs `summer new`, and prints next steps; summercms.io serves it at `/new` as `text/plain`.
|
||
4. In a clean `node:24-bookworm`-style container with Go 1.27, Docker, git and jq, `curl … | bash -s -- demo` → `cd sm-demo-app && docker compose up -d && summer build && summer migrate && summer serve` ends with the starter route answering.
|
||
5. `docs/setup/installation.md` leads with the one-liner, `go-modules-and-workspaces.md` and the console docs cover `summer new`, and `llms.txt`/`llms-full.txt` include the changes; docs checks pass.
|
||
6. Generator Go tests (golden output plus a real build of the scaffold in a temp dir) are the last plan.
|
||
|
||
**Plans:** 4/4 plans executed
|
||
|
||
Plans:
|
||
|
||
**Wave 1**
|
||
- [x] 14.3-01-PLAN.md — `summer new` generator: starter plugin via make:plugin/plugin:add, relative go.work, config, .env + lagoon.GenerateAppKey, compose, CLAUDE.md/AGENTS.md, first commit, D-12 docker gate
|
||
|
||
**Wave 2** *(blocked on Wave 1 completion)*
|
||
- [x] 14.3-02-PLAN.md — Bootstrap script in `modules/sunrise`, `GET /new` in the site plugin, node:24-bookworm acceptance, security review
|
||
|
||
**Wave 3** *(blocked on Wave 2 completion)*
|
||
- [x] 14.3-03-PLAN.md — Docs (installation one-liner first, go modules, console, llms) and landing page "Get started" with its tests
|
||
|
||
**Wave 4** *(blocked on Wave 3 completion)*
|
||
- [x] 14.3-04-PLAN.md — Unit tests last: goldens, real builds, error cases, script matrix, phase gate; post-phase release checklist (tag v0.1.13, site bump, deploy are user actions)
|
||
|
||
### Phase 15: Journal plugin
|
||
|
||
**Goal:** The Golem15 Journal plugin is ported to Go as `sm-journal-plugin` and mounts in a host application the same way `sm-user-plugin` does, so a blog can run on SummerCMS without the PHP plugin.
|
||
**Mode:** mvp
|
||
**Depends on:** Phase 7, Phase 10, Phase 12.1
|
||
**Repos:** `sm-journal-plugin` (new, under `git.golem15.com/golem15/`); a host application only as needed to boot and test the plugin; `summercms.go` only if a framework feature is missing
|
||
**Requirements**: TBD
|
||
**Success Criteria** (what must be TRUE):
|
||
|
||
1. The Journal plugin repo exists with models, migrations and admin screens ported from PHP `Golem15.Journal`, driven by YAML forms/lists, permission-gated in the admin SPA.
|
||
2. A host application can mount the plugin as a submodule and serve its public content (posts, categories, the PHP-equivalent routes or a documented successor).
|
||
3. Plugin README and docs stay application-neutral (`the application`, example names such as `blog`).
|
||
4. The new code has unit tests, delivered in the phase's last plan.
|
||
|
||
**Plans:** 4/4 plans executed
|
||
|
||
Plans:
|
||
|
||
**Wave 1**
|
||
- [x] 15-01-PLAN.md — Clone plugin, squashed `golem15_journal_*` schema, Translatable, host 3-plugin boot + CORS
|
||
|
||
**Wave 2** *(blocked on Wave 1 completion)*
|
||
- [x] 15-02-PLAN.md — Admin YAML (`mlmarkdown`), FormatHTML, permissions/nav SVG, import/export
|
||
|
||
**Wave 3** *(blocked on Wave 2 completion)*
|
||
- [x] 15-03-PLAN.md — `/_journal/api/v1`, buckets, backend Bearer writes, media, Typesense gate off
|
||
|
||
**Wave 4** *(blocked on Wave 3 completion)*
|
||
- [x] 15-04-PLAN.md — Unit/integration tests last, PHPUnit map, phase gate, security review
|
||
|
||
### Phase 16: grzybyfunkcjonalne.pl on reusable blog views
|
||
|
||
**Goal:** grzybyfunkcjonalne.pl runs on SummerCMS with a new blog views layer that other Golem15 blogs can reuse, instead of a one-off theme.
|
||
**Mode:** mvp
|
||
**Depends on:** Phase 15
|
||
**Repos:** the grzybyfunkcjonalne application (Go host + views/frontend); `sm-journal-plugin`; `sm-sitemap-plugin` if the blog needs it (todo `sitemap-plugin-port.md`); `summercms.go` only if a framework feature is missing
|
||
**Requirements**: TBD
|
||
**Success Criteria** (what must be TRUE):
|
||
|
||
1. grzybyfunkcjonalne.pl is served by a SummerCMS binary (Journal content, routing, i18n as the site needs).
|
||
2. The blog views (templates/components/layout) live in a reusable place, not a site-only fork, so a later blog can mount them with its own content.
|
||
3. Public pages that PHP Journal + the current theme rendered have a named successor on Go; sitemap is in if the site still needs it.
|
||
4. The new code has unit tests, delivered in the phase's last plan.
|
||
|
||
**Plans:** 5/5 plans executed
|
||
|
||
Plans:
|
||
**Wave 1**
|
||
- [x] 16-01-PLAN.md — vue-journal-layer: prerequisite gate (repos, D-20/D-21), git-tag install spike, Journal views, typed client, routes config, tokens, pl/en, SEO/sitemap/RSS; tag v0.1.0
|
||
|
||
**Wave 2** *(blocked on Wave 1 completion)*
|
||
- [x] 16-02-PLAN.md — vue-grzybyfunkcjonalne-app: SSR site on the layer with the PHP URL map, homepage/menu/FAQ/kontakt content port, GA via runtime config; submodule in the host
|
||
|
||
**Wave 3** *(blocked on Wave 2 completion)*
|
||
- [x] 16-03-PLAN.md — sm-grzybyfunkcjonalne-app: /gfadmin, app plugin with featured-image and category-content commands, migration runbook, two-process deploy files, urls.txt and local rehearsal
|
||
|
||
**Wave 4** *(blocked on Wave 3 completion)*
|
||
- [x] 16-04-PLAN.md — Staging deploy on the PHP site's server (human checkpoint) and smoke over every old URL
|
||
|
||
**Wave 5** *(blocked on Wave 4 completion)*
|
||
- [x] 16-05-PLAN.md — Unit tests: layer and app node --test (incl. SSR), Go command and boot tests, phase gate
|
||
|
||
### Phase 16.1: Journal admin urgent fixes: relation search, list state, post form content and excerpt (INSERTED)
|
||
|
||
**Goal:** The Journal posts list and post form in the admin work on real imported data, found on UAT (uat.grzybyfunkcjonalne.pl/gfadmin/golem15/journal/posts): (1) list search no longer fails with "Server error" — the Go log shows `ERROR: column Categories.post_id does not exist (SQLSTATE 42703)` from summercms@v0.1.9 modules/cabana/query.go:129, i.e. search across a belongsToMany (pivot) relation builds a hasMany join; (2) list sort and filters are remembered after opening a record and going back, as WinterCMS persisted list state; (3) the post form shows the stored content (DB has content/content_html on 104/104 posts, the form shows it empty); (4) the post form shows the stored excerpt (100/104 posts have it in the DB). Fixes span summercms.go core (modules/cabana — coordinate with the concurrent session working in modules/) and sm-journal-plugin; release a summercms tag, bump the host, redeploy to UAT and re-run the staging smoke.
|
||
**Requirements**: TBD
|
||
**Depends on:** Phase 16
|
||
**Plans:** 3/3 plans complete
|
||
|
||
Plans:
|
||
**Wave 1**
|
||
- [x] 16.1-01-PLAN.md — All fixes: cabana relation search and to-many sort through EXISTS/correlated subqueries (D-02), default locale first in form enabledLocales (D-03), Journal FormatHTML keeps raw HTML behind the gate (D-03), admin list state in the URL and browser storage (D-01)
|
||
|
||
**Wave 2** *(blocked on Wave 1 completion)*
|
||
- [x] 16.1-02-PLAN.md — Release gate (human), summercms tag, journal and host bumps, UAT redeploy and on-server smoke, re-save posts 53 and 64 (D-04)
|
||
|
||
**Wave 3** *(blocked on Wave 2 completion)*
|
||
- [x] 16.1-03-PLAN.md — Unit tests: DryRun SQL matrix, locale order, FormatHTML keep/refuse, list-state vitest; Postgres tests for a Docker host; phase gate
|
||
|
||
### Phase 16.2: Plugin-provided field types and the Blog Editor formwidget (INSERTED)
|
||
|
||
**Goal:** Plugins can register their own admin form field types (todo: cabana-richeditor-field-type), and the Journal plugin ships a first-class Blog Editor formwidget for post content — beyond the current markdown field or a plain rich editor — that edits imported HTML posts losslessly.
|
||
**Requirements**: TBD
|
||
**Depends on:** Phase 16.1
|
||
**Plans:** 7/7 plans complete
|
||
|
||
Plans:
|
||
**Wave 1**
|
||
- [x] 16.2-01-PLAN.md — Framework (summercms.go): pact field-type API (human API checkpoint), cabana registry, boot option checks, Decode/Load/list in the save transaction, Decode notices as meta.notices (D-10 strip report), untouched skip, FieldWritesFromContext, field-types and field upload routes, OpenAPI and TS types, SPA PluginField/MLPluginField, global loader and notice display, dist, READMEs and docs
|
||
- [x] 16.2-02-PLAN.md — Journal Blog Editor element (sm-journal-plugin assets/src to assets/dist): TipTap package check (human), untouched value contract, lossless schema and serializer, policy-safe links, uploads and YouTube embeds, the committed editor-output corpus, JS build, vitest, check-dist, embed
|
||
|
||
**Wave 2** *(blocked on Wave 1 completion)*
|
||
- [x] 16.2-03-PLAN.md — Journal Go integration: blogeditor and mlblogeditor types, strip-and-report Decode per locale (SanitizeHTMLReport), D-07 Load, post and category fields (category 422 gate retired), content_html companion writes without goldmark, bluemonday policy extended for the editor's own output (corpus test), editor uploads through the extracted media storage
|
||
- [x] 16.2-04-PLAN.md — vue-journal-layer: sanitize-html mirror extended for the editor's own output (corpus copy), expandEmbeds as the last step after sanitizeJournalHtml, SSR proof, tag v0.2.0 (human push gate), pin in vue-grzybyfunkcjonalne-app
|
||
|
||
**Wave 3** *(blocked on Wave 2 completion)*
|
||
- [x] 16.2-05-PLAN.md — Release gate (human), summercms tag v0.1.18 (next free), journal and host bumps, UAT redeploy and on-server smoke, UAT acceptance: untouched re-saves byte-identical, editor edit with upload and embed, unsafe markup stripped and reported
|
||
|
||
**Wave 4** *(blocked on Wave 3 completion)*
|
||
- [x] 16.2-06-PLAN.md — Unit tests: resave contract at every layer, strip-and-report and notices, policy cross-check corpus (both policies, identical copies), category strip, full Go and JS coverage, Postgres tests for a Docker host, phase gate scripts/check-phase16.2.sh, validation map
|
||
|
||
**Wave 5** *(gap closure, blocked on Wave 4 completion)*
|
||
- [x] 16.2-07-PLAN.md — Gap closure G-16.2-1 and G-16.2-2: conformance cases for GET /field-types and the field upload route, Docker-free route inventory in the gate, TestJournalSVGEmbed fix; Journal post/category admin saves purge the host Nuxt route cache after commit through a token-protected vue-journal-layer route (browsers revalidate); full gate incl. postgres, release summercms v0.1.20 and vue-journal-layer v0.3.0, UAT redeploy, re-run of UAT Tests 3 and 2
|
||
|
||
### Phase 16.3: Backend admin personal API tokens with scopes (INSERTED)
|
||
|
||
**Goal:** Backend users can create personal API tokens with a scope they pick at creation time (for example Journal only, or system-wide; later read-only Payment Gateway), so an agent or integration can call the admin and plugin APIs as that backend user, with no more access than the token's scope and the user's own permissions allow. This is the successor of Apparatus `PersonalApiToken` (todo `backend-admin-api-tokens.md`, note `apparatus-dissolved-into-framework.md`) and reverses the Phase 15 D-15 rule that Journal writes accept only a backend JWT.
|
||
**Requirements**: TBD
|
||
**Depends on:** Phase 9, Phase 10, Phase 15
|
||
**Repos:** `summercms.go` (bouncer guard, token model and migration, scope registry, admin SPA panel, CLI); `sm-journal-plugin` to register its scope and accept tokens on its write routes; host applications only for the version bump
|
||
**Constraints from explore (2026-10-10):**
|
||
- Tokens keep the `g15_` prefix and are stored only as a hash. The rest of the format can change, and WinterCMS Apparatus tokens are not migrated.
|
||
- The user picks the scope when creating a token. Plugins register their own scopes. Scope design (names, read-only variants, system-wide) is settled in discuss-phase.
|
||
- Tokens are managed in two places, as in WinterCMS: a panel on the admin SPA backend user form (create, show once, revoke) and a `summer` CLI command.
|
||
|
||
**Success Criteria** (what must be TRUE):
|
||
|
||
1. The `backend` guard accepts a valid, unexpired, unrevoked `g15_` token as well as the backend JWT, and the request runs as the token's backend user.
|
||
2. A request outside the token's scope is refused even when the user's role would allow it, and a token never grants more than its owner's permissions.
|
||
3. A backend user can create, list and revoke their own tokens in the admin SPA. The plaintext token is shown once. A CLI command does the same for any backend user.
|
||
4. The Journal API accepts a Journal-scoped token for writes, so an agent can manage blog posts as a backend author.
|
||
5. Module READMEs and `docs/` cover the token API, scopes and CLI commands.
|
||
6. The new code has unit tests, delivered in the phase's last plan.
|
||
|
||
**Plans:** 4/4 plans complete
|
||
|
||
Plans:
|
||
**Wave 1**
|
||
- [x] 16.3-01-PLAN.md — Framework (summercms.go): pact.HasTokenScopes, bouncer scope limit, Allows rewrite (scope before the superuser shortcut), backend_api_tokens migration, composite backend guard, interactive-only marker, route gates, published cabana.ResolveBackend, account token API with OpenAPI and inventories, admin:token:* CLI with party.Activated and summer delegates, READMEs and docs
|
||
|
||
**Wave 2** *(blocked on Wave 1 completion)*
|
||
- [x] 16.3-02-PLAN.md — Admin SPA API tokens card on My account per UI-SPEC: list states, two-step create with show-once plaintext and copy, revoke, phrase keys, dist, docs
|
||
|
||
**Wave 3** *(blocked on Wave 2 completion)*
|
||
- [x] 16.3-03-PLAN.md — Journal: journal and journal:read scopes, token auth through ResolveBackend with byte-identical bodies (reverses Phase 15 D-15), Journal README and the ResolveBackend docs example; release gate (human), summercms tag after v0.1.20, Journal go.mod and host gitlink bumps
|
||
|
||
**Wave 4** *(blocked on Wave 3 completion)*
|
||
- [x] 16.3-04-PLAN.md — Unit tests: scope rules, route classification, guard, resolver, account API, CLI, migration, SPA backstops, Journal token matrix; Postgres tests for a Docker host; phase gate scripts/check-phase16.3.sh; validation map
|
||
|
||
### Phase 17: Blog companions
|
||
|
||
**Goal:** The sitemap, menu and FAQ plugins run on SummerCMS as shared `sm-*-plugin` repos, so a blog site can manage its menus, FAQs and sitemap in the admin again instead of hardcoding them in the Nuxt app.
|
||
**Mode:** mvp
|
||
**Depends on:** Phase 15, Phase 16
|
||
**Repos:** `sm-sitemap-plugin`, `sm-menu-plugin`, `sm-faq-plugin` (new, under `git.golem15.com/golem15/`, see `.planning/notes/core-plugins-own-repos.md`); `summercms.go` for the menu-item type registry; `sm-journal-plugin` for an additive registration of its item types; `sm-grzybyfunkcjonalne-app` to move its FAQ and menu into the plugins
|
||
**Port sources:** `Golem15.Sitemap` at `/media/nvme/dev/golem15/fonoteka/plugins/golem15/sitemap`; `Golem15.Menu` and `Golem15.Faq` at `/media/nvme/dev/golem15/grzybyfunkcjonalne.pl/plugins/golem15/{menu,faq}`. Todos: `sitemap-plugin-port.md`, `menu-plugin-port.md`, `faq-plugin-port.md`. Inventory: `.planning/notes/core-plugin-port-inventory.md`.
|
||
**Requirements**: TBD
|
||
**Success Criteria** (what must be TRUE):
|
||
|
||
1. The three plugins port their PHP schema, models, admin screens and import commands 1:1, each in its own repo with a README.
|
||
2. The framework has a typed successor of the `pages.menuitem.listTypes`, `getTypeInfo` and `resolveItem` events. Journal registers its item types through it, and both menu and sitemap consume it.
|
||
3. `sitemap.xml` and `sitemap.xsl` match the PHP output, including the `frontend_url` domain rewrite and hreflang. Menu and FAQ expose public read APIs that a Nuxt layer can consume.
|
||
4. grzybyfunkcjonalne.pl reads its menu and FAQ from the plugins instead of app config.
|
||
5. The new code has unit tests, delivered in the phase's last plan.
|
||
|
||
**Plans:** 5/6 plans executed
|
||
|
||
Plans:
|
||
**Wave 1**
|
||
|
||
- [x] 17-01-PLAN.md — Framework Go: pact menu-item types and the signpost registry, seaglass sanitizer, cabana repeater, dependent dropdown, richeditor, tree reorder and singleton form
|
||
|
||
**Wave 2** *(blocked on Wave 1 completion)*
|
||
|
||
- [x] 17-02-PLAN.md — Admin SPA (RichEditorField on TipTap, RepeaterField, dependent dropdown, ReorderView, singleton form), dist, and the framework release tag
|
||
- [x] 17-03-PLAN.md — Journal registers its five item types; sm-sitemap-plugin with PHP byte parity, admin and import commands
|
||
- [x] 17-04-PLAN.md — sm-menu-plugin and sm-faq-plugin: admin, Winter imports, and the /_menu and /_faq read APIs
|
||
|
||
**Wave 3** *(blocked on Wave 2 completion)*
|
||
|
||
- [x] 17-05-PLAN.md — grzybyfunkcjonalne.pl integration: repos, vue-menu-layer and vue-faq-layer, data copy, nginx, UAT redeploy
|
||
|
||
**Wave 4** *(blocked on Wave 3 completion)*
|
||
|
||
- [ ] 17-06-PLAN.md — Unit tests across all repositories, scripts/check-phase17.sh and the validation map
|
||
|
||
### Phase 18: Payment Gateway
|
||
|
||
**Goal:** `Golem15.PaymentGateway` and its Stripe operator run on SummerCMS as shared plugins whose contracts let Horoskopia's horoscope plugin create orders, take payments and react to payment events, as it does on PHP.
|
||
**Mode:** mvp
|
||
**Depends on:** Phase 15
|
||
**Repos:** `sm-paymentgateway-plugin` and `sm-pgstripe-plugin` (new, under `git.golem15.com/golem15/`); `summercms.go` only if a framework feature is missing
|
||
**Port sources:** `Golem15.PaymentGateway` v2.6.0 and `Golem15.PGStripe`, at `/media/nvme/dev/golem15/horoskopia.eu/plugins/golem15/{paymentgateway,pgstripe}` (starter submodule `wn-paymentgateway-plugin`, branch `upgrade`). Acceptance consumer: Horoskopia's `horoscope` plugin, which uses `Order`, `OrderFactory`, `Cart`/`Item`/`Details`, `Settings` and `Payment`.
|
||
**Splits:** Stripe subscriptions (`Golem15.PaymentGatewaySubscriptions`, 5.5k LOC, used by Horoskopia and four other projects) and any further Stripe add-ons go into decimal phases 18.1, 18.2 and so on if the Phase 18 discussion decides they do not fit.
|
||
**Requirements**: TBD
|
||
**Success Criteria** (what must be TRUE):
|
||
|
||
1. Payment (Created, Sent, Accepted, Rejected, Canceled, Refunded) and Order (12 states, all refund paths) state machines port with the PHP transitions, and use exact decimal money.
|
||
2. Payment operators register at build time through a Go interface that succeeds `core/Operator` and the `paymentgateway.booted` config push. Cash, manual transfer and Stripe operators are implemented. The `_paymentgateway/{operator}/{mode?}/{result?}` notification route keeps its PHP behavior.
|
||
3. The Order and Payment events, their mail templates, the admin screens (orders, payments, settings) and the scheduled cancel/finish/update commands are ported. Plugin contracts (tables, event names, config keys) stay non-breaking.
|
||
4. A host test proves the order-create, Stripe-pay and order-paid flow that Horoskopia relies on.
|
||
5. The new code has unit tests, delivered in the phase's last plan.
|
||
|
||
**Plans:** 0 plans
|
||
|
||
Plans:
|
||
- [ ] TBD (run $gsd-plan-phase 18 to break down)
|
||
|
||
### Phase 18.1: Payment Gateway Subscriptions (INSERTED)
|
||
|
||
**Goal:** The generic core of `Golem15.PaymentGatewaySubscriptions` and pgstripe's subscription gateway and webhook run on SummerCMS. Horoskopia's subscription flow (plans, checkout, Stripe webhook, `golem15.pgsub.*` events, cancel and portal) works as it does on PHP.
|
||
**Mode:** mvp
|
||
**Depends on:** Phase 18
|
||
**Repos:** `sm-paymentgatewaysubscriptions-plugin` (new, under `git.golem15.com/golem15/`); a subpackage of `sm-pgstripe-plugin`; `vue-paymentgateway-layer`; `summercms.go` only if a framework feature is missing
|
||
**Port sources:** `Golem15.PaymentGatewaySubscriptions` at `ac3d73c` plus `5fb9dac`, and pgstripe `8b47457`, at `/media/nvme/dev/golem15/horoskopia.eu/plugins/golem15/{paymentgatewaysubscriptions,pgstripe}`. Quotify's lead fees, featured placements and analytics are not ported here.
|
||
**Requirements**: TBD
|
||
**Success Criteria** (what must be TRUE):
|
||
|
||
1. Subscription and SubscriptionPlan port on a squashed, idempotent v2.4.0 baseline that adopts existing PHP databases, with polymorphic subscribers registered at build time and a built-in `user` subscribable.
|
||
2. The `SubscriptionGateway` contract (including `syncPlanToGateway`) has a Stripe implementation. `POST /webhook/stripe/subscriptions` and its legacy alias verify the event, enqueue a job and process it idempotently, and fire the six `golem15.pgsub.*` events.
|
||
3. The Subscriptions and SubscriptionPlans admin screens, a Go service API, a headless customer API and subscription components in `vue-paymentgateway-layer` are delivered.
|
||
4. A host test proves checkout, activation, `invoice.paid`, `past_due` with its grace period, cancel and duplicate-event idempotency against a Stripe fake.
|
||
5. The new code has unit tests, delivered in the phase's last plan.
|
||
|
||
**Plans:** 0 plans
|
||
|
||
Plans:
|
||
- [ ] TBD (run /msd-plan-phase 18.1 to break down)
|
||
|
||
### Phase 19: Chat and ChatChannels
|
||
|
||
**Goal:** `Golem15.Chat` and `Golem15.ChatChannels` run on SummerCMS as shared plugins, with realtime delivery through `lighthouse`, ready for the Wavepath port.
|
||
**Mode:** mvp
|
||
**Depends on:** Phase 11
|
||
**Repos:** `sm-chat-plugin` and `sm-chatchannels-plugin` (new, under `git.golem15.com/golem15/`); `summercms.go` only if `lighthouse` is missing a feature
|
||
**Port sources:** `/media/nvme/dev/jz/wavepath.org/plugins/golem15/{chat,chatchannels}`. Port the Wavepath copy (with `root_id` threading and `thread_summaries`); decided 2026-10-11, see `.planning/notes/wavepath-framework-asks-division.md`.
|
||
**Requirements**: TBD
|
||
**Success Criteria** (what must be TRUE):
|
||
|
||
1. The chat schema, models, adapters (context-agnostic groups), jobs, commands and routes port 1:1. ChatChannels plugs into chat through the same adapter contract.
|
||
2. Realtime broadcasts and channel authorization go through `lighthouse` (publisher, `Authorizer` per namespace). No WebSockets plugin is ported.
|
||
3. Public API payloads match PHP for the routes Wavepath's clients call.
|
||
4. The new code has unit tests, delivered in the phase's last plan.
|
||
|
||
**Plans:** 0 plans
|
||
|
||
Plans:
|
||
- [ ] TBD (run $gsd-plan-phase 19 to break down)
|
||
|
||
### Phase 19.1: Chat satellites
|
||
|
||
**Goal:** The remaining shared plugins that Wavepath's app plugin requires run on SummerCMS: chatbots, chatvideo, forum, herald and userfriends.
|
||
**Mode:** mvp
|
||
**Depends on:** Phase 19
|
||
**Repos:** one `sm-*-plugin` repo each, under `git.golem15.com/golem15/`
|
||
**Port sources:** `/media/nvme/dev/jz/wavepath.org/plugins/golem15/{chatbots,chatvideo,forum,herald,userfriends}`
|
||
**Requirements**: TBD
|
||
**Success Criteria** (what must be TRUE):
|
||
|
||
1. Each plugin ports its schema, models, routes and events 1:1 against its PHP original.
|
||
2. Herald (Web Push) has a decision note naming its VAPID/Web Push dependency before code lands. Chatbots is wired to `sm-golem-plugin` where the PHP original calls Golem.
|
||
3. Userfriends is checked against fonoteka first, so there is one shared copy.
|
||
4. The new code has unit tests, delivered in the phase's last plan.
|
||
|
||
**Plans:** 0 plans
|
||
|
||
Plans:
|
||
- [ ] TBD (run $gsd-plan-phase 19.1 to break down)
|
||
|
||
### Phase 19.2: Chat polish against WavePath clients (INSERTED)
|
||
|
||
**Goal:** The ported chat and chat satellites carry WavePath's chat traffic exactly as PHP does. The chat routes that WavePath's clients call are recorded with `tide`, replayed against the Go plugins and fixed until green, so that chat, the core feature of WavePath, is not the risk in the WavePath migration.
|
||
**Mode:** mvp
|
||
**Depends on:** Phase 19, Phase 19.1
|
||
**Repos:** `sm-chat-plugin`, `sm-chatchannels-plugin` and the satellite repos; `summercms.go` only for `lighthouse` or `tide` gaps
|
||
**Requirements**: TBD
|
||
**Success Criteria** (what must be TRUE):
|
||
|
||
1. A `tide` corpus of the chat, channel, thread, video-room and push routes, recorded from the PHP backend through `vue-wavepath-app`, `wavepath-mcp` and `hubot-wavepath`, replays green against the Go plugins.
|
||
2. Realtime events on the `chat`, `video` and `presence:` namespaces match the PHP broadcasts recorded by the `tide` Centrifugo recorder.
|
||
3. Behaviour stays neutral to the other consumers of these plugins (QuestStream, Horoskopia, Portal). Framework READMEs and docs still name no application.
|
||
4. The new code has unit tests, delivered in the phase's last plan.
|
||
|
||
**Plans:** 0 plans
|
||
|
||
Plans:
|
||
- [ ] TBD (run /msd-plan-phase 19.2 to break down)
|
||
|
||
### Phase 19.3: Framework transport and infrastructure for WavePath (INSERTED)
|
||
|
||
**Goal:** The framework has the transport and infrastructure pieces a multi-client, multi-instance application needs: JWT cookie refresh across subdomains, credentialed CORS, a cache module with a Redis-backed rate-limit store, an S3 blob option, an OpenAPI page and Turnstile verification.
|
||
**Mode:** mvp
|
||
**Depends on:** Phase 11
|
||
**Repos:** `summercms.go`
|
||
**Requirements**: TBD
|
||
**Success Criteria** (what must be TRUE):
|
||
|
||
1. `bouncer` refresh issues a new httpOnly `token` cookie whose `Domain`, `SameSite` and `Secure` attributes are configurable to match PHP, and blacklists the old JTI. The 60-minute TTL and 14-day refresh window are config keys, and credentialed CORS for a configured SPA origin is available as middleware.
|
||
2. A cache module ships with memory and Redis drivers, and `surf` gains a Redis-backed `Store`, so rate limits hold across instances. The Redis client dependency has a decision note before code lands.
|
||
3. An S3 blob driver can be selected through framework config, so the application does not import it. `/api-docs` and `/api-docs.json` can be served. Turnstile verification middleware is available.
|
||
4. The touched modules' READMEs and `docs/` pages are updated. The new code has unit tests, delivered in the phase's last plan.
|
||
|
||
**Plans:** 0 plans
|
||
|
||
Plans:
|
||
- [ ] TBD (run /msd-plan-phase 19.3 to break down)
|
||
|
||
### Phase 19.4: OIDC provider on wristband (INSERTED)
|
||
|
||
**Goal:** `wristband` is a full OpenID Connect provider that existing relying parties can switch to without noticing: discovery, JWKS with asymmetric signing and key rotation, `id_token`, userinfo, resumable authorization with account switching, a consent store and pairwise subjects. The existing issuer, signing keys and client secrets carry over.
|
||
**Mode:** mvp
|
||
**Depends on:** Phase 8, Phase 19.3
|
||
**Repos:** `summercms.go`
|
||
**Requirements**: TBD
|
||
**Success Criteria** (what must be TRUE):
|
||
|
||
1. A decision note settles whether the full OIDC provider extends the hand-written `wristband` server or puts zitadel/oidc v3 behind it, before code lands.
|
||
2. `/.well-known/openid-configuration`, the JWKS endpoint, userinfo and `id_token` in the token response work with RS256 or ES256 keys. Rotation publishes the old and the new key together.
|
||
3. The authorization flow supports resuming a request, switching account, the consent list and revocation, and pairwise and public subjects. A scheduled prune job removes expired codes, tokens and requests.
|
||
4. An existing issuer, its signing keys and client secrets can be imported so that relying parties keep working. The phase gets a security review.
|
||
5. The `wristband` README and `docs/` pages are updated. The new code has unit tests, delivered in the phase's last plan.
|
||
|
||
**Plans:** 0 plans
|
||
|
||
Plans:
|
||
- [ ] TBD (run /msd-plan-phase 19.4 to break down)
|
||
|
||
### Phase 19.5: Social login in sm-user-plugin (INSERTED)
|
||
|
||
**Goal:** `sm-user-plugin` signs users in with Google and Facebook through `/oauth/{provider}` and its callback, with a provider registry open to more providers, the SPA one-time-code handoff and the pending-registration branch, matching the PHP user plugin's contract.
|
||
**Mode:** mvp
|
||
**Depends on:** Phase 19.3
|
||
**Repos:** `sm-user-plugin`; `summercms.go` only if a provider client belongs in the framework
|
||
**Requirements**: TBD
|
||
**Success Criteria** (what must be TRUE):
|
||
|
||
1. `/oauth/google` and `/oauth/facebook` and their callbacks keep the PHP paths, because desktop shells hard-code them.
|
||
2. SPA mode (`?mode=spa&frontend_callback=…`) redirects with a one-time code that `oauth-complete` exchanges. A new social user who still needs profile completion goes through `oauth-register-complete`.
|
||
3. Identities are linked on login and listed and unlinked through the existing identity routes. Both applications that consume the plugin still build and pass their tests.
|
||
4. The new code has unit tests, delivered in the phase's last plan.
|
||
|
||
**Plans:** 0 plans
|
||
|
||
Plans:
|
||
- [ ] TBD (run /msd-plan-phase 19.5 to break down)
|
||
|
||
### Phase 19.6: Two-factor auth and device flow in sm-user-plugin (INSERTED)
|
||
|
||
**Goal:** `sm-user-plugin` has the PHP user plugin's second factors and device login: TOTP, WebAuthn, email codes, recovery codes and trusted devices, a challenge step during login, and QR/code device authorization.
|
||
**Mode:** mvp
|
||
**Depends on:** Phase 19.3
|
||
**Repos:** `sm-user-plugin`
|
||
**Requirements**: TBD
|
||
**Success Criteria** (what must be TRUE):
|
||
|
||
1. TOTP setup, confirm and disable; WebAuthn registration and login challenges; email codes; recovery codes; and trusted devices all work against the PHP contract. The WebAuthn dependency has a decision note before code lands.
|
||
2. Login with a second factor enabled returns the unauthenticated challenge step, rate-limited by the `2fa-verify` bucket.
|
||
3. Device authorization (`devices/initiate`, `devices/status/{token}`, `devices/authorize`, and the device list and revoke) works on the `DeviceAuthSession` model.
|
||
4. The models TwoFactorMethod, TwoFactorChallenge, TwoFactorRecoveryCode, TrustedDevice, WebAuthnCredential and DeviceAuthSession and their migrations are ported. The phase gets a security review. The new code has unit tests, delivered in the phase's last plan.
|
||
|
||
**Plans:** 0 plans
|
||
|
||
Plans:
|
||
- [ ] TBD (run /msd-plan-phase 19.6 to break down)
|
||
|
||
### Phase 20: Płytarium cutover
|
||
|
||
**Goal**: The parity harness is green on every manifest route and `vue-fonoteka-app` and `fonoteka-mcp` run unchanged against the Go backend in daily use in production — the Płytarium definition of done.
|
||
**Mode:** mvp
|
||
**Depends on**: Phase 2, Phase 8, Phase 9, Phase 10, Phase 12, Phase 12.1, Phase 13, Phase 14, Phase 14.1, Phase 14.2, Phase 15, Phase 16, Phases 19 to 19.6 (decided 2026-10-11: the 19.x WavePath readiness work lands before the cutover)
|
||
**Repos:** fonoteka.go, summercms.go
|
||
**Requirements**: API-09, QA-05
|
||
**Success Criteria** (what must be TRUE):
|
||
|
||
1. All manifest 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 every manifest route (zero `pending`).
|
||
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 → 14.1 → 14.2 → 14.2.1 → 14.3 → 15 → 16 → 17 → 18 → 18.1 → 19 → 19.1 → 19.2 → 19.3 → 19.4 → 19.5 → 19.6 → 20
|
||
(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 | Complete | 2026-10-01 |
|
||
| 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 | 3/3 | In Progress| |
|
||
| 12. Płytarium API — Collections and Albums | 5/5 | Complete | 2026-10-02 |
|
||
| 13. Płytarium API — wishlist, notifications, CSV, credentials, public routes | 6/6 | In Progress| |
|
||
| 14. Domain jobs and external integrations | 6/6 | In Progress| |
|
||
| 14.1. OAuth identities and fonoteka me routes (INSERTED) | 2/2 | Complete | 2026-10-05 |
|
||
| 14.2. Local Fonoteka frontend on local SummerCMS backend (INSERTED) | 4/4 | Complete | 2026-10-12 |
|
||
| 14.3. One-command app scaffolding (INSERTED) | 4/4 | In Progress| |
|
||
| 15. Journal plugin | 4/4 | Complete | 2026-10-06 |
|
||
| 16. grzybyfunkcjonalne.pl on reusable blog views | 5/5 | In Progress| |
|
||
| 17. Blog companions | 5/6 | In Progress| |
|
||
| 18. Payment Gateway | 0/TBD | Not started | - |
|
||
| 18.1. Payment Gateway Subscriptions | 0/TBD | Not started | - |
|
||
| 19. Chat and ChatChannels | 0/TBD | Not started | - |
|
||
| 19.1. Chat satellites | 0/TBD | Not started | - |
|
||
| 19.2. Chat polish against WavePath clients | 0/TBD | Not started | - |
|
||
| 19.3. Framework transport and infrastructure for WavePath | 0/TBD | Not started | - |
|
||
| 19.4. OIDC provider on wristband | 0/TBD | Not started | - |
|
||
| 19.5. Social login in sm-user-plugin | 0/TBD | Not started | - |
|
||
| 19.6. Two-factor auth and device flow in sm-user-plugin | 0/TBD | Not started | - |
|
||
| 20. Płytarium cutover | 0/TBD | Not started | - |
|
||
|
||
## Backlog
|
||
|
||
### Phase 999.1: Newsletter plugin and signup on summercms.io (BACKLOG)
|
||
|
||
**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
|
||
**Deferred:** 2026-10-02, formerly Phase 11.3. It is one more Golem15 plugin port; Journal is Phase 15 and Płytarium cutover is Phase 20. Before planning, re-check what 11.2 shipped since: the landing-page design has no signup slot, nginx on rome blocks `/backend` and non-GET methods, and rome has no mail relay configured.
|
||
**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 (promote with /gsd-review-backlog when ready)
|