--- phase: 01-framework-kernel-foundation plan: 02 subsystem: kernel tags: [go, koanf, config, plugins, events, context, services] requires: - phase: 01-framework-kernel-foundation provides: Hello app boot path, party.Activate, compass.Load, HasConfig/HasCommands provides: - Six-level compass config with Set/Persist/Reload - Optional plugin composition via HasPlugin and typed backpack services - Typed festival event bus with Fire/Collect/UntilHandled - towel request-context accessors affects: [01-03, 01-04, 03] tech-stack: added: - github.com/knadh/koanf/providers/file v1.2.1 - github.com/knadh/koanf/providers/confmap v1.0.1 - github.com/knadh/koanf/providers/env/v2 v2.0.1 patterns: - Config priority: runtime Set, persisted overrides, SUMMER_ env, env YAML, base YAML, plugin defaults - Optional integration without importing the optional package (HasPlugin + pact interface) - App-owned service registry and event bus; no process-global request state key-files: created: - compass/env.go - compass/persist.go - backpack/services.go - festival/bus.go - towel/context.go - examples/hello/plugins/optional/plugin.go modified: - compass/config.go - party/registry.go - backpack/app.go - pact/capabilities.go - examples/hello/plugins/greeter/plugin.go - examples/hello/summer.yaml key-decisions: - "Runtime Set lives in a separate koanf tree so Reload drops it and rebuilds disk layers" - "pact.OptionalMessage is the shared service interface; greeter never imports the optional module" - "Collect/UntilHandled read Collectable/Handleable on the event so Listen stays func(ctx, T) error" - "HasPlugin uses the full activated ID set recorded before Register, not the Booted subset" - "Panic recovery names the owner plugin ID and omits event payload (T-01-05)" patterns-established: - "Pattern: plugin embed.FS config/config.yaml merged at the bare plugin ID via MergeAt" - "Pattern: SUMMER_ keys split only on __; single underscores stay in the leaf" - "Pattern: Publish in Register, Lookup in Boot so optional services are visible regardless of Boot order" requirements-completed: [KERN-01, KERN-02, KERN-03, KERN-05, KERN-06, KERN-07, KERN-08] duration: 25min completed: 2026-09-16 --- # Phase 1 Plan 2: Layered Config, Services and Events Summary **Six-level koanf config with Set/Persist/Reload, optional plugin services without a hard import, and typed festival dispatch with context-scoped request state** ## Performance - **Duration:** 25 min - **Started:** 2026-09-16T11:06:17Z - **Completed:** 2026-09-16T11:31:20Z - **Tasks:** 3 - **Files modified:** 40 ## Accomplishments - Compass loads plugin defaults, sorted base YAML, env overlays, `SUMMER_` variables (split on `__`), persisted overrides and runtime Set in D-09 order; Persist is atomic with `0600` files; Reload clears Set - `golem15.optional` is a third hello plugin that publishes `pact.OptionalMessage`; greeter uses `HasPlugin` plus typed lookup and boots with or without it - App-owned `festival.Bus` implements Fire (join errors), Collect (later key wins, partial payload) and UntilHandled, recovers panics with owner IDs, and `towel` carries actor/org/collection/locale on `context.Context` ## Task Commits Each task was committed atomically (TDD RED then GREEN): 1. **Task 1 RED: failing layered config tests** - `255de60` (test) 2. **Task 1 GREEN: layered compass config** - `cc44a1f` (feat) 3. **Task 2 RED: failing optional service tests** - `5f22918` (test) 4. **Task 2 GREEN: optional plugin services and HasPlugin** - `8696973` (feat) 5. **Task 3 RED: failing event and context tests** - `36c152f` (test) 6. **Task 3 GREEN: typed events with request context** - `a654e96` (feat) **Plan metadata:** pending (docs: complete plan) ## Files Created/Modified - `compass/config.go` / `env.go` / `persist.go` - layered load, env mapping, `.env` fill, Set/Persist/Reload - `party/registry.go` - HasConfig merge and SetPlugins before Register - `backpack/app.go` / `services.go` - HasPlugin, typed Publish/Lookup, app-owned Events bus - `pact/capabilities.go` - OptionalMessage plus documented future KERN-03 families - `festival/bus.go` - typed Listen/Fire/Collect/UntilHandled - `towel/context.go` - unexported-key request accessors - `examples/hello/plugins/optional/` - optional plugin module - `examples/hello/plugins/greeter/plugin.go` - optional lookup and hello event demo - `examples/hello/summer.yaml` / `plugins.gen.go` / `go.work` - third plugin wired ## Decisions Made - Keep runtime Set off the disk tree so Reload is a full rebuild plus an empty overlay - Put the optional service interface in `pact`, not in the optional plugin package - Model Collect/UntilHandled as event interfaces (`Collected`, `IsHandled`) so one `Listen[T]` serves all three modes - Record activated plugin IDs on the app before Register so HasPlugin sees not-yet-Booted plugins - Recover panics as `festival: plugin panicked` without event contents ## Deviations from Plan ### Auto-fixed Issues **1. [Rule 2 - Missing Critical] Restore toolchain go1.27.0 after go mod tidy** - **Found during:** Task 1 (provider install) - **Issue:** `go mod tidy` dropped `toolchain go1.27.0` (same as Plan 01) - **Fix:** Re-added the directive on root and example modules - **Files modified:** `go.mod`, example `go.mod` files - **Verification:** files contain `toolchain go1.27.0` - **Committed in:** `cc44a1f` (Task 1 GREEN) **2. [Rule 1 - Bug] Greeter extra leaked across Activate on the init singleton** - **Found during:** Task 2 (hello without-optional test) - **Issue:** `party.Register` stores one plugin instance; Boot with optional set `extra`, later Boot without optional left the old string - **Fix:** Clear `extra` at the start of Boot - **Files modified:** `examples/hello/plugins/greeter/plugin.go` - **Verification:** `TestGreeterRunsWithoutOptionalPlugin` passes - **Committed in:** `8696973` (Task 2 GREEN) **3. [Rule 1 - Bug] Hello manifest test still expected two plugins** - **Found during:** Task 2 (`go test ./...`) - **Issue:** `internal/build.TestLoadHelloManifest` hard-coded plugin count 2 - **Fix:** Expect `golem15.optional` as the third manifest entry - **Files modified:** `internal/build/build_test.go` - **Verification:** `go test ./internal/build` passes - **Committed in:** `8696973` (Task 2 GREEN) --- **Total deviations:** 3 auto-fixed (1 missing critical, 2 bugs) **Impact on plan:** Required for D-05, correct optional skip, and a green suite. No scope creep. ## Issues Encountered - `go get` of koanf file/env/confmap providers pulled `fsnotify` transitively through the file provider; it is indirect only and unused by compass (no Watch). Plan 03 still owns `summer dev`. ## Authentication Gates None. ## User Setup Required None - no external service configuration required. ## Next Phase Readiness Ready for `01-03-PLAN.md` (watch loop and CLI UX). Config, optional composition, services and events are in place on the hello path. Do not add HTTP/DB/auth here. ## Self-Check: PASSED - Created files exist on disk (`compass/env.go`, `compass/persist.go`, `backpack/services.go`, `festival/bus.go`, `towel/context.go`, optional plugin) - Commits `255de60`, `cc44a1f`, `5f22918`, `8696973`, `36c152f`, `a654e96` exist - Root and hello `go vet ./...` and `go test ./...` passed - `go test -race ./compass ./party ./backpack ./festival ./towel` passed - `go run . greeter:hello` prints layered config, optional extra, and event results - Greeter source has no import of the optional plugin module - STATE.md and ROADMAP.md were not updated in this worktree --- *Phase: 01-framework-kernel-foundation* *Completed: 2026-09-16*