@peterxiaoyang/superspec 0.1.13 → 0.1.15-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +302 -0
- package/dist/format.d.ts +29 -0
- package/dist/format.js +141 -0
- package/dist/next.d.ts +3 -0
- package/dist/next.js +161 -0
- package/dist/openspec.d.ts +9 -0
- package/dist/openspec.js +41 -0
- package/dist/record.d.ts +20 -0
- package/dist/record.js +211 -0
- package/dist/skill_loop.d.ts +23 -0
- package/dist/skill_loop.js +58 -0
- package/dist/store.d.ts +31 -0
- package/dist/store.js +213 -0
- package/dist/sync.d.ts +3 -0
- package/dist/sync.js +135 -0
- package/dist/task.d.ts +7 -0
- package/dist/task.js +44 -0
- package/dist/transition.d.ts +34 -0
- package/dist/transition.js +400 -0
- package/dist/types.d.ts +138 -0
- package/dist/types.js +3 -0
- package/package.json +8 -53
- package/templates/workflow/skills/superspec-apply/SKILL.md +46 -118
- package/templates/workflow/skills/superspec-archive/SKILL.md +22 -47
- package/templates/workflow/skills/superspec-explore/SKILL.md +38 -69
- package/templates/workflow/skills/superspec-propose/SKILL.md +58 -57
- package/templates/workflow/skills/superspec-review/SKILL.md +36 -81
- package/adapters/codex/agents/architect.toml +0 -13
- package/adapters/codex/agents/code-reviewer.toml +0 -13
- package/adapters/codex/agents/critic.toml +0 -13
- package/adapters/codex/agents/executor.toml +0 -13
- package/adapters/codex/agents/explore.toml +0 -13
- package/adapters/codex/agents/test-engineer.toml +0 -13
- package/adapters/codex/agents/test-runner.toml +0 -13
- package/adapters/codex/agents/verifier.toml +0 -13
- package/adapters/codex/install-map.json +0 -116
- package/bin/launch.js +0 -37
- package/bin/superspec-check.js +0 -4
- package/bin/superspec-hook.js +0 -4
- package/bin/superspec-init.js +0 -4
- package/bin/superspec.js +0 -4
- package/dist/src/apply_worker_chain.d.ts +0 -60
- package/dist/src/apply_worker_chain.js +0 -1283
- package/dist/src/apply_worker_chain_lifecycle.d.ts +0 -21
- package/dist/src/apply_worker_chain_lifecycle.js +0 -409
- package/dist/src/archive.d.ts +0 -23
- package/dist/src/archive.js +0 -430
- package/dist/src/cli.d.ts +0 -1
- package/dist/src/cli.js +0 -33
- package/dist/src/cli_args.d.ts +0 -32
- package/dist/src/cli_args.js +0 -454
- package/dist/src/core.d.ts +0 -20
- package/dist/src/core.js +0 -394
- package/dist/src/disclosure.d.ts +0 -35
- package/dist/src/disclosure.js +0 -671
- package/dist/src/doctor.d.ts +0 -44
- package/dist/src/doctor.js +0 -230
- package/dist/src/evidence.d.ts +0 -29
- package/dist/src/evidence.js +0 -1107
- package/dist/src/gates.d.ts +0 -19
- package/dist/src/gates.js +0 -1631
- package/dist/src/git.d.ts +0 -8
- package/dist/src/git.js +0 -112
- package/dist/src/hooks/adapter.d.ts +0 -5
- package/dist/src/hooks/adapter.js +0 -347
- package/dist/src/hooks/guard_api.d.ts +0 -12
- package/dist/src/hooks/guard_api.js +0 -797
- package/dist/src/hooks/health.d.ts +0 -4
- package/dist/src/hooks/health.js +0 -86
- package/dist/src/hooks/policy_event.d.ts +0 -41
- package/dist/src/hooks/policy_event.js +0 -2634
- package/dist/src/hooks/types.d.ts +0 -72
- package/dist/src/hooks/types.js +0 -1
- package/dist/src/hooks/validation.d.ts +0 -3
- package/dist/src/hooks/validation.js +0 -70
- package/dist/src/i18n.d.ts +0 -21
- package/dist/src/i18n.js +0 -720
- package/dist/src/init_cli.d.ts +0 -28
- package/dist/src/init_cli.js +0 -399
- package/dist/src/install_engine.d.ts +0 -71
- package/dist/src/install_engine.js +0 -538
- package/dist/src/invariants.d.ts +0 -16
- package/dist/src/invariants.js +0 -363
- package/dist/src/openspec.d.ts +0 -37
- package/dist/src/openspec.js +0 -274
- package/dist/src/packet_render.d.ts +0 -5
- package/dist/src/packet_render.js +0 -1759
- package/dist/src/packet_schema.d.ts +0 -58
- package/dist/src/packet_schema.js +0 -1
- package/dist/src/paths.d.ts +0 -22
- package/dist/src/paths.js +0 -203
- package/dist/src/project_init.d.ts +0 -31
- package/dist/src/project_init.js +0 -167
- package/dist/src/self_update.d.ts +0 -14
- package/dist/src/self_update.js +0 -56
- package/dist/src/state.d.ts +0 -37
- package/dist/src/state.js +0 -464
- package/dist/src/tasks.d.ts +0 -38
- package/dist/src/tasks.js +0 -373
- package/dist/src/util.d.ts +0 -163
- package/dist/src/util.js +0 -792
- package/dist/superspec.d.ts +0 -6
- package/dist/superspec.js +0 -97
- package/dist/superspec_guard.d.ts +0 -4
- package/dist/superspec_guard.js +0 -19
- package/dist/superspec_hook.d.ts +0 -4
- package/dist/superspec_hook.js +0 -35
- package/dist/superspec_init.d.ts +0 -2
- package/dist/superspec_init.js +0 -17
- package/schemas/hook-event.schema.json +0 -27
- package/schemas/install-manifest.schema.json +0 -97
- package/templates/hooks/codex-hooks.json +0 -35
- package/templates/sidecar/archive-preservation.json +0 -11
- package/templates/sidecar/business-invariants.md +0 -38
- package/templates/sidecar/config.yaml +0 -13
- package/templates/sidecar/discovery.md +0 -33
- package/templates/sidecar/test-contract.md +0 -27
- package/templates/workflow/prompts/architect.md +0 -27
- package/templates/workflow/prompts/code-reviewer.md +0 -34
- package/templates/workflow/prompts/critic.md +0 -30
- package/templates/workflow/prompts/executor.md +0 -32
- package/templates/workflow/prompts/explore.md +0 -27
- package/templates/workflow/prompts/test-engineer.md +0 -28
- package/templates/workflow/prompts/test-runner.md +0 -35
- package/templates/workflow/prompts/verifier.md +0 -35
|
@@ -1,1759 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { load_config, sidecar_business_invariants_path, sidecar_discovery_path, sidecar_test_contract_path } from "./paths.js";
|
|
4
|
-
import {} from "./cli_args.js";
|
|
5
|
-
import { CLAIM_ADJUDICATION_DECISIONS, FINDING_ADJUDICATION_DECISIONS, fingerprint_obj, GuardError, MAIN_ADJUDICATION_REQUIRED_FIELDS, MAIN_ADJUDICATION_DECISIONS, NO_TDD_REASONS, 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
|
-
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
|
-
import { build_finding_ledger, enumerate_review_targets, FINDING_CATEGORIES, FINDING_TYPES, MATERIAL_CATEGORIES, 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, } from "./gates.js";
|
|
9
|
-
import { final_verification_evidences, index_evidence, live_pass, live_user_confirmations } from "./evidence.js";
|
|
10
|
-
import { file_blob_sha, dirty_worktree_paths } from "./git.js";
|
|
11
|
-
import { preset_upgrade_reasons, preset_upgrade_required_from_context } from "./archive.js";
|
|
12
|
-
import { state_corrupt_reasons, state_stale_reasons } from "./state.js";
|
|
13
|
-
import { parse_tasks, resolve_test_contract_command, splitList, task_apply_execution_surface, task_apply_execution_surface_reasons, test_contract_invariant_refs_by_test, } from "./tasks.js";
|
|
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, pre_edit_worker_test_run_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";
|
|
16
|
-
import { PACKAGE_ROOT } from "./install_engine.js";
|
|
17
|
-
// B (template serving): discovery.md fill rules handed to the model alongside the template skeleton
|
|
18
|
-
// so every model produces the same structure (instead of freehanding a section the gate then has to
|
|
19
|
-
// parse heuristically). Kept here, not in the .md template, so they are versioned with the code that
|
|
20
|
-
// depends on them (the open-questions checkbox convention consumed by discovery_open_question_count).
|
|
21
|
-
const DISCOVERY_RULES = [
|
|
22
|
-
"按模板骨架填写 discovery.md,保留全部段落(调查范围 / 现有实现事实 / 隐性合约 / 风险与歧义 / 待确认问题 / Subagent Evidence),不得自创或删减段落骨架。",
|
|
23
|
-
"所有需要用户拍板的问题写进「## 待确认问题」段,每条用「- [ ]」;用户确认后改成「- [x]」或在项内写「已确认:」。",
|
|
24
|
-
"explore_complete 检查在该段仍有「- [ ]」或「仍需确认/待确认」项时不会通过。",
|
|
25
|
-
"段标题含「确认」字样即可被识别(待确认问题 / 需要用户确认的问题 等),不要改成无「确认」字样的标题。",
|
|
26
|
-
];
|
|
27
|
-
// Reads the canonical discovery template from the package. packageRoot is parameterized so tests can
|
|
28
|
-
// inject an empty dir to exercise the "unreadable → omit field" path; PACKAGE_ROOT itself is a
|
|
29
|
-
// module-load-time const and cannot be mocked. Returns null (not throws) when missing so a transient
|
|
30
|
-
// read failure never turns the whole packet into an error response.
|
|
31
|
-
export function read_discovery_template(packageRoot = PACKAGE_ROOT) {
|
|
32
|
-
const p = join(packageRoot, "templates", "sidecar", "discovery.md");
|
|
33
|
-
if (!existsSync(p) || !statSync(p).isFile())
|
|
34
|
-
return null;
|
|
35
|
-
try {
|
|
36
|
-
return readFileSync(p, "utf8");
|
|
37
|
-
}
|
|
38
|
-
catch {
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
function load_packet_context(change) {
|
|
43
|
-
if (typeof runtime.load_context === "function") {
|
|
44
|
-
const [status, repoRoot, changeRoot, evidences] = runtime.load_context(change);
|
|
45
|
-
return { change, status, repoRoot, changeRoot, evidences };
|
|
46
|
-
}
|
|
47
|
-
const status = openspec_status(change);
|
|
48
|
-
return {
|
|
49
|
-
change,
|
|
50
|
-
repoRoot: get_repo_root(status),
|
|
51
|
-
changeRoot: get_change_root(status),
|
|
52
|
-
evidences: index_evidence(get_change_root(status)),
|
|
53
|
-
status,
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
function change_pinned_ref(changeRoot, relPath) {
|
|
57
|
-
const absPath = join(changeRoot, relPath);
|
|
58
|
-
if (!existsSync(absPath) || !statSync(absPath).isFile())
|
|
59
|
-
return null;
|
|
60
|
-
return { root: "change", path: relPath, blob_sha: file_blob_sha(absPath) };
|
|
61
|
-
}
|
|
62
|
-
function repo_pinned_ref(repoRoot, relPath) {
|
|
63
|
-
const absPath = join(repoRoot, relPath);
|
|
64
|
-
if (!existsSync(absPath) || !statSync(absPath).isFile())
|
|
65
|
-
return null;
|
|
66
|
-
return { root: "repo", path: relPath, blob_sha: file_blob_sha(absPath) };
|
|
67
|
-
}
|
|
68
|
-
function collect_change_refs(changeRoot, relPaths) {
|
|
69
|
-
const refs = [];
|
|
70
|
-
for (const relPath of relPaths) {
|
|
71
|
-
const ref = change_pinned_ref(changeRoot, relPath);
|
|
72
|
-
if (ref)
|
|
73
|
-
refs.push(ref);
|
|
74
|
-
}
|
|
75
|
-
return refs;
|
|
76
|
-
}
|
|
77
|
-
function collect_repo_refs(repoRoot, relPaths) {
|
|
78
|
-
const refs = [];
|
|
79
|
-
for (const relPath of relPaths) {
|
|
80
|
-
const ref = repo_pinned_ref(repoRoot, relPath);
|
|
81
|
-
if (ref)
|
|
82
|
-
refs.push(ref);
|
|
83
|
-
}
|
|
84
|
-
return refs;
|
|
85
|
-
}
|
|
86
|
-
function unique_pinned_refs(refs) {
|
|
87
|
-
const seen = new Set();
|
|
88
|
-
const out = [];
|
|
89
|
-
for (const ref of refs) {
|
|
90
|
-
const key = `${ref.root}\u0000${ref.path}\u0000${ref.blob_sha}`;
|
|
91
|
-
if (seen.has(key))
|
|
92
|
-
continue;
|
|
93
|
-
seen.add(key);
|
|
94
|
-
out.push(ref);
|
|
95
|
-
}
|
|
96
|
-
return out;
|
|
97
|
-
}
|
|
98
|
-
function unique_strings(items) {
|
|
99
|
-
return [...new Set(items.filter(Boolean))];
|
|
100
|
-
}
|
|
101
|
-
function evidence_pinned_ref(root, item) {
|
|
102
|
-
if (!isObject(item))
|
|
103
|
-
return null;
|
|
104
|
-
if (typeof item.path !== "string" || !item.path)
|
|
105
|
-
return null;
|
|
106
|
-
if (typeof item.blob_sha !== "string" || !item.blob_sha)
|
|
107
|
-
return null;
|
|
108
|
-
return { root, path: item.path, blob_sha: item.blob_sha };
|
|
109
|
-
}
|
|
110
|
-
function dirty_repo_refs(ctx, reason) {
|
|
111
|
-
try {
|
|
112
|
-
const dirtyPaths = typeof runtime.dirty_worktree_paths === "function"
|
|
113
|
-
? runtime.dirty_worktree_paths(ctx.repoRoot)
|
|
114
|
-
: dirty_worktree_paths(ctx.repoRoot);
|
|
115
|
-
return collect_repo_refs(ctx.repoRoot, dirtyPaths);
|
|
116
|
-
}
|
|
117
|
-
catch (err) {
|
|
118
|
-
throw new GuardError(`${reason}: ${err.message}`);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
function packet_data_problems(ctx) {
|
|
122
|
-
const { change, status, repoRoot, changeRoot, evidences } = ctx;
|
|
123
|
-
const [config, configProblems] = load_config(repoRoot, changeRoot);
|
|
124
|
-
const shapeProblems = openspec_status_shape_reasons(status);
|
|
125
|
-
const evidenceProblems = evidence_schema_guard(change, changeRoot, repoRoot, evidences);
|
|
126
|
-
const changedPaths = String(config.preset ?? "full") !== "full"
|
|
127
|
-
? (typeof runtime.dirty_worktree_paths === "function" ? runtime.dirty_worktree_paths(repoRoot) : dirty_worktree_paths(repoRoot))
|
|
128
|
-
: [];
|
|
129
|
-
const presetRequired = preset_upgrade_required_from_context(config, changedPaths);
|
|
130
|
-
const presetHumanConfirmed = live_user_confirmations(evidences, "preset_upgrade").length > 0;
|
|
131
|
-
const presetProblems = preset_upgrade_reasons(config, changedPaths, presetHumanConfirmed);
|
|
132
|
-
return [
|
|
133
|
-
...shapeProblems,
|
|
134
|
-
...configProblems,
|
|
135
|
-
...evidenceProblems,
|
|
136
|
-
...state_corrupt_reasons(changeRoot),
|
|
137
|
-
...state_stale_reasons(changeRoot, status),
|
|
138
|
-
...(presetRequired ? presetProblems : presetProblems),
|
|
139
|
-
];
|
|
140
|
-
}
|
|
141
|
-
function packet_contract_problems(ctx) {
|
|
142
|
-
const { change, status, repoRoot, changeRoot, evidences } = ctx;
|
|
143
|
-
const [, configProblems] = load_config(repoRoot, changeRoot);
|
|
144
|
-
const shapeProblems = openspec_status_shape_reasons(status);
|
|
145
|
-
const evidenceProblems = evidence_schema_guard(change, changeRoot, repoRoot, evidences);
|
|
146
|
-
return [...shapeProblems, ...configProblems, ...evidenceProblems];
|
|
147
|
-
}
|
|
148
|
-
function assert_packet_context_clean(ctx) {
|
|
149
|
-
const problems = packet_contract_problems(ctx);
|
|
150
|
-
if (problems.length === 0)
|
|
151
|
-
return;
|
|
152
|
-
throw new GuardError(problems[0].message);
|
|
153
|
-
}
|
|
154
|
-
function apply_data_problems(change, decision, dataProblems) {
|
|
155
|
-
if (dataProblems.length === 0)
|
|
156
|
-
return decision;
|
|
157
|
-
if (decision.allowed) {
|
|
158
|
-
return block(change, String(decision.gate ?? "guard_error"), dataProblems, {
|
|
159
|
-
task_id: decision.task_id,
|
|
160
|
-
openspec_summary: decision.openspec_status_summary,
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
return {
|
|
164
|
-
...decision,
|
|
165
|
-
block_reasons: [...(Array.isArray(decision.block_reasons) ? decision.block_reasons : []), ...dataProblems],
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
function evaluate_workflow_decision(ctx, gateRaw, taskId) {
|
|
169
|
-
const gate = normalize_gate(gateRaw);
|
|
170
|
-
let decision;
|
|
171
|
-
if (gate === "apply_ready") {
|
|
172
|
-
decision = check_apply_ready(ctx.change, ctx.status, ctx.changeRoot, ctx.evidences);
|
|
173
|
-
}
|
|
174
|
-
else if (gate === "task_edit") {
|
|
175
|
-
decision = check_task_edit(ctx.change, ctx.status, ctx.changeRoot, ctx.evidences, taskId ?? "");
|
|
176
|
-
}
|
|
177
|
-
else if (gate === "task_complete") {
|
|
178
|
-
decision = check_task_complete(ctx.change, ctx.status, ctx.changeRoot, ctx.evidences, taskId ?? "");
|
|
179
|
-
}
|
|
180
|
-
else if (gate === "task_reopen") {
|
|
181
|
-
decision = check_task_reopen(ctx.change, ctx.status, ctx.changeRoot, ctx.evidences, taskId ?? "");
|
|
182
|
-
}
|
|
183
|
-
else if (gate === "review_complete") {
|
|
184
|
-
decision = check_review_complete(ctx.change, ctx.status, ctx.changeRoot, ctx.evidences);
|
|
185
|
-
}
|
|
186
|
-
else if (gate === "archive_ready") {
|
|
187
|
-
decision = check_archive_ready(ctx.change, ctx.status, ctx.changeRoot, ctx.evidences);
|
|
188
|
-
}
|
|
189
|
-
else {
|
|
190
|
-
decision = check_superspec_gate(ctx.change, ctx.status, ctx.changeRoot, ctx.evidences, gate);
|
|
191
|
-
}
|
|
192
|
-
return apply_data_problems(ctx.change, decision, packet_data_problems(ctx));
|
|
193
|
-
}
|
|
194
|
-
function gate_recheck_command(change, gate, taskId, diagnostic = false) {
|
|
195
|
-
const format = diagnostic ? "json" : "agent";
|
|
196
|
-
if (gate === "apply_ready")
|
|
197
|
-
return `superspec check check-apply-ready --change "${change}" --format ${format}`;
|
|
198
|
-
if (gate === "review_complete")
|
|
199
|
-
return `superspec check check-review-complete --change "${change}" --format ${format}`;
|
|
200
|
-
if (gate === "archive_ready")
|
|
201
|
-
return `superspec check check-archive-ready --change "${change}" --format ${format}`;
|
|
202
|
-
if (gate === "task_edit")
|
|
203
|
-
return `superspec check check-task-edit --change "${change}" --task-id "${taskId ?? ""}" --format ${format}`;
|
|
204
|
-
if (gate === "task_complete")
|
|
205
|
-
return `superspec check check-task-complete --change "${change}" --task-id "${taskId ?? ""}" --format ${format}`;
|
|
206
|
-
if (gate === "task_reopen")
|
|
207
|
-
return `superspec check check-task-reopen --change "${change}" --task-id "${taskId ?? ""}" --format ${format}`;
|
|
208
|
-
return `superspec check check-enter --change "${change}" --gate "${gate}" --format ${format}`;
|
|
209
|
-
}
|
|
210
|
-
function default_allowed_next_action(gate) {
|
|
211
|
-
if (gate === "review_complete")
|
|
212
|
-
return "current review gate already passes; continue with archive-ready work.";
|
|
213
|
-
if (gate === "archive_ready")
|
|
214
|
-
return "current archive gate already passes; continue with archive handoff.";
|
|
215
|
-
if (gate === "apply_ready")
|
|
216
|
-
return "current apply gate already passes; continue with OpenSpec apply instructions and task execution.";
|
|
217
|
-
if (gate === "task_edit")
|
|
218
|
-
return "current task is clear to enter implementation edits.";
|
|
219
|
-
if (gate === "task_complete")
|
|
220
|
-
return "current task has enough completion proof to be checked off.";
|
|
221
|
-
if (gate === "task_reopen")
|
|
222
|
-
return "current task is authorized for a guarded reopen.";
|
|
223
|
-
return "current gate already passes; continue with the next workflow step.";
|
|
224
|
-
}
|
|
225
|
-
function openspec_cli_surfaces_for_gate(change, gate) {
|
|
226
|
-
if (gate === "explore_complete") {
|
|
227
|
-
return [
|
|
228
|
-
"openspec list --json",
|
|
229
|
-
`openspec status --change "${change}" --json`,
|
|
230
|
-
];
|
|
231
|
-
}
|
|
232
|
-
if (gate === "apply_ready" || gate === "task_edit" || gate === "task_complete" || gate === "task_reopen") {
|
|
233
|
-
return [`openspec instructions apply --change "${change}" --json`];
|
|
234
|
-
}
|
|
235
|
-
if (gate === "proposal_reviewed" || gate === "design_complete" || gate === "invariants_reviewed" || gate === "test_contract_drafted" || gate === "tasks_complete") {
|
|
236
|
-
return [
|
|
237
|
-
`openspec status --change "${change}" --json`,
|
|
238
|
-
`openspec instructions <artifact-id> --change "${change}" --json`,
|
|
239
|
-
];
|
|
240
|
-
}
|
|
241
|
-
if (gate === "review_complete")
|
|
242
|
-
return [`openspec validate "${change}"`];
|
|
243
|
-
if (gate === "archive_ready") {
|
|
244
|
-
return [
|
|
245
|
-
`openspec validate "${change}"`,
|
|
246
|
-
`openspec archive -y "${change}"`,
|
|
247
|
-
];
|
|
248
|
-
}
|
|
249
|
-
return [];
|
|
250
|
-
}
|
|
251
|
-
function workflow_gate_refs(ctx, gate) {
|
|
252
|
-
const reviewTargets = enumerate_review_targets(gate, ctx.changeRoot);
|
|
253
|
-
if (reviewTargets) {
|
|
254
|
-
return unique_pinned_refs([...reviewTargets.keys()].map((path) => change_pinned_ref(ctx.changeRoot, path)).filter(Boolean));
|
|
255
|
-
}
|
|
256
|
-
if (gate === "apply_ready" || gate === "task_edit" || gate === "task_complete" || gate === "task_reopen") {
|
|
257
|
-
return unique_pinned_refs(collect_change_refs(ctx.changeRoot, [
|
|
258
|
-
"tasks.md",
|
|
259
|
-
"design.md",
|
|
260
|
-
".superspec/artifacts/business-invariants.md",
|
|
261
|
-
".superspec/artifacts/test-contract.md",
|
|
262
|
-
]));
|
|
263
|
-
}
|
|
264
|
-
if (gate === "review_complete" || gate === "archive_ready") {
|
|
265
|
-
const changeRefs = collect_change_refs(ctx.changeRoot, [
|
|
266
|
-
"tasks.md",
|
|
267
|
-
"design.md",
|
|
268
|
-
".superspec/artifacts/business-invariants.md",
|
|
269
|
-
".superspec/artifacts/test-contract.md",
|
|
270
|
-
]);
|
|
271
|
-
const repoRefs = dirty_repo_refs(ctx, `${gate}: failed to inspect dirty worktree`);
|
|
272
|
-
return unique_pinned_refs([...changeRefs, ...repoRefs]);
|
|
273
|
-
}
|
|
274
|
-
if (gate === "explore_complete")
|
|
275
|
-
return collect_change_refs(ctx.changeRoot, [".superspec/artifacts/discovery.md"]);
|
|
276
|
-
return [];
|
|
277
|
-
}
|
|
278
|
-
function evidence_ref_selector(changeRoot, ev) {
|
|
279
|
-
if (typeof ev._path !== "string" || !ev._path)
|
|
280
|
-
return null;
|
|
281
|
-
return change_pinned_ref(changeRoot, ev._path);
|
|
282
|
-
}
|
|
283
|
-
function decision_selectors_for_gate(ctx, gate) {
|
|
284
|
-
const selectors = [];
|
|
285
|
-
for (const ev of ctx.evidences) {
|
|
286
|
-
if (!isObject(ev) || ev._invalid)
|
|
287
|
-
continue;
|
|
288
|
-
if (normalize_gate(String(ev.gate ?? "")) !== gate)
|
|
289
|
-
continue;
|
|
290
|
-
const evidenceRef = evidence_ref_selector(ctx.changeRoot, ev);
|
|
291
|
-
if (!evidenceRef)
|
|
292
|
-
continue;
|
|
293
|
-
if (typeof ev.decision_scope_key === "string" && ev.decision_scope_key) {
|
|
294
|
-
selectors.push({
|
|
295
|
-
evidence_id: String(ev.evidence_id ?? ""),
|
|
296
|
-
decision_scope_key: ev.decision_scope_key,
|
|
297
|
-
evidence_ref: evidenceRef,
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
|
-
for (const item of Array.isArray(ev.finding_dispositions) ? ev.finding_dispositions : []) {
|
|
301
|
-
if (!isObject(item))
|
|
302
|
-
continue;
|
|
303
|
-
if (typeof item.decision_scope_key !== "string" || !item.decision_scope_key)
|
|
304
|
-
continue;
|
|
305
|
-
selectors.push({
|
|
306
|
-
evidence_id: String(ev.evidence_id ?? ""),
|
|
307
|
-
decision_scope_key: item.decision_scope_key,
|
|
308
|
-
evidence_ref: evidenceRef,
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
return selectors;
|
|
313
|
-
}
|
|
314
|
-
function finding_selectors_for_gate(ctx, gate, round) {
|
|
315
|
-
const selectors = [];
|
|
316
|
-
for (const ev of ctx.evidences) {
|
|
317
|
-
if (!isObject(ev) || ev._invalid)
|
|
318
|
-
continue;
|
|
319
|
-
if (normalize_gate(String(ev.gate ?? "")) !== gate)
|
|
320
|
-
continue;
|
|
321
|
-
if (!Array.isArray(ev.findings))
|
|
322
|
-
continue;
|
|
323
|
-
const evidenceRef = evidence_ref_selector(ctx.changeRoot, ev);
|
|
324
|
-
if (!evidenceRef)
|
|
325
|
-
continue;
|
|
326
|
-
if (round !== undefined) {
|
|
327
|
-
const roundNumber = review_round_number(gate, String(ev.review_round_id ?? ""));
|
|
328
|
-
if (roundNumber !== round)
|
|
329
|
-
continue;
|
|
330
|
-
}
|
|
331
|
-
for (const item of ev.findings) {
|
|
332
|
-
if (!isObject(item) || typeof item.finding_uid !== "string" || !item.finding_uid)
|
|
333
|
-
continue;
|
|
334
|
-
selectors.push({
|
|
335
|
-
evidence_id: String(ev.evidence_id ?? ""),
|
|
336
|
-
finding_uid: item.finding_uid,
|
|
337
|
-
evidence_ref: evidenceRef,
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
return selectors;
|
|
342
|
-
}
|
|
343
|
-
function review_complete_finding_selectors(ctx) {
|
|
344
|
-
const selectors = [];
|
|
345
|
-
for (const ev of live_pass(ctx.evidences, { gate: "review_complete", kind: "source_guidance" })) {
|
|
346
|
-
const evidenceRef = evidence_ref_selector(ctx.changeRoot, ev);
|
|
347
|
-
if (!evidenceRef)
|
|
348
|
-
continue;
|
|
349
|
-
for (const item of Array.isArray(ev.blocking_findings) ? ev.blocking_findings : []) {
|
|
350
|
-
if (!isObject(item))
|
|
351
|
-
continue;
|
|
352
|
-
const findingUid = typeof item.finding_uid === "string" && item.finding_uid
|
|
353
|
-
? item.finding_uid
|
|
354
|
-
: (typeof item.finding_id === "string" && item.finding_id
|
|
355
|
-
? `review_complete:${String(ev.evidence_id ?? "")}:${item.finding_id}`
|
|
356
|
-
: "");
|
|
357
|
-
if (!findingUid)
|
|
358
|
-
continue;
|
|
359
|
-
selectors.push({
|
|
360
|
-
evidence_id: String(ev.evidence_id ?? ""),
|
|
361
|
-
finding_uid: findingUid,
|
|
362
|
-
evidence_ref: evidenceRef,
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
return selectors;
|
|
367
|
-
}
|
|
368
|
-
const REVIEW_PACKET_ROLES_BY_GATE = {
|
|
369
|
-
explore_complete: ["critic"],
|
|
370
|
-
proposal_reviewed: ["critic"],
|
|
371
|
-
design_complete: ["architect", "critic", "test-engineer"],
|
|
372
|
-
invariants_reviewed: ["critic", "test-engineer"],
|
|
373
|
-
test_contract_drafted: ["critic", "test-engineer"],
|
|
374
|
-
tasks_complete: ["critic"],
|
|
375
|
-
review_complete: ["code-reviewer", "architect", "critic", "verifier"],
|
|
376
|
-
};
|
|
377
|
-
function validate_review_packet_gate_and_role(gate, role) {
|
|
378
|
-
if (gate !== "review_complete" && !(gate in REVIEW_TARGETS_BY_GATE)) {
|
|
379
|
-
throw new GuardError(`review-packet only supports disclosure gates and review_complete, got ${gate}`);
|
|
380
|
-
}
|
|
381
|
-
if (role === "main-thread")
|
|
382
|
-
return;
|
|
383
|
-
const allowedRoles = REVIEW_PACKET_ROLES_BY_GATE[gate] ?? [];
|
|
384
|
-
if (!allowedRoles.includes(role)) {
|
|
385
|
-
throw new GuardError(`review-packet role ${role} is not supported for gate ${gate}; expected one of ${renderList([...allowedRoles, "main-thread"])}`);
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
function workflow_packet(ctx, gateRaw, taskId) {
|
|
389
|
-
const gate = normalize_gate(gateRaw);
|
|
390
|
-
const decision = evaluate_workflow_decision(ctx, gate, taskId);
|
|
391
|
-
const reasonCodes = unique_strings((Array.isArray(decision.block_reasons) ? decision.block_reasons : []).map((item) => item.code));
|
|
392
|
-
const topBlockers = reasonCodes.slice(0, 5);
|
|
393
|
-
const nextActions = Array.isArray(decision.next_allowed_actions) ? decision.next_allowed_actions.filter((item) => typeof item === "string" && item.length > 0) : [];
|
|
394
|
-
const packet = {
|
|
395
|
-
stage: gate_route_phase(gate),
|
|
396
|
-
current_gate: gate,
|
|
397
|
-
status: decision.allowed ? "allowed" : "blocked",
|
|
398
|
-
next_action: nextActions[0] ?? default_allowed_next_action(gate),
|
|
399
|
-
next_command: gate_recheck_command(ctx.change, gate, taskId, false),
|
|
400
|
-
diagnostic_command: gate_recheck_command(ctx.change, gate, taskId, true),
|
|
401
|
-
must_read_refs: workflow_gate_refs(ctx, gate),
|
|
402
|
-
};
|
|
403
|
-
const cliSurfaces = openspec_cli_surfaces_for_gate(ctx.change, gate);
|
|
404
|
-
if (cliSurfaces.length > 0)
|
|
405
|
-
packet.openspec_cli_surfaces = cliSurfaces;
|
|
406
|
-
if (taskId)
|
|
407
|
-
packet.task_id = taskId;
|
|
408
|
-
if (!decision.allowed) {
|
|
409
|
-
packet.top_blockers = topBlockers;
|
|
410
|
-
packet.blocker_count = reasonCodes.length;
|
|
411
|
-
packet.has_more_blockers = reasonCodes.length > topBlockers.length;
|
|
412
|
-
}
|
|
413
|
-
const findings = finding_selectors_for_gate(ctx, gate);
|
|
414
|
-
if (findings.length > 0)
|
|
415
|
-
packet.must_read_verbatim_findings = findings;
|
|
416
|
-
const decisions = decision_selectors_for_gate(ctx, gate);
|
|
417
|
-
if (decisions.length > 0)
|
|
418
|
-
packet.must_read_verbatim_decisions = decisions;
|
|
419
|
-
// B (template serving): hand the model the discovery skeleton + fill rules so its output structure
|
|
420
|
-
// is uniform. Gate-scoped to explore_complete (the only gate that produces discovery.md).
|
|
421
|
-
if (gate === "explore_complete") {
|
|
422
|
-
const tpl = read_discovery_template();
|
|
423
|
-
if (tpl)
|
|
424
|
-
packet.discovery_template = tpl;
|
|
425
|
-
packet.discovery_rules = [...DISCOVERY_RULES];
|
|
426
|
-
}
|
|
427
|
-
return packet;
|
|
428
|
-
}
|
|
429
|
-
function live_output_refs(changeRoot, evidences) {
|
|
430
|
-
const refs = [];
|
|
431
|
-
for (const ev of evidences) {
|
|
432
|
-
if (!isObject(ev))
|
|
433
|
-
continue;
|
|
434
|
-
if (typeof ev.output_ref !== "string" || !ev.output_ref)
|
|
435
|
-
continue;
|
|
436
|
-
const ref = change_pinned_ref(changeRoot, ev.output_ref);
|
|
437
|
-
if (ref)
|
|
438
|
-
refs.push(ref);
|
|
439
|
-
}
|
|
440
|
-
return unique_pinned_refs(refs);
|
|
441
|
-
}
|
|
442
|
-
function disclosure_round_reviews(ctx, gate, round) {
|
|
443
|
-
return ctx.evidences.filter((ev) => isObject(ev)
|
|
444
|
-
&& !ev._invalid
|
|
445
|
-
&& normalize_gate(String(ev.gate ?? "")) === gate
|
|
446
|
-
&& Boolean(ev.agent_role)
|
|
447
|
-
&& review_round_number(gate, String(ev.review_round_id ?? "")) === round);
|
|
448
|
-
}
|
|
449
|
-
function review_complete_role_target_refs(ctx) {
|
|
450
|
-
const repoRefs = dirty_repo_refs(ctx, "review_complete: failed to inspect dirty worktree");
|
|
451
|
-
return unique_pinned_refs([
|
|
452
|
-
...repoRefs,
|
|
453
|
-
...collect_change_refs(ctx.changeRoot, [
|
|
454
|
-
"tasks.md",
|
|
455
|
-
"design.md",
|
|
456
|
-
".superspec/artifacts/business-invariants.md",
|
|
457
|
-
".superspec/artifacts/test-contract.md",
|
|
458
|
-
]),
|
|
459
|
-
]);
|
|
460
|
-
}
|
|
461
|
-
function verification_output_fields() {
|
|
462
|
-
return [...ROLE_EVIDENCE_FIELDS, ...VERIFY_EVIDENCE_REQUIRED_FIELDS, "scope_drift"];
|
|
463
|
-
}
|
|
464
|
-
function review_output_fields() {
|
|
465
|
-
return [
|
|
466
|
-
...ROLE_EVIDENCE_FIELDS,
|
|
467
|
-
"review_round_id format: <gate>-r<round>",
|
|
468
|
-
"findings[] object fields: finding_id, finding_uid, finding_type, category, summary",
|
|
469
|
-
"finding_uid format: <gate>:<evidence_id>:<finding_id>",
|
|
470
|
-
`finding_type values: ${renderList([...FINDING_TYPES].sort())}`,
|
|
471
|
-
`category values: ${renderList([...FINDING_CATEGORIES].sort())}`,
|
|
472
|
-
`material_categories values: ${renderList([...MATERIAL_CATEGORIES].sort())}`,
|
|
473
|
-
"decision_scope_key required when material_categories is non-empty",
|
|
474
|
-
"summary must be verbatim disclosure source text",
|
|
475
|
-
"acknowledged_accepted_deviation_uids must be a string array when present",
|
|
476
|
-
"findings[].supersedes_finding_uids must be a string array when present",
|
|
477
|
-
];
|
|
478
|
-
}
|
|
479
|
-
function source_guidance_output_fields() {
|
|
480
|
-
return [
|
|
481
|
-
...ROLE_EVIDENCE_FIELDS,
|
|
482
|
-
...SOURCE_GUIDANCE_REQUIRED_FIELDS,
|
|
483
|
-
...REVIEW_EVIDENCE_REQUIRED_FIELDS,
|
|
484
|
-
"blocking_findings",
|
|
485
|
-
"non_blocking_findings",
|
|
486
|
-
"finding_dispositions",
|
|
487
|
-
"rollback_targets",
|
|
488
|
-
];
|
|
489
|
-
}
|
|
490
|
-
function main_review_digest_fields() {
|
|
491
|
-
return [
|
|
492
|
-
"review_round_id",
|
|
493
|
-
"target_refs",
|
|
494
|
-
"source_review_evidence_refs",
|
|
495
|
-
"previous_digest_refs",
|
|
496
|
-
"finding_dispositions",
|
|
497
|
-
];
|
|
498
|
-
}
|
|
499
|
-
function main_adjudication_fields() {
|
|
500
|
-
return [
|
|
501
|
-
...MAIN_ADJUDICATION_REQUIRED_FIELDS,
|
|
502
|
-
"review_decision",
|
|
503
|
-
"request_changes_route",
|
|
504
|
-
"blocking_source_evidence_refs",
|
|
505
|
-
"reopen_task_ids",
|
|
506
|
-
`review_decision values: ${renderList([...MAIN_ADJUDICATION_DECISIONS])}`,
|
|
507
|
-
`request_changes_route values: ${renderList([...REQUEST_CHANGES_ROUTES])}`,
|
|
508
|
-
`claim_adjudications decision values: ${renderList([...CLAIM_ADJUDICATION_DECISIONS])}`,
|
|
509
|
-
`finding_adjudications decision values: ${renderList([...FINDING_ADJUDICATION_DECISIONS])}`,
|
|
510
|
-
];
|
|
511
|
-
}
|
|
512
|
-
function review_packet(ctx, gateRaw, role, round, requestedKind) {
|
|
513
|
-
const gate = normalize_gate(gateRaw);
|
|
514
|
-
const consumer = role === "main-thread" ? "main-thread" : "role";
|
|
515
|
-
validate_review_packet_gate_and_role(gate, role);
|
|
516
|
-
let targetRefs = [];
|
|
517
|
-
let sourceRefs = [];
|
|
518
|
-
let requiredLoadRefs = [];
|
|
519
|
-
let requiredClaimIds = [];
|
|
520
|
-
let requiredOutputKind = "review";
|
|
521
|
-
let outputContractFields = review_output_fields();
|
|
522
|
-
let stopConditions = [];
|
|
523
|
-
if (gate === "review_complete") {
|
|
524
|
-
targetRefs = review_complete_role_target_refs(ctx);
|
|
525
|
-
if (consumer === "main-thread") {
|
|
526
|
-
if (requestedKind !== undefined)
|
|
527
|
-
throw new GuardError("review-packet --kind is only supported for review_complete role lanes");
|
|
528
|
-
const sourceGuidance = live_pass(ctx.evidences, { gate: "review_complete", kind: "source_guidance" });
|
|
529
|
-
const verification = final_verification_evidences(ctx.evidences);
|
|
530
|
-
sourceRefs = unique_pinned_refs([
|
|
531
|
-
...live_output_refs(ctx.changeRoot, sourceGuidance),
|
|
532
|
-
...live_output_refs(ctx.changeRoot, verification),
|
|
533
|
-
]);
|
|
534
|
-
requiredLoadRefs = unique_pinned_refs(sourceGuidance.flatMap((ev) => Array.isArray(ev.required_load_refs)
|
|
535
|
-
? ev.required_load_refs.map((item) => evidence_pinned_ref("repo", item)).filter(Boolean)
|
|
536
|
-
: []));
|
|
537
|
-
requiredClaimIds = unique_strings(sourceGuidance.flatMap((ev) => Array.isArray(ev.required_claim_ids) ? ev.required_claim_ids.map(String) : []));
|
|
538
|
-
requiredOutputKind = "main_adjudication";
|
|
539
|
-
outputContractFields = main_adjudication_fields();
|
|
540
|
-
stopConditions = [
|
|
541
|
-
"Stop if any required_load_refs item has not been actually read by the main thread.",
|
|
542
|
-
"Stop if any required_claim_ids entry is left unadjudicated.",
|
|
543
|
-
"Stop if any blocking finding remains needs_fix.",
|
|
544
|
-
"Stop if request_changes_route='change_update'; hand off back to propose instead of forcing review_complete.",
|
|
545
|
-
];
|
|
546
|
-
}
|
|
547
|
-
else if (role === "verifier" || (role === "critic" && requestedKind === "verification_review")) {
|
|
548
|
-
if (role === "verifier" && requestedKind !== undefined && requestedKind !== "verification_review") {
|
|
549
|
-
throw new GuardError("review-packet role verifier only supports --kind verification_review");
|
|
550
|
-
}
|
|
551
|
-
sourceRefs = targetRefs;
|
|
552
|
-
requiredOutputKind = "verification_review";
|
|
553
|
-
outputContractFields = verification_output_fields();
|
|
554
|
-
stopConditions = [
|
|
555
|
-
"Stop after writing verification_review only; do not write main_adjudication.",
|
|
556
|
-
"Stop if validation or evidence gaps remain unresolved.",
|
|
557
|
-
];
|
|
558
|
-
}
|
|
559
|
-
else {
|
|
560
|
-
if (requestedKind !== undefined && requestedKind !== "source_guidance") {
|
|
561
|
-
throw new GuardError(`review-packet role ${role} cannot write ${requestedKind} for gate ${gate}`);
|
|
562
|
-
}
|
|
563
|
-
sourceRefs = targetRefs;
|
|
564
|
-
requiredOutputKind = "source_guidance";
|
|
565
|
-
outputContractFields = source_guidance_output_fields();
|
|
566
|
-
stopConditions = [
|
|
567
|
-
"Stop after writing source_guidance only; do not write main_adjudication.",
|
|
568
|
-
"Stop if reviewed_files does not cover the implementation diff.",
|
|
569
|
-
"Stop if rollback_targets are missing.",
|
|
570
|
-
];
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
else if (consumer === "main-thread") {
|
|
574
|
-
if (requestedKind !== undefined)
|
|
575
|
-
throw new GuardError("review-packet --kind is only supported for review_complete role lanes");
|
|
576
|
-
const roundReviews = disclosure_round_reviews(ctx, gate, round);
|
|
577
|
-
const targetSet = enumerate_review_targets(gate, ctx.changeRoot);
|
|
578
|
-
targetRefs = targetSet ? unique_pinned_refs([...targetSet.keys()].map((path) => change_pinned_ref(ctx.changeRoot, path)).filter(Boolean)) : [];
|
|
579
|
-
sourceRefs = live_output_refs(ctx.changeRoot, roundReviews);
|
|
580
|
-
requiredLoadRefs = sourceRefs;
|
|
581
|
-
requiredOutputKind = "main_review_digest";
|
|
582
|
-
outputContractFields = main_review_digest_fields();
|
|
583
|
-
stopConditions = [
|
|
584
|
-
"Stop if any finding from the round lacks a disposition in the digest.",
|
|
585
|
-
"Stop if any material finding lacks a user decision, standing authorization, or baseline decision anchor.",
|
|
586
|
-
"Stop if a round>1 digest is missing the deterministic ledger block.",
|
|
587
|
-
];
|
|
588
|
-
}
|
|
589
|
-
else {
|
|
590
|
-
if (requestedKind !== undefined)
|
|
591
|
-
throw new GuardError("review-packet --kind is only supported for review_complete role lanes");
|
|
592
|
-
const targetSet = enumerate_review_targets(gate, ctx.changeRoot);
|
|
593
|
-
targetRefs = targetSet ? unique_pinned_refs([...targetSet.keys()].map((path) => change_pinned_ref(ctx.changeRoot, path)).filter(Boolean)) : [];
|
|
594
|
-
sourceRefs = targetRefs;
|
|
595
|
-
requiredOutputKind = "review";
|
|
596
|
-
outputContractFields = review_output_fields();
|
|
597
|
-
stopConditions = [
|
|
598
|
-
"Stop after writing role review evidence only; do not write main_review_digest or main_adjudication.",
|
|
599
|
-
"Stop if target_refs do not pin the current gate target set.",
|
|
600
|
-
];
|
|
601
|
-
}
|
|
602
|
-
const packet = {
|
|
603
|
-
consumer,
|
|
604
|
-
gate,
|
|
605
|
-
role,
|
|
606
|
-
round,
|
|
607
|
-
target_refs: targetRefs,
|
|
608
|
-
source_refs: sourceRefs,
|
|
609
|
-
required_output_kind: requiredOutputKind,
|
|
610
|
-
output_contract_fields: outputContractFields,
|
|
611
|
-
required_review_scope: targetRefs.map((item) => item.path),
|
|
612
|
-
stop_conditions: stopConditions,
|
|
613
|
-
};
|
|
614
|
-
if (requiredLoadRefs.length > 0)
|
|
615
|
-
packet.required_load_refs = requiredLoadRefs;
|
|
616
|
-
if (requiredClaimIds.length > 0)
|
|
617
|
-
packet.required_claim_ids = requiredClaimIds;
|
|
618
|
-
const findingSelectors = gate === "review_complete"
|
|
619
|
-
? (consumer === "main-thread" ? review_complete_finding_selectors(ctx) : [])
|
|
620
|
-
: finding_selectors_for_gate(ctx, gate, round);
|
|
621
|
-
if (findingSelectors.length > 0)
|
|
622
|
-
packet.must_read_verbatim_findings = findingSelectors;
|
|
623
|
-
const decisionSelectors = decision_selectors_for_gate(ctx, gate);
|
|
624
|
-
if (decisionSelectors.length > 0)
|
|
625
|
-
packet.must_read_verbatim_decisions = decisionSelectors;
|
|
626
|
-
return packet;
|
|
627
|
-
}
|
|
628
|
-
function decision_status(decision) {
|
|
629
|
-
return decision.allowed ? "allowed" : "blocked";
|
|
630
|
-
}
|
|
631
|
-
function safe_decision_status(ctx, gate, taskId) {
|
|
632
|
-
try {
|
|
633
|
-
return decision_status(evaluate_workflow_decision(ctx, gate, taskId));
|
|
634
|
-
}
|
|
635
|
-
catch {
|
|
636
|
-
return "blocked";
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
function decision_reasons(decision) {
|
|
640
|
-
return Array.isArray(decision.block_reasons) ? decision.block_reasons : [];
|
|
641
|
-
}
|
|
642
|
-
function apply_worker_report_policy() {
|
|
643
|
-
return {
|
|
644
|
-
max_inline_report_chars: 12000,
|
|
645
|
-
artifact_ref_policy: "raw transcripts, diffs, logs, and long outputs must be returned as refs",
|
|
646
|
-
truncation_policy: "fail_closed",
|
|
647
|
-
};
|
|
648
|
-
}
|
|
649
|
-
function without_audit_metadata(value) {
|
|
650
|
-
if (Array.isArray(value))
|
|
651
|
-
return value.map(without_audit_metadata);
|
|
652
|
-
if (!isObject(value))
|
|
653
|
-
return value;
|
|
654
|
-
const out = {};
|
|
655
|
-
for (const [key, child] of Object.entries(value)) {
|
|
656
|
-
if (key === "computed_at")
|
|
657
|
-
continue;
|
|
658
|
-
out[key] = without_audit_metadata(child);
|
|
659
|
-
}
|
|
660
|
-
return out;
|
|
661
|
-
}
|
|
662
|
-
function apply_worker_stop_conditions(kind) {
|
|
663
|
-
if (kind === "apply_test") {
|
|
664
|
-
return [
|
|
665
|
-
"Stop after reporting the bounded test command result only.",
|
|
666
|
-
"Do not let the main thread run or forge formal RED/characterization/GREEN evidence.",
|
|
667
|
-
"Do not change implementation files.",
|
|
668
|
-
"Return raw logs as pinned artifact refs when output is long.",
|
|
669
|
-
];
|
|
670
|
-
}
|
|
671
|
-
if (kind === "apply_executor") {
|
|
672
|
-
return [
|
|
673
|
-
"Stay inside declared_task_write_scope.",
|
|
674
|
-
"Stop after implementation report; do not mark tasks complete.",
|
|
675
|
-
"Do not write OpenSpec artifacts or .superspec evidence directly.",
|
|
676
|
-
];
|
|
677
|
-
}
|
|
678
|
-
if (kind === "apply_code_review") {
|
|
679
|
-
return [
|
|
680
|
-
"Review only the task-local executor output and current diff.",
|
|
681
|
-
"Stop after implementation code-review report.",
|
|
682
|
-
"Do not write GREEN evidence or task completion state.",
|
|
683
|
-
];
|
|
684
|
-
}
|
|
685
|
-
return [
|
|
686
|
-
"Verify the accepted worker chain and GREEN evidence only.",
|
|
687
|
-
"Stop after verifier report.",
|
|
688
|
-
"Do not mark tasks complete or enter change-level review.",
|
|
689
|
-
];
|
|
690
|
-
}
|
|
691
|
-
function apply_worker_packet_fingerprint(packet) {
|
|
692
|
-
const { generated_at, guard_fingerprint, block_reasons, apply_worker_chain_refs, apply_worker_chain_active_refs, ...stable } = packet;
|
|
693
|
-
const blockerCodes = Array.isArray(block_reasons) ? block_reasons.map((item) => item.code).sort() : [];
|
|
694
|
-
return fingerprint_obj(without_audit_metadata({ ...stable, blocker_codes: blockerCodes }));
|
|
695
|
-
}
|
|
696
|
-
function finalize_apply_worker_packet(packet) {
|
|
697
|
-
packet.guard_fingerprint = apply_worker_packet_fingerprint(packet);
|
|
698
|
-
return packet;
|
|
699
|
-
}
|
|
700
|
-
function apply_packet_common(ctx, packetKind, taskId, workerChainContext, blockers) {
|
|
701
|
-
const applyReady = evaluate_workflow_decision(ctx, "apply_ready");
|
|
702
|
-
const packet = {
|
|
703
|
-
packet_kind: packetKind,
|
|
704
|
-
change: ctx.change,
|
|
705
|
-
task_id: taskId,
|
|
706
|
-
generated_at: new Date().toISOString(),
|
|
707
|
-
worker_chain_context: workerChainContext,
|
|
708
|
-
worker_state: blockers.length === 0 ? "ready" : "blocked",
|
|
709
|
-
apply_ready: decision_status(applyReady),
|
|
710
|
-
openspec_context_file_refs: collect_change_refs(ctx.changeRoot, [
|
|
711
|
-
"proposal.md",
|
|
712
|
-
"design.md",
|
|
713
|
-
"tasks.md",
|
|
714
|
-
".superspec/artifacts/business-invariants.md",
|
|
715
|
-
".superspec/artifacts/test-contract.md",
|
|
716
|
-
]),
|
|
717
|
-
openspec_dynamic_instructions: openspec_cli_surfaces_for_gate(ctx.change, "task_edit"),
|
|
718
|
-
task_refs: collect_change_refs(ctx.changeRoot, ["tasks.md"]),
|
|
719
|
-
test_contract_refs: collect_change_refs(ctx.changeRoot, [".superspec/artifacts/test-contract.md"]),
|
|
720
|
-
common_worker_report_policy: apply_worker_report_policy(),
|
|
721
|
-
stop_conditions: apply_worker_stop_conditions(packetKind),
|
|
722
|
-
};
|
|
723
|
-
if (blockers.length > 0) {
|
|
724
|
-
packet.blockers = unique_strings(blockers.map((item) => item.code));
|
|
725
|
-
packet.block_reasons = blockers;
|
|
726
|
-
}
|
|
727
|
-
return packet;
|
|
728
|
-
}
|
|
729
|
-
function task_lookup_blockers(ctx, taskId) {
|
|
730
|
-
const task = parse_tasks(ctx.changeRoot)[taskId];
|
|
731
|
-
if (!task)
|
|
732
|
-
return { task: null, blockers: [reason("unknown_task", `task ${taskId} not found`)] };
|
|
733
|
-
return { task, blockers: [] };
|
|
734
|
-
}
|
|
735
|
-
function active_apply_worker_chain_id(ctx, taskId) {
|
|
736
|
-
const active = active_apply_worker_chain(ctx, taskId).active;
|
|
737
|
-
return active ? String(active.apply_worker_chain_id) : null;
|
|
738
|
-
}
|
|
739
|
-
function apply_worker_chain_packet_state(ctx, taskId) {
|
|
740
|
-
const state = apply_worker_chain_lifecycle_state(ctx.repoRoot, ctx.changeRoot, ctx.evidences, taskId);
|
|
741
|
-
return { active: state.active, blockers: state.packetBlockers };
|
|
742
|
-
}
|
|
743
|
-
function active_apply_worker_chain(ctx, taskId) {
|
|
744
|
-
return apply_worker_chain_packet_state(ctx, taskId);
|
|
745
|
-
}
|
|
746
|
-
function require_active_apply_worker_chain(ctx, taskId, blockers) {
|
|
747
|
-
const state = active_apply_worker_chain(ctx, taskId);
|
|
748
|
-
blockers.push(...state.blockers);
|
|
749
|
-
const active = state.active;
|
|
750
|
-
const chainId = active ? String(active.apply_worker_chain_id) : null;
|
|
751
|
-
if (!chainId) {
|
|
752
|
-
blockers.push(reason("missing_apply_worker_chain_active", `task ${taskId} requires an active apply_worker_chain marker before downstream worker packet generation`));
|
|
753
|
-
}
|
|
754
|
-
else {
|
|
755
|
-
blockers.push(...pre_edit_evidence_ref_reasons(ctx.changeRoot, ctx.evidences, active, taskId, "apply_worker_chain_active_invalid"));
|
|
756
|
-
}
|
|
757
|
-
return chainId;
|
|
758
|
-
}
|
|
759
|
-
function generated_apply_worker_chain_id(ctx, taskId, writeScope = []) {
|
|
760
|
-
const digest = sha256_text(`${ctx.change}\n${taskId}\n${writeScope.join("\n")}`).slice("sha256:".length, "sha256:".length + 16);
|
|
761
|
-
const base = `CHAIN-${taskId}-${digest}`;
|
|
762
|
-
const used = new Set(ctx.evidences
|
|
763
|
-
.filter((ev) => isObject(ev) && ev.kind === "apply_worker_chain" && ev.task_id === taskId && typeof ev.apply_worker_chain_id === "string")
|
|
764
|
-
.map((ev) => String(ev.apply_worker_chain_id)));
|
|
765
|
-
if (!used.has(base))
|
|
766
|
-
return base;
|
|
767
|
-
for (let idx = 2; idx < 1000; idx += 1) {
|
|
768
|
-
const candidate = `${base}-R${idx}`;
|
|
769
|
-
if (!used.has(candidate))
|
|
770
|
-
return candidate;
|
|
771
|
-
}
|
|
772
|
-
return `${base}-R${sha256_text([...used].sort().join("\n")).slice("sha256:".length, "sha256:".length + 8)}`;
|
|
773
|
-
}
|
|
774
|
-
function implementation_fingerprint(ctx, declaredTaskWriteScope = []) {
|
|
775
|
-
return apply_worker_implementation_fingerprint(ctx.repoRoot, ctx.changeRoot, [], { declaredTaskWriteScope });
|
|
776
|
-
}
|
|
777
|
-
function read_json_ref(ctx, ref) {
|
|
778
|
-
const target = safe_within(ctx.changeRoot, ref);
|
|
779
|
-
if (target === null)
|
|
780
|
-
return { value: null, blockers: [reason("ref_path_unsafe", `ref escapes change root: ${ref}`, [ref])] };
|
|
781
|
-
if (!existsSync(target) || !statSync(target).isFile())
|
|
782
|
-
return { value: null, blockers: [reason("ref_not_readable", `ref is not readable: ${ref}`, [ref])] };
|
|
783
|
-
if (statSync(target).size <= 0)
|
|
784
|
-
return { value: null, blockers: [reason("ref_empty", `ref is empty: ${ref}`, [ref])] };
|
|
785
|
-
try {
|
|
786
|
-
const value = JSON.parse(readFileSync(target, "utf8"));
|
|
787
|
-
return isObject(value) ? { value, blockers: [] } : { value: null, blockers: [reason("ref_type_mismatch", `ref must contain a JSON object: ${ref}`, [ref])] };
|
|
788
|
-
}
|
|
789
|
-
catch {
|
|
790
|
-
return { value: null, blockers: [reason("ref_type_mismatch", `ref must contain valid JSON: ${ref}`, [ref])] };
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
function validate_pinned_artifact_ref_object(ctx, item, label, expected, code = "pinned_artifact_ref_invalid") {
|
|
794
|
-
return shared_pinned_artifact_ref_reasons(ctx.changeRoot, item, label, {
|
|
795
|
-
kind: expected.kind ?? "worker_report",
|
|
796
|
-
role: expected.role,
|
|
797
|
-
taskId: expected.taskId,
|
|
798
|
-
chainId: expected.chainId,
|
|
799
|
-
}, code);
|
|
800
|
-
}
|
|
801
|
-
function validate_pinned_artifact_ref(ctx, ref, expected) {
|
|
802
|
-
const loaded = read_json_ref(ctx, ref);
|
|
803
|
-
if (loaded.blockers.length > 0 || !loaded.value)
|
|
804
|
-
return { ref: null, blockers: loaded.blockers };
|
|
805
|
-
const item = loaded.value;
|
|
806
|
-
const blockers = validate_pinned_artifact_ref_object(ctx, item, ref, expected);
|
|
807
|
-
return { ref: blockers.length === 0 ? item : null, blockers };
|
|
808
|
-
}
|
|
809
|
-
function validate_worker_report_refs(ctx, refs, expected) {
|
|
810
|
-
if (refs.length === 0)
|
|
811
|
-
return { refs: [], blockers: [reason(expected.missingCode, expected.missingMessage)] };
|
|
812
|
-
const resolved = [];
|
|
813
|
-
const blockers = [];
|
|
814
|
-
for (const ref of refs) {
|
|
815
|
-
const result = validate_pinned_artifact_ref(ctx, ref, { role: expected.role, taskId: expected.taskId, chainId: expected.chainId });
|
|
816
|
-
blockers.push(...result.blockers);
|
|
817
|
-
if (result.ref)
|
|
818
|
-
resolved.push(result.ref);
|
|
819
|
-
}
|
|
820
|
-
return { refs: resolved, blockers };
|
|
821
|
-
}
|
|
822
|
-
function worker_report_origin_blockers(refItem, expectedOrigin, label) {
|
|
823
|
-
if (!isObject(refItem) || typeof expectedOrigin !== "string" || !expectedOrigin.startsWith("sha256:"))
|
|
824
|
-
return [];
|
|
825
|
-
return refItem.origin_packet_fingerprint === expectedOrigin
|
|
826
|
-
? []
|
|
827
|
-
: [reason("worker_report_origin_mismatch", `${label} origin_packet_fingerprint must match active apply_worker_chain executor_packet_fingerprint`, [])];
|
|
828
|
-
}
|
|
829
|
-
function worker_report_input_blockers(refItem, refs, label) {
|
|
830
|
-
if (!isObject(refItem))
|
|
831
|
-
return [];
|
|
832
|
-
const expected = worker_input_ref_digest(refs);
|
|
833
|
-
return refItem.input_ref_digest === expected
|
|
834
|
-
? []
|
|
835
|
-
: [reason("worker_report_input_ref_mismatch", `${label} input_ref_digest must match upstream refs`, [])];
|
|
836
|
-
}
|
|
837
|
-
function worker_report_input_digest_blockers(refItem, expectedDigest, label) {
|
|
838
|
-
if (!isObject(refItem))
|
|
839
|
-
return [];
|
|
840
|
-
return refItem.input_ref_digest === expectedDigest
|
|
841
|
-
? []
|
|
842
|
-
: [reason("worker_report_input_ref_mismatch", `${label} input_ref_digest must match active executor packet inputs`, [])];
|
|
843
|
-
}
|
|
844
|
-
function code_review_executor_binding_blockers(ctx, codeReviewRef, activeChain, taskId, chainId) {
|
|
845
|
-
const blockers = [];
|
|
846
|
-
const report = read_pinned_artifact_json(ctx.changeRoot, codeReviewRef);
|
|
847
|
-
if (!report)
|
|
848
|
-
return [reason("worker_report_content_invalid", "task_code_review_report_ref content must be readable before spawning GREEN test-runner", [])];
|
|
849
|
-
const executorReportRef = report.executor_report_ref;
|
|
850
|
-
blockers.push(...validate_pinned_artifact_ref_object(ctx, executorReportRef, "task_code_review_report_ref.executor_report_ref", {
|
|
851
|
-
role: "executor",
|
|
852
|
-
taskId,
|
|
853
|
-
chainId,
|
|
854
|
-
}));
|
|
855
|
-
if (isObject(executorReportRef)) {
|
|
856
|
-
blockers.push(...worker_report_origin_blockers(executorReportRef, activeChain?.executor_packet_fingerprint, "task_code_review_report_ref.executor_report_ref"));
|
|
857
|
-
if (activeChain) {
|
|
858
|
-
blockers.push(...worker_report_input_digest_blockers(executorReportRef, apply_worker_executor_input_ref_digest(ctx.evidences, activeChain), "task_code_review_report_ref.executor_report_ref"));
|
|
859
|
-
}
|
|
860
|
-
blockers.push(...worker_report_input_blockers(codeReviewRef, [executorReportRef], "task_code_review_report_ref"));
|
|
861
|
-
}
|
|
862
|
-
return blockers;
|
|
863
|
-
}
|
|
864
|
-
function validate_executor_worker_test_run_refs(ctx, ev, ref, taskId, chainId) {
|
|
865
|
-
return worker_test_run_reasons(ctx.changeRoot, ev, taskId, chainId, "pinned_evidence_ref_invalid")
|
|
866
|
-
.map((item) => ({ ...item, refs: [ref] }));
|
|
867
|
-
}
|
|
868
|
-
function validate_test_run_evidence_refs(ctx, refs, expected) {
|
|
869
|
-
if (refs.length === 0)
|
|
870
|
-
return { refs: [], blockers: [reason(expected.missingCode, expected.missingMessage)] };
|
|
871
|
-
const resolved = [];
|
|
872
|
-
const blockers = [];
|
|
873
|
-
for (const ref of refs) {
|
|
874
|
-
let evidenceId = ref;
|
|
875
|
-
const maybePath = safe_within(ctx.changeRoot, ref);
|
|
876
|
-
if (maybePath !== null && existsSync(maybePath) && statSync(maybePath).isFile()) {
|
|
877
|
-
const loaded = read_json_ref(ctx, ref);
|
|
878
|
-
blockers.push(...loaded.blockers);
|
|
879
|
-
if (loaded.value) {
|
|
880
|
-
if (loaded.value.kind !== "test_run")
|
|
881
|
-
blockers.push(reason("pinned_evidence_ref_invalid", `${ref}: kind must be test_run`, [ref]));
|
|
882
|
-
if (normalize_gate(String(loaded.value.gate ?? "")) !== expected.gate)
|
|
883
|
-
blockers.push(reason("pinned_evidence_ref_invalid", `${ref}: gate must be ${expected.gate}`, [ref]));
|
|
884
|
-
if (loaded.value.task_id !== expected.taskId)
|
|
885
|
-
blockers.push(reason("pinned_evidence_ref_invalid", `${ref}: task_id must be ${expected.taskId}`, [ref]));
|
|
886
|
-
if (loaded.value.phase !== undefined && loaded.value.phase !== expected.phase)
|
|
887
|
-
blockers.push(reason("pinned_evidence_ref_invalid", `${ref}: phase must be ${expected.phase}`, [ref]));
|
|
888
|
-
if (loaded.value.semantic_status !== expected.semanticStatus)
|
|
889
|
-
blockers.push(reason("pinned_evidence_ref_invalid", `${ref}: semantic_status must be ${expected.semanticStatus}`, [ref]));
|
|
890
|
-
if (expected.chainId && loaded.value.apply_worker_chain_id !== expected.chainId)
|
|
891
|
-
blockers.push(reason("pinned_evidence_ref_invalid", `${ref}: apply_worker_chain_id mismatch`, [ref]));
|
|
892
|
-
if (typeof loaded.value.evidence_id === "string" && loaded.value.evidence_id)
|
|
893
|
-
evidenceId = loaded.value.evidence_id;
|
|
894
|
-
if (expected.chainId)
|
|
895
|
-
blockers.push(...validate_executor_worker_test_run_refs(ctx, loaded.value, ref, expected.taskId, expected.chainId));
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
const ev = live_pass(ctx.evidences, { kind: "test_run", task_id: expected.taskId })
|
|
899
|
-
.find((item) => String(item.evidence_id ?? "") === evidenceId);
|
|
900
|
-
if (!ev) {
|
|
901
|
-
blockers.push(reason("pinned_evidence_ref_invalid", `test_run evidence ref is not live/pass for ${expected.taskId}: ${ref}`, [ref]));
|
|
902
|
-
continue;
|
|
903
|
-
}
|
|
904
|
-
if (normalize_gate(String(ev.gate ?? "")) !== expected.gate)
|
|
905
|
-
blockers.push(reason("pinned_evidence_ref_invalid", `${ref}: gate must be ${expected.gate}`, [ref]));
|
|
906
|
-
if (ev.semantic_status !== expected.semanticStatus)
|
|
907
|
-
blockers.push(reason("pinned_evidence_ref_invalid", `${ref}: semantic_status must be ${expected.semanticStatus}`, [ref]));
|
|
908
|
-
if (ev.phase !== undefined && ev.phase !== expected.phase)
|
|
909
|
-
blockers.push(reason("pinned_evidence_ref_invalid", `${ref}: phase must be ${expected.phase}`, [ref]));
|
|
910
|
-
if (expected.chainId) {
|
|
911
|
-
if (ev.apply_execution_chain !== "executor_worker")
|
|
912
|
-
blockers.push(reason("pinned_evidence_ref_invalid", `${ref}: apply_execution_chain must be executor_worker`, [ref]));
|
|
913
|
-
if (ev.apply_worker_chain_id !== expected.chainId)
|
|
914
|
-
blockers.push(reason("pinned_evidence_ref_invalid", `${ref}: apply_worker_chain_id mismatch`, [ref]));
|
|
915
|
-
blockers.push(...validate_executor_worker_test_run_refs(ctx, ev, ref, expected.taskId, expected.chainId));
|
|
916
|
-
}
|
|
917
|
-
resolved.push(ev);
|
|
918
|
-
}
|
|
919
|
-
return { refs: resolved, blockers };
|
|
920
|
-
}
|
|
921
|
-
function validate_active_apply_worker_chain_refs(ctx, refs, taskId, expectedChainId, expectedPacketFingerprint, expectedSourceImplementationFingerprint, expectedDeclaredTaskWriteScope, expectedPreEditEvidenceRefs, expectedActiveFields = {}) {
|
|
922
|
-
if (refs.length === 0) {
|
|
923
|
-
return {
|
|
924
|
-
refs: [],
|
|
925
|
-
blockers: [reason("missing_apply_worker_chain_ref", "apply-executor-packet --format prompt requires --apply-worker-chain-ref pointing at the recorded active apply_worker_chain evidence")],
|
|
926
|
-
};
|
|
927
|
-
}
|
|
928
|
-
const resolved = [];
|
|
929
|
-
const blockers = [];
|
|
930
|
-
const hasTerminal = ctx.evidences.some((ev) => isObject(ev)
|
|
931
|
-
&& !ev._invalid
|
|
932
|
-
&& ev.status === "pass"
|
|
933
|
-
&& ev.gate === "task_complete"
|
|
934
|
-
&& ev.kind === "apply_worker_chain"
|
|
935
|
-
&& ev.task_id === taskId
|
|
936
|
-
&& ev.apply_worker_chain_id === expectedChainId
|
|
937
|
-
&& (ev.chain_state === "closed" || ev.chain_state === "abandoned"));
|
|
938
|
-
if (hasTerminal) {
|
|
939
|
-
blockers.push(reason("apply_worker_chain_ref_invalid", `apply_worker_chain ${expectedChainId} already has a terminal marker and cannot authorize executor spawn`, [expectedChainId]));
|
|
940
|
-
}
|
|
941
|
-
const stringList = (value) => Array.isArray(value)
|
|
942
|
-
? value.map(String).filter(Boolean).sort()
|
|
943
|
-
: [];
|
|
944
|
-
const activeListBindingBlockers = (value, ref) => {
|
|
945
|
-
const fieldBlockers = [];
|
|
946
|
-
if (!deepEqual(stringList(value.declared_task_write_scope), stringList(expectedDeclaredTaskWriteScope))) {
|
|
947
|
-
fieldBlockers.push(reason("apply_worker_chain_ref_invalid", `${ref}: declared_task_write_scope mismatch`, [ref]));
|
|
948
|
-
}
|
|
949
|
-
if (!deepEqual(stringList(value.pre_edit_evidence_refs), stringList(expectedPreEditEvidenceRefs))) {
|
|
950
|
-
fieldBlockers.push(reason("apply_worker_chain_ref_invalid", `${ref}: pre_edit_evidence_refs mismatch`, [ref]));
|
|
951
|
-
}
|
|
952
|
-
for (const field of ["pre_edit_proof_kind", "apply_execution_surface", "tdd_required", "no_tdd_reason"]) {
|
|
953
|
-
if (field in expectedActiveFields && !deepEqual(value[field], expectedActiveFields[field])) {
|
|
954
|
-
fieldBlockers.push(reason("apply_worker_chain_ref_invalid", `${ref}: ${field} mismatch`, [ref]));
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
return fieldBlockers;
|
|
958
|
-
};
|
|
959
|
-
for (const ref of refs) {
|
|
960
|
-
let evidenceId = ref;
|
|
961
|
-
const maybePath = safe_within(ctx.changeRoot, ref);
|
|
962
|
-
if (maybePath !== null && existsSync(maybePath) && statSync(maybePath).isFile()) {
|
|
963
|
-
const loaded = read_json_ref(ctx, ref);
|
|
964
|
-
blockers.push(...loaded.blockers);
|
|
965
|
-
if (loaded.value) {
|
|
966
|
-
if (loaded.value.kind !== "apply_worker_chain")
|
|
967
|
-
blockers.push(reason("apply_worker_chain_ref_invalid", `${ref}: kind must be apply_worker_chain`, [ref]));
|
|
968
|
-
if (normalize_gate(String(loaded.value.gate ?? "")) !== "task_complete")
|
|
969
|
-
blockers.push(reason("apply_worker_chain_ref_invalid", `${ref}: gate must be task_complete`, [ref]));
|
|
970
|
-
if (loaded.value.task_id !== taskId)
|
|
971
|
-
blockers.push(reason("apply_worker_chain_ref_invalid", `${ref}: task_id must be ${taskId}`, [ref]));
|
|
972
|
-
if (loaded.value.chain_state !== "active")
|
|
973
|
-
blockers.push(reason("apply_worker_chain_ref_invalid", `${ref}: chain_state must be active`, [ref]));
|
|
974
|
-
if (loaded.value.apply_worker_chain_id !== expectedChainId)
|
|
975
|
-
blockers.push(reason("apply_worker_chain_ref_invalid", `${ref}: apply_worker_chain_id mismatch`, [ref]));
|
|
976
|
-
if (loaded.value.executor_packet_fingerprint !== expectedPacketFingerprint)
|
|
977
|
-
blockers.push(reason("apply_worker_chain_ref_invalid", `${ref}: executor_packet_fingerprint mismatch`, [ref]));
|
|
978
|
-
if (!fingerprint_matches(loaded.value.source_implementation_fingerprint, expectedSourceImplementationFingerprint)) {
|
|
979
|
-
blockers.push(reason("apply_worker_chain_ref_invalid", `${ref}: source_implementation_fingerprint mismatch`, [ref]));
|
|
980
|
-
}
|
|
981
|
-
blockers.push(...activeListBindingBlockers(loaded.value, ref));
|
|
982
|
-
if (typeof loaded.value.evidence_id === "string" && loaded.value.evidence_id)
|
|
983
|
-
evidenceId = loaded.value.evidence_id;
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
const ev = live_pass(ctx.evidences, { gate: "task_complete", kind: "apply_worker_chain", task_id: taskId })
|
|
987
|
-
.find((item) => String(item.evidence_id ?? "") === evidenceId);
|
|
988
|
-
if (!ev) {
|
|
989
|
-
blockers.push(reason("apply_worker_chain_ref_invalid", `apply_worker_chain ref is not live/pass for ${taskId}: ${ref}`, [ref]));
|
|
990
|
-
continue;
|
|
991
|
-
}
|
|
992
|
-
if (ev.chain_state !== "active")
|
|
993
|
-
blockers.push(reason("apply_worker_chain_ref_invalid", `${ref}: chain_state must be active`, [ref]));
|
|
994
|
-
if (ev.apply_worker_chain_id !== expectedChainId)
|
|
995
|
-
blockers.push(reason("apply_worker_chain_ref_invalid", `${ref}: apply_worker_chain_id mismatch`, [ref]));
|
|
996
|
-
if (ev.executor_packet_fingerprint !== expectedPacketFingerprint)
|
|
997
|
-
blockers.push(reason("apply_worker_chain_ref_invalid", `${ref}: executor_packet_fingerprint mismatch`, [ref]));
|
|
998
|
-
if (!fingerprint_matches(ev.source_implementation_fingerprint, expectedSourceImplementationFingerprint)) {
|
|
999
|
-
blockers.push(reason("apply_worker_chain_ref_invalid", `${ref}: source_implementation_fingerprint mismatch`, [ref]));
|
|
1000
|
-
}
|
|
1001
|
-
blockers.push(...activeListBindingBlockers(ev, ref));
|
|
1002
|
-
resolved.push(ev);
|
|
1003
|
-
}
|
|
1004
|
-
return { refs: resolved, blockers };
|
|
1005
|
-
}
|
|
1006
|
-
function pre_edit_refs_bound_to_active_chain(ctx, active, refs, label, taskId) {
|
|
1007
|
-
const allowed = new Set(Array.isArray(active?.pre_edit_evidence_refs) ? active.pre_edit_evidence_refs.map(String) : []);
|
|
1008
|
-
if (allowed.size === 0)
|
|
1009
|
-
return [reason("pinned_evidence_ref_invalid", `task ${taskId} active apply_worker_chain has no pre_edit_evidence_refs`)];
|
|
1010
|
-
const blockers = [];
|
|
1011
|
-
for (const ev of refs) {
|
|
1012
|
-
const evidenceId = String(ev.evidence_id ?? "");
|
|
1013
|
-
if (!allowed.has(evidenceId))
|
|
1014
|
-
blockers.push(reason("pinned_evidence_ref_invalid", `${label} evidence ref is not part of active apply_worker_chain pre_edit_evidence_refs: ${evidenceId}`, [evidenceId]));
|
|
1015
|
-
}
|
|
1016
|
-
blockers.push(...pre_edit_evidence_ref_reasons(ctx.changeRoot, ctx.evidences, active, taskId, "pinned_evidence_ref_invalid"));
|
|
1017
|
-
return blockers;
|
|
1018
|
-
}
|
|
1019
|
-
function validate_alternative_verification_evidence_refs(ctx, refs, taskId) {
|
|
1020
|
-
if (refs.length === 0) {
|
|
1021
|
-
return {
|
|
1022
|
-
refs: [],
|
|
1023
|
-
blockers: [reason("missing_alternative_verification_evidence_ref", "apply-verify-packet requires --alternative-verification-evidence-ref for no-TDD alternative verification")],
|
|
1024
|
-
};
|
|
1025
|
-
}
|
|
1026
|
-
const resolved = [];
|
|
1027
|
-
const blockers = [];
|
|
1028
|
-
for (const ref of refs) {
|
|
1029
|
-
let evidenceId = ref;
|
|
1030
|
-
const maybePath = safe_within(ctx.changeRoot, ref);
|
|
1031
|
-
if (maybePath !== null && existsSync(maybePath) && statSync(maybePath).isFile()) {
|
|
1032
|
-
const loaded = read_json_ref(ctx, ref);
|
|
1033
|
-
blockers.push(...loaded.blockers);
|
|
1034
|
-
if (loaded.value) {
|
|
1035
|
-
if (loaded.value.kind !== "alternative_verification" && loaded.value.kind !== "manual_verification") {
|
|
1036
|
-
blockers.push(reason("pinned_evidence_ref_invalid", `${ref}: kind must be alternative_verification or manual_verification`, [ref]));
|
|
1037
|
-
}
|
|
1038
|
-
if (normalize_gate(String(loaded.value.gate ?? "")) !== "task_complete")
|
|
1039
|
-
blockers.push(reason("pinned_evidence_ref_invalid", `${ref}: gate must be task_complete`, [ref]));
|
|
1040
|
-
if (loaded.value.task_id !== taskId)
|
|
1041
|
-
blockers.push(reason("pinned_evidence_ref_invalid", `${ref}: task_id must be ${taskId}`, [ref]));
|
|
1042
|
-
if (typeof loaded.value.evidence_id === "string" && loaded.value.evidence_id)
|
|
1043
|
-
evidenceId = loaded.value.evidence_id;
|
|
1044
|
-
}
|
|
1045
|
-
}
|
|
1046
|
-
const ev = live_pass(ctx.evidences, { task_id: taskId })
|
|
1047
|
-
.find((item) => String(item.evidence_id ?? "") === evidenceId && (item.kind === "alternative_verification" || item.kind === "manual_verification"));
|
|
1048
|
-
if (!ev) {
|
|
1049
|
-
blockers.push(reason("pinned_evidence_ref_invalid", `alternative verification evidence ref is not live/pass for ${taskId}: ${ref}`, [ref]));
|
|
1050
|
-
continue;
|
|
1051
|
-
}
|
|
1052
|
-
if (normalize_gate(String(ev.gate ?? "")) !== "task_complete")
|
|
1053
|
-
blockers.push(reason("pinned_evidence_ref_invalid", `${ref}: gate must be task_complete`, [ref]));
|
|
1054
|
-
resolved.push(ev);
|
|
1055
|
-
}
|
|
1056
|
-
return { refs: resolved, blockers };
|
|
1057
|
-
}
|
|
1058
|
-
function active_chain_verifier_input_ref(active) {
|
|
1059
|
-
return {
|
|
1060
|
-
kind: "apply_worker_chain",
|
|
1061
|
-
evidence_id: active?.evidence_id,
|
|
1062
|
-
task_id: active?.task_id,
|
|
1063
|
-
apply_worker_chain_id: active?.apply_worker_chain_id,
|
|
1064
|
-
pre_edit_proof_kind: active?.pre_edit_proof_kind ?? "red_or_characterization",
|
|
1065
|
-
pre_edit_evidence_refs: Array.isArray(active?.pre_edit_evidence_refs) ? active.pre_edit_evidence_refs.map(String).filter(Boolean).sort() : [],
|
|
1066
|
-
...(active?.apply_execution_surface !== undefined ? { apply_execution_surface: active.apply_execution_surface } : {}),
|
|
1067
|
-
...(active?.tdd_required !== undefined ? { tdd_required: active.tdd_required } : {}),
|
|
1068
|
-
...(active?.no_tdd_reason !== undefined ? { no_tdd_reason: active.no_tdd_reason } : {}),
|
|
1069
|
-
};
|
|
1070
|
-
}
|
|
1071
|
-
function alternative_evidence_input_ref(ev) {
|
|
1072
|
-
return {
|
|
1073
|
-
kind: ev.kind,
|
|
1074
|
-
evidence_id: ev.evidence_id,
|
|
1075
|
-
gate: ev.gate,
|
|
1076
|
-
task_id: ev.task_id,
|
|
1077
|
-
};
|
|
1078
|
-
}
|
|
1079
|
-
function write_scope_blockers(taskId, writeScope) {
|
|
1080
|
-
const blockers = [];
|
|
1081
|
-
if (writeScope.length === 0) {
|
|
1082
|
-
blockers.push(reason("missing_write_scope", `task ${taskId} requires declared write_scope before executor worker handoff`));
|
|
1083
|
-
return blockers;
|
|
1084
|
-
}
|
|
1085
|
-
const forbidden = writeScope.filter((item) => item === "."
|
|
1086
|
-
|| item === ""
|
|
1087
|
-
|| item.startsWith("/")
|
|
1088
|
-
|| item.includes("..")
|
|
1089
|
-
|| item === "proposal.md"
|
|
1090
|
-
|| item === "design.md"
|
|
1091
|
-
|| item === "tasks.md"
|
|
1092
|
-
|| item.startsWith("specs/")
|
|
1093
|
-
|| item.startsWith(".superspec/"));
|
|
1094
|
-
if (forbidden.length > 0) {
|
|
1095
|
-
blockers.push(reason("unsafe_write_scope", `task ${taskId} has unsafe executor write_scope entries: ${renderList(forbidden)}`, forbidden));
|
|
1096
|
-
}
|
|
1097
|
-
return blockers;
|
|
1098
|
-
}
|
|
1099
|
-
function pre_edit_evidence_refs(ctx, taskId) {
|
|
1100
|
-
return live_pass(ctx.evidences, { gate: "task_edit", kind: "test_run", task_id: taskId })
|
|
1101
|
-
.filter((ev) => ev.semantic_status === "expected_failure" || ev.semantic_status === "expected_success")
|
|
1102
|
-
.filter((ev) => pre_edit_worker_test_run_reasons(ctx.changeRoot, ev, taskId).length === 0)
|
|
1103
|
-
.map((ev) => String(ev.evidence_id ?? ""))
|
|
1104
|
-
.filter(Boolean)
|
|
1105
|
-
.sort();
|
|
1106
|
-
}
|
|
1107
|
-
function apply_task_context_fields(ctx, taskId, task, writeScope, expectedGuardRefs = []) {
|
|
1108
|
-
const executionSurface = task ? task_apply_execution_surface(task) : "";
|
|
1109
|
-
return {
|
|
1110
|
-
task_content_ref: change_pinned_ref(ctx.changeRoot, "tasks.md"),
|
|
1111
|
-
apply_execution_surface: executionSurface,
|
|
1112
|
-
apply_execution_surface_source: task?.attrs.apply_execution_surface ? "explicit" : "default",
|
|
1113
|
-
apply_execution_surface_policy: {
|
|
1114
|
-
implementation_requires_executor_worker_chain: true,
|
|
1115
|
-
runtime_config_requires_executor_worker_chain: true,
|
|
1116
|
-
docs_generated_allows_direct_alternative_verification: true,
|
|
1117
|
-
no_code_requires_empty_write_scope: true,
|
|
1118
|
-
},
|
|
1119
|
-
task_acceptance_refs: task ? splitList(task.attrs.requirement_refs ?? "") : [],
|
|
1120
|
-
task_invariant_refs: task ? splitList(task.attrs.invariant_refs ?? "") : [],
|
|
1121
|
-
task_test_refs: task ? splitList(task.attrs.test_refs ?? "") : [],
|
|
1122
|
-
business_invariant_refs: collect_change_refs(ctx.changeRoot, [".superspec/artifacts/business-invariants.md"]),
|
|
1123
|
-
test_contract_refs: collect_change_refs(ctx.changeRoot, [".superspec/artifacts/test-contract.md"]),
|
|
1124
|
-
pre_edit_evidence_refs: pre_edit_evidence_refs(ctx, taskId),
|
|
1125
|
-
current_worktree_refs: dirty_repo_refs(ctx, `apply worker current worktree refs for ${taskId}`),
|
|
1126
|
-
protected_path_refs: apply_worker_protected_path_refs(ctx.repoRoot, ctx.changeRoot, expectedGuardRefs),
|
|
1127
|
-
scope_diff_review_policy: {
|
|
1128
|
-
declared_task_write_scope: writeScope,
|
|
1129
|
-
forbidden_paths: ["proposal.md", "design.md", "tasks.md", "specs/", ".superspec/"],
|
|
1130
|
-
require_scope_verdict: true,
|
|
1131
|
-
mismatch_behavior: "fail_closed",
|
|
1132
|
-
},
|
|
1133
|
-
};
|
|
1134
|
-
}
|
|
1135
|
-
function apply_test_packet(ctx, args) {
|
|
1136
|
-
const taskId = args.task_id ?? "";
|
|
1137
|
-
const testId = args.test_id ?? "";
|
|
1138
|
-
const phase = args.phase ?? "red";
|
|
1139
|
-
const blockers = [];
|
|
1140
|
-
const applyReady = evaluate_workflow_decision(ctx, "apply_ready");
|
|
1141
|
-
if (!applyReady.allowed)
|
|
1142
|
-
blockers.push(...decision_reasons(applyReady));
|
|
1143
|
-
const { task, blockers: taskBlockers } = task_lookup_blockers(ctx, taskId);
|
|
1144
|
-
blockers.push(...taskBlockers);
|
|
1145
|
-
const declaredTests = new Set(task ? splitList(task.attrs.test_refs ?? "") : []);
|
|
1146
|
-
if (task && !declaredTests.has(testId)) {
|
|
1147
|
-
blockers.push(reason("test_not_declared_for_task", `test ${testId} is not declared in task ${taskId} test_refs`, [testId]));
|
|
1148
|
-
}
|
|
1149
|
-
const [config, configProblems] = load_config(ctx.repoRoot, ctx.changeRoot);
|
|
1150
|
-
blockers.push(...configProblems);
|
|
1151
|
-
const command = resolve_test_contract_command(ctx.changeRoot, testId, config);
|
|
1152
|
-
blockers.push(...command.blockers);
|
|
1153
|
-
const invariantRefs = [...(test_contract_invariant_refs_by_test(ctx.changeRoot).get(testId) ?? new Set())].sort();
|
|
1154
|
-
if (invariantRefs.length === 0)
|
|
1155
|
-
blockers.push(reason("missing_invariant_refs", `test ${testId} requires invariant refs in test contract`, [testId]));
|
|
1156
|
-
if (phase === "green") {
|
|
1157
|
-
const taskEdit = evaluate_workflow_decision(ctx, "task_edit", taskId);
|
|
1158
|
-
if (!taskEdit.allowed)
|
|
1159
|
-
blockers.push(...decision_reasons(taskEdit));
|
|
1160
|
-
if ((args.task_code_review_report_refs ?? []).length === 0) {
|
|
1161
|
-
blockers.push(reason("missing_task_code_review_report_ref", "apply-test-packet --phase green requires --task-code-review-report-ref before spawning test-runner"));
|
|
1162
|
-
}
|
|
1163
|
-
}
|
|
1164
|
-
else if ((args.task_code_review_report_refs ?? []).length > 0) {
|
|
1165
|
-
blockers.push(reason("unexpected_task_code_review_report_ref", "apply-test-packet --phase red/characterization must not consume --task-code-review-report-ref"));
|
|
1166
|
-
}
|
|
1167
|
-
const workerChainContext = phase === "green" ? "executor_worker" : "none";
|
|
1168
|
-
const packet = apply_packet_common(ctx, "apply_test", taskId, workerChainContext, blockers);
|
|
1169
|
-
packet.test_id = testId;
|
|
1170
|
-
packet.phase = phase;
|
|
1171
|
-
packet.expected_semantic_status = phase === "red" ? "expected_failure" : "expected_success";
|
|
1172
|
-
packet.test_runner_report_required_fields = [...APPLY_TEST_RUNNER_REPORT_REQUIRED_FIELDS];
|
|
1173
|
-
packet.required_invariant_refs = invariantRefs;
|
|
1174
|
-
packet.allowed_test_command = command.command;
|
|
1175
|
-
packet.test_command_source = command.source;
|
|
1176
|
-
packet.expected_worktree_side_effects = [];
|
|
1177
|
-
if (command.command_ref)
|
|
1178
|
-
packet.test_command_ref = command.command_ref;
|
|
1179
|
-
if (command.expected_failure_signature)
|
|
1180
|
-
packet.expected_failure_signature = command.expected_failure_signature;
|
|
1181
|
-
if (command.expected_failure_classifier)
|
|
1182
|
-
packet.expected_failure_classifier = command.expected_failure_classifier;
|
|
1183
|
-
if (workerChainContext === "executor_worker") {
|
|
1184
|
-
const chainId = require_active_apply_worker_chain(ctx, taskId, blockers);
|
|
1185
|
-
if (chainId) {
|
|
1186
|
-
const reviewRefs = validate_worker_report_refs(ctx, args.task_code_review_report_refs ?? [], {
|
|
1187
|
-
role: "code-reviewer",
|
|
1188
|
-
taskId,
|
|
1189
|
-
chainId,
|
|
1190
|
-
missingCode: "missing_task_code_review_report_ref",
|
|
1191
|
-
missingMessage: "apply-test-packet --phase green requires --task-code-review-report-ref before spawning test-runner",
|
|
1192
|
-
});
|
|
1193
|
-
blockers.push(...reviewRefs.blockers);
|
|
1194
|
-
packet.apply_worker_chain_id = chainId;
|
|
1195
|
-
packet.task_code_review_report_pinned_refs = reviewRefs.refs;
|
|
1196
|
-
packet.task_code_review_report_refs = args.task_code_review_report_refs ?? [];
|
|
1197
|
-
const activeChain = active_apply_worker_chain(ctx, taskId).active;
|
|
1198
|
-
if (reviewRefs.refs[0]) {
|
|
1199
|
-
const reviewReport = read_pinned_artifact_json(ctx.changeRoot, reviewRefs.refs[0]);
|
|
1200
|
-
if (isObject(reviewReport?.executor_report_ref))
|
|
1201
|
-
packet.executor_report_pinned_refs = [reviewReport.executor_report_ref];
|
|
1202
|
-
blockers.push(...code_review_executor_binding_blockers(ctx, reviewRefs.refs[0], activeChain, taskId, chainId));
|
|
1203
|
-
}
|
|
1204
|
-
const activeScope = Array.isArray(activeChain?.declared_task_write_scope) ? activeChain.declared_task_write_scope.map(String).filter(Boolean) : [];
|
|
1205
|
-
const currentImplementation = apply_worker_implementation_fingerprint(ctx.repoRoot, ctx.changeRoot, reviewRefs.refs, { declaredTaskWriteScope: activeScope });
|
|
1206
|
-
packet.post_code_review_worktree_fingerprint = currentImplementation;
|
|
1207
|
-
const observedDigest = fingerprint_digest(reviewRefs.refs[0]?.observed_implementation_fingerprint);
|
|
1208
|
-
if (!observedDigest) {
|
|
1209
|
-
blockers.push(reason("post_code_review_worktree_fingerprint_missing", "apply-test-packet --phase green requires code-review report observed_implementation_fingerprint"));
|
|
1210
|
-
}
|
|
1211
|
-
else if (observedDigest !== currentImplementation.fingerprint_digest) {
|
|
1212
|
-
blockers.push(reason("post_code_review_worktree_fingerprint_mismatch", "apply-test-packet --phase green requires code-review report observed implementation fingerprint to match current worktree"));
|
|
1213
|
-
}
|
|
1214
|
-
}
|
|
1215
|
-
else if ((args.task_code_review_report_refs ?? []).length === 0) {
|
|
1216
|
-
blockers.push(reason("missing_task_code_review_report_ref", "apply-test-packet --phase green requires --task-code-review-report-ref before spawning test-runner"));
|
|
1217
|
-
}
|
|
1218
|
-
}
|
|
1219
|
-
packet.worker_state = blockers.length === 0 ? "ready" : "blocked";
|
|
1220
|
-
if (blockers.length > 0) {
|
|
1221
|
-
packet.blockers = unique_strings(blockers.map((item) => item.code));
|
|
1222
|
-
packet.block_reasons = blockers;
|
|
1223
|
-
}
|
|
1224
|
-
return finalize_apply_worker_packet(packet);
|
|
1225
|
-
}
|
|
1226
|
-
function apply_executor_packet(ctx, args) {
|
|
1227
|
-
const taskId = args.task_id ?? "";
|
|
1228
|
-
const blockers = [];
|
|
1229
|
-
const applyReady = evaluate_workflow_decision(ctx, "apply_ready");
|
|
1230
|
-
if (!applyReady.allowed)
|
|
1231
|
-
blockers.push(...decision_reasons(applyReady));
|
|
1232
|
-
const taskEdit = evaluate_workflow_decision(ctx, "task_edit", taskId);
|
|
1233
|
-
if (!taskEdit.allowed)
|
|
1234
|
-
blockers.push(...decision_reasons(taskEdit));
|
|
1235
|
-
const { task, blockers: taskBlockers } = task_lookup_blockers(ctx, taskId);
|
|
1236
|
-
blockers.push(...taskBlockers);
|
|
1237
|
-
const writeScope = task ? splitList(task.attrs.write_scope ?? "") : [];
|
|
1238
|
-
const executionSurface = task ? task_apply_execution_surface(task) : "";
|
|
1239
|
-
const tddRequired = task ? (task.attrs.tdd_required ?? "true").toLowerCase() !== "false" : true;
|
|
1240
|
-
const noTddReason = task?.attrs.no_tdd_reason;
|
|
1241
|
-
const preEditProofKind = tddRequired ? "red_or_characterization" : "no_tdd_declared";
|
|
1242
|
-
const activePreEditRefs = preEditProofKind === "no_tdd_declared" ? [] : pre_edit_evidence_refs(ctx, taskId);
|
|
1243
|
-
if (task) {
|
|
1244
|
-
blockers.push(...task_apply_execution_surface_reasons(taskId, task));
|
|
1245
|
-
if (executionSurface === "implementation" || executionSurface === "runtime_config") {
|
|
1246
|
-
if (!tddRequired && !NO_TDD_REASONS.has(String(noTddReason ?? ""))) {
|
|
1247
|
-
blockers.push(reason("invalid_no_tdd_reason", `task ${taskId}: no_tdd_reason=${String(noTddReason ?? "")}`));
|
|
1248
|
-
}
|
|
1249
|
-
blockers.push(...write_scope_blockers(taskId, writeScope));
|
|
1250
|
-
}
|
|
1251
|
-
else {
|
|
1252
|
-
blockers.push(reason("executor_handoff_not_required", `task ${taskId} apply_execution_surface=${executionSurface} does not use executor-worker implementation handoff`));
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
1255
|
-
else {
|
|
1256
|
-
blockers.push(...write_scope_blockers(taskId, writeScope));
|
|
1257
|
-
}
|
|
1258
|
-
blockers.push(...apply_worker_chain_packet_state(ctx, taskId).blockers);
|
|
1259
|
-
const packet = apply_packet_common(ctx, "apply_executor", taskId, "executor_worker", blockers);
|
|
1260
|
-
const chainId = active_apply_worker_chain_id(ctx, taskId) ?? generated_apply_worker_chain_id(ctx, taskId, writeScope);
|
|
1261
|
-
packet.worker_chain_context = "executor_worker";
|
|
1262
|
-
packet.apply_worker_chain_id = chainId;
|
|
1263
|
-
packet.apply_execution_surface = executionSurface;
|
|
1264
|
-
packet.declared_task_write_scope = writeScope;
|
|
1265
|
-
packet.task_edit = decision_status(taskEdit);
|
|
1266
|
-
packet.task_complete = safe_decision_status(ctx, "task_complete", taskId);
|
|
1267
|
-
Object.assign(packet, apply_task_context_fields(ctx, taskId, task, writeScope));
|
|
1268
|
-
packet.required_test_refs = task ? splitList(task.attrs.test_refs ?? "") : [];
|
|
1269
|
-
packet.executor_report_required_fields = [...APPLY_EXECUTOR_REPORT_REQUIRED_FIELDS];
|
|
1270
|
-
packet.executor_runtime_policy = {
|
|
1271
|
-
initial_wait_seconds: 60,
|
|
1272
|
-
max_running_seconds: 7200,
|
|
1273
|
-
status_request_after_seconds: 300,
|
|
1274
|
-
max_silent_seconds: 900,
|
|
1275
|
-
stale_after_seconds: 1800,
|
|
1276
|
-
};
|
|
1277
|
-
packet.chain_activation_template = {
|
|
1278
|
-
kind: "apply_worker_chain",
|
|
1279
|
-
gate: "task_complete",
|
|
1280
|
-
status: "pass",
|
|
1281
|
-
task_id: taskId,
|
|
1282
|
-
apply_worker_chain_id: chainId,
|
|
1283
|
-
chain_state: "active",
|
|
1284
|
-
executor_packet_fingerprint: "",
|
|
1285
|
-
source_implementation_fingerprint: implementation_fingerprint(ctx, writeScope),
|
|
1286
|
-
declared_task_write_scope: writeScope,
|
|
1287
|
-
pre_edit_proof_kind: preEditProofKind,
|
|
1288
|
-
apply_execution_surface: executionSurface,
|
|
1289
|
-
tdd_required: tddRequired,
|
|
1290
|
-
...(tddRequired ? {} : { no_tdd_reason: noTddReason }),
|
|
1291
|
-
pre_edit_evidence_refs: activePreEditRefs,
|
|
1292
|
-
};
|
|
1293
|
-
if (args.packet_format === "prompt") {
|
|
1294
|
-
const expectedPacketFingerprint = apply_worker_packet_fingerprint(packet);
|
|
1295
|
-
const activeRefs = validate_active_apply_worker_chain_refs(ctx, args.apply_worker_chain_refs ?? [], taskId, chainId, expectedPacketFingerprint, packet.chain_activation_template.source_implementation_fingerprint, packet.chain_activation_template.declared_task_write_scope, packet.chain_activation_template.pre_edit_evidence_refs, {
|
|
1296
|
-
pre_edit_proof_kind: packet.chain_activation_template.pre_edit_proof_kind,
|
|
1297
|
-
apply_execution_surface: packet.chain_activation_template.apply_execution_surface,
|
|
1298
|
-
tdd_required: packet.chain_activation_template.tdd_required,
|
|
1299
|
-
...(packet.chain_activation_template.no_tdd_reason !== undefined ? { no_tdd_reason: packet.chain_activation_template.no_tdd_reason } : {}),
|
|
1300
|
-
});
|
|
1301
|
-
blockers.push(...activeRefs.blockers);
|
|
1302
|
-
packet.apply_worker_chain_refs = args.apply_worker_chain_refs ?? [];
|
|
1303
|
-
packet.apply_worker_chain_active_refs = activeRefs.refs;
|
|
1304
|
-
}
|
|
1305
|
-
packet.worker_state = blockers.length === 0 ? "ready" : "blocked";
|
|
1306
|
-
if (blockers.length > 0) {
|
|
1307
|
-
packet.blockers = unique_strings(blockers.map((item) => item.code));
|
|
1308
|
-
packet.block_reasons = blockers;
|
|
1309
|
-
}
|
|
1310
|
-
finalize_apply_worker_packet(packet);
|
|
1311
|
-
packet.chain_activation_template.executor_packet_fingerprint = packet.guard_fingerprint;
|
|
1312
|
-
return packet;
|
|
1313
|
-
}
|
|
1314
|
-
function apply_code_review_packet(ctx, args) {
|
|
1315
|
-
const taskId = args.task_id ?? "";
|
|
1316
|
-
const blockers = [];
|
|
1317
|
-
const applyReady = evaluate_workflow_decision(ctx, "apply_ready");
|
|
1318
|
-
if (!applyReady.allowed)
|
|
1319
|
-
blockers.push(...decision_reasons(applyReady));
|
|
1320
|
-
const taskEdit = evaluate_workflow_decision(ctx, "task_edit", taskId);
|
|
1321
|
-
if (!taskEdit.allowed)
|
|
1322
|
-
blockers.push(...decision_reasons(taskEdit));
|
|
1323
|
-
const { task, blockers: taskBlockers } = task_lookup_blockers(ctx, taskId);
|
|
1324
|
-
blockers.push(...taskBlockers);
|
|
1325
|
-
const writeScope = task ? splitList(task.attrs.write_scope ?? "") : [];
|
|
1326
|
-
blockers.push(...write_scope_blockers(taskId, writeScope));
|
|
1327
|
-
const chainId = require_active_apply_worker_chain(ctx, taskId, blockers);
|
|
1328
|
-
const activeChain = active_apply_worker_chain(ctx, taskId).active;
|
|
1329
|
-
if (chainId) {
|
|
1330
|
-
const executorRefs = validate_worker_report_refs(ctx, args.executor_report_refs ?? [], {
|
|
1331
|
-
role: "executor",
|
|
1332
|
-
taskId,
|
|
1333
|
-
chainId,
|
|
1334
|
-
missingCode: "missing_executor_report_ref",
|
|
1335
|
-
missingMessage: "apply-code-review-packet requires --executor-report-ref",
|
|
1336
|
-
});
|
|
1337
|
-
blockers.push(...executorRefs.blockers);
|
|
1338
|
-
if (executorRefs.refs[0]) {
|
|
1339
|
-
blockers.push(...worker_report_origin_blockers(executorRefs.refs[0], activeChain?.executor_packet_fingerprint, "executor_report_ref"));
|
|
1340
|
-
if (activeChain) {
|
|
1341
|
-
blockers.push(...worker_report_input_digest_blockers(executorRefs.refs[0], apply_worker_executor_input_ref_digest(ctx.evidences, activeChain), "executor_report_ref"));
|
|
1342
|
-
}
|
|
1343
|
-
}
|
|
1344
|
-
const packet = apply_packet_common(ctx, "apply_code_review", taskId, "executor_worker", blockers);
|
|
1345
|
-
packet.apply_worker_chain_id = chainId;
|
|
1346
|
-
packet.declared_task_write_scope = writeScope;
|
|
1347
|
-
packet.executor_report_pinned_refs = executorRefs.refs;
|
|
1348
|
-
packet.executor_report_refs = args.executor_report_refs ?? [];
|
|
1349
|
-
packet.expected_executor_origin_packet_fingerprint = activeChain?.executor_packet_fingerprint;
|
|
1350
|
-
if (activeChain)
|
|
1351
|
-
packet.expected_executor_input_ref_digest = apply_worker_executor_input_ref_digest(ctx.evidences, activeChain);
|
|
1352
|
-
packet.executor_report_required_fields = [...APPLY_EXECUTOR_REPORT_REQUIRED_FIELDS];
|
|
1353
|
-
packet.code_review_report_required_fields = [...APPLY_CODE_REVIEW_REPORT_REQUIRED_FIELDS];
|
|
1354
|
-
packet.task_edit = decision_status(taskEdit);
|
|
1355
|
-
Object.assign(packet, apply_task_context_fields(ctx, taskId, task, writeScope, executorRefs.refs));
|
|
1356
|
-
packet.code_review_checks = [
|
|
1357
|
-
"executor_report_matches_current_diff",
|
|
1358
|
-
"changed_files_within_declared_task_write_scope",
|
|
1359
|
-
"protected_paths_unchanged",
|
|
1360
|
-
"test_and_invariant_mapping_supported",
|
|
1361
|
-
"suggest_green_test_ids",
|
|
1362
|
-
];
|
|
1363
|
-
packet.worker_state = blockers.length === 0 ? "ready" : "blocked";
|
|
1364
|
-
if (blockers.length > 0) {
|
|
1365
|
-
packet.blockers = unique_strings(blockers.map((item) => item.code));
|
|
1366
|
-
packet.block_reasons = blockers;
|
|
1367
|
-
}
|
|
1368
|
-
return finalize_apply_worker_packet(packet);
|
|
1369
|
-
}
|
|
1370
|
-
const packet = apply_packet_common(ctx, "apply_code_review", taskId, "executor_worker", blockers);
|
|
1371
|
-
packet.declared_task_write_scope = writeScope;
|
|
1372
|
-
packet.executor_report_refs = args.executor_report_refs ?? [];
|
|
1373
|
-
packet.executor_report_required_fields = [...APPLY_EXECUTOR_REPORT_REQUIRED_FIELDS];
|
|
1374
|
-
packet.code_review_report_required_fields = [...APPLY_CODE_REVIEW_REPORT_REQUIRED_FIELDS];
|
|
1375
|
-
packet.task_edit = decision_status(taskEdit);
|
|
1376
|
-
Object.assign(packet, apply_task_context_fields(ctx, taskId, task, writeScope));
|
|
1377
|
-
return finalize_apply_worker_packet(packet);
|
|
1378
|
-
}
|
|
1379
|
-
function apply_verify_packet(ctx, args) {
|
|
1380
|
-
const taskId = args.task_id ?? "";
|
|
1381
|
-
const blockers = [];
|
|
1382
|
-
const applyReady = evaluate_workflow_decision(ctx, "apply_ready");
|
|
1383
|
-
if (!applyReady.allowed)
|
|
1384
|
-
blockers.push(...decision_reasons(applyReady));
|
|
1385
|
-
const taskEdit = evaluate_workflow_decision(ctx, "task_edit", taskId);
|
|
1386
|
-
if (!taskEdit.allowed)
|
|
1387
|
-
blockers.push(...decision_reasons(taskEdit));
|
|
1388
|
-
const { task, blockers: taskBlockers } = task_lookup_blockers(ctx, taskId);
|
|
1389
|
-
blockers.push(...taskBlockers);
|
|
1390
|
-
const writeScope = task ? splitList(task.attrs.write_scope ?? "") : [];
|
|
1391
|
-
const executionSurface = task ? task_apply_execution_surface(task) : "";
|
|
1392
|
-
const tddRequired = task ? (task.attrs.tdd_required ?? "true").toLowerCase() !== "false" : true;
|
|
1393
|
-
blockers.push(...write_scope_blockers(taskId, writeScope));
|
|
1394
|
-
const chainId = require_active_apply_worker_chain(ctx, taskId, blockers);
|
|
1395
|
-
const activeChain = active_apply_worker_chain(ctx, taskId).active;
|
|
1396
|
-
const packet = apply_packet_common(ctx, "apply_verify", taskId, "executor_worker", blockers);
|
|
1397
|
-
const completionProofKind = (args.alternative_verification_evidence_refs ?? []).length > 0 ? "alternative_verification" : "green_tests";
|
|
1398
|
-
if (chainId) {
|
|
1399
|
-
const executorRefs = validate_worker_report_refs(ctx, args.executor_report_refs ?? [], {
|
|
1400
|
-
role: "executor",
|
|
1401
|
-
taskId,
|
|
1402
|
-
chainId,
|
|
1403
|
-
missingCode: "missing_executor_report_ref",
|
|
1404
|
-
missingMessage: "apply-verify-packet requires --executor-report-ref",
|
|
1405
|
-
});
|
|
1406
|
-
const codeReviewRefs = validate_worker_report_refs(ctx, args.task_code_review_report_refs ?? [], {
|
|
1407
|
-
role: "code-reviewer",
|
|
1408
|
-
taskId,
|
|
1409
|
-
chainId,
|
|
1410
|
-
missingCode: "missing_task_code_review_report_ref",
|
|
1411
|
-
missingMessage: "apply-verify-packet requires --task-code-review-report-ref",
|
|
1412
|
-
});
|
|
1413
|
-
const greenRefs = validate_test_run_evidence_refs(ctx, args.green_test_run_evidence_refs ?? [], {
|
|
1414
|
-
taskId,
|
|
1415
|
-
gate: "task_complete",
|
|
1416
|
-
phase: "green",
|
|
1417
|
-
semanticStatus: "expected_success",
|
|
1418
|
-
chainId,
|
|
1419
|
-
missingCode: "missing_green_test_run_evidence_ref",
|
|
1420
|
-
missingMessage: "apply-verify-packet requires --green-test-run-evidence-ref",
|
|
1421
|
-
});
|
|
1422
|
-
const redRefs = validate_test_run_evidence_refs(ctx, args.red_test_run_evidence_refs ?? [], {
|
|
1423
|
-
taskId,
|
|
1424
|
-
gate: "task_edit",
|
|
1425
|
-
phase: "red",
|
|
1426
|
-
semanticStatus: "expected_failure",
|
|
1427
|
-
chainId: null,
|
|
1428
|
-
missingCode: "missing_red_test_run_evidence_ref",
|
|
1429
|
-
missingMessage: "apply-verify-packet requires --red-test-run-evidence-ref",
|
|
1430
|
-
});
|
|
1431
|
-
const characterizationRefs = validate_test_run_evidence_refs(ctx, args.characterization_test_run_evidence_refs ?? [], {
|
|
1432
|
-
taskId,
|
|
1433
|
-
gate: "task_edit",
|
|
1434
|
-
phase: "characterization",
|
|
1435
|
-
semanticStatus: "expected_success",
|
|
1436
|
-
chainId: null,
|
|
1437
|
-
missingCode: "missing_characterization_test_run_evidence_ref",
|
|
1438
|
-
missingMessage: "apply-verify-packet requires --characterization-test-run-evidence-ref",
|
|
1439
|
-
});
|
|
1440
|
-
const alternativeRefs = validate_alternative_verification_evidence_refs(ctx, args.alternative_verification_evidence_refs ?? [], taskId);
|
|
1441
|
-
blockers.push(...executorRefs.blockers, ...codeReviewRefs.blockers);
|
|
1442
|
-
if (executorRefs.refs[0]) {
|
|
1443
|
-
blockers.push(...worker_report_origin_blockers(executorRefs.refs[0], activeChain?.executor_packet_fingerprint, "executor_report_ref"));
|
|
1444
|
-
if (activeChain) {
|
|
1445
|
-
blockers.push(...worker_report_input_digest_blockers(executorRefs.refs[0], apply_worker_executor_input_ref_digest(ctx.evidences, activeChain), "executor_report_ref"));
|
|
1446
|
-
}
|
|
1447
|
-
}
|
|
1448
|
-
if (executorRefs.refs[0] && codeReviewRefs.refs[0]) {
|
|
1449
|
-
blockers.push(...worker_report_input_blockers(codeReviewRefs.refs[0], [executorRefs.refs[0]], "task_code_review_report_ref"));
|
|
1450
|
-
}
|
|
1451
|
-
if (completionProofKind === "green_tests") {
|
|
1452
|
-
blockers.push(...greenRefs.blockers);
|
|
1453
|
-
if ((args.red_test_run_evidence_refs ?? []).length === 0 && (args.characterization_test_run_evidence_refs ?? []).length === 0) {
|
|
1454
|
-
blockers.push(reason("missing_pre_edit_test_run_evidence_ref", "apply-verify-packet requires --red-test-run-evidence-ref or --characterization-test-run-evidence-ref"));
|
|
1455
|
-
}
|
|
1456
|
-
else {
|
|
1457
|
-
if ((args.red_test_run_evidence_refs ?? []).length > 0)
|
|
1458
|
-
blockers.push(...redRefs.blockers, ...pre_edit_refs_bound_to_active_chain(ctx, activeChain, redRefs.refs, "red", taskId));
|
|
1459
|
-
if ((args.characterization_test_run_evidence_refs ?? []).length > 0)
|
|
1460
|
-
blockers.push(...characterizationRefs.blockers, ...pre_edit_refs_bound_to_active_chain(ctx, activeChain, characterizationRefs.refs, "characterization", taskId));
|
|
1461
|
-
}
|
|
1462
|
-
}
|
|
1463
|
-
else {
|
|
1464
|
-
blockers.push(...alternativeRefs.blockers);
|
|
1465
|
-
const activePreIds = Array.isArray(activeChain?.pre_edit_evidence_refs) ? activeChain.pre_edit_evidence_refs.map(String).filter(Boolean) : [];
|
|
1466
|
-
if (activeChain?.pre_edit_proof_kind !== "no_tdd_declared") {
|
|
1467
|
-
blockers.push(reason("apply_worker_chain_active_invalid", "alternative verification requires active apply_worker_chain pre_edit_proof_kind=no_tdd_declared"));
|
|
1468
|
-
}
|
|
1469
|
-
if (activePreIds.length > 0) {
|
|
1470
|
-
blockers.push(reason("apply_worker_chain_active_invalid", `alternative verification requires empty active pre_edit_evidence_refs: ${renderList(activePreIds)}`, activePreIds));
|
|
1471
|
-
}
|
|
1472
|
-
if (tddRequired || activeChain?.tdd_required !== false) {
|
|
1473
|
-
blockers.push(reason("apply_worker_chain_active_invalid", "alternative verification requires task and active apply_worker_chain tdd_required=false"));
|
|
1474
|
-
}
|
|
1475
|
-
if (executionSurface !== "implementation" && executionSurface !== "runtime_config") {
|
|
1476
|
-
blockers.push(reason("alternative_verification_surface_invalid", `alternative verification worker-chain proof requires implementation/runtime_config surface, got ${executionSurface}`));
|
|
1477
|
-
}
|
|
1478
|
-
if (activeChain?.apply_execution_surface !== executionSurface) {
|
|
1479
|
-
blockers.push(reason("apply_worker_chain_active_invalid", "active apply_worker_chain apply_execution_surface must match task surface"));
|
|
1480
|
-
}
|
|
1481
|
-
if (!NO_TDD_REASONS.has(String(activeChain?.no_tdd_reason ?? "")) || activeChain?.no_tdd_reason !== task?.attrs.no_tdd_reason) {
|
|
1482
|
-
blockers.push(reason("apply_worker_chain_active_invalid", "active apply_worker_chain no_tdd_reason must be valid and match task"));
|
|
1483
|
-
}
|
|
1484
|
-
}
|
|
1485
|
-
packet.apply_worker_chain_id = chainId;
|
|
1486
|
-
packet.completion_proof_kind = completionProofKind;
|
|
1487
|
-
packet.pre_edit_proof_kind = activeChain?.pre_edit_proof_kind ?? "red_or_characterization";
|
|
1488
|
-
packet.executor_report_pinned_refs = executorRefs.refs;
|
|
1489
|
-
packet.task_code_review_report_pinned_refs = codeReviewRefs.refs;
|
|
1490
|
-
packet.green_test_run_evidence_pinned_refs = greenRefs.refs;
|
|
1491
|
-
packet.alternative_verification_evidence_pinned_refs = alternativeRefs.refs;
|
|
1492
|
-
packet.red_test_run_evidence_pinned_refs = redRefs.refs;
|
|
1493
|
-
packet.characterization_test_run_evidence_pinned_refs = characterizationRefs.refs;
|
|
1494
|
-
if (executorRefs.refs[0])
|
|
1495
|
-
packet.expected_executor_origin_packet_fingerprint = activeChain?.executor_packet_fingerprint;
|
|
1496
|
-
if (executorRefs.refs[0] && activeChain)
|
|
1497
|
-
packet.expected_executor_input_ref_digest = apply_worker_executor_input_ref_digest(ctx.evidences, activeChain);
|
|
1498
|
-
if (executorRefs.refs[0] && codeReviewRefs.refs[0]) {
|
|
1499
|
-
packet.expected_code_review_input_ref_digest = worker_input_ref_digest([executorRefs.refs[0]]);
|
|
1500
|
-
}
|
|
1501
|
-
if (completionProofKind === "green_tests" && executorRefs.refs[0] && codeReviewRefs.refs[0] && greenRefs.refs[0]) {
|
|
1502
|
-
const canonicalGreenRefs = [...greenRefs.refs].sort((a, b) => String(a.evidence_id ?? "").localeCompare(String(b.evidence_id ?? "")));
|
|
1503
|
-
const expectedFreshness = compute_apply_worker_freshness(ctx.repoRoot, ctx.changeRoot, ctx.evidences, taskId, chainId, {
|
|
1504
|
-
executor_report_ref: executorRefs.refs[0],
|
|
1505
|
-
task_code_review_report_ref: codeReviewRefs.refs[0],
|
|
1506
|
-
green_test_run_evidence_ref: String(canonicalGreenRefs[0].evidence_id ?? ""),
|
|
1507
|
-
green_test_run_evidence_refs: canonicalGreenRefs.map((ev) => String(ev.evidence_id ?? "")).filter(Boolean),
|
|
1508
|
-
completion_proof_kind: "green_tests",
|
|
1509
|
-
});
|
|
1510
|
-
packet.expected_freshness_fingerprint = expectedFreshness;
|
|
1511
|
-
const preEditRefs = [
|
|
1512
|
-
...(Array.isArray(packet.red_test_run_evidence_pinned_refs) ? packet.red_test_run_evidence_pinned_refs : []),
|
|
1513
|
-
...(Array.isArray(packet.characterization_test_run_evidence_pinned_refs) ? packet.characterization_test_run_evidence_pinned_refs : []),
|
|
1514
|
-
];
|
|
1515
|
-
packet.expected_verifier_input_ref_digest = worker_input_ref_digest([
|
|
1516
|
-
executorRefs.refs[0],
|
|
1517
|
-
codeReviewRefs.refs[0],
|
|
1518
|
-
...canonicalGreenRefs.map((ev) => ({ kind: "test_run", evidence_id: String(ev.evidence_id ?? ""), phase: ev.phase, semantic_status: ev.semantic_status })),
|
|
1519
|
-
...preEditRefs.map((ev) => ({ kind: "test_run", evidence_id: String(ev.evidence_id ?? ""), phase: ev.phase, semantic_status: ev.semantic_status })),
|
|
1520
|
-
]);
|
|
1521
|
-
const missingGreenFingerprint = greenRefs.refs.filter((ev) => !fingerprint_digest(ev.implementation_fingerprint));
|
|
1522
|
-
if (missingGreenFingerprint.length > 0) {
|
|
1523
|
-
blockers.push(reason("green_implementation_fingerprint_missing", "apply-verify-packet requires GREEN evidence to record implementation_fingerprint"));
|
|
1524
|
-
}
|
|
1525
|
-
else if (!greenRefs.refs.every((ev) => fingerprint_matches(ev.implementation_fingerprint, expectedFreshness.implementation_fingerprint))) {
|
|
1526
|
-
blockers.push(reason("green_implementation_fingerprint_mismatch", "apply-verify-packet requires current implementation fingerprint to match GREEN evidence implementation_fingerprint"));
|
|
1527
|
-
}
|
|
1528
|
-
if (!fingerprint_matches(codeReviewRefs.refs[0].observed_implementation_fingerprint, expectedFreshness.implementation_fingerprint)) {
|
|
1529
|
-
blockers.push(reason("task_code_review_implementation_fingerprint_mismatch", "apply-verify-packet requires code-reviewer observed_implementation_fingerprint to match current implementation fingerprint"));
|
|
1530
|
-
}
|
|
1531
|
-
if (Array.isArray(expectedFreshness.protected_dirty_paths) && expectedFreshness.protected_dirty_paths.length > 0) {
|
|
1532
|
-
blockers.push(reason("protected_path_dirty", `apply-verify-packet requires protected change artifacts to stay unchanged during executor-worker verification: ${renderList(expectedFreshness.protected_dirty_paths.map(String))}`, expectedFreshness.protected_dirty_paths.map(String)));
|
|
1533
|
-
}
|
|
1534
|
-
}
|
|
1535
|
-
else if (completionProofKind === "alternative_verification" && executorRefs.refs[0] && codeReviewRefs.refs[0] && alternativeRefs.refs[0]) {
|
|
1536
|
-
const canonicalAlternativeRefs = [...alternativeRefs.refs].sort((a, b) => String(a.evidence_id ?? "").localeCompare(String(b.evidence_id ?? "")));
|
|
1537
|
-
const expectedFreshness = compute_apply_worker_freshness(ctx.repoRoot, ctx.changeRoot, ctx.evidences, taskId, chainId, {
|
|
1538
|
-
executor_report_ref: executorRefs.refs[0],
|
|
1539
|
-
task_code_review_report_ref: codeReviewRefs.refs[0],
|
|
1540
|
-
alternative_verification_evidence_refs: canonicalAlternativeRefs.map((ev) => String(ev.evidence_id ?? "")).filter(Boolean),
|
|
1541
|
-
completion_proof_kind: "alternative_verification",
|
|
1542
|
-
});
|
|
1543
|
-
packet.expected_freshness_fingerprint = expectedFreshness;
|
|
1544
|
-
packet.expected_verifier_input_ref_digest = worker_input_ref_digest([
|
|
1545
|
-
executorRefs.refs[0],
|
|
1546
|
-
codeReviewRefs.refs[0],
|
|
1547
|
-
...canonicalAlternativeRefs.map(alternative_evidence_input_ref),
|
|
1548
|
-
active_chain_verifier_input_ref(activeChain),
|
|
1549
|
-
]);
|
|
1550
|
-
if (!fingerprint_matches(codeReviewRefs.refs[0].observed_implementation_fingerprint, expectedFreshness.implementation_fingerprint)) {
|
|
1551
|
-
blockers.push(reason("task_code_review_implementation_fingerprint_mismatch", "apply-verify-packet requires code-reviewer observed_implementation_fingerprint to match current implementation fingerprint"));
|
|
1552
|
-
}
|
|
1553
|
-
if (Array.isArray(expectedFreshness.protected_dirty_paths) && expectedFreshness.protected_dirty_paths.length > 0) {
|
|
1554
|
-
blockers.push(reason("protected_path_dirty", `apply-verify-packet requires protected change artifacts to stay unchanged during executor-worker verification: ${renderList(expectedFreshness.protected_dirty_paths.map(String))}`, expectedFreshness.protected_dirty_paths.map(String)));
|
|
1555
|
-
}
|
|
1556
|
-
}
|
|
1557
|
-
}
|
|
1558
|
-
packet.declared_task_write_scope = writeScope;
|
|
1559
|
-
packet.apply_execution_surface = executionSurface;
|
|
1560
|
-
packet.tdd_required = tddRequired;
|
|
1561
|
-
if (!tddRequired)
|
|
1562
|
-
packet.no_tdd_reason = task?.attrs.no_tdd_reason;
|
|
1563
|
-
packet.task_edit = decision_status(taskEdit);
|
|
1564
|
-
packet.task_complete = safe_decision_status(ctx, "task_complete", taskId);
|
|
1565
|
-
Object.assign(packet, apply_task_context_fields(ctx, taskId, task, writeScope, [
|
|
1566
|
-
...(Array.isArray(packet.executor_report_pinned_refs) ? packet.executor_report_pinned_refs : []),
|
|
1567
|
-
...(Array.isArray(packet.task_code_review_report_pinned_refs) ? packet.task_code_review_report_pinned_refs : []),
|
|
1568
|
-
]));
|
|
1569
|
-
packet.current_worktree_refs = dirty_repo_refs(ctx, "apply_verify current worktree refs");
|
|
1570
|
-
packet.protected_path_refs = Array.isArray(packet.expected_freshness_fingerprint?.protected_path_refs)
|
|
1571
|
-
? packet.expected_freshness_fingerprint.protected_path_refs
|
|
1572
|
-
: apply_worker_protected_path_refs(ctx.repoRoot, ctx.changeRoot);
|
|
1573
|
-
packet.scope_diff_review_policy = {
|
|
1574
|
-
declared_task_write_scope: writeScope,
|
|
1575
|
-
forbidden_paths: ["proposal.md", "design.md", "tasks.md", "specs/", ".superspec/"],
|
|
1576
|
-
require_scope_verdict: true,
|
|
1577
|
-
};
|
|
1578
|
-
packet.verification_checks = [
|
|
1579
|
-
completionProofKind === "green_tests"
|
|
1580
|
-
? "red_or_characterization_pre_edit_evidence_bound_to_active_chain"
|
|
1581
|
-
: "no_tdd_declared_active_chain_has_empty_pre_edit_refs",
|
|
1582
|
-
"executor_report_same_chain_and_fresh",
|
|
1583
|
-
"task_code_review_report_same_chain_and_fresh",
|
|
1584
|
-
completionProofKind === "green_tests"
|
|
1585
|
-
? "green_test_run_same_chain_and_pinned_transcripts"
|
|
1586
|
-
: "alternative_or_manual_verification_refs_live_pass_and_digest_bound",
|
|
1587
|
-
"current_freshness_matches_expected_freshness_fingerprint",
|
|
1588
|
-
"protected_change_artifacts_clean",
|
|
1589
|
-
];
|
|
1590
|
-
packet.executor_report_required_fields = [...APPLY_EXECUTOR_REPORT_REQUIRED_FIELDS];
|
|
1591
|
-
packet.code_review_report_required_fields = [...APPLY_CODE_REVIEW_REPORT_REQUIRED_FIELDS];
|
|
1592
|
-
packet.verifier_report_required_fields = [
|
|
1593
|
-
...APPLY_VERIFIER_REPORT_REQUIRED_FIELDS,
|
|
1594
|
-
...(completionProofKind === "green_tests"
|
|
1595
|
-
? ["green_test_run_evidence_refs", "red_test_run_evidence_refs_or_characterization_test_run_evidence_refs"]
|
|
1596
|
-
: ["alternative_verification_evidence_refs", "apply_execution_surface", "tdd_required", "no_tdd_reason"]),
|
|
1597
|
-
];
|
|
1598
|
-
packet.test_evidence_required_fields = ["command", "cwd", "exit_code", "repo_head", "implementation_fingerprint", "guard_artifact_manifest_fingerprint", "raw_log_pinned_refs"];
|
|
1599
|
-
packet.executor_report_refs = args.executor_report_refs ?? [];
|
|
1600
|
-
packet.task_code_review_report_refs = args.task_code_review_report_refs ?? [];
|
|
1601
|
-
packet.green_test_run_evidence_refs = args.green_test_run_evidence_refs ?? [];
|
|
1602
|
-
packet.alternative_verification_evidence_refs = args.alternative_verification_evidence_refs ?? [];
|
|
1603
|
-
packet.red_test_run_evidence_refs = args.red_test_run_evidence_refs ?? [];
|
|
1604
|
-
packet.characterization_test_run_evidence_refs = args.characterization_test_run_evidence_refs ?? [];
|
|
1605
|
-
packet.worker_state = blockers.length === 0 ? "ready" : "blocked";
|
|
1606
|
-
if (blockers.length > 0) {
|
|
1607
|
-
packet.blockers = unique_strings(blockers.map((item) => item.code));
|
|
1608
|
-
packet.block_reasons = blockers;
|
|
1609
|
-
}
|
|
1610
|
-
return finalize_apply_worker_packet(packet);
|
|
1611
|
-
}
|
|
1612
|
-
function render_apply_worker_prompt(packet) {
|
|
1613
|
-
const blockedHeaders = {
|
|
1614
|
-
apply_test: "DO NOT SPAWN TEST-RUNNER",
|
|
1615
|
-
apply_executor: "DO NOT SPAWN IMPLEMENTATION EXECUTOR",
|
|
1616
|
-
apply_code_review: "DO NOT SPAWN IMPLEMENTATION CODE-REVIEWER",
|
|
1617
|
-
apply_verify: "DO NOT SPAWN IMPLEMENTATION VERIFIER",
|
|
1618
|
-
};
|
|
1619
|
-
const lines = [];
|
|
1620
|
-
if (packet.worker_state === "blocked") {
|
|
1621
|
-
lines.push(blockedHeaders[String(packet.packet_kind)] ?? "DO NOT SPAWN WORKER", "");
|
|
1622
|
-
}
|
|
1623
|
-
else {
|
|
1624
|
-
lines.push("# SuperSpec Apply Worker Packet", "");
|
|
1625
|
-
}
|
|
1626
|
-
lines.push(`packet_kind: ${String(packet.packet_kind)}`, `change: ${String(packet.change)}`, `task_id: ${String(packet.task_id)}`, `worker_state: ${String(packet.worker_state)}`, `worker_chain_context: ${String(packet.worker_chain_context)}`);
|
|
1627
|
-
if (typeof packet.test_id === "string")
|
|
1628
|
-
lines.push(`test_id: ${packet.test_id}`);
|
|
1629
|
-
if (typeof packet.phase === "string")
|
|
1630
|
-
lines.push(`phase: ${packet.phase}`);
|
|
1631
|
-
if (typeof packet.allowed_test_command === "string")
|
|
1632
|
-
lines.push(`allowed_test_command: ${packet.allowed_test_command}`);
|
|
1633
|
-
if (typeof packet.apply_worker_chain_id === "string")
|
|
1634
|
-
lines.push(`apply_worker_chain_id: ${packet.apply_worker_chain_id}`);
|
|
1635
|
-
if (packet.packet_kind === "apply_executor" && packet.worker_state === "ready") {
|
|
1636
|
-
lines.push("", "CHAIN ACTIVATION VERIFIED FOR IMPLEMENTATION EXECUTOR", "The active apply_worker_chain evidence ref has been validated for this prompt.", "", "chain_activation_template:", JSON.stringify(packet.chain_activation_template ?? {}, null, 2));
|
|
1637
|
-
}
|
|
1638
|
-
if (packet.worker_state === "ready") {
|
|
1639
|
-
lines.push("", "Packet JSON:", JSON.stringify(packet, null, 2));
|
|
1640
|
-
}
|
|
1641
|
-
if (Array.isArray(packet.blockers) && packet.blockers.length > 0) {
|
|
1642
|
-
lines.push("", "Blockers:", ...packet.blockers.map((item) => `- ${item}`));
|
|
1643
|
-
}
|
|
1644
|
-
if (Array.isArray(packet.block_reasons) && packet.block_reasons.length > 0) {
|
|
1645
|
-
lines.push("", "Blocker details:", ...packet.block_reasons.map((item) => `- ${item.code}: ${item.message}`));
|
|
1646
|
-
}
|
|
1647
|
-
if (Array.isArray(packet.stop_conditions) && packet.stop_conditions.length > 0) {
|
|
1648
|
-
lines.push("", "Stop conditions:", ...packet.stop_conditions.map((item) => `- ${item}`));
|
|
1649
|
-
}
|
|
1650
|
-
return `${lines.join("\n")}\n`;
|
|
1651
|
-
}
|
|
1652
|
-
function render_ref(ref) {
|
|
1653
|
-
return `- ${ref.root}:${ref.path} @ ${ref.blob_sha}`;
|
|
1654
|
-
}
|
|
1655
|
-
function render_review_prompt(ctx, packet) {
|
|
1656
|
-
const lines = [
|
|
1657
|
-
`# SuperSpec Review Packet`,
|
|
1658
|
-
"",
|
|
1659
|
-
`consumer: ${packet.consumer}`,
|
|
1660
|
-
`gate: ${packet.gate}`,
|
|
1661
|
-
`role: ${packet.role}`,
|
|
1662
|
-
`round: ${packet.round}`,
|
|
1663
|
-
"",
|
|
1664
|
-
`Target refs:`,
|
|
1665
|
-
...(packet.target_refs.length > 0 ? packet.target_refs.map(render_ref) : ["- none"]),
|
|
1666
|
-
"",
|
|
1667
|
-
`Source refs:`,
|
|
1668
|
-
...(packet.source_refs.length > 0 ? packet.source_refs.map(render_ref) : ["- none"]),
|
|
1669
|
-
];
|
|
1670
|
-
if (packet.required_load_refs && packet.required_load_refs.length > 0) {
|
|
1671
|
-
lines.push("", "Required load refs:", ...packet.required_load_refs.map(render_ref));
|
|
1672
|
-
}
|
|
1673
|
-
if (packet.required_claim_ids && packet.required_claim_ids.length > 0) {
|
|
1674
|
-
lines.push("", "Required claim ids:", ...packet.required_claim_ids.map((item) => `- ${item}`));
|
|
1675
|
-
}
|
|
1676
|
-
if (packet.must_read_verbatim_findings && packet.must_read_verbatim_findings.length > 0) {
|
|
1677
|
-
lines.push("", "Must read verbatim findings:", ...packet.must_read_verbatim_findings.map((item) => `- ${item.evidence_id} :: ${item.finding_uid} @ ${item.evidence_ref.path}`));
|
|
1678
|
-
}
|
|
1679
|
-
if (packet.must_read_verbatim_decisions && packet.must_read_verbatim_decisions.length > 0) {
|
|
1680
|
-
lines.push("", "Must read decision bindings:", ...packet.must_read_verbatim_decisions.map((item) => `- ${item.evidence_id} :: ${item.decision_scope_key} @ ${item.evidence_ref.path}`));
|
|
1681
|
-
}
|
|
1682
|
-
lines.push("", `Required output kind: ${packet.required_output_kind}`, "Output contract fields:", ...packet.output_contract_fields.map((item) => `- ${item}`));
|
|
1683
|
-
if (packet.required_review_scope && packet.required_review_scope.length > 0) {
|
|
1684
|
-
lines.push("", "Required review scope:", ...packet.required_review_scope.map((item) => `- ${item}`));
|
|
1685
|
-
}
|
|
1686
|
-
lines.push("", "Stop conditions:", ...packet.stop_conditions.map((item) => `- ${item}`));
|
|
1687
|
-
if (packet.round > 1 && packet.gate in REVIEW_TARGETS_BY_GATE) {
|
|
1688
|
-
lines.push("", render_finding_ledger(packet.gate, build_finding_ledger(packet.gate, ctx.evidences, packet.round)));
|
|
1689
|
-
}
|
|
1690
|
-
return `${lines.join("\n")}\n`;
|
|
1691
|
-
}
|
|
1692
|
-
function render_ledger(ctx, gateRaw, round) {
|
|
1693
|
-
const gate = normalize_gate(gateRaw);
|
|
1694
|
-
const entries = build_finding_ledger(gate, ctx.evidences, round ?? Number.POSITIVE_INFINITY);
|
|
1695
|
-
return `${render_finding_ledger(gate, entries)}\n`;
|
|
1696
|
-
}
|
|
1697
|
-
export function dispatch_packet(args) {
|
|
1698
|
-
const ctx = load_packet_context(args.change);
|
|
1699
|
-
if (args.command === "workflow-packet") {
|
|
1700
|
-
const gate = normalize_gate(args.gate ?? "");
|
|
1701
|
-
if ((gate === "task_edit" || gate === "task_complete" || gate === "task_reopen") && !args.task_id) {
|
|
1702
|
-
throw new GuardError(`workflow-packet requires --task-id for gate ${gate}`);
|
|
1703
|
-
}
|
|
1704
|
-
return { output_format: "agent", payload: workflow_packet(ctx, gate, args.task_id) };
|
|
1705
|
-
}
|
|
1706
|
-
if (args.command === "review-packet") {
|
|
1707
|
-
assert_packet_context_clean(ctx);
|
|
1708
|
-
const round = args.round ?? 0;
|
|
1709
|
-
if (round < 1)
|
|
1710
|
-
throw new GuardError("review-packet requires --round >= 1");
|
|
1711
|
-
const packet = review_packet(ctx, args.gate ?? "", args.role ?? "", round, args.evidence_kind);
|
|
1712
|
-
if (args.packet_format === "prompt") {
|
|
1713
|
-
return { output_format: "prompt", payload: render_review_prompt(ctx, packet) };
|
|
1714
|
-
}
|
|
1715
|
-
return { output_format: "agent", payload: packet };
|
|
1716
|
-
}
|
|
1717
|
-
if (args.command === "apply-test-packet") {
|
|
1718
|
-
assert_packet_context_clean(ctx);
|
|
1719
|
-
const packet = apply_test_packet(ctx, args);
|
|
1720
|
-
if (args.packet_format === "prompt")
|
|
1721
|
-
return { output_format: "prompt", payload: render_apply_worker_prompt(packet) };
|
|
1722
|
-
return { output_format: "agent", payload: packet };
|
|
1723
|
-
}
|
|
1724
|
-
if (args.command === "apply-executor-packet") {
|
|
1725
|
-
assert_packet_context_clean(ctx);
|
|
1726
|
-
const packet = apply_executor_packet(ctx, args);
|
|
1727
|
-
if (args.packet_format === "prompt")
|
|
1728
|
-
return { output_format: "prompt", payload: render_apply_worker_prompt(packet) };
|
|
1729
|
-
return { output_format: "agent", payload: packet };
|
|
1730
|
-
}
|
|
1731
|
-
if (args.command === "apply-code-review-packet") {
|
|
1732
|
-
assert_packet_context_clean(ctx);
|
|
1733
|
-
const packet = apply_code_review_packet(ctx, args);
|
|
1734
|
-
if (args.packet_format === "prompt")
|
|
1735
|
-
return { output_format: "prompt", payload: render_apply_worker_prompt(packet) };
|
|
1736
|
-
return { output_format: "agent", payload: packet };
|
|
1737
|
-
}
|
|
1738
|
-
if (args.command === "apply-verify-packet") {
|
|
1739
|
-
assert_packet_context_clean(ctx);
|
|
1740
|
-
const packet = apply_verify_packet(ctx, args);
|
|
1741
|
-
if (args.packet_format === "prompt")
|
|
1742
|
-
return { output_format: "prompt", payload: render_apply_worker_prompt(packet) };
|
|
1743
|
-
return { output_format: "agent", payload: packet };
|
|
1744
|
-
}
|
|
1745
|
-
if (args.command === "ledger-render") {
|
|
1746
|
-
assert_packet_context_clean(ctx);
|
|
1747
|
-
return { output_format: "prompt", payload: render_ledger(ctx, args.gate ?? "", args.round) };
|
|
1748
|
-
}
|
|
1749
|
-
throw new GuardError(`unknown packet command: ${args.command}`);
|
|
1750
|
-
}
|
|
1751
|
-
export function is_packet_command(command) {
|
|
1752
|
-
return command === "workflow-packet"
|
|
1753
|
-
|| command === "review-packet"
|
|
1754
|
-
|| command === "apply-test-packet"
|
|
1755
|
-
|| command === "apply-executor-packet"
|
|
1756
|
-
|| command === "apply-code-review-packet"
|
|
1757
|
-
|| command === "apply-verify-packet"
|
|
1758
|
-
|| command === "ledger-render";
|
|
1759
|
-
}
|