@danielblomma/cortex-mcp 2.0.16 → 2.0.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -4
- package/bin/cortex.mjs +75 -10
- package/package.json +2 -1
- package/scaffold/.context/config.yaml +2 -3
- package/scaffold/.githooks/_cortex-update-runner.sh +2 -2
- package/scaffold/mcp/package-lock.json +59 -60
- package/scaffold/mcp/package.json +2 -2
- package/scaffold/mcp/src/cli/stage.ts +15 -18
- package/scaffold/mcp/src/core/validators/builtins.ts +24 -4
- package/scaffold/mcp/src/core/workflow/index.ts +1 -0
- package/scaffold/mcp/src/core/workflow/mcp-tools.ts +15 -25
- package/scaffold/mcp/src/core/workflow/resolution.ts +113 -0
- package/scaffold/mcp/src/embeddings.ts +1 -1
- package/scaffold/mcp/src/enterprise/index.ts +16 -1
- package/scaffold/mcp/src/enterprise/reviews/policy-selection.ts +65 -0
- package/scaffold/mcp/src/enterprise/reviews/push.ts +55 -4
- package/scaffold/mcp/src/enterprise/reviews/trust-state.ts +305 -0
- package/scaffold/mcp/src/enterprise/tools/enterprise.ts +40 -47
- package/scaffold/mcp/src/enterprise/tools/harness.ts +2 -0
- package/scaffold/mcp/src/enterprise/workflow/state.ts +21 -0
- package/scaffold/mcp/src/graph.ts +3 -3
- package/scaffold/mcp/tests/enterprise-review-trust.test.mjs +318 -0
- package/scaffold/mcp/tests/fixtures/org-skillz-contract.json +59 -0
- package/scaffold/mcp/tests/fixtures/review-trust-contract.json +111 -0
- package/scaffold/mcp/tests/review-policy-selection.test.mjs +85 -0
- package/scaffold/mcp/tests/review-trust-contract.test.mjs +167 -0
- package/scaffold/mcp/tests/review-validator-source.test.mjs +52 -0
- package/scaffold/mcp/tests/skill-sync-checker.test.mjs +277 -0
- package/scaffold/mcp/tests/workflow-cli.test.mjs +102 -1
- package/scaffold/mcp/tests/workflow-mcp-tools.test.mjs +22 -0
- package/scaffold/mcp/tests/workflow-synced-registry.test.mjs +56 -0
- package/scaffold/scripts/bootstrap.sh +9 -8
- package/scaffold/scripts/context.sh +1 -1
- package/scaffold/scripts/dashboard.mjs +3 -1
- package/scaffold/scripts/dashboard.sh +1 -1
- package/scaffold/scripts/doctor.sh +2 -1
- package/scaffold/scripts/embed.sh +2 -1
- package/scaffold/scripts/ingest.mjs +3 -1
- package/scaffold/scripts/ingest.sh +3 -2
- package/scaffold/scripts/install-git-hooks.sh +2 -1
- package/scaffold/scripts/load-kuzu.sh +2 -2
- package/scaffold/scripts/load-ryu.sh +2 -1
- package/scaffold/scripts/memory-compile.mjs +2 -2
- package/scaffold/scripts/memory-compile.sh +3 -2
- package/scaffold/scripts/memory-lint.mjs +2 -2
- package/scaffold/scripts/memory-lint.sh +3 -2
- package/scaffold/scripts/parsers/node_modules/.package-lock.json +0 -1
- package/scaffold/scripts/parsers/package-lock.json +0 -1
- package/scaffold/scripts/refresh.sh +2 -2
- package/scaffold/scripts/status.sh +6 -5
- package/scaffold/scripts/update-context.sh +5 -5
- package/scaffold/scripts/watch.sh +7 -8
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
loadReviewTrustState,
|
|
9
|
+
recordQueuedReviewTrustState,
|
|
10
|
+
} from "../dist/enterprise/reviews/trust-state.js";
|
|
11
|
+
import {
|
|
12
|
+
pendingCount,
|
|
13
|
+
pushReviewResults,
|
|
14
|
+
queueReviewResult,
|
|
15
|
+
setReviewPushContext,
|
|
16
|
+
} from "../dist/enterprise/reviews/push.js";
|
|
17
|
+
|
|
18
|
+
function makeProject() {
|
|
19
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), "cortex-review-trust-"));
|
|
20
|
+
const contextDir = path.join(root, ".context");
|
|
21
|
+
fs.mkdirSync(contextDir, { recursive: true });
|
|
22
|
+
return { root, contextDir };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function writeTaskState(root, taskId, workflowId = "secure-build") {
|
|
26
|
+
const taskDir = path.join(root, ".agents", taskId);
|
|
27
|
+
fs.mkdirSync(taskDir, { recursive: true });
|
|
28
|
+
fs.writeFileSync(
|
|
29
|
+
path.join(taskDir, "state.json"),
|
|
30
|
+
JSON.stringify(
|
|
31
|
+
{
|
|
32
|
+
workflow_id: workflowId,
|
|
33
|
+
outcome: "in_progress",
|
|
34
|
+
},
|
|
35
|
+
null,
|
|
36
|
+
2,
|
|
37
|
+
),
|
|
38
|
+
"utf8",
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function queueOneReview(reviewedAt = "2026-05-17T10:00:00.000Z") {
|
|
43
|
+
queueReviewResult({
|
|
44
|
+
policy_id: "no-secrets-in-code",
|
|
45
|
+
pass: true,
|
|
46
|
+
severity: "info",
|
|
47
|
+
message: "No new secrets found",
|
|
48
|
+
reviewed_at: reviewedAt,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
test.afterEach(async () => {
|
|
53
|
+
const originalFetch = globalThis.fetch;
|
|
54
|
+
globalThis.fetch = async () => ({ ok: true, status: 200 });
|
|
55
|
+
try {
|
|
56
|
+
while (pendingCount() > 0) {
|
|
57
|
+
await pushReviewResults("https://example.com", "ent_test");
|
|
58
|
+
}
|
|
59
|
+
} finally {
|
|
60
|
+
globalThis.fetch = originalFetch;
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("review trust state persists queued review evidence", () => {
|
|
65
|
+
const { root, contextDir } = makeProject();
|
|
66
|
+
const state = recordQueuedReviewTrustState(
|
|
67
|
+
{
|
|
68
|
+
contextDir,
|
|
69
|
+
projectRoot: root,
|
|
70
|
+
repo: "demo-repo",
|
|
71
|
+
instance_id: "abc123",
|
|
72
|
+
session_id: "session-1",
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
reviewedAt: "2026-05-17T10:00:00.000Z",
|
|
76
|
+
summary: {
|
|
77
|
+
total: 2,
|
|
78
|
+
passed: 1,
|
|
79
|
+
failed: 1,
|
|
80
|
+
warnings: 0,
|
|
81
|
+
skipped: 1,
|
|
82
|
+
},
|
|
83
|
+
skippedPolicies: [
|
|
84
|
+
{
|
|
85
|
+
policy_id: "require-code-review",
|
|
86
|
+
kind: "predefined",
|
|
87
|
+
type: null,
|
|
88
|
+
reason: "validate this policy from workflow state on the next run.",
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
assert.equal(state.delivery.status, "queued");
|
|
95
|
+
assert.equal(state.review_summary.total, 2);
|
|
96
|
+
assert.equal(state.skipped_policies.length, 1);
|
|
97
|
+
assert.ok(fs.existsSync(path.join(contextDir, "review-trust.json")));
|
|
98
|
+
assert.match(
|
|
99
|
+
state.trust_warnings.map((warning) => warning.code).join(","),
|
|
100
|
+
/workflow-unresolved/,
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("review trust state writes workflow state even when no workflow file exists yet", () => {
|
|
105
|
+
const { contextDir } = makeProject();
|
|
106
|
+
const state = loadReviewTrustState(contextDir);
|
|
107
|
+
|
|
108
|
+
assert.equal(state.delivery.status, "never_attempted");
|
|
109
|
+
assert.ok(fs.existsSync(path.join(contextDir, "workflow", "state.json")));
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("review trust uses the explicit active task instead of guessing from the newest agent run", () => {
|
|
113
|
+
const { root, contextDir } = makeProject();
|
|
114
|
+
const fakeHome = fs.mkdtempSync(path.join(os.tmpdir(), "cortex-review-home-"));
|
|
115
|
+
process.env.HOME = fakeHome;
|
|
116
|
+
try {
|
|
117
|
+
writeTaskState(root, "task-a");
|
|
118
|
+
writeTaskState(root, "task-b", "shadow-workflow");
|
|
119
|
+
const state = recordQueuedReviewTrustState(
|
|
120
|
+
{
|
|
121
|
+
contextDir,
|
|
122
|
+
projectRoot: root,
|
|
123
|
+
repo: "demo-repo",
|
|
124
|
+
instance_id: "abc123",
|
|
125
|
+
session_id: "session-1",
|
|
126
|
+
task_id: "task-a",
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
reviewedAt: "2026-05-17T10:00:00.000Z",
|
|
130
|
+
summary: {
|
|
131
|
+
total: 1,
|
|
132
|
+
passed: 1,
|
|
133
|
+
failed: 0,
|
|
134
|
+
warnings: 0,
|
|
135
|
+
skipped: 0,
|
|
136
|
+
},
|
|
137
|
+
skippedPolicies: [],
|
|
138
|
+
},
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
assert.equal(state.workflow.task_id, "task-a");
|
|
142
|
+
assert.equal(state.workflow.id, "secure-build");
|
|
143
|
+
} finally {
|
|
144
|
+
delete process.env.HOME;
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("review push marks delivery accepted on successful ingestion", async () => {
|
|
149
|
+
const { contextDir } = makeProject();
|
|
150
|
+
const originalFetch = globalThis.fetch;
|
|
151
|
+
globalThis.fetch = async () => ({ ok: true, status: 200 });
|
|
152
|
+
|
|
153
|
+
setReviewPushContext({
|
|
154
|
+
repo: "demo-repo",
|
|
155
|
+
instance_id: "abc123",
|
|
156
|
+
session_id: "session-accepted",
|
|
157
|
+
context_dir: contextDir,
|
|
158
|
+
});
|
|
159
|
+
queueOneReview();
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
const result = await pushReviewResults("https://example.com", "ent_test");
|
|
163
|
+
assert.equal(result.success, true);
|
|
164
|
+
assert.equal(result.delivery_status, "accepted");
|
|
165
|
+
} finally {
|
|
166
|
+
globalThis.fetch = originalFetch;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const state = loadReviewTrustState(contextDir);
|
|
170
|
+
assert.equal(state.delivery.status, "accepted");
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("review push marks delivery failed on HTTP errors", async () => {
|
|
174
|
+
const { contextDir } = makeProject();
|
|
175
|
+
const originalFetch = globalThis.fetch;
|
|
176
|
+
globalThis.fetch = async () => ({ ok: false, status: 502 });
|
|
177
|
+
|
|
178
|
+
setReviewPushContext({
|
|
179
|
+
repo: "demo-repo",
|
|
180
|
+
instance_id: "abc123",
|
|
181
|
+
session_id: "session-http-fail",
|
|
182
|
+
context_dir: contextDir,
|
|
183
|
+
});
|
|
184
|
+
queueOneReview();
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
const result = await pushReviewResults("https://example.com", "ent_test");
|
|
188
|
+
assert.equal(result.success, false);
|
|
189
|
+
assert.equal(result.delivery_status, "failed");
|
|
190
|
+
} finally {
|
|
191
|
+
globalThis.fetch = originalFetch;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const state = loadReviewTrustState(contextDir);
|
|
195
|
+
assert.equal(state.delivery.status, "failed");
|
|
196
|
+
assert.equal(state.delivery.last_error, "HTTP 502");
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test("review push marks delivery failed on network errors", async () => {
|
|
200
|
+
const { contextDir } = makeProject();
|
|
201
|
+
const originalFetch = globalThis.fetch;
|
|
202
|
+
globalThis.fetch = async () => {
|
|
203
|
+
throw new Error("network down");
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
setReviewPushContext({
|
|
207
|
+
repo: "demo-repo",
|
|
208
|
+
instance_id: "abc123",
|
|
209
|
+
session_id: "session-network-fail",
|
|
210
|
+
context_dir: contextDir,
|
|
211
|
+
});
|
|
212
|
+
queueOneReview();
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
const result = await pushReviewResults("https://example.com", "ent_test");
|
|
216
|
+
assert.equal(result.success, false);
|
|
217
|
+
assert.equal(result.delivery_status, "failed");
|
|
218
|
+
} finally {
|
|
219
|
+
globalThis.fetch = originalFetch;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const state = loadReviewTrustState(contextDir);
|
|
223
|
+
assert.equal(state.delivery.status, "failed");
|
|
224
|
+
assert.equal(state.delivery.last_error, "network down");
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test("queuing a new review resets prior accepted delivery markers", async () => {
|
|
228
|
+
const { root, contextDir } = makeProject();
|
|
229
|
+
const originalFetch = globalThis.fetch;
|
|
230
|
+
globalThis.fetch = async () => ({ ok: true, status: 200 });
|
|
231
|
+
|
|
232
|
+
setReviewPushContext({
|
|
233
|
+
repo: "demo-repo",
|
|
234
|
+
instance_id: "abc123",
|
|
235
|
+
session_id: "session-accepted",
|
|
236
|
+
context_dir: contextDir,
|
|
237
|
+
});
|
|
238
|
+
queueOneReview("2026-05-17T10:00:00.000Z");
|
|
239
|
+
|
|
240
|
+
try {
|
|
241
|
+
const result = await pushReviewResults("https://example.com", "ent_test");
|
|
242
|
+
assert.equal(result.success, true);
|
|
243
|
+
} finally {
|
|
244
|
+
globalThis.fetch = originalFetch;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const accepted = loadReviewTrustState(contextDir);
|
|
248
|
+
assert.equal(accepted.delivery.status, "accepted");
|
|
249
|
+
assert.ok(accepted.delivery.last_accepted_at);
|
|
250
|
+
|
|
251
|
+
const queued = recordQueuedReviewTrustState(
|
|
252
|
+
{
|
|
253
|
+
contextDir,
|
|
254
|
+
projectRoot: root,
|
|
255
|
+
repo: "demo-repo",
|
|
256
|
+
instance_id: "abc123",
|
|
257
|
+
session_id: "session-next",
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
reviewedAt: "2026-05-17T11:00:00.000Z",
|
|
261
|
+
summary: {
|
|
262
|
+
total: 1,
|
|
263
|
+
passed: 1,
|
|
264
|
+
failed: 0,
|
|
265
|
+
warnings: 0,
|
|
266
|
+
skipped: 0,
|
|
267
|
+
},
|
|
268
|
+
skippedPolicies: [],
|
|
269
|
+
},
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
assert.equal(queued.delivery.status, "queued");
|
|
273
|
+
assert.equal(queued.delivery.last_attempted_at, null);
|
|
274
|
+
assert.equal(queued.delivery.last_pushed_at, null);
|
|
275
|
+
assert.equal(queued.delivery.last_accepted_at, null);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
test("zero-result reviews stay never_attempted instead of looking queued", () => {
|
|
279
|
+
const { root, contextDir } = makeProject();
|
|
280
|
+
const state = recordQueuedReviewTrustState(
|
|
281
|
+
{
|
|
282
|
+
contextDir,
|
|
283
|
+
projectRoot: root,
|
|
284
|
+
repo: "demo-repo",
|
|
285
|
+
instance_id: "abc123",
|
|
286
|
+
session_id: "session-empty",
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
reviewedAt: "2026-05-17T11:00:00.000Z",
|
|
290
|
+
summary: {
|
|
291
|
+
total: 0,
|
|
292
|
+
passed: 0,
|
|
293
|
+
failed: 0,
|
|
294
|
+
warnings: 0,
|
|
295
|
+
skipped: 2,
|
|
296
|
+
},
|
|
297
|
+
skippedPolicies: [
|
|
298
|
+
{
|
|
299
|
+
policy_id: "require-code-review",
|
|
300
|
+
kind: "predefined",
|
|
301
|
+
type: null,
|
|
302
|
+
reason: "validate this policy from workflow state on the next run.",
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
policy_id: "custom.missing",
|
|
306
|
+
kind: "custom",
|
|
307
|
+
type: "regex-custom",
|
|
308
|
+
reason: 'No evaluator registered for type "regex-custom"',
|
|
309
|
+
},
|
|
310
|
+
],
|
|
311
|
+
},
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
assert.equal(state.delivery.status, "never_attempted");
|
|
315
|
+
assert.equal(state.delivery.review_count, 0);
|
|
316
|
+
assert.equal(state.delivery.queued_at, null);
|
|
317
|
+
assert.equal(state.delivery.last_attempted_at, null);
|
|
318
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"skills": [
|
|
3
|
+
{
|
|
4
|
+
"name": "alpha-codex",
|
|
5
|
+
"description": "Codex-first org skill",
|
|
6
|
+
"body": "# Alpha Codex\n\nUse for Codex review flows.",
|
|
7
|
+
"scope": "cli:codex",
|
|
8
|
+
"enabled": true,
|
|
9
|
+
"updated_at": "2026-05-19T09:00:00.000Z"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"name": "claude-playbook",
|
|
13
|
+
"description": "Claude-only org skill",
|
|
14
|
+
"body": "# Claude Playbook\n\nUse for Claude-only flows.",
|
|
15
|
+
"scope": "cli:claude",
|
|
16
|
+
"enabled": true,
|
|
17
|
+
"updated_at": "2026-05-19T08:45:00.000Z"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"name": "global-guard",
|
|
21
|
+
"description": "Shared org skill",
|
|
22
|
+
"body": "# Global Guard\n\nApply for both CLIs.",
|
|
23
|
+
"scope": "global",
|
|
24
|
+
"enabled": true,
|
|
25
|
+
"updated_at": "2026-05-19T08:30:00.000Z"
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
"manifest": {
|
|
29
|
+
"codex": [
|
|
30
|
+
{
|
|
31
|
+
"name": "alpha-codex",
|
|
32
|
+
"scope": "cli:codex",
|
|
33
|
+
"updated_at": "2026-05-19T09:00:00.000Z"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"name": "global-guard",
|
|
37
|
+
"scope": "global",
|
|
38
|
+
"updated_at": "2026-05-19T08:30:00.000Z"
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
"claude": [
|
|
42
|
+
{
|
|
43
|
+
"name": "claude-playbook",
|
|
44
|
+
"scope": "cli:claude",
|
|
45
|
+
"updated_at": "2026-05-19T08:45:00.000Z"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"name": "global-guard",
|
|
49
|
+
"scope": "global",
|
|
50
|
+
"updated_at": "2026-05-19T08:30:00.000Z"
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
},
|
|
54
|
+
"markdown": {
|
|
55
|
+
"alpha-codex": "---\nname: alpha-codex\ndescription: \"Codex-first org skill\"\n---\n\n# Alpha Codex\n\nUse for Codex review flows.\n",
|
|
56
|
+
"claude-playbook": "---\nname: claude-playbook\ndescription: \"Claude-only org skill\"\n---\n\n# Claude Playbook\n\nUse for Claude-only flows.\n",
|
|
57
|
+
"global-guard": "---\nname: global-guard\ndescription: \"Shared org skill\"\n---\n\n# Global Guard\n\nApply for both CLIs.\n"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
{
|
|
2
|
+
"accepted": {
|
|
3
|
+
"review_session": {
|
|
4
|
+
"sessionId": "session-accepted",
|
|
5
|
+
"repo": "demo-repo",
|
|
6
|
+
"instanceId": "abc123",
|
|
7
|
+
"reviewCount": 3,
|
|
8
|
+
"lastReviewedAt": "2026-05-19T10:00:00.000Z",
|
|
9
|
+
"lastReceivedAt": "2026-05-19T10:02:00.000Z"
|
|
10
|
+
},
|
|
11
|
+
"workflow_session": {
|
|
12
|
+
"sessionId": "session-accepted",
|
|
13
|
+
"repo": "demo-repo",
|
|
14
|
+
"instanceId": "abc123",
|
|
15
|
+
"phase": "reviewed",
|
|
16
|
+
"reviewStatus": "passed",
|
|
17
|
+
"approvalStatus": "ready",
|
|
18
|
+
"lastReceivedAt": "2026-05-19T10:02:00.000Z"
|
|
19
|
+
},
|
|
20
|
+
"workflow_snapshot": {
|
|
21
|
+
"sessionId": "session-accepted",
|
|
22
|
+
"repo": "demo-repo",
|
|
23
|
+
"instanceId": "abc123",
|
|
24
|
+
"phase": "reviewed",
|
|
25
|
+
"reviewStatus": "passed",
|
|
26
|
+
"approvalStatus": "ready",
|
|
27
|
+
"receivedAt": "2026-05-19T10:02:00.000Z",
|
|
28
|
+
"snapshot": {
|
|
29
|
+
"review_trust": {
|
|
30
|
+
"workflow": {
|
|
31
|
+
"id": "secure-build",
|
|
32
|
+
"source": "synced"
|
|
33
|
+
},
|
|
34
|
+
"last_review_at": "2026-05-19T10:00:00.000Z",
|
|
35
|
+
"skipped_policies": [
|
|
36
|
+
{
|
|
37
|
+
"policy_id": "require-code-review",
|
|
38
|
+
"kind": "predefined",
|
|
39
|
+
"type": null,
|
|
40
|
+
"reason": "validate this policy from workflow state on the next run."
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
"delivery": {
|
|
44
|
+
"status": "accepted",
|
|
45
|
+
"review_count": 3,
|
|
46
|
+
"last_accepted_at": "2026-05-19T10:02:00.000Z",
|
|
47
|
+
"last_attempted_at": "2026-05-19T10:02:00.000Z",
|
|
48
|
+
"last_error": null
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"expected": {
|
|
54
|
+
"trustHealth": "healthy",
|
|
55
|
+
"workflowSource": "synced",
|
|
56
|
+
"reviewCount": 3,
|
|
57
|
+
"skippedPolicyIds": [
|
|
58
|
+
"require-code-review"
|
|
59
|
+
]
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"failed": {
|
|
63
|
+
"review_session": null,
|
|
64
|
+
"workflow_session": {
|
|
65
|
+
"sessionId": "session-failed",
|
|
66
|
+
"repo": "demo-repo",
|
|
67
|
+
"instanceId": "abc123",
|
|
68
|
+
"phase": "reviewed",
|
|
69
|
+
"reviewStatus": "passed",
|
|
70
|
+
"approvalStatus": "ready",
|
|
71
|
+
"lastReceivedAt": "2026-05-19T11:02:00.000Z"
|
|
72
|
+
},
|
|
73
|
+
"workflow_snapshot": {
|
|
74
|
+
"sessionId": "session-failed",
|
|
75
|
+
"repo": "demo-repo",
|
|
76
|
+
"instanceId": "abc123",
|
|
77
|
+
"phase": "reviewed",
|
|
78
|
+
"reviewStatus": "passed",
|
|
79
|
+
"approvalStatus": "ready",
|
|
80
|
+
"receivedAt": "2026-05-19T11:02:00.000Z",
|
|
81
|
+
"snapshot": {
|
|
82
|
+
"review_trust": {
|
|
83
|
+
"workflow": {
|
|
84
|
+
"id": "secure-build",
|
|
85
|
+
"source": "synced"
|
|
86
|
+
},
|
|
87
|
+
"last_review_at": "2026-05-19T11:00:00.000Z",
|
|
88
|
+
"skipped_policies": [],
|
|
89
|
+
"delivery": {
|
|
90
|
+
"status": "failed",
|
|
91
|
+
"review_count": 2,
|
|
92
|
+
"last_accepted_at": null,
|
|
93
|
+
"last_attempted_at": "2026-05-19T11:02:00.000Z",
|
|
94
|
+
"last_error": "HTTP 502"
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
"expected": {
|
|
100
|
+
"trustHealth": "failed_delivery",
|
|
101
|
+
"workflowSource": "synced",
|
|
102
|
+
"reviewCount": 2,
|
|
103
|
+
"lastPushFailure": {
|
|
104
|
+
"at": "2026-05-19T11:02:00.000Z",
|
|
105
|
+
"error": "HTTP 502",
|
|
106
|
+
"sessionId": "session-failed",
|
|
107
|
+
"repo": "demo-repo"
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
|
|
4
|
+
import { partitionReviewPolicies } from "../dist/enterprise/reviews/policy-selection.js";
|
|
5
|
+
import { DEFERRED_CODE_REVIEW_REASON } from "../dist/enterprise/reviews/policy-selection.js";
|
|
6
|
+
import "../dist/core/validators/builtins.js";
|
|
7
|
+
|
|
8
|
+
test("partitionReviewPolicies: defers require-code-review with explicit evidence", () => {
|
|
9
|
+
const result = partitionReviewPolicies([
|
|
10
|
+
{
|
|
11
|
+
id: "require-code-review",
|
|
12
|
+
description: "Code review is required",
|
|
13
|
+
priority: 10,
|
|
14
|
+
scope: "global",
|
|
15
|
+
enforce: true,
|
|
16
|
+
source: "org",
|
|
17
|
+
kind: "predefined",
|
|
18
|
+
type: null,
|
|
19
|
+
config: null,
|
|
20
|
+
severity: "block",
|
|
21
|
+
status: "active",
|
|
22
|
+
title: "Require code review",
|
|
23
|
+
},
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
assert.deepEqual(result.enforced, []);
|
|
27
|
+
assert.deepEqual(result.skipped, [
|
|
28
|
+
{
|
|
29
|
+
policy_id: "require-code-review",
|
|
30
|
+
kind: "predefined",
|
|
31
|
+
type: null,
|
|
32
|
+
reason: DEFERRED_CODE_REVIEW_REASON,
|
|
33
|
+
},
|
|
34
|
+
]);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("partitionReviewPolicies: keeps executable validators enforced and skips missing evaluators", () => {
|
|
38
|
+
const result = partitionReviewPolicies([
|
|
39
|
+
{
|
|
40
|
+
id: "no-secrets-in-code",
|
|
41
|
+
description: "Block secrets",
|
|
42
|
+
priority: 10,
|
|
43
|
+
scope: "global",
|
|
44
|
+
enforce: true,
|
|
45
|
+
source: "org",
|
|
46
|
+
kind: "predefined",
|
|
47
|
+
type: null,
|
|
48
|
+
config: null,
|
|
49
|
+
severity: "block",
|
|
50
|
+
status: "active",
|
|
51
|
+
title: "No secrets",
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: "custom.missing",
|
|
55
|
+
description: "Missing evaluator",
|
|
56
|
+
priority: 20,
|
|
57
|
+
scope: "global",
|
|
58
|
+
enforce: true,
|
|
59
|
+
source: "org",
|
|
60
|
+
kind: "custom",
|
|
61
|
+
type: "regex-custom",
|
|
62
|
+
config: { pattern: "secret" },
|
|
63
|
+
severity: "warning",
|
|
64
|
+
status: "active",
|
|
65
|
+
title: "Missing custom evaluator",
|
|
66
|
+
},
|
|
67
|
+
]);
|
|
68
|
+
|
|
69
|
+
assert.deepEqual(result.enforced, [
|
|
70
|
+
{
|
|
71
|
+
id: "no-secrets-in-code",
|
|
72
|
+
type: null,
|
|
73
|
+
config: null,
|
|
74
|
+
severity: "block",
|
|
75
|
+
},
|
|
76
|
+
]);
|
|
77
|
+
assert.deepEqual(result.skipped, [
|
|
78
|
+
{
|
|
79
|
+
policy_id: "custom.missing",
|
|
80
|
+
kind: "custom",
|
|
81
|
+
type: "regex-custom",
|
|
82
|
+
reason: 'No evaluator registered for type "regex-custom"',
|
|
83
|
+
},
|
|
84
|
+
]);
|
|
85
|
+
});
|