@danielblomma/cortex-mcp 2.0.15 → 2.0.18
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 +5 -3
- package/package.json +1 -1
- 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/cli/telemetry-test.ts +2 -1
- package/scaffold/mcp/src/core/license.ts +3 -2
- package/scaffold/mcp/src/core/telemetry/collector.ts +5 -4
- package/scaffold/mcp/src/core/telemetry/state-dir.ts +40 -0
- 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/daemon/main.ts +4 -3
- 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/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/telemetry-collector.test.mjs +31 -1
- 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/parsers/node_modules/.package-lock.json +0 -1
|
@@ -13,7 +13,9 @@ import type { InjectionMatch } from "../../core/policy/injection.js";
|
|
|
13
13
|
import { getLastPush } from "../telemetry/sync.js";
|
|
14
14
|
import { syncFromCloud, syncFromLocal, getLastSync } from "../policy/sync.js";
|
|
15
15
|
import { queueViolation } from "../violations/push.js";
|
|
16
|
-
import { queueReviewResult } from "../reviews/push.js";
|
|
16
|
+
import { getReviewPushContext, queueReviewResult } from "../reviews/push.js";
|
|
17
|
+
import { partitionReviewPolicies } from "../reviews/policy-selection.js";
|
|
18
|
+
import { recordQueuedReviewTrustState } from "../reviews/trust-state.js";
|
|
17
19
|
import { pushWorkflowSnapshot } from "../workflow/push.js";
|
|
18
20
|
import { OUTBOUND_DATA_BOUNDARY } from "../privacy/boundary.js";
|
|
19
21
|
import {
|
|
@@ -31,11 +33,7 @@ import {
|
|
|
31
33
|
} from "../workflow/state.js";
|
|
32
34
|
import { queryAuditLog } from "../../core/audit/query.js";
|
|
33
35
|
import { checkAccess, getAccessDeniedMessage, type Role } from "../../core/rbac/check.js";
|
|
34
|
-
import {
|
|
35
|
-
getGenericEvaluator,
|
|
36
|
-
getValidator,
|
|
37
|
-
runValidators,
|
|
38
|
-
} from "../../core/validators/engine.js";
|
|
36
|
+
import { runValidators } from "../../core/validators/engine.js";
|
|
39
37
|
import "../../core/validators/builtins.js";
|
|
40
38
|
import { recordToolActivity } from "../index.js";
|
|
41
39
|
|
|
@@ -904,41 +902,10 @@ export function registerEnterpriseTools(
|
|
|
904
902
|
// rules. Predefined rules leave type/config null and route to the
|
|
905
903
|
// name-based validator registry.
|
|
906
904
|
const policies = policyStore.getMergedPolicies();
|
|
907
|
-
const
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
if (p.id === "require-code-review") {
|
|
912
|
-
return {
|
|
913
|
-
policy_id: p.id,
|
|
914
|
-
kind: p.kind ?? null,
|
|
915
|
-
type: p.type ?? null,
|
|
916
|
-
reason: "Current context.review invocation is the review being recorded; validate this policy from workflow state on the next run.",
|
|
917
|
-
};
|
|
918
|
-
}
|
|
919
|
-
return {
|
|
920
|
-
policy_id: p.id,
|
|
921
|
-
kind: p.kind ?? null,
|
|
922
|
-
type: p.type ?? null,
|
|
923
|
-
reason: p.type
|
|
924
|
-
? `No evaluator registered for type "${p.type}"`
|
|
925
|
-
: "No executable validator registered for this policy",
|
|
926
|
-
};
|
|
927
|
-
});
|
|
928
|
-
|
|
929
|
-
const enforced = policies
|
|
930
|
-
.filter((p) => p.enforce)
|
|
931
|
-
.filter((p) => {
|
|
932
|
-
if (p.id === "require-code-review") return false;
|
|
933
|
-
if (p.type) return Boolean(getGenericEvaluator(p.type));
|
|
934
|
-
return Boolean(getValidator(p.id));
|
|
935
|
-
})
|
|
936
|
-
.map((p) => ({
|
|
937
|
-
id: p.id,
|
|
938
|
-
type: p.type ?? null,
|
|
939
|
-
config: p.config ?? null,
|
|
940
|
-
severity: p.severity ?? "block",
|
|
941
|
-
}));
|
|
905
|
+
const {
|
|
906
|
+
enforced,
|
|
907
|
+
skipped: skippedPolicies,
|
|
908
|
+
} = partitionReviewPolicies(policies);
|
|
942
909
|
|
|
943
910
|
const now = new Date().toISOString();
|
|
944
911
|
|
|
@@ -964,9 +931,9 @@ export function registerEnterpriseTools(
|
|
|
964
931
|
occurred_at: now,
|
|
965
932
|
});
|
|
966
933
|
}
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
934
|
+
queueReviewResult({
|
|
935
|
+
policy_id: r.policy_id,
|
|
936
|
+
pass: r.pass,
|
|
970
937
|
severity: r.severity,
|
|
971
938
|
message: r.message,
|
|
972
939
|
detail: r.detail,
|
|
@@ -1001,20 +968,43 @@ export function registerEnterpriseTools(
|
|
|
1001
968
|
reviewed_files: reviewedFiles,
|
|
1002
969
|
});
|
|
1003
970
|
const lastReview = workflowState.last_review;
|
|
971
|
+
const reviewedAt = lastReview?.reviewed_at ?? now;
|
|
1004
972
|
if (lastReview) {
|
|
1005
973
|
writeFileSync(
|
|
1006
974
|
join(contextDir, "review-status.json"),
|
|
1007
975
|
`${JSON.stringify({
|
|
1008
976
|
reviewed: lastReview.status === "passed",
|
|
1009
977
|
reviewer: "context.review",
|
|
1010
|
-
timestamp:
|
|
978
|
+
timestamp: reviewedAt,
|
|
1011
979
|
scope: parsed.scope,
|
|
1012
980
|
reviewed_files: reviewedFiles,
|
|
1013
981
|
}, null, 2)}\n`,
|
|
1014
982
|
"utf8",
|
|
1015
983
|
);
|
|
1016
984
|
}
|
|
1017
|
-
|
|
985
|
+
const trustState = recordQueuedReviewTrustState(
|
|
986
|
+
{
|
|
987
|
+
contextDir,
|
|
988
|
+
projectRoot,
|
|
989
|
+
repo: getReviewPushContext().repo,
|
|
990
|
+
instance_id: getReviewPushContext().instance_id,
|
|
991
|
+
session_id: getReviewPushContext().session_id,
|
|
992
|
+
task_id: process.env.CORTEX_ACTIVE_TASK_ID?.trim() || undefined,
|
|
993
|
+
},
|
|
994
|
+
{
|
|
995
|
+
reviewedAt,
|
|
996
|
+
summary: {
|
|
997
|
+
total: output.summary.total,
|
|
998
|
+
passed: output.summary.passed,
|
|
999
|
+
failed: output.summary.failed,
|
|
1000
|
+
warnings: output.summary.warnings,
|
|
1001
|
+
skipped: skippedPolicies.length,
|
|
1002
|
+
},
|
|
1003
|
+
skippedPolicies: skippedPolicies,
|
|
1004
|
+
},
|
|
1005
|
+
);
|
|
1006
|
+
const workflowWithTrust = loadWorkflowState(contextDir);
|
|
1007
|
+
await pushWorkflowStateIfConfigured(config, workflowWithTrust);
|
|
1018
1008
|
|
|
1019
1009
|
return buildToolResult({
|
|
1020
1010
|
scope: parsed.scope,
|
|
@@ -1024,7 +1014,10 @@ export function registerEnterpriseTools(
|
|
|
1024
1014
|
...output.summary,
|
|
1025
1015
|
skipped: skippedPolicies.length,
|
|
1026
1016
|
},
|
|
1027
|
-
workflow:
|
|
1017
|
+
workflow: workflowWithTrust,
|
|
1018
|
+
workflow_source: trustState.workflow.source,
|
|
1019
|
+
delivery: trustState.delivery,
|
|
1020
|
+
trust_warnings: trustState.trust_warnings,
|
|
1028
1021
|
});
|
|
1029
1022
|
},
|
|
1030
1023
|
);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { isEnforcedMode } from "../../hooks/shared.js";
|
|
2
3
|
import {
|
|
3
4
|
WorkflowAdvanceInput,
|
|
4
5
|
WorkflowEnvelopeInput,
|
|
@@ -38,6 +39,7 @@ export function registerHarnessTools(server: McpServer): void {
|
|
|
38
39
|
async (input) => buildResult(
|
|
39
40
|
runWorkflowStart(WorkflowStartInput.parse(input ?? {}), {
|
|
40
41
|
cwd: resolveProjectRoot(),
|
|
42
|
+
bundledFallbackPolicy: isEnforcedMode(resolveProjectRoot()) ? "block" : "allow",
|
|
41
43
|
}) as ToolPayload,
|
|
42
44
|
),
|
|
43
45
|
);
|
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
ReviewResult,
|
|
11
11
|
ReviewSummary,
|
|
12
12
|
} from "../../core/validators/engine.js";
|
|
13
|
+
import type { ReviewTrustState } from "../reviews/trust-state.js";
|
|
13
14
|
|
|
14
15
|
export type WorkflowPhase =
|
|
15
16
|
| "planning"
|
|
@@ -111,6 +112,7 @@ export type WorkflowState = {
|
|
|
111
112
|
notes: WorkflowNote[];
|
|
112
113
|
todos: WorkflowTodo[];
|
|
113
114
|
history: WorkflowHistoryEntry[];
|
|
115
|
+
review_trust: ReviewTrustState | null;
|
|
114
116
|
};
|
|
115
117
|
|
|
116
118
|
export type WorkflowMutationResult = {
|
|
@@ -145,6 +147,10 @@ function workflowStatePath(contextDir: string): string {
|
|
|
145
147
|
return join(workflowDir(contextDir), "state.json");
|
|
146
148
|
}
|
|
147
149
|
|
|
150
|
+
export function hasWorkflowState(contextDir: string): boolean {
|
|
151
|
+
return existsSync(workflowStatePath(contextDir));
|
|
152
|
+
}
|
|
153
|
+
|
|
148
154
|
function workflowReviewsDir(contextDir: string): string {
|
|
149
155
|
return join(workflowDir(contextDir), "reviews");
|
|
150
156
|
}
|
|
@@ -195,6 +201,7 @@ function initialState(): WorkflowState {
|
|
|
195
201
|
notes: [],
|
|
196
202
|
todos: [],
|
|
197
203
|
history: [],
|
|
204
|
+
review_trust: null,
|
|
198
205
|
};
|
|
199
206
|
}
|
|
200
207
|
|
|
@@ -457,6 +464,20 @@ export function recordWorkflowUpdate(
|
|
|
457
464
|
);
|
|
458
465
|
}
|
|
459
466
|
|
|
467
|
+
export function setWorkflowReviewTrust(
|
|
468
|
+
contextDir: string,
|
|
469
|
+
reviewTrust: ReviewTrustState,
|
|
470
|
+
): WorkflowState {
|
|
471
|
+
if (!hasWorkflowState(contextDir)) {
|
|
472
|
+
const state = initialState();
|
|
473
|
+
state.review_trust = reviewTrust;
|
|
474
|
+
return writeState(contextDir, withRecalculatedApproval(state, true));
|
|
475
|
+
}
|
|
476
|
+
const state = loadWorkflowState(contextDir);
|
|
477
|
+
state.review_trust = reviewTrust;
|
|
478
|
+
return writeState(contextDir, withRecalculatedApproval(state, true));
|
|
479
|
+
}
|
|
480
|
+
|
|
460
481
|
export function addWorkflowNote(
|
|
461
482
|
contextDir: string,
|
|
462
483
|
input: { title: string; details: string }
|
|
@@ -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
|
+
}
|