@peterxiaoyang/superspec 0.1.10 → 0.1.12
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 +8 -10
- package/dist/src/apply_worker_chain.d.ts +6 -3
- package/dist/src/apply_worker_chain.js +125 -30
- package/dist/src/apply_worker_chain_lifecycle.d.ts +7 -5
- package/dist/src/apply_worker_chain_lifecycle.js +153 -22
- package/dist/src/cli_args.d.ts +1 -0
- package/dist/src/cli_args.js +33 -15
- package/dist/src/disclosure.js +2 -2
- package/dist/src/evidence.js +104 -8
- package/dist/src/gates.d.ts +1 -0
- package/dist/src/gates.js +160 -27
- package/dist/src/hooks/adapter.js +43 -7
- package/dist/src/hooks/guard_api.js +2 -2
- package/dist/src/hooks/health.js +0 -12
- package/dist/src/hooks/policy_event.js +11 -1
- package/dist/src/i18n.js +63 -3
- package/dist/src/init_cli.js +1 -1
- package/dist/src/install_engine.js +64 -0
- package/dist/src/openspec.d.ts +2 -0
- package/dist/src/openspec.js +31 -0
- package/dist/src/packet_render.d.ts +1 -0
- package/dist/src/packet_render.js +248 -31
- package/dist/src/packet_schema.d.ts +2 -0
- package/dist/src/tasks.d.ts +5 -0
- package/dist/src/tasks.js +62 -0
- package/dist/src/util.d.ts +4 -0
- package/dist/src/util.js +13 -0
- package/dist/superspec.js +4 -4
- package/package.json +3 -3
- package/templates/hooks/codex-hooks.json +0 -26
- package/templates/sidecar/discovery.md +9 -0
- package/templates/sidecar/test-contract.md +2 -1
- package/templates/workflow/prompts/code-reviewer.md +1 -1
- package/templates/workflow/prompts/executor.md +1 -1
- package/templates/workflow/prompts/test-engineer.md +1 -0
- package/templates/workflow/prompts/test-runner.md +3 -1
- package/templates/workflow/prompts/verifier.md +9 -9
- package/templates/workflow/skills/superspec-apply/SKILL.md +58 -40
- package/templates/workflow/skills/superspec-archive/SKILL.md +5 -13
- package/templates/workflow/skills/superspec-explore/SKILL.md +17 -18
- package/templates/workflow/skills/superspec-propose/SKILL.md +26 -22
- package/templates/workflow/skills/superspec-review/SKILL.md +17 -24
- package/dist/src/packet_measure.d.ts +0 -43
- package/dist/src/packet_measure.js +0 -382
- /package/bin/{superspec-guard.js → superspec-check.js} +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { isObject, reason, renderList, repr } from "./util.js";
|
|
2
|
-
import { find_pass, live_pass } from "./evidence.js";
|
|
1
|
+
import { NO_TDD_REASONS, isObject, reason, renderList, repr } from "./util.js";
|
|
2
|
+
import { find_pass, live_pass, live_user_confirmations } from "./evidence.js";
|
|
3
|
+
import { effective_superseded_ids } from "./openspec.js";
|
|
3
4
|
import { apply_worker_implementation_fingerprint, apply_worker_executor_input_ref_digest, compute_apply_worker_freshness, fingerprint_matches, pinned_artifact_ref_reasons as shared_pinned_artifact_ref_reasons, pre_edit_evidence_ref_reasons, read_pinned_artifact_json, worker_input_ref_digest, worker_test_run_reasons, } from "./apply_worker_chain.js";
|
|
4
5
|
function pinned_artifact_ref_matches(changeRoot, refItem, role, taskId, chainId, kind = "worker_report") {
|
|
5
6
|
return shared_pinned_artifact_ref_reasons(changeRoot, refItem, `${role}_ref`, { kind, role, taskId, chainId }).length === 0;
|
|
@@ -25,6 +26,23 @@ function chain_green_ids(ev) {
|
|
|
25
26
|
: String(ev.green_test_run_evidence_ref ?? "");
|
|
26
27
|
return greenId ? [greenId] : [];
|
|
27
28
|
}
|
|
29
|
+
function chain_alternative_ids(ev) {
|
|
30
|
+
const refs = new Set();
|
|
31
|
+
if (Array.isArray(ev.alternative_verification_evidence_refs)) {
|
|
32
|
+
for (const item of ev.alternative_verification_evidence_refs) {
|
|
33
|
+
if (isObject(item) && typeof item.evidence_id === "string" && item.evidence_id)
|
|
34
|
+
refs.add(item.evidence_id);
|
|
35
|
+
else if (typeof item === "string" && item)
|
|
36
|
+
refs.add(item);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const single = ev.alternative_verification_evidence_ref;
|
|
40
|
+
if (isObject(single) && typeof single.evidence_id === "string" && single.evidence_id)
|
|
41
|
+
refs.add(single.evidence_id);
|
|
42
|
+
else if (typeof single === "string" && single)
|
|
43
|
+
refs.add(single);
|
|
44
|
+
return [...refs].sort();
|
|
45
|
+
}
|
|
28
46
|
function active_pre_edit_input_refs(evidences, active) {
|
|
29
47
|
const preIds = Array.isArray(active.pre_edit_evidence_refs) ? active.pre_edit_evidence_refs.map(String) : [];
|
|
30
48
|
return preIds.map((evidenceId) => {
|
|
@@ -34,13 +52,42 @@ function active_pre_edit_input_refs(evidences, active) {
|
|
|
34
52
|
: { kind: "test_run", evidence_id: evidenceId };
|
|
35
53
|
});
|
|
36
54
|
}
|
|
55
|
+
function active_chain_verifier_input_ref(active) {
|
|
56
|
+
const result = {
|
|
57
|
+
kind: "apply_worker_chain",
|
|
58
|
+
evidence_id: active.evidence_id,
|
|
59
|
+
task_id: active.task_id,
|
|
60
|
+
apply_worker_chain_id: active.apply_worker_chain_id,
|
|
61
|
+
pre_edit_proof_kind: active.pre_edit_proof_kind ?? "red_or_characterization",
|
|
62
|
+
pre_edit_evidence_refs: Array.isArray(active.pre_edit_evidence_refs) ? active.pre_edit_evidence_refs.map(String).filter(Boolean).sort() : [],
|
|
63
|
+
};
|
|
64
|
+
for (const field of ["apply_execution_surface", "tdd_required", "no_tdd_reason"]) {
|
|
65
|
+
if (active[field] !== undefined)
|
|
66
|
+
result[field] = active[field];
|
|
67
|
+
}
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
function active_no_tdd_declared_valid(active) {
|
|
71
|
+
const preIds = Array.isArray(active.pre_edit_evidence_refs) ? active.pre_edit_evidence_refs.map(String).filter(Boolean) : [];
|
|
72
|
+
return active.pre_edit_proof_kind === "no_tdd_declared"
|
|
73
|
+
&& preIds.length === 0
|
|
74
|
+
&& active.tdd_required === false
|
|
75
|
+
&& (active.apply_execution_surface === "implementation" || active.apply_execution_surface === "runtime_config")
|
|
76
|
+
&& NO_TDD_REASONS.has(String(active.no_tdd_reason ?? ""));
|
|
77
|
+
}
|
|
78
|
+
function alternative_evidence_input_ref(ev) {
|
|
79
|
+
return {
|
|
80
|
+
kind: ev.kind,
|
|
81
|
+
evidence_id: ev.evidence_id,
|
|
82
|
+
gate: ev.gate,
|
|
83
|
+
task_id: ev.task_id,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
37
86
|
function closed_apply_worker_chain_valid(repoRoot, changeRoot, ev, evidences, taskId, chainId) {
|
|
38
87
|
const active = live_pass(evidences, { gate: "task_complete", kind: "apply_worker_chain", task_id: taskId })
|
|
39
88
|
.find((item) => item.chain_state === "active" && item.apply_worker_chain_id === chainId);
|
|
40
89
|
if (!active)
|
|
41
90
|
return false;
|
|
42
|
-
if (pre_edit_evidence_ref_reasons(evidences, active, taskId).length > 0)
|
|
43
|
-
return false;
|
|
44
91
|
if (!pinned_artifact_ref_matches(changeRoot, ev.executor_report_ref, "executor", taskId, chainId))
|
|
45
92
|
return false;
|
|
46
93
|
if (!pinned_artifact_ref_matches(changeRoot, ev.task_code_review_report_ref, "code-reviewer", taskId, chainId))
|
|
@@ -59,6 +106,47 @@ function closed_apply_worker_chain_valid(repoRoot, changeRoot, ev, evidences, ta
|
|
|
59
106
|
const verifierObserved = verifierReport?.observed_freshness_fingerprint ?? (isObject(ev.verifier_report_ref) ? ev.verifier_report_ref.observed_freshness_fingerprint : undefined);
|
|
60
107
|
if (!fingerprint_matches(verifierObserved, ev.observed_freshness_fingerprint))
|
|
61
108
|
return false;
|
|
109
|
+
const completionProofKind = String(ev.completion_proof_kind ?? "green_tests");
|
|
110
|
+
if (completionProofKind !== "green_tests" && completionProofKind !== "alternative_verification")
|
|
111
|
+
return false;
|
|
112
|
+
const preEditProofKind = String(active.pre_edit_proof_kind ?? "red_or_characterization");
|
|
113
|
+
if (completionProofKind === "green_tests" && preEditProofKind !== "red_or_characterization")
|
|
114
|
+
return false;
|
|
115
|
+
if (completionProofKind === "alternative_verification" && !active_no_tdd_declared_valid(active))
|
|
116
|
+
return false;
|
|
117
|
+
if (pre_edit_evidence_ref_reasons(changeRoot, evidences, active, taskId).length > 0)
|
|
118
|
+
return false;
|
|
119
|
+
if (completionProofKind === "alternative_verification") {
|
|
120
|
+
const alternativeIds = chain_alternative_ids(ev);
|
|
121
|
+
if (alternativeIds.length === 0)
|
|
122
|
+
return false;
|
|
123
|
+
const alternatives = alternativeIds.map((evidenceId) => live_pass(evidences, { gate: "task_complete", task_id: taskId })
|
|
124
|
+
.find((item) => (String(item.evidence_id ?? "") === evidenceId
|
|
125
|
+
&& (item.kind === "alternative_verification" || item.kind === "manual_verification")))).filter((item) => isObject(item));
|
|
126
|
+
if (alternatives.length !== alternativeIds.length)
|
|
127
|
+
return false;
|
|
128
|
+
if (!report_input_refs_match(ev.verifier_report_ref, [
|
|
129
|
+
ev.executor_report_ref,
|
|
130
|
+
ev.task_code_review_report_ref,
|
|
131
|
+
...alternatives.map(alternative_evidence_input_ref),
|
|
132
|
+
active_chain_verifier_input_ref(active),
|
|
133
|
+
]))
|
|
134
|
+
return false;
|
|
135
|
+
const currentFreshness = compute_apply_worker_freshness(repoRoot, changeRoot, evidences, taskId, chainId, {
|
|
136
|
+
executor_report_ref: ev.executor_report_ref,
|
|
137
|
+
task_code_review_report_ref: ev.task_code_review_report_ref,
|
|
138
|
+
alternative_verification_evidence_refs: alternativeIds,
|
|
139
|
+
completion_proof_kind: "alternative_verification",
|
|
140
|
+
verifier_report_ref: ev.verifier_report_ref,
|
|
141
|
+
});
|
|
142
|
+
if (Array.isArray(currentFreshness.unexpected_guard_owned_dirty_paths) && currentFreshness.unexpected_guard_owned_dirty_paths.length > 0)
|
|
143
|
+
return false;
|
|
144
|
+
if (Array.isArray(currentFreshness.protected_dirty_paths) && currentFreshness.protected_dirty_paths.length > 0)
|
|
145
|
+
return false;
|
|
146
|
+
if (!fingerprint_matches(isObject(ev.task_code_review_report_ref) ? ev.task_code_review_report_ref.observed_implementation_fingerprint : undefined, currentFreshness.implementation_fingerprint))
|
|
147
|
+
return false;
|
|
148
|
+
return fingerprint_matches(currentFreshness, verifierObserved);
|
|
149
|
+
}
|
|
62
150
|
const greenIds = chain_green_ids(ev);
|
|
63
151
|
if (greenIds.length === 0)
|
|
64
152
|
return false;
|
|
@@ -82,6 +170,7 @@ function closed_apply_worker_chain_valid(repoRoot, changeRoot, ev, evidences, ta
|
|
|
82
170
|
task_code_review_report_ref: ev.task_code_review_report_ref,
|
|
83
171
|
green_test_run_evidence_ref: greenIds[0],
|
|
84
172
|
green_test_run_evidence_refs: greenIds,
|
|
173
|
+
completion_proof_kind: "green_tests",
|
|
85
174
|
verifier_report_ref: ev.verifier_report_ref,
|
|
86
175
|
});
|
|
87
176
|
if (Array.isArray(currentFreshness.unexpected_guard_owned_dirty_paths) && currentFreshness.unexpected_guard_owned_dirty_paths.length > 0)
|
|
@@ -97,9 +186,37 @@ function closed_apply_worker_chain_valid(repoRoot, changeRoot, ev, evidences, ta
|
|
|
97
186
|
function pinned_takeover_baseline_valid(changeRoot, refItem, taskId, chainId) {
|
|
98
187
|
return shared_pinned_artifact_ref_reasons(changeRoot, refItem, "serial_takeover_baseline_ref", { kind: "status_report", role: "verifier", taskId, chainId }).length === 0;
|
|
99
188
|
}
|
|
100
|
-
function
|
|
189
|
+
function serial_takeover_baseline_ref_ids(ev) {
|
|
190
|
+
const refs = new Set();
|
|
191
|
+
if (isObject(ev.serial_takeover_baseline_ref)) {
|
|
192
|
+
for (const field of ["path", "ref_path", "evidence_id"]) {
|
|
193
|
+
const value = ev.serial_takeover_baseline_ref[field];
|
|
194
|
+
if (typeof value === "string" && value)
|
|
195
|
+
refs.add(value);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return [...refs].sort();
|
|
199
|
+
}
|
|
200
|
+
function serial_takeover_confirmation_valid(evidences, ev, active) {
|
|
201
|
+
const confirmationId = String(ev.takeover_confirmation_evidence_id ?? "");
|
|
202
|
+
const activeId = String(active.evidence_id ?? "");
|
|
203
|
+
const baselineIds = serial_takeover_baseline_ref_ids(ev);
|
|
204
|
+
if (!confirmationId || !activeId || baselineIds.length === 0)
|
|
205
|
+
return false;
|
|
206
|
+
const confirmation = live_user_confirmations(evidences, "serial_takeover")
|
|
207
|
+
.find((item) => String(item.evidence_id ?? "") === confirmationId);
|
|
208
|
+
if (!confirmation)
|
|
209
|
+
return false;
|
|
210
|
+
const confirmed = new Set(Array.isArray(confirmation.confirmed_refs) ? confirmation.confirmed_refs.map(String).filter(Boolean) : []);
|
|
211
|
+
return confirmed.has(activeId) && baselineIds.some((item) => confirmed.has(item));
|
|
212
|
+
}
|
|
213
|
+
function abandoned_apply_worker_chain_valid(repoRoot, changeRoot, ev, active, evidences, taskId, chainId) {
|
|
101
214
|
if (!active)
|
|
102
215
|
return false;
|
|
216
|
+
const hasRestored = "restored_implementation_fingerprint" in ev;
|
|
217
|
+
const hasSerialTakeover = "serial_takeover_baseline_ref" in ev;
|
|
218
|
+
if (hasRestored && hasSerialTakeover)
|
|
219
|
+
return false;
|
|
103
220
|
if ("restored_implementation_fingerprint" in ev) {
|
|
104
221
|
const activeScope = Array.isArray(active.declared_task_write_scope) ? active.declared_task_write_scope.map(String).filter(Boolean) : [];
|
|
105
222
|
const current = apply_worker_implementation_fingerprint(repoRoot, changeRoot, [], { declaredTaskWriteScope: activeScope });
|
|
@@ -107,21 +224,16 @@ function abandoned_apply_worker_chain_valid(repoRoot, changeRoot, ev, active, ta
|
|
|
107
224
|
&& fingerprint_matches(current, active.source_implementation_fingerprint))
|
|
108
225
|
return true;
|
|
109
226
|
}
|
|
110
|
-
if (
|
|
227
|
+
if (hasSerialTakeover) {
|
|
111
228
|
return pinned_takeover_baseline_valid(changeRoot, ev.serial_takeover_baseline_ref, taskId, chainId)
|
|
112
|
-
&&
|
|
113
|
-
&& ev.successor_green_evidence_refs.length > 0
|
|
114
|
-
&& ev.successor_green_evidence_refs.every((item) => typeof item === "string" && item.length > 0);
|
|
229
|
+
&& serial_takeover_confirmation_valid(evidences, ev, active);
|
|
115
230
|
}
|
|
116
231
|
return false;
|
|
117
232
|
}
|
|
118
|
-
export function serial_completion_green(ev) {
|
|
119
|
-
return (ev.runner_origin === undefined || ev.runner_origin === "main-thread")
|
|
120
|
-
&& ev.apply_execution_chain !== "executor_worker"
|
|
121
|
-
&& typeof ev.apply_worker_chain_id !== "string";
|
|
122
|
-
}
|
|
123
233
|
export function apply_worker_chain_lifecycle_state(repoRoot, changeRoot, evidences, taskId) {
|
|
124
234
|
const chains = find_pass(evidences, { gate: "task_complete", kind: "apply_worker_chain", task_id: taskId });
|
|
235
|
+
const supersededIds = effective_superseded_ids(evidences);
|
|
236
|
+
const liveChainMarker = (ev) => !supersededIds.has(String(ev.evidence_id ?? ""));
|
|
125
237
|
const activeByChain = new Map();
|
|
126
238
|
const activeCounts = new Map();
|
|
127
239
|
for (const ev of chains) {
|
|
@@ -147,9 +259,9 @@ export function apply_worker_chain_lifecycle_state(repoRoot, changeRoot, evidenc
|
|
|
147
259
|
if (String(ev.chain_state ?? "") === "closed")
|
|
148
260
|
valid = closed_apply_worker_chain_valid(repoRoot, changeRoot, ev, evidences, taskId, chainId);
|
|
149
261
|
if (String(ev.chain_state ?? "") === "abandoned")
|
|
150
|
-
valid = abandoned_apply_worker_chain_valid(repoRoot, changeRoot, ev, activeByChain.get(chainId), taskId, chainId);
|
|
262
|
+
valid = abandoned_apply_worker_chain_valid(repoRoot, changeRoot, ev, activeByChain.get(chainId), evidences, taskId, chainId);
|
|
151
263
|
terminalValidity.set(ev, valid);
|
|
152
|
-
return valid;
|
|
264
|
+
return valid && liveChainMarker(ev);
|
|
153
265
|
})
|
|
154
266
|
.map((ev) => String(ev.apply_worker_chain_id ?? ""))
|
|
155
267
|
.filter(Boolean));
|
|
@@ -158,7 +270,7 @@ export function apply_worker_chain_lifecycle_state(repoRoot, changeRoot, evidenc
|
|
|
158
270
|
.filter((ev) => {
|
|
159
271
|
const chainId = String(ev.apply_worker_chain_id ?? "");
|
|
160
272
|
const valid = String(ev.chain_state ?? "") === "closed" && (terminalValidity.get(ev) ?? closed_apply_worker_chain_valid(repoRoot, changeRoot, ev, evidences, taskId, chainId));
|
|
161
|
-
return chainId && valid;
|
|
273
|
+
return chainId && valid && liveChainMarker(ev);
|
|
162
274
|
})
|
|
163
275
|
.map((ev) => String(ev.apply_worker_chain_id ?? ""))
|
|
164
276
|
.filter(Boolean));
|
|
@@ -166,10 +278,24 @@ export function apply_worker_chain_lifecycle_state(repoRoot, changeRoot, evidenc
|
|
|
166
278
|
.filter((ev) => {
|
|
167
279
|
const chainId = String(ev.apply_worker_chain_id ?? "");
|
|
168
280
|
const valid = String(ev.chain_state ?? "") === "closed" && (terminalValidity.get(ev) ?? closed_apply_worker_chain_valid(repoRoot, changeRoot, ev, evidences, taskId, chainId));
|
|
169
|
-
return chainId && valid;
|
|
281
|
+
return chainId && valid && liveChainMarker(ev);
|
|
170
282
|
})
|
|
171
283
|
.flatMap(chain_green_ids));
|
|
172
|
-
const
|
|
284
|
+
const validClosedAlternativeChainIds = new Set(chains
|
|
285
|
+
.filter((ev) => {
|
|
286
|
+
const chainId = String(ev.apply_worker_chain_id ?? "");
|
|
287
|
+
const valid = String(ev.chain_state ?? "") === "closed" && String(ev.completion_proof_kind ?? "green_tests") === "alternative_verification" && (terminalValidity.get(ev) ?? closed_apply_worker_chain_valid(repoRoot, changeRoot, ev, evidences, taskId, chainId));
|
|
288
|
+
return chainId && valid && liveChainMarker(ev);
|
|
289
|
+
})
|
|
290
|
+
.map((ev) => String(ev.apply_worker_chain_id ?? ""))
|
|
291
|
+
.filter(Boolean));
|
|
292
|
+
const validClosedAlternativeEvidenceIds = new Set(chains
|
|
293
|
+
.filter((ev) => {
|
|
294
|
+
const chainId = String(ev.apply_worker_chain_id ?? "");
|
|
295
|
+
const valid = String(ev.chain_state ?? "") === "closed" && String(ev.completion_proof_kind ?? "green_tests") === "alternative_verification" && (terminalValidity.get(ev) ?? closed_apply_worker_chain_valid(repoRoot, changeRoot, ev, evidences, taskId, chainId));
|
|
296
|
+
return chainId && valid && liveChainMarker(ev);
|
|
297
|
+
})
|
|
298
|
+
.flatMap(chain_alternative_ids));
|
|
173
299
|
const completionReasons = [];
|
|
174
300
|
const duplicateActives = [...activeCounts.entries()]
|
|
175
301
|
.filter(([chainId]) => openActiveChainIds.has(chainId))
|
|
@@ -206,6 +332,7 @@ export function apply_worker_chain_lifecycle_state(repoRoot, changeRoot, evidenc
|
|
|
206
332
|
if (duplicateTerminals.length > 0) {
|
|
207
333
|
completionReasons.push(reason("apply_worker_chain_terminal_conflict", `task ${taskId} has duplicate apply worker chain terminal markers: ${renderList(duplicateTerminals)}`, duplicateTerminals));
|
|
208
334
|
}
|
|
335
|
+
const takeoverbaselines = [];
|
|
209
336
|
const invalidTerminals = chains
|
|
210
337
|
.filter((ev) => String(ev.chain_state ?? "") === "closed" || String(ev.chain_state ?? "") === "abandoned")
|
|
211
338
|
.filter((ev) => {
|
|
@@ -214,11 +341,11 @@ export function apply_worker_chain_lifecycle_state(repoRoot, changeRoot, evidenc
|
|
|
214
341
|
return true;
|
|
215
342
|
if (String(ev.chain_state ?? "") === "closed")
|
|
216
343
|
return !(terminalValidity.get(ev) ?? closed_apply_worker_chain_valid(repoRoot, changeRoot, ev, evidences, taskId, chainId));
|
|
217
|
-
const valid = terminalValidity.get(ev) ?? abandoned_apply_worker_chain_valid(repoRoot, changeRoot, ev, activeByChain.get(chainId), taskId, chainId);
|
|
344
|
+
const valid = terminalValidity.get(ev) ?? abandoned_apply_worker_chain_valid(repoRoot, changeRoot, ev, activeByChain.get(chainId), evidences, taskId, chainId);
|
|
218
345
|
if (valid && isObject(ev.serial_takeover_baseline_ref)) {
|
|
219
346
|
const baseline = read_pinned_artifact_json(changeRoot, ev.serial_takeover_baseline_ref);
|
|
220
347
|
if (baseline?.implementation_fingerprint !== undefined) {
|
|
221
|
-
|
|
348
|
+
takeoverbaselines.push({
|
|
222
349
|
fingerprint: baseline.implementation_fingerprint,
|
|
223
350
|
successorGreenRefs: Array.isArray(ev.successor_green_evidence_refs) ? ev.successor_green_evidence_refs.map(String).filter(Boolean) : [],
|
|
224
351
|
});
|
|
@@ -250,6 +377,8 @@ export function apply_worker_chain_lifecycle_state(repoRoot, changeRoot, evidenc
|
|
|
250
377
|
if (duplicateTerminals.length > 0 || activeConflicts.length > 0) {
|
|
251
378
|
validClosedChainIds.clear();
|
|
252
379
|
validClosedGreenEvidenceIds.clear();
|
|
380
|
+
validClosedAlternativeChainIds.clear();
|
|
381
|
+
validClosedAlternativeEvidenceIds.clear();
|
|
253
382
|
}
|
|
254
383
|
const activeBlockingReasons = completionReasons.filter((item) => (item.code === "apply_worker_chain_active_conflict"
|
|
255
384
|
|| item.code === "apply_worker_chain_terminal_conflict"
|
|
@@ -270,7 +399,9 @@ export function apply_worker_chain_lifecycle_state(repoRoot, changeRoot, evidenc
|
|
|
270
399
|
validTerminalChainIds: chainIdsWithTerminal,
|
|
271
400
|
validClosedChainIds,
|
|
272
401
|
validClosedGreenEvidenceIds,
|
|
273
|
-
|
|
402
|
+
validClosedAlternativeEvidenceIds,
|
|
403
|
+
validClosedAlternativeChainIds,
|
|
404
|
+
takeoverbaselines,
|
|
274
405
|
};
|
|
275
406
|
}
|
|
276
407
|
export function apply_worker_chain_lifecycle_reasons(repoRoot, changeRoot, evidences, taskId) {
|
package/dist/src/cli_args.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ export type ParsedArgs = {
|
|
|
21
21
|
task_code_review_report_refs?: string[];
|
|
22
22
|
apply_worker_chain_refs?: string[];
|
|
23
23
|
green_test_run_evidence_refs?: string[];
|
|
24
|
+
alternative_verification_evidence_refs?: string[];
|
|
24
25
|
red_test_run_evidence_refs?: string[];
|
|
25
26
|
characterization_test_run_evidence_refs?: string[];
|
|
26
27
|
create?: boolean;
|
package/dist/src/cli_args.js
CHANGED
|
@@ -48,6 +48,13 @@ function isPacketCommand(command) {
|
|
|
48
48
|
|| command === "apply-verify-packet"
|
|
49
49
|
|| command === "ledger-render";
|
|
50
50
|
}
|
|
51
|
+
function isHookEventCommand(command) {
|
|
52
|
+
return command === "hook-check-write"
|
|
53
|
+
|| command === "hook-check-command"
|
|
54
|
+
|| command === "hook-record-test"
|
|
55
|
+
|| command === "hook-record-subagent-start"
|
|
56
|
+
|| command === "hook-record-subagent-stop";
|
|
57
|
+
}
|
|
51
58
|
function requiredValueFlags(command) {
|
|
52
59
|
const flags = ["--change"];
|
|
53
60
|
if (command === "check-artifact")
|
|
@@ -67,11 +74,10 @@ function requiredValueFlags(command) {
|
|
|
67
74
|
if (command === "apply-code-review-packet")
|
|
68
75
|
flags.push("--task-id", "--executor-report-ref");
|
|
69
76
|
if (command === "apply-verify-packet")
|
|
70
|
-
flags.push("--task-id", "--executor-report-ref", "--task-code-review-report-ref"
|
|
77
|
+
flags.push("--task-id", "--executor-report-ref", "--task-code-review-report-ref");
|
|
71
78
|
if (command === "ledger-render")
|
|
72
79
|
flags.push("--gate");
|
|
73
|
-
if (command
|
|
74
|
-
|| command === "hook-record-subagent-start" || command === "hook-record-subagent-stop")
|
|
80
|
+
if (isHookEventCommand(command))
|
|
75
81
|
flags.push("--event-ref");
|
|
76
82
|
if (command === "hook-session-begin")
|
|
77
83
|
flags.push("--workflow", "--entrypoint-token");
|
|
@@ -97,7 +103,7 @@ function optionalValueFlags(command) {
|
|
|
97
103
|
if (command === "apply-executor-packet")
|
|
98
104
|
return ["--apply-worker-chain-ref"];
|
|
99
105
|
if (command === "apply-verify-packet")
|
|
100
|
-
return ["--red-test-run-evidence-ref", "--characterization-test-run-evidence-ref"];
|
|
106
|
+
return ["--green-test-run-evidence-ref", "--alternative-verification-evidence-ref", "--red-test-run-evidence-ref", "--characterization-test-run-evidence-ref"];
|
|
101
107
|
if (command === "ledger-render")
|
|
102
108
|
return ["--round"];
|
|
103
109
|
if (command === "hook-session-end")
|
|
@@ -126,7 +132,7 @@ function requiresFormat(command) {
|
|
|
126
132
|
|| command === "apply-verify-packet";
|
|
127
133
|
}
|
|
128
134
|
function rootUsage() {
|
|
129
|
-
return `usage:
|
|
135
|
+
return `usage: superspec [-h]\n {${COMMAND_LIST}}\n ...\n`;
|
|
130
136
|
}
|
|
131
137
|
function rootHelp() {
|
|
132
138
|
const commandLines = COMMANDS.map((command) => {
|
|
@@ -144,7 +150,7 @@ function commandUsage(command) {
|
|
|
144
150
|
...requiredBooleanFlags(command),
|
|
145
151
|
...optionalBooleanFlags(command),
|
|
146
152
|
];
|
|
147
|
-
return `usage:
|
|
153
|
+
return `usage: superspec ${command} ${usageFlags.join(" ")}\n`;
|
|
148
154
|
}
|
|
149
155
|
function commandHelp(command) {
|
|
150
156
|
const zh = command_zh(command);
|
|
@@ -179,7 +185,7 @@ function missingRequiredFlags(command, args) {
|
|
|
179
185
|
}
|
|
180
186
|
export function emitArgparsePreamble(argv) {
|
|
181
187
|
if (argv.length === 0) {
|
|
182
|
-
process.stderr.write(`${rootUsage()}
|
|
188
|
+
process.stderr.write(`${rootUsage()}superspec:错误:缺少必填参数:command\n`);
|
|
183
189
|
return 2;
|
|
184
190
|
}
|
|
185
191
|
const command = argv[0];
|
|
@@ -188,7 +194,7 @@ export function emitArgparsePreamble(argv) {
|
|
|
188
194
|
return 0;
|
|
189
195
|
}
|
|
190
196
|
if (!COMMANDS.includes(command)) {
|
|
191
|
-
process.stderr.write(`${rootUsage()}
|
|
197
|
+
process.stderr.write(`${rootUsage()}superspec:错误:命令无效:'${command}';可选值:${COMMAND_CHOICES}\n`);
|
|
192
198
|
return 2;
|
|
193
199
|
}
|
|
194
200
|
const args = argv.slice(1);
|
|
@@ -199,7 +205,11 @@ export function emitArgparsePreamble(argv) {
|
|
|
199
205
|
if (!isPacketCommand(command)) {
|
|
200
206
|
const missing = missingRequiredFlags(command, args);
|
|
201
207
|
if (missing.length > 0) {
|
|
202
|
-
|
|
208
|
+
if (isHookEventCommand(command) && missing.length === 1 && missing[0] === "--event-ref") {
|
|
209
|
+
process.stderr.write(`${commandUsage(command)}superspec ${command}:错误:--event-ref must point to a readable hook event JSON file\n`);
|
|
210
|
+
return 2;
|
|
211
|
+
}
|
|
212
|
+
process.stderr.write(`${commandUsage(command)}superspec ${command}:错误:缺少必填参数:${missing.join(", ")}\n`);
|
|
203
213
|
return 2;
|
|
204
214
|
}
|
|
205
215
|
}
|
|
@@ -357,6 +367,7 @@ export function parse_argv(argv) {
|
|
|
357
367
|
const executorRefs = getValues("--executor-report-ref");
|
|
358
368
|
const codeReviewRefs = getValues("--task-code-review-report-ref");
|
|
359
369
|
const greenRefs = getValues("--green-test-run-evidence-ref");
|
|
370
|
+
const alternativeRefs = getValues("--alternative-verification-evidence-ref");
|
|
360
371
|
const redRefs = getValues("--red-test-run-evidence-ref");
|
|
361
372
|
const characterizationRefs = getValues("--characterization-test-run-evidence-ref");
|
|
362
373
|
if (!taskId)
|
|
@@ -365,11 +376,18 @@ export function parse_argv(argv) {
|
|
|
365
376
|
throw new GuardError("apply-verify-packet 缺少必填参数 --executor-report-ref");
|
|
366
377
|
if (codeReviewRefs.length === 0)
|
|
367
378
|
throw new GuardError("apply-verify-packet 缺少必填参数 --task-code-review-report-ref");
|
|
368
|
-
if (greenRefs.length === 0)
|
|
369
|
-
throw new GuardError("apply-verify-packet
|
|
370
|
-
|
|
379
|
+
if (greenRefs.length === 0 && alternativeRefs.length === 0) {
|
|
380
|
+
throw new GuardError("apply-verify-packet requires --green-test-run-evidence-ref or --alternative-verification-evidence-ref");
|
|
381
|
+
}
|
|
382
|
+
if (greenRefs.length > 0 && alternativeRefs.length > 0) {
|
|
383
|
+
throw new GuardError("apply-verify-packet requires either GREEN refs or alternative verification refs, not both");
|
|
384
|
+
}
|
|
385
|
+
if (greenRefs.length > 0 && redRefs.length === 0 && characterizationRefs.length === 0) {
|
|
371
386
|
throw new GuardError("apply-verify-packet requires --red-test-run-evidence-ref or --characterization-test-run-evidence-ref");
|
|
372
387
|
}
|
|
388
|
+
if (alternativeRefs.length > 0 && (redRefs.length > 0 || characterizationRefs.length > 0)) {
|
|
389
|
+
throw new GuardError("apply-verify-packet alternative verification branch must not include RED or characterization refs");
|
|
390
|
+
}
|
|
373
391
|
return {
|
|
374
392
|
command,
|
|
375
393
|
change,
|
|
@@ -377,6 +395,7 @@ export function parse_argv(argv) {
|
|
|
377
395
|
executor_report_refs: executorRefs,
|
|
378
396
|
task_code_review_report_refs: codeReviewRefs,
|
|
379
397
|
green_test_run_evidence_refs: greenRefs,
|
|
398
|
+
alternative_verification_evidence_refs: alternativeRefs,
|
|
380
399
|
red_test_run_evidence_refs: redRefs,
|
|
381
400
|
characterization_test_run_evidence_refs: characterizationRefs,
|
|
382
401
|
packet_format: parsePacketOutputFormat(selectedPacketFormat, { allowPrompt: true }),
|
|
@@ -394,11 +413,10 @@ export function parse_argv(argv) {
|
|
|
394
413
|
throw new GuardError("--round 必须是大于等于 1 的整数");
|
|
395
414
|
return { command, change, gate, round };
|
|
396
415
|
}
|
|
397
|
-
if (command
|
|
398
|
-
|| command === "hook-record-subagent-start" || command === "hook-record-subagent-stop") {
|
|
416
|
+
if (isHookEventCommand(command)) {
|
|
399
417
|
const eventRef = getValue("--event-ref");
|
|
400
418
|
if (!eventRef)
|
|
401
|
-
throw new
|
|
419
|
+
throw new GuardError("--event-ref must point to a readable hook event JSON file");
|
|
402
420
|
return { command, change, format, event_ref: eventRef };
|
|
403
421
|
}
|
|
404
422
|
if (command === "hook-session-begin") {
|
package/dist/src/disclosure.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
11
11
|
import { join, relative } from "node:path";
|
|
12
12
|
import { isObject, reason, renderList, repr, runtime, safe_within, toPosix, walkFiles } from "./util.js";
|
|
13
|
-
import { normalize_gate } from "./openspec.js";
|
|
13
|
+
import { effective_superseded_ids, normalize_gate } from "./openspec.js";
|
|
14
14
|
// Explicit target map (design §7); later phases extend this table gate by gate.
|
|
15
15
|
// Entries containing "*" are globs enumerated at check time; set equality (P1-6) means a spec
|
|
16
16
|
// file added after the digest makes the digest stale even though every pinned blob still matches.
|
|
@@ -462,7 +462,7 @@ export function review_disclosure_reasons(gate, changeRoot, evidences) {
|
|
|
462
462
|
return [reason("missing_review_digest", `${gate}: the disclosure loop is mandatory on this gate; need round-tagged role review evidence (review_round_id ${gate}-r1, findings[]) plus a main_review_digest`)];
|
|
463
463
|
}
|
|
464
464
|
const out = [];
|
|
465
|
-
const dead =
|
|
465
|
+
const dead = effective_superseded_ids(valid);
|
|
466
466
|
const byId = new Map();
|
|
467
467
|
for (const ev of valid) {
|
|
468
468
|
if (typeof ev.evidence_id === "string" && ev.evidence_id)
|
package/dist/src/evidence.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
2
|
import { join, relative } from "node:path";
|
|
3
|
-
import { CLAIM_ADJUDICATION_DECISIONS, EVIDENCE_KINDS, EVIDENCE_STATUSES, FINAL_VERIFICATION_ROLES, HUMAN_CONFIRMATION_GATES, FINAL_TEST_REQUIRED_FIELDS, FINDING_ADJUDICATION_DECISIONS, FORBIDDEN_FIELDS, MAIN_ADJUDICATION_DECISIONS, MAIN_ADJUDICATION_REQUIRED_FIELDS, REQUEST_CHANGES_ROUTES, ROLE_EVIDENCE_FIELDS, REVIEW_GUIDANCE_ROLES, SELF_REVIEW_MARKERS, SOURCE_GUIDANCE_REQUIRED_FIELDS, TASK_REOPEN_INVALIDITY_CLASSES, TASK_REOPEN_REQUIRED_FIELDS, TASK_REOPEN_RESOLVED_REQUIRED_FIELDS, VERIFY_EVIDENCE_REQUIRED_FIELDS, fingerprint_obj, isObject, reason, renderList, repr, pinned_ref_key, runtime, safe_within, sha256_file, toPosix, walkFiles, } from "./util.js";
|
|
4
|
-
import { normalize_gate } from "./openspec.js";
|
|
3
|
+
import { CLAIM_ADJUDICATION_DECISIONS, EVIDENCE_KINDS, EVIDENCE_STATUSES, FINAL_VERIFICATION_ROLES, HUMAN_CONFIRMATION_GATES, FINAL_TEST_REQUIRED_FIELDS, FINDING_ADJUDICATION_DECISIONS, FORBIDDEN_FIELDS, MAIN_ADJUDICATION_DECISIONS, MAIN_ADJUDICATION_REQUIRED_FIELDS, NO_TDD_REASONS, REQUEST_CHANGES_ROUTES, ROLE_EVIDENCE_FIELDS, REVIEW_GUIDANCE_ROLES, SELF_REVIEW_MARKERS, SOURCE_GUIDANCE_REQUIRED_FIELDS, TASK_REOPEN_INVALIDITY_CLASSES, TASK_REOPEN_REQUIRED_FIELDS, TASK_REOPEN_RESOLVED_REQUIRED_FIELDS, VERIFY_EVIDENCE_REQUIRED_FIELDS, confirmation_text_has_pending_wording, fingerprint_obj, isObject, reason, renderList, repr, pinned_ref_key, runtime, safe_within, sha256_file, toPosix, UNCHECKED_CHECKBOX_RE, walkFiles, } from "./util.js";
|
|
4
|
+
import { effective_superseded_ids, normalize_gate } from "./openspec.js";
|
|
5
5
|
import { superspec_dir } from "./paths.js";
|
|
6
6
|
import { findings_schema_reasons, review_digest_schema_reasons, standing_authorization_schema_reasons, user_decision_schema_reasons, } from "./disclosure.js";
|
|
7
7
|
import { pinned_artifact_ref_reasons as shared_pinned_artifact_ref_reasons, worker_test_run_reasons, } from "./apply_worker_chain.js";
|
|
@@ -143,6 +143,24 @@ function string_list_field_reasons(ev, field, code, opts = {}) {
|
|
|
143
143
|
}
|
|
144
144
|
return [];
|
|
145
145
|
}
|
|
146
|
+
const APPLY_WORKER_CHAIN_SURFACES = new Set(["implementation", "runtime_config"]);
|
|
147
|
+
function apply_worker_chain_ref_ids(ev, singleField, listField) {
|
|
148
|
+
const refs = new Set();
|
|
149
|
+
const single = ev[singleField];
|
|
150
|
+
if (isObject(single) && typeof single.evidence_id === "string" && single.evidence_id)
|
|
151
|
+
refs.add(single.evidence_id);
|
|
152
|
+
else if (typeof single === "string" && single)
|
|
153
|
+
refs.add(single);
|
|
154
|
+
if (Array.isArray(ev[listField])) {
|
|
155
|
+
for (const item of ev[listField]) {
|
|
156
|
+
if (isObject(item) && typeof item.evidence_id === "string" && item.evidence_id)
|
|
157
|
+
refs.add(item.evidence_id);
|
|
158
|
+
else if (typeof item === "string" && item)
|
|
159
|
+
refs.add(item);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return [...refs].sort();
|
|
163
|
+
}
|
|
146
164
|
function fingerprint_field_reasons(ev, field, code) {
|
|
147
165
|
const raw = ev[field];
|
|
148
166
|
if (typeof raw === "string") {
|
|
@@ -161,7 +179,37 @@ function pinned_artifact_ref_item_reasons(refItem, field, changeRoot, expected =
|
|
|
161
179
|
});
|
|
162
180
|
}
|
|
163
181
|
export function role_target_ref_reasons(ev, targetRoot) {
|
|
164
|
-
|
|
182
|
+
const problems = [];
|
|
183
|
+
const raw = ev.target_refs;
|
|
184
|
+
if (!Array.isArray(raw)) {
|
|
185
|
+
problems.push(reason("target_ref_invalid", `${ev._path}: target_refs must be a list of {path, blob_sha}`));
|
|
186
|
+
return problems;
|
|
187
|
+
}
|
|
188
|
+
for (const refItem of raw) {
|
|
189
|
+
if (!isObject(refItem)) {
|
|
190
|
+
problems.push(reason("target_ref_invalid", `${ev._path}: target_refs item must be object`));
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
const rel = refItem.path;
|
|
194
|
+
const expected = refItem.blob_sha;
|
|
195
|
+
if (typeof rel !== "string" || !rel || typeof expected !== "string" || !expected) {
|
|
196
|
+
problems.push(reason("target_ref_invalid", `${ev._path}: target_refs requires path and blob_sha`));
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
const target = safe_within(targetRoot, rel);
|
|
200
|
+
if (target === null) {
|
|
201
|
+
problems.push(reason("evidence_unsafe_ref", `${ev._path}: target_refs escapes allowed root: ${rel}`));
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
if (!existsSync(target) || !statSync(target).isFile()) {
|
|
205
|
+
problems.push(reason("stale_review", `${ev._path}: target_refs is not readable: ${rel}`));
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
const actual = runtime.file_blob_sha(target);
|
|
209
|
+
if (actual !== expected)
|
|
210
|
+
problems.push(reason("stale_review", `${ev._path}: target_refs stale for ${rel}`));
|
|
211
|
+
}
|
|
212
|
+
return problems;
|
|
165
213
|
}
|
|
166
214
|
export function loaded_ref_reasons(ev, repoRoot) {
|
|
167
215
|
return pinned_ref_reasons(ev, "loaded_refs", repoRoot, "stale_loaded_ref");
|
|
@@ -358,6 +406,16 @@ function human_confirmation_reasons(ev) {
|
|
|
358
406
|
&& (typeof ev.tasks_structure_hash !== "string" || !ev.tasks_structure_hash.trim())) {
|
|
359
407
|
problems.push(reason("human_confirmation_invalid", `${ev._path}: ${gate} human_confirmation requires tasks_structure_hash pinning the approved tasks.md structure`));
|
|
360
408
|
}
|
|
409
|
+
// B3: a pass confirmation must not carry unresolved signals. Strong signal: an unchecked checkbox
|
|
410
|
+
// "[ ]" pasted into confirmation_text (the agent copied an open question list into the confirmation).
|
|
411
|
+
// Weak signal: confirmation-pending wording shared with the explore gate (仍需确认 / 待确认 / ...),
|
|
412
|
+
// scoped to confirmation semantics so legitimate "细节仍需在 propose 细化" prose is not flagged.
|
|
413
|
+
// Real incident: secondment-hour-unit-support sealed status:"pass" while its confirmation_text said
|
|
414
|
+
// "还需要评估..." — the gate's presence-check then let propose through with 8 open questions.
|
|
415
|
+
const ctext = typeof ev.confirmation_text === "string" ? ev.confirmation_text : "";
|
|
416
|
+
if (UNCHECKED_CHECKBOX_RE.test(ctext) || confirmation_text_has_pending_wording(ctext)) {
|
|
417
|
+
problems.push(reason("human_confirmation_invalid", `${ev._path}: confirmation_text carries unresolved signals (unchecked checkbox or 「仍需确认/待确认」wording); do not record pass while open items remain`));
|
|
418
|
+
}
|
|
361
419
|
return problems;
|
|
362
420
|
}
|
|
363
421
|
// FIX-10 (audit C-5/H-4): test_run evidence is archived per run, not as a bare self-report.
|
|
@@ -455,7 +513,28 @@ function apply_worker_chain_reasons(ev, changeRoot) {
|
|
|
455
513
|
problems.push(...fingerprint_field_reasons(ev, "executor_packet_fingerprint", "apply_worker_chain_invalid"));
|
|
456
514
|
problems.push(...fingerprint_field_reasons(ev, "source_implementation_fingerprint", "apply_worker_chain_invalid"));
|
|
457
515
|
problems.push(...string_list_field_reasons(ev, "declared_task_write_scope", "apply_worker_chain_invalid"));
|
|
458
|
-
|
|
516
|
+
const proofKind = String(ev.pre_edit_proof_kind ?? "red_or_characterization");
|
|
517
|
+
if (proofKind !== "red_or_characterization" && proofKind !== "no_tdd_declared") {
|
|
518
|
+
problems.push(reason("apply_worker_chain_invalid", `${ev._path}: active apply_worker_chain pre_edit_proof_kind must be red_or_characterization or no_tdd_declared`));
|
|
519
|
+
}
|
|
520
|
+
else if (proofKind === "no_tdd_declared") {
|
|
521
|
+
problems.push(...string_list_field_reasons(ev, "pre_edit_evidence_refs", "apply_worker_chain_invalid", { allowEmpty: true }));
|
|
522
|
+
if (Array.isArray(ev.pre_edit_evidence_refs) && ev.pre_edit_evidence_refs.length > 0) {
|
|
523
|
+
problems.push(reason("apply_worker_chain_invalid", `${ev._path}: no_tdd_declared active apply_worker_chain requires empty pre_edit_evidence_refs`));
|
|
524
|
+
}
|
|
525
|
+
if (ev.tdd_required !== false) {
|
|
526
|
+
problems.push(reason("apply_worker_chain_invalid", `${ev._path}: no_tdd_declared active apply_worker_chain requires tdd_required=false`));
|
|
527
|
+
}
|
|
528
|
+
if (!APPLY_WORKER_CHAIN_SURFACES.has(String(ev.apply_execution_surface ?? ""))) {
|
|
529
|
+
problems.push(reason("apply_worker_chain_invalid", `${ev._path}: no_tdd_declared active apply_worker_chain requires apply_execution_surface implementation or runtime_config`));
|
|
530
|
+
}
|
|
531
|
+
if (!NO_TDD_REASONS.has(String(ev.no_tdd_reason ?? ""))) {
|
|
532
|
+
problems.push(reason("apply_worker_chain_invalid", `${ev._path}: no_tdd_declared active apply_worker_chain requires valid no_tdd_reason`));
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
else {
|
|
536
|
+
problems.push(...string_list_field_reasons(ev, "pre_edit_evidence_refs", "apply_worker_chain_invalid"));
|
|
537
|
+
}
|
|
459
538
|
}
|
|
460
539
|
if (state === "closed") {
|
|
461
540
|
const taskId = typeof ev.task_id === "string" ? ev.task_id : undefined;
|
|
@@ -468,8 +547,17 @@ function apply_worker_chain_reasons(ev, changeRoot) {
|
|
|
468
547
|
problems.push(...refProblems);
|
|
469
548
|
if (refProblems.length > 0)
|
|
470
549
|
problems.push(reason("apply_worker_chain_invalid", `${ev._path}: closed apply_worker_chain report refs must be pinned same-chain worker reports`));
|
|
471
|
-
|
|
472
|
-
|
|
550
|
+
const proofKind = String(ev.completion_proof_kind ?? "green_tests");
|
|
551
|
+
if (proofKind !== "green_tests" && proofKind !== "alternative_verification") {
|
|
552
|
+
problems.push(reason("apply_worker_chain_invalid", `${ev._path}: closed apply_worker_chain completion_proof_kind must be green_tests or alternative_verification`));
|
|
553
|
+
}
|
|
554
|
+
else if (proofKind === "green_tests") {
|
|
555
|
+
if (apply_worker_chain_ref_ids(ev, "green_test_run_evidence_ref", "green_test_run_evidence_refs").length === 0) {
|
|
556
|
+
problems.push(reason("apply_worker_chain_invalid", `${ev._path}: green_tests closed apply_worker_chain requires green_test_run_evidence_ref(s)`));
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
else if (apply_worker_chain_ref_ids(ev, "alternative_verification_evidence_ref", "alternative_verification_evidence_refs").length === 0) {
|
|
560
|
+
problems.push(reason("apply_worker_chain_invalid", `${ev._path}: alternative_verification closed apply_worker_chain requires alternative_verification_evidence_ref(s)`));
|
|
473
561
|
}
|
|
474
562
|
problems.push(...fingerprint_field_reasons(ev, "observed_freshness_fingerprint", "apply_worker_chain_invalid"));
|
|
475
563
|
}
|
|
@@ -479,6 +567,9 @@ function apply_worker_chain_reasons(ev, changeRoot) {
|
|
|
479
567
|
if ("restored_implementation_fingerprint" in ev) {
|
|
480
568
|
problems.push(...fingerprint_field_reasons(ev, "restored_implementation_fingerprint", "apply_worker_chain_invalid"));
|
|
481
569
|
}
|
|
570
|
+
if ("restored_implementation_fingerprint" in ev && "serial_takeover_baseline_ref" in ev) {
|
|
571
|
+
problems.push(reason("apply_worker_chain_invalid", `${ev._path}: abandoned apply_worker_chain restored_implementation_fingerprint and serial_takeover_baseline_ref are mutually exclusive`));
|
|
572
|
+
}
|
|
482
573
|
if (!("restored_implementation_fingerprint" in ev) && !("serial_takeover_baseline_ref" in ev)) {
|
|
483
574
|
problems.push(reason("apply_worker_chain_invalid", `${ev._path}: abandoned apply_worker_chain requires restored_implementation_fingerprint or serial_takeover_baseline_ref`));
|
|
484
575
|
}
|
|
@@ -487,7 +578,12 @@ function apply_worker_chain_reasons(ev, changeRoot) {
|
|
|
487
578
|
problems.push(...baselineProblems);
|
|
488
579
|
if (baselineProblems.length > 0)
|
|
489
580
|
problems.push(reason("apply_worker_chain_invalid", `${ev._path}: abandoned apply_worker_chain serial_takeover_baseline_ref must be a pinned same-chain status_report`));
|
|
490
|
-
|
|
581
|
+
if (typeof ev.takeover_confirmation_evidence_id !== "string" || !ev.takeover_confirmation_evidence_id) {
|
|
582
|
+
problems.push(reason("apply_worker_chain_invalid", `${ev._path}: abandoned apply_worker_chain serial takeover requires takeover_confirmation_evidence_id`));
|
|
583
|
+
}
|
|
584
|
+
if ("successor_green_evidence_refs" in ev) {
|
|
585
|
+
problems.push(...string_list_field_reasons(ev, "successor_green_evidence_refs", "apply_worker_chain_invalid"));
|
|
586
|
+
}
|
|
491
587
|
}
|
|
492
588
|
}
|
|
493
589
|
return problems;
|
|
@@ -915,7 +1011,7 @@ export function find_pass(evidences, filters = {}) {
|
|
|
915
1011
|
});
|
|
916
1012
|
}
|
|
917
1013
|
export function superseded_ids(evidences) {
|
|
918
|
-
return
|
|
1014
|
+
return effective_superseded_ids(evidences);
|
|
919
1015
|
}
|
|
920
1016
|
// FIX-6 (audit C-2): supersede is a rollback mechanism, so it needs an authorization model.
|
|
921
1017
|
// A supersede evidence must point at an existing evidence_id, and a cross-gate supersede
|
package/dist/src/gates.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export declare function superspec_agent_reasons(repoRoot: string): Reason[];
|
|
|
5
5
|
export declare function openspec_cli_capability_reasons(): Reason[];
|
|
6
6
|
export declare function evidence_schema_guard(change: string, changeRoot: string, repoRoot: string, evidences: JsonMap[]): Reason[];
|
|
7
7
|
export declare function check_init(change: string, status: JsonMap, repoRoot: string, changeRoot: string): Decision;
|
|
8
|
+
export declare function discovery_open_question_count(discoveryPath: string): number;
|
|
8
9
|
export declare function check_superspec_gate(change: string, status: JsonMap, changeRoot: string, evidences: JsonMap[], gateRaw: string): Decision;
|
|
9
10
|
export declare function check_artifact(change: string, status: JsonMap, changeRoot: string, evidences: JsonMap[], artifact: string): Decision;
|
|
10
11
|
export declare function check_task_reopen(change: string, status: JsonMap, changeRoot: string, evidences: JsonMap[], taskId: string): Decision;
|