Per-package KCL config fragments — service-owned defaults (db, embeddednats, embeddedfga) #12

Closed
opened 2026-08-06 19:01:39 +00:00 by ginjiruu · 1 comment
Owner

Question

Each toolbelt service package (db, embeddednats, embeddedfga) needs a default data directory (NATS data/nats, sqlite data/sqlite/<app>.db, FGA file://data/fga/openfga.db). Today those defaults are hand-written twice — as Go consts/values in the service package and as defaults in each app's schema.k. Where should each service's default config live and how do apps inherit it?

Decisions this ticket resolves:

  1. Each service package ships its own KCL fragment declaring its schema + defaults (embeddednats/schema.k, db/schema.k, embeddedfga/schema.k), and kcl-generate generalizes its embedded-fragment merge (parseSchemaFiles) to merge N fragments — apps compose via nats: NATS = NATS {}.
  2. Service packages drop their hand-written Go const defaults; the composition root always passes the resolved field value. The bootstrap object from ticket #3 is agnostic to where defaults came from.
  3. Merging happens at codegen time (build-time), not runtime — no runtime KCL eval; the generated var DefaultX ldflags-overridable pattern survives.

Graduated from ticket #3 (config reexport absorption) during its grilling session.

Resolve with /grilling + /domain-modeling, /prototype if the merge shape needs a concrete look. On close, write the plan to docs/plans/ and reshape kcl-generate + example/ surfaces.

## Question Each toolbelt service package (db, embeddednats, embeddedfga) needs a default data directory (NATS `data/nats`, sqlite `data/sqlite/<app>.db`, FGA `file://data/fga/openfga.db`). Today those defaults are hand-written twice — as Go consts/values in the service package and as defaults in each app's `schema.k`. Where should each service's default config live and how do apps inherit it? Decisions this ticket resolves: 1. Each service package ships its own KCL fragment declaring its schema + defaults (`embeddednats/schema.k`, `db/schema.k`, `embeddedfga/schema.k`), and `kcl-generate` generalizes its embedded-fragment merge (`parseSchemaFiles`) to merge N fragments — apps compose via `nats: NATS = NATS {}`. 2. Service packages drop their hand-written Go const defaults; the composition root always passes the resolved field value. The bootstrap object from ticket #3 is agnostic to where defaults came from. 3. Merging happens at codegen time (build-time), not runtime — no runtime KCL eval; the generated `var DefaultX` ldflags-overridable pattern survives. Graduated from ticket #3 (config reexport absorption) during its grilling session. Resolve with `/grilling` + `/domain-modeling`, `/prototype` if the merge shape needs a concrete look. On close, write the plan to `docs/plans/` and reshape `kcl-generate` + `example/` surfaces.
Author
Owner

Review complete; implementation verified. See closing comment for the review + lessons learned.

Verification: root go test ./..., tools go test ./kcl-generate/, and example build+tests all pass; task check-kcl drift guard passes clean (no stale output). Implementation is commit f3cbcd0 (implements sub-tickets #15-#18 under parent spec #14). ttrpg consumer break recorded: the two WithStoreDir(t.TempDir()) test call sites (assetstore_test.go, feature/campaign/projectors_test.go) must become positional SetupNATS(ctx, logger, dir) — separate consumer-migration project, as specced.

Lessons learned (full text in the closing comment):

  1. Directive correlation had to become file-aware: multi-file fragment merging collides line numbers across files, so #@kcl: directive assignment now groups fields/directives by source file.
  2. kcl.Run does not read kcl.mod [dependencies]; both codegen and runtime must pass path deps explicitly as external packages. The shared kclmod package prevents the two resolution paths from drifting.
  3. The app core fragment is inherited (schema Config(app.Config)), not composed — the spec's 'app identity (name)' field dissolved once direct composition of app-owned values covered it.
  4. The app-wins / canonical-key / required-field contract all landed exactly as specced, with the example proving each in the generator test suite.
Review complete; implementation verified. See closing comment for the review + lessons learned. Verification: root `go test ./...`, tools `go test ./kcl-generate/`, and example build+tests all pass; `task check-kcl` drift guard passes clean (no stale output). Implementation is commit f3cbcd0 (implements sub-tickets #15-#18 under parent spec #14). ttrpg consumer break recorded: the two `WithStoreDir(t.TempDir())` test call sites (assetstore_test.go, feature/campaign/projectors_test.go) must become positional `SetupNATS(ctx, logger, dir)` — separate consumer-migration project, as specced. Lessons learned (full text in the closing comment): 1. Directive correlation had to become file-aware: multi-file fragment merging collides line numbers across files, so #@kcl: directive assignment now groups fields/directives by source file. 2. kcl.Run does not read kcl.mod [dependencies]; both codegen and runtime must pass path deps explicitly as external packages. The shared kclmod package prevents the two resolution paths from drifting. 3. The app core fragment is inherited (schema Config(app.Config)), not composed — the spec's 'app identity (name)' field dissolved once direct composition of app-owned values covered it. 4. The app-wins / canonical-key / required-field contract all landed exactly as specced, with the example proving each in the generator test suite.
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.

Dependencies

No dependencies set.

Reference
ginjiruu/toolbelt#12
No description provided.