Absorb the composition-root Config/BuildLogger bootstrap into toolbelt/app #4

Closed
opened 2026-08-06 16:45:52 +00:00 by ginjiruu · 3 comments
Owner

Question

Should toolbelt/app ship a standard Config-struct + BuildLogger bootstrap so app.go shrinks to feature wiring?

Consumers and the example all hand-write in internal/app/app.go: a Config struct (~16 fields), NewConfig() reading every field via config.ReadFieldString, and a BuildLogger() (~30 lines byte-identical across consumers). This passes the "example proves it" boundary.

Decisions this ticket resolves:

  1. Does toolbelt/app provide the Config-reading + logger construction, or is it a helper the app calls (keeping the typed Config struct per-app)?
  2. What is the seam between this and the config reexport layer (which re-exports the ReadField* functions and BuildLogger)? Depends on the config ticket's answer.

Blocked by the config-reexport ticket: this Config struct reads through the reexport layer, so its shape follows that decision.

Resolve with /grilling + /domain-modeling. On close, write the plan to docs/plans/ and reshape the relevant example/ surface.

## Question Should `toolbelt/app` ship a standard `Config`-struct + `BuildLogger` bootstrap so `app.go` shrinks to feature wiring? Consumers and the example all hand-write in `internal/app/app.go`: a `Config` struct (~16 fields), `NewConfig()` reading every field via `config.ReadFieldString`, and a `BuildLogger()` (~30 lines byte-identical across consumers). This passes the "example proves it" boundary. Decisions this ticket resolves: 1. Does `toolbelt/app` provide the Config-reading + logger construction, or is it a helper the app calls (keeping the typed `Config` struct per-app)? 2. What is the seam between this and the config reexport layer (which re-exports the `ReadField*` functions and `BuildLogger`)? Depends on the config ticket's answer. **Blocked by** the config-reexport ticket: this `Config` struct reads through the reexport layer, so its shape follows that decision. Resolve with `/grilling` + `/domain-modeling`. On close, write the plan to `docs/plans/` and reshape the relevant `example/` surface.
Author
Owner

Resolved via /grilling + /domain-modeling.

Decision: toolbelt/app owns the entire composition-root bootstrap. New(ctx, cfg *config.Config, opts) reads its platform fields from the config object internally and builds the logger (exported app.BuildLogger(level, format)); the startup summary moves into New; Component.Config becomes the *config.Config object. Features self-populate at Registerauthn.New(opts) loses its six OIDC fields and the logger arg, reading both from c.Config/c.Logger; web.New()/kanban.New() drop the logger arg. app.go shrinks to NewKanbanApp(ctx, config.App) + feature wiring; the flat Config struct, NewConfig(), and all five BuildLogger copies die.

Answer to the ticket's decision 1: toolbelt/app provides the Config-reading + logger construction outright (not a helper the app calls), because the per-app typed struct is a zero-logic passthrough over byte-identical field names.

Answer to the ticket's decision 2 (the seam): the seam is the generated config.App object (ticket #3); BuildLogger lives in toolbelt/app; config reads flow through config.App.ReadField*. Nothing is re-exported.

Coordination: platform field definitions follow ticket #12's per-package KCL fragments; toolbelt/app's field symbols must be referenceable from the merged registry.

Ripple (consumer breaks, migration out of scope): app.New signature (ctx, logger, cfg, opts)(ctx, cfg, opts); authn.New(logger, opts)authn.New(opts) with six OIDC Options fields removed; config package-level API removed per #3.

Plan: docs/plans/app-bootstrap.md. Example reshape deferred to the implementation session shared with the config ticket.

Resolved via `/grilling` + `/domain-modeling`. **Decision:** `toolbelt/app` owns the entire composition-root bootstrap. `New(ctx, cfg *config.Config, opts)` reads its platform fields from the config object internally and builds the logger (exported `app.BuildLogger(level, format)`); the startup summary moves into `New`; `Component.Config` becomes the `*config.Config` object. Features self-populate at `Register` — `authn.New(opts)` loses its six OIDC fields and the logger arg, reading both from `c.Config`/`c.Logger`; `web.New()`/`kanban.New()` drop the logger arg. `app.go` shrinks to `NewKanbanApp(ctx, config.App)` + feature wiring; the flat `Config` struct, `NewConfig()`, and all five `BuildLogger` copies die. **Answer to the ticket's decision 1:** `toolbelt/app` provides the Config-reading + logger construction outright (not a helper the app calls), because the per-app typed struct is a zero-logic passthrough over byte-identical field names. **Answer to the ticket's decision 2 (the seam):** the seam is the generated `config.App` object (ticket #3); `BuildLogger` lives in `toolbelt/app`; config reads flow through `config.App.ReadField*`. Nothing is re-exported. **Coordination:** platform field definitions follow ticket #12's per-package KCL fragments; `toolbelt/app`'s field symbols must be referenceable from the merged registry. **Ripple (consumer breaks, migration out of scope):** `app.New` signature `(ctx, logger, cfg, opts)` → `(ctx, cfg, opts)`; `authn.New(logger, opts)` → `authn.New(opts)` with six OIDC `Options` fields removed; `config` package-level API removed per #3. **Plan:** `docs/plans/app-bootstrap.md`. Example reshape deferred to the implementation session shared with the config ticket.
Author
Owner

Resolution recorded in the decision comment; plan at docs/plans/app-bootstrap.md.

Resolution recorded in the decision comment; plan at docs/plans/app-bootstrap.md.
Author
Owner

Coordination from #12 (per-package KCL config fragments): authn ships its own KCL fragment. When this ticket's authn absorption lands, add authn/schema.k declaring the standard OIDC schema (issuer, client_id, client_secret, redirect_uri, supported_algs, group_scope) with service-neutral defaults, and have apps compose it via 'oidc: authn.OIDC = authn.OIDC {}' (KCL import, per #12's KCL-native resolution). The OIDC schema is generic and defined once; only the values vary per app. #12 itself keeps OIDC app-authored; this ticket owns moving it into authn.

Coordination from #12 (per-package KCL config fragments): authn ships its own KCL fragment. When this ticket's authn absorption lands, add authn/schema.k declaring the standard OIDC schema (issuer, client_id, client_secret, redirect_uri, supported_algs, group_scope) with service-neutral defaults, and have apps compose it via 'oidc: authn.OIDC = authn.OIDC {}' (KCL import, per #12's KCL-native resolution). The OIDC schema is generic and defined once; only the values vary per app. #12 itself keeps OIDC app-authored; this ticket owns moving it into authn.
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
ginjiruu/toolbelt#4
No description provided.