specverb M1: wire the engine into ward, mount forgejo repo create|delete (proving slice) #62

Closed
opened 2026-06-07 01:51:49 +00:00 by coilysiren · 2 comments
Owner

Summary

Wire cli-guard's specverb engine into ward as its first consumer: embed the Forgejo Swagger 2.0 spec + a KDL Guardfile, mount ward ops forgejo repo create|delete (the self-cleaning proving pair), and diff the result live against coily's hand-written coily ops forgejo repo create. This is M1 of the spec-driven verb ladder; the engine (M0) landed in cli-guard.

  • Engine + design: cli-guard#75 (design + checkpoint), cli-guard#87 (engine, landed), cli-guard#86 (guardfile parser, landed).
  • The concrete M1 cut of the umbrella #51. #51 stays open for the broader per-binary migration (make/gh/aws/kubectl); this issue is just the forgejo proving slice that proves the engine in a real consumer.

Why ward first

ward depends on cli-guard and has zero forgejo verbs today (no ops group, no SSM resolver, no go:embed in cmd/ward/), so forgejo-in-ward is greenfield - nothing to migrate, nothing to break. coily's hand-written ops forgejo repo create is the cross-repo behavioral oracle: ward's specverb-built verb must produce the same API call.

Scope (M1)

  1. Dev-loop dependency. ward pins cli-guard v0.3.0; specverb is unreleased. Add a go.mod replace pointing at the local cli-guard checkout for M1-M3. Watch: pre-commit may flag the replace directive - tolerate or gate it in dev. Dropped and bumped to v0.4.0 at M4 (separate issue).
  2. Embed the inputs. go:embed the Forgejo swagger.v1.json (Swagger 2.0) and a Guardfile (wrap ward ops forgejo { ... can read repos; can create repos; can delete repos }) under cmd/ward/.
  3. Add the ops group. ward's root (cmd/ward/main.go) has no ops command yet. Add one and mount the *cli.Command that specverb.Build returns (the forgejo group) under it.
  4. Wire the seams into specverb.Config:
    • Guardfile - guardfile.Parse(embeddedGuardfile).
    • Spec - the embedded swagger bytes.
    • Wrap - func(s verb.Spec) cli.ActionFunc { return verb.Wrap(s, auditWriter) } (ward's audit writer).
    • Token - the real SSM TokenResolver (func(ctx, ssmPath) (string, error) over the AWS SDK). The AWS SDK lives in ward, not cli-guard - this is the injection point the engine was built around. Resolves /forgejo/api-token.
  5. Prove it.
    • dry-run first: ward ops forgejo repo create --name <x> --dry-run prints the resolved POST /user/repos with the token auth header redacted and the body assembled from flags.
    • live next, diffed against coily ops forgejo repo create for the same input (method, path, header, body must match).
    • self-cleaning integration test: create a throwaway repo, assert it exists, ward ops forgejo repo delete <owner> <repo>, assert it is gone. create exercises body-flags + token auth + the 301-refusal default; delete exercises deny-by-default mount + path-param fill.

Out of scope (named follow-ups)

  • Dropping the replace and bumping ward to cli-guard v0.4.0 - M4, separate issue, after a cli-guard tag ships specverb.
  • Fanning the Guardfile out to the wider forgejo surface ward actually needs - M3.
  • Fanatical-UX pass (--yes destructive-confirm, teaching errors, generated help) - M2, lands in specverb (cli-guard), inherited here.
  • Migrating coily's hand-written verbs to specverb - cli-guard#186, M4.

Done when

ward ops forgejo repo create|delete mount from the embedded spec + Guardfile, --dry-run works, the live create+delete pair round-trips against the real Forgejo API and matches coily's hand-written verb, and the self-cleaning integration test is green.

## Summary Wire cli-guard's `specverb` engine into ward as its first consumer: embed the Forgejo Swagger 2.0 spec + a KDL Guardfile, mount `ward ops forgejo repo create|delete` (the self-cleaning proving pair), and diff the result live against coily's hand-written `coily ops forgejo repo create`. This is M1 of the spec-driven verb ladder; the engine (M0) landed in cli-guard. - Engine + design: [cli-guard#75](https://forgejo.coilysiren.me/coilyco-flight-deck/cli-guard/issues/75) (design + checkpoint), [cli-guard#87](https://forgejo.coilysiren.me/coilyco-flight-deck/cli-guard/issues/87) (engine, landed), [cli-guard#86](https://forgejo.coilysiren.me/coilyco-flight-deck/cli-guard/issues/86) (guardfile parser, landed). - The concrete M1 cut of the umbrella #51. #51 stays open for the broader per-binary migration (make/gh/aws/kubectl); this issue is just the forgejo proving slice that proves the engine in a real consumer. ## Why ward first ward depends on cli-guard and has **zero forgejo verbs today** (no `ops` group, no SSM resolver, no `go:embed` in `cmd/ward/`), so forgejo-in-ward is greenfield - nothing to migrate, nothing to break. coily's hand-written `ops forgejo repo create` is the cross-repo behavioral oracle: ward's specverb-built verb must produce the same API call. ## Scope (M1) 1. **Dev-loop dependency.** ward pins `cli-guard v0.3.0`; specverb is unreleased. Add a `go.mod replace` pointing at the local cli-guard checkout for M1-M3. Watch: pre-commit may flag the `replace` directive - tolerate or gate it in dev. Dropped and bumped to `v0.4.0` at M4 (separate issue). 2. **Embed the inputs.** `go:embed` the Forgejo `swagger.v1.json` (Swagger 2.0) and a Guardfile (`wrap ward ops forgejo { ... can read repos; can create repos; can delete repos }`) under `cmd/ward/`. 3. **Add the `ops` group.** ward's root (`cmd/ward/main.go`) has no `ops` command yet. Add one and mount the `*cli.Command` that `specverb.Build` returns (the `forgejo` group) under it. 4. **Wire the seams into `specverb.Config`:** - `Guardfile` - `guardfile.Parse(embeddedGuardfile)`. - `Spec` - the embedded swagger bytes. - `Wrap` - `func(s verb.Spec) cli.ActionFunc { return verb.Wrap(s, auditWriter) }` (ward's audit writer). - `Token` - the **real SSM `TokenResolver`** (`func(ctx, ssmPath) (string, error)` over the AWS SDK). The AWS SDK lives in ward, not cli-guard - this is the injection point the engine was built around. Resolves `/forgejo/api-token`. 5. **Prove it.** - **dry-run** first: `ward ops forgejo repo create --name <x> --dry-run` prints the resolved `POST /user/repos` with the `token ` auth header redacted and the body assembled from flags. - **live** next, diffed against `coily ops forgejo repo create` for the same input (method, path, header, body must match). - **self-cleaning integration test:** create a throwaway repo, assert it exists, `ward ops forgejo repo delete <owner> <repo>`, assert it is gone. create exercises body-flags + token auth + the 301-refusal default; delete exercises deny-by-default mount + path-param fill. ## Out of scope (named follow-ups) - Dropping the `replace` and bumping ward to cli-guard `v0.4.0` - M4, separate issue, after a cli-guard tag ships specverb. - Fanning the Guardfile out to the wider forgejo surface ward actually needs - M3. - Fanatical-UX pass (`--yes` destructive-confirm, teaching errors, generated help) - M2, lands in specverb (cli-guard), inherited here. - Migrating coily's hand-written verbs to specverb - [cli-guard#186](https://forgejo.coilysiren.me/coilyco-bridge/coily/issues/186), M4. ## Done when `ward ops forgejo repo create|delete` mount from the embedded spec + Guardfile, `--dry-run` works, the live create+delete pair round-trips against the real Forgejo API and matches coily's hand-written verb, and the self-cleaning integration test is green.
coilysiren added
P3
and removed
P2
labels 2026-06-17 08:39:47 +00:00
Member

Re-triage (goose-triage method, claude-macos-kais-macbook-pro-2 as the judgment engine, 2026-06-24)

  • P2 - specverb M1 proving slice, committed, but likely largely landed now that ward ops forgejo exists (flag for a close/verify).
  • interactive - Engine-wiring with a live diff against coily, agent works it and a human verifies.
<!-- goose-triage --> **Re-triage** (goose-triage method, claude-macos-kais-macbook-pro-2 as the judgment engine, 2026-06-24) - **P2** - specverb M1 proving slice, committed, but likely largely landed now that ward ops forgejo exists (flag for a close/verify). - **interactive** - Engine-wiring with a live diff against coily, agent works it and a human verifies.
Member

Re-triage (goose-triage method, claude-macos-kais-macbook-pro-2 as the judgment engine, 2026-06-24)

  • P2 - specverb M1 proving slice, committed, but likely largely landed now that ward ops forgejo exists (flag for a close/verify).
  • interactive - Engine-wiring with a live diff against coily, agent works it and a human verifies.
<!-- goose-triage --> **Re-triage** (goose-triage method, claude-macos-kais-macbook-pro-2 as the judgment engine, 2026-06-24) - **P2** - specverb M1 proving slice, committed, but likely largely landed now that ward ops forgejo exists (flag for a close/verify). - **interactive** - Engine-wiring with a live diff against coily, agent works it and a human verifies.
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#62
No description provided.