[tracking] Full ward-kdl read/write/admin tier migration: every area, context-selected (read=CI, write=warded, admin=native) #339

Open
opened 2026-06-25 17:20:12 +00:00 by coilysiren · 6 comments
Owner

The migration

Move the whole ward-kdl ops surface to permission tiers - ward-kdl-read ⊂ ward-kdl-write ⊂ ward-kdl-admin, one standalone binary per tier carrying every area - and load the right tier by context:

  • ward-kdl-read in CI / docker containers (read-only now, likely finely-scoped write later).
  • ward-kdl-write in warded feature containers (the broker already shells this, ward#329).
  • ward-kdl-admin native, for Kai.

Today only forgejo is tiered (ward#240), as three binaries that each carry forgejo alone. This epic extends the chain to every area and wires the context selection.

The posture (decided 2026-06-25, Kai)

read is the least-privileged base. Build UP. Fail-closed. Each higher tier inherits the one below and adds grants; a forgotten grant stays denied. "admin as root, derive down" is rejected - one missed subtraction silently hands a read-tier child an admin verb (fail-open), which is catastrophic for a credentialed agent surface. Escalation is always conscious and upward, never implicit and downward.

  • read = semantic reads (get/list/search/view).
  • write = inherit read + create/edit.
  • admin = inherit write + delete (the destructive superset).
  • Curated never denials are authored where their verb family lives and inherit upward. Where a higher tier must escalate past a broad base denial, it does so by name via the cli-guard override keyword (cli-guard#169) - never by restating or loosening silently.

Exec-dialect areas tier too (docker, aws, kubectl, git, agent launchers, brew), mapped read-only / +safe-mutation / full per area judgement against this rubric.

Engine dependency

  • cli-guard#169 - never must propagate upward through inherit, and an override keyword must lift one inherited denial by name (fail-closed). This is the prerequisite for every area that needs selective escalation past a broad base denial (forgejo: allow delete repo, keep never delete issue). Areas with no such escalation (e.g. signoz, CRU-only) do not need it. Security-sensitive - recommend it lands with review, not fire-and-forget.

Sequence: foundation first, then fan out

Foundation (dispatched)

  • ward#338 - generalize the tier build past the forgejo-hardcoded triplet into an area-discovering build-ward-kdl-tiers, and prove the additive read-base build-up on a second area, signoz (CRU-only, so admin == write, no override needed). Proves the structural goal: multiple areas' guardfiles in one tier subdir merge into one tier binary. Phase-1 boundary - monolith and ward ops untouched.
  • ward#278 - the forgejo curated port: move the hand-hardened monolith surface (every never, op pin, fixed-body toggle, and compound action) into the three forgejo tiers, build-up, denials per tier. Already filed, interactive (human reviews the tier diff). Consumes cli-guard#169 for its selective-escalation denials. This is the other half of the schema - signoz proves the simple additive case, forgejo proves the curated-denial-with-escalation case.

Fan out (after the foundation schema lands)

One run per remaining area, each factoring that area's curated guardfile into read/write/admin, build-up, fail-closed, denials inheriting upward, escalation via override where needed:

  • aws (exec) - reads / +safe mutation / full
  • kubectl (exec) - reads / +apply,scale,rollout / full
  • docker (exec) - already read-only; map the safe-mutation + full tiers
  • git (exec) - reads / +commit,push / full (reconcile with hand-written ward git, ward-kdl-in-ward.md)
  • brew (exec) - jailed reads / +install / full (reconcile with hand-written ward pkg brew)
  • agent launchers (exec: claude, codex, opencode, aider, goose, ollama) - tiering axis TBD per launcher
  • trello (spec)
  • tailscale (spec)
  • glitchtip (spec)
  • signoz (spec) - done by the foundation, ward#338
  • glama (spec)
  • skillsmp (spec)
  • forgejo (spec) - ward#278

Phase 2: context selection (Kai-owned)

  • Build + ship a full ward-kdl-admin native binary across every area; retire or alias ward's embedded per-area surface onto it. Resolves ward#278's open "what does ward ops forgejo become" - the answer generalizes to every area.
  • Install ward-kdl-read into the CI / docker image entrypoint (the read context).
  • ward-kdl-write in warded containers - the broker (ward#329) already shells it; confirm it carries the all-area write surface once the fan-out lands, not forgejo alone.
  • Tap / release wiring for all three all-area tier binaries (extends ward#246, which builds+installs the forgejo-only triplet today).

References

ward#240 (the chain), ward#278 (forgejo port), ward#246 (build+install the tier binaries), ward#338 (this epic's foundation run), ward#329 (the broker shelling write), ward#241 (signoz CRU-only), cli-guard#169 (the override engine capability), cli-guard#159/#160 (wildcard grants, inherit), ward-kdl-in-ward.md (exec auto-mount, the ward git/pkg brew collisions the exec fan-out must reconcile).

## The migration Move the **whole** `ward-kdl` ops surface to permission tiers - `ward-kdl-read ⊂ ward-kdl-write ⊂ ward-kdl-admin`, one standalone binary per tier carrying every area - and **load the right tier by context**: - **`ward-kdl-read`** in CI / docker containers (read-only now, likely finely-scoped write later). - **`ward-kdl-write`** in `warded` feature containers (the broker already shells this, ward#329). - **`ward-kdl-admin`** native, for Kai. Today only **forgejo** is tiered (ward#240), as three binaries that each carry forgejo alone. This epic extends the chain to every area and wires the context selection. ## The posture (decided 2026-06-25, Kai) **read is the least-privileged base. Build UP. Fail-closed.** Each higher tier `inherit`s the one below and **adds** grants; a forgotten grant stays denied. "admin as root, derive down" is **rejected** - one missed subtraction silently hands a read-tier child an admin verb (fail-open), which is catastrophic for a credentialed agent surface. Escalation is always conscious and upward, never implicit and downward. - read = semantic reads (get/list/search/view). - write = inherit read + create/edit. - admin = inherit write + delete (the destructive superset). - Curated `never` denials are authored where their verb family lives and **inherit upward**. Where a higher tier must escalate past a broad base denial, it does so **by name** via the cli-guard `override` keyword (cli-guard#169) - never by restating or loosening silently. **Exec-dialect areas tier too** (docker, aws, kubectl, git, agent launchers, brew), mapped read-only / +safe-mutation / full per area judgement against this rubric. ## Engine dependency - **cli-guard#169** - `never` must propagate upward through `inherit`, and an `override` keyword must lift one inherited denial by name (fail-closed). This is the prerequisite for every area that needs **selective escalation past a broad base denial** (forgejo: allow `delete repo`, keep `never delete issue`). Areas with no such escalation (e.g. signoz, CRU-only) do not need it. Security-sensitive - recommend it lands with review, not fire-and-forget. ## Sequence: foundation first, then fan out ### Foundation (dispatched) - **ward#338** - generalize the tier build past the forgejo-hardcoded triplet into an area-discovering `build-ward-kdl-tiers`, and prove the additive read-base build-up on a **second area, signoz** (CRU-only, so admin == write, no override needed). Proves the structural goal: multiple areas' guardfiles in one tier subdir **merge into one tier binary**. Phase-1 boundary - monolith and `ward ops` untouched. - **ward#278** - the forgejo curated port: move the hand-hardened monolith surface (every `never`, `op` pin, fixed-body toggle, and compound action) into the three forgejo tiers, build-up, denials per tier. Already filed, `interactive` (human reviews the tier diff). Consumes cli-guard#169 for its selective-escalation denials. This is the **other half of the schema** - signoz proves the simple additive case, forgejo proves the curated-denial-with-escalation case. ### Fan out (after the foundation schema lands) One run per remaining area, each factoring that area's curated guardfile into read/write/admin, build-up, fail-closed, denials inheriting upward, escalation via `override` where needed: - [ ] aws (exec) - reads / +safe mutation / full - [ ] kubectl (exec) - reads / +apply,scale,rollout / full - [ ] docker (exec) - already read-only; map the safe-mutation + full tiers - [ ] git (exec) - reads / +commit,push / full (reconcile with hand-written `ward git`, ward-kdl-in-ward.md) - [ ] brew (exec) - jailed reads / +install / full (reconcile with hand-written `ward pkg brew`) - [ ] agent launchers (exec: claude, codex, opencode, aider, goose, ollama) - tiering axis TBD per launcher - [ ] trello (spec) - [ ] tailscale (spec) - [ ] glitchtip (spec) - [ ] signoz (spec) - **done by the foundation, ward#338** - [ ] glama (spec) - [ ] skillsmp (spec) - [x] forgejo (spec) - ward#278 ### Phase 2: context selection (Kai-owned) - [ ] Build + ship a **full `ward-kdl-admin`** native binary across every area; retire or alias `ward`'s embedded per-area surface onto it. Resolves ward#278's open "what does `ward ops forgejo` become" - the answer generalizes to every area. - [ ] Install `ward-kdl-read` into the CI / docker image entrypoint (the read context). - [ ] `ward-kdl-write` in warded containers - the broker (ward#329) already shells it; confirm it carries the all-area write surface once the fan-out lands, not forgejo alone. - [ ] Tap / release wiring for all three all-area tier binaries (extends ward#246, which builds+installs the forgejo-only triplet today). ## References ward#240 (the chain), ward#278 (forgejo port), ward#246 (build+install the tier binaries), ward#338 (this epic's foundation run), ward#329 (the broker shelling write), ward#241 (signoz CRU-only), cli-guard#169 (the `override` engine capability), cli-guard#159/#160 (wildcard grants, `inherit`), ward-kdl-in-ward.md (exec auto-mount, the `ward git`/`pkg brew` collisions the exec fan-out must reconcile).
Author
Owner

Schema note from the foundation run (ward#338): "adding an area is three guardfiles" is NOT literally true for vendored-spec areas. A vendored spec (signoz, and likely glitchtip/trello/tailscale/glama/skillsmp) must sit in EACH tier dir, because inheriting write/admin members resolve the spec relative to their own dir and go:embed cannot reach a sibling. The generalized build vendors the spec per tier via a copy-before-lock keyed off the read guardfile spec line, copies gitignored so the base file stays the single source of truth. Remote-spec areas (forgejo) do not need this. The fan-out for vendored-spec areas must account for it. Follow-ups filed from the run: ward#340 (pre-existing failing test on main, unrelated), ward#341 (signoz write-tier needs an env-auth override for the in-container broker - a Phase-2 dependency).

Schema note from the foundation run (ward#338): "adding an area is three guardfiles" is NOT literally true for vendored-spec areas. A vendored spec (signoz, and likely glitchtip/trello/tailscale/glama/skillsmp) must sit in EACH tier dir, because inheriting write/admin members resolve the spec relative to their own dir and go:embed cannot reach a sibling. The generalized build vendors the spec per tier via a copy-before-lock keyed off the read guardfile spec line, copies gitignored so the base file stays the single source of truth. Remote-spec areas (forgejo) do not need this. The fan-out for vendored-spec areas must account for it. Follow-ups filed from the run: ward#340 (pre-existing failing test on main, unrelated), ward#341 (signoz write-tier needs an env-auth override for the in-container broker - a Phase-2 dependency).
Author
Owner

Posture correction (Kai, cli-guard#169): "everything inherits upwards, automatically." Drop the override-keyword line above - it is overbuilt. The model: every rule (can, never, restrict, singletons) auto-inherits up with no per-tier re-declaration (supersedes cli-guard#160 child-local restrict); tiering is additive can placement; never is an absolute carve-out that rides up; and the one engine rule to pin is precedence - an inherited never always beats a can, so wildcard grants stay safe. No re-allow construct. cli-guard#169 now carries this.

Posture correction (Kai, cli-guard#169): "everything inherits upwards, automatically." Drop the override-keyword line above - it is overbuilt. The model: every rule (can, never, restrict, singletons) auto-inherits up with no per-tier re-declaration (supersedes cli-guard#160 child-local restrict); tiering is additive can placement; never is an absolute carve-out that rides up; and the one engine rule to pin is precedence - an inherited never always beats a can, so wildcard grants stay safe. No re-allow construct. cli-guard#169 now carries this.
Author
Owner

Posture settled (Kai), supersedes my previous correction: override is BACK IN, with the direction deny-low / override-high. The least-privileged base (read) explicitly declares never on dangerous actions; those nevers inherit upward; and the most-privileged tier (admin) uses override can to re-grant specific denied actions BY NAME. An inherited never beats a plain can; the only thing that crosses a never is a named override. Non-overridden siblings stay denied (read never delete *, admin override can delete repo => delete issue still refused). The forgejo curated surface = base-nevers + admin-overrides. cli-guard#169 carries the settled model.

Posture settled (Kai), supersedes my previous correction: override is BACK IN, with the direction deny-low / override-high. The least-privileged base (read) explicitly declares never on dangerous actions; those nevers inherit upward; and the most-privileged tier (admin) uses override can <verb> <resource> to re-grant specific denied actions BY NAME. An inherited never beats a plain can; the only thing that crosses a never is a named override. Non-overridden siblings stay denied (read never delete *, admin override can delete repo => delete issue still refused). The forgejo curated surface = base-nevers + admin-overrides. cli-guard#169 carries the settled model.
Author
Owner

Phase-2 deploy + UX captured in ward#342 (decisions settled with Kai): the tier is ambient (always ward ops ), ward shells the context tier binary so privilege = which binary is present (run-time mirror of the cli-guard#169 absence engine), context = broker-socket-then-explicit-WARD_TIER-profile (dev-base=read, native=admin), and a native ward ops --tier read|write downshift. It is the capstone - gated on cli-guard#169 -> release/REF bump -> ward#278 -> the per-area fan-out, since there is no tier binary to shell until the contents exist. ward#342 also answers ward#278 Phase-2 open question (what ward ops becomes): shell the admin tier binary. Unit 1 (publish read/admin release assets, symmetric with publish-kdl-write) is the only parallel-safe slice before the fan-out.

Phase-2 deploy + UX captured in ward#342 (decisions settled with Kai): the tier is ambient (always ward ops <area>), ward shells the context tier binary so privilege = which binary is present (run-time mirror of the cli-guard#169 absence engine), context = broker-socket-then-explicit-WARD_TIER-profile (dev-base=read, native=admin), and a native ward ops --tier read|write downshift. It is the capstone - gated on cli-guard#169 -> release/REF bump -> ward#278 -> the per-area fan-out, since there is no tier binary to shell until the contents exist. ward#342 also answers ward#278 Phase-2 open question (what ward ops becomes): shell the admin tier binary. Unit 1 (publish read/admin release assets, symmetric with publish-kdl-write) is the only parallel-safe slice before the fan-out.
Author
Owner

Dispatched two parallel-safe mechanical runs (gate lifted - Kai: building a shell around empty binaries is fine): ward#343 generalizes the release publish job into publish-kdl-{read,write,admin} so all three tiers ship as per-arch assets (Unit 1 of ward#342), and ward#344 scaffolds the remaining spec areas (trello, tailscale, glitchtip, glama, skillsmp) into the per-tier folders with placeholders + vendored specs, no keyword tiering yet. Both independent of cli-guard#169.

Dispatched two parallel-safe mechanical runs (gate lifted - Kai: building a shell around empty binaries is fine): ward#343 generalizes the release publish job into publish-kdl-{read,write,admin} so all three tiers ship as per-arch assets (Unit 1 of ward#342), and ward#344 scaffolds the remaining spec areas (trello, tailscale, glitchtip, glama, skillsmp) into the per-tier folders with placeholders + vendored specs, no keyword tiering yet. Both independent of cli-guard#169.
Member

Stays consult: tracking umbrella for the tier migration - children (#278 onward) carry the dispatchable work. Recorded by Claude Code (Fable) during the 2026-07-01 ward launch triage session with Kai.

Stays consult: tracking umbrella for the tier migration - children (#278 onward) carry the dispatchable work. Recorded by Claude Code (Fable) during the 2026-07-01 ward launch triage session with Kai.
coilyco-ops added
P4
and removed
P2
labels 2026-07-14 06:50:33 +00:00
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/ward#339
No description provided.