feat(specverb): inherit directive — compose guardfiles by merging grant sets (layered read/write/admin) #160

Closed
opened 2026-06-24 05:54:51 +00:00 by coilyco-ops · 1 comment
Member

Goal

Add an inherit directive to ward-kdl guardfiles so a wrap composes on top of another by merging grant sets — enabling layered/tiered guardfiles (read ⊂ write ⊂ admin) without copy-pasting grants. Consumer: ward#240 (3 ward-kdl forgejo binaries).

Syntax

wrap ward-kdl ops forgejo {
    inherit "ward-kdl.forgejo.read.guardfile.kdl"
    can create "*"
    can edit "*"
}
  • inherit <ref> at the top of a wrap pulls in the referenced guardfile's grants (and spec / base-url / auth when the child doesn't declare its own).
  • Chainable: admin inherits write inherits read.

Semantics

  • Grant union: child effective grants = parent grants ∪ child grants.
  • Precedence unchanged — deny beats allow across the merged set (deny.go). For the additive tier model parents carry only can grants, so no deny-fighting arises; a child does not re-allow a parent never (keep it simple; if override is ever needed it's a separate feature — document this limit).
  • Singletons (spec, base-url, auth): inherited unless the child declares its own (child wins).
  • Resolution by file path relative to the inheriting guardfile. Cycle detection + missing-ref → clear error.
  • Composes with wildcard grants (cli-guard#159): tiers are can <verb> "*" sets.

Implementation (cli-guard http/specverb/ + http/guardfile/)

  • guardfile parser: accept an inherit <string> node; resolve + parse the referenced guardfile; merge grants (and singleton fields) before resolve/prune/deny run, so the existing pipeline operates on the merged set unchanged.
  • Cycle + missing-file detection with a teaching error.
  • describe.go: render the merged surface (optionally annotate inherited provenance).

Acceptance

  1. inherit merges parent grants into child; unit tests for a read ⊂ write ⊂ admin chain.
  2. Cycle and missing-ref produce clear errors.
  3. Wildcard grants (#159) compose through inheritance.
  4. spec/base-url/auth inherited unless overridden.
  5. gen/skew clean, reference doc updated, conventional commit, closes via the full Forgejo URL, push to main.
  • Depends on cli-guard#159 (wildcard "*") — tiers are wildcard grant sets, and #159 edits the same specverb files. Land #159 first.
  • Consumer: ward#240 — the read/write/admin binaries are built from inherit-chained guardfiles. ward#240 also needs a cli-guard release carrying this feature before ward's go.mod can pin it.
## Goal Add an `inherit` directive to ward-kdl guardfiles so a wrap composes on top of another by **merging grant sets** — enabling layered/tiered guardfiles (read ⊂ write ⊂ admin) without copy-pasting grants. Consumer: ward#240 (3 ward-kdl forgejo binaries). ## Syntax ```kdl wrap ward-kdl ops forgejo { inherit "ward-kdl.forgejo.read.guardfile.kdl" can create "*" can edit "*" } ``` - `inherit <ref>` at the top of a wrap pulls in the referenced guardfile's grants (and `spec` / `base-url` / `auth` when the child doesn't declare its own). - Chainable: admin inherits write inherits read. ## Semantics - **Grant union:** child effective grants = parent grants ∪ child grants. - **Precedence unchanged — deny beats allow** across the merged set (`deny.go`). For the additive tier model parents carry only `can` grants, so no deny-fighting arises; a child does **not** re-allow a parent `never` (keep it simple; if override is ever needed it's a separate feature — document this limit). - **Singletons** (`spec`, `base-url`, `auth`): inherited unless the child declares its own (child wins). - **Resolution by file path** relative to the inheriting guardfile. **Cycle detection** + missing-ref → clear error. - Composes with wildcard grants (cli-guard#159): tiers are `can <verb> "*"` sets. ## Implementation (cli-guard `http/specverb/` + `http/guardfile/`) - guardfile parser: accept an `inherit <string>` node; resolve + parse the referenced guardfile; merge grants (and singleton fields) **before** resolve/prune/deny run, so the existing pipeline operates on the merged set unchanged. - Cycle + missing-file detection with a teaching error. - `describe.go`: render the merged surface (optionally annotate inherited provenance). ## Acceptance 1. `inherit` merges parent grants into child; unit tests for a read ⊂ write ⊂ admin chain. 2. Cycle and missing-ref produce clear errors. 3. Wildcard grants (#159) compose through inheritance. 4. `spec`/`base-url`/`auth` inherited unless overridden. 5. `gen`/`skew` clean, reference doc updated, conventional commit, closes via the full Forgejo URL, push to main. ## Dependencies / related - **Depends on cli-guard#159** (wildcard `"*"`) — tiers are wildcard grant sets, and #159 edits the same specverb files. Land #159 first. - **Consumer: ward#240** — the read/write/admin binaries are built from inherit-chained guardfiles. ward#240 also needs a cli-guard *release* carrying this feature before ward's go.mod can pin it.
Author
Member

🔒 Reserved by ward agent claude — container ward-cli-guard-issue-160-claude-4f11536e on host kais-macbook-pro-2.local is carrying this issue (reserved 2026-06-24T05:55:42Z). 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 claude` — container `ward-cli-guard-issue-160-claude-4f11536e` on host `kais-macbook-pro-2.local` is carrying this issue (reserved 2026-06-24T05:55:42Z). 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`
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#160
No description provided.