From 278aa1831bd8b5d67066792d379f8500de74eedb Mon Sep 17 00:00:00 2001 From: Jakub Zych Date: Wed, 16 Sep 2026 12:03:21 +0200 Subject: [PATCH] docs(01): capture phase context --- .../01-CONTEXT.md | 119 ++++++++++++ .../01-DISCUSSION-LOG.md | 170 ++++++++++++++++++ 2 files changed, 289 insertions(+) create mode 100644 .planning/phases/01-framework-kernel-foundation/01-CONTEXT.md create mode 100644 .planning/phases/01-framework-kernel-foundation/01-DISCUSSION-LOG.md diff --git a/.planning/phases/01-framework-kernel-foundation/01-CONTEXT.md b/.planning/phases/01-framework-kernel-foundation/01-CONTEXT.md new file mode 100644 index 0000000..3955b1c --- /dev/null +++ b/.planning/phases/01-framework-kernel-foundation/01-CONTEXT.md @@ -0,0 +1,119 @@ +# 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. +- 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* diff --git a/.planning/phases/01-framework-kernel-foundation/01-DISCUSSION-LOG.md b/.planning/phases/01-framework-kernel-foundation/01-DISCUSSION-LOG.md new file mode 100644 index 0000000..1402ef6 --- /dev/null +++ b/.planning/phases/01-framework-kernel-foundation/01-DISCUSSION-LOG.md @@ -0,0 +1,170 @@ +# Phase 1: Framework kernel foundation - Discussion Log + +> **Audit trail only.** Do not use as input to planning, research, or execution agents. +> Decisions are captured in CONTEXT.md — this log preserves the alternatives considered. + +**Date:** 2026-09-16 +**Phase:** 01-framework-kernel-foundation +**Areas discussed:** Binary and repo layout, Config layout and keys, Plugin lifecycle & events, CLI and dev loop + +--- + +## Binary and repo layout + +| Option | Description | Selected | +|--------|-------------|----------| +| Two binaries, xcaddy-style | `summer` tool (make:plugin, plugin:add, build, dev) generates main.go + plugins.gen.go in the app repo; app binary embeds runtime and plugin commands | ✓ | +| One binary per app, named summer | Each app builds its own `summer` in cmd/summer; chicken-and-egg bootstrap | | +| One binary, framework ships it | Framework ships the runnable binary, apps are plugin sets linked in | | + +**User's choice:** Two binaries, xcaddy-style + +| Option | Description | Selected | +|--------|-------------|----------| +| examples/hello app, kept permanently | Tiny real app in the framework repo, go.work-linked, two or three trivial plugins; build/dev target and make:plugin template | ✓ | +| internal/testplugin fixtures only | Throwaway plugins only in test packages | | +| Bootstrap fonoteka.go now | Start the app repo in Phase 1 | | + +**User's choice:** examples/hello app, kept permanently + +| Option | Description | Selected | +|--------|-------------|----------| +| git.golem15.com/golem15/summercms | Keep go.mod as is; research docs corrected | ✓ | +| github.com/golem15/summercms | Switch go.mod to GitHub | | + +**User's choice:** git.golem15.com/golem15/summercms +**Notes:** User added the origin remote and pushed the current master during the discussion. + +| Option | Description | Selected | +|--------|-------------|----------| +| Shared bonfire command set, two entry points | Framework exposes a kernel root command; tool and generated app main both call it | ✓ | +| Tool and runtime fully separate | Runtime commands in a separate package assembled by each app main | | + +**User's choice:** Shared bonfire command set, two entry points + +| Option | Description | Selected | +|--------|-------------|----------| +| Pin toolchain go1.27.x in go.mod | Same compiler everywhere; generated app go.mod copies it | ✓ | +| Leave floating | Only the minimum go directive | | + +**User's choice:** Pin toolchain go1.27.x in go.mod + +--- + +## Config layout and keys + +| Option | Description | Selected | +|--------|-------------|----------| +| Bare plugin ID: golem15.fonoteka.key | Compass design, WinterCMS-like | ✓ | +| Prefixed: plugins.golem15.fonoteka.key | STACK.md confmap sketch | | + +**User's choice:** Bare plugin ID + +| Option | Description | Selected | +|--------|-------------|----------| +| Compass layout: config/
.yaml + config/env// | Filename becomes section key; plugins ship config/*.yaml | ✓ | +| Single file plus overlays | base.yaml + .yaml | | + +**User's choice:** Compass layout + +| Option | Description | Selected | +|--------|-------------|----------| +| SUMMER_ prefix, __ for dots, .env loaded when present | Automatic mapping, .env never overrides real env | ✓ | +| Explicit ${VAR} references in YAML | Laravel-like declared references | | +| Both | Explicit references plus automatic mapping | | + +**User's choice:** SUMMER_ prefix, __ for dots, .env loaded when present +**Notes:** User first asked "is __ commonly used in Go?". Answer given: single underscore is more common in Go (koanf docs, viper replacer) but ambiguous with snake_case keys; `__` is the cross-ecosystem convention (ASP.NET Core, Rust config crate, dynaconf, nconf); explicit references is the Laravel model. User then chose `__`. + +| Option | Description | Selected | +|--------|-------------|----------| +| Drop persist, keep in-memory Set | Persistence deferred to the admin phase | | +| Keep full set/persist/reload | Complete carry-over of the compass surface | ✓ | +| No runtime overrides at all | Immutable config after boot | | + +**User's choice:** Keep full set/persist/reload + +--- + +## Plugin lifecycle & events + +| Option | Description | Selected | +|--------|-------------|----------| +| Fail boot with a clear error | Missing or cyclic Requires exits non-zero | ✓ | +| Warn and skip the dependent plugin | WinterCMS-style disable and continue | | + +**User's choice:** Fail boot with a clear error + +| Option | Description | Selected | +|--------|-------------|----------| +| Both: HasPlugin(id) and typed service lookup | Coarse ID check plus (value, ok) interface lookup via pact | ✓ | +| Service lookup only | Only interface resolution | | +| HasPlugin(id) only | ID check plus hard import | | + +**User's choice:** Both + +| Option | Description | Selected | +|--------|-------------|----------| +| Optional priority, default 0, registration order within ties | Listen and a priority variant | ✓ | +| Registration order only | Boot order decides | | + +**User's choice:** Optional priority + +| Option | Description | Selected | +|--------|-------------|----------| +| Errors collected, panics recovered and converted | Joined errors for forget/collect, first error stops until-handled | ✓ | +| First error aborts every mode | Any error stops dispatch | | +| Listeners cannot return errors | Log-only listeners | | + +**User's choice:** Errors collected, panics recovered and converted + +--- + +## CLI and dev loop + +| Option | Description | Selected | +|--------|-------------|----------| +| Hand-rolled widgets, stdlib plus x/term | Port bonfire widget designs; full control of degradation | ✓ | +| charmbracelet lipgloss + huh + bubbles | Charm stack | | +| pterm | Single library with fallbacks | | + +**User's choice:** Hand-rolled widgets + +| Option | Description | Selected | +|--------|-------------|----------| +| Colon style: summer fonoteka:reindex | WinterCMS/Artisan spelling | ✓ | +| Nested subcommands: summer fonoteka reindex | Idiomatic cobra tree | | + +**User's choice:** Colon style + +| Option | Description | Selected | +|--------|-------------|----------| +| Built-in summer dev using fsnotify | Watch, rebuild, restart, print latency | ✓ | +| External air with generated .air.toml | Shell out to air | | +| Both | Built-in default with --air | | + +**User's choice:** Built-in summer dev using fsnotify + +| Option | Description | Selected | +|--------|-------------|----------| +| Bonfire Command interface wrapped into cobra | Injected Input/Output, cobra adapter | ✓ | +| Plain cobra commands | Plugins return []*cobra.Command | | + +**User's choice:** Bonfire Command interface wrapped into cobra + +--- + +## Claude's Discretion + +- Typed struct events, synchronous dispatch; bus and registry live on the app instance +- Plugin ID casing (lowercase `vendor.plugin`), `SUMMER_ENV` default `production` +- Typed section loading via koanf Unmarshal; compass-like dot-path getters +- Ordered manifest as the source for `plugins.gen.go`; build time printed by `summer build` +- Minimum `make:plugin` scaffold in Phase 1; package layout per ARCHITECTURE.md + +## Deferred Ideas + +- Full `make:*` scaffolding (model, migration, command, job, admin controller) — Phase 4 +- Admin config editor on top of `Persist` — later admin phase +- Stack plugin extraction to own repos — when keios.eu needs one +- Research doc corrections (module path, air, plugins. sketch) — docs commit