---
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
---
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.
@$HOME/.codex/get-shit-done/workflows/execute-plan.md
@$HOME/.codex/get-shit-done/templates/summary.md
@.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
Task 1: Make the hello command observe full layered config
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
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
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.
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/.yaml` base files; sorted `config/env//.yaml` overlays excluding `overrides.yaml`; `SUMMER_` variables split only on `__` so `POSTS_PER_PAGE` remains one snake_case leaf; `config/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.
go test ./compass ./pact && (cd examples/hello && go test ./...)
- `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//overrides.yaml`; `Reload` clears Set and re-reads files.
- Untouched keys in nested YAML maps survive later overlays.
The hello app reads exactly the D-09 priority order through typed and dot-path config access.
Task 2: Let a hello plugin use optional services without a hard import
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
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
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.
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.
go test ./party ./backpack ./pact && (cd examples/hello && go test ./...)
- 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.
The hello app demonstrates optional plugin composition and deterministic failure handling.
Task 3: Dispatch typed hello events with request context
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
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
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.
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.
go test ./festival ./towel ./backpack && (cd examples/hello && go test ./...)
- 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.
The hello path demonstrates all three event modes and context-scoped request state.
## 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/` 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. |
- 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.
- 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.