Files
summercms/.planning/phases/01-framework-kernel-foundation/01-RESEARCH.md
2026-09-16 12:30:05 +02:00

14 KiB

Phase 1: Framework kernel foundation — Research

Researched: 2026-09-16 Domain: Go 1.27 compiled plugin kernel, layered configuration, CLI and watch loop Confidence: High for documented library behavior; medium for proposed API shapes until the examples/hello integration proves them.

<user_constraints>

User Constraints

The locked decisions are D-01 through D-17 in 01-CONTEXT.md. In particular: two binaries and one bonfire command kernel; canonical module path git.golem15.com/golem15/summercms; root go.work plus permanent examples/hello; bare plugin-ID config namespaces; the exact six-level config priority; required Plugin plus optional capability interfaces; deterministic lifecycle and event dispatch; hand-rolled output; fsnotify watch loop. The framework repository must not import Płytarium or grow the Phase 3 HTTP/ORM/auth stack.

The context leaves the exact event and service APIs, file manifest format, and package dependency direction to implementation. Defer model/migration/controller scaffolding, admin config editing, shared-plugin extraction, and all surf/lagoon/bouncer implementation. </user_constraints>

<architectural_responsibility_map>

Architectural Responsibility Map

Single-tier CLI and backend framework. cmd/summer owns tool entry, generated app main.go owns runtime entry, party owns plugin ordering, backpack owns app-scoped services, compass owns configuration, festival owns events, and bonfire owns commands and terminal output. The generated app binary is the integration boundary. </architectural_responsibility_map>

<research_summary>

Summary

Build the first executable path through examples/hello: explicit summer.yaml manifest → generated plugins.gen.go → app main → config load → all plugin Register calls → all Boot calls → a namespaced plugin command. Extend that path with configuration precedence, optional service integration, event dispatch, and summer dev; do not build unused framework layers first. The source-of-truth design is the phase context, with project research supplying examples and pitfalls.

go.work can develop multiple modules together, but a root go test ./... does not by itself prove nested example modules. Plans need separate go test/go vet checks from examples/hello and a built-binary invocation. Go's toolchain directive needs a concrete release, so toolchain go1.27.0 matches the installed go1.27.0 and fulfills D-05 without an invalid wildcard. The go/toolchain directive behavior is documented by Go, and workspace module membership by the Go workspace tutorial.

Primary recommendation: Keep every package API driven by a real hello plugin and its CLI path, then verify the kernel with reordered plugin input, config precedence, non-TTY output, generated code stability, and a measured watch rebuild. </research_summary>

<standard_stack>

Standard Stack

Component Phase 1 choice Use
Compiler/workspace Go 1.27.0, go.work Root framework plus separate hello app/plugin modules.
Config github.com/knadh/koanf/v2, file/confmap/env v2 providers, koanf YAML parser Ordered deep merges, typed section decode, dot paths.
CLI github.com/spf13/cobra One root command constructor shared by tool and app.
Terminal golang.org/x/term plus standard library TTY detection, password prompt, simple widgets.
Watch github.com/fsnotify/fsnotify Observe app workspace directories and rebuild.
Tests go test, go test -race, go vet; testify only if helpful Unit and example integration coverage.

The context authorizes these dependencies. Use checked module tags and commit go.sum; do not use @latest in repeatable plan commands. The koanf docs confirm successive Load calls merge into the existing tree. The current env/v2 provider has Opt{Prefix, TransformFunc, EnvironFunc} and can inject an environment source in tests. The earlier project STACK.md's unversioned env-provider example is a sketch, not a required import path.

Package Legitimacy Audit

All Phase 1 external packages below have live package pages and tagged module versions as of 2026-09-16; none is assumed. These are verified available tags, not a claim that no newer tag exists.

Module Checked tag Primary source
github.com/knadh/koanf/v2 v2.3.6 pkg.go.dev
github.com/knadh/koanf/providers/file v1.2.1 pkg.go.dev
github.com/knadh/koanf/providers/confmap v1.0.1 pkg.go.dev
github.com/knadh/koanf/providers/env/v2 v2.0.1 pkg.go.dev
github.com/knadh/koanf/parsers/yaml v1.1.1 pkg.go.dev
github.com/spf13/cobra v1.10.2 pkg.go.dev
github.com/fsnotify/fsnotify v1.10.1 pkg.go.dev
golang.org/x/term v0.46.0 pkg.go.dev
</standard_stack>

<architecture_patterns>

Architecture Patterns

summer.yaml → summer build → generated imports + app main → go build → hello binary
                                                          ↓
                                            config → Register all → Boot all
                                                          ↓
                                         bonfire root → plugin command → Output
summer dev → fsnotify/debounce → same build function → restart hello binary

Package boundaries

  • pact holds neutral plugin and capability contracts. If Plugin.Register/Boot take an app type, define a narrow app-facing interface in pact or place the contract in party so pact → backpack → pact cannot occur. backpack implements the interface; party orchestrates it.
  • backpack.App owns config, services and event bus per instance. Typed lookup may be a generic method on a concrete type with Go 1.27, or a generic helper; no package-global service state.
  • festival.Bus uses typed struct event identity, synchronous listeners, priority descending with stable registration order, and a snapshot of listeners at dispatch. Define collision behavior for collect payloads before implementation (recommended: later invoked listener wins for duplicate keys), then test it.
  • party may have an init() registration slice as the sole allowed process-global registry. Copy it into each app, reject duplicate IDs, validate Requires() against the complete set, topologically order with deterministic tie-breaking, then run every Register before any Boot. Return missing/cycle errors with plugin IDs.
  • compass builds a fresh tree on Reload. Load plugin defaults → sorted base section files → sorted env section files → env variables → persisted overrides → runtime Set. Keep runtime overrides separate so Reload clears them; protect live reads/writes with a lock or immutable snapshot swap. Decode YAML under logical golem15.fonoteka.* paths consistently with SUMMER_GOLEM15__FONOTEKA__*.
  • bonfire constructs cobra commands with injected Input and Output, sets writers explicitly and executes with context. Cobra supports AddCommand, ExecuteContext, SetOut, and SetErr. One kernel factory avoids separate tool/app wiring.

Watch loop

fsnotify does not recursively watch subdirectories and recommends watching parent directories rather than individual files because editors replace files atomically. Walk the workspace initially, add new directories on Create, ignore generated binaries/.git, debounce bursts, terminate/reap the old process, rebuild through the summer build function, and start only a successful new binary. Drain both Events and Errors. See the fsnotify README.

MVP boundary

The roadmap marks Phase 1 mvp, and the generic GSD Walking Skeleton recipe would include DB/UI. That conflicts with the explicit Phase 1 boundary in CONTEXT.md, which excludes ORM, router, and UI until Phase 3. For this phase the real user is a framework developer: the vertical proof is a generated, bootable hello binary with a working plugin command and live rebuild. SKELETON.md should record this phase-specific boundary and hand the DB/HTTP/UI slice to Phase 3. </architecture_patterns>

<common_pitfalls>

Common Pitfalls

Risk Detection and prevention
Import cycles from optional capability signatures Compile the minimal hello plugin before adding more capabilities; keep future adapters out of pact imports.
Non-deterministic generated imports or lifecycle Manifest is an ordered list; regenerate idempotently; use explicit tie-breaking and reordered-input test.
Env key ambiguity and .env clobbering Split only on __, preserve single underscores, lowercase path segments; do not overwrite os.LookupEnv values.
Config merge semantics hidden by filenames Sort files, validate duplicate sections and malformed YAML, test all six priority layers including persisted overrides and Reload.
Nested modules skipped by root checks Run checks in both root and examples/hello; execute built app binary.
Watcher churn or stale process Test create/rename/write bursts, rebuild failure, restart, cancellation, and measured rebuild latency.
Terminal tests hang in CI Inject streams and terminal capability; test non-TTY tables, progress, spinner and prompts with pipes. x/term supplies terminal detection and password/raw-mode primitives.
Phase 1 grows into a generic CMS Require each kernel API to have a hello consumer or an explicit Phase 3 handoff; defer unused adapters.
</common_pitfalls>

<validation_architecture>

Validation Architecture

The phase has no existing Go tests. Execution should create small smoke tests alongside each functional slice and reserve a final, dedicated unit-test plan for complete behavior coverage, per CLAUDE.md. After every implementation task, run go vet ./... && go test ./... at the framework root and, after hello exists, go vet ./... && go test ./... from examples/hello. The last plan adds go test -race ./... for both modules and an integration script/test that runs summer build and the generated hello binary in a temporary workspace. Avoid tests that require an interactive terminal.

Requirement Automated evidence
KERN-01 Table-driven deep-merge, six-layer precedence, env mapping, .env, Set/Persist/Reload, typed decode tests.
KERN-02/03/05/08 Reordered lifecycle input, missing/cycle/duplicate ID, optional interface, HasPlugin and typed service lookup tests.
KERN-04 Stable codegen golden test, plugin:add workspace/manifest test, hello go build and boot smoke.
KERN-06/07 Three dispatch modes, ordering, errors/panics, app isolation, context propagation and race tests.
KERN-09 Temp workspace create/write/rename and restart test, latency line assertion.
CLI-01 Cobra command discovery and output capture, non-TTY widgets/prompts and color policy tests.

The fast feedback command should take seconds once dependencies are cached. CI can measure actual duration rather than assume a fixed latency threshold. Test a single plugin source change with summer dev and record the measured rebuild time; the acceptance criterion asks for a measurement, not an arbitrary pass threshold. </validation_architecture>

<open_questions>

Open Questions (RESOLVED)

  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 — 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 — 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>
## Sources

Phase: 01-framework-kernel-foundation
Ready for planning: yes