@windyroad/risk-scorer 0.18.12 → 0.18.13-preview.1069
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/agents/pipeline.md
CHANGED
|
@@ -293,6 +293,18 @@ readiness may be listed separately as "post-release follow-ups" outside the
|
|
|
293
293
|
residual risk computation, but MUST NOT appear in a Controls list and MUST NOT
|
|
294
294
|
reduce any inherent risk score.
|
|
295
295
|
|
|
296
|
+
**Deferral is not a control.** Moving release metadata, leaving work on a
|
|
297
|
+
branch, or promising to ship it later does not remove or bound a hazard, so it
|
|
298
|
+
MUST NOT reduce the current action's residual risk. Score only the action in
|
|
299
|
+
scope; record deferred work separately rather than charging an unintegrated
|
|
300
|
+
future action to the current score.
|
|
301
|
+
|
|
302
|
+
Do not confuse deferral with genuine scope reduction. A small, complete,
|
|
303
|
+
independently releasable change can reduce accumulated risk and blast radius.
|
|
304
|
+
Prefer frequent small commits, pushes, and releases over batching. The test is
|
|
305
|
+
whether the current action is complete without the omitted work: if yes, scope
|
|
306
|
+
was reduced; if no, the hazard was merely deferred and earns no control credit.
|
|
307
|
+
|
|
296
308
|
### R009 control vocabulary — SKILL/agent-prose surfaces (P355 / RFC-012 / ADR-075)
|
|
297
309
|
|
|
298
310
|
For diffs touching `packages/*/skills/*/SKILL.md`, `packages/*/skills/*/REFERENCE.md`, or `packages/*/agents/*.md`, the **previously-irreducible R009 "no behavioural harness for the LLM-prose surface" floor is discharged** when a paired promptfoo Tier-A/B eval exists for the changed prose AND `npx promptfoo eval` passes on this commit. Credit it as a named likelihood-reducing control with the same evidence shape as behavioural bats:
|
|
@@ -33,6 +33,37 @@ function pendingDir() {
|
|
|
33
33
|
return join(process.env.TMPDIR || "/tmp", "claude-risk-pending");
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
function fieldType(input, field) {
|
|
37
|
+
if (!input || !Object.prototype.hasOwnProperty.call(input, field)) return "absent";
|
|
38
|
+
if (input[field] === null) return "null";
|
|
39
|
+
if (Array.isArray(input[field])) return "array";
|
|
40
|
+
return typeof input[field];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function diagnoseSubagentStop(input, outcome, reason) {
|
|
44
|
+
const dir = pendingDir();
|
|
45
|
+
const path = join(dir, "subagent-stop-diagnostic.json");
|
|
46
|
+
const temporary = join(dir, `.subagent-stop-diagnostic-${process.pid}.tmp`);
|
|
47
|
+
try {
|
|
48
|
+
mkdirSync(dir, { recursive: true });
|
|
49
|
+
writeFileSync(temporary, JSON.stringify({
|
|
50
|
+
timestamp: new Date().toISOString(),
|
|
51
|
+
outcome,
|
|
52
|
+
reason,
|
|
53
|
+
event: input?.hook_event_name === "SubagentStop" ? "SubagentStop" : "other",
|
|
54
|
+
fields: Object.fromEntries([
|
|
55
|
+
"session_id",
|
|
56
|
+
"agent_id",
|
|
57
|
+
"agent_type",
|
|
58
|
+
"last_assistant_message",
|
|
59
|
+
].map((field) => [field, fieldType(input, field)])),
|
|
60
|
+
}), { mode: 0o600 });
|
|
61
|
+
renameSync(temporary, path);
|
|
62
|
+
} catch {
|
|
63
|
+
rmSync(temporary, { force: true });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
36
67
|
function statePath(input, target, suffix = "") {
|
|
37
68
|
return join(riskDir(input.session_id), `codex-agent-${Buffer.from(target).toString("base64url")}${suffix}`);
|
|
38
69
|
}
|
|
@@ -72,29 +103,47 @@ function claimTarget(input, target) {
|
|
|
72
103
|
return { claim, done };
|
|
73
104
|
}
|
|
74
105
|
|
|
75
|
-
function pipelineAssessment(output) {
|
|
106
|
+
function pipelineAssessment(output, reject = () => {}) {
|
|
107
|
+
if (typeof output !== "string" || !output) {
|
|
108
|
+
reject("missing-output");
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
76
111
|
const roots = [...output.matchAll(/^RISK_CWD:[ \t]*(.+)$/gm)];
|
|
77
|
-
if (roots.length !== 1)
|
|
112
|
+
if (roots.length !== 1) {
|
|
113
|
+
reject(roots.length ? "multiple-risk-cwd" : "missing-risk-cwd");
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
78
116
|
|
|
79
117
|
const declaredRoot = roots[0][1].trim();
|
|
80
|
-
if (!isAbsolute(declaredRoot))
|
|
118
|
+
if (!isAbsolute(declaredRoot)) {
|
|
119
|
+
reject("relative-risk-cwd");
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
81
122
|
|
|
82
123
|
let root;
|
|
83
124
|
try {
|
|
84
125
|
root = realpathSync(declaredRoot);
|
|
85
126
|
} catch {
|
|
127
|
+
reject("unreadable-risk-cwd");
|
|
86
128
|
return null;
|
|
87
129
|
}
|
|
88
130
|
const git = spawnSync("git", ["-C", root, "rev-parse", "--show-toplevel"], { encoding: "utf8" });
|
|
89
|
-
if (git.status !== 0)
|
|
131
|
+
if (git.status !== 0) {
|
|
132
|
+
reject("not-git-worktree");
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
90
135
|
|
|
91
136
|
let gitRoot;
|
|
92
137
|
try {
|
|
93
138
|
gitRoot = realpathSync(git.stdout.trim());
|
|
94
139
|
} catch {
|
|
140
|
+
reject("unreadable-git-root");
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
if (gitRoot !== root) {
|
|
144
|
+
reject("risk-cwd-not-git-root");
|
|
95
145
|
return null;
|
|
96
146
|
}
|
|
97
|
-
if (gitRoot !== root) return null;
|
|
98
147
|
|
|
99
148
|
let sanitized = output.split(/\r?\n/).filter((line) => !line.startsWith("RISK_CWD:")).join("\n");
|
|
100
149
|
for (const privatePath of new Set([declaredRoot, root])) {
|
|
@@ -133,17 +182,39 @@ function freshReceipt(path) {
|
|
|
133
182
|
}
|
|
134
183
|
|
|
135
184
|
function persistPendingPipeline(input) {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
185
|
+
diagnoseSubagentStop(input, "received", "pipeline-receipt-attempt");
|
|
186
|
+
if (input.agent_type !== "wr-risk-scorer:pipeline") {
|
|
187
|
+
diagnoseSubagentStop(input, "rejected", "unexpected-agent-type");
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
let rejection;
|
|
191
|
+
const assessment = pipelineAssessment(input.last_assistant_message, (reason) => { rejection = reason; });
|
|
192
|
+
if (!assessment) {
|
|
193
|
+
diagnoseSubagentStop(input, "rejected", rejection || "invalid-assessment");
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
139
196
|
const id = checkoutId(assessment.root);
|
|
140
197
|
const hash = stateHash(assessment.root);
|
|
141
198
|
const completion = completionId(input, assessment.output);
|
|
142
|
-
if (!hash
|
|
199
|
+
if (!hash) {
|
|
200
|
+
diagnoseSubagentStop(input, "rejected", "state-hash-failed");
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (!completion) {
|
|
204
|
+
diagnoseSubagentStop(input, "rejected", "missing-completion-identity");
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (!/^RISK_SCORES: commit=\d+ push=\d+ release=\d+$/m.test(assessment.output)) {
|
|
208
|
+
diagnoseSubagentStop(input, "rejected", "missing-risk-scores");
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
143
211
|
mkdirSync(pendingDir(), { recursive: true });
|
|
144
212
|
const path = pendingPath(id, hash, completion);
|
|
145
213
|
for (const candidate of [path, `${path}.done`]) {
|
|
146
|
-
if (freshReceipt(candidate))
|
|
214
|
+
if (freshReceipt(candidate)) {
|
|
215
|
+
diagnoseSubagentStop(input, "duplicate", "fresh-receipt-exists");
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
147
218
|
rmSync(candidate, { force: true });
|
|
148
219
|
}
|
|
149
220
|
try {
|
|
@@ -155,8 +226,14 @@ function persistPendingPipeline(input) {
|
|
|
155
226
|
completionId: completion,
|
|
156
227
|
createdAt: Date.now(),
|
|
157
228
|
}), { flag: "wx", mode: 0o600 });
|
|
229
|
+
diagnoseSubagentStop(input, "receipt-written", "checkout-bound-receipt");
|
|
158
230
|
} catch (error) {
|
|
159
|
-
if (error?.code
|
|
231
|
+
if (error?.code === "EEXIST") {
|
|
232
|
+
diagnoseSubagentStop(input, "duplicate", "receipt-race");
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
diagnoseSubagentStop(input, "rejected", "receipt-write-failed");
|
|
236
|
+
throw error;
|
|
160
237
|
}
|
|
161
238
|
}
|
|
162
239
|
|
|
@@ -306,6 +383,9 @@ let input;
|
|
|
306
383
|
try {
|
|
307
384
|
input = JSON.parse(body);
|
|
308
385
|
} catch {
|
|
386
|
+
if (process.argv.includes("--subagent-stop")) {
|
|
387
|
+
diagnoseSubagentStop({}, "rejected", "malformed-json");
|
|
388
|
+
}
|
|
309
389
|
process.exit(0);
|
|
310
390
|
}
|
|
311
391
|
|
|
@@ -314,7 +394,12 @@ if (process.argv.includes("--consume-pending")) {
|
|
|
314
394
|
process.exit(process.exitCode || 0);
|
|
315
395
|
}
|
|
316
396
|
|
|
317
|
-
if (!/^[A-Za-z0-9-]+$/.test(input.session_id || ""))
|
|
397
|
+
if (!/^[A-Za-z0-9-]+$/.test(input.session_id || "")) {
|
|
398
|
+
if (process.argv.includes("--subagent-stop")) {
|
|
399
|
+
diagnoseSubagentStop(input, "rejected", "invalid-session-id");
|
|
400
|
+
}
|
|
401
|
+
process.exit(0);
|
|
402
|
+
}
|
|
318
403
|
|
|
319
404
|
if (["collaborationspawn_agent", "spawn_agent", "multi_agent_v1__spawn_agent"].includes(input.tool_name)) {
|
|
320
405
|
rememberSpawn(input);
|
package/package.json
CHANGED