@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, find_pass, 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";
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 { 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";
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 apply_worker_chain_completion_state(repoRoot, changeRoot, evidences, taskId).reasons;
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 = apply_worker_chain_completion_state(get_repo_root(status), changeRoot, evidences, taskId);
1417
- reasons.push(...workerChainState.reasons);
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, apply_worker_chain_lifecycle_reasons, } from "./gates.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, } 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 chains = ctx.evidences
712
- .filter((ev) => isObject(ev) && !ev._invalid && ev.status === "pass" && ev.gate === "task_complete" && ev.kind === "apply_worker_chain" && ev.task_id === taskId)
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peterxiaoyang/superspec",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "SuperSpec workflow package: guard runtime, generic workflow templates, and Codex adapter payload.",
5
5
  "repository": {
6
6
  "type": "git",