Skip to content

feat(004): governance verbs (tenants, stamps, fleet) over the API client#3

Merged
bartekus merged 1 commit into
mainfrom
feat/004-governance-verbs
Jul 15, 2026
Merged

feat(004): governance verbs (tenants, stamps, fleet) over the API client#3
bartekus merged 1 commit into
mainfrom
feat/004-governance-verbs

Conversation

@bartekus

Copy link
Copy Markdown
Contributor

Summary

Spec 004: the CLI face becomes useful. The stub tenants/stamp/fleet
commands from spec 002 become real calls to the Stagecraft control
plane over the spec 003 API client, each with a stable JSON envelope
the MCP face (spec 005) will reuse.

What landed

  • New stagecraft_cli::verbs module (spec 004's establishes
    symbol): tenants (list/show/install-url, with --open), stamp
    (new/status, with --watch), fleet (list/deploy/update/backup/
    remove). Human output is aligned tables; --output json emits the
    {ok, data|error} passthrough envelope, wrapping the plane's payload
    verbatim so the contract stays stable as fields grow.
  • Generalized the API client (spec 003's api module): GET/POST/
    DELETE returning serde_json::Value, a body-carrying request path,
    an empty 2xx decoding to JSON null, GET-only retry unchanged. The
    typed whoami path routes through the same pipeline.
  • Guards are product surface: --posture is required with no
    default (the platform rejects defaulted postures); fleet remove
    carries the confirm name in the DELETE body, with no --force/--yes
    shortcut.
  • Missing-service 404 surfaces as "not enabled on this control
    plane" (exit 1, not a crash), per spec 004 §1.
  • JSON-mode failures render {ok:false,error} on stdout via a new
    AppError::Rendered (exit 1, no doubled stderr line). A stamp or
    fleet record with no readable id/status is a decode error, not a
    blank (§5.3), so --watch cannot spin on a statusless response.

Design decisions (spec 004 §5, 2026-07-15 amendment)

Endpoint shapes are grounded in stagecraft specs 004/005/006 and
inlined into §5.1 as the CLI's expectation. One endpoint is
CLI-inlined: fleet list maps to GET /tenants/:id/fleet, because
platform spec 006 defines deploy/status/update/backup/remove but no
list; the CLI adds the symmetric collection GET (mirroring
GET /tenants/:id/stamps). §1 governs if a live plane diverges.

Testing

72 tests (60 unit + 12 integration): per-verb happy path, api-error
surfacing, missing-service 404 mapping, watch-mode terminal states, the
statusless-response guard, and a JSON envelope snapshot per verb family.
The release binary was driven end-to-end against a mock plane: every
verb renders correctly in both formats, exit codes are right, the
non-idempotent DELETE is not retried, and --debug never leaks the
token.

Status: stays implementation: in-progress

The §3 manual e2e against a live plane cannot run: stagecraft's
tenants/factory/fleet services (its specs 004/005/006) are all
implementation: pending, so no reachable plane serves these
endpoints. All mock-testable acceptance holds; the live check unblocks
when a plane exists. Dated Status note added to the spec (§6).

Spec-Drift-Waiver: this PR implements the spec 004 verb surface but
necessarily edits files under the spec 002 crate scaffold and the spec
003 api module (src/api.rs, src/cli.rs, src/commands.rs, src/error.rs,
src/main.rs, tests/cli.rs). The crate's [package.metadata.spec-spine]
key couples the whole crate to spec 002, and spec 003 owns the API
client this builds on; the new logic lives in the spec-004-owned
stagecraft_cli::verbs module, while the edits to 002/003 territory are
the wiring: command arguments, dispatch, the client's new GET/POST/
DELETE verbs, and the JSON error-envelope exit path. Waiving rather
than editing the completed 002/003 specs, per the precedent set by
spec 003's PR. Approved by the repo owner.

Turn the spec 002 stub verbs into real control-plane calls on the spec
003 client. Each verb has a stable JSON envelope the MCP face (005)
reuses; guards are product surface, not options.

New stagecraft_cli::verbs module (spec 004 establishes it):
- tenants list/show/install-url (with --open)
- stamp new/status (with --watch, 2s->10s backoff, terminal green|failed)
- fleet list/deploy/update/backup/remove

Human output is aligned tables; --output json emits the {ok,data|error}
envelope, wrapping the plane's payload verbatim (passthrough) so the
contract survives field growth.

The spec 003 api module is generalized to GET/POST/DELETE returning
serde_json::Value with a body-carrying request path; an empty 2xx
decodes to JSON null; GET-only retry is unchanged (POST/DELETE are
never retried). The typed whoami path routes through the same pipeline.

Guards, per spec: --posture is required with no default (the platform
rejects defaulted postures); fleet remove echoes the confirm name in
the DELETE body, no --force/--yes. A missing service's 404 surfaces as
"not enabled on this control plane" (exit 1, not a crash), per §1.
JSON-mode failures render {ok:false,error} on stdout via a new
AppError::Rendered (exit 1, no doubled stderr line). A stamp or fleet
record with no readable id/status is a decode error, not a blank
(§5.3), so --watch cannot spin on a statusless response.

Endpoint shapes are inlined into spec 004 §5.1 from stagecraft specs
004/005/006. One is CLI-inlined: fleet list -> GET /tenants/:id/fleet,
since platform spec 006 defines no list endpoint; §1 governs divergence.

Tests: 72 (60 unit + 12 integration) covering per-verb happy path,
api-error surfacing, missing-service 404, watch terminal states, the
statusless-response guard, and JSON envelope snapshots. The release
binary was driven end-to-end against a mock plane: every verb renders
in both formats with correct exit codes, DELETE is not retried, and
--debug never leaks the token.

Stays implementation: in-progress: the §3 live-plane e2e cannot run
(stagecraft specs 004/005/006 are all implementation: pending, so no
plane serves these services yet). Dated Status note added (§6).

Includes the regenerated .derived/ shards.
@bartekus
bartekus merged commit ed010b2 into main Jul 15, 2026
2 checks passed
@bartekus
bartekus deleted the feat/004-governance-verbs branch July 15, 2026 10:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant