wicked-crew 0.7.1 → 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.
Files changed (48) hide show
  1. package/dist/api/delivery-index.d.ts +61 -0
  2. package/dist/api/delivery-index.d.ts.map +1 -0
  3. package/dist/api/delivery-index.js +87 -0
  4. package/dist/api/delivery-index.js.map +1 -0
  5. package/dist/api/governance-wiki.d.ts +28 -0
  6. package/dist/api/governance-wiki.d.ts.map +1 -0
  7. package/dist/api/governance-wiki.js +97 -0
  8. package/dist/api/governance-wiki.js.map +1 -0
  9. package/dist/api/routes.d.ts +17 -12
  10. package/dist/api/routes.d.ts.map +1 -1
  11. package/dist/api/routes.js +81 -4
  12. package/dist/api/routes.js.map +1 -1
  13. package/dist/api/server.d.ts.map +1 -1
  14. package/dist/api/server.js +56 -1
  15. package/dist/api/server.js.map +1 -1
  16. package/dist/campaign/supervision.d.ts +296 -0
  17. package/dist/campaign/supervision.d.ts.map +1 -0
  18. package/dist/campaign/supervision.js +515 -0
  19. package/dist/campaign/supervision.js.map +1 -0
  20. package/dist/campaigns/plan.d.ts +64 -0
  21. package/dist/campaigns/plan.d.ts.map +1 -0
  22. package/dist/campaigns/plan.js +201 -0
  23. package/dist/campaigns/plan.js.map +1 -0
  24. package/dist/campaigns/routes.d.ts +28 -0
  25. package/dist/campaigns/routes.d.ts.map +1 -0
  26. package/dist/campaigns/routes.js +177 -0
  27. package/dist/campaigns/routes.js.map +1 -0
  28. package/dist/core/adapter.d.ts +72 -1
  29. package/dist/core/adapter.d.ts.map +1 -1
  30. package/dist/core/adapter.js +145 -0
  31. package/dist/core/adapter.js.map +1 -1
  32. package/dist/projects/routes.d.ts +2 -2
  33. package/dist/qe/acceptance.d.ts +20 -1
  34. package/dist/qe/acceptance.d.ts.map +1 -1
  35. package/dist/qe/acceptance.js +32 -0
  36. package/dist/qe/acceptance.js.map +1 -1
  37. package/dist/qe/conformance.d.ts +158 -0
  38. package/dist/qe/conformance.d.ts.map +1 -0
  39. package/dist/qe/conformance.js +244 -0
  40. package/dist/qe/conformance.js.map +1 -0
  41. package/dist/studio/assets/index-C8rx24F1.js +530 -0
  42. package/dist/studio/assets/index-CwmIj-f2.css +32 -0
  43. package/dist/studio/index.html +2 -2
  44. package/dist/studio/testid-inventory.json +3924 -0
  45. package/endpoint-manifest.json +83 -3
  46. package/package.json +3 -3
  47. package/dist/studio/assets/index-D-BFUYnY.js +0 -530
  48. package/dist/studio/assets/index-D6S9zUtO.css +0 -32
@@ -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,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"}
@@ -0,0 +1,97 @@
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 { GovernanceScoreboardUnsupportedError } from '../core/adapter.js';
21
+ import { API_PREFIX } from './api-prefix.js';
22
+ const V = API_PREFIX;
23
+ /**
24
+ * The authoring guide the honest empty state points at: the AW-13 seed runbook — frontmatter
25
+ * conventions, the `seed_wiki.py` driver, and the `wicked-core rules` ingest/fanout/relink CLIs.
26
+ */
27
+ export const WIKI_AUTHORING_DOC = 'https://github.com/mikeparcewski/wicked-core/blob/main/crates/wicked-governance/seed/README.md';
28
+ function message(err) {
29
+ return err instanceof Error ? err.message : String(err);
30
+ }
31
+ export function registerGovernanceWikiRoutes(app, adapter) {
32
+ // The AW-23 population/connection scoreboard. `?docsRoot=` is the OPTIONAL docs root the
33
+ // typing half needs (the same directory `rules ingest --dir` consumed — enforcement_class
34
+ // lives in doc frontmatter, never on the rule node); omitted, the scoreboard still answers
35
+ // 200 with `typing.available: false` and the reason in-band — a wiki with no local docs
36
+ // checkout is a normal deployment, not an error.
37
+ app.get(`${V}/governance/wiki/scoreboard`, {
38
+ config: {
39
+ manifest: {
40
+ responseType: '{ scoreboard: GovernanceScoreboard }',
41
+ // 501: the installed engine addon predates the governanceScoreboard binding
42
+ // (wicked-core-ts < 0.7.4) — upgrade the engine, the request was already correct.
43
+ statusCodes: [200, 500, 501],
44
+ },
45
+ },
46
+ }, async (req, reply) => {
47
+ // Fastify parses a repeated `?docsRoot=a&docsRoot=b` into an array — take the first, and
48
+ // TRIM so a whitespace-only value means "no docs root" rather than a doomed doc scan.
49
+ const raw = req.query.docsRoot;
50
+ const first = Array.isArray(raw) ? raw[0] : raw;
51
+ const docsRoot = first?.trim() || undefined;
52
+ try {
53
+ const scoreboard = await adapter.governanceScoreboard(docsRoot);
54
+ return { scoreboard };
55
+ }
56
+ catch (err) {
57
+ if (err instanceof GovernanceScoreboardUnsupportedError) {
58
+ return reply.code(501).send({ error: err.message });
59
+ }
60
+ // An engine/store failure is OURS, not the caller's — there is nothing to fix in a
61
+ // bare GET, so nothing here maps to 400.
62
+ return reply.code(500).send({ error: message(err) });
63
+ }
64
+ });
65
+ // The wiki's honest empty-state signal. Works on EVERY addon (listConformanceRules is a
66
+ // required binding since crew#40), so the UI can always distinguish "nothing seeded — run the
67
+ // seed runbook" from "the engine can't tell me": `ruleset_count` is `null` (never a fabricated
68
+ // 0) on a build without the listRuleSets binding, and `scoreboard_available` says up front
69
+ // whether the scoreboard route will answer 200 here.
70
+ app.get(`${V}/governance/wiki/meta`, {
71
+ config: {
72
+ manifest: {
73
+ responseType: '{ meta: GovernanceWikiMeta }',
74
+ statusCodes: [200],
75
+ },
76
+ },
77
+ },
78
+ // Wrapped per crew house style — campaigns/rules/scoreboard all wrap, and studio's
79
+ // Wiki page reads `body.meta` (a bare seeded:false would read as "cannot tell").
80
+ async () => {
81
+ // NOTE: pre-0.7.4 addons funnel listConformanceRules through recall, which skips retired
82
+ // rows — so `rule_count` counts the LISTABLE rules. That is the right number for "is
83
+ // anything here": a store holding only retired rules has nothing recallable to show.
84
+ const rules = await adapter.listConformanceRules();
85
+ const rulesetCount = await adapter.countRuleSets();
86
+ return {
87
+ meta: {
88
+ seeded: rules.length > 0 || (rulesetCount ?? 0) > 0,
89
+ ruleset_count: rulesetCount,
90
+ rule_count: rules.length,
91
+ scoreboard_available: adapter.wikiScoreboardSupported(),
92
+ doc: WIKI_AUTHORING_DOC,
93
+ },
94
+ };
95
+ });
96
+ }
97
+ //# sourceMappingURL=governance-wiki.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"governance-wiki.js","sourceRoot":"","sources":["../../src/api/governance-wiki.ts"],"names":[],"mappings":"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"}
@@ -10,6 +10,7 @@ import { InteractiveBridgePool } from '../interactive/bridge-pool.js';
10
10
  import { AuditLog } from './audit.js';
11
11
  import { RetryIndex } from './retry-index.js';
12
12
  import { GuidanceIndex } from './guidance-index.js';
13
+ import { DeliveryIndex } from './delivery-index.js';
13
14
  import { type AuthMode } from './auth.js';
14
15
  export { API_PREFIX } from './api-prefix.js';
15
16
  /**
@@ -48,46 +49,46 @@ export declare const LaunchSchema: z.ZodEffects<z.ZodObject<{
48
49
  problem: string;
49
50
  deliver?: "pr" | undefined;
50
51
  projectId?: string | undefined;
52
+ workflow?: string | undefined;
53
+ repoRef?: string | undefined;
54
+ clisJson?: string | undefined;
51
55
  retryOf?: string | undefined;
52
56
  sessionId?: string | undefined;
53
- clisJson?: string | undefined;
54
57
  entityMode?: "shared" | "isolated" | undefined;
55
58
  humanConfirm?: string | undefined;
56
- repoRef?: string | undefined;
57
- workflow?: string | undefined;
58
59
  }, {
59
60
  problem: string;
60
61
  deliver?: "pr" | undefined;
61
62
  projectId?: string | undefined;
63
+ workflow?: string | undefined;
64
+ repoRef?: string | undefined;
65
+ clisJson?: string | undefined;
62
66
  retryOf?: string | undefined;
63
67
  sessionId?: string | undefined;
64
- clisJson?: string | undefined;
65
68
  entityMode?: "shared" | "isolated" | undefined;
66
69
  humanConfirm?: string | undefined;
67
- repoRef?: string | undefined;
68
- workflow?: string | undefined;
69
70
  }>, {
70
71
  problem: string;
71
72
  deliver?: "pr" | undefined;
72
73
  projectId?: string | undefined;
74
+ workflow?: string | undefined;
75
+ repoRef?: string | undefined;
76
+ clisJson?: string | undefined;
73
77
  retryOf?: string | undefined;
74
78
  sessionId?: string | undefined;
75
- clisJson?: string | undefined;
76
79
  entityMode?: "shared" | "isolated" | undefined;
77
80
  humanConfirm?: string | undefined;
78
- repoRef?: string | undefined;
79
- workflow?: string | undefined;
80
81
  }, {
81
82
  problem: string;
82
83
  deliver?: "pr" | undefined;
83
84
  projectId?: string | undefined;
85
+ workflow?: string | undefined;
86
+ repoRef?: string | undefined;
87
+ clisJson?: string | undefined;
84
88
  retryOf?: string | undefined;
85
89
  sessionId?: string | undefined;
86
- clisJson?: string | undefined;
87
90
  entityMode?: "shared" | "isolated" | undefined;
88
91
  humanConfirm?: string | undefined;
89
- repoRef?: string | undefined;
90
- workflow?: string | undefined;
91
92
  }>;
92
93
  export declare const GateSchema: z.ZodObject<{
93
94
  approve: z.ZodBoolean;
@@ -157,6 +158,10 @@ export interface RuntimeDeps {
157
158
  /** Run→operator-guidance index (CREW-UX-7) — `createServer` hydrates one from the audit trail
158
159
  * so a restarted daemon still echoes `guidance`; a directly-driven route set gets a fresh one. */
159
160
  guidanceIndex?: GuidanceIndex;
161
+ /** Run→delivered-PR index (CREW-UX-8, crew#321) — `createServer` hydrates one from the audit
162
+ * trail so a restarted daemon still echoes `delivery`; a directly-driven route set gets a
163
+ * fresh one. */
164
+ deliveryIndex?: DeliveryIndex;
160
165
  openWithOs?: (target: string) => Promise<void>;
161
166
  /** Seat sign-in presence probe (seat sign-in) — injectable so route tests never read the
162
167
  * developer's real dotfiles. Defaults to the file/env heuristic in seat-signin.ts. */
@@ -1 +1 @@
1
- {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/api/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAgB,MAAM,SAAS,CAAC;AAC7D,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAOxB,OAAO,EAAwB,WAAW,EAAkD,MAAM,oBAAoB,CAAC;AAEvH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAMnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAWrD,OAAO,EAAyB,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAEtF,OAAO,EAAe,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAInF,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAe,KAAK,QAAQ,EAAE,MAAM,WAAW,CAAC;AAGvD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAyF7C;;;;;;;;;;;;GAYG;AAIH,eAAO,MAAM,YAAY;;;;;;;;IAQvB;+FAC2F;;IAE3F;;wFAEoF;;IAEpF;;0FAEsF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAKtF,CAAC;AAEH,eAAO,MAAM,UAAU;;;;;;;;;EAGZ,CAAC;AAEZ;;yEAEyE;AACzE,eAAO,MAAM,cAAc;;;;;;EAEhB,CAAC;AAUZ,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;EAQpB,CAAC;AAOZ,4FAA4F;AAC5F,eAAO,MAAM,cAAc;;;;;;;;;EAGhB,CAAC;AAwBZ,sEAAsE;AACtE,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,QAAQ,CAAC;IAChB,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAED;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,UAAU,CAAC,EAAE,iBAAiB,CAAC;IAC/B;oGACgG;IAChG,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB;uGACmG;IACnG,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C;2FACuF;IACvF,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,gBAAgB,CAAC,EAAE,MAAM,KAAK,OAAO,GAAG,IAAI,CAAC;IAC1E;;0DAEsD;IACtD,kBAAkB,CAAC,EAAE,qBAAqB,CAAC;CAC5C;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,eAAe,EACpB,OAAO,EAAE,WAAW,EACpB,SAAS,EAAE,SAAS,EACpB,gBAAgB,EAAE,gBAAgB,EAIlC,YAAY,GAAE,WAA+B,EAG7C,QAAQ,GAAE,iBAAqF,EAK/F,QAAQ,GAAE,YAA0D,EAGpE,OAAO,GAAE,WAAgB,GACxB,IAAI,CA+wDN"}
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/api/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAgB,MAAM,SAAS,CAAC;AAC7D,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAOxB,OAAO,EAAwB,WAAW,EAAkD,MAAM,oBAAoB,CAAC;AAEvH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAMnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAWrD,OAAO,EAAyB,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAItF,OAAO,EAAe,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAInF,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAe,KAAK,QAAQ,EAAE,MAAM,WAAW,CAAC;AAGvD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAgG7C;;;;;;;;;;;;GAYG;AAIH,eAAO,MAAM,YAAY;;;;;;;;IAQvB;+FAC2F;;IAE3F;;wFAEoF;;IAEpF;;0FAEsF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAKtF,CAAC;AAEH,eAAO,MAAM,UAAU;;;;;;;;;EAGZ,CAAC;AAEZ;;yEAEyE;AACzE,eAAO,MAAM,cAAc;;;;;;EAEhB,CAAC;AAUZ,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;EAQpB,CAAC;AAOZ,4FAA4F;AAC5F,eAAO,MAAM,cAAc;;;;;;;;;EAGhB,CAAC;AAwBZ,sEAAsE;AACtE,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,QAAQ,CAAC;IAChB,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAED;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,UAAU,CAAC,EAAE,iBAAiB,CAAC;IAC/B;oGACgG;IAChG,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB;uGACmG;IACnG,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B;;qBAEiB;IACjB,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C;2FACuF;IACvF,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,gBAAgB,CAAC,EAAE,MAAM,KAAK,OAAO,GAAG,IAAI,CAAC;IAC1E;;0DAEsD;IACtD,kBAAkB,CAAC,EAAE,qBAAqB,CAAC;CAC5C;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,eAAe,EACpB,OAAO,EAAE,WAAW,EACpB,SAAS,EAAE,SAAS,EACpB,gBAAgB,EAAE,gBAAgB,EAIlC,YAAY,GAAE,WAA+B,EAG7C,QAAQ,GAAE,iBAAqF,EAK/F,QAAQ,GAAE,YAA0D,EAGpE,OAAO,GAAE,WAAgB,GACxB,IAAI,CA41DN"}
@@ -18,6 +18,8 @@ import { allowedRootsFor, isInsideRoot, openWithSystemDefault } from './open-pat
18
18
  import { InvalidDiffBaseError, NotARegularFileError, UnresolvableDiffBaseError, readFileCapped, worktreeDiff, } from './run-files.js';
19
19
  import { resolveProjectGraphBinding } from '../projects/graph.js';
20
20
  import { registerProjectRoutes } from '../projects/routes.js';
21
+ import { registerCampaignRoutes } from '../campaigns/routes.js';
22
+ import { registerGovernanceWikiRoutes } from './governance-wiki.js';
21
23
  import { ProjectSettingsStore } from '../projects/settings.js';
22
24
  import { boundOrigin, InteractiveBridgePool } from '../interactive/bridge-pool.js';
23
25
  import { registerInteractiveProxy } from '../interactive/proxy-routes.js';
@@ -26,6 +28,7 @@ import { MEMBERSHIP_ATTACHED, membershipAttachedKey } from '../projects/events.j
26
28
  import { AuditLog } from './audit.js';
27
29
  import { RetryIndex } from './retry-index.js';
28
30
  import { GuidanceIndex } from './guidance-index.js';
31
+ import { DeliveryIndex } from './delivery-index.js';
29
32
  import { LOCAL_ACTOR } from './auth.js';
30
33
  // Re-exported so existing `import { API_PREFIX } from './routes.js'` callers keep working; the
31
34
  // value lives in the leaf module api-prefix.ts to keep unit-output.ts out of this file's cycle.
@@ -73,6 +76,12 @@ function invalidBody(err, what) {
73
76
  : what;
74
77
  return { error, details: err.issues };
75
78
  }
79
+ // Closed facet vocabularies for the `GET /governance/rules` browse filters (wiki-mgmt) — the
80
+ // wire contract's `RuleBrowseQuery`. Sets, not zod: the query is otherwise free-form and only
81
+ // these three facets have a vocabulary to enforce.
82
+ const RULE_SEVERITIES = new Set(['info', 'warn', 'error', 'critical']);
83
+ const RULE_TYPES = new Set(['pattern', 'policy']);
84
+ const RULE_STATUSES = new Set(['active', 'retired', 'all']);
76
85
  // Repo names become directory components under ~/.wicked/repos/ — reject anything
77
86
  // that would allow path traversal (slashes, dots-only segments, control chars).
78
87
  const SAFE_REPO_NAME = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
@@ -217,10 +226,12 @@ runtime = {}) {
217
226
  const signedIn = runtime.signedIn ?? signedInHeuristic;
218
227
  const retryIndex = runtime.retryIndex ?? new RetryIndex();
219
228
  const guidanceIndex = runtime.guidanceIndex ?? new GuidanceIndex();
229
+ const deliveryIndex = runtime.deliveryIndex ?? new DeliveryIndex();
220
230
  // The run-DTO joins (DES-UX-001 §8.2/§8.3, DES-UX-002 §7.2): `project_id` from the membership
221
231
  // record — `null` = genuinely unfiled, so the field is ALWAYS present on served runs —
222
- // `retry_of` from the lineage index, and `guidance` from the guidance index, each set only
223
- // when known (absent, never null, spells "not a retry" / "no note").
232
+ // `retry_of` from the lineage index, `guidance` from the guidance index, and `delivery`
233
+ // from the delivery index (CREW-UX-8, crew#321), each set only when known (absent, never
234
+ // null, spells "not a retry" / "no note" / "delivered nothing").
224
235
  // Applied at DTO assembly on exactly the two endpoints that serve the run DTO
225
236
  // (GET /runs + GET /runs/:id); the internal sessionsDetail() consumers are untouched.
226
237
  const decorateRun = (view) => {
@@ -231,6 +242,9 @@ runtime = {}) {
231
242
  const guidance = guidanceIndex.guidanceFor(view.session.id);
232
243
  if (guidance !== undefined)
233
244
  view.session.guidance = guidance;
245
+ const delivery = deliveryIndex.deliveryFor(view.session.id);
246
+ if (delivery !== undefined)
247
+ view.session.delivery = delivery;
234
248
  return view;
235
249
  };
236
250
  // Resolved ONCE and shared by the project routes (which read/write `interactiveRoot`) and the
@@ -977,6 +991,12 @@ runtime = {}) {
977
991
  workflow,
978
992
  gateEvents: qeGateEvents,
979
993
  ...(qeRunId !== undefined && qeRunId !== '' ? { qeRunId } : {}),
994
+ // AW-14 (arch-R13a + R16): the conformance section reads the same wires the standalone
995
+ // `/governance/claims` and `/runs/:id/events` routes serve, but run-scoped and resolved
996
+ // deny-dominates BESIDE the QE gate — so a wiki-rule violation and an unenforced governed
997
+ // unit both appear on the page humans actually look at.
998
+ claims: () => adapter.listConformanceClaims(),
999
+ events: (rid) => adapter.runEvents(rid),
980
1000
  });
981
1001
  });
982
1002
  // The steering gate (§11.1). approve+amend = approve-with-steer; approve:false = reject (cancels).
@@ -1316,8 +1336,53 @@ runtime = {}) {
1316
1336
  const policies = await adapter.listPolicies();
1317
1337
  return { policies };
1318
1338
  });
1319
- app.get(`${V}/governance/rules`, async () => {
1320
- const rules = await adapter.listConformanceRules();
1339
+ // The wiki BROWSE surface (wiki-mgmt): facet filters over the listed rows. EXACT matches by
1340
+ // design — unlike `/governance/rules/preview`, whose recall semantics treat an absent rule
1341
+ // facet as a wildcard (enforcement's question: "which rules apply HERE"), browse answers
1342
+ // "show me the rules TAGGED `layer=api`", and a wildcard rule flooding every layer filter
1343
+ // would bury the tagged ones. `?status=` keeps the AW-24 kill switch visible: every row keeps
1344
+ // its `retired` flag, and `retired`/`active` narrow to one side. Default is `all` — the
1345
+ // route's historical behavior, though pre-0.7.4 addons funnel the listing through recall and
1346
+ // return active rows only (the engine's fact to fix, not this filter's to mask).
1347
+ app.get(`${V}/governance/rules`, {
1348
+ config: {
1349
+ manifest: {
1350
+ responseType: '{ rules: ConformanceRule[] }',
1351
+ statusCodes: [200, 400],
1352
+ },
1353
+ },
1354
+ }, async (req, reply) => {
1355
+ const q = req.query;
1356
+ // Same normalization as `repoParam` below: first value of a repeated param, trimmed, and
1357
+ // an empty/whitespace value means "facet not given" rather than a filter matching nothing.
1358
+ const facet = (k) => {
1359
+ const raw = q[k];
1360
+ const first = Array.isArray(raw) ? raw[0] : raw;
1361
+ const trimmed = first?.trim();
1362
+ return trimmed ? trimmed : undefined;
1363
+ };
1364
+ // Closed vocabularies reject loudly (400 names the valid set): `?severity=hihg` silently
1365
+ // matching nothing would read as "no critical rules" — an empty answer to a question the
1366
+ // caller didn't ask.
1367
+ const invalid = (name, got, valid) => reply.code(400).send({ error: `${name} must be one of ${valid} (got \`${got}\`)` });
1368
+ const severity = facet('severity');
1369
+ if (severity !== undefined && !RULE_SEVERITIES.has(severity)) {
1370
+ return invalid('severity', severity, 'info|warn|error|critical');
1371
+ }
1372
+ const ruleType = facet('rule_type');
1373
+ if (ruleType !== undefined && !RULE_TYPES.has(ruleType)) {
1374
+ return invalid('rule_type', ruleType, 'pattern|policy');
1375
+ }
1376
+ const status = facet('status') ?? 'all';
1377
+ if (!RULE_STATUSES.has(status)) {
1378
+ return invalid('status', status, 'active|retired|all');
1379
+ }
1380
+ const layer = facet('layer');
1381
+ const rules = (await adapter.listConformanceRules()).filter((r) => (severity === undefined || r.severity === severity) &&
1382
+ (ruleType === undefined || r.rule_type === ruleType) &&
1383
+ (layer === undefined || r.targets.layer === layer) &&
1384
+ // `retired` is absent on rows written before the field existed, which read as active.
1385
+ (status === 'all' || (status === 'retired') === (r.retired === true)));
1321
1386
  return { rules };
1322
1387
  });
1323
1388
  app.get(`${V}/governance/claims`, async () => {
@@ -1921,6 +1986,18 @@ runtime = {}) {
1921
1986
  });
1922
1987
  // ── Projects (DES-PROJECT-001) — the 9-route experience-plane surface ────────
1923
1988
  registerProjectRoutes(app, adapter, { ...projects, settings: projectSettings }, security);
1989
+ // ── Campaigns (crew#342 + TH-9) — the engine's durable Run-DAG scheduler over REST ──────────
1990
+ // Progress streams as campaign* CoreEvents on the existing allowlist-free /ws relay; these
1991
+ // routes are launch/resume/cancel + the reads a scoreboard builds from.
1992
+ registerCampaignRoutes(app, adapter, {
1993
+ audit,
1994
+ actorOf,
1995
+ roster: () => CoreAdapter.roster(),
1996
+ });
1997
+ // ── Governance wiki management (wiki-mgmt) — scoreboard + honest empty-state meta ──────────
1998
+ // Browse (`GET /governance/rules` + facets) and retire (`DELETE /governance/rules/:id`) stay
1999
+ // ABOVE with the other governance routes; this registers only the wiki-specific reads.
2000
+ registerGovernanceWikiRoutes(app, adapter);
1924
2001
  // ── The wicked-interactive bridge, reverse-proxied (DES-MERGE-001 §5.3/§7.2) ──
1925
2002
  // Mounted BESIDE the routes above and under the same `${V}` prefix, so it inherits one
1926
2003
  // origin, one auth hook, and one CORS posture — the whole point of slice 1.