# Phase 1: Framework kernel foundation - Context **Gathered:** 2026-09-16 **Status:** Ready for planning ## Phase Boundary Phase 1 delivers the framework kernel in `summercms.go` only: config (`compass`), plugin descriptor and registry with the generated import list and `summer build` (`party`), typed event bus (`festival`), app container and service registry (`backpack`), console command framework with rich output (`bonfire`), and a dev watch loop. It is built only as far as Phase 3's `GET /_fonoteka/api/v1/genres` slice will need, per the interleaved-not-sequential rule. No HTTP router (`surf`), no ORM (`lagoon`), no auth (`bouncer`), no i18n (`phrasebook`) and no scaffolding beyond what `summer build` and the `examples/hello` testbed require. Requirements: KERN-01 through KERN-09 and CLI-01. ## Implementation Decisions ### Binary and repo layout - **D-01:** Two binaries, xcaddy-style. `summer` is a framework tool installed once (`go install git.golem15.com/golem15/summercms/cmd/summer`) providing `make:plugin`, `plugin:add`, `build` and `dev`. It generates `main.go` and `plugins.gen.go` in an app repo and runs `go build` there. The app binary (for example `fonoteka`) embeds the framework's runtime commands plus plugin commands. The framework never links an app. - **D-02:** One command kernel, two entry points. The framework exposes a `bonfire` kernel function that builds the root cobra command with runtime commands (`serve`, `migrate:*`, `queue:work` as later phases add them). The `summer` tool wraps that kernel plus the build and scaffold commands; the generated app `main.go` calls the same kernel. No duplicated command wiring. - **D-03:** The Phase 1 testbed is `examples/hello`, kept permanently: a tiny real app inside the framework repo with its own `go.mod`, linked through a root `go.work`, containing two or three trivial plugins (enough to prove Register-before-Boot, `Requires()` ordering and an optional capability interface). `summer build` and `summer dev` are exercised against it, it is the template source for `make:plugin`, and integration tests run it. - **D-04:** Canonical module path is `git.golem15.com/golem15/summercms` (go.mod as committed; origin added and master pushed). Research docs that say `github.com/golem15/summercms` get a one-line correction. Apps require this path and use `go.work` or a local `replace` during development. - **D-05:** Pin the toolchain: add `toolchain go1.27.x` to go.mod so every developer and agent builds with the same compiler. Generated app `go.mod` files copy the same line. ### Config layout and keys - **D-06:** Plugin config keys use the bare plugin ID as namespace: `golem15.fonoteka.posts_per_page`. App-level sections (`app`, `database`) stay top-level. Rule: a top-level key containing a dot-separated vendor prefix is a plugin namespace. This follows the summer-compass design and reads like WinterCMS's `golem15.fonoteka::key`; STACK.md's `plugins..*` sketch is superseded. - **D-07:** File layout follows summer-compass: `config/
.yaml` for base config, `config/env//
.yaml` for environment overlays, filename becomes the section key. Plugins ship `config/*.yaml` inside their module (embedded FS via the `HasConfig` capability) and are loaded under their namespace. Deep merge, not top-level replace. - **D-08:** Environment variables use the `SUMMER_` prefix with `__` as the path separator: `SUMMER_DATABASE__HOST` maps to `database.host`, `SUMMER_GOLEM15__FONOTEKA__POSTS_PER_PAGE` maps to `golem15.fonoteka.posts_per_page`. Single underscores stay literal so snake_case leaf keys are unambiguous. A `.env` file in the app root is loaded when present and never overrides variables already set in the real environment. - **D-09:** The full compass override surface is carried over: in-memory `Set` (highest priority), `Persist` writing `config/env//overrides.yaml`, and `Reload` that clears runtime overrides and re-reads disk. Priority, highest wins: runtime Set, persisted overrides, env vars, env overlay file, base file, plugin defaults. ### Plugin lifecycle and events - **D-10:** A plugin whose `Requires()` names an unregistered plugin fails boot with an error naming both the plugin and the missing dependency; the binary exits non-zero. Cycles in `Requires()` fail the same way. No warn-and-disable behavior. - **D-11:** Optional integration (KERN-05) has two mechanisms, both free of imports of the optional plugin's package: `app.HasPlugin("golem15.translate")` for coarse checks mirroring PHP `class_exists` guards, and a typed lookup on the `backpack` container returning `(value, ok)` for interface-based integration where the optional plugin publishes a service under a `pact` interface. - **D-12:** Event listeners have an optional integer priority, default 0. Higher priority runs first; ties keep registration order (stable). Both a plain `Listen` and a priority-taking variant exist. This makes fire-until-handled deterministic and lets ported PHP listeners keep their priorities. - **D-13:** Listener error handling per dispatch mode. Fire-and-forget: run every listener, return the joined errors (`errors.Join`). Fire-and-collect: run every listener, return the partial merged payload together with the joined error. Fire-until-handled: the first error stops dispatch and is returned. Panics inside a listener are recovered and wrapped as an error carrying the listener's owning plugin ID. ### CLI and dev loop - **D-14:** Rich output widgets are hand-rolled on the standard library plus `golang.org/x/term` for TTY detection and raw mode: braille spinner, gradient progress bar, box-drawing table, ask/confirm/choice/secret prompts, with the non-TTY degradation table from the bonfire plan (static `[...]` line, `[N/M] pct%` updates, tab-separated tables, stdin prompts, `NO_COLOR`/`FORCE_COLOR`/`TERM=dumb` respected). No Charm stack, no pterm. - **D-15:** Command names are colon-style like WinterCMS/Artisan: `summer fonoteka:reindex`, `summer make:plugin`, `summer migrate:rollback`. Plugin commands must carry a namespace prefix; kernel commands use their group prefix. cobra accepts colons in `Use`. - **D-16:** The dev watch loop is built into the tool as `summer dev` using `fsnotify`: watch the app workspace (Go sources, config, plugin manifests), run the same build step as `summer build`, restart the app binary with a debounce, and print the measured rebuild latency after every cycle. No dependency on an external `air` install; STACK.md's air recommendation is superseded for this purpose. - **D-17:** Plugins implement a `bonfire.Command` interface (name, description, flag and argument definition, `Run(ctx, Input, Output) error`). The kernel wraps each into a `cobra.Command`. Output is an injected value so tests capture it and non-TTY degradation is decided once, not per command. ### Claude's Discretion - Event identity: typed Go struct events (generic `Listen[T]`), as sketched in ARCHITECTURE.md Pattern 3a; no string-named events. Dispatch is synchronous on the caller's goroutine; async fan-out is a listener's own concern. - The event bus and the service registry are fields of the app instance, not package globals, so tests build isolated apps. Only `party`'s `init()` self-registration uses a package-level ordered slice, drained into the app at boot. - Plugin IDs are lowercase `vendor.plugin` (`golem15.fonoteka`); matching is exact. - Environment detection reads `SUMMER_ENV`, defaulting to `production` as compass did; an explicit constructor argument overrides it. - Typed section loading uses koanf's `Unmarshal` into a struct with a `koanf` tag; dot-path getters mirror the compass API (`String`, `Int`, `Bool`, `Has`, plus a `Lookup` returning ok). - `plugins.gen.go` is regenerated from an explicit ordered manifest (a `summer.yaml` or the `go.work` use list), never from a map, per the init-order pitfall. `summer build` measures and prints build time. - What `make:plugin` scaffolds in Phase 1: the minimum that compiles and registers (go.mod, plugin.go with the required interface, an empty config dir). Model, migration, controller and command stubs belong to Phase 4 (CLI-02). - Package layout follows ARCHITECTURE.md's list (`pact`, `towel`, `compass`, `festival`, `backpack`, `party`, `bonfire`, `cmd/summer`, `examples/hello`); `internal/` is used for build codegen helpers not meant as public API. ## Canonical References **Downstream agents MUST read these before planning or implementing.** ### Kernel architecture and pitfalls - `.planning/research/ARCHITECTURE.md` §Component Responsibilities, §Recommended Project Structure, §Pattern 1, §Pattern 2, §Pattern 3, §Anti-Patterns, §Framework vs Application Repo Boundary, §Suggested Build Order — plugin interface sketch, capability interfaces, generated import list, container usage rules, package names. Note: it says `github.com/golem15/summercms` and `cmd/summer` lives in the app repo; D-01, D-02 and D-04 supersede those two points. - `.planning/research/PITFALLS.md` §Pitfall 1 (bottom-up kernel), §Pitfall 2 (globals vs context), §Init-order plugin registration nondeterminism, §Dev rebuild loop friction, §Pitfall-to-Phase Mapping — the tests and acceptance criteria Phase 1 must include (`go test -race`, reordered-input test, measured rebuild latency). - `.planning/research/SUMMARY.md` §Architecture Approach, §Phase 1, §Research Flags — kernel phase is flagged as safe to plan without deep research. - `.planning/research/STACK.md` §Core Technologies (koanf, cobra, goccy/go-yaml versions), §Config: koanf + YAML — the `plugins..*` sketch there is superseded by D-06; air for the watch loop is superseded by D-16. - `.planning/research/go-ecosystem.md` §2 Plugin architecture — the Caddy/xcaddy verdict and the hand-rolled event bus verdict. ### Design carry-overs from the Scala modules - `../modules/summer-compass/README.md` — config file layout, environment layering priority, plugin namespaces, set/persist/reload API and thread-safety notes; port the design, not the code. - `../modules/summer-bonfire/README.md` and `../modules/summer-bonfire/PLAN.md` §Input trait, §Output trait, §Command trait, §CommandRunner, §Non-TTY Graceful Degradation — the command and output interfaces and the degradation table to port. - `../IDEA_LIB_NAMES.md` — Go package names (backpack, compass, festival, party, bonfire, pact, towel). ### Project-level decisions - `.planning/PROJECT.md` §Constraints, §Key Decisions — stdlib-first rule, two-repo split, compiled plugins only. - `.planning/REQUIREMENTS.md` KERN-01..KERN-09, CLI-01 — the phase's requirement text. - `.planning/notes/why-go-not-scala.md` — the bottom-up failure mode this phase must not repeat. - `CLAUDE.md` (repo root) §GSD workflow rules — lean planning, plan-count checkpoint, unit tests as the last plan, `go vet` and `go test ./...` green at every commit. ## Existing Code Insights ### Reusable Assets - No Go code exists yet; the repo holds `go.mod` (module `git.golem15.com/golem15/summercms`, `go 1.27.0`), `README.md`, `CLAUDE.md` and `.planning/`. Everything in Phase 1 is greenfield. - Design assets to port: summer-compass (config layering, namespaces, overrides) and summer-bonfire (command interfaces, widgets, degradation table). Their READMEs are the spec; their Scala code is not ported. ### Established Patterns - Stdlib-first: new dependencies in Phase 1 are limited to koanf v2 with its file, env and confmap providers and yaml parser, goccy/go-yaml, cobra, golang.org/x/term, fsnotify, and testify for tests. Anything else needs a decision note. - Request state travels only in `context.Context` with unexported struct key types and exported accessor functions. - Container used only at Register and Boot for constructor injection; application code holds typed fields. ### Integration Points - `examples/hello` is the first consumer of every kernel package and the target of `summer build` and `summer dev`. - Phase 3 will add `surf`, `lagoon` and `bouncer` on top of `backpack` and `party`; the capability interfaces they need (`HasRoutes`, `HasModels`, `HasMigrations`) may be declared in `pact` now but must not be implemented before Phase 3 pulls them. - Phase 2's parity harness has no dependency on Phase 1 and runs in parallel. ## Specific Ideas - "It should feel like WinterCMS's drop-in loop": `summer dev` prints rebuild latency every cycle so the friction pitfall is visible, and the phase's acceptance includes a measured rebuild time for a single-plugin change in `examples/hello`. - Command spelling must let ported PHP commands keep their names (`fonoteka:reindex`, `fonoteka:oauth-client`). - The user asked whether `__` is common in Go; answer recorded: single underscore is more common in Go libraries but ambiguous with snake_case keys, `__` is the cross-ecosystem convention (ASP.NET Core, Rust config, dynaconf) and was chosen deliberately. ## Deferred Ideas - Scaffolding of model, migration, command, job and admin-controller stubs by `make:*` commands — Phase 4 (CLI-02). Phase 1's `make:plugin` produces only the compiling minimum. - Admin config editor consuming `Persist` overrides — later admin phase; Phase 1 only provides the API. - Extraction of shared stack plugins into their own repos — only when a second app (keios.eu) needs one. Superseded 2026-10-02 by `.planning/notes/core-plugins-own-repos.md` (golem15.user moved to sm-user-plugin). - One-line corrections to research docs (module path, air, `plugins.` sketch) — do as a docs commit alongside Phase 1 planning, not as code. --- *Phase: 01-framework-kernel-foundation* *Context gathered: 2026-09-16*