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.
- 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/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/routes.d.ts +17 -12
- package/dist/api/routes.d.ts.map +1 -1
- package/dist/api/routes.js +81 -4
- package/dist/api/routes.js.map +1 -1
- package/dist/api/server.d.ts.map +1 -1
- package/dist/api/server.js +56 -1
- 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/core/adapter.d.ts +72 -1
- package/dist/core/adapter.d.ts.map +1 -1
- package/dist/core/adapter.js +145 -0
- package/dist/core/adapter.js.map +1 -1
- package/dist/projects/routes.d.ts +2 -2
- 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/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 +83 -3
- package/package.json +3 -3
- package/dist/studio/assets/index-D-BFUYnY.js +0 -530
- 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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"}
|
package/dist/api/routes.d.ts
CHANGED
|
@@ -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. */
|
package/dist/api/routes.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/api/routes.js
CHANGED
|
@@ -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,
|
|
223
|
-
//
|
|
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
|
-
|
|
1320
|
-
|
|
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.
|