specverb M0: runtime engine (Build, Swagger-2.0 reader, expansion table, generic action, --dry-run) #87

Closed
opened 2026-06-07 01:35:22 +00:00 by coilysiren · 0 comments
Owner

Summary

The M0 runtime engine of the spec-driven verb design (the specverb package), the second half of the M0 milestone after the KDL Guardfile parser landed in #86. Builds a guarded *cli.Command tree at runtime from a parsed Guardfile plus an embedded Swagger 2.0 spec, with one generic action backing every verb.

Companion to the design + checkpoint on #75.

Scope (M0)

  • Minimal Swagger 2.0 reader (swagger.go) - reads only what the proving ops need: method, path, operationId, ordered path params, and the request-body schema's scalar fields (resolving a one-hop #/definitions $ref). Fails closed on a non-2.0 spec. Full kin-openapi openapi2conv 2.0 -> 3.x is M1.
  • Committed expansion table (expansion.go) - (verb, resource) -> {cliGroup, cliLeaf, operationId}. Deny-by-default: no row, no mount. The set of rows is the M0 allowlist. M0 covers the forgejo repo read/create/delete trio.
  • Build(Config) (specverb.go) - resolves each can grant to a concrete op and mounts a guarded leaf under the consumer's verb.Wrap. Fails closed: an unresolvable grant is an error, never a silently dropped verb. Returns the Guardfile group's leaf command (e.g. forgejo) for the consumer to mount.
  • One generic action (request.go) - path-param positionals, request-body scalars promoted to typed flags (required schema field -> required flag, unset optional omitted from the body), header-token auth via an injected TokenResolver (keeps the AWS SDK out of cli-guard), a --dry-run preview that prints the resolved request with the secret redacted and fires nothing, and live response rendering through the respfmt --query/--output rail.

Out of scope (named follow-ups, not silent gaps)

  • Guardfile -> OpenAPI Overlay -> Speakeasy lowering (the L1 interchange form) - M1.
  • Full Swagger-2.0 -> 3.x conversion - M1.
  • Embed + pin-by-hash of the spec - M4.
  • --yes destructive-confirm and the rest of the fanatical-UX pass - M2 (the descriptor already carries Destructive).
  • ward wiring of the real SSM TokenResolver and the live diff against coily's hand-written repo create - M1, in ward via go.mod replace.

Done

  • specverb/{specverb,swagger,expansion,request}.go + specverb/specverb_test.go + testdata (faithful minimal Swagger 2.0 spec + the proving Guardfile).
  • Tests: tree-mount shape, deny-by-default, swagger-2.0 gate, --dry-run (no wire, no secret resolve, redacted preview), live create (auth header, body, unset-optional omission), live delete (path-param fill, 204 confirmation), positional-arg-count validation, and composition under the real verb.Wrap audit pipeline.
  • make test / make vet / make lint green; godoc-current.txt regenerated; FEATURES + features-detail updated.
## Summary The M0 runtime engine of the spec-driven verb design (the `specverb` package), the second half of the M0 milestone after the KDL Guardfile parser landed in #86. Builds a guarded `*cli.Command` tree at runtime from a parsed Guardfile plus an embedded Swagger 2.0 spec, with one generic action backing every verb. Companion to the design + checkpoint on #75. ## Scope (M0) - **Minimal Swagger 2.0 reader** (`swagger.go`) - reads only what the proving ops need: method, path, operationId, ordered path params, and the request-body schema's scalar fields (resolving a one-hop `#/definitions` `$ref`). Fails closed on a non-2.0 spec. Full kin-openapi `openapi2conv` 2.0 -> 3.x is M1. - **Committed expansion table** (`expansion.go`) - `(verb, resource) -> {cliGroup, cliLeaf, operationId}`. Deny-by-default: no row, no mount. The set of rows is the M0 allowlist. M0 covers the forgejo repo read/create/delete trio. - **`Build(Config)`** (`specverb.go`) - resolves each `can` grant to a concrete op and mounts a guarded leaf under the consumer's `verb.Wrap`. Fails closed: an unresolvable grant is an error, never a silently dropped verb. Returns the Guardfile group's leaf command (e.g. `forgejo`) for the consumer to mount. - **One generic action** (`request.go`) - path-param positionals, request-body scalars promoted to typed flags (required schema field -> required flag, unset optional omitted from the body), header-token auth via an injected `TokenResolver` (keeps the AWS SDK out of cli-guard), a `--dry-run` preview that prints the resolved request with the secret redacted and fires nothing, and live response rendering through the `respfmt` `--query`/`--output` rail. ## Out of scope (named follow-ups, not silent gaps) - Guardfile -> OpenAPI Overlay -> Speakeasy lowering (the L1 interchange form) - M1. - Full Swagger-2.0 -> 3.x conversion - M1. - Embed + pin-by-hash of the spec - M4. - `--yes` destructive-confirm and the rest of the fanatical-UX pass - M2 (the descriptor already carries `Destructive`). - ward wiring of the real SSM `TokenResolver` and the live diff against coily's hand-written `repo create` - M1, in ward via `go.mod replace`. ## Done - `specverb/{specverb,swagger,expansion,request}.go` + `specverb/specverb_test.go` + testdata (faithful minimal Swagger 2.0 spec + the proving Guardfile). - Tests: tree-mount shape, deny-by-default, swagger-2.0 gate, `--dry-run` (no wire, no secret resolve, redacted preview), live create (auth header, body, unset-optional omission), live delete (path-param fill, 204 confirmation), positional-arg-count validation, and composition under the real `verb.Wrap` audit pipeline. - `make test` / `make vet` / `make lint` green; `godoc-current.txt` regenerated; FEATURES + features-detail updated.
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#87
No description provided.