guardfile: parse grant properties (key=value) for structured scoping constraints #116

Closed
opened 2026-06-08 01:10:17 +00:00 by coilysiren · 0 comments
Owner

guardfile: parse grant properties (key=value) for structured scoping constraints

The KDL Guardfile grammar parses a grant as modal verb resource [qualifiers...],
reading only positional arguments. KDL node properties (key=value) on a
grant are silently dropped. They are the natural way to express a structured
scoping constraint, distinct from a bareword qualifier:

can delete repos org="coilyco-flight-deck"

Change

http/guardfile:

  • Add Props map[string]string to Grant, populated from n.Properties() in
    parseGrant. Positional barewords stay in Qualifiers; anything with a value
    lands in Props. nil when the sentence has no properties.
  • Test TestGrantProperties asserts org="..." parses into Props{org: ...}.

This is already implemented locally (the change is in the working tree) and
drives the org-scoping demo slice: the speakeasy-guardrail overlaygen reads
Grant.Props["org"] and emits an org:<name> scope token that the ward endpoint
enforces at the call boundary. It needs an issue to land per the repo's
one-issue-per-change rule - this is it.

Drive-by also in the working tree (call out before committing)

The package move in
cli-guard issue 113
(guardfile -> http/guardfile, audit -> pkg/audit, decision/profile/
profiles -> cli/...) broke two external consumers in
speakeasy-guardrail
and speakeasy-ward,
which still imported the old paths. Both are fixed locally. Note: the reorg put
decision/profiles/profile under cli/, but speakeasy-ward (an HTTP
consumer) imports them - the cross-surface case
cli-guard issue 114
argues belongs in pkg.

## guardfile: parse grant properties (key=value) for structured scoping constraints The KDL Guardfile grammar parses a grant as `modal verb resource [qualifiers...]`, reading only positional arguments. KDL node **properties** (`key=value`) on a grant are silently dropped. They are the natural way to express a structured scoping constraint, distinct from a bareword qualifier: ```kdl can delete repos org="coilyco-flight-deck" ``` ### Change `http/guardfile`: - Add `Props map[string]string` to `Grant`, populated from `n.Properties()` in `parseGrant`. Positional barewords stay in `Qualifiers`; anything with a value lands in `Props`. nil when the sentence has no properties. - Test `TestGrantProperties` asserts `org="..."` parses into `Props{org: ...}`. This is **already implemented locally** (the change is in the working tree) and drives the org-scoping demo slice: the speakeasy-guardrail `overlaygen` reads `Grant.Props["org"]` and emits an `org:<name>` scope token that the ward endpoint enforces at the call boundary. It needs an issue to land per the repo's one-issue-per-change rule - this is it. ### Drive-by also in the working tree (call out before committing) The package move in [cli-guard issue 113](https://forgejo.coilysiren.me/coilyco-flight-deck/cli-guard/issues/113) (`guardfile` -> `http/guardfile`, `audit` -> `pkg/audit`, `decision`/`profile`/ `profiles` -> `cli/...`) broke two external consumers in [speakeasy-guardrail](https://forgejo.coilysiren.me/coilyco-proving-ground/speakeasy-cli-guard) and [speakeasy-ward](https://forgejo.coilysiren.me/coilyco-proving-ground/speakeasy-ward), which still imported the old paths. Both are fixed locally. Note: the reorg put `decision`/`profiles`/`profile` under `cli/`, but speakeasy-ward (an HTTP consumer) imports them - the cross-surface case [cli-guard issue 114](https://forgejo.coilysiren.me/coilyco-flight-deck/cli-guard/issues/114) argues belongs in `pkg`.
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#116
No description provided.