docs(01): create phase plan
This commit is contained in:
@@ -7,6 +7,7 @@ v1 ports the Płytarium (fonoteka) headless PHP backend to a single Go binary wi
|
|||||||
## Phases
|
## Phases
|
||||||
|
|
||||||
**Phase Numbering:**
|
**Phase Numbering:**
|
||||||
|
|
||||||
- Integer phases (1, 2, 3): Planned milestone work
|
- Integer phases (1, 2, 3): Planned milestone work
|
||||||
- Decimal phases (2.1, 2.2): Urgent insertions (marked with INSERTED)
|
- Decimal phases (2.1, 2.2): Urgent insertions (marked with INSERTED)
|
||||||
|
|
||||||
@@ -31,209 +32,271 @@ Decimal phases appear between their surrounding integers in numeric order.
|
|||||||
## Phase Details
|
## Phase Details
|
||||||
|
|
||||||
### Phase 1: Framework kernel foundation
|
### 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.
|
**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
|
**Mode:** mvp
|
||||||
**Depends on**: Nothing (first phase)
|
**Depends on**: Nothing (first phase)
|
||||||
**Repos:** summercms.go
|
**Repos:** summercms.go
|
||||||
**Requirements**: KERN-01, KERN-02, KERN-03, KERN-04, KERN-05, KERN-06, KERN-07, KERN-08, KERN-09, CLI-01
|
**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):
|
**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.
|
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.
|
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.
|
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.
|
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.
|
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**: TBD
|
|
||||||
|
**Plans**: 4 plans
|
||||||
|
|
||||||
|
Plans:
|
||||||
|
**Wave 1**
|
||||||
|
|
||||||
|
- [ ] 01-01-PLAN.md — Build and boot the separate hello app through the shared command kernel
|
||||||
|
|
||||||
|
**Wave 2** *(blocked on Wave 1 completion)*
|
||||||
|
|
||||||
|
- [ ] 01-02-PLAN.md — Add layered config, optional plugin composition, services and typed events
|
||||||
|
|
||||||
|
**Wave 3** *(blocked on Wave 2 completion)*
|
||||||
|
|
||||||
|
- [ ] 01-03-PLAN.md — Add plugin scaffolding, rich output and the watch rebuild loop
|
||||||
|
|
||||||
|
**Wave 4** *(blocked on Wave 3 completion)*
|
||||||
|
|
||||||
|
- [ ] 01-04-PLAN.md — Complete unit, integration and race-enabled verification
|
||||||
|
|
||||||
### Phase 2: API parity harness bootstrap
|
### 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. This is a day-one workstream with no dependency on framework progress, since it only needs a running PHP backend to record against.
|
**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. This is a day-one workstream with no dependency on framework progress, since it only needs a running PHP backend to record against.
|
||||||
**Mode:** mvp
|
**Mode:** mvp
|
||||||
**Depends on**: Nothing (parallel with Phase 1)
|
**Depends on**: Nothing (parallel with Phase 1)
|
||||||
**Repos:** summercms.go, fonoteka.go
|
**Repos:** summercms.go, fonoteka.go
|
||||||
**Requirements**: QA-01, QA-02, QA-03
|
**Requirements**: QA-01, QA-02, QA-03
|
||||||
**Success Criteria** (what must be TRUE):
|
**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.
|
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.
|
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.
|
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.
|
4. `go vet` and `go test ./...` are green, and the harness's own integration tests run against testcontainers Postgres.
|
||||||
|
|
||||||
**Plans**: TBD
|
**Plans**: TBD
|
||||||
|
|
||||||
### Phase 3: First vertical slice — genres end to end
|
### 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.
|
**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
|
**Mode:** mvp
|
||||||
**Depends on**: Phase 1, Phase 2
|
**Depends on**: Phase 1, Phase 2
|
||||||
**Repos:** summercms.go, fonoteka.go
|
**Repos:** summercms.go, fonoteka.go
|
||||||
**Requirements**: DATA-01, DATA-02, HTTP-01, HTTP-02, QA-04
|
**Requirements**: DATA-01, DATA-02, HTTP-01, HTTP-02, QA-04
|
||||||
**Success Criteria** (what must be TRUE):
|
**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.
|
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).
|
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.
|
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.
|
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**: TBD
|
**Plans**: TBD
|
||||||
|
|
||||||
### Phase 4: CLI scaffolding, i18n and mail
|
### 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.
|
**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
|
**Mode:** mvp
|
||||||
**Depends on**: Phase 1
|
**Depends on**: Phase 1
|
||||||
**Repos:** summercms.go
|
**Repos:** summercms.go
|
||||||
**Requirements**: CLI-02, I18N-01, I18N-03
|
**Requirements**: CLI-02, I18N-01, I18N-03
|
||||||
**Success Criteria** (what must be TRUE):
|
**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`.
|
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.
|
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.
|
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**: TBD
|
**Plans**: TBD
|
||||||
|
|
||||||
### Phase 5: Data layer full fidelity
|
### Phase 5: Data layer full fidelity
|
||||||
|
|
||||||
**Goal**: All 25 Płytarium models and 27 migrations 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.
|
**Goal**: All 25 Płytarium models and 27 migrations 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
|
**Mode:** mvp
|
||||||
**Depends on**: Phase 1, Phase 3
|
**Depends on**: Phase 1, Phase 3
|
||||||
**Repos:** summercms.go, fonoteka.go
|
**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
|
**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):
|
**Success Criteria** (what must be TRUE):
|
||||||
|
|
||||||
1. All 25 models exist with matching table names, columns, indexes and defaults; all 27 migrations run up and down individually, and `summer migrate:rollback --plugin=fonoteka` rolls back only that plugin's last migration.
|
1. All 25 models exist with matching table names, columns, indexes and defaults; all 27 migrations run up and down individually, and `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.
|
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. Every write endpoint uses a request DTO that enforces its model's fillable allow-list (a fuzz test posting unknown fields asserts they are rejected or ignored, never persisted), and serialization honors the hidden deny-list with an explicit per-call override.
|
3. Every write endpoint uses a request DTO that enforces its model's fillable allow-list (a fuzz test posting unknown fields asserts they are rejected or ignored, never persisted), and 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.
|
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.
|
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**: TBD
|
**Plans**: TBD
|
||||||
|
|
||||||
### Phase 6: HTTP routing, auth groups and rate limiting
|
### 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.
|
**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
|
**Mode:** mvp
|
||||||
**Depends on**: Phase 3, Phase 5
|
**Depends on**: Phase 3, Phase 5
|
||||||
**Repos:** summercms.go, fonoteka.go
|
**Repos:** summercms.go, fonoteka.go
|
||||||
**Requirements**: HTTP-03, HTTP-04, HTTP-05, HTTP-06, HTTP-07, HTTP-08, HTTP-09
|
**Requirements**: HTTP-03, HTTP-04, HTTP-05, HTTP-06, HTTP-07, HTTP-08, HTTP-09
|
||||||
**Success Criteria** (what must be TRUE):
|
**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.
|
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 seven named rate-limit buckets are enforced with the documented keys and limits, including a route stacking two limiters.
|
2. All seven 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.
|
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).
|
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.
|
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**: TBD
|
**Plans**: TBD
|
||||||
|
|
||||||
### Phase 7: User plugin and authentication
|
### 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.
|
**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
|
**Mode:** mvp
|
||||||
**Depends on**: Phase 6
|
**Depends on**: Phase 6
|
||||||
**Repos:** summercms.go, fonoteka.go
|
**Repos:** summercms.go, fonoteka.go
|
||||||
**Requirements**: AUTH-01, AUTH-02, AUTH-03, AUTH-04, I18N-02
|
**Requirements**: AUTH-01, AUTH-02, AUTH-03, AUTH-04, I18N-02
|
||||||
**Success Criteria** (what must be TRUE):
|
**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.
|
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.
|
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.
|
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.
|
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**: TBD
|
**Plans**: TBD
|
||||||
|
|
||||||
### Phase 8: OAuth2.1 authorization server
|
### Phase 8: OAuth2.1 authorization server
|
||||||
|
|
||||||
**Goal**: An RFC 8414/6749/7591-compliant OAuth2.1 server on zitadel/oidc serves fonoteka-mcp and the ChatGPT connector unchanged, including exact `WWW-Authenticate` and protected-resource-metadata headers. Security-load-bearing — bearer tokens, PKCE and constant-time secret comparison all live here; apply the security-review agent.
|
**Goal**: An RFC 8414/6749/7591-compliant OAuth2.1 server on zitadel/oidc serves fonoteka-mcp and the ChatGPT connector unchanged, including exact `WWW-Authenticate` and protected-resource-metadata headers. Security-load-bearing — bearer tokens, PKCE and constant-time secret comparison all live here; apply the security-review agent.
|
||||||
**Mode:** mvp
|
**Mode:** mvp
|
||||||
**Depends on**: Phase 6, Phase 7
|
**Depends on**: Phase 6, Phase 7
|
||||||
**Repos:** summercms.go, fonoteka.go
|
**Repos:** summercms.go, fonoteka.go
|
||||||
**Requirements**: AUTH-05, AUTH-06, AUTH-07
|
**Requirements**: AUTH-05, AUTH-06, AUTH-07
|
||||||
**Success Criteria** (what must be TRUE):
|
**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`).
|
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.
|
2. RFC 7591 dynamic client registration and RFC 8707 resource parameter tolerance both work against a real client registration call.
|
||||||
3. A 401 on a protected route carries the exact `WWW-Authenticate` and protected-resource-metadata header contract, verified against fonoteka-mcp's actual discovery flow, not just a Go unit test.
|
3. A 401 on a protected route carries the exact `WWW-Authenticate` and protected-resource-metadata header contract, verified against fonoteka-mcp's 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`.
|
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**: TBD
|
**Plans**: TBD
|
||||||
**Research flag:** yes
|
**Research flag:** yes
|
||||||
|
|
||||||
### Phase 9: Backend admin authentication and schema pipeline
|
### 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).
|
**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
|
**Mode:** mvp
|
||||||
**Depends on**: Phase 5, Phase 6
|
**Depends on**: Phase 5, Phase 6
|
||||||
**Repos:** summercms.go, fonoteka.go
|
**Repos:** summercms.go, fonoteka.go
|
||||||
**Requirements**: AUTH-08, ADMIN-01, ADMIN-02, ADMIN-03, ADMIN-04, ADMIN-05
|
**Requirements**: AUTH-08, ADMIN-01, ADMIN-02, ADMIN-03, ADMIN-04, ADMIN-05
|
||||||
**Success Criteria** (what must be TRUE):
|
**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.
|
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.
|
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.
|
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.
|
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.
|
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**: TBD
|
**Plans**: TBD
|
||||||
**Research flag:** yes
|
**Research flag:** yes
|
||||||
|
|
||||||
### Phase 10: Admin Vue SPA
|
### 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.
|
**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
|
**Mode:** mvp
|
||||||
**Depends on**: Phase 9
|
**Depends on**: Phase 9
|
||||||
**Repos:** summercms.go, fonoteka.go
|
**Repos:** summercms.go, fonoteka.go
|
||||||
**Requirements**: ADMIN-06
|
**Requirements**: ADMIN-06
|
||||||
**Success Criteria** (what must be TRUE):
|
**Success Criteria** (what must be TRUE):
|
||||||
|
|
||||||
1. An admin logs in through the SPA and sees only the navigation items their permissions allow.
|
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.
|
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.
|
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.
|
4. API calls in the SPA use TypeScript types generated from the OpenAPI document, with no hand-maintained duplicate type.
|
||||||
|
|
||||||
**Plans**: TBD
|
**Plans**: TBD
|
||||||
**UI hint**: yes
|
**UI hint**: yes
|
||||||
|
|
||||||
### Phase 11: Jobs, realtime and search infrastructure
|
### 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.
|
**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
|
**Mode:** mvp
|
||||||
**Depends on**: Phase 5, Phase 7
|
**Depends on**: Phase 5, Phase 7
|
||||||
**Repos:** summercms.go, fonoteka.go
|
**Repos:** summercms.go, fonoteka.go
|
||||||
**Requirements**: JOBS-01, CLI-04, CLI-06, RT-01, RT-02, RT-03, SRCH-01
|
**Requirements**: JOBS-01, CLI-04, CLI-06, RT-01, RT-02, RT-03, SRCH-01
|
||||||
**Success Criteria** (what must be TRUE):
|
**Success Criteria** (what must be TRUE):
|
||||||
|
|
||||||
1. River runs on the shared `*sql.DB` (`riverdatabasesql`, transactional enqueue) with a separate `riverpgxv5` client for LISTEN/NOTIFY dispatch, 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.
|
1. River runs on the shared `*sql.DB` (`riverdatabasesql`, transactional enqueue) with a separate `riverpgxv5` client for LISTEN/NOTIFY dispatch, 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`.
|
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`.
|
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.
|
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.
|
5. Typesense sync is scoped by `collection_id` behind a settings kill-switch and degrades gracefully without DB/config.
|
||||||
|
|
||||||
**Plans**: TBD
|
**Plans**: TBD
|
||||||
**Research flag:** yes
|
**Research flag:** yes
|
||||||
|
|
||||||
### Phase 12: Płytarium API — Collections and Albums
|
### Phase 12: Płytarium API — Collections and Albums
|
||||||
|
|
||||||
**Goal**: Collections and Albums endpoints are ported with byte-compatible request/response shapes, including active-context switching, editor invitations, ratings, reservations, cover handling and search.
|
**Goal**: Collections and Albums endpoints are ported with byte-compatible request/response shapes, including active-context switching, editor invitations, ratings, reservations, cover handling and search.
|
||||||
**Mode:** mvp
|
**Mode:** mvp
|
||||||
**Depends on**: Phase 5, Phase 6, Phase 7, Phase 11
|
**Depends on**: Phase 5, Phase 6, Phase 7, Phase 11
|
||||||
**Repos:** fonoteka.go
|
**Repos:** fonoteka.go
|
||||||
**Requirements**: API-01, API-02
|
**Requirements**: API-01, API-02
|
||||||
**Success Criteria** (what must be TRUE):
|
**Success Criteria** (what must be TRUE):
|
||||||
|
|
||||||
1. Collections CRUD, the `me/context` active-collection switch (returning an opaque channel name), editor invitation/acceptance, share-link regeneration and public token views all pass the parity diff.
|
1. Collections CRUD, the `me/context` active-collection switch (returning an opaque channel name), editor invitation/acceptance, share-link regeneration and public token views all pass the parity diff.
|
||||||
2. Albums CRUD, ratings, reservations, photo upload, manual cover URL and Discogs cover price all pass the parity diff.
|
2. Albums CRUD, ratings, reservations, photo upload, manual cover URL and Discogs cover price all pass the parity diff.
|
||||||
3. Album search treats Typesense results as a pre-filter re-gated in SQL, verified by a security test that a stale/mis-scoped search document cannot leak an unauthorized result.
|
3. Album search treats Typesense results as a pre-filter re-gated in SQL, verified by a security test that a stale/mis-scoped search document cannot leak an unauthorized result.
|
||||||
4. Artists/genres/styles lookup endpoints used by the Albums UI pass the parity diff.
|
4. Artists/genres/styles lookup endpoints used by the Albums UI pass the parity diff.
|
||||||
|
|
||||||
**Plans**: TBD
|
**Plans**: TBD
|
||||||
|
|
||||||
### Phase 13: Płytarium API — wishlist, notifications, CSV, credentials, public routes
|
### 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.
|
**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
|
**Mode:** mvp
|
||||||
**Depends on**: Phase 11, Phase 12
|
**Depends on**: Phase 11, Phase 12
|
||||||
**Repos:** fonoteka.go
|
**Repos:** fonoteka.go
|
||||||
**Requirements**: API-03, API-04, API-05, API-06, API-07
|
**Requirements**: API-03, API-04, API-05, API-06, API-07
|
||||||
**Success Criteria** (what must be TRUE):
|
**Success Criteria** (what must be TRUE):
|
||||||
|
|
||||||
1. Wishlist items, subscriptions, `public-wishlist/{token}` views and purchase/digest triggers pass the parity diff.
|
1. Wishlist items, subscriptions, `public-wishlist/{token}` views and purchase/digest triggers pass the parity diff.
|
||||||
2. Notifications list/mark-read/prune endpoints pass the parity diff (the realtime token endpoint itself is owned by the websockets plugin, ported in Phase 11).
|
2. Notifications list/mark-read/prune endpoints pass the parity diff (the realtime token endpoint itself is owned by the websockets plugin, ported in Phase 11).
|
||||||
3. CSV import runs as a multi-step session (store, show/poll, mapping patch, per-row edit, commit, cancel) and CSV export works on both authenticated groups, all passing the parity diff.
|
3. CSV import runs as a multi-step session (store, show/poll, mapping patch, per-row edit, commit, cancel) and CSV export works on both authenticated groups, all passing the parity diff.
|
||||||
4. Per-user and per-org Discogs/AI credentials CRUD store encrypted values, honor the org-lock flag, and resolve env-to-org-to-user correctly.
|
4. Per-user and per-org Discogs/AI credentials CRUD store encrypted values, honor the org-lock flag, and resolve env-to-org-to-user correctly.
|
||||||
5. Onboarding, public and invitation-inspection routes are reachable without auth and enforce their own public rate-limit buckets.
|
5. Onboarding, public and invitation-inspection routes are reachable without auth and enforce their own public rate-limit buckets.
|
||||||
|
|
||||||
**Plans**: TBD
|
**Plans**: TBD
|
||||||
|
|
||||||
### Phase 14: Domain jobs and external integrations
|
### 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.
|
**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
|
**Mode:** mvp
|
||||||
**Depends on**: Phase 11, Phase 13
|
**Depends on**: Phase 11, Phase 13
|
||||||
**Repos:** fonoteka.go (plus summercms.go only if a framework helper is needed)
|
**Repos:** fonoteka.go (plus summercms.go only if a framework helper is needed)
|
||||||
**Requirements**: JOBS-02, JOBS-03, SRCH-02, INTG-01, INTG-02, API-08, CLI-05
|
**Requirements**: JOBS-02, JOBS-03, SRCH-02, INTG-01, INTG-02, API-08, CLI-05
|
||||||
**Success Criteria** (what must be TRUE):
|
**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.
|
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.
|
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.
|
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.
|
4. The Discogs client enforces its proactive rate threshold, bounded wait budget, retry-after fallback and host-locked cover fetch.
|
||||||
5. AI cover recognition works through both Anthropic and OpenAI-compatible adapters with per-credential model/base-URL overrides.
|
5. AI cover recognition works through both Anthropic and OpenAI-compatible adapters with per-credential model/base-URL overrides.
|
||||||
6. Feedback submissions and sitemap output work; the ported `oauth-client`/`prune-notifications`/`reindex` commands all run correctly.
|
6. Feedback submissions and sitemap output work; the ported `oauth-client`/`prune-notifications`/`reindex` commands all run correctly.
|
||||||
|
|
||||||
**Plans**: TBD
|
**Plans**: TBD
|
||||||
|
|
||||||
### Phase 15: Cutover
|
### Phase 15: Cutover
|
||||||
|
|
||||||
**Goal**: The parity harness is green on all 154 routes and `vue-fonoteka-app` and `fonoteka-mcp` run unchanged against the Go backend in daily use — the project's definition of done.
|
**Goal**: The parity harness is green on all 154 routes and `vue-fonoteka-app` and `fonoteka-mcp` run unchanged against the Go backend in daily use — the project's definition of done.
|
||||||
**Mode:** mvp
|
**Mode:** mvp
|
||||||
**Depends on**: Phase 2, Phase 8, Phase 9, Phase 10, Phase 12, Phase 13, Phase 14
|
**Depends on**: Phase 2, Phase 8, Phase 9, Phase 10, Phase 12, Phase 13, Phase 14
|
||||||
**Repos:** fonoteka.go, summercms.go
|
**Repos:** fonoteka.go, summercms.go
|
||||||
**Requirements**: API-09, QA-05
|
**Requirements**: API-09, QA-05
|
||||||
**Success Criteria** (what must be TRUE):
|
**Success Criteria** (what must be TRUE):
|
||||||
|
|
||||||
1. All 154 routes are registered on the correct auth groups with identical paths, methods and status codes.
|
1. All 154 routes are registered on the correct auth groups with identical paths, methods and status codes.
|
||||||
2. The parity harness runs green across recorded fixtures for all 154 routes.
|
2. The parity harness runs green across recorded fixtures for all 154 routes.
|
||||||
3. `vue-fonoteka-app` runs unchanged against the Go backend for a full manual session (browse, edit, upload, invite, OAuth-connect an MCP client).
|
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.
|
4. `fonoteka-mcp` completes its install/auth flow and a representative set of tool calls unchanged against the Go backend.
|
||||||
|
|
||||||
**Plans**: TBD
|
**Plans**: TBD
|
||||||
|
|
||||||
## Progress
|
## Progress
|
||||||
@@ -244,7 +307,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 →
|
|||||||
|
|
||||||
| Phase | Plans Complete | Status | Completed |
|
| Phase | Plans Complete | Status | Completed |
|
||||||
|-------|----------------|--------|-----------|
|
|-------|----------------|--------|-----------|
|
||||||
| 1. Framework kernel foundation | 0/TBD | Not started | - |
|
| 1. Framework kernel foundation | 0/4 | Planned | - |
|
||||||
| 2. API parity harness bootstrap | 0/TBD | Not started | - |
|
| 2. API parity harness bootstrap | 0/TBD | Not started | - |
|
||||||
| 3. First vertical slice — genres end to end | 0/TBD | Not started | - |
|
| 3. First vertical slice — genres end to end | 0/TBD | Not started | - |
|
||||||
| 4. CLI scaffolding, i18n and mail | 0/TBD | Not started | - |
|
| 4. CLI scaffolding, i18n and mail | 0/TBD | Not started | - |
|
||||||
|
|||||||
@@ -2,14 +2,14 @@
|
|||||||
gsd_state_version: 1.0
|
gsd_state_version: 1.0
|
||||||
milestone: v1.0
|
milestone: v1.0
|
||||||
milestone_name: milestone
|
milestone_name: milestone
|
||||||
status: planning
|
status: executing
|
||||||
stopped_at: Phase 1 context gathered
|
stopped_at: Phase 1 plans ready
|
||||||
last_updated: "2026-09-16T10:03:21.568Z"
|
last_updated: "2026-09-16T10:29:26.505Z"
|
||||||
last_activity: 2026-09-16 — ROADMAP.md and STATE.md created from PROJECT.md, REQUIREMENTS.md and research/{SUMMARY,ARCHITECTURE,PITFALLS}.md
|
last_activity: 2026-09-16 -- Phase 1 planning complete
|
||||||
progress:
|
progress:
|
||||||
total_phases: 15
|
total_phases: 15
|
||||||
completed_phases: 0
|
completed_phases: 0
|
||||||
total_plans: 0
|
total_plans: 4
|
||||||
completed_plans: 0
|
completed_plans: 0
|
||||||
percent: 0
|
percent: 0
|
||||||
---
|
---
|
||||||
@@ -26,9 +26,9 @@ See: .planning/PROJECT.md (updated 2026-09-16)
|
|||||||
## Current Position
|
## Current Position
|
||||||
|
|
||||||
Phase: 1 of 15 (Framework kernel foundation)
|
Phase: 1 of 15 (Framework kernel foundation)
|
||||||
Plan: TBD (roadmap just created, plans not yet written)
|
Plan: 0 of 4 (Phase 1 plans ready)
|
||||||
Status: Ready to plan
|
Status: Ready to execute
|
||||||
Last activity: 2026-09-16 — ROADMAP.md and STATE.md created from PROJECT.md, REQUIREMENTS.md and research/{SUMMARY,ARCHITECTURE,PITFALLS}.md
|
Last activity: 2026-09-16 -- Phase 1 planning complete
|
||||||
|
|
||||||
Progress: [░░░░░░░░░░] 0%
|
Progress: [░░░░░░░░░░] 0%
|
||||||
|
|
||||||
|
|||||||
145
.planning/phases/01-framework-kernel-foundation/01-01-PLAN.md
Normal file
145
.planning/phases/01-framework-kernel-foundation/01-01-PLAN.md
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
---
|
||||||
|
phase: 01-framework-kernel-foundation
|
||||||
|
plan: 01
|
||||||
|
type: execute
|
||||||
|
wave: 1
|
||||||
|
depends_on: []
|
||||||
|
files_modified:
|
||||||
|
- go.mod
|
||||||
|
- go.sum
|
||||||
|
- go.work
|
||||||
|
- party/registry.go
|
||||||
|
- backpack/app.go
|
||||||
|
- pact/capabilities.go
|
||||||
|
- compass/config.go
|
||||||
|
- bonfire/command.go
|
||||||
|
- bonfire/root.go
|
||||||
|
- internal/build/build.go
|
||||||
|
- cmd/summer/main.go
|
||||||
|
- examples/hello/summer.yaml
|
||||||
|
- examples/hello/go.mod
|
||||||
|
- examples/hello/main.go
|
||||||
|
- examples/hello/plugins.gen.go
|
||||||
|
- examples/hello/hello_test.go
|
||||||
|
- examples/hello/plugins/base/go.mod
|
||||||
|
- examples/hello/plugins/base/plugin.go
|
||||||
|
- examples/hello/plugins/greeter/go.mod
|
||||||
|
- examples/hello/plugins/greeter/plugin.go
|
||||||
|
- examples/hello/config/app.yaml
|
||||||
|
autonomous: true
|
||||||
|
requirements: [KERN-01, KERN-02, KERN-03, KERN-04, CLI-01]
|
||||||
|
must_haves:
|
||||||
|
truths:
|
||||||
|
- "D-01 D-02: An installed `summer` tool builds a separate hello app binary, and both entry points use the same `bonfire` root-command constructor."
|
||||||
|
- "D-03 D-04 D-05: The permanent hello app has separate plugin modules in root `go.work`, imports `git.golem15.com/golem15/summercms`, and uses a concrete Go 1.27 toolchain directive."
|
||||||
|
- "D-15 D-17: The hello app boots plugins and executes `greeter:hello` through a `bonfire.Command` adapter with injected Input and Output."
|
||||||
|
- "The generated import list preserves the explicit manifest order while the plugin registry orders lifecycle by `Requires()`."
|
||||||
|
artifacts:
|
||||||
|
- path: internal/build/build.go
|
||||||
|
provides: Manifest-driven code generation and app build
|
||||||
|
- path: party/registry.go
|
||||||
|
provides: Required plugin interface and activation
|
||||||
|
- path: bonfire/root.go
|
||||||
|
provides: Shared cobra command construction
|
||||||
|
- path: examples/hello/summer.yaml
|
||||||
|
provides: Ordered hello plugin manifest
|
||||||
|
key_links:
|
||||||
|
- from: cmd/summer/main.go
|
||||||
|
to: bonfire/root.go
|
||||||
|
via: Shared root command constructor
|
||||||
|
- from: examples/hello/main.go
|
||||||
|
to: party/registry.go
|
||||||
|
via: Generated app activation with ordered plugin IDs
|
||||||
|
- from: examples/hello/plugins.gen.go
|
||||||
|
to: examples/hello/plugins/greeter/plugin.go
|
||||||
|
via: Blank import triggers self-registration
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
As a framework developer, I can build a tiny app from two compiled plugins and run its namespaced hello command, so the first kernel API is proven by a real binary.
|
||||||
|
|
||||||
|
Purpose: Establish the thinnest executable path that future Phase 1 work can extend.
|
||||||
|
Output: Framework tool, shared runtime command kernel, plugin registry, minimal config, root workspace, and permanent `examples/hello` app.
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<execution_context>
|
||||||
|
@$HOME/.codex/get-shit-done/workflows/execute-plan.md
|
||||||
|
@$HOME/.codex/get-shit-done/templates/summary.md
|
||||||
|
</execution_context>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
@.planning/PROJECT.md
|
||||||
|
@.planning/ROADMAP.md
|
||||||
|
@.planning/REQUIREMENTS.md
|
||||||
|
@.planning/phases/01-framework-kernel-foundation/01-CONTEXT.md
|
||||||
|
@.planning/phases/01-framework-kernel-foundation/01-RESEARCH.md
|
||||||
|
@.planning/phases/01-framework-kernel-foundation/01-VALIDATION.md
|
||||||
|
@../modules/summer-compass/README.md
|
||||||
|
@../modules/summer-bonfire/PLAN.md
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto" tdd="true">
|
||||||
|
<name>Task 1: Make the hello app boot and dispatch a plugin command</name>
|
||||||
|
<files>go.mod, go.sum, go.work, party/registry.go, backpack/app.go, pact/capabilities.go, compass/config.go, bonfire/command.go, bonfire/root.go, examples/hello/go.mod, examples/hello/main.go, examples/hello/summer.yaml, examples/hello/plugins.gen.go, examples/hello/hello_test.go, examples/hello/plugins/base/go.mod, examples/hello/plugins/base/plugin.go, examples/hello/plugins/greeter/go.mod, examples/hello/plugins/greeter/plugin.go, examples/hello/config/app.yaml</files>
|
||||||
|
<read_first>go.mod, go.sum, go.work, .planning/phases/01-framework-kernel-foundation/01-CONTEXT.md, .planning/phases/01-framework-kernel-foundation/01-RESEARCH.md, .planning/research/ARCHITECTURE.md, ../modules/summer-bonfire/PLAN.md, party/registry.go, backpack/app.go, pact/capabilities.go, compass/config.go, bonfire/command.go, bonfire/root.go, examples/hello/go.mod, examples/hello/main.go, examples/hello/summer.yaml, examples/hello/plugins.gen.go, examples/hello/hello_test.go, examples/hello/plugins/base/go.mod, examples/hello/plugins/base/plugin.go, examples/hello/plugins/greeter/go.mod, examples/hello/plugins/greeter/plugin.go, examples/hello/config/app.yaml</read_first>
|
||||||
|
<behavior>The hello binary loads `app.name` from `config/app.yaml`, runs Register for both plugins before Boot, and executes `greeter:hello` through an injected Output writer.</behavior>
|
||||||
|
<action>Implement the smallest real boot path. Pin `toolchain go1.27.0` in root and generated/example modules and root `go.work` (D-05). Put the required `Plugin` interface (`ID`, `Requires`, `Register`, `Boot`) and `Register`/`Activate` in `party`, with `Register(*backpack.App) error` and `Boot(*backpack.App) error`; `backpack` must not import `party`. `Activate` must resolve IDs from the manifest order, topologically sort Requires, run every Register before any Boot, and expose the resulting plugins to command collection. Define `pact.HasCommands` returning `[]bonfire.Command` and `pact.HasConfig` returning `fs.FS` as optional type-asserted capabilities; reserve the remaining KERN-03 capability names in documentation for their first consumers. `bonfire.Command` has Name, Description, flag/argument definition and `Run(ctx, Input, Output) error`; its root factory accepts command values, not a party registry, to avoid an import cycle (D-02, D-17). Use `cobra` v1.10.2, `koanf/v2` v2.3.6 and `koanf/parsers/yaml` v1.1.1 with an injected output writer. Create two hello plugin modules with IDs `golem15.hello` and `golem15.greeter`; greeter Requires hello and provides `greeter:hello`. Create `summer.yaml` with `module`, `binary`, and an ordered `plugins` list of `{id, module}` entries; write initial `main.go` and `plugins.gen.go` from those two entries so the app already runs before codegen is automated in Task 2. Add root `go.work` entries for framework, app and both plugins. Keep compass at a working `config/app.yaml` section load and dot-path String lookup here; Plan 02 fills the full precedence contract. Write the smoke test before implementation inside this task, but commit only once both root and hello module tests are green.</action>
|
||||||
|
<verify><automated>go vet ./... && go test ./... && (cd examples/hello && go vet ./... && go test ./...)</automated></verify>
|
||||||
|
<acceptance_criteria>
|
||||||
|
- `party.Plugin` has ID, Requires, Register and Boot; `backpack` has no import of `party`.
|
||||||
|
- `bonfire.NewRoot` or equivalent takes a slice of `bonfire.Command` and is used by the hello main.
|
||||||
|
- `examples/hello` contains two independently versioned plugin modules and a working `greeter:hello` command.
|
||||||
|
- Root and hello `go vet ./...` and `go test ./...` exit 0.
|
||||||
|
</acceptance_criteria>
|
||||||
|
<done>The initial hello command executes through a real app activation path with no package import cycle.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 2: Generate and build the hello app from its ordered manifest</name>
|
||||||
|
<files>internal/build/build.go, cmd/summer/main.go, examples/hello/summer.yaml, examples/hello/main.go, examples/hello/plugins.gen.go, examples/hello/hello_test.go, go.sum</files>
|
||||||
|
<read_first>go.mod, go.sum, go.work, party/registry.go, bonfire/root.go, examples/hello/go.mod, examples/hello/main.go, examples/hello/summer.yaml, examples/hello/plugins.gen.go, examples/hello/hello_test.go, .planning/phases/01-framework-kernel-foundation/01-CONTEXT.md, .planning/phases/01-framework-kernel-foundation/01-RESEARCH.md, internal/build/build.go, cmd/summer/main.go</read_first>
|
||||||
|
<action>Implement `summer build` in the framework-owned `cmd/summer` (D-01, D-04). Parse `examples/hello/summer.yaml` as an explicit ordered `plugins` list of `{id, module}` entries, reject duplicate/invalid entries, generate app-root `main.go` and `plugins.gen.go` containing blank imports plus an ordered plugin-ID slice, and invoke `go build -o bin/hello .` with `exec.CommandContext` in the app directory. Keep generated bytes stable and rewrite only when changed. `main.go` calls the same `bonfire` root constructor as the tool, then activates the manifest IDs; never rely on Go package init order for lifecycle order. Tool `build` prints measured build duration. `go install ./cmd/summer` builds a reusable tool while the app binary remains a distinct artifact. Update the smoke test to run the built binary's `greeter:hello` command and assert its config value. Do not add HTTP, DB, auth or full scaffolding.</action>
|
||||||
|
<verify><automated>go vet ./... && go test ./... && (cd examples/hello && go vet ./... && go test ./... && go run ../../cmd/summer build && ./bin/hello greeter:hello)</automated></verify>
|
||||||
|
<acceptance_criteria>
|
||||||
|
- `summer build` creates `examples/hello/plugins.gen.go` with imports in `summer.yaml` order and creates an executable `examples/hello/bin/hello`.
|
||||||
|
- Two consecutive `summer build` calls produce byte-identical generated files.
|
||||||
|
- `./bin/hello greeter:hello` exits 0 and prints a value from `config/app.yaml`.
|
||||||
|
- The tool does not import an example plugin package or contain a hard-coded example module path.
|
||||||
|
</acceptance_criteria>
|
||||||
|
<done>A developer can build and run the independent hello app through the installed framework tool.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<threat_model>
|
||||||
|
## Trust Boundaries
|
||||||
|
|
||||||
|
| Boundary | Description |
|
||||||
|
|---|---|
|
||||||
|
| App manifest → generated Go and build process | A repository-controlled YAML file supplies module import paths and output name. |
|
||||||
|
|
||||||
|
## STRIDE Threat Register
|
||||||
|
|
||||||
|
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| T-01-01 | Tampering | `internal/build` manifest parser | mitigate | Validate module path syntax and duplicate IDs before generating code; render imports with Go string quoting. |
|
||||||
|
| T-01-02 | Elevation of privilege | `summer build` process launch | mitigate | Use `exec.CommandContext("go", ...)` with separate args and fixed app working directory; never invoke a shell string from YAML. |
|
||||||
|
</threat_model>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
- Run `go vet ./...` and `go test ./...` in the root and hello modules after each task commit.
|
||||||
|
- Run `summer build` twice; compare generated file hashes and execute `bin/hello greeter:hello`.
|
||||||
|
- Confirm the app binary imports the framework module but the framework tool does not import hello.
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
- A separate `summer` tool and generated hello binary build under Go 1.27.0.
|
||||||
|
- The hello plugin command runs from the app binary using shared `bonfire` wiring.
|
||||||
|
- Two plugins Register before either Boots, irrespective of manifest order.
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
Create `.planning/phases/01-framework-kernel-foundation/01-01-SUMMARY.md` after completion.
|
||||||
|
</output>
|
||||||
168
.planning/phases/01-framework-kernel-foundation/01-02-PLAN.md
Normal file
168
.planning/phases/01-framework-kernel-foundation/01-02-PLAN.md
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
---
|
||||||
|
phase: 01-framework-kernel-foundation
|
||||||
|
plan: 02
|
||||||
|
type: execute
|
||||||
|
wave: 2
|
||||||
|
depends_on: ["01-01"]
|
||||||
|
files_modified:
|
||||||
|
- go.mod
|
||||||
|
- go.sum
|
||||||
|
- compass/config.go
|
||||||
|
- compass/env.go
|
||||||
|
- compass/persist.go
|
||||||
|
- party/registry.go
|
||||||
|
- pact/capabilities.go
|
||||||
|
- backpack/app.go
|
||||||
|
- backpack/services.go
|
||||||
|
- festival/bus.go
|
||||||
|
- towel/context.go
|
||||||
|
- examples/hello/plugins/base/plugin.go
|
||||||
|
- examples/hello/plugins/base/config/config.yaml
|
||||||
|
- examples/hello/plugins/greeter/plugin.go
|
||||||
|
- examples/hello/plugins/optional/go.mod
|
||||||
|
- examples/hello/plugins/optional/plugin.go
|
||||||
|
- examples/hello/plugins/optional/config/config.yaml
|
||||||
|
- examples/hello/summer.yaml
|
||||||
|
- examples/hello/go.mod
|
||||||
|
- examples/hello/plugins.gen.go
|
||||||
|
- examples/hello/hello_test.go
|
||||||
|
- examples/hello/config/env/development/app.yaml
|
||||||
|
- go.work
|
||||||
|
autonomous: true
|
||||||
|
requirements: [KERN-01, KERN-02, KERN-03, KERN-05, KERN-06, KERN-07, KERN-08]
|
||||||
|
must_haves:
|
||||||
|
truths:
|
||||||
|
- "D-06 D-07 D-08 D-09: `golem15.optional.*` config resolves from embedded plugin defaults, sorted base/env YAML, `SUMMER_` variables, persisted overrides and runtime Set in the specified priority; Reload clears Set."
|
||||||
|
- "D-10: A missing required plugin or cycle stops app boot with an error naming the involved plugin IDs and exits non-zero."
|
||||||
|
- "D-11: An app-scoped HasPlugin check and typed `backpack` lookup let greeter skip an absent optional plugin without importing its package."
|
||||||
|
- "D-12 D-13: Typed struct event listeners run by descending priority with stable ties; the three dispatch modes obey their error and panic rules."
|
||||||
|
- "Request actor, organization, collection and locale values pass through `context.Context`, with no package-global request state."
|
||||||
|
artifacts:
|
||||||
|
- path: compass/config.go
|
||||||
|
provides: Config precedence and dot-path access
|
||||||
|
- path: backpack/services.go
|
||||||
|
provides: Typed app-scoped service registry
|
||||||
|
- path: festival/bus.go
|
||||||
|
provides: Typed event dispatch
|
||||||
|
- path: towel/context.go
|
||||||
|
provides: Typed request context accessors
|
||||||
|
key_links:
|
||||||
|
- from: party/registry.go
|
||||||
|
to: compass/config.go
|
||||||
|
via: `HasConfig` discovery before plugin Register
|
||||||
|
- from: examples/hello/plugins/greeter/plugin.go
|
||||||
|
to: backpack/services.go
|
||||||
|
via: Optional typed service lookup during Boot
|
||||||
|
- from: backpack/app.go
|
||||||
|
to: festival/bus.go
|
||||||
|
via: Bus instance owned by each app
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
As a framework developer, I can change a hello plugin's config, compose it with an optional plugin, and observe a typed event through the built app, so kernel extension behavior is proven in use.
|
||||||
|
|
||||||
|
Purpose: Complete only the config, lifecycle, service and event behavior needed by the first real app slice.
|
||||||
|
Output: Full `compass` layering, deterministic `party` failures, typed `backpack` services, `festival` dispatch and context accessors.
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<execution_context>
|
||||||
|
@$HOME/.codex/get-shit-done/workflows/execute-plan.md
|
||||||
|
@$HOME/.codex/get-shit-done/templates/summary.md
|
||||||
|
</execution_context>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
@.planning/PROJECT.md
|
||||||
|
@.planning/ROADMAP.md
|
||||||
|
@.planning/REQUIREMENTS.md
|
||||||
|
@.planning/phases/01-framework-kernel-foundation/01-CONTEXT.md
|
||||||
|
@.planning/phases/01-framework-kernel-foundation/01-RESEARCH.md
|
||||||
|
@.planning/phases/01-framework-kernel-foundation/01-VALIDATION.md
|
||||||
|
@.planning/phases/01-framework-kernel-foundation/01-01-SUMMARY.md
|
||||||
|
@../modules/summer-compass/README.md
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto" tdd="true">
|
||||||
|
<name>Task 1: Make the hello command observe full layered config</name>
|
||||||
|
<files>go.mod, go.sum, compass/config.go, compass/env.go, compass/persist.go, pact/capabilities.go, party/registry.go, examples/hello/plugins/base/plugin.go, examples/hello/plugins/base/config/config.yaml, examples/hello/config/env/development/app.yaml, examples/hello/hello_test.go</files>
|
||||||
|
<read_first>go.mod, go.sum, compass/config.go, compass/env.go, compass/persist.go, pact/capabilities.go, party/registry.go, examples/hello/plugins/base/plugin.go, examples/hello/plugins/base/config/config.yaml, examples/hello/config/app.yaml, examples/hello/config/env/development/app.yaml, examples/hello/hello_test.go, .planning/phases/01-framework-kernel-foundation/01-CONTEXT.md, ../modules/summer-compass/README.md</read_first>
|
||||||
|
<behavior>With `SUMMER_ENV=development`, the hello command sees a deep-merged env overlay; `SUMMER_GOLEM15__HELLO__POSTS_PER_PAGE` overrides plugin defaults, and Set/Persist/Reload follow D-09.</behavior>
|
||||||
|
<action>Extend `compass` from Plan 01 with deterministic layers (D-06, D-07, D-08, D-09), using pinned koanf providers/file v1.2.1, providers/confmap v1.0.1 and providers/env/v2 v2.0.1: embedded `HasConfig.ConfigFS() fs.FS` files, with `config/config.yaml` merged at the bare plugin ID path (for example `golem15.hello.posts_per_page`) via koanf `MergeAt`; sorted `config/<section>.yaml` base files; sorted `config/env/<env>/<section>.yaml` overlays excluding `overrides.yaml`; `SUMMER_` variables split only on `__` so `POSTS_PER_PAGE` remains one snake_case leaf; `config/env/<env>/overrides.yaml`; then in-memory Set. Read `.env` in the app root into an injected env list only for keys absent from real `os.Environ`; do not mutate process environment. Add `Lookup(path) (any,bool)`, `String`, `Int`, `Bool`, `Has` and typed `LoadSection(path,out)` using `koanf` tags. `SUMMER_ENV` defaults to `production` and explicit constructor env wins. `Persist` atomically writes runtime overrides to the env-specific overrides file with restrictive permissions; `Reload` rebuilds from disk and clears runtime Set. Guard concurrent access with a lock or immutable snapshot. Sanitize env names before creating paths. Keep all behavior visible through the hello command.</action>
|
||||||
|
<verify><automated>go test ./compass ./pact && (cd examples/hello && go test ./...)</automated></verify>
|
||||||
|
<acceptance_criteria>
|
||||||
|
- `SUMMER_DATABASE__HOST` resolves to `database.host`; `SUMMER_GOLEM15__HELLO__POSTS_PER_PAGE` resolves to `golem15.hello.posts_per_page`.
|
||||||
|
- Single underscores remain literal in leaf keys and real environment variables win over `.env` values.
|
||||||
|
- `Set` wins over persisted overrides; `Persist` writes `config/env/<env>/overrides.yaml`; `Reload` clears Set and re-reads files.
|
||||||
|
- Untouched keys in nested YAML maps survive later overlays.
|
||||||
|
</acceptance_criteria>
|
||||||
|
<done>The hello app reads exactly the D-09 priority order through typed and dot-path config access.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto" tdd="true">
|
||||||
|
<name>Task 2: Let a hello plugin use optional services without a hard import</name>
|
||||||
|
<files>party/registry.go, pact/capabilities.go, backpack/app.go, backpack/services.go, examples/hello/plugins/greeter/plugin.go, examples/hello/plugins/optional/go.mod, examples/hello/plugins/optional/plugin.go, examples/hello/plugins/optional/config/config.yaml, examples/hello/summer.yaml, examples/hello/go.mod, examples/hello/plugins.gen.go, examples/hello/hello_test.go, go.work</files>
|
||||||
|
<read_first>party/registry.go, pact/capabilities.go, backpack/app.go, backpack/services.go, examples/hello/plugins/greeter/plugin.go, examples/hello/plugins/optional/go.mod, examples/hello/plugins/optional/plugin.go, examples/hello/plugins/optional/config/config.yaml, examples/hello/summer.yaml, examples/hello/go.mod, examples/hello/plugins.gen.go, examples/hello/hello_test.go, go.work, .planning/phases/01-framework-kernel-foundation/01-CONTEXT.md, .planning/research/ARCHITECTURE.md</read_first>
|
||||||
|
<behavior>Greeter boots whether `golem15.optional` is present or absent; when present it uses a service published under a shared interface, and when absent it skips that integration. Invalid Requires graphs fail before any Boot.</behavior>
|
||||||
|
<action>Add a third trivial hello plugin `golem15.optional` as its own module and optional manifest entry (D-03, D-11). Put the small shared service interface in `pact`, not in the optional plugin package. Implement app-owned `backpack.Registry` with typed `Publish[T]` and `Lookup[T] (T,bool)` on a concrete Go 1.27 type (or equivalent typed helpers), duplicate-provider handling, and no process-global state. `backpack.App.HasPlugin(id)` must use the complete registered set, not only already-Booted plugins. During greeter Boot, use `HasPlugin("golem15.optional")` and typed lookup; the greeter package must not import the optional package. Harden `party.Activate` to reject duplicate IDs, missing Requires and dependency cycles with named IDs (D-10), then keep stable ordering of independent plugins while guaranteeing all Register calls precede every Boot. Define and type-assert the Phase 1 `HasConfig`/`HasCommands` capabilities. Document the future KERN-03 capability families (`HasModels`, `HasMigrations`, `HasRoutes`, `HasMiddleware`, `HasJobs`, `HasListeners`, `HasAdminControllers`, `HasNavigation`, `HasPermissions`, `HasSchedule`, `HasMailTemplates`, `HasLang`) without coupling their payloads to absent Phase 3 packages. Do not implement their adapters in this phase.</action>
|
||||||
|
<verify><automated>go test ./party ./backpack ./pact && (cd examples/hello && go test ./...)</automated></verify>
|
||||||
|
<acceptance_criteria>
|
||||||
|
- Reordered manifest input still causes all Register calls to precede every Boot and respects `Requires()`.
|
||||||
|
- Missing dependency and cycle errors include the plugin IDs involved and make app boot exit non-zero.
|
||||||
|
- Greeter runs with and without `golem15.optional`; its source has no import of the optional plugin module.
|
||||||
|
- Typed service lookup returns `(value, false)` for an absent interface and a concrete value when published.
|
||||||
|
</acceptance_criteria>
|
||||||
|
<done>The hello app demonstrates optional plugin composition and deterministic failure handling.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto" tdd="true">
|
||||||
|
<name>Task 3: Dispatch typed hello events with request context</name>
|
||||||
|
<files>festival/bus.go, backpack/app.go, towel/context.go, examples/hello/plugins/base/plugin.go, examples/hello/plugins/greeter/plugin.go, examples/hello/hello_test.go</files>
|
||||||
|
<read_first>festival/bus.go, backpack/app.go, towel/context.go, examples/hello/plugins/base/plugin.go, examples/hello/plugins/greeter/plugin.go, examples/hello/hello_test.go, .planning/phases/01-framework-kernel-foundation/01-CONTEXT.md, .planning/research/ARCHITECTURE.md, .planning/phases/01-framework-kernel-foundation/01-RESEARCH.md</read_first>
|
||||||
|
<behavior>The hello command fires a typed struct event; listeners can notify, collect merged values, or stop at the first handled result. Context values remain scoped to that invocation.</behavior>
|
||||||
|
<action>Implement an app-owned `festival.Bus` keyed by concrete Go event type, with synchronous dispatch on the caller goroutine and owner plugin IDs. Expose plain `Listen[T]` at priority 0 plus `ListenPriority[T]`; run higher integer priorities first and ties by registration order (D-12). Provide `Fire[T] error`, `Collect[T] (map[string]any,error)` with later invoked listener winning duplicate keys, and `UntilHandled[T] (bool,error)`. For Fire, invoke every listener and return `errors.Join`; for Collect, return partial merged data and joined errors; for UntilHandled, stop on first handled result or first error. Recover listener panics into an error naming its owner plugin ID (D-13). Do not add async fan-out. Add `towel.WithActor/Actor`, `WithOrganization/Organization`, `WithCollection/Collection`, and `WithLocale/Locale` typed context helpers using unexported key types; no package-global request values (KERN-07). Pass ctx from cobra Run into event dispatch and demonstrate one hello event listener.</action>
|
||||||
|
<verify><automated>go test ./festival ./towel ./backpack && (cd examples/hello && go test ./...)</automated></verify>
|
||||||
|
<acceptance_criteria>
|
||||||
|
- Fire runs all listeners and joins their errors; Collect returns partial payload plus joined errors.
|
||||||
|
- UntilHandled stops on its first error or first handled listener.
|
||||||
|
- Higher priority runs first and equal priority preserves registration order.
|
||||||
|
- A recovered panic error names the owning plugin ID; two app instances have isolated listener sets.
|
||||||
|
- Context values are retrieved only from the passed `context.Context` and no request-state package globals exist.
|
||||||
|
</acceptance_criteria>
|
||||||
|
<done>The hello path demonstrates all three event modes and context-scoped request state.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<threat_model>
|
||||||
|
## Trust Boundaries
|
||||||
|
|
||||||
|
| Boundary | Description |
|
||||||
|
|---|---|
|
||||||
|
| YAML and `.env` → live config | Local files can change runtime behavior and persisted overrides. |
|
||||||
|
| Plugin listener → app dispatch | A compiled plugin can panic or return an error during another plugin's event. |
|
||||||
|
|
||||||
|
## STRIDE Threat Register
|
||||||
|
|
||||||
|
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| T-01-03 | Tampering | `compass.Persist` path | mitigate | Validate env name; write only beneath configured `config/env/<env>` and use atomic replacement with restrictive file permissions. |
|
||||||
|
| T-01-04 | Denial of service | `festival.Bus` listener | mitigate | Recover panics with owner ID and keep dispatch error behavior deterministic; no implicit goroutine fan-out. |
|
||||||
|
| T-01-05 | Information disclosure | `.env` and config logging | mitigate | Do not print config values or secret env contents in errors or build logs. |
|
||||||
|
</threat_model>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
- Run root and nested hello `go vet ./...` and `go test ./...` after each task commit.
|
||||||
|
- Run focused `go test -race ./compass ./party ./backpack ./festival ./towel` before this plan closes.
|
||||||
|
- Exercise the hello command with and without the optional plugin entry.
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
- All six config priority levels are observable and Reload semantics are tested.
|
||||||
|
- Optional plugin lookup and typed services work without a hard import.
|
||||||
|
- Typed event modes obey D-12/D-13 and do not share state across apps.
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
Create `.planning/phases/01-framework-kernel-foundation/01-02-SUMMARY.md` after completion.
|
||||||
|
</output>
|
||||||
158
.planning/phases/01-framework-kernel-foundation/01-03-PLAN.md
Normal file
158
.planning/phases/01-framework-kernel-foundation/01-03-PLAN.md
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
---
|
||||||
|
phase: 01-framework-kernel-foundation
|
||||||
|
plan: 03
|
||||||
|
type: execute
|
||||||
|
wave: 3
|
||||||
|
depends_on: ["01-02"]
|
||||||
|
files_modified:
|
||||||
|
- internal/build/build.go
|
||||||
|
- internal/build/manifest.go
|
||||||
|
- internal/build/scaffold.go
|
||||||
|
- internal/dev/watch.go
|
||||||
|
- cmd/summer/main.go
|
||||||
|
- bonfire/root.go
|
||||||
|
- bonfire/command.go
|
||||||
|
- bonfire/output.go
|
||||||
|
- bonfire/widgets.go
|
||||||
|
- bonfire/prompts.go
|
||||||
|
- bonfire/output_test.go
|
||||||
|
- bonfire/prompts_test.go
|
||||||
|
- internal/build/build_test.go
|
||||||
|
- internal/dev/watch_test.go
|
||||||
|
- examples/hello/plugins/greeter/plugin.go
|
||||||
|
- go.mod
|
||||||
|
- go.sum
|
||||||
|
autonomous: true
|
||||||
|
requirements: [KERN-04, KERN-09, CLI-01]
|
||||||
|
must_haves:
|
||||||
|
truths:
|
||||||
|
- "D-01 D-03 D-04 D-05: `summer make:plugin` scaffolds a compiling plugin and `summer plugin:add` adds it to the ordered manifest/workspace; `summer build` regenerates imports and a separate app binary."
|
||||||
|
- "D-14: Spinner, progress, table, ask/confirm/choice/secret prompts and color policy use standard library plus `x/term`, with deterministic non-TTY output."
|
||||||
|
- "D-15 D-17: Tool and app commands use colon-style names; plugin command metadata, flags, arguments and injected Output are wrapped by the shared cobra kernel."
|
||||||
|
- "D-16: `summer dev` watches sources/config/manifests, debounces, calls the same build function, restarts the app only after successful build, and prints measured rebuild latency."
|
||||||
|
artifacts:
|
||||||
|
- path: internal/build/scaffold.go
|
||||||
|
provides: Minimal plugin scaffold and manifest/workspace registration
|
||||||
|
- path: bonfire/widgets.go
|
||||||
|
provides: Spinner, progress and table rendering
|
||||||
|
- path: bonfire/prompts.go
|
||||||
|
provides: Prompt handling and non-TTY fallback
|
||||||
|
- path: internal/dev/watch.go
|
||||||
|
provides: Built-in watch rebuild loop
|
||||||
|
key_links:
|
||||||
|
- from: cmd/summer/main.go
|
||||||
|
to: internal/build/scaffold.go
|
||||||
|
via: `make:plugin` and `plugin:add` tool commands
|
||||||
|
- from: internal/dev/watch.go
|
||||||
|
to: internal/build/build.go
|
||||||
|
via: One shared build function
|
||||||
|
- from: bonfire/root.go
|
||||||
|
to: bonfire/output.go
|
||||||
|
via: Output injection into plugin commands
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
As a framework developer, I can add a plugin, rebuild, see useful CLI output, and keep an app running while editing source, so compiled plugins have a practical development loop.
|
||||||
|
|
||||||
|
Purpose: Complete the Phase 1 tool experience around the already bootable hello app.
|
||||||
|
Output: `make:plugin`, `plugin:add`, rich CLI output and built-in `summer dev` watch loop.
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<execution_context>
|
||||||
|
@$HOME/.codex/get-shit-done/workflows/execute-plan.md
|
||||||
|
@$HOME/.codex/get-shit-done/templates/summary.md
|
||||||
|
</execution_context>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
@.planning/PROJECT.md
|
||||||
|
@.planning/ROADMAP.md
|
||||||
|
@.planning/REQUIREMENTS.md
|
||||||
|
@.planning/phases/01-framework-kernel-foundation/01-CONTEXT.md
|
||||||
|
@.planning/phases/01-framework-kernel-foundation/01-RESEARCH.md
|
||||||
|
@.planning/phases/01-framework-kernel-foundation/01-VALIDATION.md
|
||||||
|
@.planning/phases/01-framework-kernel-foundation/01-02-SUMMARY.md
|
||||||
|
@../modules/summer-bonfire/PLAN.md
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 1: Add and rebuild a compiling plugin from the tool</name>
|
||||||
|
<files>internal/build/build.go, internal/build/manifest.go, internal/build/scaffold.go, internal/build/build_test.go, cmd/summer/main.go</files>
|
||||||
|
<read_first>internal/build/build.go, internal/build/manifest.go, internal/build/scaffold.go, internal/build/build_test.go, cmd/summer/main.go, examples/hello/summer.yaml, examples/hello/go.mod, examples/hello/plugins.gen.go, examples/hello/plugins/base/plugin.go, go.work, .planning/phases/01-framework-kernel-foundation/01-CONTEXT.md, .planning/phases/01-framework-kernel-foundation/01-RESEARCH.md</read_first>
|
||||||
|
<action>Complete D-01/D-03/D-04/D-05 using `summer.yaml` as the sole ordered source of plugin IDs and module paths. `summer make:plugin <vendor.plugin>` scaffolds `plugins/<plugin>/go.mod` with module path `<app-module>/plugins/<plugin>` and `toolchain go1.27.0`, `plugin.go` implementing ID/Requires/Register/Boot and `init(){ party.Register(...) }`, plus an empty `config/` placeholder; no model/migration/controller/command stubs. `summer plugin:add <local-plugin-dir>` reads the module path from that directory's `go.mod`, validates the ID and module path, adds the plugin once to `summer.yaml`, updates the nearest app `go.work` with `go work use`, and updates app `go.mod` as needed for a portable dependency graph. Preserve manifest order and reject conflicting IDs. `summer build` must regenerate both app files from the updated manifest and print elapsed build time; no map iteration controls output. Keep tool source independent of app/plugin imports. Exercise the commands on a temporary copy of `examples/hello` so the permanent testbed stays small.</action>
|
||||||
|
<verify><automated>go test ./internal/build ./cmd/summer && (cd examples/hello && go test ./...)</automated></verify>
|
||||||
|
<acceptance_criteria>
|
||||||
|
- `make:plugin golem15.demo` produces a compiling plugin module with only `go.mod`, `plugin.go` and `config/`.
|
||||||
|
- `plugin:add` adds one manifest entry and workspace module; repeating it is idempotent.
|
||||||
|
- `summer build` produces byte-stable `main.go` and `plugins.gen.go` from the updated manifest.
|
||||||
|
- The framework tool has no import of an app-specific plugin package.
|
||||||
|
</acceptance_criteria>
|
||||||
|
<done>A new local plugin can be scaffolded, added and compiled into the hello app.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 2: Give tool and plugin commands deterministic rich output</name>
|
||||||
|
<files>bonfire/root.go, bonfire/command.go, bonfire/output.go, bonfire/widgets.go, bonfire/prompts.go, bonfire/output_test.go, bonfire/prompts_test.go, cmd/summer/main.go, examples/hello/plugins/greeter/plugin.go, go.mod, go.sum</files>
|
||||||
|
<read_first>bonfire/root.go, bonfire/command.go, bonfire/output.go, bonfire/widgets.go, bonfire/prompts.go, bonfire/output_test.go, bonfire/prompts_test.go, cmd/summer/main.go, examples/hello/plugins/greeter/plugin.go, go.mod, go.sum, ../modules/summer-bonfire/PLAN.md, .planning/phases/01-framework-kernel-foundation/01-CONTEXT.md, .planning/phases/01-framework-kernel-foundation/01-RESEARCH.md</read_first>
|
||||||
|
<action>Implement D-14/D-15/D-17 with stdlib formatting and `golang.org/x/term` v0.46.0 only for terminal capability/raw/password primitives. `bonfire.Output` is constructed once from injected stdin/stdout/stderr and terminal/color policy; the cobra adapter passes `ctx`, parsed `Input`, and that Output to every `bonfire.Command`. Command metadata defines arguments and flags without duplicating root wiring; reject plugin command names without `<namespace>:<verb>`, while kernel tool commands are `make:plugin`, `plugin:add`, `build`, `dev`. Implement braille spinner and gradient progress for TTY, box-drawing table, ask/confirm/choice/secret prompts. For non-TTY: spinner prints one `[...] message`; progress prints `[N/M] pct%` at 10% steps; table is tab-separated; ask/choice/secret read stdin lines; confirm uses its default. `NO_COLOR` or `TERM=dumb` disables ANSI; `FORCE_COLOR` enables it only when neither disable condition is present. `secret` uses `term.ReadPassword` on TTY and a plain stdin line otherwise. Make the hello plugin command exercise table and one prompt without hanging when stdin is closed.</action>
|
||||||
|
<verify><automated>go test ./bonfire ./cmd/summer && (cd examples/hello && go test ./...)</automated></verify>
|
||||||
|
<acceptance_criteria>
|
||||||
|
- `summer make:plugin --help` and the app's `greeter:hello --help` are generated through the shared `bonfire` cobra adapter.
|
||||||
|
- Non-TTY output has no ANSI when `NO_COLOR=1` or `TERM=dumb` and uses the exact spinner/progress/table fallback shapes above.
|
||||||
|
- Injected streams let tests capture Output and feed prompt answers without a real TTY.
|
||||||
|
- Plugin command names without `:` fail registration with a named error.
|
||||||
|
</acceptance_criteria>
|
||||||
|
<done>Both binaries expose namespaced commands and usable output in interactive and CI environments.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 3: Rebuild and restart the hello binary during source edits</name>
|
||||||
|
<files>internal/dev/watch.go, internal/dev/watch_test.go, internal/build/build.go, cmd/summer/main.go, go.mod, go.sum</files>
|
||||||
|
<read_first>internal/build/build.go, internal/dev/watch.go, internal/dev/watch_test.go, cmd/summer/main.go, go.mod, go.sum, examples/hello/summer.yaml, .planning/phases/01-framework-kernel-foundation/01-CONTEXT.md, .planning/phases/01-framework-kernel-foundation/01-RESEARCH.md</read_first>
|
||||||
|
<action>Implement `summer dev` with `github.com/fsnotify/fsnotify` v1.10.1 (D-16). Watch app workspace directories recursively by walking them and adding new directories on Create; include `.go`, `.yaml`, `.yml`, `.env`, `go.mod`, `go.work` and plugin manifests. Ignore `.git`, `bin`, temp build outputs and generated app files to prevent loops. Debounce bursts (for example 200ms) and serialize builds. Call the exact `internal/build` function used by `summer build`; after success stop and reap the old child, start the new app binary with inherited streams, and print `rebuild: <duration>` measured from build start to completion. On build failure keep the old process running and print the build error; on context cancellation stop/reap the child and close the watcher. Do not execute manifest-derived shell commands. Add a smoke test with a temporary workspace to assert one restart after a source edit, no restart after ignored generated output and a latency line.</action>
|
||||||
|
<verify><automated>go test ./internal/dev ./internal/build ./cmd/summer && (cd examples/hello && go test ./...)</automated></verify>
|
||||||
|
<acceptance_criteria>
|
||||||
|
- Editing a watched hello plugin `.go` file causes one debounced rebuild and successful restart.
|
||||||
|
- Build failure leaves the last successful child running; cancellation reaps it.
|
||||||
|
- A generated `plugins.gen.go` write does not cause an infinite rebuild loop.
|
||||||
|
- Every rebuild cycle prints measured elapsed time, including the cycle after a single-plugin edit.
|
||||||
|
</acceptance_criteria>
|
||||||
|
<done>The local development loop rebuilds and restarts without an external watcher install.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<threat_model>
|
||||||
|
## Trust Boundaries
|
||||||
|
|
||||||
|
| Boundary | Description |
|
||||||
|
|---|---|
|
||||||
|
| CLI plugin input → filesystem | Plugin ID and directory values select paths and module names. |
|
||||||
|
| Filesystem event → build/restart | File changes trigger local tool execution and process replacement. |
|
||||||
|
| Prompt → terminal | Secret values may be echoed or captured if mode detection is wrong. |
|
||||||
|
|
||||||
|
## STRIDE Threat Register
|
||||||
|
|
||||||
|
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| T-01-06 | Tampering | `make:plugin` and `plugin:add` paths | mitigate | Validate lowercase vendor.plugin ID and keep generated paths under the app root; reject traversal and duplicate/conflicting entries. |
|
||||||
|
| T-01-07 | Denial of service | `summer dev` watcher | mitigate | Debounce, ignore output dirs, serialize builds, and reap child processes on cancellation. |
|
||||||
|
| T-01-08 | Information disclosure | `bonfire` secret prompt | mitigate | Use `term.ReadPassword` for TTY and document plain stdin fallback; never log answers. |
|
||||||
|
</threat_model>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
- After each task commit, run `go vet ./... && go test ./...` in root and hello modules.
|
||||||
|
- Smoke-test `make:plugin`, `plugin:add`, `build` and `dev` against temporary copies of `examples/hello`.
|
||||||
|
- Capture non-TTY output with pipes; manually inspect one TTY command during phase verification.
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
- A new plugin is scaffolded, registered and built using only the framework tool.
|
||||||
|
- CLI output degrades predictably without a TTY.
|
||||||
|
- A plugin source change triggers a measured rebuild and app restart.
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
Create `.planning/phases/01-framework-kernel-foundation/01-03-SUMMARY.md` after completion.
|
||||||
|
</output>
|
||||||
131
.planning/phases/01-framework-kernel-foundation/01-04-PLAN.md
Normal file
131
.planning/phases/01-framework-kernel-foundation/01-04-PLAN.md
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
---
|
||||||
|
phase: 01-framework-kernel-foundation
|
||||||
|
plan: 04
|
||||||
|
type: execute
|
||||||
|
wave: 4
|
||||||
|
depends_on: ["01-03"]
|
||||||
|
files_modified:
|
||||||
|
- compass/config_test.go
|
||||||
|
- party/registry_test.go
|
||||||
|
- backpack/services_test.go
|
||||||
|
- festival/bus_test.go
|
||||||
|
- towel/context_test.go
|
||||||
|
- bonfire/output_test.go
|
||||||
|
- bonfire/prompts_test.go
|
||||||
|
- internal/build/build_test.go
|
||||||
|
- internal/dev/watch_test.go
|
||||||
|
- examples/hello/hello_test.go
|
||||||
|
- scripts/check-phase1.sh
|
||||||
|
- .planning/phases/01-framework-kernel-foundation/01-VALIDATION.md
|
||||||
|
autonomous: true
|
||||||
|
requirements: [KERN-01, KERN-02, KERN-03, KERN-04, KERN-05, KERN-06, KERN-07, KERN-08, KERN-09, CLI-01]
|
||||||
|
must_haves:
|
||||||
|
truths:
|
||||||
|
- "Every KERN-01 through KERN-09 and CLI-01 contract has a behavioral test, including failure and non-TTY paths."
|
||||||
|
- "The final check runs `go vet`, `go test`, and `go test -race` in the framework, hello app and each hello plugin module."
|
||||||
|
- "A temporary app can run `summer build` and its generated binary; source edit under `summer dev` records measured rebuild time and restarts the child."
|
||||||
|
artifacts:
|
||||||
|
- path: scripts/check-phase1.sh
|
||||||
|
provides: Repeatable phase-wide verification command
|
||||||
|
- path: compass/config_test.go
|
||||||
|
provides: Config precedence regression tests
|
||||||
|
- path: festival/bus_test.go
|
||||||
|
provides: Event dispatch regression tests
|
||||||
|
- path: examples/hello/hello_test.go
|
||||||
|
provides: Built-app integration test
|
||||||
|
key_links:
|
||||||
|
- from: scripts/check-phase1.sh
|
||||||
|
to: examples/hello/go.mod
|
||||||
|
via: Checks nested app and plugin modules independently
|
||||||
|
- from: examples/hello/hello_test.go
|
||||||
|
to: cmd/summer/main.go
|
||||||
|
via: Builds and executes a generated app binary
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
As a framework maintainer, I can run one repeatable check that proves the Phase 1 kernel and development loop before Phase 3 depends on them.
|
||||||
|
|
||||||
|
Purpose: Add the phase's dedicated unit and integration test plan, with broad behavior and failure coverage.
|
||||||
|
Output: Focused Go tests, a nested-module check script, a generated-binary smoke and completed Nyquist validation map.
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<execution_context>
|
||||||
|
@$HOME/.codex/get-shit-done/workflows/execute-plan.md
|
||||||
|
@$HOME/.codex/get-shit-done/templates/summary.md
|
||||||
|
</execution_context>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
@.planning/PROJECT.md
|
||||||
|
@.planning/ROADMAP.md
|
||||||
|
@.planning/REQUIREMENTS.md
|
||||||
|
@.planning/phases/01-framework-kernel-foundation/01-CONTEXT.md
|
||||||
|
@.planning/phases/01-framework-kernel-foundation/01-RESEARCH.md
|
||||||
|
@.planning/phases/01-framework-kernel-foundation/01-VALIDATION.md
|
||||||
|
@.planning/phases/01-framework-kernel-foundation/01-03-SUMMARY.md
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 1: Cover config, lifecycle, services, events and context behavior</name>
|
||||||
|
<files>compass/config_test.go, party/registry_test.go, backpack/services_test.go, festival/bus_test.go, towel/context_test.go</files>
|
||||||
|
<read_first>compass/config.go, compass/env.go, compass/persist.go, party/registry.go, backpack/app.go, backpack/services.go, festival/bus.go, towel/context.go, compass/config_test.go, party/registry_test.go, backpack/services_test.go, festival/bus_test.go, towel/context_test.go, .planning/phases/01-framework-kernel-foundation/01-CONTEXT.md</read_first>
|
||||||
|
<action>Expand earlier smoke tests into table-driven behavioral tests for all config precedence levels, malformed YAML, snake_case env keys, `.env` non-override, dotted plugin namespace, typed section decode, Set/Persist/Reload and concurrent reads. Test plugin duplicate/missing/cycle errors, reordered manifest inputs, all-Register-before-any-Boot, HasConfig/HasCommands assertions, absent/present HasPlugin and typed service lookup with two separate App instances. Test three typed event dispatch modes, priority and stable ties, partial collect payload, joined errors, stop-on-error, owner-labelled panic recovery and concurrent independent app buses. Test actor/organization/collection/locale context accessors and nested context isolation. Fix any production defect exposed by these tests in the same task, keeping API behavior from D-01–D-13 intact. Report per-package coverage for these packages; cover contract branches rather than chasing an arbitrary percentage.</action>
|
||||||
|
<verify><automated>go test -race ./compass ./party ./backpack ./festival ./towel ./pact</automated></verify>
|
||||||
|
<acceptance_criteria>
|
||||||
|
- Each KERN-01/02/03/05/06/07/08 behavior above has at least one source-to-observable assertion.
|
||||||
|
- `go test -race ./compass ./party ./backpack ./festival ./towel ./pact` exits 0.
|
||||||
|
- No test asserts only implementation structure when a behavior can be observed.
|
||||||
|
</acceptance_criteria>
|
||||||
|
<done>The kernel's core behavioral and failure contracts are regression tested.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 2: Cover tool, output, watch loop and every workspace module</name>
|
||||||
|
<files>bonfire/output_test.go, bonfire/prompts_test.go, internal/build/build_test.go, internal/dev/watch_test.go, examples/hello/hello_test.go, scripts/check-phase1.sh, .planning/phases/01-framework-kernel-foundation/01-VALIDATION.md</files>
|
||||||
|
<read_first>bonfire/root.go, bonfire/output.go, bonfire/widgets.go, bonfire/prompts.go, internal/build/build.go, internal/build/manifest.go, internal/build/scaffold.go, internal/dev/watch.go, cmd/summer/main.go, examples/hello/summer.yaml, bonfire/output_test.go, bonfire/prompts_test.go, internal/build/build_test.go, internal/dev/watch_test.go, examples/hello/hello_test.go, scripts/check-phase1.sh, .planning/phases/01-framework-kernel-foundation/01-VALIDATION.md</read_first>
|
||||||
|
<action>Test namespaced command discovery and flag/argument parsing, injected output capture, all non-TTY widget/prompt fallbacks, `NO_COLOR`/`FORCE_COLOR`/`TERM=dumb` policy and closed stdin. In a temporary copied hello workspace, assert `make:plugin` minimal files, `plugin:add` idempotence, stable generated bytes, malicious ID/path rejection, `summer build` success, built hello command output and non-zero failure on invalid Requires. Use deterministic build/process hooks for debounce/cancellation tests, plus a real fsnotify temp-workspace edit for one successful rebuild/restart; capture and assert a `rebuild: <duration>` line without assuming a threshold. Add `scripts/check-phase1.sh` that runs `go vet ./...`, `go test ./...` and `go test -race ./...` from root, `examples/hello`, and each nested plugin module; include built-binary integration. Update VALIDATION.md's per-task map/status and set `nyquist_compliant: true` only when every planned automated check exists and passes. Fix production defects the tests expose, while keeping this plan focused on verification.</action>
|
||||||
|
<verify><automated>bash scripts/check-phase1.sh</automated></verify>
|
||||||
|
<acceptance_criteria>
|
||||||
|
- `bash scripts/check-phase1.sh` exits 0 and names root, hello app, base, greeter and optional plugin modules.
|
||||||
|
- Test suite observes `summer build` and a separately executed hello binary, not only package compilation.
|
||||||
|
- Watch test observes a source-change rebuild, restart and measured latency line; generated-file edits do not loop.
|
||||||
|
- Non-TTY spinner/progress/table/prompt and color policy assertions run without a terminal.
|
||||||
|
- VALIDATION.md records passing automated checks and `nyquist_compliant: true` only after the full script passes.
|
||||||
|
</acceptance_criteria>
|
||||||
|
<done>The phase has a repeatable, race-enabled verification command and completed validation evidence.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<threat_model>
|
||||||
|
## Trust Boundaries
|
||||||
|
|
||||||
|
| Boundary | Description |
|
||||||
|
|---|---|
|
||||||
|
| Test fixture input → tool/process | Malformed manifests and source edits should fail safely during local tooling. |
|
||||||
|
|
||||||
|
## STRIDE Threat Register
|
||||||
|
|
||||||
|
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| T-01-09 | Tampering | Manifest and scaffold inputs | mitigate | Add negative tests for invalid IDs, duplicate modules and path traversal. |
|
||||||
|
| T-01-10 | Denial of service | Watch loop | mitigate | Test debounce, ignored outputs, failed build and child cleanup. |
|
||||||
|
| T-01-11 | Information disclosure | CLI secret and config | mitigate | Assert secret prompt answer and config values do not leak into captured error/output streams. |
|
||||||
|
</threat_model>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
- Run `bash scripts/check-phase1.sh` and inspect its per-module output.
|
||||||
|
- Confirm `go test -race` passes in all five modules and `go vet` is green.
|
||||||
|
- Record measured single-plugin rebuild time in 01-04-SUMMARY.md.
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
- All ten phase requirement IDs have automated behavioral evidence.
|
||||||
|
- The final testing plan runs after implementation and passes across all workspace modules.
|
||||||
|
- VALIDATION.md can be signed off without missing tests or broken Wave 0 references.
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
Create `.planning/phases/01-framework-kernel-foundation/01-04-SUMMARY.md` after completion.
|
||||||
|
</output>
|
||||||
@@ -121,11 +121,11 @@ The fast feedback command should take seconds once dependencies are cached. CI c
|
|||||||
</validation_architecture>
|
</validation_architecture>
|
||||||
|
|
||||||
<open_questions>
|
<open_questions>
|
||||||
## Open Questions
|
## Open Questions (RESOLVED)
|
||||||
|
|
||||||
1. **Exact optional capability method signatures for Phase 3+** — `HasModels`, `HasRoutes` and peers are named in KERN-03, but their concrete adapters do not exist yet. Prefer contracts that compile without importing future packages and document that signatures may be refined when the first consumer arrives.
|
1. **Exact optional capability method signatures for Phase 3+ — RESOLVED:** Phase 1 implements the type-asserted `HasConfig` and `HasCommands` adapters and documents the remaining named capability families. Their payload methods are declared when the first relevant phase has concrete consumer types, per the context's interleaved kernel rule. No `[]any` placeholders are introduced just to fill an interface list.
|
||||||
2. **Plugin config representation with dotted IDs** — test koanf lookup behavior for a YAML key `golem15.fonoteka` versus nested `golem15: {fonoteka: ...}` before locking the on-disk form. The public logical path and env mapping remain fixed by D-06/D-08.
|
2. **Plugin config representation with dotted IDs — RESOLVED:** Each plugin's embedded `config/config.yaml` contains keys relative to its ID; `compass` merges it at the logical path `golem15.fonoteka` using koanf `MergeAt`. Additional embedded section files, if used later, merge at `golem15.fonoteka.<filename>`. App-level `config/<section>.yaml` retains filename-as-section behavior. This avoids relying on a dotted YAML root key while preserving the D-06/D-08 public paths.
|
||||||
3. **Dev watch process ownership across platforms** — implement and test Linux first in this environment; keep command execution behind a small abstraction if later platform handling needs adjustment.
|
3. **Dev watch process ownership across platforms — RESOLVED:** Use `exec.CommandContext` and an explicit child stop/reap path. Test Linux in this environment and keep build/process launch behind small test hooks; no platform-specific daemon API is required for Phase 1.
|
||||||
</open_questions>
|
</open_questions>
|
||||||
|
|
||||||
<sources>
|
<sources>
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# Walking Skeleton — SummerCMS (Go)
|
||||||
|
|
||||||
|
**Phase:** 1
|
||||||
|
**Generated:** 2026-09-16
|
||||||
|
|
||||||
|
## Capability Proven End-to-End
|
||||||
|
|
||||||
|
A framework developer can run `summer build` in `examples/hello`, boot a separately compiled app from YAML config and generated plugin imports, then execute a plugin command through the app binary.
|
||||||
|
|
||||||
|
## Architectural Decisions
|
||||||
|
|
||||||
|
| Decision | Choice | Rationale |
|
||||||
|
|---|---|---|
|
||||||
|
| Framework | Go 1.27.0, module `git.golem15.com/golem15/summercms` | Locked by Phase 1 context. |
|
||||||
|
| Plugin linkage | App `summer.yaml` → generated `plugins.gen.go` blank imports | Compiled plugins with an explicit ordered manifest. |
|
||||||
|
| App composition | `party` lifecycle, `backpack.App`, `compass`, `festival`, `bonfire` | The smallest Phase 1 kernel path with a real consumer. |
|
||||||
|
| Data layer | Phase 3: Postgres + GORM/gormigrate | Phase 1 context excludes ORM and DB; the first real data slice is `GET /_fonoteka/api/v1/genres`. |
|
||||||
|
| HTTP/auth/UI | Phase 3 HTTP/auth; existing Nuxt UI remains the app frontend | Phase 1 is a framework CLI phase and excludes router, auth and UI. |
|
||||||
|
| Dev target | Local `summer build` and `summer dev` in `examples/hello` | Makes plugin development observable before deployment is introduced. |
|
||||||
|
| Directory layout | Framework packages at root, tool in `cmd/summer`, app/modules under `examples/hello` | Proves two-binary and two-repository architecture without importing the real app. |
|
||||||
|
|
||||||
|
## Stack Touched in Phase 1
|
||||||
|
|
||||||
|
- [ ] Framework module and root `go.work` with separate hello app/plugin modules
|
||||||
|
- [ ] Generated app entry point and plugin import list
|
||||||
|
- [ ] YAML config → Register-all → Boot-all → plugin command
|
||||||
|
- [ ] Built-in watch rebuild and restart loop
|
||||||
|
- [ ] Local run command: `cd examples/hello && summer build && ./bin/hello greeter:hello`
|
||||||
|
|
||||||
|
## Phase Boundary
|
||||||
|
|
||||||
|
The generic Walking Skeleton recipe calls for routing, a DB read/write and a UI interaction. Phase 1's locked CONTEXT.md explicitly excludes `surf`, `lagoon`, `bouncer` and UI until Phase 3. This skeleton proves the complete **kernel developer path** and records the later app slice as its handoff. No substitute fake DB/UI is added to satisfy a generic template.
|
||||||
|
|
||||||
|
## Subsequent Slice Plan
|
||||||
|
|
||||||
|
- Phase 2 builds the API parity harness independently.
|
||||||
|
- Phase 3 adds the first real Postgres-backed Płytarium endpoint and exercises the framework from the separate app repository.
|
||||||
@@ -16,14 +16,14 @@ created: 2026-09-16
|
|||||||
| Framework | `go test` (Go 1.27.0) |
|
| Framework | `go test` (Go 1.27.0) |
|
||||||
| Config file | No test harness yet; first implementation slice adds Go tests |
|
| Config file | No test harness yet; first implementation slice adds Go tests |
|
||||||
| Quick run | `go vet ./... && go test ./...` from repo root, then from `examples/hello` once created |
|
| Quick run | `go vet ./... && go test ./...` from repo root, then from `examples/hello` once created |
|
||||||
| Full suite | Both modules: `go vet ./...`, `go test ./...`, `go test -race ./...`; generated binary integration |
|
| Full suite | Framework, hello app and three hello plugin modules: `go vet ./...`, `go test ./...`, `go test -race ./...`; generated binary integration |
|
||||||
| Estimated runtime | Measure after dependencies are cached; no assumed threshold |
|
| Estimated runtime | Measure after dependencies are cached; no assumed threshold |
|
||||||
|
|
||||||
## Sampling Rate
|
## Sampling Rate
|
||||||
|
|
||||||
- After each implementation task commit, run root `go vet ./... && go test ./...`; run the same from `examples/hello` after it exists.
|
- After each implementation task commit, run root `go vet ./... && go test ./...`; run the same from `examples/hello` after it exists.
|
||||||
- After each plan wave, run both module checks plus the relevant built-binary smoke.
|
- After each plan wave, run both module checks plus the relevant built-binary smoke.
|
||||||
- Before verify-work, run race tests in both modules and an end-to-end `summer build`/hello invocation.
|
- Before verify-work, run race tests in all five modules and an end-to-end `summer build`/hello invocation.
|
||||||
- Record actual feedback latency and single-plugin rebuild latency; no fixed performance promise has been set.
|
- Record actual feedback latency and single-plugin rebuild latency; no fixed performance promise has been set.
|
||||||
|
|
||||||
## Per-Requirement Verification Map
|
## Per-Requirement Verification Map
|
||||||
@@ -41,6 +41,21 @@ created: 2026-09-16
|
|||||||
| KERN-09 | integration | Temp app source edit triggers one debounced rebuild and restart, latency line captured | Pending |
|
| KERN-09 | integration | Temp app source edit triggers one debounced rebuild and restart, latency line captured | Pending |
|
||||||
| CLI-01 | unit + integration | Plugin command discovery, injected output, non-TTY spinner/progress/table/prompt behavior | Pending |
|
| CLI-01 | unit + integration | Plugin command discovery, injected output, non-TTY spinner/progress/table/prompt behavior | Pending |
|
||||||
|
|
||||||
|
## Per-Task Verification Map
|
||||||
|
|
||||||
|
| Task ID | Plan | Wave | Requirements | Automated check | Status |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| 01-01-01 | 01 | 1 | KERN-01/02/03/04, CLI-01 | Root and hello `go vet ./...` + `go test ./...` | Pending |
|
||||||
|
| 01-01-02 | 01 | 1 | KERN-04, CLI-01 | Same checks plus `summer build` and built hello command | Pending |
|
||||||
|
| 01-02-01 | 02 | 2 | KERN-01/03 | `go test ./compass ./pact`; hello tests | Pending |
|
||||||
|
| 01-02-02 | 02 | 2 | KERN-02/03/05/08 | `go test ./party ./backpack ./pact`; hello tests | Pending |
|
||||||
|
| 01-02-03 | 02 | 2 | KERN-06/07 | `go test ./festival ./towel ./backpack`; hello tests | Pending |
|
||||||
|
| 01-03-01 | 03 | 3 | KERN-04 | `go test ./internal/build ./cmd/summer`; hello tests | Pending |
|
||||||
|
| 01-03-02 | 03 | 3 | CLI-01 | `go test ./bonfire ./cmd/summer`; hello tests | Pending |
|
||||||
|
| 01-03-03 | 03 | 3 | KERN-09 | `go test ./internal/dev ./internal/build ./cmd/summer`; hello tests | Pending |
|
||||||
|
| 01-04-01 | 04 | 4 | KERN-01/02/03/05/06/07/08 | `go test -race ./compass ./party ./backpack ./festival ./towel ./pact` | Pending |
|
||||||
|
| 01-04-02 | 04 | 4 | KERN-04/09, CLI-01 | `bash scripts/check-phase1.sh` | Pending |
|
||||||
|
|
||||||
## Wave 0 Requirements
|
## Wave 0 Requirements
|
||||||
|
|
||||||
- [ ] First implementation slice creates `examples/hello`, its `go.mod`, and a root `go.work` that names every example module.
|
- [ ] First implementation slice creates `examples/hello`, its `go.mod`, and a root `go.work` that names every example module.
|
||||||
@@ -58,8 +73,8 @@ created: 2026-09-16
|
|||||||
|
|
||||||
- [ ] Every plan task has an automated verification or an explicit Wave 0 dependency.
|
- [ ] Every plan task has an automated verification or an explicit Wave 0 dependency.
|
||||||
- [ ] No three consecutive implementation tasks lack automated feedback.
|
- [ ] No three consecutive implementation tasks lack automated feedback.
|
||||||
- [ ] Root and nested example modules both run `go vet`, tests and race tests.
|
- [ ] Root, hello app and three plugin modules run `go vet`, tests and race tests.
|
||||||
- [ ] No watch-mode flags in CI commands.
|
- [ ] No watch-mode flags in CI commands.
|
||||||
- [ ] Set `nyquist_compliant: true` after plan-task mapping is complete.
|
- [ ] Set `nyquist_compliant: true` after all mapped checks exist and pass.
|
||||||
|
|
||||||
**Approval:** pending plan verification
|
**Approval:** pending plan verification
|
||||||
|
|||||||
Reference in New Issue
Block a user