wicked-crew 0.7.0 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +103 -0
- package/dist/api/delivery-index.d.ts +61 -0
- package/dist/api/delivery-index.d.ts.map +1 -0
- package/dist/api/delivery-index.js +87 -0
- package/dist/api/delivery-index.js.map +1 -0
- package/dist/api/endpoint-manifest-live.d.ts +29 -0
- package/dist/api/endpoint-manifest-live.d.ts.map +1 -0
- package/dist/api/endpoint-manifest-live.js +72 -0
- package/dist/api/endpoint-manifest-live.js.map +1 -0
- package/dist/api/endpoint-manifest.d.ts +107 -0
- package/dist/api/endpoint-manifest.d.ts.map +1 -0
- package/dist/api/endpoint-manifest.js +108 -0
- package/dist/api/endpoint-manifest.js.map +1 -0
- package/dist/api/governance-wiki.d.ts +28 -0
- package/dist/api/governance-wiki.d.ts.map +1 -0
- package/dist/api/governance-wiki.js +97 -0
- package/dist/api/governance-wiki.js.map +1 -0
- package/dist/api/requirements.d.ts +7 -0
- package/dist/api/requirements.d.ts.map +1 -1
- package/dist/api/requirements.js +23 -2
- package/dist/api/requirements.js.map +1 -1
- package/dist/api/routes.d.ts +21 -16
- package/dist/api/routes.d.ts.map +1 -1
- package/dist/api/routes.js +129 -12
- package/dist/api/routes.js.map +1 -1
- package/dist/api/server.d.ts.map +1 -1
- package/dist/api/server.js +106 -5
- package/dist/api/server.js.map +1 -1
- package/dist/campaign/supervision.d.ts +296 -0
- package/dist/campaign/supervision.d.ts.map +1 -0
- package/dist/campaign/supervision.js +515 -0
- package/dist/campaign/supervision.js.map +1 -0
- package/dist/campaigns/plan.d.ts +64 -0
- package/dist/campaigns/plan.d.ts.map +1 -0
- package/dist/campaigns/plan.js +201 -0
- package/dist/campaigns/plan.js.map +1 -0
- package/dist/campaigns/routes.d.ts +28 -0
- package/dist/campaigns/routes.d.ts.map +1 -0
- package/dist/campaigns/routes.js +177 -0
- package/dist/campaigns/routes.js.map +1 -0
- package/dist/cli/index.js +13 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/core/adapter.d.ts +84 -1
- package/dist/core/adapter.d.ts.map +1 -1
- package/dist/core/adapter.js +204 -6
- package/dist/core/adapter.js.map +1 -1
- package/dist/core/deliverable-floor.d.ts +65 -13
- package/dist/core/deliverable-floor.d.ts.map +1 -1
- package/dist/core/deliverable-floor.js +96 -21
- package/dist/core/deliverable-floor.js.map +1 -1
- package/dist/interactive/bridge-pool.d.ts +24 -0
- package/dist/interactive/bridge-pool.d.ts.map +1 -1
- package/dist/interactive/bridge-pool.js +26 -2
- package/dist/interactive/bridge-pool.js.map +1 -1
- package/dist/interactive/edit-events.d.ts +3 -2
- package/dist/interactive/edit-events.d.ts.map +1 -1
- package/dist/interactive/edit-events.js +15 -7
- package/dist/interactive/edit-events.js.map +1 -1
- package/dist/projects/graph-paths.d.ts +32 -2
- package/dist/projects/graph-paths.d.ts.map +1 -1
- package/dist/projects/graph-paths.js +49 -4
- package/dist/projects/graph-paths.js.map +1 -1
- package/dist/projects/graph.d.ts +13 -1
- package/dist/projects/graph.d.ts.map +1 -1
- package/dist/projects/graph.js +73 -19
- package/dist/projects/graph.js.map +1 -1
- package/dist/projects/routes.d.ts +12 -2
- package/dist/projects/routes.d.ts.map +1 -1
- package/dist/projects/routes.js +18 -1
- package/dist/projects/routes.js.map +1 -1
- package/dist/qe/acceptance.d.ts +20 -1
- package/dist/qe/acceptance.d.ts.map +1 -1
- package/dist/qe/acceptance.js +32 -0
- package/dist/qe/acceptance.js.map +1 -1
- package/dist/qe/conformance.d.ts +158 -0
- package/dist/qe/conformance.d.ts.map +1 -0
- package/dist/qe/conformance.js +244 -0
- package/dist/qe/conformance.js.map +1 -0
- package/dist/qe/ledger.d.ts +3 -2
- package/dist/qe/ledger.d.ts.map +1 -1
- package/dist/qe/ledger.js +5 -4
- package/dist/qe/ledger.js.map +1 -1
- package/dist/studio/assets/index-C8rx24F1.js +530 -0
- package/dist/studio/assets/index-CwmIj-f2.css +32 -0
- package/dist/studio/index.html +2 -2
- package/dist/studio/testid-inventory.json +3924 -0
- package/endpoint-manifest.json +704 -0
- package/package.json +8 -5
- package/dist/studio/assets/index-8p8uwCxG.js +0 -530
- package/dist/studio/assets/index-D6S9zUtO.css +0 -32
package/README.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# wicked-crew
|
|
2
|
+
|
|
3
|
+
**The harness for your agent harnesses** — run the coding-agent CLIs you already use as governed
|
|
4
|
+
workers through durable, multi-agent workflows. Intent in, verified work out: the evaluator is
|
|
5
|
+
structurally not the creator, gates are deny-dominates, and "done" is re-derived from evidence
|
|
6
|
+
instead of asserted.
|
|
7
|
+
|
|
8
|
+
Local-first: loopback only, no accounts, no billing, no model reselling. Crew drives the agents
|
|
9
|
+
*you* already pay for, on *your* auth and *your* plan.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
Requires Node.js ≥ 22 and at least one coding-agent CLI on your machine (e.g. Claude Code, Codex).
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install -g wicked-crew
|
|
17
|
+
wicked-crew serve
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Or without installing:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npx wicked-crew serve
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Or use the family installer — [`npx wicked-installer`](https://www.npmjs.com/package/wicked-installer)
|
|
27
|
+
installs/updates the whole wicked-\* family, crew included.
|
|
28
|
+
|
|
29
|
+
`serve` starts the daemon on `http://127.0.0.1:7701` (override with `--port` or `CREW_PORT`) and
|
|
30
|
+
serves the bundled **wicked-studio** browser console same-origin — open the URL and you have the
|
|
31
|
+
control room: launch and steer runs, answer human gates, browse projects and evidence, watch live
|
|
32
|
+
engine events. Durable state (runs, evidence, event log) lives in `~/.wicked-crew/` (`--db`
|
|
33
|
+
overrides).
|
|
34
|
+
|
|
35
|
+
## Quickstart
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# 1. Start the daemon (leave it running)
|
|
39
|
+
wicked-crew serve
|
|
40
|
+
|
|
41
|
+
# 2. Launch a governed run — from the console at http://127.0.0.1:7701,
|
|
42
|
+
# or headless over the API:
|
|
43
|
+
curl -X POST http://127.0.0.1:7701/api/v1/runs \
|
|
44
|
+
-H 'content-type: application/json' \
|
|
45
|
+
-d '{"problem": "Add input validation to the signup form", "workflow": "feature"}'
|
|
46
|
+
|
|
47
|
+
# 3. Watch it move through the workflow's phases; approve or reject at the gates.
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
One run token moves through the workflow's phases with a gate between every one — e.g. for
|
|
51
|
+
`feature`: clarify → design → build → adversarial-review → test → review. Each phase is executed
|
|
52
|
+
by a versioned skill on an assigned CLI worker; the gates decide, and every decision is audited.
|
|
53
|
+
|
|
54
|
+
## The governed-run model
|
|
55
|
+
|
|
56
|
+
- **You own the workflow; the agent owns the work.** The orchestrator sequences phases and holds
|
|
57
|
+
the gates; the agent does the coding inside a phase.
|
|
58
|
+
- **Workflows are data.** `feature` / `bug` / `migration` ship built-in; new ones are drop-in JSON
|
|
59
|
+
files, not code.
|
|
60
|
+
- **Gates are real, not self-graded.** Phase transitions resolve **deny-dominates** on a
|
|
61
|
+
deterministic structural floor, with an independent evaluator seat that reads cold evidence
|
|
62
|
+
only — a model may fail a gate, never solely approve one.
|
|
63
|
+
- **Evidence, not assertion.** "Done" is re-derived from evidence at the gate, never claimed by
|
|
64
|
+
the agent that did the work.
|
|
65
|
+
|
|
66
|
+
The engine underneath is [wicked-core](https://github.com/mikeparcewski/wicked-core) (Rust,
|
|
67
|
+
single-writer), embedded via the `wicked-core-ts` napi bridge — the daemon is a thin REST+WS
|
|
68
|
+
layer over it.
|
|
69
|
+
|
|
70
|
+
## The acceptance gate
|
|
71
|
+
|
|
72
|
+
`GET /api/v1/runs/:id/acceptance` answers "does the QE evidence ledger accept this run's work?" —
|
|
73
|
+
crew's machine gate, absorbed from the retired wicked-testing product. <!-- historical --> It reads the repo's
|
|
74
|
+
evidence ledger (a wicked-ledger store at `<repo>/.wicked-qe/`, written by wicked-garden's QE
|
|
75
|
+
skills; legacy `.wicked-testing/` ledgers are still read) and resolves the workflow's acceptance
|
|
76
|
+
requirement **deny-dominates**: only a `PASS` verdict satisfies it. `FAIL`, `CONDITIONAL`,
|
|
77
|
+
`PARTIAL`, `INCONCLUSIVE`, a missing ledger, or a missing verdict each deny with their own named
|
|
78
|
+
reason — no evidence is never a pass. The route always returns 200 for a known run ("no verdict"
|
|
79
|
+
is a real answer about the gate, not an error); `?qeRun=<id>` pins the read to one QE run.
|
|
80
|
+
|
|
81
|
+
## CLI
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
wicked-crew serve|start|resume|gate|status|mcp
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
| command | what it does |
|
|
88
|
+
|---|---|
|
|
89
|
+
| `serve` | run the daemon: REST `/api/v1` + WS `/ws` + the bundled studio console |
|
|
90
|
+
| `start` | boot and launch a run headless (`--problem`, `--workflow`, `--repo`) |
|
|
91
|
+
| `resume` | resume a persisted run (`--session <id>`) |
|
|
92
|
+
| `gate` | answer a pending human gate from the terminal |
|
|
93
|
+
| `status` | inspect run state |
|
|
94
|
+
| `mcp` | stdio MCP server — crew-as-a-tool for coding agents |
|
|
95
|
+
|
|
96
|
+
## Links
|
|
97
|
+
|
|
98
|
+
- Repo: <https://github.com/mikeparcewski/wicked-crew>
|
|
99
|
+
- Site & docs: <https://wc.wickedagile.com>
|
|
100
|
+
- Wire contract (every `/api/v1` + `/ws` shape, types-only): [`wicked-crew-api-types`](https://www.npmjs.com/package/wicked-crew-api-types)
|
|
101
|
+
- Engine: [wicked-core](https://github.com/mikeparcewski/wicked-core) · Console: [wicked-studio](https://github.com/mikeparcewski/wicked-studio)
|
|
102
|
+
|
|
103
|
+
MIT
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The run→delivered-PR index (CREW-UX-8, crew#321).
|
|
3
|
+
*
|
|
4
|
+
* `LaunchRunBody.deliver?: 'pr'` is input-only; the deliver phase computes the PR URL
|
|
5
|
+
* (`core/deliver.ts` — re-derived from the remote, refused without one) and prints it as the
|
|
6
|
+
* unit's last output line, which is console text, not a wire. This index is the read-side
|
|
7
|
+
* latency layer that lets the run DTOs echo `session.delivery` on BOTH `GET /runs` and
|
|
8
|
+
* `GET /runs/:id` without a `workOutput` read per request — the persisted field that makes a
|
|
9
|
+
* list surface's "4 of 5 siblings delivered" rollup affordable without an N-fetch fan-out
|
|
10
|
+
* (wicked-studio#27).
|
|
11
|
+
*
|
|
12
|
+
* Mirrors `RetryIndex`/`GuidanceIndex`'s posture exactly: the DURABLE record is the audit
|
|
13
|
+
* trail (the engine run record has no such field) — action `run.delivered`, `detail.url` —
|
|
14
|
+
* hydrated once at server start (best-effort: a missing or unreadable trail leaves deliveries
|
|
15
|
+
* blank, the pre-#321 behavior, not an error) and updated at the same post-terminal point
|
|
16
|
+
* that writes the audit entry.
|
|
17
|
+
*
|
|
18
|
+
* KNOWN LIMIT (stated in the contract too): runs that delivered before this landed have no
|
|
19
|
+
* `run.delivered` entry and carry no field. Do NOT backfill by scanning `work_output` at
|
|
20
|
+
* hydrate — that is an unbounded boot cost. Historical runs still resolve their URL through
|
|
21
|
+
* the per-run output endpoint.
|
|
22
|
+
*/
|
|
23
|
+
import type { AuditLog } from './audit.js';
|
|
24
|
+
import type { SessionView, WorkUnit } from '../core/types.js';
|
|
25
|
+
/** The wire shape `AgentSession.delivery` carries (api-types 0.11.0). */
|
|
26
|
+
export interface SessionDelivery {
|
|
27
|
+
kind: 'pull_request';
|
|
28
|
+
url: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The PR URL in a deliver transcript — crew's own extraction, mirrored
|
|
32
|
+
* (`core/deliver.ts`: `grep -Eo 'https://[^[:space:]]+/pull/[0-9]+' | tail -1`). Requiring
|
|
33
|
+
* the digits keeps `…/pull/new/<branch>` — the create-PR form git prints on every push —
|
|
34
|
+
* from ever matching; the LAST match wins, same as `tail -1`.
|
|
35
|
+
*/
|
|
36
|
+
export declare function prUrlFrom(text: string): string | null;
|
|
37
|
+
/**
|
|
38
|
+
* This run's deliver unit, or `null`. The composed id suffix (`<base>:deliver`) is the
|
|
39
|
+
* primary key; the `tool_cmd` probe is the fallback for an operator OVERLAY that carried the
|
|
40
|
+
* deliver phase under its own name — do NOT key on `workflow_id`, which is plain for
|
|
41
|
+
* overlay-carried deliver phases (crew#321).
|
|
42
|
+
*/
|
|
43
|
+
export declare function deliverUnitOf(view: SessionView): WorkUnit | null;
|
|
44
|
+
export declare class DeliveryIndex {
|
|
45
|
+
private readonly runToUrl;
|
|
46
|
+
/**
|
|
47
|
+
* Load deliveries from EVERY `run.delivered` entry in the trail — exhaustively, not capped
|
|
48
|
+
* (BRIEF-UX-002 C5, the same defect class as `RetryIndex.hydrate`: a durable record must
|
|
49
|
+
* not vanish because 1000+ newer writes landed on top of it). The trail answers newest
|
|
50
|
+
* first, so the FIRST entry seen per run wins; older entries for the same run are
|
|
51
|
+
* superseded and skipped. Still best-effort, like its siblings; cost is one full-file scan
|
|
52
|
+
* at boot. (Third exhaustive trail scan at boot, after RetryIndex and GuidanceIndex — a
|
|
53
|
+
* FOURTH should trigger consolidating them into one pass, per crew#321.)
|
|
54
|
+
*/
|
|
55
|
+
hydrate(audit: AuditLog, log?: (msg: string) => void): Promise<void>;
|
|
56
|
+
/** Record the run's delivered PR URL (idempotent — the newest write wins). */
|
|
57
|
+
set(runId: string, url: string): void;
|
|
58
|
+
/** The delivery for this run, or `undefined` (the DTO spells that as an ABSENT field). */
|
|
59
|
+
deliveryFor(runId: string): SessionDelivery | undefined;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=delivery-index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delivery-index.d.ts","sourceRoot":"","sources":["../../src/api/delivery-index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE9D,yEAAyE;AACzE,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGrD;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,WAAW,GAAG,QAAQ,GAAG,IAAI,CAIhE;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA6B;IAEtD;;;;;;;;OAQG;IACG,OAAO,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAsB1E,8EAA8E;IAC9E,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAIrC,0FAA0F;IAC1F,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS;CAIxD"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The run→delivered-PR index (CREW-UX-8, crew#321).
|
|
3
|
+
*
|
|
4
|
+
* `LaunchRunBody.deliver?: 'pr'` is input-only; the deliver phase computes the PR URL
|
|
5
|
+
* (`core/deliver.ts` — re-derived from the remote, refused without one) and prints it as the
|
|
6
|
+
* unit's last output line, which is console text, not a wire. This index is the read-side
|
|
7
|
+
* latency layer that lets the run DTOs echo `session.delivery` on BOTH `GET /runs` and
|
|
8
|
+
* `GET /runs/:id` without a `workOutput` read per request — the persisted field that makes a
|
|
9
|
+
* list surface's "4 of 5 siblings delivered" rollup affordable without an N-fetch fan-out
|
|
10
|
+
* (wicked-studio#27).
|
|
11
|
+
*
|
|
12
|
+
* Mirrors `RetryIndex`/`GuidanceIndex`'s posture exactly: the DURABLE record is the audit
|
|
13
|
+
* trail (the engine run record has no such field) — action `run.delivered`, `detail.url` —
|
|
14
|
+
* hydrated once at server start (best-effort: a missing or unreadable trail leaves deliveries
|
|
15
|
+
* blank, the pre-#321 behavior, not an error) and updated at the same post-terminal point
|
|
16
|
+
* that writes the audit entry.
|
|
17
|
+
*
|
|
18
|
+
* KNOWN LIMIT (stated in the contract too): runs that delivered before this landed have no
|
|
19
|
+
* `run.delivered` entry and carry no field. Do NOT backfill by scanning `work_output` at
|
|
20
|
+
* hydrate — that is an unbounded boot cost. Historical runs still resolve their URL through
|
|
21
|
+
* the per-run output endpoint.
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* The PR URL in a deliver transcript — crew's own extraction, mirrored
|
|
25
|
+
* (`core/deliver.ts`: `grep -Eo 'https://[^[:space:]]+/pull/[0-9]+' | tail -1`). Requiring
|
|
26
|
+
* the digits keeps `…/pull/new/<branch>` — the create-PR form git prints on every push —
|
|
27
|
+
* from ever matching; the LAST match wins, same as `tail -1`.
|
|
28
|
+
*/
|
|
29
|
+
export function prUrlFrom(text) {
|
|
30
|
+
const matches = text.match(/https:\/\/\S+\/pull\/\d+/g);
|
|
31
|
+
return matches === null ? null : (matches[matches.length - 1] ?? null);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* This run's deliver unit, or `null`. The composed id suffix (`<base>:deliver`) is the
|
|
35
|
+
* primary key; the `tool_cmd` probe is the fallback for an operator OVERLAY that carried the
|
|
36
|
+
* deliver phase under its own name — do NOT key on `workflow_id`, which is plain for
|
|
37
|
+
* overlay-carried deliver phases (crew#321).
|
|
38
|
+
*/
|
|
39
|
+
export function deliverUnitOf(view) {
|
|
40
|
+
const byId = view.units.find((u) => u.id.endsWith(':deliver'));
|
|
41
|
+
if (byId !== undefined)
|
|
42
|
+
return byId;
|
|
43
|
+
return view.units.find((u) => (u.tool_cmd ?? []).join(' ').includes('gh pr create')) ?? null;
|
|
44
|
+
}
|
|
45
|
+
export class DeliveryIndex {
|
|
46
|
+
runToUrl = new Map();
|
|
47
|
+
/**
|
|
48
|
+
* Load deliveries from EVERY `run.delivered` entry in the trail — exhaustively, not capped
|
|
49
|
+
* (BRIEF-UX-002 C5, the same defect class as `RetryIndex.hydrate`: a durable record must
|
|
50
|
+
* not vanish because 1000+ newer writes landed on top of it). The trail answers newest
|
|
51
|
+
* first, so the FIRST entry seen per run wins; older entries for the same run are
|
|
52
|
+
* superseded and skipped. Still best-effort, like its siblings; cost is one full-file scan
|
|
53
|
+
* at boot. (Third exhaustive trail scan at boot, after RetryIndex and GuidanceIndex — a
|
|
54
|
+
* FOURTH should trigger consolidating them into one pass, per crew#321.)
|
|
55
|
+
*/
|
|
56
|
+
async hydrate(audit, log) {
|
|
57
|
+
try {
|
|
58
|
+
const seen = new Set();
|
|
59
|
+
for (const entry of await audit.readAll({ action: 'run.delivered' })) {
|
|
60
|
+
if (typeof entry.runId !== 'string')
|
|
61
|
+
continue;
|
|
62
|
+
if (seen.has(entry.runId))
|
|
63
|
+
continue;
|
|
64
|
+
// Newest entry decides, even when malformed — marking the run seen BEFORE the url
|
|
65
|
+
// check keeps a corrupt newest write from resurrecting an older one (the #312 rule).
|
|
66
|
+
seen.add(entry.runId);
|
|
67
|
+
const url = entry.detail?.['url'];
|
|
68
|
+
if (typeof url !== 'string' || url === '')
|
|
69
|
+
continue;
|
|
70
|
+
this.runToUrl.set(entry.runId, url);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
log?.(`[runs] delivery-index hydrate failed (prior runs read as undelivered until restart): ${err instanceof Error ? err.message : String(err)}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/** Record the run's delivered PR URL (idempotent — the newest write wins). */
|
|
78
|
+
set(runId, url) {
|
|
79
|
+
this.runToUrl.set(runId, url);
|
|
80
|
+
}
|
|
81
|
+
/** The delivery for this run, or `undefined` (the DTO spells that as an ABSENT field). */
|
|
82
|
+
deliveryFor(runId) {
|
|
83
|
+
const url = this.runToUrl.get(runId);
|
|
84
|
+
return url === undefined ? undefined : { kind: 'pull_request', url };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=delivery-index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delivery-index.js","sourceRoot":"","sources":["../../src/api/delivery-index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAWH;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC;IACxD,OAAO,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC;AACzE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,IAAiB;IAC7C,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC;IAC/D,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACpC,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC,IAAI,IAAI,CAAC;AAC/F,CAAC;AAED,MAAM,OAAO,aAAa;IACP,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAC;IAEtD;;;;;;;;OAQG;IACH,KAAK,CAAC,OAAO,CAAC,KAAe,EAAE,GAA2B;QACxD,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;YAC/B,KAAK,MAAM,KAAK,IAAI,MAAM,KAAK,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC,EAAE,CAAC;gBACrE,IAAI,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ;oBAAE,SAAS;gBAC9C,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC;oBAAE,SAAS;gBACpC,kFAAkF;gBAClF,qFAAqF;gBACrF,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;gBACtB,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC;gBAClC,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,EAAE;oBAAE,SAAS;gBACpD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;YACtC,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,GAAG,EAAE,CACH,wFACE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CACjD,EAAE,CACH,CAAC;QACJ,CAAC;IACH,CAAC;IAED,8EAA8E;IAC9E,GAAG,CAAC,KAAa,EAAE,GAAW;QAC5B,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IAChC,CAAC;IAED,0FAA0F;IAC1F,WAAW,CAAC,KAAa;QACvB,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACrC,OAAO,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,GAAG,EAAE,CAAC;IACvE,CAAC;CACF"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the LIVE endpoint manifest by booting the real server assembly (TH-11).
|
|
3
|
+
*
|
|
4
|
+
* A separate module from endpoint-manifest.ts on purpose: this imports `createServer` while
|
|
5
|
+
* server.ts imports the hook installer — folding both into one file would make the cycle
|
|
6
|
+
* structural instead of avoiding it.
|
|
7
|
+
*
|
|
8
|
+
* # Why boot `createServer` rather than re-list routes by hand
|
|
9
|
+
*
|
|
10
|
+
* The manifest's whole value is that it cannot drift from the daemon: it is read from the same
|
|
11
|
+
* `onRoute` hook, over the same registration calls (`registerRoutes`, project routes, terminal
|
|
12
|
+
* WS, `/ws`, interactive event routes), that the served daemon runs. A hand-maintained list would
|
|
13
|
+
* be a second spelling of the route table — the exact thing the manifest exists to end.
|
|
14
|
+
*
|
|
15
|
+
* # What is faked, and why it is safe
|
|
16
|
+
*
|
|
17
|
+
* Route REGISTRATION never calls the engine — only handlers do, and no request is ever injected
|
|
18
|
+
* here. The adapter stub answers the three things `createServer` itself asks at boot:
|
|
19
|
+
* `getSettings()` (worker-config root export), `projectsSupported()` → false (skips membership
|
|
20
|
+
* hydration), and `stub: true` (any answering seam that were ever armed would refuse rather than
|
|
21
|
+
* subscribe). Every optional seam is disabled explicitly, the audit trail goes to a temp file
|
|
22
|
+
* (never `~/.wicked-crew/audit.log`), and `studioRoot` points at a nonexistent directory so the
|
|
23
|
+
* server boots HEADLESS — deliberately: the static wildcard route depends on whether a studio
|
|
24
|
+
* bundle is installed on the generating machine, and a committed manifest must not vary by
|
|
25
|
+
* install state. The manifest documents the API + WS surface.
|
|
26
|
+
*/
|
|
27
|
+
import { type EndpointManifest } from './endpoint-manifest.js';
|
|
28
|
+
export declare function collectLiveEndpointManifest(): Promise<EndpointManifest>;
|
|
29
|
+
//# sourceMappingURL=endpoint-manifest-live.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"endpoint-manifest-live.d.ts","sourceRoot":"","sources":["../../src/api/endpoint-manifest-live.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAQH,OAAO,EAAyB,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAEtF,wBAAsB,2BAA2B,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAmC7E"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the LIVE endpoint manifest by booting the real server assembly (TH-11).
|
|
3
|
+
*
|
|
4
|
+
* A separate module from endpoint-manifest.ts on purpose: this imports `createServer` while
|
|
5
|
+
* server.ts imports the hook installer — folding both into one file would make the cycle
|
|
6
|
+
* structural instead of avoiding it.
|
|
7
|
+
*
|
|
8
|
+
* # Why boot `createServer` rather than re-list routes by hand
|
|
9
|
+
*
|
|
10
|
+
* The manifest's whole value is that it cannot drift from the daemon: it is read from the same
|
|
11
|
+
* `onRoute` hook, over the same registration calls (`registerRoutes`, project routes, terminal
|
|
12
|
+
* WS, `/ws`, interactive event routes), that the served daemon runs. A hand-maintained list would
|
|
13
|
+
* be a second spelling of the route table — the exact thing the manifest exists to end.
|
|
14
|
+
*
|
|
15
|
+
* # What is faked, and why it is safe
|
|
16
|
+
*
|
|
17
|
+
* Route REGISTRATION never calls the engine — only handlers do, and no request is ever injected
|
|
18
|
+
* here. The adapter stub answers the three things `createServer` itself asks at boot:
|
|
19
|
+
* `getSettings()` (worker-config root export), `projectsSupported()` → false (skips membership
|
|
20
|
+
* hydration), and `stub: true` (any answering seam that were ever armed would refuse rather than
|
|
21
|
+
* subscribe). Every optional seam is disabled explicitly, the audit trail goes to a temp file
|
|
22
|
+
* (never `~/.wicked-crew/audit.log`), and `studioRoot` points at a nonexistent directory so the
|
|
23
|
+
* server boots HEADLESS — deliberately: the static wildcard route depends on whether a studio
|
|
24
|
+
* bundle is installed on the generating machine, and a committed manifest must not vary by
|
|
25
|
+
* install state. The manifest documents the API + WS surface.
|
|
26
|
+
*/
|
|
27
|
+
import { mkdtempSync, rmSync } from 'node:fs';
|
|
28
|
+
import { tmpdir } from 'node:os';
|
|
29
|
+
import { join } from 'node:path';
|
|
30
|
+
import { createServer } from './server.js';
|
|
31
|
+
import { buildEndpointManifest } from './endpoint-manifest.js';
|
|
32
|
+
export async function collectLiveEndpointManifest() {
|
|
33
|
+
// Deterministic boot: auth mode is pinned OFF via options (no env/file resolution), and the
|
|
34
|
+
// logger is silenced for the duration — this runs inside test output and CI logs.
|
|
35
|
+
const savedLogLevel = process.env['LOG_LEVEL'];
|
|
36
|
+
process.env['LOG_LEVEL'] = 'silent';
|
|
37
|
+
const scratch = mkdtempSync(join(tmpdir(), 'crew-endpoint-manifest-'));
|
|
38
|
+
const adapter = {
|
|
39
|
+
stub: true,
|
|
40
|
+
projectsSupported: () => false,
|
|
41
|
+
getSettings: async () => ({}),
|
|
42
|
+
// The daemon's single CoreEvent fan-out subscribes at boot; no event ever arrives here.
|
|
43
|
+
onEvent: () => () => { },
|
|
44
|
+
};
|
|
45
|
+
try {
|
|
46
|
+
const app = await createServer(adapter, {
|
|
47
|
+
auth: { mode: 'off' },
|
|
48
|
+
auditPath: join(scratch, 'audit.log'),
|
|
49
|
+
projectEvents: { disabled: true },
|
|
50
|
+
interactiveWsRelay: { disabled: true },
|
|
51
|
+
seatHealthProbe: { enabled: false },
|
|
52
|
+
stallWatchdog: { enabled: false },
|
|
53
|
+
// Nonexistent on purpose — headless boot; see the module header.
|
|
54
|
+
studioRoot: join(scratch, 'no-studio-bundle'),
|
|
55
|
+
});
|
|
56
|
+
try {
|
|
57
|
+
await app.ready();
|
|
58
|
+
return buildEndpointManifest(app.endpointManifest ?? []);
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
await app.close();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
if (savedLogLevel === undefined)
|
|
66
|
+
delete process.env['LOG_LEVEL'];
|
|
67
|
+
else
|
|
68
|
+
process.env['LOG_LEVEL'] = savedLogLevel;
|
|
69
|
+
rmSync(scratch, { recursive: true, force: true });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=endpoint-manifest-live.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"endpoint-manifest-live.js","sourceRoot":"","sources":["../../src/api/endpoint-manifest-live.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGjC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,qBAAqB,EAAyB,MAAM,wBAAwB,CAAC;AAEtF,MAAM,CAAC,KAAK,UAAU,2BAA2B;IAC/C,4FAA4F;IAC5F,kFAAkF;IAClF,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAC/C,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,GAAG,QAAQ,CAAC;IACpC,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,yBAAyB,CAAC,CAAC,CAAC;IACvE,MAAM,OAAO,GAAG;QACd,IAAI,EAAE,IAAI;QACV,iBAAiB,EAAE,GAAG,EAAE,CAAC,KAAK;QAC9B,WAAW,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;QAC7B,wFAAwF;QACxF,OAAO,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE,GAAE,CAAC;KACE,CAAC;IAC5B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,YAAY,CAAC,OAAO,EAAE;YACtC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE;YACrB,SAAS,EAAE,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC;YACrC,aAAa,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE;YACjC,kBAAkB,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE;YACtC,eAAe,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE;YACnC,aAAa,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE;YACjC,iEAAiE;YACjE,UAAU,EAAE,IAAI,CAAC,OAAO,EAAE,kBAAkB,CAAC;SAC9C,CAAC,CAAC;QACH,IAAI,CAAC;YACH,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC;YAClB,OAAO,qBAAqB,CAAC,GAAG,CAAC,gBAAgB,IAAI,EAAE,CAAC,CAAC;QAC3D,CAAC;gBAAS,CAAC;YACT,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC;QACpB,CAAC;IACH,CAAC;YAAS,CAAC;QACT,IAAI,aAAa,KAAK,SAAS;YAAE,OAAO,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;;YAC5D,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,GAAG,aAAa,CAAC;QAC9C,MAAM,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACpD,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The endpoint manifest (TH-11) — crew's route table as a MACHINE-READABLE build artifact.
|
|
3
|
+
*
|
|
4
|
+
* # Why this exists
|
|
5
|
+
*
|
|
6
|
+
* routes.ts declares zero fastify schemas (verified: grep `schema:` = 0), no OpenAPI spec exists
|
|
7
|
+
* anywhere in crew, and `wicked-crew-api-types` is types-only with no endpoint-to-type binding —
|
|
8
|
+
* so nothing machine-readable said which methods+paths this daemon serves, what a request body is
|
|
9
|
+
* called, or which status codes a route can answer. Request validation is NOT a vacuum (13
|
|
10
|
+
* `.strict()` zod objects guard the bodies), but zod objects are route-internal: a test harness,
|
|
11
|
+
* a drift check, or an API-test generator had nothing to read. With no fastify schemas declared,
|
|
12
|
+
* `@fastify/swagger` has nothing to generate from either — the `onRoute` hook IS the cheap path.
|
|
13
|
+
*
|
|
14
|
+
* # How it works
|
|
15
|
+
*
|
|
16
|
+
* {@link installEndpointManifestHook} registers a fastify `onRoute` hook (added by `createServer`
|
|
17
|
+
* BEFORE any route registration, so every route the daemon serves is seen) that accumulates one
|
|
18
|
+
* {@link EndpointEntry} per method+path. Two declaration channels feed the type/status fields:
|
|
19
|
+
*
|
|
20
|
+
* 1. `config.manifest` on the route options ({@link ManifestRouteConfig}) — the explicit
|
|
21
|
+
* channel. Names should reference `wicked-crew-api-types` exports where one exists
|
|
22
|
+
* (`LaunchRunBody`, `GateDecision`, …) so the manifest binds endpoints to the published
|
|
23
|
+
* wire contract; a structural spelling (`'{ runId: string }'`) is the honest fallback for
|
|
24
|
+
* wire shapes the contract has no name for.
|
|
25
|
+
* 2. `schema.response` keys, when fastify schemas start existing (the longer-term
|
|
26
|
+
* zod-to-json-schema path) — status codes are folded in automatically, so migrating a route
|
|
27
|
+
* to real fastify schemas enriches the manifest without touching this module.
|
|
28
|
+
*
|
|
29
|
+
* Routes that declare neither still land in the manifest with `requestType`/`responseType` null
|
|
30
|
+
* and `statusCodes: []` — the manifest records "where declared", never invents.
|
|
31
|
+
*
|
|
32
|
+
* # What consumes it
|
|
33
|
+
*
|
|
34
|
+
* - `scripts/generate-endpoint-manifest.ts` (npm run manifest:endpoints) writes the committed
|
|
35
|
+
* `endpoint-manifest.json` at the package root — the build artifact.
|
|
36
|
+
* - `tests/endpoint-manifest.test.ts` rebuilds the live route table and fails on ANY drift from
|
|
37
|
+
* the committed file, so an added/removed/renamed endpoint fails CI until the manifest is
|
|
38
|
+
* regenerated and the diff reviewed. The manifest diff IS the API regression trigger.
|
|
39
|
+
* - `scripts/generate-api-tests.ts` derives positive + 400/404/409 negative API tests from the
|
|
40
|
+
* committed manifest (see `tests/generated/`).
|
|
41
|
+
*
|
|
42
|
+
* HEAD entries are dropped: fastify v5 auto-exposes a HEAD twin for every GET
|
|
43
|
+
* (`exposeHeadRoutes`), which would double the GET surface with rows nobody declared.
|
|
44
|
+
*/
|
|
45
|
+
import type { FastifyInstance } from 'fastify';
|
|
46
|
+
/** The explicit per-route declaration channel — `{ config: { manifest: {...} } }` on a route. */
|
|
47
|
+
export interface ManifestRouteConfig {
|
|
48
|
+
/** Request-body type name — a `wicked-crew-api-types` export where one exists. */
|
|
49
|
+
requestType?: string;
|
|
50
|
+
/** Success-response type name, or a structural spelling when the contract has no name for it. */
|
|
51
|
+
responseType?: string;
|
|
52
|
+
/** Every status code this route answers on purpose (success + named error codes). */
|
|
53
|
+
statusCodes?: number[];
|
|
54
|
+
}
|
|
55
|
+
declare module 'fastify' {
|
|
56
|
+
interface FastifyContextConfig {
|
|
57
|
+
/** TH-11 — this route's row in the committed endpoint manifest. */
|
|
58
|
+
manifest?: ManifestRouteConfig;
|
|
59
|
+
}
|
|
60
|
+
interface FastifyInstance {
|
|
61
|
+
/** Accumulated by `createServer`'s onRoute hook; read after `app.ready()`. */
|
|
62
|
+
endpointManifest?: EndpointEntry[];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** One method+path the daemon serves. */
|
|
66
|
+
export interface EndpointEntry {
|
|
67
|
+
method: string;
|
|
68
|
+
path: string;
|
|
69
|
+
/** Declared request-body type name, or null — "where declared", never invented. */
|
|
70
|
+
requestType: string | null;
|
|
71
|
+
/** Declared response type name, or null. */
|
|
72
|
+
responseType: string | null;
|
|
73
|
+
/** Declared status codes (config.manifest + fastify schema.response keys), sorted. */
|
|
74
|
+
statusCodes: number[];
|
|
75
|
+
/** Present (true) only on websocket routes (`/ws`, `/ws/terminals/:id`). */
|
|
76
|
+
websocket?: boolean;
|
|
77
|
+
}
|
|
78
|
+
/** The committed artifact's shape (endpoint-manifest.json). */
|
|
79
|
+
export interface EndpointManifest {
|
|
80
|
+
version: 1;
|
|
81
|
+
/**
|
|
82
|
+
* The `wicked-crew-api-types` version this route table was generated against — the published
|
|
83
|
+
* wire contract the type names bind to. Evidence manifests that cite endpoints should carry
|
|
84
|
+
* this (recon R8/R11: api-types drift is live; studio pinned ^0.8.0 against 0.10.0).
|
|
85
|
+
*/
|
|
86
|
+
apiTypesVersion: string;
|
|
87
|
+
/** Sorted by path, then method — a stable order so drift diffs are readable. */
|
|
88
|
+
endpoints: EndpointEntry[];
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Register the accumulating `onRoute` hook. MUST run before any route is registered — fastify
|
|
92
|
+
* only replays `onRoute` for routes added after the hook exists. Returns the live array the hook
|
|
93
|
+
* appends to; `createServer` exposes it as `app.endpointManifest`.
|
|
94
|
+
*/
|
|
95
|
+
export declare function installEndpointManifestHook(app: FastifyInstance): EndpointEntry[];
|
|
96
|
+
/**
|
|
97
|
+
* The published wire contract's version, read from the actually-installed dependency.
|
|
98
|
+
*
|
|
99
|
+
* NOT `require('wicked-crew-api-types/package.json')`: that package's exports map exposes only
|
|
100
|
+
* `.` (types-only, no runtime condition), so both the subpath and a bare resolve are refused.
|
|
101
|
+
* Walking the resolver's candidate directories and reading the file with fs sidesteps the
|
|
102
|
+
* exports map while still honoring the real resolution order (workspace link included).
|
|
103
|
+
*/
|
|
104
|
+
export declare function apiTypesVersion(): string;
|
|
105
|
+
/** Entries → the committed artifact: stable order, contract version stamped. */
|
|
106
|
+
export declare function buildEndpointManifest(entries: EndpointEntry[]): EndpointManifest;
|
|
107
|
+
//# sourceMappingURL=endpoint-manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"endpoint-manifest.d.ts","sourceRoot":"","sources":["../../src/api/endpoint-manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAgB,MAAM,SAAS,CAAC;AAE7D,iGAAiG;AACjG,MAAM,WAAW,mBAAmB;IAClC,kFAAkF;IAClF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iGAAiG;IACjG,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qFAAqF;IACrF,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,OAAO,QAAQ,SAAS,CAAC;IACvB,UAAU,oBAAoB;QAC5B,mEAAmE;QACnE,QAAQ,CAAC,EAAE,mBAAmB,CAAC;KAChC;IACD,UAAU,eAAe;QACvB,8EAA8E;QAC9E,gBAAgB,CAAC,EAAE,aAAa,EAAE,CAAC;KACpC;CACF;AAED,yCAAyC;AACzC,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,mFAAmF;IACnF,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,4CAA4C;IAC5C,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,sFAAsF;IACtF,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,4EAA4E;IAC5E,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,+DAA+D;AAC/D,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,CAAC,CAAC;IACX;;;;OAIG;IACH,eAAe,EAAE,MAAM,CAAC;IACxB,gFAAgF;IAChF,SAAS,EAAE,aAAa,EAAE,CAAC;CAC5B;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,eAAe,GAAG,aAAa,EAAE,CAgCjF;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAWxC;AAED,gFAAgF;AAChF,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,aAAa,EAAE,GAAG,gBAAgB,CAKhF"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The endpoint manifest (TH-11) — crew's route table as a MACHINE-READABLE build artifact.
|
|
3
|
+
*
|
|
4
|
+
* # Why this exists
|
|
5
|
+
*
|
|
6
|
+
* routes.ts declares zero fastify schemas (verified: grep `schema:` = 0), no OpenAPI spec exists
|
|
7
|
+
* anywhere in crew, and `wicked-crew-api-types` is types-only with no endpoint-to-type binding —
|
|
8
|
+
* so nothing machine-readable said which methods+paths this daemon serves, what a request body is
|
|
9
|
+
* called, or which status codes a route can answer. Request validation is NOT a vacuum (13
|
|
10
|
+
* `.strict()` zod objects guard the bodies), but zod objects are route-internal: a test harness,
|
|
11
|
+
* a drift check, or an API-test generator had nothing to read. With no fastify schemas declared,
|
|
12
|
+
* `@fastify/swagger` has nothing to generate from either — the `onRoute` hook IS the cheap path.
|
|
13
|
+
*
|
|
14
|
+
* # How it works
|
|
15
|
+
*
|
|
16
|
+
* {@link installEndpointManifestHook} registers a fastify `onRoute` hook (added by `createServer`
|
|
17
|
+
* BEFORE any route registration, so every route the daemon serves is seen) that accumulates one
|
|
18
|
+
* {@link EndpointEntry} per method+path. Two declaration channels feed the type/status fields:
|
|
19
|
+
*
|
|
20
|
+
* 1. `config.manifest` on the route options ({@link ManifestRouteConfig}) — the explicit
|
|
21
|
+
* channel. Names should reference `wicked-crew-api-types` exports where one exists
|
|
22
|
+
* (`LaunchRunBody`, `GateDecision`, …) so the manifest binds endpoints to the published
|
|
23
|
+
* wire contract; a structural spelling (`'{ runId: string }'`) is the honest fallback for
|
|
24
|
+
* wire shapes the contract has no name for.
|
|
25
|
+
* 2. `schema.response` keys, when fastify schemas start existing (the longer-term
|
|
26
|
+
* zod-to-json-schema path) — status codes are folded in automatically, so migrating a route
|
|
27
|
+
* to real fastify schemas enriches the manifest without touching this module.
|
|
28
|
+
*
|
|
29
|
+
* Routes that declare neither still land in the manifest with `requestType`/`responseType` null
|
|
30
|
+
* and `statusCodes: []` — the manifest records "where declared", never invents.
|
|
31
|
+
*
|
|
32
|
+
* # What consumes it
|
|
33
|
+
*
|
|
34
|
+
* - `scripts/generate-endpoint-manifest.ts` (npm run manifest:endpoints) writes the committed
|
|
35
|
+
* `endpoint-manifest.json` at the package root — the build artifact.
|
|
36
|
+
* - `tests/endpoint-manifest.test.ts` rebuilds the live route table and fails on ANY drift from
|
|
37
|
+
* the committed file, so an added/removed/renamed endpoint fails CI until the manifest is
|
|
38
|
+
* regenerated and the diff reviewed. The manifest diff IS the API regression trigger.
|
|
39
|
+
* - `scripts/generate-api-tests.ts` derives positive + 400/404/409 negative API tests from the
|
|
40
|
+
* committed manifest (see `tests/generated/`).
|
|
41
|
+
*
|
|
42
|
+
* HEAD entries are dropped: fastify v5 auto-exposes a HEAD twin for every GET
|
|
43
|
+
* (`exposeHeadRoutes`), which would double the GET surface with rows nobody declared.
|
|
44
|
+
*/
|
|
45
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
46
|
+
import { createRequire } from 'node:module';
|
|
47
|
+
import { join } from 'node:path';
|
|
48
|
+
/**
|
|
49
|
+
* Register the accumulating `onRoute` hook. MUST run before any route is registered — fastify
|
|
50
|
+
* only replays `onRoute` for routes added after the hook exists. Returns the live array the hook
|
|
51
|
+
* appends to; `createServer` exposes it as `app.endpointManifest`.
|
|
52
|
+
*/
|
|
53
|
+
export function installEndpointManifestHook(app) {
|
|
54
|
+
const entries = [];
|
|
55
|
+
app.addHook('onRoute', (route) => {
|
|
56
|
+
const methods = Array.isArray(route.method) ? route.method : [route.method];
|
|
57
|
+
const declared = route.config?.manifest;
|
|
58
|
+
// The future-proofing channel: fastify response schemas keyed by status code. None exist
|
|
59
|
+
// today (routes.ts declares zero), but the fold means the zod→fastify-schema migration
|
|
60
|
+
// enriches the manifest for free.
|
|
61
|
+
const schemaResponse = route.schema
|
|
62
|
+
?.response;
|
|
63
|
+
const schemaCodes = Object.keys(schemaResponse ?? {})
|
|
64
|
+
.map(Number)
|
|
65
|
+
.filter((n) => Number.isInteger(n) && n >= 100 && n <= 599);
|
|
66
|
+
const statusCodes = [...new Set([...(declared?.statusCodes ?? []), ...schemaCodes])].sort((a, b) => a - b);
|
|
67
|
+
const websocket = route.websocket === true;
|
|
68
|
+
for (const method of methods) {
|
|
69
|
+
// Fastify v5 auto-exposes a HEAD twin per GET; recording it would double the GET surface
|
|
70
|
+
// with rows nobody declared and no client calls on purpose.
|
|
71
|
+
if (method === 'HEAD')
|
|
72
|
+
continue;
|
|
73
|
+
entries.push({
|
|
74
|
+
method,
|
|
75
|
+
path: route.url,
|
|
76
|
+
requestType: declared?.requestType ?? null,
|
|
77
|
+
responseType: declared?.responseType ?? null,
|
|
78
|
+
statusCodes,
|
|
79
|
+
...(websocket ? { websocket: true } : {}),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
return entries;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The published wire contract's version, read from the actually-installed dependency.
|
|
87
|
+
*
|
|
88
|
+
* NOT `require('wicked-crew-api-types/package.json')`: that package's exports map exposes only
|
|
89
|
+
* `.` (types-only, no runtime condition), so both the subpath and a bare resolve are refused.
|
|
90
|
+
* Walking the resolver's candidate directories and reading the file with fs sidesteps the
|
|
91
|
+
* exports map while still honoring the real resolution order (workspace link included).
|
|
92
|
+
*/
|
|
93
|
+
export function apiTypesVersion() {
|
|
94
|
+
const require = createRequire(import.meta.url);
|
|
95
|
+
for (const dir of require.resolve.paths('wicked-crew-api-types') ?? []) {
|
|
96
|
+
const pkgPath = join(dir, 'wicked-crew-api-types', 'package.json');
|
|
97
|
+
if (existsSync(pkgPath)) {
|
|
98
|
+
return JSON.parse(readFileSync(pkgPath, 'utf8')).version;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
throw new Error('wicked-crew-api-types is not resolvable from packages/crew — the manifest cannot stamp the wire-contract version');
|
|
102
|
+
}
|
|
103
|
+
/** Entries → the committed artifact: stable order, contract version stamped. */
|
|
104
|
+
export function buildEndpointManifest(entries) {
|
|
105
|
+
const endpoints = [...entries].sort((a, b) => a.path.localeCompare(b.path) || a.method.localeCompare(b.method));
|
|
106
|
+
return { version: 1, apiTypesVersion: apiTypesVersion(), endpoints };
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=endpoint-manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"endpoint-manifest.js","sourceRoot":"","sources":["../../src/api/endpoint-manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAmDjC;;;;GAIG;AACH,MAAM,UAAU,2BAA2B,CAAC,GAAoB;IAC9D,MAAM,OAAO,GAAoB,EAAE,CAAC;IACpC,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,KAAwD,EAAE,EAAE;QAClF,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC5E,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,EAAE,QAAQ,CAAC;QACxC,yFAAyF;QACzF,uFAAuF;QACvF,kCAAkC;QAClC,MAAM,cAAc,GAAI,KAAK,CAAC,MAA6D;YACzF,EAAE,QAAQ,CAAC;QACb,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,cAAc,IAAI,EAAE,CAAC;aAClD,GAAG,CAAC,MAAM,CAAC;aACX,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC;QAC9D,MAAM,WAAW,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,WAAW,IAAI,EAAE,CAAC,EAAE,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI,CACvF,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAChB,CAAC;QACF,MAAM,SAAS,GAAI,KAAiC,CAAC,SAAS,KAAK,IAAI,CAAC;QACxE,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,yFAAyF;YACzF,4DAA4D;YAC5D,IAAI,MAAM,KAAK,MAAM;gBAAE,SAAS;YAChC,OAAO,CAAC,IAAI,CAAC;gBACX,MAAM;gBACN,IAAI,EAAE,KAAK,CAAC,GAAG;gBACf,WAAW,EAAE,QAAQ,EAAE,WAAW,IAAI,IAAI;gBAC1C,YAAY,EAAE,QAAQ,EAAE,YAAY,IAAI,IAAI;gBAC5C,WAAW;gBACX,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACnD,CAAC,CAAC;QACL,CAAC;IACH,CAAC,CAAC,CAAC;IACH,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe;IAC7B,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/C,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,uBAAuB,CAAC,IAAI,EAAE,EAAE,CAAC;QACvE,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,uBAAuB,EAAE,cAAc,CAAC,CAAC;QACnE,IAAI,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YACxB,OAAQ,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAyB,CAAC,OAAO,CAAC;QACpF,CAAC;IACH,CAAC;IACD,MAAM,IAAI,KAAK,CACb,kHAAkH,CACnH,CAAC;AACJ,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,qBAAqB,CAAC,OAAwB;IAC5D,MAAM,SAAS,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,CACjC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,CAC3E,CAAC;IACF,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,eAAe,EAAE,eAAe,EAAE,EAAE,SAAS,EAAE,CAAC;AACvE,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `/api/v1/governance/wiki/*` surface (wiki-mgmt) — the management wire for the graph-backed
|
|
3
|
+
* architecture wiki, so the wiki is VISIBLE (not just queryable): a scoreboard that tells a
|
|
4
|
+
* populated wiki from an ingested-once-and-decaying one, and a meta probe cheap enough for the
|
|
5
|
+
* UI to call on mount so an empty store shows an honest "nothing seeded — here's the runbook"
|
|
6
|
+
* instead of a blank table.
|
|
7
|
+
*
|
|
8
|
+
* Thin by design, like `campaigns/routes.ts`: the scoreboard is computed by wicked-core's
|
|
9
|
+
* governance layer (`wicked-governance::scoreboard`, read-only `open_store_ro` — safe beside the
|
|
10
|
+
* live single-writer daemon); the daemon keeps no shadow wiki state. Error posture mirrors the
|
|
11
|
+
* campaign surface: a build whose engine addon lacks the `governanceScoreboard` binding answers
|
|
12
|
+
* 501 (`GovernanceScoreboardUnsupportedError` — "upgrade the engine"), never 400 ("fix your
|
|
13
|
+
* request").
|
|
14
|
+
*
|
|
15
|
+
* Rule/ruleset BROWSE and RETIRE deliberately live elsewhere — this module adds no second door:
|
|
16
|
+
* `GET /governance/rules` (facet-filterable, retired rows flagged) is the browse surface and
|
|
17
|
+
* `DELETE /governance/rules/:id` (retire-not-delete, FINDING-038) is the kill switch, both in
|
|
18
|
+
* `api/routes.ts`.
|
|
19
|
+
*/
|
|
20
|
+
import type { FastifyInstance } from 'fastify';
|
|
21
|
+
import { type CoreAdapter } from '../core/adapter.js';
|
|
22
|
+
/**
|
|
23
|
+
* The authoring guide the honest empty state points at: the AW-13 seed runbook — frontmatter
|
|
24
|
+
* conventions, the `seed_wiki.py` driver, and the `wicked-core rules` ingest/fanout/relink CLIs.
|
|
25
|
+
*/
|
|
26
|
+
export declare const WIKI_AUTHORING_DOC = "https://github.com/mikeparcewski/wicked-core/blob/main/crates/wicked-governance/seed/README.md";
|
|
27
|
+
export declare function registerGovernanceWikiRoutes(app: FastifyInstance, adapter: CoreAdapter): void;
|
|
28
|
+
//# sourceMappingURL=governance-wiki.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"governance-wiki.d.ts","sourceRoot":"","sources":["../../src/api/governance-wiki.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C,OAAO,EAAwC,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAM5F;;;GAGG;AACH,eAAO,MAAM,kBAAkB,mGACmE,CAAC;AAMnG,wBAAgB,4BAA4B,CAAC,GAAG,EAAE,eAAe,EAAE,OAAO,EAAE,WAAW,GAAG,IAAI,CAwE7F"}
|