Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .derived/codebase-index/by-spec/003-auth-api-client.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,5 @@
"specStatus": "approved"
},
"schemaVersion": "1.1.0",
"shardHash": "e6fd7efd240ef302be413f7448761f2261659e888da91930484cbc6439de53bb"
"shardHash": "c5fef062ac8d9429215bef31321e37f07fd7c74fdb018ea77bca55f76230d39f"
}
28 changes: 16 additions & 12 deletions .derived/codebase-index/by-spec/004-governance-verbs.json
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
{
"diagnostics": {
"errors": [],
"warnings": [
{
"code": "W-001",
"message": "spec '004-governance-verbs' symbol unit 'stagecraft_cli::verbs' did not resolve"
}
]
},
"mapping": {
"dependsOn": [
"003-auth-api-client"
],
"implementingPaths": [],
"implementingPaths": [
{
"path": "src/main.rs",
"source": "spec-edge"
}
],
"resolvedUnits": [
{
"locations": [],
"locations": [
{
"file": "src/main.rs",
"span": {
"endLine": 12,
"startLine": 12
}
}
],
"ownership": true,
"sourceField": "establishes",
"unit": {
Expand All @@ -28,5 +32,5 @@
"specStatus": "approved"
},
"schemaVersion": "1.1.0",
"shardHash": "f755522484b253a2461f9038878b9ff0940af5284ac65cb95dd00b90a721b1c7"
"shardHash": "3314a5176288efdc33f1dcb517735e6ff3c09f8074431c7a51d77ff9fb1c39fc"
}
12 changes: 9 additions & 3 deletions .derived/spec-registry/by-spec/004-governance-verbs.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,25 @@
}
],
"id": "004-governance-verbs",
"implementation": "pending",
"implementation": "in-progress",
"sectionHeadings": [
"004: Governance verbs",
"1. Cross-repo dependency",
"2. Verb surface (all support --output json)",
"3. Acceptance",
"4. Out of scope"
"4. Out of scope",
"5. Endpoint map and JSON envelope (2026-07-15 amendment)",
"5.1 Endpoint map (all paths under the plane's `/api/v1`)",
"5.2 JSON envelope (`--output json`)",
"5.3 Response-shape expectations (human rendering only)",
"5.4 Watch semantics (`stamp status --watch`)",
"6. Status (2026-07-15)"
],
"specPath": "specs/004-governance-verbs/spec.md",
"status": "approved",
"summary": "The CLI face becomes useful: the stub commands from spec 002 gain real implementations over the API client, mirroring the control plane's tenants (stagecraft spec 004), factory (spec 005), and fleet (spec 006) services. Every verb has a stable JSON output shape, because spec 005 exposes these same verbs as MCP tools and the JSON is the shared contract between both faces.\n",
"title": "Governance verbs v1: tenants, stamps, fleet"
},
"shardHash": "2f0b2011cc2ce356f66a5ecc59537186442dcf760a74cce56bbb37146c63f922",
"shardHash": "da3bdd371187d7af89428ee04e060365b9583c4a3f7cfa3b2605d4a2e8cf012c",
"specVersion": "1.1.0"
}
110 changes: 109 additions & 1 deletion specs/004-governance-verbs/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ id: "004-governance-verbs"
title: "Governance verbs v1: tenants, stamps, fleet"
status: approved
created: "2026-07-14"
implementation: pending
implementation: in-progress
depends_on:
- "003-auth-api-client"
establishes:
Expand Down Expand Up @@ -74,3 +74,111 @@ target plane must surface as "not enabled on this control plane"
- Local certificate verification: when the signed-cert path lands
(enrahitu spec 012 §7, born-with certVersion 2), verification is
repo-local via pinned tenant-tail; the CLI does not wrap it.

## 5. Endpoint map and JSON envelope (2026-07-15 amendment)

§2 named the verbs but left the concrete HTTP mapping, the request
bodies, and the envelope's error arm implicit. This section inlines
them, read off stagecraft specs 004 (tenants), 005 (factory), and 006
(fleet) on 2026-07-15. §1's divergence rule still governs: if a live
plane differs, the platform spec wins and this section is re-amended,
never coded around.

### 5.1 Endpoint map (all paths under the plane's `/api/v1`)

| Verb | Method + path | Request body |
| --- | --- | --- |
| `tenants list` | GET `/tenants` | (none) |
| `tenants show <id>` | GET `/tenants/:id` | (none) |
| `tenants install-url <id>` | GET `/tenants/:id/github/install-url` | (none) |
| `stamp new` | POST `/tenants/:id/stamps` | `{appName, targetOrg, posture, frontend?}` |
| `stamp status <jobId>` | GET `/stamps/:jobId` | (none) |
| `fleet list --tenant <id>` | GET `/tenants/:id/fleet` | (none) |
| `fleet deploy` | POST `/tenants/:id/fleet` | `{name, image}` |
| `fleet update <appId>` | POST `/fleet/:appId/update` | `{image}` |
| `fleet backup <appId>` | POST `/fleet/:appId/backup` | `{}` |
| `fleet remove <appId>` | DELETE `/fleet/:appId` | `{confirm}` |

Every mapping above is verbatim from the platform specs except one:
`fleet list` -> GET `/tenants/:id/fleet`. Stagecraft spec 006 defines
the fleet collection's deploy (POST `/tenants/:id/fleet`) and the
per-app status/update/backup/remove, but names no list endpoint. The
CLI inlines the symmetric collection GET as its expectation, mirroring
the factory's `GET /tenants/:id/stamps` list; if the plane lands a
different list shape, §1 governs. `stamp new`'s `posture` is a required
field the caller must supply: the factory rejects a defaulted posture
by design (stagecraft spec 005 §3, pipeline step 3), so the CLI makes
`--posture` a required flag with no default and forwards it as
`posture`. `fleet remove`'s `confirm` carries the literal app name the
operator echoes (stagecraft spec 006 §3, destructive guard); there is
no `--force`/`--yes` shortcut.

### 5.2 JSON envelope (`--output json`)

Both faces (spec 005 reuses this) consume one stable envelope:

- Success: `{"ok": true, "data": <platform response, passed through
verbatim>}`. The CLI does not reshape the plane's payload; it wraps
it, so the envelope stays stable as platform fields grow.
- Failure: `{"ok": false, "error": {"kind": <k>, "message": <m>,
"status"?: <http status>}}`, where `kind` is one of `network`,
`unauthenticated`, `api`, `server`, `decode` (the spec 003 taxonomy).
The envelope is emitted on stdout and the process still exits 1.

In human mode, success renders as an aligned table (ids, names, status,
age); failure prints the taxonomy message to stderr and exits 1 (the
spec 002 exit-code contract). Only the request taxonomy above is
enveloped. A pre-flight failure the CLI detects before any request is
reported plainly (stderr) in both formats, never wrapped: a missing
base URL is usage (exit 2), and a missing stored credential is
operational (exit 1) with the `run login` hint, matching `whoami`.

### 5.3 Response-shape expectations (human rendering only)

The passthrough envelope means `--output json` is faithful to whatever
the plane returns. Human tables need named fields, so the CLI reads a
tolerant subset (unknown fields ignored, absent optional fields blank):
list endpoints return a JSON array of records; `tenants show` returns
one tenant carrying an `installations` array; stamp and fleet records
carry at least `id` and `status`. These are the CLI's expectation per
§1; a shape the human renderer cannot read surfaces as a `decode`
error (exit 1), which is the intended drift signal.

### 5.4 Watch semantics (`stamp status --watch`)

Poll GET `/stamps/:jobId` starting at 2s and backing off toward a 10s
cap, emitting each observed state change (a line in human mode, an
envelope in JSON mode) rather than every poll. A terminal `green`
exits 0; a terminal `failed` exits 1 (the final state is already on
stdout, so no additional stderr line). A transport error mid-poll
aborts through the normal taxonomy after the client's GET retries are
exhausted.

## 6. Status (2026-07-15)

Implemented: the full §2/§5.1 verb surface (tenants list/show/
install-url, stamp new/status with `--watch`, fleet list/deploy/update/
backup/remove) in the new `stagecraft_cli::verbs` module over the spec
003 client, which grew GET/POST/DELETE returning a passthrough
`serde_json::Value`. Human output is aligned tables; `--output json`
emits the §5.2 `{ok, data|error}` envelope. The `--posture` guard is
required with no default and the `fleet remove` confirm-name guard is
surfaced verbatim, both per §2. A missing service's 404 surfaces as
"not enabled on this control plane" (exit 1, not a crash) per §1.

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

Outstanding: the §3 manual e2e against a **live** control 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; the CLI is built for exactly that case (a missing service is
a clean exit-1 404, §1). This spec stays `implementation: in-progress`
until a live plane exists; at that point the only open item is the
live e2e (login, then a governed read/write against real tenants,
factory, and fleet services), which drops in with no code change.
Loading
Loading