wicked-crew 0.7.1 → 0.7.3
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-steering.d.ts +139 -0
- package/dist/api/governance-steering.d.ts.map +1 -0
- package/dist/api/governance-steering.js +302 -0
- package/dist/api/governance-steering.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 +21 -16
- package/dist/api/routes.d.ts.map +1 -1
- package/dist/api/routes.js +196 -10
- 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/api/testing.d.ts +117 -0
- package/dist/api/testing.d.ts.map +1 -0
- package/dist/api/testing.js +152 -0
- package/dist/api/testing.js.map +1 -0
- 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 +160 -1
- package/dist/core/adapter.d.ts.map +1 -1
- package/dist/core/adapter.js +300 -1
- package/dist/core/adapter.js.map +1 -1
- package/dist/projects/routes.d.ts +4 -4
- 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-BAW9rqNT.css +32 -0
- package/dist/studio/assets/index-UahdRwF7.js +532 -0
- package/dist/studio/index.html +2 -2
- package/dist/studio/testid-inventory.json +4387 -0
- package/endpoint-manifest.json +154 -9
- 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,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;
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
workflow?: string | undefined;
|
|
53
|
+
repoRef?: string | undefined;
|
|
53
54
|
clisJson?: string | undefined;
|
|
55
|
+
sessionId?: string | undefined;
|
|
56
|
+
retryOf?: 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;
|
|
62
|
-
|
|
63
|
-
|
|
63
|
+
workflow?: string | undefined;
|
|
64
|
+
repoRef?: string | undefined;
|
|
64
65
|
clisJson?: string | undefined;
|
|
66
|
+
sessionId?: string | undefined;
|
|
67
|
+
retryOf?: 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;
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
workflow?: string | undefined;
|
|
75
|
+
repoRef?: string | undefined;
|
|
75
76
|
clisJson?: string | undefined;
|
|
77
|
+
sessionId?: string | undefined;
|
|
78
|
+
retryOf?: 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;
|
|
84
|
-
|
|
85
|
-
|
|
85
|
+
workflow?: string | undefined;
|
|
86
|
+
repoRef?: string | undefined;
|
|
86
87
|
clisJson?: string | undefined;
|
|
88
|
+
sessionId?: string | undefined;
|
|
89
|
+
retryOf?: 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,
|
|
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,EAA4E,MAAM,oBAAoB,CAAC;AAEjJ,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;AAWtF,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,CAk+DN"}
|
package/dist/api/routes.js
CHANGED
|
@@ -5,7 +5,7 @@ import { readFileSync, existsSync } from 'node:fs';
|
|
|
5
5
|
import { promises as fsp } from 'node:fs';
|
|
6
6
|
import { isAbsolute, join, relative, resolve } from 'node:path';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
|
-
import { ChatUnsupportedError, CoreAdapter, ElicitationUnsupportedError, humanGatePhaseIds } from '../core/adapter.js';
|
|
8
|
+
import { ChatUnsupportedError, CoreAdapter, ElicitationUnsupportedError, SteeringUnsupportedError, humanGatePhaseIds } from '../core/adapter.js';
|
|
9
9
|
import { codeGraphDb, requirementsGraph } from '../core/repoPaths.js';
|
|
10
10
|
import { QeGateCache } from '../qe/gate-events.js';
|
|
11
11
|
import { buildAcceptanceView, resolveRunWorkflow } from '../qe/acceptance.js';
|
|
@@ -18,6 +18,10 @@ 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';
|
|
23
|
+
import { DEFAULT_STEERING_TYPE, STEERING_TYPE_VALUES, STEERING_TYPES, registerGovernanceSteeringRoutes, } from './governance-steering.js';
|
|
24
|
+
import { registerTestingRoutes } from './testing.js';
|
|
21
25
|
import { ProjectSettingsStore } from '../projects/settings.js';
|
|
22
26
|
import { boundOrigin, InteractiveBridgePool } from '../interactive/bridge-pool.js';
|
|
23
27
|
import { registerInteractiveProxy } from '../interactive/proxy-routes.js';
|
|
@@ -26,6 +30,7 @@ import { MEMBERSHIP_ATTACHED, membershipAttachedKey } from '../projects/events.j
|
|
|
26
30
|
import { AuditLog } from './audit.js';
|
|
27
31
|
import { RetryIndex } from './retry-index.js';
|
|
28
32
|
import { GuidanceIndex } from './guidance-index.js';
|
|
33
|
+
import { DeliveryIndex } from './delivery-index.js';
|
|
29
34
|
import { LOCAL_ACTOR } from './auth.js';
|
|
30
35
|
// Re-exported so existing `import { API_PREFIX } from './routes.js'` callers keep working; the
|
|
31
36
|
// value lives in the leaf module api-prefix.ts to keep unit-output.ts out of this file's cycle.
|
|
@@ -73,6 +78,12 @@ function invalidBody(err, what) {
|
|
|
73
78
|
: what;
|
|
74
79
|
return { error, details: err.issues };
|
|
75
80
|
}
|
|
81
|
+
// Closed facet vocabularies for the `GET /governance/rules` browse filters (wiki-mgmt) — the
|
|
82
|
+
// wire contract's `RuleBrowseQuery`. Sets, not zod: the query is otherwise free-form and only
|
|
83
|
+
// these three facets have a vocabulary to enforce.
|
|
84
|
+
const RULE_SEVERITIES = new Set(['info', 'warn', 'error', 'critical']);
|
|
85
|
+
const RULE_TYPES = new Set(['pattern', 'policy']);
|
|
86
|
+
const RULE_STATUSES = new Set(['active', 'retired', 'all']);
|
|
76
87
|
// Repo names become directory components under ~/.wicked/repos/ — reject anything
|
|
77
88
|
// that would allow path traversal (slashes, dots-only segments, control chars).
|
|
78
89
|
const SAFE_REPO_NAME = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
|
|
@@ -217,10 +228,12 @@ runtime = {}) {
|
|
|
217
228
|
const signedIn = runtime.signedIn ?? signedInHeuristic;
|
|
218
229
|
const retryIndex = runtime.retryIndex ?? new RetryIndex();
|
|
219
230
|
const guidanceIndex = runtime.guidanceIndex ?? new GuidanceIndex();
|
|
231
|
+
const deliveryIndex = runtime.deliveryIndex ?? new DeliveryIndex();
|
|
220
232
|
// The run-DTO joins (DES-UX-001 §8.2/§8.3, DES-UX-002 §7.2): `project_id` from the membership
|
|
221
233
|
// record — `null` = genuinely unfiled, so the field is ALWAYS present on served runs —
|
|
222
|
-
// `retry_of` from the lineage index,
|
|
223
|
-
//
|
|
234
|
+
// `retry_of` from the lineage index, `guidance` from the guidance index, and `delivery`
|
|
235
|
+
// from the delivery index (CREW-UX-8, crew#321), each set only when known (absent, never
|
|
236
|
+
// null, spells "not a retry" / "no note" / "delivered nothing").
|
|
224
237
|
// Applied at DTO assembly on exactly the two endpoints that serve the run DTO
|
|
225
238
|
// (GET /runs + GET /runs/:id); the internal sessionsDetail() consumers are untouched.
|
|
226
239
|
const decorateRun = (view) => {
|
|
@@ -231,6 +244,9 @@ runtime = {}) {
|
|
|
231
244
|
const guidance = guidanceIndex.guidanceFor(view.session.id);
|
|
232
245
|
if (guidance !== undefined)
|
|
233
246
|
view.session.guidance = guidance;
|
|
247
|
+
const delivery = deliveryIndex.deliveryFor(view.session.id);
|
|
248
|
+
if (delivery !== undefined)
|
|
249
|
+
view.session.delivery = delivery;
|
|
234
250
|
return view;
|
|
235
251
|
};
|
|
236
252
|
// Resolved ONCE and shared by the project routes (which read/write `interactiveRoot`) and the
|
|
@@ -977,6 +993,12 @@ runtime = {}) {
|
|
|
977
993
|
workflow,
|
|
978
994
|
gateEvents: qeGateEvents,
|
|
979
995
|
...(qeRunId !== undefined && qeRunId !== '' ? { qeRunId } : {}),
|
|
996
|
+
// AW-14 (arch-R13a + R16): the conformance section reads the same wires the standalone
|
|
997
|
+
// `/governance/claims` and `/runs/:id/events` routes serve, but run-scoped and resolved
|
|
998
|
+
// deny-dominates BESIDE the QE gate — so a wiki-rule violation and an unenforced governed
|
|
999
|
+
// unit both appear on the page humans actually look at.
|
|
1000
|
+
claims: () => adapter.listConformanceClaims(),
|
|
1001
|
+
events: (rid) => adapter.runEvents(rid),
|
|
980
1002
|
});
|
|
981
1003
|
});
|
|
982
1004
|
// The steering gate (§11.1). approve+amend = approve-with-steer; approve:false = reject (cancels).
|
|
@@ -1312,12 +1334,92 @@ runtime = {}) {
|
|
|
1312
1334
|
return { runId: id, total: events.length, returned: filtered.length, events: filtered };
|
|
1313
1335
|
});
|
|
1314
1336
|
// ── Governance reads (crew#40) ──────────────────────────────────────────────
|
|
1315
|
-
|
|
1337
|
+
// STEERING fold: the READ half of the old policy surface stays — retired-not-deleted means a
|
|
1338
|
+
// past gate decision citing a policy id must stay resolvable, and the engine keeps serving the
|
|
1339
|
+
// listing (as a thin shim over the unified store once the model merges). The WRITE half
|
|
1340
|
+
// (POST/DELETE below) answers 410 on a steering engine — see the fold comment there.
|
|
1341
|
+
app.get(`${V}/governance/policies`, { config: { manifest: { responseType: '{ policies: GovernancePolicy[] }', statusCodes: [200] } } }, async () => {
|
|
1316
1342
|
const policies = await adapter.listPolicies();
|
|
1317
1343
|
return { policies };
|
|
1318
1344
|
});
|
|
1319
|
-
|
|
1320
|
-
|
|
1345
|
+
// The wiki BROWSE surface (wiki-mgmt): facet filters over the listed rows. EXACT matches by
|
|
1346
|
+
// design — unlike `/governance/rules/preview`, whose recall semantics treat an absent rule
|
|
1347
|
+
// facet as a wildcard (enforcement's question: "which rules apply HERE"), browse answers
|
|
1348
|
+
// "show me the rules TAGGED `layer=api`", and a wildcard rule flooding every layer filter
|
|
1349
|
+
// would bury the tagged ones. `?status=` keeps the AW-24 kill switch visible: every row keeps
|
|
1350
|
+
// its `retired` flag, and `retired`/`active` narrow to one side. Default is `all` — the
|
|
1351
|
+
// route's historical behavior, though pre-0.7.4 addons funnel the listing through recall and
|
|
1352
|
+
// return active rows only (the engine's fact to fix, not this filter's to mask).
|
|
1353
|
+
app.get(`${V}/governance/rules`, {
|
|
1354
|
+
config: {
|
|
1355
|
+
manifest: {
|
|
1356
|
+
responseType: '{ rules: ConformanceRule[] }',
|
|
1357
|
+
// 501: `?type=` on a pre-steering engine (wicked-core-ts < 0.7.5) — see the facet below.
|
|
1358
|
+
statusCodes: [200, 400, 501],
|
|
1359
|
+
},
|
|
1360
|
+
},
|
|
1361
|
+
}, async (req, reply) => {
|
|
1362
|
+
const q = req.query;
|
|
1363
|
+
// Same normalization as `repoParam` below: first value of a repeated param, trimmed, and
|
|
1364
|
+
// an empty/whitespace value means "facet not given" rather than a filter matching nothing.
|
|
1365
|
+
const facet = (k) => {
|
|
1366
|
+
const raw = q[k];
|
|
1367
|
+
const first = Array.isArray(raw) ? raw[0] : raw;
|
|
1368
|
+
const trimmed = first?.trim();
|
|
1369
|
+
return trimmed ? trimmed : undefined;
|
|
1370
|
+
};
|
|
1371
|
+
// Closed vocabularies reject loudly (400 names the valid set): `?severity=hihg` silently
|
|
1372
|
+
// matching nothing would read as "no critical rules" — an empty answer to a question the
|
|
1373
|
+
// caller didn't ask.
|
|
1374
|
+
const invalid = (name, got, valid) => reply.code(400).send({ error: `${name} must be one of ${valid} (got \`${got}\`)` });
|
|
1375
|
+
const severity = facet('severity');
|
|
1376
|
+
if (severity !== undefined && !RULE_SEVERITIES.has(severity)) {
|
|
1377
|
+
return invalid('severity', severity, 'info|warn|error|critical');
|
|
1378
|
+
}
|
|
1379
|
+
const ruleType = facet('rule_type');
|
|
1380
|
+
if (ruleType !== undefined && !RULE_TYPES.has(ruleType)) {
|
|
1381
|
+
return invalid('rule_type', ruleType, 'pattern|policy');
|
|
1382
|
+
}
|
|
1383
|
+
// `?type=` — the STEERING facet (one Steering sub-page per type). Closed vocabulary, and
|
|
1384
|
+
// presence-gated on the steering engine: rows served by a pre-0.7.5 addon carry no
|
|
1385
|
+
// `steering_type`, so filtering them would answer "no <type> rules" to a question this
|
|
1386
|
+
// engine cannot answer — 501 ("upgrade the engine"), never an empty non-answer.
|
|
1387
|
+
const steeringType = facet('type');
|
|
1388
|
+
if (steeringType !== undefined && !STEERING_TYPES.has(steeringType)) {
|
|
1389
|
+
return invalid('type', steeringType, STEERING_TYPE_VALUES.join('|'));
|
|
1390
|
+
}
|
|
1391
|
+
if (steeringType !== undefined && !adapter.steeringSupported()) {
|
|
1392
|
+
return reply
|
|
1393
|
+
.code(501)
|
|
1394
|
+
.send({ error: new SteeringUnsupportedError('Filtering rules by steering type').message });
|
|
1395
|
+
}
|
|
1396
|
+
// `?include_retired=` — the boolean spelling of the retire filter (`true` ⇒ `all`,
|
|
1397
|
+
// `false` ⇒ `active`). Mutually exclusive with `?status=`: the two are one filter in two
|
|
1398
|
+
// spellings, and silently picking a winner when they contradict would answer a question
|
|
1399
|
+
// the caller didn't ask.
|
|
1400
|
+
const includeRetired = facet('include_retired');
|
|
1401
|
+
if (includeRetired !== undefined && includeRetired !== 'true' && includeRetired !== 'false') {
|
|
1402
|
+
return invalid('include_retired', includeRetired, 'true|false');
|
|
1403
|
+
}
|
|
1404
|
+
const statusGiven = facet('status');
|
|
1405
|
+
if (includeRetired !== undefined && statusGiven !== undefined) {
|
|
1406
|
+
return reply.code(400).send({
|
|
1407
|
+
error: 'send `status` or `include_retired`, not both — they are two spellings of the same retire filter and can contradict',
|
|
1408
|
+
});
|
|
1409
|
+
}
|
|
1410
|
+
const status = statusGiven ?? (includeRetired === undefined ? 'all' : includeRetired === 'true' ? 'all' : 'active');
|
|
1411
|
+
if (!RULE_STATUSES.has(status)) {
|
|
1412
|
+
return invalid('status', status, 'active|retired|all');
|
|
1413
|
+
}
|
|
1414
|
+
const layer = facet('layer');
|
|
1415
|
+
const rules = (await adapter.listConformanceRules()).filter((r) => (severity === undefined || r.severity === severity) &&
|
|
1416
|
+
(ruleType === undefined || r.rule_type === ruleType) &&
|
|
1417
|
+
(layer === undefined || r.targets.layer === layer) &&
|
|
1418
|
+
// Absent `steering_type` reads as the engine's serde default (architecture) — a
|
|
1419
|
+
// steering engine always stamps it, but filter defensively over mixed-era rows.
|
|
1420
|
+
(steeringType === undefined || (r.steering_type ?? DEFAULT_STEERING_TYPE) === steeringType) &&
|
|
1421
|
+
// `retired` is absent on rows written before the field existed, which read as active.
|
|
1422
|
+
(status === 'all' || (status === 'retired') === (r.retired === true)));
|
|
1321
1423
|
return { rules };
|
|
1322
1424
|
});
|
|
1323
1425
|
app.get(`${V}/governance/claims`, async () => {
|
|
@@ -1372,8 +1474,27 @@ runtime = {}) {
|
|
|
1372
1474
|
return reply.code(repoReadStatus(msg)).send({ error: msg });
|
|
1373
1475
|
}
|
|
1374
1476
|
});
|
|
1375
|
-
// ── Governance writes (crew#42)
|
|
1376
|
-
|
|
1477
|
+
// ── Governance writes (crew#42; STEERING fold) ─────────────────────────────
|
|
1478
|
+
// The OLD policy WRITE surface, folded into the unified rules routes (STEERING program): on a
|
|
1479
|
+
// steering engine (wicked-core-ts ≥ 0.7.5, where policy rows have MIGRATED into steering
|
|
1480
|
+
// rules) these answer 410 Gone with a pointer at the rules CRUD — the wire contract's honest
|
|
1481
|
+
// spelling of "this resource model no longer exists" (a silent alias would accept a write into
|
|
1482
|
+
// a store decide()/select() no longer read). On a PRE-steering engine the legacy behavior
|
|
1483
|
+
// stands untouched: folding before the model merges would strand policy writes with no unified
|
|
1484
|
+
// store to land in.
|
|
1485
|
+
//
|
|
1486
|
+
// The READ (`GET /governance/policies` above) deliberately stays: retired-not-deleted means
|
|
1487
|
+
// past decisions citing a policy id must stay resolvable, the engine keeps a read shim for
|
|
1488
|
+
// exactly that, and the released studio 0.4.2 still renders the listing.
|
|
1489
|
+
const policyWriteGone = (reply, pointer) => reply.code(410).send({
|
|
1490
|
+
error: 'the policy model merged into steering rules (STEERING program): policies are now ' +
|
|
1491
|
+
`steering rules with effect/trigger/obligations/criteria as first-class fields — use ${pointer}`,
|
|
1492
|
+
see: pointer,
|
|
1493
|
+
});
|
|
1494
|
+
app.post(`${V}/governance/policies`, { config: { manifest: { statusCodes: [200, 400, 410] } } }, async (req, reply) => {
|
|
1495
|
+
if (adapter.steeringSupported()) {
|
|
1496
|
+
return policyWriteGone(reply, `POST ${V}/governance/rules`);
|
|
1497
|
+
}
|
|
1377
1498
|
try {
|
|
1378
1499
|
const policy = req.body;
|
|
1379
1500
|
await adapter.upsertPolicy(policy);
|
|
@@ -1384,9 +1505,44 @@ runtime = {}) {
|
|
|
1384
1505
|
return reply.code(400).send({ error: message(err) });
|
|
1385
1506
|
}
|
|
1386
1507
|
});
|
|
1387
|
-
|
|
1508
|
+
// The steering fields a rule write may carry — the merged model's additions (STEERING). On an
|
|
1509
|
+
// engine that predates the model, `upsertConformanceRule` would ACCEPT the write and silently
|
|
1510
|
+
// drop every one of them (ConformanceRule has no `deny_unknown_fields`), persisting a rule
|
|
1511
|
+
// that recalls and enforces differently than the caller wrote — so their presence is gated
|
|
1512
|
+
// loudly (the extraWriteRoots doctrine): 501, upgrade the engine. A write WITHOUT them is the
|
|
1513
|
+
// legacy request and passes through on any engine.
|
|
1514
|
+
const STEERING_RULE_FIELDS = [
|
|
1515
|
+
'steering_type',
|
|
1516
|
+
'applies_to',
|
|
1517
|
+
'excludes',
|
|
1518
|
+
'weight',
|
|
1519
|
+
'effect',
|
|
1520
|
+
'trigger',
|
|
1521
|
+
'obligations',
|
|
1522
|
+
'criteria',
|
|
1523
|
+
];
|
|
1524
|
+
app.post(`${V}/governance/rules`, {
|
|
1525
|
+
config: {
|
|
1526
|
+
manifest: {
|
|
1527
|
+
requestType: 'ConformanceRule',
|
|
1528
|
+
// 501: the body carries steering fields but the installed engine predates the
|
|
1529
|
+
// steering model (wicked-core-ts < 0.7.5) and would silently drop them.
|
|
1530
|
+
statusCodes: [200, 400, 501],
|
|
1531
|
+
},
|
|
1532
|
+
},
|
|
1533
|
+
}, async (req, reply) => {
|
|
1388
1534
|
try {
|
|
1389
1535
|
const rule = req.body;
|
|
1536
|
+
if (!adapter.steeringSupported() && rule !== null && typeof rule === 'object') {
|
|
1537
|
+
const carried = STEERING_RULE_FIELDS.filter((f) => rule[f] !== undefined);
|
|
1538
|
+
if (carried.length > 0) {
|
|
1539
|
+
return reply.code(501).send({
|
|
1540
|
+
error: `${new SteeringUnsupportedError('Writing steering rule fields').message} — ` +
|
|
1541
|
+
`the installed engine would silently drop ${carried.map((f) => `\`${f}\``).join(', ')} ` +
|
|
1542
|
+
'and persist a rule that enforces differently than you wrote',
|
|
1543
|
+
});
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1390
1546
|
await adapter.upsertConformanceRule(rule);
|
|
1391
1547
|
audit.record('governance.rule.upserted', actorOf(req), { detail: { id: rule?.id } });
|
|
1392
1548
|
return { status: 'ok' };
|
|
@@ -1397,8 +1553,13 @@ runtime = {}) {
|
|
|
1397
1553
|
});
|
|
1398
1554
|
// Retire, not delete. The record survives so past decisions citing it stay explicable; it just
|
|
1399
1555
|
// stops being enforced (FINDING-038 — a mis-authored policy otherwise denied forever).
|
|
1400
|
-
app.delete(`${V}/governance/policies/:id`, async (req, reply) => {
|
|
1556
|
+
app.delete(`${V}/governance/policies/:id`, { config: { manifest: { statusCodes: [200, 400, 404, 410] } } }, async (req, reply) => {
|
|
1401
1557
|
const { id } = req.params;
|
|
1558
|
+
// Folded like the POST above: on a steering engine the migrated row retires through the
|
|
1559
|
+
// rules kill switch (same id — migration keeps ids stable so decision audits resolve).
|
|
1560
|
+
if (adapter.steeringSupported()) {
|
|
1561
|
+
return policyWriteGone(reply, `DELETE ${V}/governance/rules/${id}`);
|
|
1562
|
+
}
|
|
1402
1563
|
try {
|
|
1403
1564
|
const existed = await adapter.retirePolicy(id);
|
|
1404
1565
|
if (!existed)
|
|
@@ -1921,6 +2082,31 @@ runtime = {}) {
|
|
|
1921
2082
|
});
|
|
1922
2083
|
// ── Projects (DES-PROJECT-001) — the 9-route experience-plane surface ────────
|
|
1923
2084
|
registerProjectRoutes(app, adapter, { ...projects, settings: projectSettings }, security);
|
|
2085
|
+
// ── Campaigns (crew#342 + TH-9) — the engine's durable Run-DAG scheduler over REST ──────────
|
|
2086
|
+
// Progress streams as campaign* CoreEvents on the existing allowlist-free /ws relay; these
|
|
2087
|
+
// routes are launch/resume/cancel + the reads a scoreboard builds from.
|
|
2088
|
+
registerCampaignRoutes(app, adapter, {
|
|
2089
|
+
audit,
|
|
2090
|
+
actorOf,
|
|
2091
|
+
roster: () => CoreAdapter.roster(),
|
|
2092
|
+
});
|
|
2093
|
+
// ── Governance wiki management (wiki-mgmt) — scoreboard + honest empty-state meta ──────────
|
|
2094
|
+
// Browse (`GET /governance/rules` + facets) and retire (`DELETE /governance/rules/:id`) stay
|
|
2095
|
+
// ABOVE with the other governance routes; this registers only the wiki-specific reads.
|
|
2096
|
+
// Both wiki reads feed the Steering page header too — they stay, unchanged (STEERING).
|
|
2097
|
+
registerGovernanceWikiRoutes(app, adapter);
|
|
2098
|
+
// ── Steering management (STEERING program) — batch import + "add with chat" authoring ──────
|
|
2099
|
+
// Per-rule CRUD stays ABOVE with the other governance routes (a steering rule IS a
|
|
2100
|
+
// conformance rule); this registers only the import batch and the governed authoring launch.
|
|
2101
|
+
registerGovernanceSteeringRoutes(app, adapter, {
|
|
2102
|
+
audit,
|
|
2103
|
+
actorOf,
|
|
2104
|
+
roster: () => CoreAdapter.roster(),
|
|
2105
|
+
});
|
|
2106
|
+
// ── Testing (crew-testing) — governance evals + eval corpora ────────────────
|
|
2107
|
+
// Run the evals (does the steering corpus catch what it claims to?) and import a named eval
|
|
2108
|
+
// corpus. Both presence-gated on the engine's evals bindings (501 on wicked-core-ts < 0.7.5).
|
|
2109
|
+
registerTestingRoutes(app, adapter, { audit, actorOf });
|
|
1924
2110
|
// ── The wicked-interactive bridge, reverse-proxied (DES-MERGE-001 §5.3/§7.2) ──
|
|
1925
2111
|
// Mounted BESIDE the routes above and under the same `${V}` prefix, so it inherits one
|
|
1926
2112
|
// origin, one auth hook, and one CORS posture — the whole point of slice 1.
|