Refactor: security-pure engine + thin KDL-expressed consumers (coily/ward/cli-guard) #123

Closed
opened 2026-06-09 04:45:13 +00:00 by coilysiren · 2 comments
Owner

Direction

Refactor the coily / ward / cli-guard family toward "security-pure engine + thin KDL-expressed consumers." Captured from a design discussion. Verdict: ~75% coherent, one internal contradiction to resolve before starting.

Two axes (don't conflate them)

  • Axis A - engine vs config. Push all Go into cli-guard, express each consumer as a KDL Guardfile. coily/ward become "the cli-guard binary + a guardfile."
  • Axis B - coverage. Everything routes through the boundary (strong funnel) vs only-dangerous-stuff routes through it (leaky funnel).

The KDL move and the merge move are pure Axis A (good). "Strictly security tooling / remove general-purpose automation" and "more opt-in" are Axis B pushing toward narrower coverage - which is less security, fighting the "more strictly security" framing.

The contradiction

coily's security value is being the path of least resistance for everything (Bash(coily:*) funnels all ops through one re-validating binary). The mundane dev verbs (coily exec test, .coily/coily.yaml) look like non-security automation but they ARE the hook - the bait that makes agents route through the funnel. Strip them and agents reach for bare make / sh -c / python -c, which are the exact escape hatches the funnel exists to close.

Reframe "remove general-purpose automation":

  • "Remove automation surface" -> breaks the funnel. Don't.
  • "Remove bespoke business logic from the binary" -> good. The binary gates and audits, it does not do things.

Make it security-pure in implementation, security-comprehensive in coverage.

Per-move calls

  1. Strictly security tooling - yes as code-purity (no bespoke logic in the binary), no as coverage-narrowing (keep dev verbs in the funnel).
  2. KDL as the entire interface, all Go in cli-guard - full yes. Natural endpoint of the existing "thin consumer" design. speakeasy-ward + ward/cmd/ward-kdl already prove it. This is the move that earns the "security tooling" reframe (policy as reviewable plain sentences, not buried Go).
  3. More opt-in - audience-dependent. ward (contributor/external): opt-in is correct, already true (v0). coily (Kai's own machines): opt-in guts the deny-by-default lockdown that is coily's teeth. Keep mandatory there. Don't apply uniformly.
  4. Merge coily into ward - coherent at the binary level (both collapse to engine + guardfile). Two cautions:
    • Merge direction. coily is the mature, documented, branded one ("auditors love to see me coming"). ward is v0. Folding senior into junior loses coily's brand/history - decide the survivor name deliberately.
    • Reference-impl home. cli-guard already ships examples/ (one per primitive). A gallery of example guardfiles likely belongs there, next to the engine, not bloating a consumer back into the fat thing. Possible end state: cli-guard = engine + example guardfile gallery (the reference impl), coily/ward = the two real guardfiles (operator profile, contributor profile).

Target state

  • cli-guard - all Go, all primitives, the example guardfile gallery as reference impl.
  • One thin binary consumed via KDL Guardfiles. The real consolidation is "Go stops living in consumers," achieved with or without a physical merge.
  • Operator profile mandatory, contributor profile opt-in - keep the audience split as policy, not as code.
  • Coverage stays comprehensive - dev verbs remain in the funnel.

Hardest pushback

Don't let "make it strictly security tooling" become "make it cover less." Those feel like the same instinct and they are opposites.

Next step

Turn this into a sequenced migration plan (repo moves + ordering) before any code moves.

## Direction Refactor the coily / ward / cli-guard family toward "security-pure engine + thin KDL-expressed consumers." Captured from a design discussion. Verdict: ~75% coherent, one internal contradiction to resolve before starting. ## Two axes (don't conflate them) - **Axis A - engine vs config.** Push all Go into cli-guard, express each consumer as a KDL Guardfile. coily/ward become "the cli-guard binary + a guardfile." - **Axis B - coverage.** Everything routes through the boundary (strong funnel) vs only-dangerous-stuff routes through it (leaky funnel). The KDL move and the merge move are pure Axis A (good). "Strictly security tooling / remove general-purpose automation" and "more opt-in" are Axis B pushing toward *narrower coverage* - which is **less** security, fighting the "more strictly security" framing. ## The contradiction coily's security value is being the **path of least resistance for everything** (`Bash(coily:*)` funnels all ops through one re-validating binary). The mundane dev verbs (`coily exec test`, `.coily/coily.yaml`) look like non-security automation but they ARE the hook - the bait that makes agents route through the funnel. Strip them and agents reach for bare `make` / `sh -c` / `python -c`, which are the exact escape hatches the funnel exists to close. Reframe "remove general-purpose automation": - "Remove automation *surface*" -> breaks the funnel. Don't. - "Remove bespoke business logic from the binary" -> good. The binary gates and audits, it does not *do* things. Make it security-**pure** in implementation, security-**comprehensive** in coverage. ## Per-move calls 1. **Strictly security tooling** - yes as code-purity (no bespoke logic in the binary), no as coverage-narrowing (keep dev verbs in the funnel). 2. **KDL as the entire interface, all Go in cli-guard** - full yes. Natural endpoint of the existing "thin consumer" design. speakeasy-ward + `ward/cmd/ward-kdl` already prove it. This is the move that earns the "security tooling" reframe (policy as reviewable plain sentences, not buried Go). 3. **More opt-in** - audience-dependent. ward (contributor/external): opt-in is correct, already true (v0). coily (Kai's own machines): opt-in guts the deny-by-default lockdown that is coily's teeth. Keep mandatory there. Don't apply uniformly. 4. **Merge coily into ward** - coherent at the binary level (both collapse to engine + guardfile). Two cautions: - **Merge direction.** coily is the mature, documented, branded one ("auditors love to see me coming"). ward is v0. Folding senior into junior loses coily's brand/history - decide the survivor name deliberately. - **Reference-impl home.** cli-guard already ships `examples/` (one per primitive). A gallery of example guardfiles likely belongs there, next to the engine, not bloating a consumer back into the fat thing. Possible end state: cli-guard = engine + example guardfile gallery (the reference impl), coily/ward = the two *real* guardfiles (operator profile, contributor profile). ## Target state - **cli-guard** - all Go, all primitives, the example guardfile gallery as reference impl. - **One thin binary** consumed via KDL Guardfiles. The real consolidation is "Go stops living in consumers," achieved with or without a physical merge. - **Operator profile mandatory, contributor profile opt-in** - keep the audience split as policy, not as code. - **Coverage stays comprehensive** - dev verbs remain in the funnel. ## Hardest pushback Don't let "make it strictly security tooling" become "make it cover less." Those feel like the same instinct and they are opposites. ## Next step Turn this into a sequenced migration plan (repo moves + ordering) before any code moves.
Author
Owner

Migration status (autonomous session, 2026-06-10)

Axis A is moving: the entire forgejo HTTP surface now lives as KDL sentences.

Landed today (cli-guard v0.7.0 -> v0.10.0, ward main):

  • specverb runtime shape set completed: scalar query params, array bodies, --body-file (gate-safe spill), fixed-body state toggles, multipart formData uploads, untyped-array (names) bodies, reserved-flag collision gate. #125 #127 #128 #129
  • ward-kdl mounts 40 verbs across repo/org/label/milestone/issue/issue-label/release/pull/task groups - the full HTTP slice of the 40-leaf surface. Runtime-foundation blocker closed (coilyco-flight-deck/ward#73). Verified by dry-run parity against coily's request shapes (archive oracle byte-identical) plus live filtered reads.
  • name->id pre-flight dissolved: the modern forgejo API accepts label names directly.

Next, in sequence:

  1. exec dialect (#130) - KDL for passthrough/remote-exec verbs; absorbs the admin/doctor kubectl-exec leaves and starts the git/brew/docker surfaces.
  2. Generic extraction band (issues 90-102) - coily's reusable capabilities into cli-guard packages.
  3. Concrete impls into ward - REST surfaces (eco, factorio, modio, CI, discord) as further guardfiles, exec surfaces on the dialect; coily leaves deleted only after each ward replacement is proven. ward is the surviving operator binary.

Per-decision record: ward survives the merge; coverage stays comprehensive (the funnel keeps its dev verbs); operator profile stays mandatory.

## Migration status (autonomous session, 2026-06-10) Axis A is moving: the entire forgejo HTTP surface now lives as KDL sentences. **Landed today** (cli-guard v0.7.0 -> v0.10.0, ward main): * specverb runtime shape set completed: scalar query params, array bodies, --body-file (gate-safe spill), fixed-body state toggles, multipart formData uploads, untyped-array (names) bodies, reserved-flag collision gate. https://forgejo.coilysiren.me/coilyco-flight-deck/cli-guard/issues/125 https://forgejo.coilysiren.me/coilyco-flight-deck/cli-guard/issues/127 https://forgejo.coilysiren.me/coilyco-flight-deck/cli-guard/issues/128 https://forgejo.coilysiren.me/coilyco-flight-deck/cli-guard/issues/129 * ward-kdl mounts **40 verbs** across repo/org/label/milestone/issue/issue-label/release/pull/task groups - the full HTTP slice of the 40-leaf surface. Runtime-foundation blocker closed (https://forgejo.coilysiren.me/coilyco-flight-deck/ward/issues/73). Verified by dry-run parity against coily's request shapes (archive oracle byte-identical) plus live filtered reads. * name->id pre-flight dissolved: the modern forgejo API accepts label names directly. **Next, in sequence:** 1. exec dialect (https://forgejo.coilysiren.me/coilyco-flight-deck/cli-guard/issues/130) - KDL for passthrough/remote-exec verbs; absorbs the admin/doctor kubectl-exec leaves and starts the git/brew/docker surfaces. 2. Generic extraction band (issues 90-102) - coily's reusable capabilities into cli-guard packages. 3. Concrete impls into ward - REST surfaces (eco, factorio, modio, CI, discord) as further guardfiles, exec surfaces on the dialect; coily leaves deleted only after each ward replacement is proven. ward is the surviving operator binary. Per-decision record: ward survives the merge; coverage stays comprehensive (the funnel keeps its dev verbs); operator profile stays mandatory.
Author
Owner

Backlog burndown 2026-06-17: closing low-priority (P3/P4) to bring the open count to a manageable level. Nothing lost — reopen if this resurfaces. Batch tag: burndown-2026-06.

Backlog burndown 2026-06-17: closing low-priority (P3/P4) to bring the open count to a manageable level. Nothing lost — reopen if this resurfaces. Batch tag: `burndown-2026-06`.
coilysiren 2026-06-17 08:23:17 +00:00
Sign in to join this conversation.
No milestone
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
coilyco-flight-deck/cli-guard#123
No description provided.