@peterxiaoyang/superspec 0.1.6 → 0.1.7
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.
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { JsonMap, Reason } from "./util.ts";
|
|
2
|
+
export type ApplyWorkerChainLifecycleState = {
|
|
3
|
+
completionReasons: Reason[];
|
|
4
|
+
packetBlockers: Reason[];
|
|
5
|
+
activeBlockingReasons: Reason[];
|
|
6
|
+
activePresence: "none" | "single_open" | "blocked";
|
|
7
|
+
active: JsonMap | null;
|
|
8
|
+
openActiveChainIds: Set<string>;
|
|
9
|
+
validTerminalChainIds: Set<string>;
|
|
10
|
+
validClosedChainIds: Set<string>;
|
|
11
|
+
validClosedGreenEvidenceIds: Set<string>;
|
|
12
|
+
takeoverBaselines: {
|
|
13
|
+
fingerprint: unknown;
|
|
14
|
+
successorGreenRefs: string[];
|
|
15
|
+
}[];
|
|
16
|
+
};
|
|
17
|
+
export declare function serial_completion_green(ev: JsonMap): boolean;
|
|
18
|
+
export declare function apply_worker_chain_lifecycle_state(repoRoot: string, changeRoot: string, evidences: JsonMap[], taskId: string): ApplyWorkerChainLifecycleState;
|
|
19
|
+
export declare function apply_worker_chain_lifecycle_reasons(repoRoot: string, changeRoot: string, evidences: JsonMap[], taskId: string): Reason[];
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { isObject, reason, renderList, repr } from "./util.js";
|
|
2
|
+
import { find_pass, live_pass } from "./evidence.js";
|
|
3
|
+
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
|
+
function pinned_artifact_ref_matches(changeRoot, refItem, role, taskId, chainId, kind = "worker_report") {
|
|
5
|
+
return shared_pinned_artifact_ref_reasons(changeRoot, refItem, `${role}_ref`, { kind, role, taskId, chainId }).length === 0;
|
|
6
|
+
}
|
|
7
|
+
function executor_worker_test_run_refs_valid(changeRoot, ev, taskId, chainId) {
|
|
8
|
+
return worker_test_run_reasons(changeRoot, ev, taskId, chainId).length === 0;
|
|
9
|
+
}
|
|
10
|
+
function report_origin_matches(refItem, expected) {
|
|
11
|
+
return isObject(refItem) && typeof expected === "string" && refItem.origin_packet_fingerprint === expected;
|
|
12
|
+
}
|
|
13
|
+
function report_input_refs_match(refItem, refs) {
|
|
14
|
+
return isObject(refItem) && refItem.input_ref_digest === worker_input_ref_digest(refs);
|
|
15
|
+
}
|
|
16
|
+
function report_input_digest_matches(refItem, expectedDigest) {
|
|
17
|
+
return isObject(refItem) && refItem.input_ref_digest === expectedDigest;
|
|
18
|
+
}
|
|
19
|
+
function chain_green_ids(ev) {
|
|
20
|
+
if (Array.isArray(ev.green_test_run_evidence_refs)) {
|
|
21
|
+
return ev.green_test_run_evidence_refs.map(String).filter(Boolean).sort();
|
|
22
|
+
}
|
|
23
|
+
const greenId = isObject(ev.green_test_run_evidence_ref)
|
|
24
|
+
? String(ev.green_test_run_evidence_ref.evidence_id ?? "")
|
|
25
|
+
: String(ev.green_test_run_evidence_ref ?? "");
|
|
26
|
+
return greenId ? [greenId] : [];
|
|
27
|
+
}
|
|
28
|
+
function active_pre_edit_input_refs(evidences, active) {
|
|
29
|
+
const preIds = Array.isArray(active.pre_edit_evidence_refs) ? active.pre_edit_evidence_refs.map(String) : [];
|
|
30
|
+
return preIds.map((evidenceId) => {
|
|
31
|
+
const ev = live_pass(evidences, { kind: "test_run" }).find((item) => String(item.evidence_id ?? "") === evidenceId);
|
|
32
|
+
return ev
|
|
33
|
+
? { kind: "test_run", evidence_id: evidenceId, phase: ev.phase, semantic_status: ev.semantic_status }
|
|
34
|
+
: { kind: "test_run", evidence_id: evidenceId };
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function closed_apply_worker_chain_valid(repoRoot, changeRoot, ev, evidences, taskId, chainId) {
|
|
38
|
+
const active = live_pass(evidences, { gate: "task_complete", kind: "apply_worker_chain", task_id: taskId })
|
|
39
|
+
.find((item) => item.chain_state === "active" && item.apply_worker_chain_id === chainId);
|
|
40
|
+
if (!active)
|
|
41
|
+
return false;
|
|
42
|
+
if (pre_edit_evidence_ref_reasons(evidences, active, taskId).length > 0)
|
|
43
|
+
return false;
|
|
44
|
+
if (!pinned_artifact_ref_matches(changeRoot, ev.executor_report_ref, "executor", taskId, chainId))
|
|
45
|
+
return false;
|
|
46
|
+
if (!pinned_artifact_ref_matches(changeRoot, ev.task_code_review_report_ref, "code-reviewer", taskId, chainId))
|
|
47
|
+
return false;
|
|
48
|
+
if (!pinned_artifact_ref_matches(changeRoot, ev.verifier_report_ref, "verifier", taskId, chainId))
|
|
49
|
+
return false;
|
|
50
|
+
if (!report_origin_matches(ev.executor_report_ref, active.executor_packet_fingerprint))
|
|
51
|
+
return false;
|
|
52
|
+
if (!report_input_digest_matches(ev.executor_report_ref, apply_worker_executor_input_ref_digest(evidences, active)))
|
|
53
|
+
return false;
|
|
54
|
+
if (!report_input_refs_match(ev.task_code_review_report_ref, [ev.executor_report_ref]))
|
|
55
|
+
return false;
|
|
56
|
+
if (typeof ev.observed_freshness_fingerprint !== "string" && !(isObject(ev.observed_freshness_fingerprint) && typeof ev.observed_freshness_fingerprint.fingerprint_digest === "string"))
|
|
57
|
+
return false;
|
|
58
|
+
const verifierReport = read_pinned_artifact_json(changeRoot, ev.verifier_report_ref);
|
|
59
|
+
const verifierObserved = verifierReport?.observed_freshness_fingerprint ?? (isObject(ev.verifier_report_ref) ? ev.verifier_report_ref.observed_freshness_fingerprint : undefined);
|
|
60
|
+
if (!fingerprint_matches(verifierObserved, ev.observed_freshness_fingerprint))
|
|
61
|
+
return false;
|
|
62
|
+
const greenIds = chain_green_ids(ev);
|
|
63
|
+
if (greenIds.length === 0)
|
|
64
|
+
return false;
|
|
65
|
+
const greenRuns = greenIds.map((greenId) => live_pass(evidences, { gate: "task_complete", kind: "test_run", task_id: taskId }).find((item) => (String(item.evidence_id ?? "") === greenId
|
|
66
|
+
&& item.semantic_status === "expected_success"
|
|
67
|
+
&& (item.phase === undefined || item.phase === "green")
|
|
68
|
+
&& item.apply_execution_chain === "executor_worker"
|
|
69
|
+
&& item.apply_worker_chain_id === chainId
|
|
70
|
+
&& executor_worker_test_run_refs_valid(changeRoot, item, taskId, chainId)))).filter((item) => isObject(item));
|
|
71
|
+
if (greenRuns.length !== greenIds.length)
|
|
72
|
+
return false;
|
|
73
|
+
if (!report_input_refs_match(ev.verifier_report_ref, [
|
|
74
|
+
ev.executor_report_ref,
|
|
75
|
+
ev.task_code_review_report_ref,
|
|
76
|
+
...greenRuns.map((green) => ({ kind: "test_run", evidence_id: String(green.evidence_id ?? ""), phase: green.phase, semantic_status: green.semantic_status })),
|
|
77
|
+
...active_pre_edit_input_refs(evidences, active),
|
|
78
|
+
]))
|
|
79
|
+
return false;
|
|
80
|
+
const currentFreshness = compute_apply_worker_freshness(repoRoot, changeRoot, evidences, taskId, chainId, {
|
|
81
|
+
executor_report_ref: ev.executor_report_ref,
|
|
82
|
+
task_code_review_report_ref: ev.task_code_review_report_ref,
|
|
83
|
+
green_test_run_evidence_ref: greenIds[0],
|
|
84
|
+
green_test_run_evidence_refs: greenIds,
|
|
85
|
+
verifier_report_ref: ev.verifier_report_ref,
|
|
86
|
+
});
|
|
87
|
+
if (Array.isArray(currentFreshness.unexpected_guard_owned_dirty_paths) && currentFreshness.unexpected_guard_owned_dirty_paths.length > 0)
|
|
88
|
+
return false;
|
|
89
|
+
if (Array.isArray(currentFreshness.protected_dirty_paths) && currentFreshness.protected_dirty_paths.length > 0)
|
|
90
|
+
return false;
|
|
91
|
+
if (!greenRuns.every((green) => fingerprint_matches(green.implementation_fingerprint, currentFreshness.implementation_fingerprint)))
|
|
92
|
+
return false;
|
|
93
|
+
if (!fingerprint_matches(isObject(ev.task_code_review_report_ref) ? ev.task_code_review_report_ref.observed_implementation_fingerprint : undefined, currentFreshness.implementation_fingerprint))
|
|
94
|
+
return false;
|
|
95
|
+
return fingerprint_matches(currentFreshness, verifierObserved);
|
|
96
|
+
}
|
|
97
|
+
function pinned_takeover_baseline_valid(changeRoot, refItem, taskId, chainId) {
|
|
98
|
+
return shared_pinned_artifact_ref_reasons(changeRoot, refItem, "serial_takeover_baseline_ref", { kind: "status_report", role: "verifier", taskId, chainId }).length === 0;
|
|
99
|
+
}
|
|
100
|
+
function abandoned_apply_worker_chain_valid(repoRoot, changeRoot, ev, active, taskId, chainId) {
|
|
101
|
+
if (!active)
|
|
102
|
+
return false;
|
|
103
|
+
if ("restored_implementation_fingerprint" in ev) {
|
|
104
|
+
const activeScope = Array.isArray(active.declared_task_write_scope) ? active.declared_task_write_scope.map(String).filter(Boolean) : [];
|
|
105
|
+
const current = apply_worker_implementation_fingerprint(repoRoot, changeRoot, [], { declaredTaskWriteScope: activeScope });
|
|
106
|
+
if (fingerprint_matches(ev.restored_implementation_fingerprint, active.source_implementation_fingerprint)
|
|
107
|
+
&& fingerprint_matches(current, active.source_implementation_fingerprint))
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
if ("serial_takeover_baseline_ref" in ev) {
|
|
111
|
+
return pinned_takeover_baseline_valid(changeRoot, ev.serial_takeover_baseline_ref, taskId, chainId)
|
|
112
|
+
&& Array.isArray(ev.successor_green_evidence_refs)
|
|
113
|
+
&& ev.successor_green_evidence_refs.length > 0
|
|
114
|
+
&& ev.successor_green_evidence_refs.every((item) => typeof item === "string" && item.length > 0);
|
|
115
|
+
}
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
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
|
+
export function apply_worker_chain_lifecycle_state(repoRoot, changeRoot, evidences, taskId) {
|
|
124
|
+
const chains = find_pass(evidences, { gate: "task_complete", kind: "apply_worker_chain", task_id: taskId });
|
|
125
|
+
const activeByChain = new Map();
|
|
126
|
+
const activeCounts = new Map();
|
|
127
|
+
for (const ev of chains) {
|
|
128
|
+
if (String(ev.chain_state ?? "") !== "active")
|
|
129
|
+
continue;
|
|
130
|
+
const chainId = String(ev.apply_worker_chain_id ?? "");
|
|
131
|
+
if (!chainId)
|
|
132
|
+
continue;
|
|
133
|
+
const bucket = activeCounts.get(chainId) ?? [];
|
|
134
|
+
bucket.push(ev);
|
|
135
|
+
activeCounts.set(chainId, bucket);
|
|
136
|
+
if (!activeByChain.has(chainId))
|
|
137
|
+
activeByChain.set(chainId, ev);
|
|
138
|
+
}
|
|
139
|
+
const chainIdsWithActive = new Set([...activeByChain.keys()]);
|
|
140
|
+
const terminalValidity = new Map();
|
|
141
|
+
const chainIdsWithTerminal = new Set(chains
|
|
142
|
+
.filter((ev) => {
|
|
143
|
+
const chainId = String(ev.apply_worker_chain_id ?? "");
|
|
144
|
+
if (!chainId)
|
|
145
|
+
return false;
|
|
146
|
+
let valid = false;
|
|
147
|
+
if (String(ev.chain_state ?? "") === "closed")
|
|
148
|
+
valid = closed_apply_worker_chain_valid(repoRoot, changeRoot, ev, evidences, taskId, chainId);
|
|
149
|
+
if (String(ev.chain_state ?? "") === "abandoned")
|
|
150
|
+
valid = abandoned_apply_worker_chain_valid(repoRoot, changeRoot, ev, activeByChain.get(chainId), taskId, chainId);
|
|
151
|
+
terminalValidity.set(ev, valid);
|
|
152
|
+
return valid;
|
|
153
|
+
})
|
|
154
|
+
.map((ev) => String(ev.apply_worker_chain_id ?? ""))
|
|
155
|
+
.filter(Boolean));
|
|
156
|
+
const openActiveChainIds = new Set([...activeByChain.keys()].filter((chainId) => !chainIdsWithTerminal.has(chainId)));
|
|
157
|
+
const validClosedChainIds = new Set(chains
|
|
158
|
+
.filter((ev) => {
|
|
159
|
+
const chainId = String(ev.apply_worker_chain_id ?? "");
|
|
160
|
+
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;
|
|
162
|
+
})
|
|
163
|
+
.map((ev) => String(ev.apply_worker_chain_id ?? ""))
|
|
164
|
+
.filter(Boolean));
|
|
165
|
+
const validClosedGreenEvidenceIds = new Set(chains
|
|
166
|
+
.filter((ev) => {
|
|
167
|
+
const chainId = String(ev.apply_worker_chain_id ?? "");
|
|
168
|
+
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;
|
|
170
|
+
})
|
|
171
|
+
.flatMap(chain_green_ids));
|
|
172
|
+
const takeoverBaselines = [];
|
|
173
|
+
const completionReasons = [];
|
|
174
|
+
const duplicateActives = [...activeCounts.entries()]
|
|
175
|
+
.filter(([chainId]) => openActiveChainIds.has(chainId))
|
|
176
|
+
.map(([, items]) => items)
|
|
177
|
+
.filter((items) => items.length > 1)
|
|
178
|
+
.flatMap((items) => items.map((ev) => String(ev.evidence_id ?? ev.apply_worker_chain_id ?? "")))
|
|
179
|
+
.filter(Boolean)
|
|
180
|
+
.sort();
|
|
181
|
+
const openActiveValues = [...openActiveChainIds].map((chainId) => activeByChain.get(chainId)).filter((ev) => isObject(ev));
|
|
182
|
+
const parallelActives = openActiveValues.length > 1
|
|
183
|
+
? openActiveValues.map((ev) => String(ev.evidence_id ?? ev.apply_worker_chain_id ?? "")).filter(Boolean).sort()
|
|
184
|
+
: [];
|
|
185
|
+
const activeConflicts = [...new Set([...duplicateActives, ...parallelActives])].sort();
|
|
186
|
+
if (activeConflicts.length > 0) {
|
|
187
|
+
completionReasons.push(reason("apply_worker_chain_active_conflict", `task ${taskId} has conflicting active apply worker chain markers: ${renderList(activeConflicts)}`, activeConflicts));
|
|
188
|
+
}
|
|
189
|
+
const terminalsByChain = new Map();
|
|
190
|
+
for (const ev of chains) {
|
|
191
|
+
const state = String(ev.chain_state ?? "");
|
|
192
|
+
if (state !== "closed" && state !== "abandoned")
|
|
193
|
+
continue;
|
|
194
|
+
const chainId = String(ev.apply_worker_chain_id ?? "");
|
|
195
|
+
if (!chainId)
|
|
196
|
+
continue;
|
|
197
|
+
const bucket = terminalsByChain.get(chainId) ?? [];
|
|
198
|
+
bucket.push(ev);
|
|
199
|
+
terminalsByChain.set(chainId, bucket);
|
|
200
|
+
}
|
|
201
|
+
const duplicateTerminals = [...terminalsByChain.values()]
|
|
202
|
+
.filter((items) => items.length > 1)
|
|
203
|
+
.flatMap((items) => items.map((ev) => String(ev.evidence_id ?? ev.apply_worker_chain_id ?? "")))
|
|
204
|
+
.filter(Boolean)
|
|
205
|
+
.sort();
|
|
206
|
+
if (duplicateTerminals.length > 0) {
|
|
207
|
+
completionReasons.push(reason("apply_worker_chain_terminal_conflict", `task ${taskId} has duplicate apply worker chain terminal markers: ${renderList(duplicateTerminals)}`, duplicateTerminals));
|
|
208
|
+
}
|
|
209
|
+
const invalidTerminals = chains
|
|
210
|
+
.filter((ev) => String(ev.chain_state ?? "") === "closed" || String(ev.chain_state ?? "") === "abandoned")
|
|
211
|
+
.filter((ev) => {
|
|
212
|
+
const chainId = String(ev.apply_worker_chain_id ?? "");
|
|
213
|
+
if (!chainId)
|
|
214
|
+
return true;
|
|
215
|
+
if (String(ev.chain_state ?? "") === "closed")
|
|
216
|
+
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);
|
|
218
|
+
if (valid && isObject(ev.serial_takeover_baseline_ref)) {
|
|
219
|
+
const baseline = read_pinned_artifact_json(changeRoot, ev.serial_takeover_baseline_ref);
|
|
220
|
+
if (baseline?.implementation_fingerprint !== undefined) {
|
|
221
|
+
takeoverBaselines.push({
|
|
222
|
+
fingerprint: baseline.implementation_fingerprint,
|
|
223
|
+
successorGreenRefs: Array.isArray(ev.successor_green_evidence_refs) ? ev.successor_green_evidence_refs.map(String).filter(Boolean) : [],
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return !valid;
|
|
228
|
+
})
|
|
229
|
+
.map((ev) => String(ev.evidence_id ?? ev.apply_worker_chain_id ?? ""))
|
|
230
|
+
.filter(Boolean)
|
|
231
|
+
.sort();
|
|
232
|
+
if (invalidTerminals.length > 0) {
|
|
233
|
+
completionReasons.push(reason("apply_worker_chain_terminal_invalid", `task ${taskId} has invalid apply worker chain terminal markers: ${renderList(invalidTerminals)}`, invalidTerminals));
|
|
234
|
+
}
|
|
235
|
+
for (const chainId of [...openActiveChainIds].sort()) {
|
|
236
|
+
if (!chainIdsWithTerminal.has(chainId)) {
|
|
237
|
+
completionReasons.push(reason("apply_worker_chain_active", `task ${taskId} has active apply worker chain and cannot use non-chain task completion: ${chainId}`, [chainId]));
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
for (const ev of evidences) {
|
|
241
|
+
if (!isObject(ev) || ev._invalid || ev.status !== "pass" || ev.task_id !== taskId)
|
|
242
|
+
continue;
|
|
243
|
+
const chainId = typeof ev.apply_worker_chain_id === "string" ? ev.apply_worker_chain_id : "";
|
|
244
|
+
if (!chainId)
|
|
245
|
+
continue;
|
|
246
|
+
if (!chainIdsWithActive.has(chainId)) {
|
|
247
|
+
completionReasons.push(reason("apply_worker_chain_missing_active", `task ${taskId} references apply_worker_chain_id=${repr(chainId)} without a matching active marker`, [chainId]));
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (duplicateTerminals.length > 0 || activeConflicts.length > 0) {
|
|
251
|
+
validClosedChainIds.clear();
|
|
252
|
+
validClosedGreenEvidenceIds.clear();
|
|
253
|
+
}
|
|
254
|
+
const activeBlockingReasons = completionReasons.filter((item) => (item.code === "apply_worker_chain_active_conflict"
|
|
255
|
+
|| item.code === "apply_worker_chain_terminal_conflict"
|
|
256
|
+
|| item.code === "apply_worker_chain_terminal_invalid"
|
|
257
|
+
|| item.code === "apply_worker_chain_missing_active"));
|
|
258
|
+
const activePresence = activeBlockingReasons.length > 0
|
|
259
|
+
? "blocked"
|
|
260
|
+
: openActiveValues.length === 1
|
|
261
|
+
? "single_open"
|
|
262
|
+
: "none";
|
|
263
|
+
return {
|
|
264
|
+
completionReasons,
|
|
265
|
+
packetBlockers: activeBlockingReasons,
|
|
266
|
+
activeBlockingReasons,
|
|
267
|
+
activePresence,
|
|
268
|
+
active: activePresence === "single_open" ? openActiveValues[0] : null,
|
|
269
|
+
openActiveChainIds,
|
|
270
|
+
validTerminalChainIds: chainIdsWithTerminal,
|
|
271
|
+
validClosedChainIds,
|
|
272
|
+
validClosedGreenEvidenceIds,
|
|
273
|
+
takeoverBaselines,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
export function apply_worker_chain_lifecycle_reasons(repoRoot, changeRoot, evidences, taskId) {
|
|
277
|
+
return apply_worker_chain_lifecycle_state(repoRoot, changeRoot, evidences, taskId).completionReasons;
|
|
278
|
+
}
|
package/dist/src/gates.js
CHANGED
|
@@ -5,10 +5,11 @@ import { all_done, artifact_status_map, get_repo_root, is_done, normalize_gate,
|
|
|
5
5
|
import { read_agent_toml_name, read_skill_frontmatter_name, sidecar_business_invariants_path, sidecar_discovery_path, sidecar_test_contract_path } from "./paths.js";
|
|
6
6
|
import { business_invariant_ids, business_invariant_validation_reasons, automated_hard_business_invariant_ids, evidence_invariant_refs, evidence_invariant_ref_reasons, evidence_test_contract_invariant_reasons, human_confirmation_business_invariant_ids, invariant_matrix_coverage_reasons, post_implementation_business_invariant_ids, red_green_invariant_ids, test_contract_invariant_ids, } from "./invariants.js";
|
|
7
7
|
import { evidence_test_id_reasons, declared_test_evidence_reasons, parse_spec_scenarios, parse_tasks, parse_test_contract_ids, parse_test_contract_records, red_green_test_ids, splitList, tasks_structure_hash, task_alternative_verification, task_test_evidence, task_test_refs, test_contract_covers_scenario, test_contract_invariant_refs_by_test, write_scope_conflict_reasons, } from "./tasks.js";
|
|
8
|
-
import { duplicate_evidence_id_reasons, dangling_evidence_ref_reasons,
|
|
8
|
+
import { duplicate_evidence_id_reasons, dangling_evidence_ref_reasons, final_verification_evidences, live_task_reopens, live_task_reopen_resolutions, live_pass, live_user_confirmations, pass_task_reopens, supersede_reasons, unresolved_live_task_reopens, validate_evidence_schema, verify_reference_reasons, } from "./evidence.js";
|
|
9
9
|
import { review_disclosure_reasons } from "./disclosure.js";
|
|
10
10
|
import { archive_manifest_path } from "./archive.js";
|
|
11
|
-
import {
|
|
11
|
+
import { fingerprint_matches, } from "./apply_worker_chain.js";
|
|
12
|
+
import { apply_worker_chain_lifecycle_reasons as shared_apply_worker_chain_lifecycle_reasons, apply_worker_chain_lifecycle_state, serial_completion_green, } from "./apply_worker_chain_lifecycle.js";
|
|
12
13
|
function action_list(...items) {
|
|
13
14
|
return [...new Set(items.filter((item) => typeof item === "string" && item.length > 0))];
|
|
14
15
|
}
|
|
@@ -1145,259 +1146,8 @@ export function check_task_edit(change, status, changeRoot, evidences, taskId) {
|
|
|
1145
1146
|
}
|
|
1146
1147
|
return allow(change, gate, { task_id: taskId });
|
|
1147
1148
|
}
|
|
1148
|
-
function pinned_artifact_ref_matches(changeRoot, refItem, role, taskId, chainId, kind = "worker_report") {
|
|
1149
|
-
return shared_pinned_artifact_ref_reasons(changeRoot, refItem, `${role}_ref`, { kind, role, taskId, chainId }).length === 0;
|
|
1150
|
-
}
|
|
1151
|
-
function executor_worker_test_run_refs_valid(changeRoot, ev, taskId, chainId) {
|
|
1152
|
-
return worker_test_run_reasons(changeRoot, ev, taskId, chainId).length === 0;
|
|
1153
|
-
}
|
|
1154
|
-
function report_origin_matches(refItem, expected) {
|
|
1155
|
-
return isObject(refItem) && typeof expected === "string" && refItem.origin_packet_fingerprint === expected;
|
|
1156
|
-
}
|
|
1157
|
-
function report_input_refs_match(refItem, refs) {
|
|
1158
|
-
return isObject(refItem) && refItem.input_ref_digest === worker_input_ref_digest(refs);
|
|
1159
|
-
}
|
|
1160
|
-
function report_input_digest_matches(refItem, expectedDigest) {
|
|
1161
|
-
return isObject(refItem) && refItem.input_ref_digest === expectedDigest;
|
|
1162
|
-
}
|
|
1163
|
-
function chain_green_ids(ev) {
|
|
1164
|
-
if (Array.isArray(ev.green_test_run_evidence_refs)) {
|
|
1165
|
-
return ev.green_test_run_evidence_refs.map(String).filter(Boolean).sort();
|
|
1166
|
-
}
|
|
1167
|
-
const greenId = isObject(ev.green_test_run_evidence_ref)
|
|
1168
|
-
? String(ev.green_test_run_evidence_ref.evidence_id ?? "")
|
|
1169
|
-
: String(ev.green_test_run_evidence_ref ?? "");
|
|
1170
|
-
return greenId ? [greenId] : [];
|
|
1171
|
-
}
|
|
1172
|
-
function active_pre_edit_input_refs(evidences, active) {
|
|
1173
|
-
const preIds = Array.isArray(active.pre_edit_evidence_refs) ? active.pre_edit_evidence_refs.map(String) : [];
|
|
1174
|
-
return preIds.map((evidenceId) => {
|
|
1175
|
-
const ev = live_pass(evidences, { kind: "test_run" }).find((item) => String(item.evidence_id ?? "") === evidenceId);
|
|
1176
|
-
return ev
|
|
1177
|
-
? { kind: "test_run", evidence_id: evidenceId, phase: ev.phase, semantic_status: ev.semantic_status }
|
|
1178
|
-
: { kind: "test_run", evidence_id: evidenceId };
|
|
1179
|
-
});
|
|
1180
|
-
}
|
|
1181
|
-
function closed_apply_worker_chain_valid(repoRoot, changeRoot, ev, evidences, taskId, chainId) {
|
|
1182
|
-
const active = live_pass(evidences, { gate: "task_complete", kind: "apply_worker_chain", task_id: taskId })
|
|
1183
|
-
.find((item) => item.chain_state === "active" && item.apply_worker_chain_id === chainId);
|
|
1184
|
-
if (!active)
|
|
1185
|
-
return false;
|
|
1186
|
-
if (pre_edit_evidence_ref_reasons(evidences, active, taskId).length > 0)
|
|
1187
|
-
return false;
|
|
1188
|
-
if (!pinned_artifact_ref_matches(changeRoot, ev.executor_report_ref, "executor", taskId, chainId))
|
|
1189
|
-
return false;
|
|
1190
|
-
if (!pinned_artifact_ref_matches(changeRoot, ev.task_code_review_report_ref, "code-reviewer", taskId, chainId))
|
|
1191
|
-
return false;
|
|
1192
|
-
if (!pinned_artifact_ref_matches(changeRoot, ev.verifier_report_ref, "verifier", taskId, chainId))
|
|
1193
|
-
return false;
|
|
1194
|
-
if (!report_origin_matches(ev.executor_report_ref, active.executor_packet_fingerprint))
|
|
1195
|
-
return false;
|
|
1196
|
-
if (!report_input_digest_matches(ev.executor_report_ref, apply_worker_executor_input_ref_digest(evidences, active)))
|
|
1197
|
-
return false;
|
|
1198
|
-
if (!report_input_refs_match(ev.task_code_review_report_ref, [ev.executor_report_ref]))
|
|
1199
|
-
return false;
|
|
1200
|
-
if (typeof ev.observed_freshness_fingerprint !== "string" && !(isObject(ev.observed_freshness_fingerprint) && typeof ev.observed_freshness_fingerprint.fingerprint_digest === "string"))
|
|
1201
|
-
return false;
|
|
1202
|
-
const verifierReport = read_pinned_artifact_json(changeRoot, ev.verifier_report_ref);
|
|
1203
|
-
const verifierObserved = verifierReport?.observed_freshness_fingerprint ?? (isObject(ev.verifier_report_ref) ? ev.verifier_report_ref.observed_freshness_fingerprint : undefined);
|
|
1204
|
-
if (!fingerprint_matches(verifierObserved, ev.observed_freshness_fingerprint))
|
|
1205
|
-
return false;
|
|
1206
|
-
const greenIds = chain_green_ids(ev);
|
|
1207
|
-
if (greenIds.length === 0)
|
|
1208
|
-
return false;
|
|
1209
|
-
const greenRuns = greenIds.map((greenId) => live_pass(evidences, { gate: "task_complete", kind: "test_run", task_id: taskId }).find((item) => (String(item.evidence_id ?? "") === greenId
|
|
1210
|
-
&& item.semantic_status === "expected_success"
|
|
1211
|
-
&& (item.phase === undefined || item.phase === "green")
|
|
1212
|
-
&& item.apply_execution_chain === "executor_worker"
|
|
1213
|
-
&& item.apply_worker_chain_id === chainId
|
|
1214
|
-
&& executor_worker_test_run_refs_valid(changeRoot, item, taskId, chainId)))).filter((item) => isObject(item));
|
|
1215
|
-
if (greenRuns.length !== greenIds.length)
|
|
1216
|
-
return false;
|
|
1217
|
-
if (!report_input_refs_match(ev.verifier_report_ref, [
|
|
1218
|
-
ev.executor_report_ref,
|
|
1219
|
-
ev.task_code_review_report_ref,
|
|
1220
|
-
...greenRuns.map((green) => ({ kind: "test_run", evidence_id: String(green.evidence_id ?? ""), phase: green.phase, semantic_status: green.semantic_status })),
|
|
1221
|
-
...active_pre_edit_input_refs(evidences, active),
|
|
1222
|
-
]))
|
|
1223
|
-
return false;
|
|
1224
|
-
const currentFreshness = compute_apply_worker_freshness(repoRoot, changeRoot, evidences, taskId, chainId, {
|
|
1225
|
-
executor_report_ref: ev.executor_report_ref,
|
|
1226
|
-
task_code_review_report_ref: ev.task_code_review_report_ref,
|
|
1227
|
-
green_test_run_evidence_ref: greenIds[0],
|
|
1228
|
-
green_test_run_evidence_refs: greenIds,
|
|
1229
|
-
verifier_report_ref: ev.verifier_report_ref,
|
|
1230
|
-
});
|
|
1231
|
-
if (Array.isArray(currentFreshness.unexpected_guard_owned_dirty_paths) && currentFreshness.unexpected_guard_owned_dirty_paths.length > 0)
|
|
1232
|
-
return false;
|
|
1233
|
-
if (Array.isArray(currentFreshness.protected_dirty_paths) && currentFreshness.protected_dirty_paths.length > 0)
|
|
1234
|
-
return false;
|
|
1235
|
-
if (!greenRuns.every((green) => fingerprint_matches(green.implementation_fingerprint, currentFreshness.implementation_fingerprint)))
|
|
1236
|
-
return false;
|
|
1237
|
-
if (!fingerprint_matches(isObject(ev.task_code_review_report_ref) ? ev.task_code_review_report_ref.observed_implementation_fingerprint : undefined, currentFreshness.implementation_fingerprint))
|
|
1238
|
-
return false;
|
|
1239
|
-
return fingerprint_matches(currentFreshness, verifierObserved);
|
|
1240
|
-
}
|
|
1241
|
-
function pinned_takeover_baseline_valid(changeRoot, refItem, taskId, chainId) {
|
|
1242
|
-
return shared_pinned_artifact_ref_reasons(changeRoot, refItem, "serial_takeover_baseline_ref", { kind: "status_report", role: "verifier", taskId, chainId }).length === 0;
|
|
1243
|
-
}
|
|
1244
|
-
function abandoned_apply_worker_chain_valid(repoRoot, changeRoot, ev, active, taskId, chainId) {
|
|
1245
|
-
if (!active)
|
|
1246
|
-
return false;
|
|
1247
|
-
if ("restored_implementation_fingerprint" in ev) {
|
|
1248
|
-
const activeScope = Array.isArray(active.declared_task_write_scope) ? active.declared_task_write_scope.map(String).filter(Boolean) : [];
|
|
1249
|
-
const current = apply_worker_implementation_fingerprint(repoRoot, changeRoot, [], { declaredTaskWriteScope: activeScope });
|
|
1250
|
-
if (fingerprint_matches(ev.restored_implementation_fingerprint, active.source_implementation_fingerprint)
|
|
1251
|
-
&& fingerprint_matches(current, active.source_implementation_fingerprint))
|
|
1252
|
-
return true;
|
|
1253
|
-
}
|
|
1254
|
-
if ("serial_takeover_baseline_ref" in ev) {
|
|
1255
|
-
return pinned_takeover_baseline_valid(changeRoot, ev.serial_takeover_baseline_ref, taskId, chainId)
|
|
1256
|
-
&& Array.isArray(ev.successor_green_evidence_refs)
|
|
1257
|
-
&& ev.successor_green_evidence_refs.length > 0
|
|
1258
|
-
&& ev.successor_green_evidence_refs.every((item) => typeof item === "string" && item.length > 0);
|
|
1259
|
-
}
|
|
1260
|
-
return false;
|
|
1261
|
-
}
|
|
1262
|
-
function serial_completion_green(ev) {
|
|
1263
|
-
return (ev.runner_origin === undefined || ev.runner_origin === "main-thread")
|
|
1264
|
-
&& ev.apply_execution_chain !== "executor_worker"
|
|
1265
|
-
&& typeof ev.apply_worker_chain_id !== "string";
|
|
1266
|
-
}
|
|
1267
|
-
function apply_worker_chain_completion_state(repoRoot, changeRoot, evidences, taskId) {
|
|
1268
|
-
const chains = find_pass(evidences, { gate: "task_complete", kind: "apply_worker_chain", task_id: taskId });
|
|
1269
|
-
const activeByChain = new Map();
|
|
1270
|
-
const activeCounts = new Map();
|
|
1271
|
-
for (const ev of chains) {
|
|
1272
|
-
if (String(ev.chain_state ?? "") !== "active")
|
|
1273
|
-
continue;
|
|
1274
|
-
const chainId = String(ev.apply_worker_chain_id ?? "");
|
|
1275
|
-
if (!chainId)
|
|
1276
|
-
continue;
|
|
1277
|
-
const bucket = activeCounts.get(chainId) ?? [];
|
|
1278
|
-
bucket.push(ev);
|
|
1279
|
-
activeCounts.set(chainId, bucket);
|
|
1280
|
-
if (!activeByChain.has(chainId))
|
|
1281
|
-
activeByChain.set(chainId, ev);
|
|
1282
|
-
}
|
|
1283
|
-
const chainIdsWithActive = new Set([...activeByChain.keys()]);
|
|
1284
|
-
const chainIdsWithTerminal = new Set(chains
|
|
1285
|
-
.filter((ev) => {
|
|
1286
|
-
const chainId = String(ev.apply_worker_chain_id ?? "");
|
|
1287
|
-
if (!chainId)
|
|
1288
|
-
return false;
|
|
1289
|
-
if (String(ev.chain_state ?? "") === "closed")
|
|
1290
|
-
return closed_apply_worker_chain_valid(repoRoot, changeRoot, ev, evidences, taskId, chainId);
|
|
1291
|
-
if (String(ev.chain_state ?? "") === "abandoned")
|
|
1292
|
-
return abandoned_apply_worker_chain_valid(repoRoot, changeRoot, ev, activeByChain.get(chainId), taskId, chainId);
|
|
1293
|
-
return false;
|
|
1294
|
-
})
|
|
1295
|
-
.map((ev) => String(ev.apply_worker_chain_id ?? ""))
|
|
1296
|
-
.filter(Boolean));
|
|
1297
|
-
const chainIdsWithOpenActive = new Set([...activeByChain.keys()].filter((chainId) => !chainIdsWithTerminal.has(chainId)));
|
|
1298
|
-
const validClosedChainIds = new Set(chains
|
|
1299
|
-
.filter((ev) => {
|
|
1300
|
-
const chainId = String(ev.apply_worker_chain_id ?? "");
|
|
1301
|
-
return chainId
|
|
1302
|
-
&& String(ev.chain_state ?? "") === "closed"
|
|
1303
|
-
&& closed_apply_worker_chain_valid(repoRoot, changeRoot, ev, evidences, taskId, chainId);
|
|
1304
|
-
})
|
|
1305
|
-
.map((ev) => String(ev.apply_worker_chain_id ?? ""))
|
|
1306
|
-
.filter(Boolean));
|
|
1307
|
-
const validClosedGreenEvidenceIds = new Set(chains
|
|
1308
|
-
.filter((ev) => {
|
|
1309
|
-
const chainId = String(ev.apply_worker_chain_id ?? "");
|
|
1310
|
-
return chainId
|
|
1311
|
-
&& String(ev.chain_state ?? "") === "closed"
|
|
1312
|
-
&& closed_apply_worker_chain_valid(repoRoot, changeRoot, ev, evidences, taskId, chainId);
|
|
1313
|
-
})
|
|
1314
|
-
.flatMap(chain_green_ids));
|
|
1315
|
-
const takeoverBaselines = [];
|
|
1316
|
-
const reasons = [];
|
|
1317
|
-
const duplicateActives = [...activeCounts.entries()]
|
|
1318
|
-
.filter(([chainId]) => chainIdsWithOpenActive.has(chainId))
|
|
1319
|
-
.map(([, items]) => items)
|
|
1320
|
-
.filter((items) => items.length > 1)
|
|
1321
|
-
.flatMap((items) => items.map((ev) => String(ev.evidence_id ?? ev.apply_worker_chain_id ?? "")))
|
|
1322
|
-
.filter(Boolean)
|
|
1323
|
-
.sort();
|
|
1324
|
-
const openActiveValues = [...chainIdsWithOpenActive].map((chainId) => activeByChain.get(chainId)).filter((ev) => isObject(ev));
|
|
1325
|
-
const parallelActives = openActiveValues.length > 1
|
|
1326
|
-
? openActiveValues.map((ev) => String(ev.evidence_id ?? ev.apply_worker_chain_id ?? "")).filter(Boolean).sort()
|
|
1327
|
-
: [];
|
|
1328
|
-
const activeConflicts = [...new Set([...duplicateActives, ...parallelActives])].sort();
|
|
1329
|
-
if (activeConflicts.length > 0) {
|
|
1330
|
-
reasons.push(reason("apply_worker_chain_active_conflict", `task ${taskId} has conflicting active apply worker chain markers: ${renderList(activeConflicts)}`, activeConflicts));
|
|
1331
|
-
}
|
|
1332
|
-
const terminalsByChain = new Map();
|
|
1333
|
-
for (const ev of chains) {
|
|
1334
|
-
const state = String(ev.chain_state ?? "");
|
|
1335
|
-
if (state !== "closed" && state !== "abandoned")
|
|
1336
|
-
continue;
|
|
1337
|
-
const chainId = String(ev.apply_worker_chain_id ?? "");
|
|
1338
|
-
if (!chainId)
|
|
1339
|
-
continue;
|
|
1340
|
-
const bucket = terminalsByChain.get(chainId) ?? [];
|
|
1341
|
-
bucket.push(ev);
|
|
1342
|
-
terminalsByChain.set(chainId, bucket);
|
|
1343
|
-
}
|
|
1344
|
-
const duplicateTerminals = [...terminalsByChain.values()]
|
|
1345
|
-
.filter((items) => items.length > 1)
|
|
1346
|
-
.flatMap((items) => items.map((ev) => String(ev.evidence_id ?? ev.apply_worker_chain_id ?? "")))
|
|
1347
|
-
.filter(Boolean)
|
|
1348
|
-
.sort();
|
|
1349
|
-
if (duplicateTerminals.length > 0) {
|
|
1350
|
-
reasons.push(reason("apply_worker_chain_terminal_conflict", `task ${taskId} has duplicate apply worker chain terminal markers: ${renderList(duplicateTerminals)}`, duplicateTerminals));
|
|
1351
|
-
}
|
|
1352
|
-
const invalidTerminals = chains
|
|
1353
|
-
.filter((ev) => String(ev.chain_state ?? "") === "closed" || String(ev.chain_state ?? "") === "abandoned")
|
|
1354
|
-
.filter((ev) => {
|
|
1355
|
-
const chainId = String(ev.apply_worker_chain_id ?? "");
|
|
1356
|
-
if (!chainId)
|
|
1357
|
-
return true;
|
|
1358
|
-
if (String(ev.chain_state ?? "") === "closed")
|
|
1359
|
-
return !closed_apply_worker_chain_valid(repoRoot, changeRoot, ev, evidences, taskId, chainId);
|
|
1360
|
-
const valid = abandoned_apply_worker_chain_valid(repoRoot, changeRoot, ev, activeByChain.get(chainId), taskId, chainId);
|
|
1361
|
-
if (valid && isObject(ev.serial_takeover_baseline_ref)) {
|
|
1362
|
-
const baseline = read_pinned_artifact_json(changeRoot, ev.serial_takeover_baseline_ref);
|
|
1363
|
-
if (baseline?.implementation_fingerprint !== undefined) {
|
|
1364
|
-
takeoverBaselines.push({
|
|
1365
|
-
fingerprint: baseline.implementation_fingerprint,
|
|
1366
|
-
successorGreenRefs: Array.isArray(ev.successor_green_evidence_refs) ? ev.successor_green_evidence_refs.map(String).filter(Boolean) : [],
|
|
1367
|
-
});
|
|
1368
|
-
}
|
|
1369
|
-
}
|
|
1370
|
-
return !valid;
|
|
1371
|
-
})
|
|
1372
|
-
.map((ev) => String(ev.evidence_id ?? ev.apply_worker_chain_id ?? ""))
|
|
1373
|
-
.filter(Boolean)
|
|
1374
|
-
.sort();
|
|
1375
|
-
if (invalidTerminals.length > 0) {
|
|
1376
|
-
reasons.push(reason("apply_worker_chain_terminal_invalid", `task ${taskId} has invalid apply worker chain terminal markers: ${renderList(invalidTerminals)}`, invalidTerminals));
|
|
1377
|
-
}
|
|
1378
|
-
for (const chainId of [...chainIdsWithOpenActive].sort()) {
|
|
1379
|
-
if (!chainIdsWithTerminal.has(chainId)) {
|
|
1380
|
-
reasons.push(reason("apply_worker_chain_active", `task ${taskId} has active apply worker chain and cannot use non-chain task completion: ${chainId}`, [chainId]));
|
|
1381
|
-
}
|
|
1382
|
-
}
|
|
1383
|
-
for (const ev of evidences) {
|
|
1384
|
-
if (!isObject(ev) || ev._invalid || ev.status !== "pass" || ev.task_id !== taskId)
|
|
1385
|
-
continue;
|
|
1386
|
-
const chainId = typeof ev.apply_worker_chain_id === "string" ? ev.apply_worker_chain_id : "";
|
|
1387
|
-
if (!chainId)
|
|
1388
|
-
continue;
|
|
1389
|
-
if (!chainIdsWithActive.has(chainId)) {
|
|
1390
|
-
reasons.push(reason("apply_worker_chain_missing_active", `task ${taskId} references apply_worker_chain_id=${repr(chainId)} without a matching active marker`, [chainId]));
|
|
1391
|
-
}
|
|
1392
|
-
}
|
|
1393
|
-
if (duplicateTerminals.length > 0 || activeConflicts.length > 0) {
|
|
1394
|
-
validClosedChainIds.clear();
|
|
1395
|
-
validClosedGreenEvidenceIds.clear();
|
|
1396
|
-
}
|
|
1397
|
-
return { reasons, validClosedChainIds, validClosedGreenEvidenceIds, takeoverBaselines };
|
|
1398
|
-
}
|
|
1399
1149
|
export function apply_worker_chain_lifecycle_reasons(repoRoot, changeRoot, evidences, taskId) {
|
|
1400
|
-
return
|
|
1150
|
+
return shared_apply_worker_chain_lifecycle_reasons(repoRoot, changeRoot, evidences, taskId);
|
|
1401
1151
|
}
|
|
1402
1152
|
export function check_task_complete(change, status, changeRoot, evidences, taskId) {
|
|
1403
1153
|
const gate = "task_complete";
|
|
@@ -1413,8 +1163,8 @@ export function check_task_complete(change, status, changeRoot, evidences, taskI
|
|
|
1413
1163
|
const task = parse_tasks(changeRoot)[taskId];
|
|
1414
1164
|
if (!task)
|
|
1415
1165
|
return block(change, gate, [reason("unknown_task", `task ${taskId} not found`)], { task_id: taskId });
|
|
1416
|
-
const workerChainState =
|
|
1417
|
-
reasons.push(...workerChainState.
|
|
1166
|
+
const workerChainState = apply_worker_chain_lifecycle_state(get_repo_root(status), changeRoot, evidences, taskId);
|
|
1167
|
+
reasons.push(...workerChainState.completionReasons);
|
|
1418
1168
|
const reopenHistory = task_reopen_history(evidences, taskId);
|
|
1419
1169
|
if (task.checked && reopenHistory.pass.length > 1) {
|
|
1420
1170
|
reasons.push(reason("reopen_lifecycle_exhausted", `task ${taskId} has multiple task_reopen histories in this change; v1 allows at most one`, [taskId]));
|
|
@@ -5,13 +5,14 @@ import {} from "./cli_args.js";
|
|
|
5
5
|
import { CLAIM_ADJUDICATION_DECISIONS, FINDING_ADJUDICATION_DECISIONS, fingerprint_obj, GuardError, MAIN_ADJUDICATION_REQUIRED_FIELDS, MAIN_ADJUDICATION_DECISIONS, REQUEST_CHANGES_ROUTES, REVIEW_EVIDENCE_REQUIRED_FIELDS, ROLE_EVIDENCE_FIELDS, SOURCE_GUIDANCE_REQUIRED_FIELDS, VERIFY_EVIDENCE_REQUIRED_FIELDS, allow, block, deepEqual, isObject, reason, renderList, runtime, safe_within, sha256_text, } from "./util.js";
|
|
6
6
|
import { artifact_status_map, gate_route_phase, get_change_root, get_repo_root, normalize_gate, openspec_status, openspec_status_shape_reasons, } from "./openspec.js";
|
|
7
7
|
import { build_finding_ledger, enumerate_review_targets, render_finding_ledger, REVIEW_TARGETS_BY_GATE, review_round_number, } from "./disclosure.js";
|
|
8
|
-
import { evidence_schema_guard, check_apply_ready, check_archive_ready, check_review_complete, check_superspec_gate, check_task_complete, check_task_edit, check_task_reopen,
|
|
8
|
+
import { evidence_schema_guard, check_apply_ready, check_archive_ready, check_review_complete, check_superspec_gate, check_task_complete, check_task_edit, check_task_reopen, } from "./gates.js";
|
|
9
9
|
import { final_verification_evidences, index_evidence, live_pass, live_user_confirmations } from "./evidence.js";
|
|
10
10
|
import { file_blob_sha, dirty_worktree_paths } from "./git.js";
|
|
11
11
|
import { preset_upgrade_reasons, preset_upgrade_required_from_context } from "./archive.js";
|
|
12
12
|
import { state_corrupt_reasons, state_stale_reasons } from "./state.js";
|
|
13
13
|
import { parse_tasks, resolve_test_contract_command, splitList, test_contract_invariant_refs_by_test } from "./tasks.js";
|
|
14
14
|
import { APPLY_CODE_REVIEW_REPORT_REQUIRED_FIELDS, APPLY_EXECUTOR_REPORT_REQUIRED_FIELDS, APPLY_TEST_RUNNER_REPORT_REQUIRED_FIELDS, APPLY_VERIFIER_REPORT_REQUIRED_FIELDS, apply_worker_implementation_fingerprint, apply_worker_executor_input_ref_digest, apply_worker_protected_path_refs, compute_apply_worker_freshness, fingerprint_digest, 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";
|
|
15
|
+
import { apply_worker_chain_lifecycle_state } from "./apply_worker_chain_lifecycle.js";
|
|
15
16
|
function load_packet_context(change) {
|
|
16
17
|
if (typeof runtime.load_context === "function") {
|
|
17
18
|
const [status, repoRoot, changeRoot, evidences] = runtime.load_context(change);
|
|
@@ -688,91 +689,9 @@ function active_apply_worker_chain_id(ctx, taskId) {
|
|
|
688
689
|
const active = active_apply_worker_chain(ctx, taskId).active;
|
|
689
690
|
return active ? String(active.apply_worker_chain_id) : null;
|
|
690
691
|
}
|
|
691
|
-
function terminal_apply_worker_chain_valid(ctx, ev, taskId, chainId) {
|
|
692
|
-
const state = String(ev.chain_state ?? "");
|
|
693
|
-
if (state === "closed") {
|
|
694
|
-
return shared_pinned_artifact_ref_reasons(ctx.changeRoot, ev.executor_report_ref, "executor_report_ref", { kind: "worker_report", role: "executor", taskId, chainId }).length === 0
|
|
695
|
-
&& shared_pinned_artifact_ref_reasons(ctx.changeRoot, ev.task_code_review_report_ref, "task_code_review_report_ref", { kind: "worker_report", role: "code-reviewer", taskId, chainId }).length === 0
|
|
696
|
-
&& shared_pinned_artifact_ref_reasons(ctx.changeRoot, ev.verifier_report_ref, "verifier_report_ref", { kind: "worker_report", role: "verifier", taskId, chainId }).length === 0;
|
|
697
|
-
}
|
|
698
|
-
if (state === "abandoned") {
|
|
699
|
-
if ("restored_implementation_fingerprint" in ev && fingerprint_digest(ev.restored_implementation_fingerprint))
|
|
700
|
-
return true;
|
|
701
|
-
if ("serial_takeover_baseline_ref" in ev) {
|
|
702
|
-
return shared_pinned_artifact_ref_reasons(ctx.changeRoot, ev.serial_takeover_baseline_ref, "serial_takeover_baseline_ref", { kind: "status_report", role: "verifier", taskId, chainId }).length === 0
|
|
703
|
-
&& Array.isArray(ev.successor_green_evidence_refs)
|
|
704
|
-
&& ev.successor_green_evidence_refs.length > 0
|
|
705
|
-
&& ev.successor_green_evidence_refs.every((item) => typeof item === "string" && item.length > 0);
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
return false;
|
|
709
|
-
}
|
|
710
692
|
function apply_worker_chain_packet_state(ctx, taskId) {
|
|
711
|
-
const
|
|
712
|
-
|
|
713
|
-
.filter((ev) => typeof ev.apply_worker_chain_id === "string" && ev.apply_worker_chain_id);
|
|
714
|
-
const blockers = [];
|
|
715
|
-
const activeByChain = new Map();
|
|
716
|
-
const terminalsByChain = new Map();
|
|
717
|
-
for (const ev of chains) {
|
|
718
|
-
const chainId = String(ev.apply_worker_chain_id ?? "");
|
|
719
|
-
if (String(ev.chain_state ?? "") === "active") {
|
|
720
|
-
const bucket = activeByChain.get(chainId) ?? [];
|
|
721
|
-
bucket.push(ev);
|
|
722
|
-
activeByChain.set(chainId, bucket);
|
|
723
|
-
}
|
|
724
|
-
if (String(ev.chain_state ?? "") === "closed" || String(ev.chain_state ?? "") === "abandoned") {
|
|
725
|
-
const bucket = terminalsByChain.get(chainId) ?? [];
|
|
726
|
-
bucket.push(ev);
|
|
727
|
-
terminalsByChain.set(chainId, bucket);
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
const validTerminalChainIds = new Set(chains
|
|
731
|
-
.filter((ev) => String(ev.chain_state ?? "") === "closed" || String(ev.chain_state ?? "") === "abandoned")
|
|
732
|
-
.filter((ev) => terminal_apply_worker_chain_valid(ctx, ev, taskId, String(ev.apply_worker_chain_id ?? "")))
|
|
733
|
-
.map((ev) => String(ev.apply_worker_chain_id ?? ""))
|
|
734
|
-
.filter(Boolean));
|
|
735
|
-
const duplicateActives = [...activeByChain.entries()]
|
|
736
|
-
.filter(([chainId]) => !validTerminalChainIds.has(chainId))
|
|
737
|
-
.map(([, items]) => items)
|
|
738
|
-
.filter((items) => items.length > 1)
|
|
739
|
-
.flatMap((items) => items.map((ev) => String(ev.evidence_id ?? ev.apply_worker_chain_id ?? "")))
|
|
740
|
-
.filter(Boolean)
|
|
741
|
-
.sort();
|
|
742
|
-
const activeCandidates = [...activeByChain.entries()]
|
|
743
|
-
.filter(([chainId]) => !validTerminalChainIds.has(chainId))
|
|
744
|
-
.map(([, items]) => items[0])
|
|
745
|
-
.filter(Boolean);
|
|
746
|
-
blockers.push(...apply_worker_chain_lifecycle_reasons(ctx.repoRoot, ctx.changeRoot, ctx.evidences, taskId).filter((item) => (item.code === "apply_worker_chain_active_conflict"
|
|
747
|
-
|| item.code === "apply_worker_chain_terminal_conflict"
|
|
748
|
-
|| item.code === "apply_worker_chain_terminal_invalid"
|
|
749
|
-
|| item.code === "apply_worker_chain_missing_active")));
|
|
750
|
-
const parallelActives = activeCandidates.length > 1
|
|
751
|
-
? activeCandidates.map((ev) => String(ev.evidence_id ?? ev.apply_worker_chain_id ?? "")).filter(Boolean).sort()
|
|
752
|
-
: [];
|
|
753
|
-
const activeConflicts = [...new Set([...duplicateActives, ...parallelActives])].sort();
|
|
754
|
-
if (activeConflicts.length > 0) {
|
|
755
|
-
blockers.push(reason("apply_worker_chain_active_conflict", `task ${taskId} has conflicting active apply worker chain markers: ${renderList(activeConflicts)}`, activeConflicts));
|
|
756
|
-
}
|
|
757
|
-
const duplicateTerminals = [...terminalsByChain.values()]
|
|
758
|
-
.filter((items) => items.length > 1)
|
|
759
|
-
.flatMap((items) => items.map((ev) => String(ev.evidence_id ?? ev.apply_worker_chain_id ?? "")))
|
|
760
|
-
.filter(Boolean)
|
|
761
|
-
.sort();
|
|
762
|
-
if (duplicateTerminals.length > 0) {
|
|
763
|
-
blockers.push(reason("apply_worker_chain_terminal_conflict", `task ${taskId} has duplicate apply worker chain terminal markers: ${renderList(duplicateTerminals)}`, duplicateTerminals));
|
|
764
|
-
}
|
|
765
|
-
const invalidTerminals = chains
|
|
766
|
-
.filter((ev) => String(ev.chain_state ?? "") === "closed" || String(ev.chain_state ?? "") === "abandoned")
|
|
767
|
-
.filter((ev) => !terminal_apply_worker_chain_valid(ctx, ev, taskId, String(ev.apply_worker_chain_id ?? "")))
|
|
768
|
-
.map((ev) => String(ev.evidence_id ?? ev.apply_worker_chain_id ?? ""))
|
|
769
|
-
.filter(Boolean)
|
|
770
|
-
.sort();
|
|
771
|
-
if (invalidTerminals.length > 0) {
|
|
772
|
-
blockers.push(reason("apply_worker_chain_terminal_invalid", `task ${taskId} has invalid apply worker chain terminal markers: ${renderList(invalidTerminals)}`, invalidTerminals));
|
|
773
|
-
}
|
|
774
|
-
const active = activeCandidates[0] ?? null;
|
|
775
|
-
return { active: blockers.length === 0 ? active : null, blockers };
|
|
693
|
+
const state = apply_worker_chain_lifecycle_state(ctx.repoRoot, ctx.changeRoot, ctx.evidences, taskId);
|
|
694
|
+
return { active: state.active, blockers: state.packetBlockers };
|
|
776
695
|
}
|
|
777
696
|
function active_apply_worker_chain(ctx, taskId) {
|
|
778
697
|
return apply_worker_chain_packet_state(ctx, taskId);
|