wicked-crew 0.7.20 → 0.7.22
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/routes.d.ts +25 -0
- package/dist/api/routes.d.ts.map +1 -1
- package/dist/api/routes.js +155 -9
- package/dist/api/routes.js.map +1 -1
- package/dist/core/adapter.d.ts.map +1 -1
- package/dist/core/adapter.js +44 -0
- package/dist/core/adapter.js.map +1 -1
- package/dist/studio/assets/{index-dRfKAWIp.js → index-CDWyxcSj.js} +52 -52
- package/dist/studio/index.html +1 -1
- package/dist/studio/testid-inventory.json +9 -3
- package/package.json +2 -2
package/dist/api/routes.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { CoreAdapter } from '../core/adapter.js';
|
|
|
4
4
|
import { type GateCache } from './gate-cache.js';
|
|
5
5
|
import type { ElicitationCache } from './elicitation-cache.js';
|
|
6
6
|
import { QeGateCache } from '../qe/gate-events.js';
|
|
7
|
+
import type { ConformanceRule, SteeringType } from '../core/types.js';
|
|
7
8
|
import { SeatHealthTracker } from './seat-health.js';
|
|
8
9
|
import { type ProjectRoutesDeps } from '../projects/routes.js';
|
|
9
10
|
import { InteractiveBridgePool } from '../interactive/bridge-pool.js';
|
|
@@ -18,6 +19,30 @@ import { DeliveryDerivationCache } from './delivery-cache.js';
|
|
|
18
19
|
import { type DeliverExec, type WorktreeReprovisioner } from './post-hoc-deliver.js';
|
|
19
20
|
import { type AuthMode } from './auth.js';
|
|
20
21
|
export { API_PREFIX } from './api-prefix.js';
|
|
22
|
+
/**
|
|
23
|
+
* Map an APPROVED policy proposal (estate `proposal.approve` → `handed_off`) into a steering
|
|
24
|
+
* ConformanceRule (DES-MEM-FACETED-001 §5.2). A policy proposal's `kind_type` is
|
|
25
|
+
* `policy:<steering_type>` and its `payload` is `{ rule, severity }`; estate writes NOTHING for it
|
|
26
|
+
* (the AW-11 "no rules.write on estate" invariant) and hands the payload back for crew — the ONE
|
|
27
|
+
* governed rules-write path — to land. Returns the rule to upsert plus the resolved steering type,
|
|
28
|
+
* or a loud `error` string when the proposal cannot be shaped into a valid rule (a malformed
|
|
29
|
+
* `kind_type` / missing `rule` / out-of-enum `severity`) — the caller reports it as a failed
|
|
30
|
+
* landing, never a silent drop (the crew#388 anti-silent-loss doctrine).
|
|
31
|
+
*
|
|
32
|
+
* The rule id is DETERMINISTIC (`proposal:<id>`) so a re-driven landing UPSERTS the same rule
|
|
33
|
+
* (idempotent) instead of minting a duplicate; it sits OUTSIDE the reserved `PAT-/POL-` namespace,
|
|
34
|
+
* which UI/chat/proposal-authored rules are free to do (INV-C1). The rule carries NO `effect`
|
|
35
|
+
* (recall-only — exactly what `{rule, severity}` supports: an enforcement rule would need a
|
|
36
|
+
* non-blank `applies_to`, which the payload does not carry, and INV-S3 fails such a rule closed).
|
|
37
|
+
* `steering_type` is validated against the vocabulary here so the engine's INV-S1 never rejects it
|
|
38
|
+
* as an unknown page.
|
|
39
|
+
*/
|
|
40
|
+
export declare function policyProposalToRule(proposalId: string, kindType: string, payload: unknown, facets: Record<string, string> | undefined): {
|
|
41
|
+
rule: ConformanceRule;
|
|
42
|
+
steeringType: SteeringType;
|
|
43
|
+
} | {
|
|
44
|
+
error: string;
|
|
45
|
+
};
|
|
21
46
|
/**
|
|
22
47
|
* The launch body. Every field but `problem` is optional, which is what made stripping dangerous:
|
|
23
48
|
* omitting one is a legitimate request that gets an engine default, so a MISSPELLED one was
|
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,EAAmD,WAAW,EAA4E,MAAM,oBAAoB,CAAC;AAG5K,OAAO,EAAiB,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAChE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/api/routes.ts"],"names":[],"mappings":"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"}
|
package/dist/api/routes.js
CHANGED
|
@@ -100,6 +100,80 @@ function isStringRecord(v) {
|
|
|
100
100
|
!Array.isArray(v) &&
|
|
101
101
|
Object.values(v).every((x) => typeof x === 'string'));
|
|
102
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Map an APPROVED policy proposal (estate `proposal.approve` → `handed_off`) into a steering
|
|
105
|
+
* ConformanceRule (DES-MEM-FACETED-001 §5.2). A policy proposal's `kind_type` is
|
|
106
|
+
* `policy:<steering_type>` and its `payload` is `{ rule, severity }`; estate writes NOTHING for it
|
|
107
|
+
* (the AW-11 "no rules.write on estate" invariant) and hands the payload back for crew — the ONE
|
|
108
|
+
* governed rules-write path — to land. Returns the rule to upsert plus the resolved steering type,
|
|
109
|
+
* or a loud `error` string when the proposal cannot be shaped into a valid rule (a malformed
|
|
110
|
+
* `kind_type` / missing `rule` / out-of-enum `severity`) — the caller reports it as a failed
|
|
111
|
+
* landing, never a silent drop (the crew#388 anti-silent-loss doctrine).
|
|
112
|
+
*
|
|
113
|
+
* The rule id is DETERMINISTIC (`proposal:<id>`) so a re-driven landing UPSERTS the same rule
|
|
114
|
+
* (idempotent) instead of minting a duplicate; it sits OUTSIDE the reserved `PAT-/POL-` namespace,
|
|
115
|
+
* which UI/chat/proposal-authored rules are free to do (INV-C1). The rule carries NO `effect`
|
|
116
|
+
* (recall-only — exactly what `{rule, severity}` supports: an enforcement rule would need a
|
|
117
|
+
* non-blank `applies_to`, which the payload does not carry, and INV-S3 fails such a rule closed).
|
|
118
|
+
* `steering_type` is validated against the vocabulary here so the engine's INV-S1 never rejects it
|
|
119
|
+
* as an unknown page.
|
|
120
|
+
*/
|
|
121
|
+
export function policyProposalToRule(proposalId, kindType, payload, facets) {
|
|
122
|
+
const steeringType = kindType.startsWith('policy:') ? kindType.slice('policy:'.length) : '';
|
|
123
|
+
if (!STEERING_TYPES.has(steeringType)) {
|
|
124
|
+
return {
|
|
125
|
+
error: `the approved proposal's kind_type ${JSON.stringify(kindType)} does not name a steering ` +
|
|
126
|
+
`type — expected \`policy:<${STEERING_TYPE_VALUES.join('|')}>\``,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
const body = typeof payload === 'object' && payload !== null && !Array.isArray(payload)
|
|
130
|
+
? payload
|
|
131
|
+
: {};
|
|
132
|
+
const statement = typeof body['rule'] === 'string' ? body['rule'].trim() : '';
|
|
133
|
+
if (statement === '') {
|
|
134
|
+
return {
|
|
135
|
+
error: 'the approved policy proposal payload has no `rule` string to make a rule statement from',
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
const rawSeverity = body['severity'];
|
|
139
|
+
// Normalize common LLM drift before the enum check: a model naturally writes the English word
|
|
140
|
+
// "warning", but the engine's enum is the short "warn" (info/error/critical are already the
|
|
141
|
+
// natural words). Tolerate case/whitespace too. A capture worker that says "warning" must still
|
|
142
|
+
// land its policy — otherwise every derived policy at the middle band silently fails to land.
|
|
143
|
+
const normSeverity = typeof rawSeverity === 'string'
|
|
144
|
+
? (() => {
|
|
145
|
+
const s = rawSeverity.trim().toLowerCase();
|
|
146
|
+
return s === 'warning' ? 'warn' : s;
|
|
147
|
+
})()
|
|
148
|
+
: rawSeverity;
|
|
149
|
+
const severity = normSeverity === 'info' || normSeverity === 'warn' || normSeverity === 'error' || normSeverity === 'critical'
|
|
150
|
+
? normSeverity
|
|
151
|
+
: undefined;
|
|
152
|
+
if (rawSeverity !== undefined && severity === undefined) {
|
|
153
|
+
return {
|
|
154
|
+
error: `the approved policy proposal payload has an invalid severity ${JSON.stringify(rawSeverity)} — ` +
|
|
155
|
+
'expected info|warn|error|critical (or the natural "warning" for warn)',
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
const language = facets?.['language'];
|
|
159
|
+
const rule = {
|
|
160
|
+
id: `proposal:${proposalId}`,
|
|
161
|
+
rule_type: 'policy',
|
|
162
|
+
statement,
|
|
163
|
+
// A policy proposal SHOULD carry severity; a missing one defaults to `warn` (the middle band)
|
|
164
|
+
// rather than failing the landing, but a present-but-garbage one fails loud above.
|
|
165
|
+
severity: severity ?? 'warn',
|
|
166
|
+
// REQUIRED engine-side (f32, no serde default, INV-C2 `[0,1]`); the payload carries none, so a
|
|
167
|
+
// fixed authority — the same 0.8 the steering-author landing defaults to.
|
|
168
|
+
confidence: 0.8,
|
|
169
|
+
// Only the `language` facet maps onto the engine's Targets facet object; `repo`/`project`
|
|
170
|
+
// facets have no ConformanceRule slot and are dropped (see the route's open question).
|
|
171
|
+
targets: typeof language === 'string' && language !== '' ? { language } : {},
|
|
172
|
+
provenance: { source: 'proposal', source_kinds: [] },
|
|
173
|
+
steering_type: steeringType,
|
|
174
|
+
};
|
|
175
|
+
return { rule, steeringType: steeringType };
|
|
176
|
+
}
|
|
103
177
|
/** A JSON object whose every value is a number (a coverage breakdown map). */
|
|
104
178
|
function isNumberRecord(v) {
|
|
105
179
|
return (typeof v === 'object' &&
|
|
@@ -2744,23 +2818,95 @@ runtime = {}) {
|
|
|
2744
2818
|
}
|
|
2745
2819
|
});
|
|
2746
2820
|
// POST /proposals/:id/approve → proposal.approve →
|
|
2747
|
-
// { outcome:'promoted', active_id }
|
|
2748
|
-
// { outcome:'handed_off', payload }
|
|
2821
|
+
// { outcome:'promoted', active_id } — a MEMORY proposal, now an active memory (complete);
|
|
2822
|
+
// { outcome:'handed_off', payload, landing } — a POLICY proposal, LANDED here as a steering rule.
|
|
2823
|
+
//
|
|
2824
|
+
// The policy→steering landing (DES-MEM-FACETED-001 §5.2): estate performs NO rules-write (the
|
|
2825
|
+
// AW-11 invariant) — it marks the proposal approved and hands its `{ rule, severity }` payload
|
|
2826
|
+
// back, and CREW is the one governed rules-write path. On a `handed_off` outcome we recover the
|
|
2827
|
+
// proposal's `kind_type` (which carries the `policy:<steering_type>` the approve response does
|
|
2828
|
+
// NOT echo), shape a `ConformanceRule` from it, and upsert it via the single-writer actor. The
|
|
2829
|
+
// posture mirrors the steering-author landing (crew#388): the estate decision (approve) already
|
|
2830
|
+
// stands, so a landing failure is reported LOUD in-band as `landing.outcome:"failed"` — never a
|
|
2831
|
+
// 500, never the silent no-op that crew#388 exists to end — and `payload` is still returned for
|
|
2832
|
+
// backward compatibility. The MEMORY path is UNTOUCHED: a `promoted` outcome returns immediately
|
|
2833
|
+
// with no extra estate call and no landing.
|
|
2749
2834
|
app.post(`${V}/proposals/:id/approve`, { config: { manifest: { responseType: 'ApproveProposalResponse', statusCodes: [200, 400, 502] } } }, async (req, reply) => {
|
|
2750
|
-
|
|
2751
|
-
|
|
2835
|
+
// Normalize ONCE and use the trimmed value throughout — an id like `%20pol1%20`
|
|
2836
|
+
// decodes to a padded, non-empty string that would otherwise ride upstream as-is
|
|
2837
|
+
// and into `proposal:<id>` derivations.
|
|
2838
|
+
const id = req.params.id.trim();
|
|
2839
|
+
if (id === '') {
|
|
2752
2840
|
return reply.code(400).send({ error: '`id` is required' });
|
|
2753
2841
|
}
|
|
2842
|
+
let approved;
|
|
2754
2843
|
try {
|
|
2755
|
-
|
|
2756
|
-
// §5.2). Policy→steering routing is OUT OF SCOPE here: a `handed_off` outcome is passed
|
|
2757
|
-
// through as-is for a later steering-write to consume; memory proposals return `promoted` and
|
|
2758
|
-
// need nothing more.
|
|
2759
|
-
return (await estateTool('proposal.approve', { id }));
|
|
2844
|
+
approved = (await estateTool('proposal.approve', { id }));
|
|
2760
2845
|
}
|
|
2761
2846
|
catch (err) {
|
|
2762
2847
|
return estateUpstreamError(reply, err);
|
|
2763
2848
|
}
|
|
2849
|
+
// MEMORY proposal (or any future non-policy outcome): estate already promoted it — pass through
|
|
2850
|
+
// verbatim, exactly as before. No extra estate round-trip on the memory path.
|
|
2851
|
+
if (approved.outcome !== 'handed_off') {
|
|
2852
|
+
return approved;
|
|
2853
|
+
}
|
|
2854
|
+
// POLICY proposal — land the handed-off payload as a steering rule.
|
|
2855
|
+
const landFailed = (error) => {
|
|
2856
|
+
audit.record('governance.steering.landing_failed', actorOf(req), {
|
|
2857
|
+
detail: { proposalId: id, error },
|
|
2858
|
+
});
|
|
2859
|
+
return { outcome: 'handed_off', payload: approved.payload, landing: { outcome: 'failed', error } };
|
|
2860
|
+
};
|
|
2861
|
+
// A pre-steering engine would SILENTLY DROP `steering_type` and the other steering fields
|
|
2862
|
+
// (ConformanceRule has no deny_unknown_fields), persisting a rule that enforces differently than
|
|
2863
|
+
// authored — the same guard POST /governance/rules applies. Fail loud (before any further estate
|
|
2864
|
+
// round-trip), persist nothing.
|
|
2865
|
+
if (!adapter.steeringSupported()) {
|
|
2866
|
+
return landFailed(`${new SteeringUnsupportedError('Landing an approved policy proposal').message} — the ` +
|
|
2867
|
+
`installed engine would silently drop the rule's steering fields; upgrade wicked-core-ts (>= 0.7.5)`);
|
|
2868
|
+
}
|
|
2869
|
+
// Recover the proposal's `kind_type` + `facets`: `proposal.approve` does not echo them, and the
|
|
2870
|
+
// steering type lives ONLY in `kind_type` (`policy:<type>`). The proposal is `approved` now, so
|
|
2871
|
+
// read it back from the approved queue.
|
|
2872
|
+
let proposal;
|
|
2873
|
+
try {
|
|
2874
|
+
const listed = (await estateTool('proposal.list', { state: 'approved' }));
|
|
2875
|
+
proposal = listed.proposals.find((p) => p.id === id);
|
|
2876
|
+
}
|
|
2877
|
+
catch (err) {
|
|
2878
|
+
return landFailed(`the policy proposal was approved but its record could not be re-read to land a steering ` +
|
|
2879
|
+
`rule (${message(err)}); land it by hand via POST ${V}/governance/rules`);
|
|
2880
|
+
}
|
|
2881
|
+
if (proposal === undefined || typeof proposal.kind_type !== 'string') {
|
|
2882
|
+
return landFailed(`the policy proposal was approved but could not be found in the approved queue to recover ` +
|
|
2883
|
+
`its steering type; land it by hand via POST ${V}/governance/rules`);
|
|
2884
|
+
}
|
|
2885
|
+
const built = policyProposalToRule(id, proposal.kind_type, approved.payload, isStringRecord(proposal.facets) ? proposal.facets : undefined);
|
|
2886
|
+
if ('error' in built) {
|
|
2887
|
+
return landFailed(built.error);
|
|
2888
|
+
}
|
|
2889
|
+
try {
|
|
2890
|
+
await adapter.upsertConformanceRule(built.rule);
|
|
2891
|
+
}
|
|
2892
|
+
catch (err) {
|
|
2893
|
+
return landFailed(`the store refused the steering rule derived from the approved policy proposal: ${message(err)}`);
|
|
2894
|
+
}
|
|
2895
|
+
audit.record('governance.rule.upserted', actorOf(req), {
|
|
2896
|
+
detail: {
|
|
2897
|
+
id: built.rule.id,
|
|
2898
|
+
source: 'proposal',
|
|
2899
|
+
via: 'proposal-approve',
|
|
2900
|
+
steeringType: built.steeringType,
|
|
2901
|
+
proposalId: id,
|
|
2902
|
+
},
|
|
2903
|
+
});
|
|
2904
|
+
const landing = {
|
|
2905
|
+
outcome: 'landed',
|
|
2906
|
+
ruleId: built.rule.id,
|
|
2907
|
+
steering_type: built.steeringType,
|
|
2908
|
+
};
|
|
2909
|
+
return { outcome: 'handed_off', payload: approved.payload, landing };
|
|
2764
2910
|
});
|
|
2765
2911
|
// POST /proposals/:id/reject → proposal.reject → { ok: true }.
|
|
2766
2912
|
app.post(`${V}/proposals/:id/reject`, { config: { manifest: { responseType: 'RejectProposalResponse', statusCodes: [200, 400, 502] } } }, async (req, reply) => {
|