pkg/fleetconfig: typed KDL fleet-config validator (core, not a guarded surface) - blocks the ward-kdl dialect-2 chain #178

Closed
opened 2026-07-01 08:28:24 +00:00 by coilysiren · 2 comments
Owner

Issue 1 of the ward<=>aos config architecture pass (aos#310). Two formats ratified by Kai: KDL source, agent-shaped schema in cli-guard core (decision C, literal reading). This blocks the entire ward-kdl dialect-2 chain - land it first.

Deliverable

A new pkg/fleetconfig package in cli-guard core:

  • Typed structs: Fleet, Agent, Defaults.
  • Parse([]byte) (Fleet, error) over a KDL source, reusing cli-guard's existing KDL node walker.
  • A Source enum (Embedded | OperatorLocal): Embedded accepts the full fleet schema; OperatorLocal accepts the narrower per-host node set (e.g. director.default-scope) and rejects embed-only fields. One grammar, two accepted subsets - 'one parser, two sources'.
  • No Mount, no exec, no can run in its vocabulary. It structurally cannot express a permission. This is pure core data-typing, NOT a fourth guarded surface (cli/, http/, mcp/ stay the three guarded surfaces). That package boundary IS the permission/config partition the pass requires.

Schema (from the aos#310 advisor sketch)

fleet {
    schema-version 2
    defaults {
        agent claude
        attribution name="coilyco-ops" email="coilyco-ops@coilysiren.me"
    }
    agent codex {
        binary codex
        context-level 1
        stream "none"
        auth "codex-file"
        model "gpt-5.4-mini"
        reasoning-effort "low"
        verbosity "low"
        argv { headless codex "exec"; interactive codex }
    }
    // ... claude, opencode, goose per the design
}

Fields per agent: binary, context-level, stream, auth, model, endpoint, provider, reasoning-effort, verbosity, argv{preflight,headless,interactive}. Top level: schema-version, defaults{agent, attribution{name,email}}.

Docs

  • docs/fleetconfig.md - the schema + the two-source model.
  • Note in docs/architecture.md that config validation is a core concern (pkg/fleetconfig), deliberately NOT a guarded surface, so the three-surface least-privilege identity stays legible. This is the config-placement doctrine's landing in cli-guard (generalizes the downward-only arrow at docs/architecture.md:17).

Blocks / sequencing

Blocks aos#310 issues 2 (author fleet.kdl), 3 (repoint ward reads), 4 (ward agents list --json), 10 (axis-3 OperatorLocal). Nothing here depends on ward - land it standalone. Mode: headless.

Issue 1 of the ward<=>aos config architecture pass (**aos#310**). Two formats ratified by Kai: **KDL** source, **agent-shaped schema in cli-guard core** (decision C, literal reading). **This blocks the entire ward-kdl dialect-2 chain - land it first.** ## Deliverable A new **`pkg/fleetconfig`** package in cli-guard core: - Typed structs: `Fleet`, `Agent`, `Defaults`. - `Parse([]byte) (Fleet, error)` over a **KDL** source, reusing cli-guard's existing KDL node walker. - A `Source` enum (`Embedded` | `OperatorLocal`): `Embedded` accepts the full fleet schema; `OperatorLocal` accepts the narrower per-host node set (e.g. `director.default-scope`) and **rejects embed-only fields**. One grammar, two accepted subsets - 'one parser, two sources'. - **No `Mount`, no `exec`, no `can run` in its vocabulary.** It structurally cannot express a permission. This is pure core data-typing, **NOT a fourth guarded surface** (`cli/`, `http/`, `mcp/` stay the three guarded surfaces). That package boundary IS the permission/config partition the pass requires. ## Schema (from the aos#310 advisor sketch) ```kdl fleet { schema-version 2 defaults { agent claude attribution name="coilyco-ops" email="coilyco-ops@coilysiren.me" } agent codex { binary codex context-level 1 stream "none" auth "codex-file" model "gpt-5.4-mini" reasoning-effort "low" verbosity "low" argv { headless codex "exec"; interactive codex } } // ... claude, opencode, goose per the design } ``` Fields per agent: `binary`, `context-level`, `stream`, `auth`, `model`, `endpoint`, `provider`, `reasoning-effort`, `verbosity`, `argv{preflight,headless,interactive}`. Top level: `schema-version`, `defaults{agent, attribution{name,email}}`. ## Docs - `docs/fleetconfig.md` - the schema + the two-source model. - Note in `docs/architecture.md` that config validation is a **core** concern (`pkg/fleetconfig`), deliberately NOT a guarded surface, so the three-surface least-privilege identity stays legible. This is the config-placement doctrine's landing in cli-guard (generalizes the downward-only arrow at `docs/architecture.md:17`). ## Blocks / sequencing Blocks aos#310 issues 2 (author fleet.kdl), 3 (repoint ward reads), 4 (ward agents list --json), 10 (axis-3 OperatorLocal). Nothing here depends on ward - land it standalone. Mode: headless.
Member

🔒 Reserved by ward agent --driver claude — container engineer-claude-cli-guard-178 on host kais-macbook-pro-2.local is carrying this issue (reserved 2026-07-01T08:28:30Z). Concurrent ward agent runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); --force overrides.

— Claude (she/her), via ward agent

<!-- ward-agent-reservation --> 🔒 Reserved by `ward agent --driver claude` — container `engineer-claude-cli-guard-178` on host `kais-macbook-pro-2.local` is carrying this issue (reserved 2026-07-01T08:28:30Z). Concurrent `ward agent` runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); `--force` overrides. <!-- ward-agent-signature --> — Claude (she/her), via `ward agent`
Author
Owner

WARD-OUTCOME: done - pkg/fleetconfig typed KDL fleet-config validator landed on main (one parser, two sources; no permission vocabulary).

Retrospective: this one went smoothly, mostly because the ground was well-prepared. The existing execverb/guardfile parsers gave me a clear house style for walking kdl-go nodes and failing closed, so the core parser was more transcription-of-a-pattern than invention. The one design judgement call was the two-source model: I read "Embedded accepts the full schema, OperatorLocal accepts the narrow per-host set" as Embedded=superset (fleet + optional director seed), OperatorLocal=director-only, rejecting the embed-only fleet block. That felt like the cleanest literal reading and it made the boundary testable.

What fought back was entirely the doc-size and comment-discipline hooks, not the code. FEATURES.md was sitting exactly 1 char under the 4000 cap, so my one-line entry overflowed it and I had to claw back chars from unrelated bullets. The code-comments hook (2-line contiguous cap) also made me compress every doc comment. Annoying, but both are the repo keeping itself tidy, so fair enough.

Confidence is high: parser + tests are green, lint/vet/godoc/pre-commit all pass, and the sandbox test failures are pre-existing and environmental (seccomp under an unprivileged container). The structural "no mount/exec/can-run" boundary is enforced by name and covered by tests.

Rough edges / follow-ups worth filing: (1) the OperatorLocal vocabulary is currently just director.default-scope - the real per-host axis-3 set (aos#310 issue 10) will want more nodes, and I kept it deliberately minimal; (2) schema-version is hard-pinned to 2 and fails closed on anything else, which is intentional for dialect-2 but will need a conscious bump; (3) no golden/round-trip fixture file yet - issue 2 (author fleet.kdl) is the natural place to add one and exercise Parse against the real artifact.

WARD-OUTCOME: done - pkg/fleetconfig typed KDL fleet-config validator landed on main (one parser, two sources; no permission vocabulary). Retrospective: this one went smoothly, mostly because the ground was well-prepared. The existing execverb/guardfile parsers gave me a clear house style for walking kdl-go nodes and failing closed, so the core parser was more transcription-of-a-pattern than invention. The one design judgement call was the two-source model: I read "Embedded accepts the full schema, OperatorLocal accepts the narrow per-host set" as Embedded=superset (fleet + optional director seed), OperatorLocal=director-only, rejecting the embed-only `fleet` block. That felt like the cleanest literal reading and it made the boundary testable. What fought back was entirely the doc-size and comment-discipline hooks, not the code. FEATURES.md was sitting exactly 1 char under the 4000 cap, so my one-line entry overflowed it and I had to claw back chars from unrelated bullets. The code-comments hook (2-line contiguous cap) also made me compress every doc comment. Annoying, but both are the repo keeping itself tidy, so fair enough. Confidence is high: parser + tests are green, lint/vet/godoc/pre-commit all pass, and the sandbox test failures are pre-existing and environmental (seccomp under an unprivileged container). The structural "no mount/exec/can-run" boundary is enforced by name and covered by tests. Rough edges / follow-ups worth filing: (1) the OperatorLocal vocabulary is currently just `director.default-scope` - the real per-host axis-3 set (aos#310 issue 10) will want more nodes, and I kept it deliberately minimal; (2) schema-version is hard-pinned to 2 and fails closed on anything else, which is intentional for dialect-2 but will need a conscious bump; (3) no golden/round-trip fixture file yet - issue 2 (author fleet.kdl) is the natural place to add one and exercise Parse against the real artifact.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
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
coilyco-flight-deck/cli-guard#178
No description provided.