wydcode 0.4.3

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.
Files changed (41) hide show
  1. package/LICENSE +100 -0
  2. package/NOTICE +5 -0
  3. package/README.md +434 -0
  4. package/SECURITY.md +64 -0
  5. package/SOCRA_SOURCE.md +36 -0
  6. package/TRADEMARKS.md +12 -0
  7. package/bin/wydcode.js +74 -0
  8. package/examples/behavior-preference-card.json +12 -0
  9. package/examples/exact-recurrence-card.json +11 -0
  10. package/examples/heldout-gene-evidence.json +6 -0
  11. package/examples/humaneval-ornith.md +29 -0
  12. package/examples/large_context_json_diagnostic.py +187 -0
  13. package/fixtures/failing-node-repo/package.json +9 -0
  14. package/fixtures/failing-node-repo/src/math.js +3 -0
  15. package/fixtures/failing-node-repo/test/math.test.js +8 -0
  16. package/package.json +55 -0
  17. package/pyproject.toml +24 -0
  18. package/socra_harness/__init__.py +3 -0
  19. package/socra_harness/attempts.py +76 -0
  20. package/socra_harness/central_traces.py +627 -0
  21. package/socra_harness/cli.py +265 -0
  22. package/socra_harness/eval/__init__.py +1 -0
  23. package/socra_harness/eval/humaneval.py +388 -0
  24. package/socra_harness/events.py +39 -0
  25. package/socra_harness/init.py +206 -0
  26. package/socra_harness/job/__init__.py +2 -0
  27. package/socra_harness/job/events.py +132 -0
  28. package/socra_harness/job/lock.py +90 -0
  29. package/socra_harness/job/manager.py +3785 -0
  30. package/socra_harness/job/planner.py +253 -0
  31. package/socra_harness/job/state.py +106 -0
  32. package/socra_harness/mumpix_trace_bridge.cjs +32 -0
  33. package/socra_harness/product.py +533 -0
  34. package/socra_harness/recurrence.py +263 -0
  35. package/socra_harness/scan/__init__.py +1 -0
  36. package/socra_harness/scan/repo.py +85 -0
  37. package/socra_harness/serve/__init__.py +1 -0
  38. package/socra_harness/serve/local_proxy.py +293 -0
  39. package/socra_harness/start.py +331 -0
  40. package/socra_harness/tui/__init__.py +1 -0
  41. package/socra_harness/tui/app.py +1114 -0
@@ -0,0 +1,3785 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import base64
5
+ import difflib
6
+ import hashlib
7
+ import json
8
+ import os
9
+ import re
10
+ import shutil
11
+ import subprocess
12
+ import sys
13
+ import tempfile
14
+ import time
15
+ import urllib.error
16
+ import urllib.parse
17
+ import urllib.request
18
+ import uuid
19
+ from pathlib import Path
20
+ from typing import Any
21
+
22
+ from ..attempts import AttemptFailure, attempt_failure_payload, classify_creator_finish
23
+ from ..central_traces import export_failed_trace, export_promoted_trace, retrieve_validated_behavior
24
+ from ..recurrence import canonical_json as recurrence_canonical_json
25
+ from ..recurrence import behavior_overlaps_patch
26
+ from ..recurrence import injection_budget_decision
27
+ from ..recurrence import sha256_json as recurrence_sha256_json
28
+ from .events import JobEventLog, read_events
29
+ from .lock import JobLock
30
+ from .planner import canonical_task_hash, materialize_plan_from_events, planner_tasks_for_goal, ready_task_ids, validate_task_graph
31
+ from .state import replay, state_to_json, write_state_snapshot
32
+
33
+ CREATOR_PROMPT_LAYOUT = "stable_prefix_v2_anchored_large_file"
34
+ MAX_JSON_CONTRACT_DECLARED_FILES = 2
35
+ ANCHORED_EDIT_OUTPUT_HEADROOM_RATIO = 0.75
36
+ MAX_ANCHORED_EDITS = 16
37
+
38
+
39
+ def slugify(value: str) -> str:
40
+ slug = re.sub(r"[^a-zA-Z0-9]+", "-", value.strip().lower()).strip("-")
41
+ return slug[:48] or "job"
42
+
43
+
44
+ def job_root(project: Path) -> Path:
45
+ return project / ".socra" / "jobs"
46
+
47
+
48
+ def new_job_id(goal: str) -> str:
49
+ return f"{slugify(goal)}-{uuid.uuid4().hex[:8]}"
50
+
51
+
52
+ def write_json(path: Path, payload: dict[str, Any]) -> None:
53
+ path.parent.mkdir(parents=True, exist_ok=True)
54
+ path.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")
55
+
56
+
57
+ def copytree_ignore_for_runner(_dir: str, names: list[str]) -> set[str]:
58
+ ignored = {
59
+ ".git",
60
+ ".socra",
61
+ "node_modules",
62
+ "dist",
63
+ "build",
64
+ ".next",
65
+ ".turbo",
66
+ ".vite",
67
+ "coverage",
68
+ }
69
+ return {name for name in names if name in ignored}
70
+
71
+
72
+ def sha256_file(path: Path) -> str:
73
+ return hashlib.sha256(path.read_bytes()).hexdigest()
74
+
75
+
76
+ def sha256_text(text: str) -> str:
77
+ return hashlib.sha256(text.encode("utf-8")).hexdigest()
78
+
79
+
80
+ def elapsed_ms(start: float) -> int:
81
+ return int(round((time.monotonic() - start) * 1000))
82
+
83
+
84
+ def task_inventory_package_entry(job_dir: Path | None) -> dict[str, Any] | None:
85
+ if job_dir is None:
86
+ return None
87
+ inventory_path = job_dir / "artifacts" / "tasks" / "task-001" / "inventory.json"
88
+ if not inventory_path.exists():
89
+ return None
90
+ try:
91
+ inventory = json.loads(inventory_path.read_text(encoding="utf-8"))
92
+ except Exception: # noqa: BLE001 - conventions are advisory context.
93
+ return None
94
+ for item in inventory.get("declared_files") or []:
95
+ if isinstance(item, dict) and item.get("path") == "package.json":
96
+ return {
97
+ "path": "package.json",
98
+ "sha256": item.get("sha256"),
99
+ "scripts": item.get("scripts") or [],
100
+ "dependencies": item.get("dependencies") or [],
101
+ "dev_dependencies": item.get("dev_dependencies") or [],
102
+ "inventory_path": str(inventory_path.relative_to(job_dir)),
103
+ }
104
+ return None
105
+
106
+
107
+ def project_conventions(project: Path, job_dir: Path | None = None) -> dict[str, Any]:
108
+ package_path = project / "package.json"
109
+ package_type = None
110
+ package_source = "package_json_current_file"
111
+ inventory_package = task_inventory_package_entry(job_dir)
112
+ if package_path.exists() and package_path.is_file():
113
+ try:
114
+ package = json.loads(package_path.read_text(encoding="utf-8"))
115
+ package_type = package.get("type") if isinstance(package.get("type"), str) else None
116
+ if inventory_package:
117
+ current_hash = sha256_file(package_path)
118
+ if inventory_package.get("sha256") == current_hash:
119
+ package_source = "task_001_inventory_package_json_hash_match"
120
+ else:
121
+ package_source = "package_json_current_file_inventory_hash_mismatch"
122
+ except Exception: # noqa: BLE001 - conventions are advisory context.
123
+ package_type = None
124
+ javascript_module_system = "esm" if package_type == "module" else "commonjs_or_unspecified"
125
+ return {
126
+ "source": package_source,
127
+ "inventory_package": inventory_package,
128
+ "package_type": package_type,
129
+ "javascript_module_system": javascript_module_system,
130
+ "javascript_import_rule": (
131
+ "Use ESM import/export syntax with explicit .js relative import extensions. "
132
+ "Do not use require(), module.exports, or exports.*."
133
+ if javascript_module_system == "esm"
134
+ else "Follow the existing project module syntax."
135
+ ),
136
+ }
137
+
138
+
139
+ def write_handoff(job_dir: Path, state: dict[str, Any], plan: dict[str, Any]) -> None:
140
+ tasks = plan.get("tasks") if isinstance(plan.get("tasks"), list) else []
141
+ pending = [task for task in tasks if task.get("status") == "pending"]
142
+ completed = [task for task in tasks if task.get("status") == "passed"]
143
+ creator = state.get("creator_config") if isinstance(state.get("creator_config"), dict) else {}
144
+ text = "\n".join(
145
+ [
146
+ f"# Socra Job Handoff: {state.get('goal')}",
147
+ "",
148
+ f"- Job: `{state.get('job_id')}`",
149
+ f"- Phase: `{state.get('phase')}`",
150
+ f"- Project: `{state.get('project')}`",
151
+ f"- Completed tasks: {len(completed)}",
152
+ f"- Pending tasks: {len(pending)}",
153
+ f"- Active task: `{state.get('active_task_id')}`",
154
+ f"- Creator model: `{creator.get('model_alias') or creator.get('model') or 'unconfigured'}`",
155
+ f"- Creator endpoint: `{creator.get('endpoint') or 'unconfigured'}`",
156
+ "",
157
+ "## Next Action",
158
+ "",
159
+ "Resume can safely replay `events.jsonl` and continue from the first ready pending task.",
160
+ "",
161
+ ]
162
+ )
163
+ (job_dir / "handoff.md").write_text(text, encoding="utf-8")
164
+
165
+
166
+ def starter_plan(goal: str) -> dict[str, Any]:
167
+ return {
168
+ "version": 1,
169
+ "goal": goal,
170
+ "tasks": [
171
+ {
172
+ "id": "task-001",
173
+ "description": "Clarify architecture and create the first implementation slice.",
174
+ "acceptance": ["A concrete implementation plan exists", "Declared files are known before coding"],
175
+ "risk_tier": 0,
176
+ "status": "pending",
177
+ "depends_on": [],
178
+ "declared_files": [],
179
+ "attempt_count": 0,
180
+ "produced_artifacts": [],
181
+ }
182
+ ],
183
+ }
184
+
185
+
186
+ def load_plan(job_dir: Path) -> dict[str, Any]:
187
+ path = job_dir / "plan.json"
188
+ fallback = {"version": 1, "goal": "", "tasks": []}
189
+ if not path.exists():
190
+ return fallback
191
+ return json.loads(path.read_text(encoding="utf-8"))
192
+
193
+
194
+ def materialize_job_plan(job_dir: Path) -> dict[str, Any]:
195
+ return materialize_plan_from_events(read_events(job_dir / "events.jsonl"), load_plan(job_dir))
196
+
197
+
198
+ def plan_prereg_metadata(job_dir: Path) -> dict[str, Any]:
199
+ prereg_dir = job_dir / "artifacts" / "preregistration"
200
+ candidates = sorted(prereg_dir.glob("*.json"))
201
+ if not candidates:
202
+ return {}
203
+ path = candidates[0]
204
+ payload = json.loads(path.read_text(encoding="utf-8"))
205
+ return {
206
+ "path": str(path.relative_to(job_dir)),
207
+ "sha256": hashlib.sha256(path.read_bytes()).hexdigest(),
208
+ "expected_task_count": payload.get("expected_task_count"),
209
+ }
210
+
211
+
212
+ def find_latest_incomplete_job(project: Path) -> Path | None:
213
+ root = job_root(project)
214
+ if not root.exists():
215
+ return None
216
+ candidates = []
217
+ for job_dir in root.iterdir():
218
+ if not job_dir.is_dir() or not (job_dir / "events.jsonl").exists():
219
+ continue
220
+ state = replay(read_events(job_dir / "events.jsonl"))
221
+ if state.phase not in {"done", "failed"}:
222
+ candidates.append((state.last_seq, job_dir))
223
+ if not candidates:
224
+ return None
225
+ return sorted(candidates, key=lambda item: item[0], reverse=True)[0][1]
226
+
227
+
228
+ def job_start_main(args: argparse.Namespace) -> int:
229
+ project = args.project.expanduser().resolve()
230
+ project.mkdir(parents=True, exist_ok=True)
231
+ job_id = args.job_id or new_job_id(args.goal)
232
+ job_dir = job_root(project) / job_id
233
+ job_dir.mkdir(parents=True, exist_ok=False)
234
+ with JobLock(job_dir / ".lock"):
235
+ for folder in ["artifacts", "patches", "sandbox", "checkpoints"]:
236
+ (job_dir / folder).mkdir(parents=True, exist_ok=True)
237
+
238
+ plan = starter_plan(args.goal)
239
+ write_json(job_dir / "plan.json", plan)
240
+
241
+ log = JobEventLog(job_dir / "events.jsonl", job_id)
242
+ log.append("job_created", {"goal": args.goal, "project": str(project)})
243
+ log.append("plan_written", {"plan": "plan.json", "task_count": len(plan["tasks"])})
244
+ log.append("job_paused", {"reason": "start_only_no_execution"})
245
+
246
+ state = replay(read_events(job_dir / "events.jsonl"))
247
+ write_state_snapshot(job_dir / "job.json", state)
248
+ write_handoff(job_dir, state_to_json(state), plan)
249
+
250
+ print(f"Created Socra job: {job_id}")
251
+ print(f"Project: {project}")
252
+ print(f"Job dir: {job_dir}")
253
+ print(f"Phase: {state.phase}")
254
+ return 0
255
+
256
+
257
+ def job_resume_main(args: argparse.Namespace) -> int:
258
+ project = args.project.expanduser().resolve()
259
+ if args.job_id:
260
+ job_dir = job_root(project) / args.job_id
261
+ else:
262
+ found = find_latest_incomplete_job(project)
263
+ if not found:
264
+ raise SystemExit(f"No incomplete Socra jobs found under {job_root(project)}")
265
+ job_dir = found
266
+ with JobLock(job_dir / ".lock"):
267
+ events = read_events(job_dir / "events.jsonl")
268
+ state = replay(events)
269
+ plan = materialize_plan_from_events(events, load_plan(job_dir))
270
+ write_state_snapshot(job_dir / "job.json", state)
271
+ write_json(job_dir / "plan.json", plan)
272
+ write_handoff(job_dir, state_to_json(state), plan)
273
+
274
+ print(f"Resumed Socra job: {state.job_id}")
275
+ print(f"Project: {state.project}")
276
+ print(f"Job dir: {job_dir}")
277
+ print(f"Phase: {state.phase}")
278
+ print(f"Events replayed: {len(events)}")
279
+ print(f"Active task: {state.active_task_id or 'none'}")
280
+ print(f"Plan tasks: {len(plan.get('tasks', []))}")
281
+ print(f"Ready tasks: {', '.join(ready_task_ids(plan)[:8]) or 'none'}")
282
+ print(f"Handoff: {job_dir / 'handoff.md'}")
283
+ return 0
284
+
285
+
286
+ def job_plan_main(args: argparse.Namespace) -> int:
287
+ project = args.project.expanduser().resolve()
288
+ if args.job_id:
289
+ job_dir = job_root(project) / args.job_id
290
+ else:
291
+ found = find_latest_incomplete_job(project)
292
+ if not found:
293
+ raise SystemExit(f"No incomplete Socra jobs found under {job_root(project)}")
294
+ job_dir = found
295
+
296
+ with JobLock(job_dir / ".lock"):
297
+ events = read_events(job_dir / "events.jsonl")
298
+ if any(event.type == "plan_committed" for event in events) and not args.force:
299
+ raise SystemExit("Plan already committed for this job. Pass --force to append a revised plan.")
300
+
301
+ state = replay(events)
302
+ prereg = plan_prereg_metadata(job_dir)
303
+ tasks = planner_tasks_for_goal(state.goal, project)
304
+ validate_task_graph(tasks)
305
+ task_hash = canonical_task_hash(tasks)
306
+ expected_count = prereg.get("expected_task_count")
307
+ actual_count = len(tasks)
308
+ log = JobEventLog(job_dir / "events.jsonl", state.job_id or job_dir.name)
309
+
310
+ previous_commits = [event for event in events if event.type == "plan_committed"]
311
+ revision = len(previous_commits) + 1
312
+ if previous_commits:
313
+ log.append("plan_revised", {"reason": args.reason or "manual_replan", "previous_plan_event_seen": True})
314
+ if args.reason and "count" in args.reason.lower():
315
+ log.append(
316
+ "planner_count_anchor_audit",
317
+ {
318
+ "previous_exact_match_to_prereg": True,
319
+ "template_authored_after_prereg_visible": True,
320
+ "runtime_planner_receives_expected_count": False,
321
+ "remediation": "replan_to_granularity_and_record_count_delta",
322
+ },
323
+ )
324
+
325
+ for task in tasks:
326
+ log.append("task_added", task)
327
+
328
+ if expected_count is not None and int(expected_count) != actual_count:
329
+ log.append(
330
+ "plan_count_delta_recorded",
331
+ {
332
+ "expected_task_count": int(expected_count),
333
+ "actual_task_count": actual_count,
334
+ "delta": actual_count - int(expected_count),
335
+ "note": "Planner follows task granularity; preregistered count is a reference, not a target.",
336
+ },
337
+ )
338
+
339
+ log.append(
340
+ "plan_committed",
341
+ {
342
+ "revision": revision,
343
+ "task_count": actual_count,
344
+ "task_hash": task_hash,
345
+ "preregistration": prereg,
346
+ "planner_rule": "plan_to_granularity_not_preregistered_count",
347
+ },
348
+ )
349
+
350
+ events = read_events(job_dir / "events.jsonl")
351
+ state = replay(events)
352
+ plan = materialize_plan_from_events(events, load_plan(job_dir))
353
+ write_json(job_dir / "plan.json", plan)
354
+ write_state_snapshot(job_dir / "job.json", state)
355
+ write_handoff(job_dir, state_to_json(state), plan)
356
+
357
+ ready = ready_task_ids(plan)
358
+ print(f"Planned Socra job: {state.job_id}")
359
+ print(f"Project: {project}")
360
+ print(f"Job dir: {job_dir}")
361
+ print(f"Tasks: {actual_count}")
362
+ if expected_count is not None:
363
+ print(f"Preregistered reference count: {expected_count}")
364
+ print(f"Task hash: {task_hash}")
365
+ print(f"Ready tasks: {', '.join(ready[:8]) or 'none'}")
366
+ return 0
367
+
368
+
369
+ def job_configure_creator_main(args: argparse.Namespace) -> int:
370
+ project = args.project.expanduser().resolve()
371
+ if args.job_id:
372
+ job_dir = job_root(project) / args.job_id
373
+ else:
374
+ found = find_latest_incomplete_job(project)
375
+ if not found:
376
+ raise SystemExit(f"No incomplete Socra jobs found under {job_root(project)}")
377
+ job_dir = found
378
+
379
+ config = {
380
+ "role": "creator",
381
+ "model_alias": args.model_alias,
382
+ "model": args.model,
383
+ "expected_served_model_id": args.expected_served_model_id or Path(str(args.model)).name,
384
+ "endpoint": args.endpoint,
385
+ "backend": args.backend,
386
+ "temperature": args.temperature,
387
+ "seed": args.seed,
388
+ "context_budget_tokens": args.context_budget_tokens,
389
+ "max_output_tokens": args.max_output_tokens,
390
+ "hosted_fallback": bool(args.hosted_fallback),
391
+ "local_first": not bool(args.hosted_fallback),
392
+ "notes": args.notes or "",
393
+ }
394
+
395
+ with JobLock(job_dir / ".lock"):
396
+ events = read_events(job_dir / "events.jsonl")
397
+ state = replay(events)
398
+ log = JobEventLog(job_dir / "events.jsonl", state.job_id or job_dir.name)
399
+ log.append("creator_configured", config)
400
+ events = read_events(job_dir / "events.jsonl")
401
+ state = replay(events)
402
+ plan = materialize_plan_from_events(events, load_plan(job_dir))
403
+ write_json(job_dir / "plan.json", plan)
404
+ write_state_snapshot(job_dir / "job.json", state)
405
+ write_handoff(job_dir, state_to_json(state), plan)
406
+
407
+ print(f"Configured Creator for Socra job: {state.job_id}")
408
+ print(f"Model alias: {args.model_alias}")
409
+ print(f"Model: {args.model}")
410
+ print(f"Endpoint: {args.endpoint}")
411
+ print(f"Temperature: {args.temperature}")
412
+ print(f"Seed: {args.seed if args.seed is not None else 'provider default'}")
413
+ print(f"Context budget tokens: {args.context_budget_tokens}")
414
+ print(f"Hosted fallback: {bool(args.hosted_fallback)}")
415
+ return 0
416
+
417
+
418
+ def git_capture(project: Path, args: list[str]) -> tuple[int, str, str]:
419
+ result = subprocess.run(
420
+ ["git", *args],
421
+ cwd=project,
422
+ text=True,
423
+ capture_output=True,
424
+ timeout=20,
425
+ check=False,
426
+ )
427
+ return result.returncode, result.stdout.strip(), result.stderr.strip()
428
+
429
+
430
+ def target_repo_state(project: Path) -> dict[str, Any]:
431
+ state: dict[str, Any] = {"project": str(project)}
432
+ code, out, err = git_capture(project, ["rev-parse", "--show-toplevel"])
433
+ state["is_git_repo"] = code == 0
434
+ state["git_toplevel"] = out if code == 0 else None
435
+ if code != 0:
436
+ state["error"] = err or "not_a_git_repository"
437
+ return state
438
+ for key, git_args in {
439
+ "current_head": ["rev-parse", "HEAD"],
440
+ "current_branch": ["branch", "--show-current"],
441
+ "origin_dev_head": ["rev-parse", "--verify", "refs/remotes/origin/dev"],
442
+ "origin_head": ["rev-parse", "--verify", "refs/remotes/origin/HEAD"],
443
+ }.items():
444
+ code, out, err = git_capture(project, git_args)
445
+ state[key] = out if code == 0 else None
446
+ if code != 0 and key in {"current_head"}:
447
+ state[f"{key}_error"] = err
448
+ code, out, _ = git_capture(project, ["status", "--short"])
449
+ state["dirty"] = bool(out.strip()) if code == 0 else None
450
+ state["status_short"] = out.splitlines() if code == 0 and out else []
451
+ return state
452
+
453
+
454
+ def default_dayjs_exam_pack_path() -> Path:
455
+ return Path(__file__).resolve().parents[2] / "docs" / "stress_tests" / "dayjs_exam_set_v1.json"
456
+
457
+
458
+ def load_dayjs_exam_pack(path: Path) -> dict[str, Any]:
459
+ return json.loads(path.expanduser().resolve().read_text(encoding="utf-8"))
460
+
461
+
462
+ def find_dayjs_candidate(pack: dict[str, Any], candidate_id: str) -> dict[str, Any]:
463
+ wanted = candidate_id.lower()
464
+ for candidate in pack.get("candidates") or []:
465
+ keys = {
466
+ str(candidate.get("exam_id") or "").lower(),
467
+ str(candidate.get("id") or "").lower(),
468
+ }
469
+ if wanted in keys:
470
+ return dict(candidate)
471
+ raise SystemExit(f"Unknown dayjs exam candidate: {candidate_id}")
472
+
473
+
474
+ def job_exam_dayjs_main(args: argparse.Namespace) -> int:
475
+ project = args.project.expanduser().resolve()
476
+ pack_path = args.pack.expanduser().resolve() if args.pack else default_dayjs_exam_pack_path()
477
+ pack = load_dayjs_exam_pack(pack_path)
478
+ candidate = find_dayjs_candidate(pack, args.candidate)
479
+ exam_id = str(candidate.get("exam_id") or args.candidate).upper()
480
+ prediction = args.prediction or (pack.get("preregistered_predictions") or {}).get(exam_id) or "unspecified"
481
+ expected_checkout = str(candidate.get("upstream_fix_commit_parent") or "")
482
+ repo_state = target_repo_state(project)
483
+
484
+ if not repo_state.get("is_git_repo"):
485
+ raise SystemExit(f"Project is not a git repository: {project}")
486
+ if repo_state.get("current_head") != expected_checkout and not args.allow_checkout_mismatch:
487
+ raise SystemExit(
488
+ "dayjs exam checkout mismatch: "
489
+ f"HEAD={repo_state.get('current_head')} expected={expected_checkout}. "
490
+ "Checkout the pre-fix parent or pass --allow-checkout-mismatch to record a dry scaffold."
491
+ )
492
+
493
+ job_id = args.job_id or f"dayjs-exam-{exam_id.lower()}-{slugify(candidate.get('id') or '')}"
494
+ job_dir = job_root(project) / job_id
495
+ job_dir.mkdir(parents=True, exist_ok=False)
496
+
497
+ source_file = str((candidate.get("declared_files") or [""])[0])
498
+ hidden_test_file = str(candidate.get("hidden_test_file") or "")
499
+ focused_gate = str((pack.get("default_commands") or {}).get("focused_gate_template") or "npx jest <hidden_test_file> --coverage=false").replace(
500
+ "<hidden_test_file>", hidden_test_file
501
+ )
502
+ hidden_gate_extract = str((pack.get("protocol") or {}).get("hidden_gate_extraction_template") or "git show <upstream_fix_commit> -- <hidden_test_file> > hidden-gate.patch")
503
+ hidden_gate_extract = hidden_gate_extract.replace("<upstream_fix_commit>", str(candidate.get("upstream_fix_commit") or "")).replace(
504
+ "<hidden_test_file>", hidden_test_file
505
+ )
506
+ goal = f"dayjs exam {exam_id}: {candidate.get('task_spec', {}).get('title') or candidate.get('id')}"
507
+ task = {
508
+ "id": "exam-task-001",
509
+ "description": (candidate.get("task_spec") or {}).get("issue_summary") or goal,
510
+ "acceptance": [
511
+ "Fix the reported Day.js behavior without seeing the hidden regression patch.",
512
+ f"Focused gate passes after hidden gate is applied: {focused_gate}",
513
+ "Promoted source diff is graded for mechanism shape against upstream, not byte identity.",
514
+ ],
515
+ "risk_tier": 1,
516
+ "status": "pending",
517
+ "depends_on": [],
518
+ "declared_files": [source_file],
519
+ "attempt_count": 0,
520
+ "produced_artifacts": [],
521
+ }
522
+ plan = {"version": 1, "goal": goal, "tasks": [task]}
523
+
524
+ with JobLock(job_dir / ".lock"):
525
+ for folder in ["artifacts", "patches", "sandbox", "checkpoints"]:
526
+ (job_dir / folder).mkdir(parents=True, exist_ok=True)
527
+ exam_dir = job_dir / "artifacts" / "exam"
528
+ exam_dir.mkdir(parents=True, exist_ok=True)
529
+ visible_spec = {
530
+ "exam_id": exam_id,
531
+ "candidate_id": candidate.get("id"),
532
+ "difficulty": candidate.get("difficulty"),
533
+ "title": (candidate.get("task_spec") or {}).get("title"),
534
+ "issue_summary": (candidate.get("task_spec") or {}).get("issue_summary"),
535
+ "source_issue_urls": candidate.get("source_issue_urls") or [],
536
+ "creator_declared_files": [source_file],
537
+ "focused_gate_command": focused_gate,
538
+ }
539
+ hidden_manifest = {
540
+ "exam_id": exam_id,
541
+ "candidate_id": candidate.get("id"),
542
+ "upstream_fix_commit": candidate.get("upstream_fix_commit"),
543
+ "upstream_fix_commit_parent": expected_checkout,
544
+ "upstream_commit_url": candidate.get("upstream_commit_url"),
545
+ "hidden_test_file": hidden_test_file,
546
+ "hidden_gate_extraction_command": hidden_gate_extract,
547
+ "upstream_changed_files": candidate.get("declared_files") or [],
548
+ "source_changes": candidate.get("source_changes"),
549
+ "test_changes": candidate.get("test_changes"),
550
+ }
551
+ code, hidden_patch, hidden_patch_err = git_capture(project, ["show", str(candidate.get("upstream_fix_commit") or ""), "--", hidden_test_file])
552
+ if code != 0:
553
+ raise SystemExit(f"Failed to extract hidden gate patch: {hidden_patch_err}")
554
+ hidden_patch_path = exam_dir / "hidden-gate.patch"
555
+ hidden_patch_path.write_text(hidden_patch + ("\n" if hidden_patch and not hidden_patch.endswith("\n") else ""), encoding="utf-8")
556
+ hidden_manifest.update(
557
+ {
558
+ "hidden_gate_patch": "artifacts/exam/hidden-gate.patch",
559
+ "hidden_gate_patch_sha256": sha256_file(hidden_patch_path),
560
+ }
561
+ )
562
+ write_json(exam_dir / "visible-task-spec.json", visible_spec)
563
+ write_json(exam_dir / "hidden-gate-manifest.json", hidden_manifest)
564
+ write_json(exam_dir / "exam-pack-excerpt.json", {"pack": {"name": pack.get("name"), "version": pack.get("version")}, "candidate": candidate})
565
+ write_json(job_dir / "plan.json", plan)
566
+
567
+ log = JobEventLog(job_dir / "events.jsonl", job_id)
568
+ log.append("job_created", {"goal": goal, "project": str(project)})
569
+ log.append(
570
+ "exam_pack_loaded",
571
+ {
572
+ "exam": "dayjs",
573
+ "pack": str(pack_path),
574
+ "pack_sha256": sha256_file(pack_path),
575
+ "pack_name": pack.get("name"),
576
+ "pack_version": pack.get("version"),
577
+ "verified_on": pack.get("verified_on"),
578
+ },
579
+ )
580
+ log.append("exam_target_repo_state_recorded", repo_state)
581
+ log.append("exam_candidate_selected", {**hidden_manifest, "visible_task_spec": "artifacts/exam/visible-task-spec.json"})
582
+ log.append(
583
+ "exam_prediction_recorded",
584
+ {
585
+ "exam_id": exam_id,
586
+ "candidate_id": candidate.get("id"),
587
+ "prediction": prediction,
588
+ "prediction_source": "cli_override" if args.prediction else "exam_pack_preregistered_predictions",
589
+ "notes": args.prediction_notes or "",
590
+ },
591
+ )
592
+ log.append("task_added", task)
593
+ task_hash = canonical_task_hash([task])
594
+ log.append(
595
+ "plan_committed",
596
+ {
597
+ "revision": 1,
598
+ "task_count": 1,
599
+ "task_hash": task_hash,
600
+ "source": "dayjs_exam_pack",
601
+ "candidate": exam_id,
602
+ },
603
+ )
604
+ log.append("job_paused", {"reason": "dayjs_exam_scaffold_created_no_execution"})
605
+
606
+ events = read_events(job_dir / "events.jsonl")
607
+ state = replay(events)
608
+ materialized = materialize_plan_from_events(events, plan)
609
+ write_json(job_dir / "plan.json", materialized)
610
+ write_state_snapshot(job_dir / "job.json", state)
611
+ write_handoff(job_dir, state_to_json(state), materialized)
612
+
613
+ print(f"Created dayjs exam job: {job_id}")
614
+ print(f"Candidate: {exam_id} / {candidate.get('id')}")
615
+ print(f"Project HEAD: {repo_state.get('current_head')}")
616
+ print(f"Expected pre-fix checkout: {expected_checkout}")
617
+ print(f"Prediction: {prediction}")
618
+ print(f"Job dir: {job_dir}")
619
+ return 0
620
+
621
+
622
+ def endpoint_health(endpoint: str) -> dict[str, Any]:
623
+ def parse_model_ids(text: str) -> list[str]:
624
+ try:
625
+ payload = json.loads(text)
626
+ except json.JSONDecodeError:
627
+ return []
628
+ ids: list[str] = []
629
+ for key in ("data", "models"):
630
+ rows = payload.get(key)
631
+ if not isinstance(rows, list):
632
+ continue
633
+ for row in rows:
634
+ if not isinstance(row, dict):
635
+ continue
636
+ for field in ("id", "name", "model"):
637
+ value = row.get(field)
638
+ if isinstance(value, str) and value and value not in ids:
639
+ ids.append(value)
640
+ return ids
641
+
642
+ url = endpoint.rstrip("/") + "/models"
643
+ try:
644
+ with urllib.request.urlopen(url, timeout=2) as response:
645
+ body = response.read().decode("utf-8", errors="replace")
646
+ return {"ok": True, "url": url, "status": response.status, "sample": body[:200], "model_ids": parse_model_ids(body)}
647
+ except Exception as exc: # noqa: BLE001 - health checks should record all failures.
648
+ urllib_error = str(exc)
649
+ try:
650
+ result = subprocess.run(
651
+ ["curl", "-sS", "--max-time", "5", "-w", "\n__HTTP_STATUS__:%{http_code}", url],
652
+ text=True,
653
+ capture_output=True,
654
+ timeout=8,
655
+ check=False,
656
+ )
657
+ marker = "\n__HTTP_STATUS__:"
658
+ body, _, raw_status = result.stdout.rpartition(marker)
659
+ status = int(raw_status.strip() or "0")
660
+ if result.returncode == 0 and 200 <= status < 500:
661
+ return {"ok": True, "url": url, "status": status, "sample": body[:200], "model_ids": parse_model_ids(body), "transport": "curl_fallback"}
662
+ return {
663
+ "ok": False,
664
+ "url": url,
665
+ "error": result.stderr.strip() or f"curl_status_{status}",
666
+ "urllib_error": urllib_error,
667
+ "transport": "curl_fallback",
668
+ }
669
+ except Exception as exc: # noqa: BLE001 - health checks should record all failures.
670
+ return {"ok": False, "url": url, "error": str(exc), "urllib_error": urllib_error}
671
+
672
+
673
+ def inventory_declared_files(project: Path, task: dict[str, Any], artifact_dir: Path) -> list[dict[str, Any]]:
674
+ rows: list[dict[str, Any]] = []
675
+ for rel in task.get("declared_files", []):
676
+ path = project / str(rel)
677
+ row: dict[str, Any] = {
678
+ "path": str(rel),
679
+ "exists": path.exists(),
680
+ }
681
+ if path.exists() and path.is_file():
682
+ text = path.read_text(encoding="utf-8", errors="replace")
683
+ row.update(
684
+ {
685
+ "bytes": path.stat().st_size,
686
+ "lines": len(text.splitlines()),
687
+ "sha256": sha256_file(path),
688
+ }
689
+ )
690
+ if str(rel) == "server.js":
691
+ row["api_routes"] = sorted(set(re.findall(r"app\.(?:get|post|put|delete|patch)\(['\"]([^'\"]+)", text)))
692
+ row["websocket_mentions"] = len(re.findall(r"WebSocket|broadcast|WebSocketServer", text))
693
+ elif str(rel) == "App.jsx":
694
+ row["state_hooks"] = len(re.findall(r"\buseState\s*\(", text))
695
+ row["effect_hooks"] = len(re.findall(r"\buseEffect\s*\(", text))
696
+ row["api_calls"] = sorted(set(re.findall(r"api(?:Get|Post|Delete|Put)?\(['\"]([^'\"]+)", text)))
697
+ elif str(rel) == "package.json":
698
+ try:
699
+ package = json.loads(text)
700
+ except json.JSONDecodeError:
701
+ package = {}
702
+ row["scripts"] = sorted((package.get("scripts") or {}).keys())
703
+ row["dependencies"] = sorted((package.get("dependencies") or {}).keys())
704
+ row["dev_dependencies"] = sorted((package.get("devDependencies") or {}).keys())
705
+ elif str(rel) == "mumpixdb-core.js":
706
+ row["class_mentions"] = sorted(set(re.findall(r"\bclass\s+([A-Za-z0-9_]+)", text)))
707
+ row["method_mentions"] = sorted(set(re.findall(r"\b(?:async\s+)?([A-Za-z0-9_]+)\s*\(", text)))[:80]
708
+ rows.append(row)
709
+
710
+ artifact_dir.mkdir(parents=True, exist_ok=True)
711
+ inventory_json = artifact_dir / "inventory.json"
712
+ inventory_md = artifact_dir / "inventory.md"
713
+ inventory_json.write_text(json.dumps({"task": task["id"], "declared_files": rows}, indent=2) + "\n", encoding="utf-8")
714
+ lines = [
715
+ f"# Task {task['id']} Inventory",
716
+ "",
717
+ f"Description: {task.get('description')}",
718
+ "",
719
+ "## Declared Files",
720
+ "",
721
+ ]
722
+ for row in rows:
723
+ lines.append(f"### `{row['path']}`")
724
+ lines.append("")
725
+ lines.append(f"- Exists: `{row['exists']}`")
726
+ if row.get("exists"):
727
+ lines.append(f"- Lines: `{row.get('lines')}`")
728
+ lines.append(f"- Bytes: `{row.get('bytes')}`")
729
+ lines.append(f"- SHA256: `{row.get('sha256')}`")
730
+ if row.get("api_routes"):
731
+ lines.append(f"- API routes: {', '.join(f'`{route}`' for route in row['api_routes'])}")
732
+ if row.get("scripts"):
733
+ lines.append(f"- Package scripts: {', '.join(f'`{script}`' for script in row['scripts'])}")
734
+ if row.get("state_hooks") is not None:
735
+ lines.append(f"- React state hooks: `{row.get('state_hooks')}`")
736
+ lines.append(f"- React effect hooks: `{row.get('effect_hooks')}`")
737
+ lines.append("")
738
+ inventory_md.write_text("\n".join(lines), encoding="utf-8")
739
+ return [
740
+ {"path": str(inventory_json.relative_to(artifact_dir.parent.parent)), "sha256": sha256_file(inventory_json)},
741
+ {"path": str(inventory_md.relative_to(artifact_dir.parent.parent)), "sha256": sha256_file(inventory_md)},
742
+ ]
743
+
744
+
745
+ def normalize_declared_path(path: str) -> str:
746
+ value = str(path).replace("\\", "/").strip()
747
+ if not value or value.startswith("/") or value.startswith("../") or "/../" in value or value == "..":
748
+ raise ValueError(f"unsafe relative path: {path}")
749
+ return value
750
+
751
+
752
+ def declared_file_set(task: dict[str, Any]) -> set[str]:
753
+ return {normalize_declared_path(str(path)) for path in task.get("declared_files", [])}
754
+
755
+
756
+ def latest_passed_artifacts_by_path(job_dir: Path, plan: dict[str, Any]) -> dict[str, dict[str, Any]]:
757
+ latest: dict[str, dict[str, Any]] = {}
758
+ tasks = plan.get("tasks") if isinstance(plan.get("tasks"), list) else []
759
+ for task in tasks:
760
+ if task.get("status") != "passed":
761
+ continue
762
+ task_id = str(task.get("id") or "")
763
+ for artifact in task.get("produced_artifacts") or []:
764
+ if not isinstance(artifact, dict):
765
+ continue
766
+ rel = artifact.get("path")
767
+ sandbox_path = artifact.get("sandbox_path")
768
+ if not isinstance(rel, str) or not isinstance(sandbox_path, str):
769
+ continue
770
+ absolute_path = job_dir / sandbox_path
771
+ if not absolute_path.exists() or not absolute_path.is_file():
772
+ continue
773
+ latest[normalize_declared_path(rel)] = {
774
+ "path": normalize_declared_path(rel),
775
+ "source": "passed_sandbox_artifact",
776
+ "source_task_id": task_id,
777
+ "sandbox_path": sandbox_path,
778
+ "absolute_path": str(absolute_path),
779
+ "sha256": artifact.get("sha256") or sha256_file(absolute_path),
780
+ }
781
+ return latest
782
+
783
+
784
+ def read_declared_file_context(project: Path, task: dict[str, Any], effective_sources: dict[str, dict[str, Any]] | None = None) -> list[dict[str, Any]]:
785
+ files: list[dict[str, Any]] = []
786
+ for rel in task.get("declared_files", []):
787
+ rel_path = normalize_declared_path(str(rel))
788
+ source = (effective_sources or {}).get(rel_path)
789
+ path = Path(str(source["absolute_path"])).resolve() if source else (project / rel_path).resolve()
790
+ project_root = project.resolve()
791
+ sandbox_source = bool(source and source.get("source") == "passed_sandbox_artifact")
792
+ if not sandbox_source and project_root != path and project_root not in path.parents:
793
+ raise ValueError(f"declared file escapes project: {rel_path}")
794
+ if path.exists() and path.is_file():
795
+ content = path.read_text(encoding="utf-8", errors="replace")
796
+ files.append(
797
+ {
798
+ "path": rel_path,
799
+ "exists": True,
800
+ "content": content,
801
+ "sha256": sha256_text(content),
802
+ "source": source.get("source") if source else "project_file",
803
+ "source_task_id": source.get("source_task_id") if source else None,
804
+ "sandbox_path": source.get("sandbox_path") if source else None,
805
+ "absolute_path": str(path),
806
+ }
807
+ )
808
+ else:
809
+ files.append(
810
+ {
811
+ "path": rel_path,
812
+ "exists": False,
813
+ "content": "",
814
+ "sha256": None,
815
+ "source": source.get("source") if source else "project_file",
816
+ "source_task_id": source.get("source_task_id") if source else None,
817
+ "sandbox_path": source.get("sandbox_path") if source else None,
818
+ "absolute_path": str(path),
819
+ }
820
+ )
821
+ return files
822
+
823
+
824
+ def strip_json_fences(text: str) -> str:
825
+ value = text.strip()
826
+ if value.startswith("```"):
827
+ value = re.sub(r"^```(?:[a-zA-Z0-9_-]+)?\s*", "", value)
828
+ value = re.sub(r"\s*```$", "", value)
829
+ return value.strip()
830
+
831
+
832
+ def strip_json_trailing_commas(text: str) -> str:
833
+ return re.sub(r",(\s*[\]}])", r"\1", text)
834
+
835
+
836
+ def parse_creator_json_with_repair(raw_text: str) -> tuple[dict[str, Any], dict[str, Any] | None]:
837
+ repair_types: list[str] = []
838
+ text = strip_json_fences(raw_text)
839
+ if text != raw_text.strip():
840
+ repair_types.append("strip_markdown_fence")
841
+ try:
842
+ payload = json.loads(text)
843
+ except json.JSONDecodeError as exc:
844
+ # Local reasoning models often add prose around JSON. Accept a single JSON
845
+ # object if it can be recovered unambiguously; otherwise attribute the
846
+ # failure to the Creator contract, not to the reviewer.
847
+ start = text.find("{")
848
+ end = text.rfind("}")
849
+ if start < 0 or end <= start:
850
+ raise ValueError(f"creator_output_malformed: {exc}") from exc
851
+ candidate = text[start : end + 1]
852
+ if candidate != text:
853
+ repair_types.append("extract_json_object")
854
+ try:
855
+ payload = json.loads(candidate)
856
+ except json.JSONDecodeError as inner:
857
+ stripped_commas = strip_json_trailing_commas(candidate)
858
+ if stripped_commas != candidate:
859
+ repair_types.append("strip_trailing_commas_before_json_closer")
860
+ try:
861
+ payload = json.loads(stripped_commas)
862
+ except json.JSONDecodeError:
863
+ raise ValueError(f"creator_output_malformed: {inner}") from inner
864
+ if not isinstance(payload, dict):
865
+ raise ValueError("creator_output_malformed: root must be an object")
866
+ files = payload.get("files")
867
+ edits = payload.get("edits")
868
+ if not isinstance(files, list) and not isinstance(edits, list):
869
+ raise ValueError("creator_output_malformed: files or edits must be a list")
870
+ if isinstance(files, list) and isinstance(edits, list):
871
+ raise ValueError("creator_output_malformed: files and edits are mutually exclusive")
872
+ if isinstance(files, list):
873
+ for index, file_payload in enumerate(files):
874
+ if not isinstance(file_payload, dict):
875
+ raise ValueError(f"creator_output_malformed: files[{index}] must be an object")
876
+ if not isinstance(file_payload.get("path"), str) or not file_payload.get("path"):
877
+ raise ValueError(f"creator_output_malformed: files[{index}].path must be a non-empty string")
878
+ content = file_payload.get("content")
879
+ content_lines = file_payload.get("content_lines")
880
+ if isinstance(content, str):
881
+ pass
882
+ elif isinstance(content_lines, list) and all(isinstance(line, str) for line in content_lines):
883
+ file_payload["content"] = "\n".join(content_lines)
884
+ file_payload.pop("content_lines", None)
885
+ else:
886
+ raise ValueError(f"creator_output_malformed: files[{index}] needs content string or content_lines string list")
887
+ if isinstance(edits, list):
888
+ for index, edit in enumerate(edits):
889
+ if not isinstance(edit, dict):
890
+ raise ValueError(f"creator_output_malformed: edits[{index}] must be an object")
891
+ if not isinstance(edit.get("path"), str) or not edit.get("path"):
892
+ raise ValueError(f"creator_output_malformed: edits[{index}].path must be a non-empty string")
893
+ if not isinstance(edit.get("before"), str) or not edit.get("before"):
894
+ raise ValueError(f"creator_output_malformed: edits[{index}].before must be a non-empty string")
895
+ if not isinstance(edit.get("after"), str):
896
+ raise ValueError(f"creator_output_malformed: edits[{index}].after must be a string")
897
+ notes = payload.get("notes")
898
+ if notes is not None and not isinstance(notes, str):
899
+ raise ValueError("creator_output_malformed: notes must be a string when present")
900
+ repair = None
901
+ if repair_types:
902
+ repair = {"types": repair_types, "applied": True}
903
+ return payload, repair
904
+
905
+
906
+ def parse_creator_json(raw_text: str) -> dict[str, Any]:
907
+ payload, _repair = parse_creator_json_with_repair(raw_text)
908
+ return payload
909
+
910
+
911
+ def coerce_single_file_creator_output(raw_text: str, allowed: set[str]) -> tuple[dict[str, Any], str] | None:
912
+ if len(allowed) != 1:
913
+ return None
914
+ rel = next(iter(allowed))
915
+ text = raw_text.strip()
916
+ if not text:
917
+ return None
918
+ if text.startswith("{") or text.startswith("["):
919
+ return None
920
+ # Recovery path for local models that follow the spirit of the request
921
+ # (full file content) but miss the JSON envelope. This is intentionally
922
+ # limited to one declared file so the path is not guessed.
923
+ if text.startswith("```"):
924
+ content = strip_json_fences(text)
925
+ else:
926
+ content = strip_reasoning_like_text(text)
927
+ if not content.strip():
928
+ return None
929
+ return {"files": [{"path": rel, "content": content}], "notes": "Recovered from single declared file raw code output."}, "single_file_raw_code"
930
+
931
+
932
+ def strip_reasoning_like_text(text: str) -> str:
933
+ value = re.sub(r"(?is)<think>.*?</think>", "", text)
934
+ value = re.sub(r"(?is)<reasoning>.*?</reasoning>", "", value)
935
+ return value.strip()
936
+
937
+
938
+ def select_creator_output_contract(
939
+ task: dict[str, Any], file_context: list[dict[str, Any]], reserved_output_tokens: int
940
+ ) -> dict[str, Any]:
941
+ declared = [str(path) for path in task.get("declared_files", [])]
942
+ if len(declared) != 1 or len(file_context) != 1 or not file_context[0].get("exists"):
943
+ return {"mode": "full_replacement", "reason": "task_shape_requires_file_payload"}
944
+ content = str(file_context[0].get("content") or "")
945
+ estimated_tokens = (len(content) + 3) // 4
946
+ effective_output_tokens = reserved_output_tokens if reserved_output_tokens > 0 else 4096
947
+ threshold = max(512, int(effective_output_tokens * ANCHORED_EDIT_OUTPUT_HEADROOM_RATIO))
948
+ if estimated_tokens < threshold:
949
+ return {
950
+ "mode": "full_replacement",
951
+ "reason": "replacement_estimate_fits_output_headroom",
952
+ "estimated_full_replacement_tokens": estimated_tokens,
953
+ "anchored_edit_threshold_tokens": threshold,
954
+ }
955
+ return {
956
+ "mode": "anchored_edits",
957
+ "reason": "replacement_estimate_exceeds_output_headroom",
958
+ "estimated_full_replacement_tokens": estimated_tokens,
959
+ "anchored_edit_threshold_tokens": threshold,
960
+ "max_edits": MAX_ANCHORED_EDITS,
961
+ }
962
+
963
+
964
+ def anchored_edit_response_schema(allowed: set[str], max_edits: int = MAX_ANCHORED_EDITS) -> dict[str, Any]:
965
+ paths = sorted(normalize_declared_path(path) for path in allowed)
966
+ if not paths:
967
+ raise ValueError("anchored edit schema requires at least one declared path")
968
+ return {
969
+ "type": "object",
970
+ "properties": {
971
+ "edits": {
972
+ "type": "array",
973
+ "minItems": 1,
974
+ "maxItems": max_edits,
975
+ "items": {
976
+ "type": "object",
977
+ "properties": {
978
+ "path": {"type": "string", "enum": paths},
979
+ "before": {"type": "string", "minLength": 1},
980
+ "after": {"type": "string"},
981
+ },
982
+ "required": ["path", "before", "after"],
983
+ "additionalProperties": False,
984
+ },
985
+ },
986
+ "notes": {"type": "string"},
987
+ },
988
+ "required": ["edits"],
989
+ "additionalProperties": False,
990
+ }
991
+
992
+
993
+ def materialize_anchored_edits(
994
+ payload: dict[str, Any], file_context: list[dict[str, Any]], allowed: set[str]
995
+ ) -> tuple[dict[str, Any], list[dict[str, Any]]]:
996
+ if "files" in payload:
997
+ raise ValueError("creator_output_contract_violation: anchored mode does not accept replacement files")
998
+ edits = payload.get("edits")
999
+ if not isinstance(edits, list) or not edits:
1000
+ raise ValueError("creator_output_contract_violation: anchored mode requires at least one edit")
1001
+ if len(edits) > MAX_ANCHORED_EDITS:
1002
+ raise ValueError(f"creator_output_contract_violation: anchored mode allows at most {MAX_ANCHORED_EDITS} edits")
1003
+ originals = {
1004
+ normalize_declared_path(str(item.get("path") or "")): str(item.get("content") or "")
1005
+ for item in file_context
1006
+ if item.get("exists")
1007
+ }
1008
+ by_path: dict[str, list[tuple[int, int, str, str]]] = {}
1009
+ metadata: list[dict[str, Any]] = []
1010
+ for index, edit in enumerate(edits):
1011
+ rel = normalize_declared_path(str(edit.get("path") or ""))
1012
+ if rel not in allowed:
1013
+ raise ValueError(f"creator_output_contract_violation: anchored edit path is not declared: {rel}")
1014
+ if rel not in originals:
1015
+ raise ValueError(f"creator_output_contract_violation: anchored edit target does not exist: {rel}")
1016
+ before = str(edit.get("before") or "")
1017
+ after = edit.get("after")
1018
+ if not before or not isinstance(after, str):
1019
+ raise ValueError(f"creator_output_contract_violation: anchored edit {index} needs before and after strings")
1020
+ if before == after:
1021
+ raise ValueError(f"creator_output_contract_violation: anchored edit {index} makes no change")
1022
+ positions: list[int] = []
1023
+ cursor = 0
1024
+ while True:
1025
+ position = originals[rel].find(before, cursor)
1026
+ if position < 0:
1027
+ break
1028
+ positions.append(position)
1029
+ cursor = position + 1
1030
+ if len(positions) != 1:
1031
+ raise ValueError(
1032
+ f"creator_output_contract_violation: anchored edit {index} before text matched {len(positions)} times in {rel}; exactly one required"
1033
+ )
1034
+ start = positions[0]
1035
+ end = start + len(before)
1036
+ by_path.setdefault(rel, []).append((start, end, before, after))
1037
+ metadata.append(
1038
+ {
1039
+ "index": index,
1040
+ "path": rel,
1041
+ "start": start,
1042
+ "before_bytes": len(before.encode("utf-8")),
1043
+ "after_bytes": len(after.encode("utf-8")),
1044
+ "before_sha256": sha256_text(before),
1045
+ "after_sha256": sha256_text(after),
1046
+ }
1047
+ )
1048
+ files: list[dict[str, Any]] = []
1049
+ for rel, path_edits in by_path.items():
1050
+ ordered = sorted(path_edits, key=lambda item: item[0])
1051
+ for previous, current in zip(ordered, ordered[1:]):
1052
+ if current[0] < previous[1]:
1053
+ raise ValueError(f"creator_output_contract_violation: anchored edits overlap in {rel}")
1054
+ content = originals[rel]
1055
+ for start, end, _before, after in reversed(ordered):
1056
+ content = content[:start] + after + content[end:]
1057
+ files.append({"path": rel, "content": content})
1058
+ return {"files": files, "notes": payload.get("notes") or "Applied exact anchored edits."}, metadata
1059
+
1060
+
1061
+ def validate_creator_declared_files(payload: dict[str, Any], allowed: set[str]) -> list[str]:
1062
+ violations: list[str] = []
1063
+ seen: set[str] = set()
1064
+ for file_payload in payload.get("files", []):
1065
+ try:
1066
+ rel = normalize_declared_path(str(file_payload.get("path")))
1067
+ except ValueError:
1068
+ violations.append(str(file_payload.get("path")))
1069
+ continue
1070
+ if rel not in allowed:
1071
+ violations.append(rel)
1072
+ if rel in seen:
1073
+ violations.append(f"{rel} (duplicate)")
1074
+ seen.add(rel)
1075
+ return violations
1076
+
1077
+
1078
+ def backend_smoke_endpoint_paths(job_dir: Path | None) -> list[str]:
1079
+ if job_dir is None:
1080
+ return []
1081
+ evidence_path = job_dir / "artifacts" / "tasks" / "task-029" / "backend-smoke-evidence.json"
1082
+ if not evidence_path.exists():
1083
+ return []
1084
+ try:
1085
+ evidence = json.loads(evidence_path.read_text(encoding="utf-8"))
1086
+ except Exception: # noqa: BLE001 - reviewer evidence is best-effort.
1087
+ return []
1088
+ paths: list[str] = []
1089
+ for row in evidence.get("endpoints") or []:
1090
+ if not isinstance(row, dict):
1091
+ continue
1092
+ url = row.get("url")
1093
+ if not isinstance(url, str):
1094
+ continue
1095
+ parsed = urllib.parse.urlparse(url)
1096
+ path = parsed.path
1097
+ if path and path not in paths:
1098
+ paths.append(path)
1099
+ return paths
1100
+
1101
+
1102
+ def admin_data_client_endpoint_errors(written_text_by_path: dict[str, str], job_dir: Path | None) -> list[str]:
1103
+ relevant_paths = {"src/adminDashboard/config.js", "src/adminDashboard/dataClient.js"}
1104
+ if not any(path in written_text_by_path for path in relevant_paths):
1105
+ return []
1106
+ combined = "\n".join(written_text_by_path.values())
1107
+ required = backend_smoke_endpoint_paths(job_dir)
1108
+ missing = [path for path in required if path not in combined]
1109
+ errors = [f"admin data client missing backend-smoke endpoint {path}" for path in missing]
1110
+ forbidden = [
1111
+ "/api/admin/health",
1112
+ "/api/admin/status",
1113
+ "/api/admin/genes",
1114
+ "/api/admin/routes",
1115
+ "/api/admin/benchmarks",
1116
+ "/api/wal",
1117
+ "/api/genes",
1118
+ "/api/routes",
1119
+ "/api/dashboard",
1120
+ ]
1121
+ for path in forbidden:
1122
+ if path in combined:
1123
+ errors.append(f"admin data client uses unverified endpoint prefix {path}")
1124
+ return errors
1125
+
1126
+
1127
+ def previous_attempt_feedback(job_dir: Path, events: list[Any], task_id: str) -> list[dict[str, Any]]:
1128
+ feedback: list[dict[str, Any]] = []
1129
+ for event in events:
1130
+ if event.payload.get("task_id") != task_id:
1131
+ continue
1132
+ if event.type == "task_failed":
1133
+ item: dict[str, Any] = {
1134
+ "event": event.type,
1135
+ "seq": event.seq,
1136
+ "error": event.payload.get("error"),
1137
+ "message": event.payload.get("message"),
1138
+ "reason": event.payload.get("reason"),
1139
+ "failure_feedback": event.payload.get("failure_feedback"),
1140
+ "review_errors": event.payload.get("review_errors"),
1141
+ "attempt": event.payload.get("attempt"),
1142
+ }
1143
+ response_path = event.payload.get("response_path")
1144
+ if isinstance(response_path, str):
1145
+ path = job_dir / response_path
1146
+ if path.exists():
1147
+ try:
1148
+ response = json.loads(path.read_text(encoding="utf-8"))
1149
+ content = str(response.get("content") or "")
1150
+ item["response_content_chars"] = len(content)
1151
+ item["response_head"] = content[:1200]
1152
+ item["response_tail"] = content[-2000:]
1153
+ item["finish_reason"] = response.get("finish_reason")
1154
+ item["reasoning_chars"] = len(str(response.get("reasoning_content") or ""))
1155
+ except Exception as exc: # noqa: BLE001 - feedback is best-effort.
1156
+ item["response_read_error"] = str(exc)
1157
+ feedback.append(item)
1158
+ elif event.type in {"creator_output_repaired", "declared_file_violation"}:
1159
+ feedback.append({"event": event.type, "seq": event.seq, **event.payload})
1160
+ return feedback[-6:]
1161
+
1162
+
1163
+ def build_creator_messages(
1164
+ job_dir: Path,
1165
+ project: Path,
1166
+ task: dict[str, Any],
1167
+ file_context: list[dict[str, Any]] | None = None,
1168
+ retry_feedback: list[dict[str, Any]] | None = None,
1169
+ validated_behavior: dict[str, Any] | None = None,
1170
+ output_contract: dict[str, Any] | None = None,
1171
+ ) -> list[dict[str, str]]:
1172
+ handoff_path = job_dir / "handoff.md"
1173
+ handoff = handoff_path.read_text(encoding="utf-8", errors="replace") if handoff_path.exists() else ""
1174
+ files = file_context if file_context is not None else read_declared_file_context(project, task)
1175
+ declared = [str(path) for path in task.get("declared_files", [])]
1176
+ conventions = project_conventions(project, job_dir)
1177
+ js_rule = str(conventions.get("javascript_import_rule") or "Follow the existing project module syntax.")
1178
+ contract = output_contract or select_creator_output_contract(task, files, 4096)
1179
+ if contract.get("mode") == "anchored_edits":
1180
+ system = "\n".join(
1181
+ [
1182
+ "You are the Socra Creator agent.",
1183
+ f"Project JavaScript module rule: {js_rule}",
1184
+ f"This task has exactly one large declared file: {declared[0]}.",
1185
+ "Return only valid JSON. Do not return Markdown, prose outside JSON, a unified diff, or a full replacement file.",
1186
+ "Output schema:",
1187
+ '{"edits":[{"path":"relative/path","before":"small exact unique existing text","after":"replacement text"}],"notes":"short implementation notes"}',
1188
+ "Each before value must match the existing file exactly once. Include only enough unchanged context to make it unique.",
1189
+ f"Return at most {int(contract.get('max_edits') or MAX_ANCHORED_EDITS)} non-overlapping edits.",
1190
+ "You may only edit the declared path. Do not use line numbers as anchors.",
1191
+ ]
1192
+ )
1193
+ elif len(declared) == 1:
1194
+ system = "\n".join(
1195
+ [
1196
+ "You are the Socra Creator agent.",
1197
+ f"Project JavaScript module rule: {js_rule}",
1198
+ f"This task has exactly one declared file: {declared[0]}.",
1199
+ "Return only the complete replacement file content for that one file.",
1200
+ "Do not return JSON, Markdown explanation, diffs, or patches.",
1201
+ "A code fence is allowed but not required.",
1202
+ "You may not create or modify any other path.",
1203
+ ]
1204
+ )
1205
+ else:
1206
+ system = "\n".join(
1207
+ [
1208
+ "You are the Socra Creator agent.",
1209
+ f"Project JavaScript module rule: {js_rule}",
1210
+ "Return only valid JSON. Do not use Markdown fences unless unavoidable.",
1211
+ "Output schema:",
1212
+ '{"files":[{"path":"relative/path","content_lines":["full replacement file content line 1","line 2"]}],"notes":"short implementation notes"}',
1213
+ "Prefer content_lines for code so newlines do not need JSON escaping. content as one string is also accepted.",
1214
+ "You may only write paths listed in the declared files. Any other path fails the task.",
1215
+ "Return full file contents, not diffs or patches.",
1216
+ "If a declared file does not need changes, omit it from files and explain in notes.",
1217
+ ]
1218
+ )
1219
+ if validated_behavior:
1220
+ system += "\nA validated PREFER behavior is included as advisory context. Derive a fresh fix for this task; do not copy a prior patch, and do not bypass any gate."
1221
+ user = json.dumps(
1222
+ {
1223
+ "prompt_layout": CREATOR_PROMPT_LAYOUT,
1224
+ "project_conventions": conventions,
1225
+ "handoff_md": handoff,
1226
+ "declared_file_contents": files,
1227
+ "task": {
1228
+ "id": task.get("id"),
1229
+ "description": task.get("description"),
1230
+ "acceptance": task.get("acceptance", []),
1231
+ "risk_tier": task.get("risk_tier"),
1232
+ "declared_files": task.get("declared_files", []),
1233
+ },
1234
+ "previous_attempt_feedback": retry_feedback or [],
1235
+ "validated_behavior_preference": validated_behavior,
1236
+ "output_contract": contract,
1237
+ },
1238
+ indent=2,
1239
+ )
1240
+ return [{"role": "system", "content": system}, {"role": "user", "content": user}]
1241
+
1242
+
1243
+ def call_creator_model(
1244
+ creator: dict[str, Any],
1245
+ messages: list[dict[str, str]],
1246
+ *,
1247
+ json_mode: bool = True,
1248
+ response_schema: dict[str, Any] | None = None,
1249
+ ) -> dict[str, Any]:
1250
+ endpoint = str(creator.get("endpoint") or "http://127.0.0.1:11439/v1").rstrip("/")
1251
+ url = endpoint + "/chat/completions"
1252
+ max_tokens = int(creator.get("max_output_tokens", 4096))
1253
+ payload = {
1254
+ "model": creator.get("model_alias") or creator.get("model") or "Socra-14B-Coder",
1255
+ "messages": messages,
1256
+ "temperature": float(creator.get("temperature", 0.2)),
1257
+ "max_tokens": max_tokens,
1258
+ }
1259
+ seed = creator.get("seed")
1260
+ if seed is not None:
1261
+ payload["seed"] = int(seed)
1262
+ if response_schema is not None:
1263
+ # llama.cpp's enforced schema transport is json_object + schema. Its
1264
+ # json_schema variant is accepted by some builds without constraining
1265
+ # output, so do not use acceptance of the request as proof of grammar use.
1266
+ payload["response_format"] = {"type": "json_object", "schema": response_schema}
1267
+ # llama.cpp applies response grammars to the answer channel. Keeping a
1268
+ # reasoning channel active has historically allowed unconstrained output.
1269
+ payload["chat_template_kwargs"] = {"enable_thinking": False}
1270
+ elif json_mode:
1271
+ payload["response_format"] = {"type": "json_object"}
1272
+ timeout_seconds = int(creator.get("timeout_seconds") or max(240, min(1800, max_tokens // 8)))
1273
+ request = urllib.request.Request(
1274
+ url,
1275
+ data=json.dumps(payload).encode("utf-8"),
1276
+ headers={"Content-Type": "application/json"},
1277
+ method="POST",
1278
+ )
1279
+ try:
1280
+ with urllib.request.urlopen(request, timeout=timeout_seconds) as response:
1281
+ body = response.read().decode("utf-8", errors="replace")
1282
+ except urllib.error.HTTPError as exc:
1283
+ body = exc.read().decode("utf-8", errors="replace")
1284
+ raise RuntimeError(f"creator_http_{exc.code}: {body[:2000]}") from exc
1285
+ data = json.loads(body)
1286
+ choice = (data.get("choices") or [{}])[0]
1287
+ message = choice.get("message") or {}
1288
+ content = message.get("content")
1289
+ if not isinstance(content, str):
1290
+ content = ""
1291
+ reasoning_content = message.get("reasoning_content")
1292
+ if not isinstance(reasoning_content, str):
1293
+ reasoning_content = ""
1294
+ return {
1295
+ "url": url,
1296
+ "request_model": payload["model"],
1297
+ "response_model": data.get("model"),
1298
+ "content": content,
1299
+ "reasoning_content": reasoning_content,
1300
+ "finish_reason": choice.get("finish_reason"),
1301
+ "usage": data.get("usage") if isinstance(data.get("usage"), dict) else {},
1302
+ "request_max_tokens": max_tokens,
1303
+ "request_temperature": payload["temperature"],
1304
+ "request_seed": payload.get("seed"),
1305
+ "json_mode": json_mode,
1306
+ "structured_output_schema_sha256": (
1307
+ recurrence_sha256_json(response_schema) if response_schema is not None else None
1308
+ ),
1309
+ "structured_output_mode": "json_schema" if response_schema is not None else ("json_object" if json_mode else None),
1310
+ "structured_output_transport": (
1311
+ "llama_cpp_response_format_json_object_schema" if response_schema is not None else None
1312
+ ),
1313
+ "thinking_disabled_for_schema": response_schema is not None,
1314
+ "raw": data,
1315
+ }
1316
+
1317
+
1318
+ def atomic_write_creator_sandbox(job_dir: Path, task_id: str, payload: dict[str, Any]) -> tuple[Path, list[dict[str, Any]]]:
1319
+ sandbox_root = job_dir / "sandbox" / "tasks"
1320
+ final_dir = sandbox_root / task_id
1321
+ sandbox_root.mkdir(parents=True, exist_ok=True)
1322
+ temp_parent = job_dir / "sandbox" / ".tmp"
1323
+ temp_parent.mkdir(parents=True, exist_ok=True)
1324
+ temp_dir = Path(tempfile.mkdtemp(prefix=f"{task_id}-", dir=temp_parent))
1325
+ written: list[dict[str, Any]] = []
1326
+ try:
1327
+ for file_payload in payload.get("files", []):
1328
+ rel = normalize_declared_path(str(file_payload["path"]))
1329
+ target = temp_dir / rel
1330
+ target.parent.mkdir(parents=True, exist_ok=True)
1331
+ content = str(file_payload["content"])
1332
+ target.write_text(content, encoding="utf-8")
1333
+ written.append(
1334
+ {
1335
+ "path": rel,
1336
+ "absolute_path": str(target),
1337
+ "sha256": sha256_text(content),
1338
+ "bytes": len(content.encode("utf-8")),
1339
+ "lines": len(content.splitlines()),
1340
+ }
1341
+ )
1342
+ if final_dir.exists():
1343
+ shutil.rmtree(final_dir)
1344
+ temp_dir.rename(final_dir)
1345
+ for row in written:
1346
+ row["absolute_path"] = str(final_dir / str(row["path"]))
1347
+ return final_dir, written
1348
+ except Exception:
1349
+ shutil.rmtree(temp_dir, ignore_errors=True)
1350
+ raise
1351
+
1352
+
1353
+ def preserve_existing_final_newlines(
1354
+ payload: dict[str, Any], file_context: list[dict[str, Any]]
1355
+ ) -> tuple[dict[str, Any], list[dict[str, Any]]]:
1356
+ originals = {
1357
+ normalize_declared_path(str(item.get("path") or "")): str(item.get("content") or "")
1358
+ for item in file_context
1359
+ if item.get("exists") and str(item.get("content") or "")
1360
+ }
1361
+ normalized_files: list[dict[str, Any]] = []
1362
+ changes: list[dict[str, Any]] = []
1363
+ for file_payload in payload.get("files", []):
1364
+ row = dict(file_payload)
1365
+ rel = normalize_declared_path(str(row.get("path") or ""))
1366
+ content = str(row.get("content") or "")
1367
+ original = originals.get(rel)
1368
+ if original is not None:
1369
+ original_has_newline = original.endswith("\n")
1370
+ generated_has_newline = content.endswith(("\n", "\r"))
1371
+ if original_has_newline and not generated_has_newline:
1372
+ content += "\n"
1373
+ elif not original_has_newline and generated_has_newline:
1374
+ content = content.rstrip("\r\n")
1375
+ if content != str(row.get("content") or ""):
1376
+ changes.append(
1377
+ {
1378
+ "path": rel,
1379
+ "original_has_final_newline": original_has_newline,
1380
+ "generated_had_final_newline": generated_has_newline,
1381
+ "normalized_has_final_newline": content.endswith("\n"),
1382
+ "policy": "preserve_existing_file_final_newline_state",
1383
+ }
1384
+ )
1385
+ row["content"] = content
1386
+ normalized_files.append(row)
1387
+ return {**payload, "files": normalized_files}, changes
1388
+
1389
+
1390
+ def js_syntax_check(project: Path, source_path: Path, rel_path: str) -> tuple[str, str]:
1391
+ suffix = Path(rel_path).suffix.lower()
1392
+ script = f"""
1393
+ const fs = require("fs");
1394
+ const source = fs.readFileSync({json.dumps(str(source_path))}, "utf8");
1395
+ const project = {json.dumps(str(project))};
1396
+ const relPath = {json.dumps(rel_path)};
1397
+ const suffix = {json.dumps(suffix)};
1398
+ function resolveFromProject(name) {{
1399
+ try {{ return require.resolve(name, {{ paths: [project] }}); }} catch (_) {{ return null; }}
1400
+ }}
1401
+ const babelPath = resolveFromProject("@babel/parser");
1402
+ const typescriptPath = resolveFromProject("typescript");
1403
+ try {{
1404
+ if (babelPath) {{
1405
+ const parser = require(babelPath);
1406
+ parser.parse(source, {{
1407
+ sourceType: "module",
1408
+ errorRecovery: false,
1409
+ plugins: ["jsx", "typescript", "classProperties", "objectRestSpread", "dynamicImport"]
1410
+ }});
1411
+ }} else if (typescriptPath) {{
1412
+ const ts = require(typescriptPath);
1413
+ const kind = suffix === ".tsx" ? ts.ScriptKind.TSX
1414
+ : suffix === ".jsx" ? ts.ScriptKind.JSX
1415
+ : suffix === ".ts" ? ts.ScriptKind.TS
1416
+ : ts.ScriptKind.JS;
1417
+ const parsed = ts.createSourceFile(relPath, source, ts.ScriptTarget.Latest, true, kind);
1418
+ if (parsed.parseDiagnostics && parsed.parseDiagnostics.length) {{
1419
+ const message = parsed.parseDiagnostics
1420
+ .map((item) => ts.flattenDiagnosticMessageText(item.messageText, "\\n"))
1421
+ .join("; ");
1422
+ throw new Error(message);
1423
+ }}
1424
+ }} else if (suffix === ".js") {{
1425
+ process.stderr.write("CHECKER_UNAVAILABLE:node --check fallback required");
1426
+ process.exit(2);
1427
+ }} else {{
1428
+ process.stderr.write("CHECKER_UNAVAILABLE:no @babel/parser or typescript resolvable from target project");
1429
+ process.exit(2);
1430
+ }}
1431
+ }} catch (error) {{
1432
+ console.error(String(error && error.message || error));
1433
+ process.exit(1);
1434
+ }}
1435
+ """
1436
+ result = subprocess.run(
1437
+ ["node", "-e", script],
1438
+ cwd=project,
1439
+ text=True,
1440
+ capture_output=True,
1441
+ timeout=30,
1442
+ check=False,
1443
+ )
1444
+ if result.returncode == 0:
1445
+ return "passed", "ok"
1446
+ if result.returncode == 2 and "CHECKER_UNAVAILABLE:" in (result.stderr or ""):
1447
+ if suffix == ".js":
1448
+ fallback = subprocess.run(
1449
+ ["node", "--check", str(source_path)],
1450
+ cwd=project,
1451
+ text=True,
1452
+ capture_output=True,
1453
+ timeout=30,
1454
+ check=False,
1455
+ )
1456
+ if fallback.returncode == 0:
1457
+ return "passed", "ok_node_check"
1458
+ detail = (fallback.stderr or fallback.stdout or "unknown parse error").strip()
1459
+ return "failed", f"{rel_path} syntax check failed: {detail}"
1460
+ detail = (result.stderr or result.stdout).strip().replace("CHECKER_UNAVAILABLE:", "")
1461
+ return "unavailable", f"{rel_path} syntax checker unavailable: {detail}"
1462
+ detail = (result.stderr or result.stdout or "unknown parse error").strip()
1463
+ return "failed", f"{rel_path} syntax check failed: {detail}"
1464
+
1465
+
1466
+ def regression_surface_tokens(source: str) -> list[str]:
1467
+ tokens: set[str] = set()
1468
+ for match in re.findall(r"app\.(?:get|post|put|delete|patch)\(\s*['\"]([^'\"]+)['\"]", source):
1469
+ tokens.add(match)
1470
+ for match in re.findall(r"\b(?:function|class)\s+([A-Za-z_$][A-Za-z0-9_$]*)", source):
1471
+ tokens.add(match)
1472
+ for match in re.findall(r"\b(?:const|let|var)\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*=", source):
1473
+ tokens.add(match)
1474
+ keywords = ("admin", "artifact", "safe", "local", "root", "wal", "event", "gene", "route", "benchmark", "health", "normalize", "summary", "source")
1475
+ return sorted(token for token in tokens if any(key in token.lower() for key in keywords))
1476
+
1477
+
1478
+ def review_creator_sandbox(
1479
+ project: Path,
1480
+ task: dict[str, Any],
1481
+ written: list[dict[str, Any]],
1482
+ output_mode: str,
1483
+ file_context: list[dict[str, Any]] | None = None,
1484
+ job_dir: Path | None = None,
1485
+ ) -> tuple[bool, list[str], str, list[dict[str, Any]], list[str]]:
1486
+ errors: list[str] = []
1487
+ infrastructure_errors: list[str] = []
1488
+ if not isinstance(written, list):
1489
+ return False, ["written artifact list missing"], "contract", [], []
1490
+ syntax_checked = False
1491
+ written_text = ""
1492
+ written_text_by_path: dict[str, str] = {}
1493
+ for row in written:
1494
+ if not row.get("path") or not row.get("sha256"):
1495
+ errors.append(f"incomplete artifact metadata for {row!r}")
1496
+ path = str(row.get("path") or "")
1497
+ if path.endswith((".jsx", ".tsx", ".js", ".ts")):
1498
+ sandbox_path = row.get("absolute_path")
1499
+ if sandbox_path:
1500
+ try:
1501
+ content = Path(str(sandbox_path)).read_text(encoding="utf-8", errors="replace").lstrip()
1502
+ except OSError:
1503
+ content = ""
1504
+ written_text += "\n" + content
1505
+ written_text_by_path[path] = content
1506
+ if content.startswith('{"files"') or content.startswith("{'files'"):
1507
+ errors.append(f"{path} looks like an unparsed Creator JSON envelope")
1508
+ if sandbox_path and output_mode in {"single_file_raw_code", "anchored_edit_json"}:
1509
+ syntax_status, detail = js_syntax_check(project, Path(str(sandbox_path)), path)
1510
+ syntax_checked = True
1511
+ if syntax_status == "failed":
1512
+ errors.append(detail)
1513
+ elif syntax_status == "unavailable":
1514
+ infrastructure_errors.append(detail)
1515
+ if not written:
1516
+ errors.append("creator returned no file changes")
1517
+ errors.extend(admin_data_client_endpoint_errors(written_text_by_path, job_dir))
1518
+ regression_checks: list[dict[str, Any]] = []
1519
+ for item in file_context or []:
1520
+ if item.get("source") != "passed_sandbox_artifact":
1521
+ continue
1522
+ prior_path = item.get("sandbox_path")
1523
+ if not isinstance(prior_path, str):
1524
+ continue
1525
+ prior_abs = Path(str(item.get("absolute_path") or ""))
1526
+ if not prior_abs.exists() and prior_path:
1527
+ # file_context uses absolute_path internally but request artifacts only
1528
+ # expose sandbox_path. Keep this fallback defensive for older entries.
1529
+ prior_abs = Path(prior_path)
1530
+ if not prior_abs.exists():
1531
+ continue
1532
+ prior_text = prior_abs.read_text(encoding="utf-8", errors="replace")
1533
+ tokens = regression_surface_tokens(prior_text)
1534
+ item_path = str(item.get("path") or "")
1535
+ if item_path not in written_text_by_path:
1536
+ check = {
1537
+ "path": item.get("path"),
1538
+ "source_task_id": item.get("source_task_id"),
1539
+ "token_count": len(tokens),
1540
+ "missing": [],
1541
+ "ok": True,
1542
+ "status": "omitted_declared_file_treated_as_unchanged",
1543
+ }
1544
+ regression_checks.append(check)
1545
+ continue
1546
+ missing = [token for token in tokens if token not in written_text_by_path[item_path]]
1547
+ check = {
1548
+ "path": item.get("path"),
1549
+ "source_task_id": item.get("source_task_id"),
1550
+ "token_count": len(tokens),
1551
+ "missing": missing[:25],
1552
+ "ok": not missing,
1553
+ "status": "rewritten_file_checked",
1554
+ }
1555
+ regression_checks.append(check)
1556
+ if missing:
1557
+ errors.append(f"{item.get('path')} regression surface missing {len(missing)} prior tokens from {item.get('source_task_id')}: {', '.join(missing[:8])}")
1558
+ if syntax_checked:
1559
+ review_level = "static_syntax"
1560
+ else:
1561
+ review_level = "contract"
1562
+ return not errors and not infrastructure_errors, errors, review_level, regression_checks, infrastructure_errors
1563
+
1564
+
1565
+ def task_review_metadata(events: list[Any]) -> dict[str, dict[str, Any]]:
1566
+ reviews: dict[str, dict[str, Any]] = {}
1567
+ for event in events:
1568
+ payload = event.payload
1569
+ task_id = str(payload.get("task_id") or "")
1570
+ if not task_id:
1571
+ continue
1572
+ if event.type == "review_completed":
1573
+ reviews.setdefault(task_id, {}).update(
1574
+ {
1575
+ "review_ok": bool(payload.get("ok")),
1576
+ "review_level": payload.get("review_level") or "contract",
1577
+ "output_mode": payload.get("output_mode"),
1578
+ "review_errors": payload.get("errors") or [],
1579
+ "review_event_seq": event.seq,
1580
+ "review_event_id": event.event_id,
1581
+ }
1582
+ )
1583
+ elif event.type == "task_passed":
1584
+ reviews.setdefault(task_id, {}).update(
1585
+ {
1586
+ "task_passed": True,
1587
+ "review_level": payload.get("review_level") or reviews.get(task_id, {}).get("review_level"),
1588
+ "output_mode": payload.get("output_mode") or reviews.get(task_id, {}).get("output_mode"),
1589
+ "acceptance_review": payload.get("acceptance_review") or reviews.get(task_id, {}).get("acceptance_review") or "unspecified",
1590
+ }
1591
+ )
1592
+ elif event.type == "task_review_reclassified":
1593
+ reviews.setdefault(task_id, {}).update(
1594
+ {
1595
+ "review_level": payload.get("review_level"),
1596
+ "output_mode": payload.get("output_mode"),
1597
+ "acceptance_review": payload.get("acceptance_review"),
1598
+ "reclassified": True,
1599
+ "reclassification_reason": payload.get("reason"),
1600
+ }
1601
+ )
1602
+ elif event.type == "task_reverified":
1603
+ criteria = payload.get("criteria") if isinstance(payload.get("criteria"), list) else []
1604
+ all_passed = bool(criteria) and all(str(item.get("status")) == "pass" for item in criteria if isinstance(item, dict))
1605
+ reviews.setdefault(task_id, {}).update(
1606
+ {
1607
+ "review_level": payload.get("review_level") or ("acceptance_reverified" if all_passed else "acceptance_partial"),
1608
+ "acceptance_review": payload.get("acceptance_review") or ("full" if all_passed else "partial"),
1609
+ "reverified": True,
1610
+ "reverification_all_passed": all_passed,
1611
+ "reverification_event": event.event_id,
1612
+ "reverification_notes": payload.get("notes"),
1613
+ }
1614
+ )
1615
+ return reviews
1616
+
1617
+
1618
+ def reviewer_audit_requirements(events: list[Any]) -> dict[str, dict[str, Any]]:
1619
+ requirements: dict[str, dict[str, Any]] = {}
1620
+ for event in events:
1621
+ if event.type != "reviewer_path_audit":
1622
+ continue
1623
+ for item in event.payload.get("affected_events") or []:
1624
+ if not isinstance(item, dict):
1625
+ continue
1626
+ task_id = str(item.get("task_id") or "")
1627
+ if not task_id:
1628
+ continue
1629
+ effect = str(item.get("effect") or "")
1630
+ if "false_review_failed" in effect:
1631
+ continue
1632
+ requirements[task_id] = {
1633
+ "audit_event_seq": event.seq,
1634
+ "audit_event_id": event.event_id,
1635
+ "affected_review_seq": item.get("seq"),
1636
+ "effect": effect,
1637
+ "reason": "reviewer audit requires explicit reverification before promotion-grade milestone claims",
1638
+ }
1639
+ return requirements
1640
+
1641
+
1642
+ def tasks_requiring_milestone_reverification(events: list[Any], task_ids: list[str]) -> list[dict[str, Any]]:
1643
+ reviews = task_review_metadata(events)
1644
+ audit_requirements = reviewer_audit_requirements(events)
1645
+ needs: list[dict[str, Any]] = []
1646
+ for task_id in task_ids:
1647
+ meta = reviews.get(task_id, {})
1648
+ review_level = str(meta.get("review_level") or "")
1649
+ acceptance_review = str(meta.get("acceptance_review") or "")
1650
+ audit_requirement = audit_requirements.get(task_id)
1651
+ if audit_requirement and not meta.get("reverification_all_passed"):
1652
+ needs.append(
1653
+ {
1654
+ "task_id": task_id,
1655
+ "review_level": review_level or "unknown",
1656
+ "acceptance_review": acceptance_review or "unknown",
1657
+ "reason": audit_requirement["reason"],
1658
+ "audit_event_seq": audit_requirement.get("audit_event_seq"),
1659
+ "affected_review_seq": audit_requirement.get("affected_review_seq"),
1660
+ "effect": audit_requirement.get("effect"),
1661
+ }
1662
+ )
1663
+ continue
1664
+ if meta.get("reverification_all_passed"):
1665
+ continue
1666
+ if acceptance_review == "partial_smoke_only" or review_level.endswith("_smoke") or review_level == "static_syntax_smoke":
1667
+ needs.append(
1668
+ {
1669
+ "task_id": task_id,
1670
+ "review_level": review_level or "unknown",
1671
+ "acceptance_review": acceptance_review or "unknown",
1672
+ "reason": "partial smoke task must be explicitly reverified by milestone runner before promotion claims",
1673
+ }
1674
+ )
1675
+ return needs
1676
+
1677
+
1678
+ def validate_reverification_evidence(task: dict[str, Any], evidence: dict[str, Any]) -> tuple[list[dict[str, Any]], list[str], bool]:
1679
+ acceptance = [str(item) for item in task.get("acceptance", [])]
1680
+ criteria = evidence.get("criteria")
1681
+ errors: list[str] = []
1682
+ if not isinstance(criteria, list):
1683
+ return [], ["evidence.criteria must be a list"], False
1684
+ normalized: list[dict[str, Any]] = []
1685
+ seen: set[str] = set()
1686
+ allowed_status = {"pass", "fail", "not_checked"}
1687
+ for index, item in enumerate(criteria):
1688
+ if not isinstance(item, dict):
1689
+ errors.append(f"criteria[{index}] must be an object")
1690
+ continue
1691
+ criterion = str(item.get("criterion") or "")
1692
+ status = str(item.get("status") or "")
1693
+ if criterion not in acceptance:
1694
+ errors.append(f"criteria[{index}] does not match task acceptance: {criterion}")
1695
+ if criterion in seen:
1696
+ errors.append(f"duplicate criterion: {criterion}")
1697
+ if status not in allowed_status:
1698
+ errors.append(f"invalid status for {criterion}: {status}")
1699
+ seen.add(criterion)
1700
+ normalized.append(
1701
+ {
1702
+ "criterion": criterion,
1703
+ "status": status,
1704
+ "evidence_kind": str(item.get("evidence_kind") or ""),
1705
+ "details": item.get("details") if item.get("details") is not None else "",
1706
+ "artifacts": item.get("artifacts") if isinstance(item.get("artifacts"), list) else [],
1707
+ }
1708
+ )
1709
+ missing = [criterion for criterion in acceptance if criterion not in seen]
1710
+ for criterion in missing:
1711
+ errors.append(f"missing criterion evidence: {criterion}")
1712
+ all_passed = bool(normalized) and not errors and all(item["status"] == "pass" for item in normalized)
1713
+ return normalized, errors, all_passed
1714
+
1715
+
1716
+ def overlay_passed_task_artifacts(project: Path, job_dir: Path, plan: dict[str, Any], target_dir: Path) -> list[dict[str, Any]]:
1717
+ shutil.copytree(project, target_dir, ignore=copytree_ignore_for_runner)
1718
+ tasks = plan.get("tasks") if isinstance(plan.get("tasks"), list) else []
1719
+ overlays: list[dict[str, Any]] = []
1720
+ for task in tasks:
1721
+ if task.get("status") != "passed":
1722
+ continue
1723
+ task_id = str(task.get("id"))
1724
+ sandbox_dir = job_dir / "sandbox" / "tasks" / task_id
1725
+ if not sandbox_dir.exists():
1726
+ continue
1727
+ for src in sandbox_dir.rglob("*"):
1728
+ if not src.is_file():
1729
+ continue
1730
+ rel = src.relative_to(sandbox_dir)
1731
+ dest = target_dir / rel
1732
+ dest.parent.mkdir(parents=True, exist_ok=True)
1733
+ shutil.copy2(src, dest)
1734
+ overlays.append({"task_id": task_id, "path": str(rel), "sha256": sha256_file(dest)})
1735
+ return overlays
1736
+
1737
+
1738
+ def link_existing_dependency_trees(project: Path, overlay_dir: Path) -> list[str]:
1739
+ """Reuse installed dependencies without copying them or invoking an installer."""
1740
+ linked: list[str] = []
1741
+ for root, dirs, _files in os.walk(project, topdown=True):
1742
+ root_path = Path(root)
1743
+ dirs[:] = [name for name in dirs if name not in {".git", ".socra"}]
1744
+ if "node_modules" not in dirs:
1745
+ continue
1746
+ source = root_path / "node_modules"
1747
+ rel = source.relative_to(project)
1748
+ target = overlay_dir / rel
1749
+ target.parent.mkdir(parents=True, exist_ok=True)
1750
+ if not target.exists() and not target.is_symlink():
1751
+ target.symlink_to(source, target_is_directory=True)
1752
+ linked.append(rel.as_posix())
1753
+ dirs.remove("node_modules")
1754
+ return linked
1755
+
1756
+
1757
+ RUNNER_INFRASTRUCTURE_PATTERNS = (
1758
+ "command not found",
1759
+ "err_pnpm_ignored_builds",
1760
+ "err_pnpm_aborted_remove_modules_dir_no_tty",
1761
+ "err_pnpm_outdated_lockfile",
1762
+ "err_pnpm_lockfile_config_mismatch",
1763
+ "could not determine executable to run",
1764
+ "jest: not found",
1765
+ "vitest: not found",
1766
+ "mocha: not found",
1767
+ "tsx: not found",
1768
+ )
1769
+ RUNNER_STARTED_INFRASTRUCTURE_PATTERNS = ("listen eperm: operation not permitted",)
1770
+
1771
+
1772
+ def classify_gate_process(returncode: int, stdout: str, stderr: str) -> tuple[str, bool, str | None]:
1773
+ if returncode == 0:
1774
+ return "passed", True, None
1775
+ combined = f"{stdout}\n{stderr}".lower()
1776
+ if returncode in {126, 127} or any(pattern in combined for pattern in RUNNER_INFRASTRUCTURE_PATTERNS):
1777
+ return "infrastructure_unavailable", False, "runner_infrastructure_unavailable"
1778
+ if any(pattern in combined for pattern in RUNNER_STARTED_INFRASTRUCTURE_PATTERNS):
1779
+ return "infrastructure_unavailable", True, "runner_infrastructure_unavailable"
1780
+ return "test_failed", True, "test_command_failed"
1781
+
1782
+
1783
+ def infer_milestone_review_level(command: str) -> tuple[str, bool]:
1784
+ text = command.lower()
1785
+ if "preview" in text or "http.server" in text or "playwright" in text:
1786
+ return "preview_gate", False
1787
+ if "npm run build" in text or "vite" in text and "build" in text or "xcodebuild" in text:
1788
+ return "build_gate", False
1789
+ return "static_gate_only", False
1790
+
1791
+
1792
+ def dependency_manifest(project_dir: Path) -> dict[str, Any]:
1793
+ payload: dict[str, Any] = {}
1794
+ for name in ["package.json", "package-lock.json", "pnpm-lock.yaml", "yarn.lock", "bun.lockb"]:
1795
+ path = project_dir / name
1796
+ if path.exists() and path.is_file():
1797
+ payload[name] = {"sha256": sha256_file(path), "bytes": path.stat().st_size}
1798
+ return payload
1799
+
1800
+
1801
+ def prepare_runner_dependencies(overlay_dir: Path, mode: str, timeout_seconds: int) -> tuple[dict[str, Any], int]:
1802
+ if mode == "none":
1803
+ return {"mode": mode, "skipped": True}, 0
1804
+ if mode == "npm-ci":
1805
+ command = "npm ci"
1806
+ elif mode == "npm-install":
1807
+ command = "npm install"
1808
+ else:
1809
+ return {"mode": mode, "error": f"unknown dependency preparation mode: {mode}"}, 2
1810
+ before = dependency_manifest(overlay_dir)
1811
+ result = subprocess.run(
1812
+ command,
1813
+ cwd=overlay_dir,
1814
+ shell=True,
1815
+ text=True,
1816
+ capture_output=True,
1817
+ timeout=timeout_seconds,
1818
+ check=False,
1819
+ )
1820
+ after = dependency_manifest(overlay_dir)
1821
+ return (
1822
+ {
1823
+ "mode": mode,
1824
+ "command": command,
1825
+ "returncode": result.returncode,
1826
+ "stdout_tail": (result.stdout or "")[-4000:],
1827
+ "stderr_tail": (result.stderr or "")[-4000:],
1828
+ "manifest_before": before,
1829
+ "manifest_after": after,
1830
+ "node_modules_exists": (overlay_dir / "node_modules").exists(),
1831
+ },
1832
+ 0 if result.returncode == 0 else 2,
1833
+ )
1834
+
1835
+
1836
+ def parse_npm_vulnerability_counts(text: str) -> dict[str, int]:
1837
+ counts = {"low": 0, "moderate": 0, "high": 0, "critical": 0}
1838
+ match = re.search(r"(\d+)\s+vulnerabilities?\s*\(([^)]*)\)", text, flags=re.IGNORECASE)
1839
+ if not match:
1840
+ return counts
1841
+ for count, level in re.findall(r"(\d+)\s+(low|moderate|high|critical)", match.group(2), flags=re.IGNORECASE):
1842
+ counts[level.lower()] = int(count)
1843
+ return counts
1844
+
1845
+
1846
+ def active_vulnerability_exception_counts(events: list[Any]) -> dict[str, int]:
1847
+ counts = {"high": 0, "critical": 0}
1848
+ for event in events:
1849
+ if event.type != "vulnerability_exception_recorded":
1850
+ continue
1851
+ payload = event.payload
1852
+ if payload.get("active") is False:
1853
+ continue
1854
+ severity = str(payload.get("severity") or "").lower()
1855
+ if severity in counts:
1856
+ counts[severity] += 1
1857
+ return counts
1858
+
1859
+
1860
+ def dependency_vulnerability_policy(dep_payload: dict[str, Any], promotion_grade: bool, exception_counts: dict[str, int] | None = None) -> dict[str, Any]:
1861
+ text = "\n".join(str(dep_payload.get(key) or "") for key in ("stdout_tail", "stderr_tail"))
1862
+ counts = parse_npm_vulnerability_counts(text)
1863
+ exceptions = exception_counts or {}
1864
+ unexcepted_high = max(0, int(counts.get("high", 0)) - int(exceptions.get("high", 0)))
1865
+ unexcepted_critical = max(0, int(counts.get("critical", 0)) - int(exceptions.get("critical", 0)))
1866
+ blocking = unexcepted_high + unexcepted_critical
1867
+ return {
1868
+ "policy": "verification_records_all_vulns_promotion_blocks_high_critical",
1869
+ "counts": counts,
1870
+ "active_exception_counts": {"high": int(exceptions.get("high", 0)), "critical": int(exceptions.get("critical", 0))},
1871
+ "unexcepted_blocking_counts": {"high": unexcepted_high, "critical": unexcepted_critical},
1872
+ "blocking_vulnerabilities": blocking,
1873
+ "promotion_grade": promotion_grade,
1874
+ "blocks_promotion": bool(promotion_grade and blocking > 0),
1875
+ }
1876
+
1877
+
1878
+ def find_job_dir(project: Path, job_id: str | None) -> Path:
1879
+ if job_id:
1880
+ return job_root(project) / job_id
1881
+ found = find_latest_incomplete_job(project)
1882
+ if not found:
1883
+ raise SystemExit(f"No incomplete Socra jobs found under {job_root(project)}")
1884
+ return found
1885
+
1886
+
1887
+ def human_promotion_review(events: list[Any], task_id: str) -> dict[str, Any] | None:
1888
+ for event in reversed(events):
1889
+ if event.type != "promotion_human_authorized":
1890
+ continue
1891
+ payload = event.payload
1892
+ if payload.get("task_id") != task_id:
1893
+ continue
1894
+ if payload.get("human_source_promotion_authorized") is True:
1895
+ return {
1896
+ "event_seq": event.seq,
1897
+ "reviewer_name": payload.get("authorizer_name"),
1898
+ "review": payload.get("authorization"),
1899
+ "sha256": payload.get("authorization_sha256"),
1900
+ "event_type": event.type,
1901
+ "bundle_sha256": payload.get("bundle_sha256"),
1902
+ "dry_run_diff_sha256": payload.get("dry_run_diff_sha256"),
1903
+ "rollback_manifest_sha256": payload.get("rollback_manifest_sha256"),
1904
+ "presentation_event_seq": payload.get("presentation_event_seq"),
1905
+ }
1906
+ for event in reversed(events):
1907
+ if event.type != "promotion_human_diff_reviewed":
1908
+ continue
1909
+ payload = event.payload
1910
+ if payload.get("task_id") != task_id:
1911
+ continue
1912
+ if payload.get("human_source_promotion_approval_granted") is True:
1913
+ return {
1914
+ "event_seq": event.seq,
1915
+ "reviewer_name": payload.get("reviewer_name"),
1916
+ "review": payload.get("review"),
1917
+ "sha256": payload.get("sha256"),
1918
+ "event_type": event.type,
1919
+ "bundle_sha256": payload.get("bundle_sha256"),
1920
+ "legacy_compatibility": True,
1921
+ }
1922
+ return None
1923
+
1924
+
1925
+ def latest_diff_presentation(events: list[Any], task_id: str, bundle_sha256: str, dry_run_diff_sha256: str) -> dict[str, Any] | None:
1926
+ for event in reversed(events):
1927
+ if event.type != "promotion_diff_presented":
1928
+ continue
1929
+ payload = event.payload
1930
+ if payload.get("task_id") != task_id:
1931
+ continue
1932
+ if payload.get("bundle_sha256") != bundle_sha256:
1933
+ continue
1934
+ if payload.get("dry_run_diff_sha256") != dry_run_diff_sha256:
1935
+ continue
1936
+ if payload.get("displayed_hash_matched_approval_target") is True:
1937
+ return {
1938
+ "event_seq": event.seq,
1939
+ "presentation": payload,
1940
+ }
1941
+ return None
1942
+
1943
+
1944
+ def matching_diff_rejection(events: list[Any], task_id: str, bundle_sha256: str, dry_run_diff_sha256: str) -> dict[str, Any] | None:
1945
+ for event in reversed(events):
1946
+ if event.type != "promotion_human_rejected":
1947
+ continue
1948
+ payload = event.payload
1949
+ if payload.get("task_id") != task_id:
1950
+ continue
1951
+ if payload.get("bundle_sha256") != bundle_sha256:
1952
+ continue
1953
+ if payload.get("dry_run_diff_sha256") != dry_run_diff_sha256:
1954
+ continue
1955
+ return {"event_seq": event.seq, "rejection": payload}
1956
+ return None
1957
+
1958
+
1959
+ def ensure_promotion_artifacts(project: Path, job_dir: Path, task_id: str) -> dict[str, Any]:
1960
+ artifact_dir = job_dir / "artifacts" / "tasks" / task_id
1961
+ dry_run_diff = artifact_dir / "dry-run-diff.md"
1962
+ patch_bundle = artifact_dir / "promotion-patch-bundle.json"
1963
+ rollback_manifest = artifact_dir / "rollback-manifest.json"
1964
+ if dry_run_diff.exists() and patch_bundle.exists() and rollback_manifest.exists():
1965
+ return {"generated": False}
1966
+
1967
+ sandbox_dir = job_dir / "sandbox" / "tasks" / task_id
1968
+ if not sandbox_dir.exists():
1969
+ return {"generated": False, "error": "promotion_sandbox_artifacts_missing"}
1970
+
1971
+ artifact_dir.mkdir(parents=True, exist_ok=True)
1972
+ changed_files: list[dict[str, Any]] = []
1973
+ rollback_entries: list[dict[str, Any]] = []
1974
+ diff_sections: list[str] = [f"# Promotion Diff: {task_id}", ""]
1975
+
1976
+ for src in sorted(sandbox_dir.rglob("*")):
1977
+ if not src.is_file():
1978
+ continue
1979
+ rel = src.relative_to(sandbox_dir).as_posix()
1980
+ target = project / rel
1981
+ old_exists = target.exists()
1982
+ old_text = target.read_text(encoding="utf-8", errors="replace") if old_exists and target.is_file() else ""
1983
+ new_text = src.read_text(encoding="utf-8", errors="replace")
1984
+ old_sha = sha256_text(old_text) if old_exists else None
1985
+ new_sha = sha256_text(new_text)
1986
+ if old_exists and old_sha == new_sha:
1987
+ continue
1988
+ sandbox_path = src.relative_to(job_dir).as_posix()
1989
+ changed_files.append(
1990
+ {
1991
+ "path": rel,
1992
+ "source_task_id": task_id,
1993
+ "sandbox_path": sandbox_path,
1994
+ "old_exists": old_exists,
1995
+ "old_sha256": old_sha,
1996
+ "new_sha256": new_sha,
1997
+ }
1998
+ )
1999
+ rollback_entries.append(
2000
+ {
2001
+ "path": rel,
2002
+ "old_exists": old_exists,
2003
+ "old_sha256": old_sha,
2004
+ }
2005
+ )
2006
+ old_lines = old_text.splitlines()
2007
+ new_lines = new_text.splitlines()
2008
+ diff = "\n".join(
2009
+ difflib.unified_diff(
2010
+ old_lines,
2011
+ new_lines,
2012
+ fromfile=f"a/{rel}",
2013
+ tofile=f"b/{rel}",
2014
+ lineterm="",
2015
+ )
2016
+ )
2017
+ diff_sections.extend([f"## {rel}", "", "```diff", diff or "(no textual diff)", "```", ""])
2018
+
2019
+ if not changed_files:
2020
+ diff_sections.extend(["No changed files in sandbox artifacts.", ""])
2021
+
2022
+ dry_run_diff.write_text("\n".join(diff_sections), encoding="utf-8")
2023
+ bundle = {
2024
+ "schema_version": 1,
2025
+ "task_id": task_id,
2026
+ "source": "sandbox_task_artifacts",
2027
+ "changed_files": changed_files,
2028
+ }
2029
+ patch_bundle.write_text(json.dumps(bundle, indent=2) + "\n", encoding="utf-8")
2030
+ rollback = {
2031
+ "schema_version": 1,
2032
+ "task_id": task_id,
2033
+ "source": "project_preimage_manifest",
2034
+ "entries": rollback_entries,
2035
+ }
2036
+ rollback_manifest.write_text(json.dumps(rollback, indent=2) + "\n", encoding="utf-8")
2037
+ return {
2038
+ "generated": True,
2039
+ "changed_file_count": len(changed_files),
2040
+ "dry_run_diff": str(dry_run_diff.relative_to(job_dir)),
2041
+ "patch_bundle": str(patch_bundle.relative_to(job_dir)),
2042
+ "rollback_manifest": str(rollback_manifest.relative_to(job_dir)),
2043
+ }
2044
+
2045
+
2046
+ def promotion_artifact_hashes(job_dir: Path, task_id: str) -> dict[str, Any]:
2047
+ artifact_dir = job_dir / "artifacts" / "tasks" / task_id
2048
+ dry_run_diff = artifact_dir / "dry-run-diff.md"
2049
+ patch_bundle = artifact_dir / "promotion-patch-bundle.json"
2050
+ rollback_manifest = artifact_dir / "rollback-manifest.json"
2051
+ missing = [str(path.relative_to(job_dir)) for path in [dry_run_diff, patch_bundle, rollback_manifest] if not path.exists()]
2052
+ hashes: dict[str, Any] = {
2053
+ "artifact_dir": artifact_dir,
2054
+ "dry_run_diff": dry_run_diff,
2055
+ "patch_bundle": patch_bundle,
2056
+ "rollback_manifest": rollback_manifest,
2057
+ "missing": missing,
2058
+ }
2059
+ if not missing:
2060
+ hashes.update(
2061
+ {
2062
+ "dry_run_diff_sha256": sha256_file(dry_run_diff),
2063
+ "bundle_sha256": sha256_file(patch_bundle),
2064
+ "rollback_manifest_sha256": sha256_file(rollback_manifest),
2065
+ }
2066
+ )
2067
+ return hashes
2068
+
2069
+
2070
+ def promotion_apply_bundle(project: Path, job_dir: Path, task_id: str) -> dict[str, Any]:
2071
+ artifact_dir = job_dir / "artifacts" / "tasks" / task_id
2072
+ bundle_path = artifact_dir / "promotion-patch-bundle.json"
2073
+ if not bundle_path.exists():
2074
+ return {
2075
+ "ok": False,
2076
+ "error": "promotion_patch_bundle_missing",
2077
+ "missing": str(bundle_path.relative_to(job_dir)),
2078
+ "real_project_modified": False,
2079
+ }
2080
+ bundle = json.loads(bundle_path.read_text(encoding="utf-8"))
2081
+ changed_files = bundle.get("changed_files") or []
2082
+ if not isinstance(changed_files, list) or not changed_files:
2083
+ return {
2084
+ "ok": False,
2085
+ "error": "promotion_patch_bundle_has_no_changed_files",
2086
+ "bundle": str(bundle_path.relative_to(job_dir)),
2087
+ "real_project_modified": False,
2088
+ }
2089
+
2090
+ errors: list[dict[str, Any]] = []
2091
+ rollback_entries: list[dict[str, Any]] = []
2092
+ for item in changed_files:
2093
+ try:
2094
+ rel = normalize_declared_path(str(item.get("path") or ""))
2095
+ except ValueError as exc:
2096
+ errors.append({"path": item.get("path"), "stage": "path_validation", "error": str(exc)})
2097
+ continue
2098
+ target = project / rel
2099
+ old_exists = target.exists()
2100
+ old_data = target.read_bytes() if old_exists else None
2101
+ old_sha = hashlib.sha256(old_data).hexdigest() if old_data is not None else None
2102
+ if old_exists != bool(item.get("old_exists")) or old_sha != item.get("old_sha256"):
2103
+ errors.append(
2104
+ {
2105
+ "path": rel,
2106
+ "stage": "preimage_check",
2107
+ "expected_exists": item.get("old_exists"),
2108
+ "actual_exists": old_exists,
2109
+ "expected_sha256": item.get("old_sha256"),
2110
+ "actual_sha256": old_sha,
2111
+ }
2112
+ )
2113
+ rollback_entries.append(
2114
+ {
2115
+ "path": rel,
2116
+ "old_exists": old_exists,
2117
+ "old_sha256": old_sha,
2118
+ "old_content_b64": base64.b64encode(old_data).decode("ascii") if old_data is not None else None,
2119
+ }
2120
+ )
2121
+ if errors:
2122
+ return {
2123
+ "ok": False,
2124
+ "error": "promotion_preimage_check_failed",
2125
+ "errors": errors,
2126
+ "real_project_modified": False,
2127
+ }
2128
+
2129
+ apply_dir = artifact_dir / "promotion-apply"
2130
+ apply_dir.mkdir(parents=True, exist_ok=True)
2131
+ rollback_snapshot = {
2132
+ "task_id": task_id,
2133
+ "bundle_sha256": sha256_file(bundle_path),
2134
+ "entries": rollback_entries,
2135
+ }
2136
+ rollback_snapshot_path = apply_dir / "rollback-content-snapshot.json"
2137
+ rollback_snapshot_text = json.dumps(rollback_snapshot, indent=2) + "\n"
2138
+ rollback_snapshot_path.write_text(rollback_snapshot_text, encoding="utf-8")
2139
+
2140
+ applied: list[dict[str, Any]] = []
2141
+ written_targets: list[Path] = []
2142
+ try:
2143
+ for item in changed_files:
2144
+ rel = normalize_declared_path(str(item.get("path") or ""))
2145
+ sandbox_rel = normalize_declared_path(str(item.get("sandbox_path") or ""))
2146
+ source = (job_dir / sandbox_rel).resolve()
2147
+ job_root_resolved = job_dir.resolve()
2148
+ if job_root_resolved != source and job_root_resolved not in source.parents:
2149
+ raise RuntimeError(f"source artifact escapes job dir for {rel}: {source}")
2150
+ target = project / rel
2151
+ if not source.exists():
2152
+ raise RuntimeError(f"source artifact missing for {rel}: {source}")
2153
+ source_data = source.read_bytes()
2154
+ source_sha = hashlib.sha256(source_data).hexdigest()
2155
+ if source_sha != item.get("new_sha256"):
2156
+ raise RuntimeError(f"source hash mismatch for {rel}: {source_sha} != {item.get('new_sha256')}")
2157
+ target.parent.mkdir(parents=True, exist_ok=True)
2158
+ # Write to a sibling temp file and replace to avoid torn targets.
2159
+ tmp = target.with_name(f".{target.name}.socra-promote-tmp")
2160
+ tmp.write_bytes(source_data)
2161
+ tmp.replace(target)
2162
+ written_targets.append(target)
2163
+ actual_sha = sha256_file(target)
2164
+ if actual_sha != item.get("new_sha256"):
2165
+ raise RuntimeError(f"post-write hash mismatch for {rel}: {actual_sha} != {item.get('new_sha256')}")
2166
+ applied.append(
2167
+ {
2168
+ "path": rel,
2169
+ "new_sha256": actual_sha,
2170
+ "source_task_id": item.get("source_task_id"),
2171
+ "sandbox_path": item.get("sandbox_path"),
2172
+ }
2173
+ )
2174
+ except Exception as exc: # noqa: BLE001 - rollback best effort is recorded.
2175
+ rollback_errors: list[dict[str, Any]] = []
2176
+ by_path = {entry["path"]: entry for entry in rollback_entries}
2177
+ for target in reversed(written_targets):
2178
+ rel = str(target.relative_to(project))
2179
+ entry = by_path.get(rel)
2180
+ if not entry:
2181
+ continue
2182
+ try:
2183
+ if entry.get("old_exists"):
2184
+ target.write_bytes(base64.b64decode(entry.get("old_content_b64") or ""))
2185
+ elif target.exists():
2186
+ target.unlink()
2187
+ except Exception as rollback_exc: # noqa: BLE001
2188
+ rollback_errors.append({"path": rel, "error": str(rollback_exc)})
2189
+ return {
2190
+ "ok": False,
2191
+ "error": "promotion_apply_failed",
2192
+ "exception": str(exc),
2193
+ "applied_before_error": applied,
2194
+ "rollback_errors": rollback_errors,
2195
+ "rollback_content_snapshot": str(rollback_snapshot_path.relative_to(job_dir)),
2196
+ "rollback_content_snapshot_sha256": sha256_text(rollback_snapshot_text),
2197
+ "real_project_modified": bool(applied),
2198
+ }
2199
+
2200
+ receipt = {
2201
+ "task_id": task_id,
2202
+ "bundle_sha256": sha256_file(bundle_path),
2203
+ "applied_count": len(applied),
2204
+ "applied": applied,
2205
+ "rollback_content_snapshot": str(rollback_snapshot_path.relative_to(job_dir)),
2206
+ "rollback_content_snapshot_sha256": sha256_text(rollback_snapshot_text),
2207
+ "real_project_modified": True,
2208
+ }
2209
+ receipt_path = apply_dir / "promotion-apply-receipt.json"
2210
+ receipt_text = json.dumps(receipt, indent=2) + "\n"
2211
+ receipt_path.write_text(receipt_text, encoding="utf-8")
2212
+ return {
2213
+ "ok": True,
2214
+ "receipt": receipt,
2215
+ "receipt_path": str(receipt_path.relative_to(job_dir)),
2216
+ "receipt_sha256": sha256_text(receipt_text),
2217
+ "rollback_content_snapshot": str(rollback_snapshot_path.relative_to(job_dir)),
2218
+ "rollback_content_snapshot_sha256": sha256_text(rollback_snapshot_text),
2219
+ "real_project_modified": True,
2220
+ }
2221
+
2222
+
2223
+ def present_promotion_diff(job_dir: Path, task_id: str, reviewer_name: str, notes: str = "") -> dict[str, Any]:
2224
+ artifacts = promotion_artifact_hashes(job_dir, task_id)
2225
+ missing = artifacts["missing"]
2226
+ if missing:
2227
+ return {
2228
+ "ok": False,
2229
+ "missing": missing,
2230
+ "error": "promotion_dry_run_artifacts_missing",
2231
+ }
2232
+
2233
+ dry_run_diff = artifacts["dry_run_diff"]
2234
+ text = dry_run_diff.read_text(encoding="utf-8", errors="replace")
2235
+ start = time.monotonic()
2236
+ # Print the exact artifact bytes as text. This is intentionally simple and
2237
+ # observable in logs; callers can pipe through a pager if desired.
2238
+ sys.stdout.write(text)
2239
+ if text and not text.endswith("\n"):
2240
+ sys.stdout.write("\n")
2241
+ sys.stdout.flush()
2242
+ elapsed = elapsed_ms(start)
2243
+ rendered_lines = len(text.splitlines())
2244
+ return {
2245
+ "ok": True,
2246
+ "task_id": task_id,
2247
+ "reviewer_role": "human",
2248
+ "reviewer_name": reviewer_name,
2249
+ "dry_run_diff": str(dry_run_diff.relative_to(job_dir)),
2250
+ "dry_run_diff_sha256": artifacts["dry_run_diff_sha256"],
2251
+ "bundle": str(artifacts["patch_bundle"].relative_to(job_dir)),
2252
+ "bundle_sha256": artifacts["bundle_sha256"],
2253
+ "rollback_manifest": str(artifacts["rollback_manifest"].relative_to(job_dir)),
2254
+ "rollback_manifest_sha256": artifacts["rollback_manifest_sha256"],
2255
+ "presentation_method": "stdout",
2256
+ "rendered_line_count": rendered_lines,
2257
+ "process_exit_status": 0,
2258
+ "elapsed_ms": elapsed,
2259
+ "displayed_hash_matched_approval_target": True,
2260
+ "notes": notes,
2261
+ }
2262
+
2263
+
2264
+ def job_review_promotion_main(args: argparse.Namespace) -> int:
2265
+ project = args.project.expanduser().resolve()
2266
+ job_dir = find_job_dir(project, args.job_id)
2267
+ with JobLock(job_dir / ".lock"):
2268
+ events = read_events(job_dir / "events.jsonl")
2269
+ state = replay(events)
2270
+ plan = materialize_plan_from_events(events, load_plan(job_dir))
2271
+ task = next((item for item in plan.get("tasks", []) if item.get("id") == args.task_id), None)
2272
+ behavior_preference = getattr(args, "behavior_preference", None)
2273
+ gene_evidence = getattr(args, "gene_evidence", None)
2274
+ generated = ensure_promotion_artifacts(project, job_dir, args.task_id)
2275
+ artifacts = promotion_artifact_hashes(job_dir, args.task_id)
2276
+ artifact_dir = artifacts["artifact_dir"]
2277
+ missing = artifacts["missing"]
2278
+ log = JobEventLog(job_dir / "events.jsonl", state.job_id or job_dir.name)
2279
+ if generated.get("generated"):
2280
+ log.append("promotion_artifacts_generated", {"task_id": args.task_id, **generated})
2281
+ if missing:
2282
+ log.append(
2283
+ "promotion_review_failed",
2284
+ {
2285
+ "task_id": args.task_id,
2286
+ "reviewer_role": "human",
2287
+ "reviewer_name": args.reviewer_name,
2288
+ "error": "promotion_dry_run_artifacts_missing",
2289
+ "missing": missing,
2290
+ },
2291
+ )
2292
+ code = 2
2293
+ print(f"Promotion review failed: {args.task_id}")
2294
+ print(f"Missing artifacts: {', '.join(missing)}")
2295
+ elif bool(behavior_preference) != bool(gene_evidence):
2296
+ log.append(
2297
+ "promotion_behavior_preference_blocked",
2298
+ {
2299
+ "task_id": args.task_id,
2300
+ "error": "behavior_and_heldout_evidence_required_together",
2301
+ "real_project_modified": False,
2302
+ },
2303
+ )
2304
+ code = 2
2305
+ print(f"Promotion authorization blocked: {args.task_id}")
2306
+ print("Reason: behavior_and_heldout_evidence_required_together")
2307
+ elif behavior_preference and behavior_overlaps_patch(
2308
+ behavior_preference,
2309
+ artifacts["dry_run_diff"].read_text(encoding="utf-8", errors="replace"),
2310
+ ):
2311
+ log.append(
2312
+ "promotion_behavior_preference_blocked",
2313
+ {
2314
+ "task_id": args.task_id,
2315
+ "error": "behavior_card_overlaps_patch_text",
2316
+ "real_project_modified": False,
2317
+ },
2318
+ )
2319
+ code = 2
2320
+ print(f"Promotion authorization blocked: {args.task_id}")
2321
+ print("Reason: behavior_card_overlaps_patch_text")
2322
+ elif behavior_preference and (
2323
+ not task
2324
+ or not isinstance(task.get("recurrence"), dict)
2325
+ or task["recurrence"].get("status") != "assignable"
2326
+ ):
2327
+ log.append(
2328
+ "promotion_behavior_preference_blocked",
2329
+ {
2330
+ "task_id": args.task_id,
2331
+ "error": "assignable_recurrence_key_required",
2332
+ "real_project_modified": False,
2333
+ },
2334
+ )
2335
+ code = 2
2336
+ print(f"Promotion authorization blocked: {args.task_id}")
2337
+ print("Reason: assignable_recurrence_key_required_for_behavior_gene")
2338
+ else:
2339
+ if args.present_diff and (args.approve_source_promotion or args.attest_reviewed):
2340
+ log.append(
2341
+ "promotion_authorization_blocked",
2342
+ {
2343
+ "task_id": args.task_id,
2344
+ "error": "promotion_presentation_and_authorization_must_be_separate_invocations",
2345
+ "policy": "diff presentation must complete before a later human authorization command can approve that exact artifact hash",
2346
+ "real_project_modified": False,
2347
+ },
2348
+ )
2349
+ code = 2
2350
+ print(f"Promotion authorization blocked: {args.task_id}")
2351
+ print("Reason: promotion_presentation_and_authorization_must_be_separate_invocations")
2352
+ else:
2353
+ if args.present_diff:
2354
+ presentation = present_promotion_diff(job_dir, args.task_id, args.reviewer_name, args.notes)
2355
+ if not presentation.get("ok"):
2356
+ log.append("promotion_review_failed", presentation)
2357
+ code = 2
2358
+ print(f"Promotion diff presentation failed: {args.task_id}")
2359
+ else:
2360
+ log.append("promotion_diff_presented", {key: value for key, value in presentation.items() if key != "ok"})
2361
+ code = 0
2362
+ else:
2363
+ presentation_event = latest_diff_presentation(
2364
+ events,
2365
+ args.task_id,
2366
+ str(artifacts["bundle_sha256"]),
2367
+ str(artifacts["dry_run_diff_sha256"]),
2368
+ )
2369
+ rejection_event = matching_diff_rejection(
2370
+ events,
2371
+ args.task_id,
2372
+ str(artifacts["bundle_sha256"]),
2373
+ str(artifacts["dry_run_diff_sha256"]),
2374
+ )
2375
+ if args.approve_source_promotion and presentation_event is None:
2376
+ log.append(
2377
+ "promotion_authorization_blocked",
2378
+ {
2379
+ "task_id": args.task_id,
2380
+ "error": "promotion_diff_presentation_required",
2381
+ "required_event": "promotion_diff_presented",
2382
+ "required_bundle_sha256": artifacts["bundle_sha256"],
2383
+ "required_dry_run_diff_sha256": artifacts["dry_run_diff_sha256"],
2384
+ "policy": "human authorization must be tied to a presented diff artifact hash",
2385
+ "real_project_modified": False,
2386
+ },
2387
+ )
2388
+ code = 2
2389
+ print(f"Promotion authorization blocked: {args.task_id}")
2390
+ print("Reason: promotion_diff_presentation_required")
2391
+ elif args.approve_source_promotion and rejection_event is not None:
2392
+ log.append(
2393
+ "promotion_authorization_blocked",
2394
+ {
2395
+ "task_id": args.task_id,
2396
+ "error": "promotion_diff_rejected",
2397
+ "rejection_event_seq": rejection_event["event_seq"],
2398
+ "bundle_sha256": artifacts["bundle_sha256"],
2399
+ "dry_run_diff_sha256": artifacts["dry_run_diff_sha256"],
2400
+ "policy": "a human-rejected artifact hash cannot later be authorized",
2401
+ "real_project_modified": False,
2402
+ },
2403
+ )
2404
+ code = 2
2405
+ print(f"Promotion authorization blocked: {args.task_id}")
2406
+ print("Reason: promotion_diff_rejected")
2407
+ else:
2408
+ if behavior_preference:
2409
+ preference_path = artifact_dir / "behavior-preference.json"
2410
+ write_json(preference_path, behavior_preference)
2411
+ evidence_path = artifact_dir / "heldout-gene-evidence.json"
2412
+ write_json(evidence_path, gene_evidence)
2413
+ log.append(
2414
+ "promotion_behavior_preference_bound",
2415
+ {
2416
+ "task_id": args.task_id,
2417
+ "reviewer_role": "human",
2418
+ "reviewer_name": args.reviewer_name,
2419
+ "bundle_sha256": artifacts["bundle_sha256"],
2420
+ "recurrence_key_sha256": task["recurrence"]["recurrence_key_sha256"],
2421
+ "preference": behavior_preference,
2422
+ "preference_path": str(preference_path.relative_to(job_dir)),
2423
+ "preference_sha256": recurrence_sha256_json(behavior_preference),
2424
+ "heldout_evidence": gene_evidence,
2425
+ "heldout_evidence_path": str(evidence_path.relative_to(job_dir)),
2426
+ "heldout_evidence_sha256": sha256_file(evidence_path),
2427
+ "contains_patch_or_source": False,
2428
+ "behavior_not_answer_human_attested": True,
2429
+ "policy": "reviewer_authored_prefer_behavior_bound_to_authorized_bundle",
2430
+ },
2431
+ )
2432
+ authorization = {
2433
+ "task_id": args.task_id,
2434
+ "authorizer_role": "human",
2435
+ "authorizer_name": args.reviewer_name,
2436
+ "human_source_promotion_authorized": bool(args.approve_source_promotion),
2437
+ "bundle": str(artifacts["patch_bundle"].relative_to(job_dir)),
2438
+ "bundle_sha256": artifacts["bundle_sha256"],
2439
+ "dry_run_diff": str(artifacts["dry_run_diff"].relative_to(job_dir)),
2440
+ "dry_run_diff_sha256": artifacts["dry_run_diff_sha256"],
2441
+ "rollback_manifest": str(artifacts["rollback_manifest"].relative_to(job_dir)),
2442
+ "rollback_manifest_sha256": artifacts["rollback_manifest_sha256"],
2443
+ "presentation_event_seq": presentation_event["event_seq"] if presentation_event else None,
2444
+ "notes": args.notes,
2445
+ "policy": "human_authorization_required_after_ai_prescreen_before_real_project_write",
2446
+ }
2447
+ authorization_path = artifact_dir / "human-promotion-authorization.json"
2448
+ authorization_path.write_text(json.dumps(authorization, indent=2) + "\n", encoding="utf-8")
2449
+ log.append(
2450
+ "promotion_human_authorized",
2451
+ {
2452
+ **authorization,
2453
+ "authorization": str(authorization_path.relative_to(job_dir)),
2454
+ "authorization_sha256": sha256_file(authorization_path),
2455
+ },
2456
+ )
2457
+ if args.attest_reviewed:
2458
+ attestation = {
2459
+ "task_id": args.task_id,
2460
+ "reviewer_role": "human",
2461
+ "reviewer_name": args.reviewer_name,
2462
+ "human_review_attested": True,
2463
+ "attestation_scope": "self_declared_review_of_presented_diff",
2464
+ "bundle_sha256": artifacts["bundle_sha256"],
2465
+ "dry_run_diff_sha256": artifacts["dry_run_diff_sha256"],
2466
+ "presentation_event_seq": presentation_event["event_seq"] if presentation_event else None,
2467
+ "notes": args.notes,
2468
+ }
2469
+ attestation_path = artifact_dir / "human-review-attestation.json"
2470
+ attestation_path.write_text(json.dumps(attestation, indent=2) + "\n", encoding="utf-8")
2471
+ log.append(
2472
+ "promotion_human_review_attested",
2473
+ {
2474
+ **attestation,
2475
+ "attestation": str(attestation_path.relative_to(job_dir)),
2476
+ "attestation_sha256": sha256_file(attestation_path),
2477
+ },
2478
+ )
2479
+ # Legacy compatibility event for old analysis tools. It is now
2480
+ # explicitly scoped as authorization, not proof of observed review.
2481
+ legacy = {
2482
+ "task_id": args.task_id,
2483
+ "reviewer_role": "human",
2484
+ "reviewer_name": args.reviewer_name,
2485
+ "reviewed_artifacts": {
2486
+ "dry_run_diff": str(artifacts["dry_run_diff"].relative_to(job_dir)),
2487
+ "patch_bundle": str(artifacts["patch_bundle"].relative_to(job_dir)),
2488
+ "rollback_manifest": str(artifacts["rollback_manifest"].relative_to(job_dir)),
2489
+ },
2490
+ "human_source_promotion_approval_granted": bool(args.approve_source_promotion),
2491
+ "authorization_event": "promotion_human_authorized",
2492
+ "review_proof_scope": "authorization_only_unless_promotion_human_review_attested_exists",
2493
+ "notes": args.notes,
2494
+ "policy": "legacy_compatibility_event_do_not_treat_as_proof_of_observed_review",
2495
+ }
2496
+ legacy_path = artifact_dir / "human-promotion-review.json"
2497
+ legacy_path.write_text(json.dumps(legacy, indent=2) + "\n", encoding="utf-8")
2498
+ log.append(
2499
+ "promotion_human_diff_reviewed",
2500
+ {
2501
+ **legacy,
2502
+ "review": str(legacy_path.relative_to(job_dir)),
2503
+ "sha256": sha256_file(legacy_path),
2504
+ },
2505
+ )
2506
+ code = 0 if args.approve_source_promotion else 2
2507
+ print(f"Promotion human authorization recorded: {args.task_id}")
2508
+ print(f"Authorized for source promotion: {bool(args.approve_source_promotion)}")
2509
+
2510
+ events = read_events(job_dir / "events.jsonl")
2511
+ state = replay(events)
2512
+ plan = materialize_plan_from_events(events, load_plan(job_dir))
2513
+ write_json(job_dir / "plan.json", plan)
2514
+ write_state_snapshot(job_dir / "job.json", state)
2515
+ write_handoff(job_dir, state_to_json(state), plan)
2516
+ return code
2517
+
2518
+
2519
+ def job_reject_promotion_main(args: argparse.Namespace) -> int:
2520
+ project = args.project.expanduser().resolve()
2521
+ job_dir = find_job_dir(project, args.job_id)
2522
+ with JobLock(job_dir / ".lock"):
2523
+ events = read_events(job_dir / "events.jsonl")
2524
+ state = replay(events)
2525
+ plan = materialize_plan_from_events(events, load_plan(job_dir))
2526
+ task = next((item for item in plan.get("tasks", []) if item.get("id") == args.task_id), None)
2527
+ artifacts = promotion_artifact_hashes(job_dir, args.task_id)
2528
+ log = JobEventLog(job_dir / "events.jsonl", state.job_id or job_dir.name)
2529
+ presentation = None
2530
+ if not artifacts.get("missing"):
2531
+ presentation = latest_diff_presentation(
2532
+ events,
2533
+ args.task_id,
2534
+ str(artifacts["bundle_sha256"]),
2535
+ str(artifacts["dry_run_diff_sha256"]),
2536
+ )
2537
+ if not task or task.get("status") != "passed":
2538
+ log.append(
2539
+ "promotion_rejection_blocked",
2540
+ {"task_id": args.task_id, "error": "promotion_task_not_passed", "real_project_modified": False},
2541
+ )
2542
+ code = 2
2543
+ print(f"Promotion rejection blocked: {args.task_id}")
2544
+ print("Reason: promotion_task_not_passed")
2545
+ elif artifacts.get("missing"):
2546
+ log.append(
2547
+ "promotion_rejection_blocked",
2548
+ {
2549
+ "task_id": args.task_id,
2550
+ "error": "promotion_artifacts_missing",
2551
+ "missing": artifacts.get("missing"),
2552
+ "real_project_modified": False,
2553
+ },
2554
+ )
2555
+ code = 2
2556
+ print(f"Promotion rejection blocked: {args.task_id}")
2557
+ print("Reason: promotion_artifacts_missing")
2558
+ elif presentation is None:
2559
+ log.append(
2560
+ "promotion_rejection_blocked",
2561
+ {
2562
+ "task_id": args.task_id,
2563
+ "error": "promotion_diff_presentation_required",
2564
+ "required_event": "promotion_diff_presented",
2565
+ "bundle_sha256": artifacts["bundle_sha256"],
2566
+ "dry_run_diff_sha256": artifacts["dry_run_diff_sha256"],
2567
+ "real_project_modified": False,
2568
+ },
2569
+ )
2570
+ code = 2
2571
+ print(f"Promotion rejection blocked: {args.task_id}")
2572
+ print("Reason: promotion_diff_presentation_required")
2573
+ elif matching_diff_rejection(
2574
+ events,
2575
+ args.task_id,
2576
+ str(artifacts["bundle_sha256"]),
2577
+ str(artifacts["dry_run_diff_sha256"]),
2578
+ ):
2579
+ print(f"Promotion rejection already recorded: {args.task_id}")
2580
+ code = 0
2581
+ else:
2582
+ rejection = {
2583
+ "task_id": args.task_id,
2584
+ "reviewer_role": "human",
2585
+ "reviewer_name": args.reviewer_name,
2586
+ "human_source_promotion_rejected": True,
2587
+ "failure_class": "human_rejects_diff",
2588
+ "error": "human_rejected_presented_diff",
2589
+ "reason": args.reason,
2590
+ "notes": args.notes,
2591
+ "bundle": str(artifacts["patch_bundle"].relative_to(job_dir)),
2592
+ "bundle_sha256": artifacts["bundle_sha256"],
2593
+ "dry_run_diff": str(artifacts["dry_run_diff"].relative_to(job_dir)),
2594
+ "dry_run_diff_sha256": artifacts["dry_run_diff_sha256"],
2595
+ "rollback_manifest": str(artifacts["rollback_manifest"].relative_to(job_dir)),
2596
+ "rollback_manifest_sha256": artifacts["rollback_manifest_sha256"],
2597
+ "presentation_event_seq": presentation["event_seq"],
2598
+ "policy": "human rejection is terminal for the exact presented artifact hashes",
2599
+ "real_project_modified": False,
2600
+ }
2601
+ rejection_path = artifacts["artifact_dir"] / "human-promotion-rejection.json"
2602
+ rejection_path.write_text(json.dumps(rejection, indent=2) + "\n", encoding="utf-8")
2603
+ rejection_event = log.append(
2604
+ "promotion_human_rejected",
2605
+ {
2606
+ **rejection,
2607
+ "rejection": str(rejection_path.relative_to(job_dir)),
2608
+ "rejection_sha256": sha256_file(rejection_path),
2609
+ },
2610
+ )
2611
+ try:
2612
+ current_events = read_events(job_dir / "events.jsonl")
2613
+ exported = export_failed_trace(project, task, current_events, rejection_event)
2614
+ trace_event_type = "central_trace_exported" if exported.get("index_ok") else "central_trace_index_pending"
2615
+ log.append(
2616
+ trace_event_type,
2617
+ {
2618
+ "task_id": args.task_id,
2619
+ "source_event_seq": rejection_event.seq,
2620
+ **exported,
2621
+ },
2622
+ )
2623
+ if not exported.get("index_ok"):
2624
+ print("Warning: rejection trace is durable but Mumpix indexing is pending; run `wydcode traces sync --project .`")
2625
+ except Exception as exc: # noqa: BLE001 - local rejection remains authoritative.
2626
+ log.append(
2627
+ "central_trace_export_failed",
2628
+ {
2629
+ "task_id": args.task_id,
2630
+ "source_event_seq": rejection_event.seq,
2631
+ "error": str(exc),
2632
+ "ledger_status": "unknown",
2633
+ "next_action": "run `wydcode traces sync --project .`",
2634
+ },
2635
+ )
2636
+ print(f"Warning: rejection trace export failed; run `wydcode traces sync --project .`: {exc}")
2637
+ code = 0
2638
+ print(f"Promotion rejected: {args.task_id}")
2639
+ print("Real project modified: false")
2640
+
2641
+ events = read_events(job_dir / "events.jsonl")
2642
+ state = replay(events)
2643
+ plan = materialize_plan_from_events(events, load_plan(job_dir))
2644
+ write_json(job_dir / "plan.json", plan)
2645
+ write_state_snapshot(job_dir / "job.json", state)
2646
+ write_handoff(job_dir, state_to_json(state), plan)
2647
+ return code
2648
+
2649
+
2650
+ def job_promote_main(args: argparse.Namespace) -> int:
2651
+ project = args.project.expanduser().resolve()
2652
+ job_dir = find_job_dir(project, args.job_id)
2653
+ with JobLock(job_dir / ".lock"):
2654
+ events = read_events(job_dir / "events.jsonl")
2655
+ state = replay(events)
2656
+ plan = materialize_plan_from_events(events, load_plan(job_dir))
2657
+ task = next((item for item in plan.get("tasks", []) if item.get("id") == args.task_id), None)
2658
+ log = JobEventLog(job_dir / "events.jsonl", state.job_id or job_dir.name)
2659
+ generated = ensure_promotion_artifacts(project, job_dir, args.task_id)
2660
+ if generated.get("generated"):
2661
+ log.append("promotion_artifacts_generated", {"task_id": args.task_id, **generated})
2662
+ human_review = human_promotion_review(events, args.task_id)
2663
+ artifacts = promotion_artifact_hashes(job_dir, args.task_id)
2664
+ if not task or task.get("status") != "passed":
2665
+ log.append(
2666
+ "promotion_blocked",
2667
+ {
2668
+ "task_id": args.task_id,
2669
+ "error": "promotion_task_not_passed",
2670
+ "task_status": task.get("status") if task else "missing",
2671
+ "policy": "promotion_requires_passed_rehearsal_task_and_human_diff_review",
2672
+ },
2673
+ )
2674
+ code = 2
2675
+ print(f"Promotion blocked: {args.task_id}")
2676
+ print("Reason: promotion_task_not_passed")
2677
+ elif human_review is None:
2678
+ log.append(
2679
+ "promotion_blocked",
2680
+ {
2681
+ "task_id": args.task_id,
2682
+ "error": "human_source_promotion_review_required",
2683
+ "policy": "ai_prescreen_is_not_source_promotion_authorization",
2684
+ "required_event": "promotion_human_authorized",
2685
+ "required_field": "human_source_promotion_authorized=true",
2686
+ "legacy_accepted_for_existing_jobs": "promotion_human_diff_reviewed",
2687
+ "real_project_modified": False,
2688
+ },
2689
+ )
2690
+ code = 2
2691
+ print(f"Promotion blocked: {args.task_id}")
2692
+ print("Reason: human_source_promotion_review_required")
2693
+ elif artifacts.get("missing"):
2694
+ log.append(
2695
+ "promotion_blocked",
2696
+ {
2697
+ "task_id": args.task_id,
2698
+ "error": "promotion_artifacts_missing",
2699
+ "missing": artifacts.get("missing"),
2700
+ "human_review": human_review,
2701
+ "real_project_modified": False,
2702
+ },
2703
+ )
2704
+ code = 2
2705
+ print(f"Promotion blocked: {args.task_id}")
2706
+ print("Reason: promotion_artifacts_missing")
2707
+ elif human_review.get("bundle_sha256") and human_review.get("bundle_sha256") != artifacts.get("bundle_sha256"):
2708
+ log.append(
2709
+ "promotion_blocked",
2710
+ {
2711
+ "task_id": args.task_id,
2712
+ "error": "promotion_authorized_bundle_hash_mismatch",
2713
+ "authorized_bundle_sha256": human_review.get("bundle_sha256"),
2714
+ "current_bundle_sha256": artifacts.get("bundle_sha256"),
2715
+ "human_review": human_review,
2716
+ "policy": "promotion applies only the exact bundle hash that was presented and authorized",
2717
+ "real_project_modified": False,
2718
+ },
2719
+ )
2720
+ code = 2
2721
+ print(f"Promotion blocked: {args.task_id}")
2722
+ print("Reason: promotion_authorized_bundle_hash_mismatch")
2723
+ elif not args.execute:
2724
+ log.append(
2725
+ "promotion_preflight_passed",
2726
+ {
2727
+ "task_id": args.task_id,
2728
+ "human_review": human_review,
2729
+ "real_project_modified": False,
2730
+ "next_action": "rerun with --execute to apply the approved bundle",
2731
+ },
2732
+ )
2733
+ code = 0
2734
+ print(f"Promotion preflight passed: {args.task_id}")
2735
+ print("Real project modified: false")
2736
+ else:
2737
+ apply_result = promotion_apply_bundle(project, job_dir, args.task_id)
2738
+ if apply_result.get("ok"):
2739
+ receipt = apply_result.get("receipt") or {}
2740
+ promotion_event = log.append(
2741
+ "promotion_applied",
2742
+ {
2743
+ "task_id": args.task_id,
2744
+ "bundle_sha256": receipt.get("bundle_sha256"),
2745
+ "applied_count": receipt.get("applied_count"),
2746
+ "receipt_path": apply_result.get("receipt_path"),
2747
+ "receipt_sha256": apply_result.get("receipt_sha256"),
2748
+ "rollback_content_snapshot_path": apply_result.get("rollback_content_snapshot"),
2749
+ "rollback_content_snapshot_sha256": apply_result.get("rollback_content_snapshot_sha256"),
2750
+ "human_review": human_review,
2751
+ "real_project_modified": True,
2752
+ },
2753
+ )
2754
+ fired_event = next(
2755
+ (
2756
+ event
2757
+ for event in reversed(read_events(job_dir / "events.jsonl"))
2758
+ if event.type == "retrieval_fired" and event.payload.get("task_id") == args.task_id
2759
+ ),
2760
+ None,
2761
+ )
2762
+ fired = fired_event.to_json() if fired_event else None
2763
+ if fired:
2764
+ log.append(
2765
+ "retrieval_backed_promotion",
2766
+ {
2767
+ "task_id": args.task_id,
2768
+ "retrieval_event_seq": fired.get("seq"),
2769
+ "retrieval_event_sha256": recurrence_sha256_json(fired),
2770
+ "gene_id": fired.get("payload", {}).get("gene_id"),
2771
+ "source_central_event_sha256": fired.get("payload", {}).get("source_central_event_sha256"),
2772
+ "recurrence_key_sha256": fired.get("payload", {}).get("recurrence_key_sha256"),
2773
+ "preference_sha256": fired.get("payload", {}).get("preference_sha256"),
2774
+ "promotion_event_seq": promotion_event.seq,
2775
+ "promotion_receipt_sha256": apply_result.get("receipt_sha256"),
2776
+ "classification": "route_fired_and_promoted_not_paired_capability_lift",
2777
+ },
2778
+ )
2779
+ try:
2780
+ export_events = read_events(job_dir / "events.jsonl")
2781
+ export_result = export_promoted_trace(project, job_dir, task, export_events, promotion_event)
2782
+ log.append(
2783
+ "central_trace_exported" if export_result.get("index_ok") else "central_trace_export_partial",
2784
+ {
2785
+ "task_id": args.task_id,
2786
+ **export_result,
2787
+ "source_of_truth": "cross_project_hash_chained_jsonl",
2788
+ "mumpix_role": "derived_rebuildable_index",
2789
+ "retry_required": not export_result.get("index_ok"),
2790
+ },
2791
+ )
2792
+ if not export_result.get("index_ok"):
2793
+ print("Warning: promotion is durable, but its Mumpix index update failed.")
2794
+ print("Run `wydcode traces sync --project .` and verify `wydcode traces status` reports zero pending events.")
2795
+ except Exception as exc: # noqa: BLE001 - promotion already succeeded and must remain successful.
2796
+ log.append(
2797
+ "central_trace_export_failed",
2798
+ {
2799
+ "task_id": args.task_id,
2800
+ "error": str(exc)[:1000],
2801
+ "promotion_remains_applied": True,
2802
+ "retry_command": "wydcode traces sync --project .",
2803
+ },
2804
+ )
2805
+ print(f"Warning: promotion is durable, but central trace export failed: {exc}")
2806
+ print("Run `wydcode traces sync --project .` before relying on the cross-project trace index.")
2807
+ code = 0
2808
+ print(f"Promotion applied: {args.task_id}")
2809
+ print(f"Applied files: {receipt.get('applied_count')}")
2810
+ print(f"Receipt: {apply_result.get('receipt_path')}")
2811
+ else:
2812
+ log.append(
2813
+ "promotion_failed",
2814
+ {
2815
+ "task_id": args.task_id,
2816
+ "error": apply_result.get("error"),
2817
+ "details": apply_result,
2818
+ "human_review": human_review,
2819
+ "real_project_modified": apply_result.get("real_project_modified", False),
2820
+ },
2821
+ )
2822
+ code = 2
2823
+ print(f"Promotion failed: {args.task_id}")
2824
+ print(f"Reason: {apply_result.get('error')}")
2825
+
2826
+ events = read_events(job_dir / "events.jsonl")
2827
+ state = replay(events)
2828
+ plan = materialize_plan_from_events(events, load_plan(job_dir))
2829
+ write_json(job_dir / "plan.json", plan)
2830
+ write_state_snapshot(job_dir / "job.json", state)
2831
+ write_handoff(job_dir, state_to_json(state), plan)
2832
+ return code
2833
+
2834
+
2835
+ def job_reverify_task_main(args: argparse.Namespace) -> int:
2836
+ project = args.project.expanduser().resolve()
2837
+ if args.job_id:
2838
+ job_dir = job_root(project) / args.job_id
2839
+ else:
2840
+ found = find_latest_incomplete_job(project)
2841
+ if not found:
2842
+ raise SystemExit(f"No incomplete Socra jobs found under {job_root(project)}")
2843
+ job_dir = found
2844
+
2845
+ with JobLock(job_dir / ".lock"):
2846
+ events = read_events(job_dir / "events.jsonl")
2847
+ state = replay(events)
2848
+ plan = materialize_plan_from_events(events, load_plan(job_dir))
2849
+ tasks = {str(task.get("id")): task for task in plan.get("tasks", [])}
2850
+ task = tasks.get(args.task_id)
2851
+ if not task:
2852
+ raise SystemExit(f"Task not found: {args.task_id}")
2853
+ if task.get("status") != "passed" and not args.allow_unpassed:
2854
+ raise SystemExit(f"Task is not passed: {args.task_id}. Pass --allow-unpassed to record evidence anyway.")
2855
+
2856
+ evidence_path = args.evidence.expanduser().resolve()
2857
+ evidence = json.loads(evidence_path.read_text(encoding="utf-8"))
2858
+ criteria, errors, all_passed = validate_reverification_evidence(task, evidence)
2859
+ artifact_dir = job_dir / "artifacts" / "tasks" / args.task_id
2860
+ artifact_dir.mkdir(parents=True, exist_ok=True)
2861
+ stored_evidence = artifact_dir / "reverification-evidence.json"
2862
+ stored_evidence.write_text(json.dumps({"task_id": args.task_id, **evidence, "criteria": criteria}, indent=2) + "\n", encoding="utf-8")
2863
+
2864
+ log = JobEventLog(job_dir / "events.jsonl", state.job_id or job_dir.name)
2865
+ if errors:
2866
+ log.append(
2867
+ "task_reverification_failed",
2868
+ {
2869
+ "task_id": args.task_id,
2870
+ "errors": errors,
2871
+ "evidence_path": str(stored_evidence.relative_to(job_dir)),
2872
+ "sha256": sha256_file(stored_evidence),
2873
+ },
2874
+ )
2875
+ code = 2
2876
+ print(f"Task reverification failed: {args.task_id}")
2877
+ for error in errors:
2878
+ print(f"- {error}")
2879
+ else:
2880
+ log.append(
2881
+ "task_reverified",
2882
+ {
2883
+ "task_id": args.task_id,
2884
+ "criteria": criteria,
2885
+ "runner_checks": evidence.get("runner_checks") if isinstance(evidence.get("runner_checks"), list) else [],
2886
+ "review_level": "acceptance_reverified" if all_passed else "acceptance_partial",
2887
+ "acceptance_review": "full" if all_passed else "partial",
2888
+ "all_criteria_passed": all_passed,
2889
+ "evidence_path": str(stored_evidence.relative_to(job_dir)),
2890
+ "sha256": sha256_file(stored_evidence),
2891
+ "notes": evidence.get("notes") or args.notes or "",
2892
+ },
2893
+ )
2894
+ code = 0 if all_passed else 2
2895
+ print(f"Task reverified: {args.task_id}")
2896
+ print(f"Acceptance review: {'full' if all_passed else 'partial'}")
2897
+
2898
+ events = read_events(job_dir / "events.jsonl")
2899
+ state = replay(events)
2900
+ plan = materialize_plan_from_events(events, load_plan(job_dir))
2901
+ write_json(job_dir / "plan.json", plan)
2902
+ write_state_snapshot(job_dir / "job.json", state)
2903
+ write_handoff(job_dir, state_to_json(state), plan)
2904
+ return code
2905
+
2906
+
2907
+ def job_run_milestone_main(args: argparse.Namespace) -> int:
2908
+ project = args.project.expanduser().resolve()
2909
+ if args.job_id:
2910
+ job_dir = job_root(project) / args.job_id
2911
+ else:
2912
+ found = find_latest_incomplete_job(project)
2913
+ if not found:
2914
+ raise SystemExit(f"No incomplete Socra jobs found under {job_root(project)}")
2915
+ job_dir = found
2916
+
2917
+ with JobLock(job_dir / ".lock"):
2918
+ events = read_events(job_dir / "events.jsonl")
2919
+ state = replay(events)
2920
+ plan = materialize_plan_from_events(events, load_plan(job_dir))
2921
+ tasks = plan.get("tasks") if isinstance(plan.get("tasks"), list) else []
2922
+ passed_task_ids = [str(task.get("id")) for task in tasks if task.get("status") == "passed"]
2923
+ milestone_id = args.milestone_id or f"milestone-{len([e for e in events if e.type == 'milestone_runner_started']) + 1:03d}"
2924
+ command = args.command or "npm run build"
2925
+ review_level, inferred_promotion_grade = infer_milestone_review_level(command)
2926
+ promotion_grade = bool(args.promotion_grade or inferred_promotion_grade)
2927
+ log = JobEventLog(job_dir / "events.jsonl", state.job_id or job_dir.name)
2928
+ log.append(
2929
+ "milestone_runner_started",
2930
+ {
2931
+ "milestone_id": milestone_id,
2932
+ "passed_task_ids": passed_task_ids,
2933
+ "command": command,
2934
+ "review_level": review_level,
2935
+ "promotion_grade": promotion_grade,
2936
+ "dependency_prep": args.prepare_deps,
2937
+ "policy": "partial_smoke_reviewer_audit_enforced_dependency_vulnerabilities_block_only_promotion_grade",
2938
+ },
2939
+ )
2940
+
2941
+ partials = tasks_requiring_milestone_reverification(events, passed_task_ids)
2942
+ if partials and not args.allow_partial_smoke:
2943
+ log.append(
2944
+ "milestone_runner_failed",
2945
+ {
2946
+ "milestone_id": milestone_id,
2947
+ "error": "partial_smoke_tasks_require_reverification",
2948
+ "review_level": review_level,
2949
+ "promotion_grade": False,
2950
+ "partial_tasks": partials,
2951
+ "next_action": "run a milestone preview/build/review that proves acceptance, then emit a reclassification event",
2952
+ },
2953
+ )
2954
+ events = read_events(job_dir / "events.jsonl")
2955
+ state = replay(events)
2956
+ plan = materialize_plan_from_events(events, load_plan(job_dir))
2957
+ write_json(job_dir / "plan.json", plan)
2958
+ write_state_snapshot(job_dir / "job.json", state)
2959
+ write_handoff(job_dir, state_to_json(state), plan)
2960
+ print(f"Milestone runner failed: {milestone_id}")
2961
+ print("Reason: partial_smoke_tasks_require_reverification")
2962
+ for item in partials:
2963
+ print(f"- {item['task_id']}: {item['review_level']} / {item['acceptance_review']}")
2964
+ return 2
2965
+
2966
+ runner_dir = job_dir / "sandbox" / "milestones" / milestone_id
2967
+ if runner_dir.exists():
2968
+ shutil.rmtree(runner_dir)
2969
+ overlay_dir = runner_dir / "project"
2970
+ overlays = overlay_passed_task_artifacts(project, job_dir, plan, overlay_dir)
2971
+ dependency_links = link_existing_dependency_trees(project, overlay_dir) if args.prepare_deps == "none" else []
2972
+ log.append(
2973
+ "milestone_overlay_written",
2974
+ {
2975
+ "milestone_id": milestone_id,
2976
+ "overlay_dir": str(overlay_dir.relative_to(job_dir)),
2977
+ "overlays": overlays,
2978
+ "reused_dependency_trees": dependency_links,
2979
+ },
2980
+ )
2981
+
2982
+ dep_payload, dep_code = prepare_runner_dependencies(overlay_dir, args.prepare_deps, args.deps_timeout_seconds)
2983
+ exception_counts = active_vulnerability_exception_counts(events)
2984
+ dep_vuln_policy = dependency_vulnerability_policy(dep_payload, promotion_grade, exception_counts) if args.prepare_deps != "none" else None
2985
+ if args.prepare_deps != "none":
2986
+ log.append(
2987
+ "sandbox_env_prepared",
2988
+ {
2989
+ "milestone_id": milestone_id,
2990
+ "overlay_dir": str(overlay_dir.relative_to(job_dir)),
2991
+ **dep_payload,
2992
+ "dependency_vulnerability_policy": dep_vuln_policy,
2993
+ },
2994
+ )
2995
+ if dep_code != 0:
2996
+ payload = {
2997
+ "milestone_id": milestone_id,
2998
+ "command": command,
2999
+ "returncode": dep_code,
3000
+ "overlay_dir": str(overlay_dir.relative_to(job_dir)),
3001
+ "overlays_count": len(overlays),
3002
+ "review_level": review_level,
3003
+ "promotion_grade": False,
3004
+ "dependency_prep": dep_payload,
3005
+ "error": "sandbox_dependency_preparation_failed",
3006
+ }
3007
+ log.append(
3008
+ "milestone_runner_unavailable",
3009
+ {**payload, "outcome": "infrastructure_unavailable", "execution_reached_test_runner": False},
3010
+ )
3011
+ events = read_events(job_dir / "events.jsonl")
3012
+ state = replay(events)
3013
+ plan = materialize_plan_from_events(events, load_plan(job_dir))
3014
+ write_json(job_dir / "plan.json", plan)
3015
+ write_state_snapshot(job_dir / "job.json", state)
3016
+ write_handoff(job_dir, state_to_json(state), plan)
3017
+ print(f"Milestone runner unavailable: {milestone_id}")
3018
+ print("Reason: sandbox_dependency_preparation_failed")
3019
+ print(str(dep_payload.get("stderr_tail") or dep_payload.get("stdout_tail") or "")[-2000:])
3020
+ return 2
3021
+ if dep_vuln_policy and dep_vuln_policy.get("blocks_promotion"):
3022
+ payload = {
3023
+ "milestone_id": milestone_id,
3024
+ "command": command,
3025
+ "returncode": 2,
3026
+ "overlay_dir": str(overlay_dir.relative_to(job_dir)),
3027
+ "overlays_count": len(overlays),
3028
+ "review_level": review_level,
3029
+ "promotion_grade": False,
3030
+ "dependency_prep": args.prepare_deps,
3031
+ "dependency_vulnerability_policy": dep_vuln_policy,
3032
+ "error": "dependency_vulnerability_policy_blocked",
3033
+ }
3034
+ log.append("milestone_runner_failed", payload)
3035
+ events = read_events(job_dir / "events.jsonl")
3036
+ state = replay(events)
3037
+ plan = materialize_plan_from_events(events, load_plan(job_dir))
3038
+ write_json(job_dir / "plan.json", plan)
3039
+ write_state_snapshot(job_dir / "job.json", state)
3040
+ write_handoff(job_dir, state_to_json(state), plan)
3041
+ print(f"Milestone runner failed: {milestone_id}")
3042
+ print("Reason: dependency_vulnerability_policy_blocked")
3043
+ print(f"Vulnerabilities: {dep_vuln_policy.get('counts')}")
3044
+ return 2
3045
+
3046
+ output_dir = runner_dir / "logs"
3047
+ output_dir.mkdir(parents=True, exist_ok=True)
3048
+ stdout_path = output_dir / "stdout.log"
3049
+ stderr_path = output_dir / "stderr.log"
3050
+ try:
3051
+ runner_env = os.environ.copy()
3052
+ if args.prepare_deps == "none":
3053
+ # A gate may inspect existing dependencies, but it must not repair or install them implicitly.
3054
+ runner_env["pnpm_config_verify_deps_before_run"] = "warn"
3055
+ result = subprocess.run(
3056
+ command,
3057
+ cwd=overlay_dir,
3058
+ shell=True,
3059
+ text=True,
3060
+ capture_output=True,
3061
+ timeout=args.timeout_seconds,
3062
+ check=False,
3063
+ env=runner_env,
3064
+ )
3065
+ stdout = result.stdout or ""
3066
+ stderr = result.stderr or ""
3067
+ returncode = result.returncode
3068
+ outcome, reached_test_runner, error = classify_gate_process(returncode, stdout, stderr)
3069
+ except subprocess.TimeoutExpired as exc:
3070
+ stdout = str(exc.stdout or "")
3071
+ stderr = str(exc.stderr or "")
3072
+ returncode = 124
3073
+ outcome, reached_test_runner, error = "infrastructure_unavailable", False, "runner_timeout"
3074
+ stdout_path.write_text(stdout, encoding="utf-8")
3075
+ stderr_path.write_text(stderr, encoding="utf-8")
3076
+ manifest_after_command = dependency_manifest(overlay_dir)
3077
+ payload = {
3078
+ "milestone_id": milestone_id,
3079
+ "command": command,
3080
+ "returncode": returncode,
3081
+ "stdout": str(stdout_path.relative_to(job_dir)),
3082
+ "stderr": str(stderr_path.relative_to(job_dir)),
3083
+ "overlay_dir": str(overlay_dir.relative_to(job_dir)),
3084
+ "overlays_count": len(overlays),
3085
+ "review_level": review_level,
3086
+ "promotion_grade": promotion_grade,
3087
+ "dependency_prep": args.prepare_deps,
3088
+ "dependency_auto_repair": False if args.prepare_deps == "none" else None,
3089
+ "reused_dependency_trees": dependency_links,
3090
+ "manifest_after_command": manifest_after_command,
3091
+ "outcome": outcome,
3092
+ "execution_reached_test_runner": reached_test_runner,
3093
+ }
3094
+ if dep_vuln_policy is not None:
3095
+ payload["dependency_vulnerability_policy"] = dep_vuln_policy
3096
+ if outcome == "passed":
3097
+ log.append("milestone_runner_passed", payload)
3098
+ code = 0
3099
+ print(f"Milestone runner passed: {milestone_id}")
3100
+ elif outcome == "infrastructure_unavailable":
3101
+ log.append("milestone_runner_unavailable", {**payload, "error": error})
3102
+ code = 3
3103
+ print(f"Milestone runner unavailable: {milestone_id}")
3104
+ print(f"Reason: {error}")
3105
+ print((stderr or stdout)[-2000:])
3106
+ else:
3107
+ log.append("milestone_runner_failed", {**payload, "error": error})
3108
+ code = 2
3109
+ print(f"Milestone runner failed: {milestone_id}")
3110
+ print(f"Reason: {error}")
3111
+ print((stderr or stdout)[-2000:])
3112
+
3113
+ events = read_events(job_dir / "events.jsonl")
3114
+ state = replay(events)
3115
+ plan = materialize_plan_from_events(events, load_plan(job_dir))
3116
+ write_json(job_dir / "plan.json", plan)
3117
+ write_state_snapshot(job_dir / "job.json", state)
3118
+ write_handoff(job_dir, state_to_json(state), plan)
3119
+ return code
3120
+
3121
+
3122
+ def job_run_next_main(args: argparse.Namespace) -> int:
3123
+ project = args.project.expanduser().resolve()
3124
+ if args.job_id:
3125
+ job_dir = job_root(project) / args.job_id
3126
+ else:
3127
+ found = find_latest_incomplete_job(project)
3128
+ if not found:
3129
+ raise SystemExit(f"No incomplete Socra jobs found under {job_root(project)}")
3130
+ job_dir = found
3131
+
3132
+ with JobLock(job_dir / ".lock"):
3133
+ events = read_events(job_dir / "events.jsonl")
3134
+ state = replay(events)
3135
+ plan = materialize_plan_from_events(events, load_plan(job_dir))
3136
+ ready = ready_task_ids(plan)
3137
+ if not ready:
3138
+ raise SystemExit("No ready pending tasks.")
3139
+ task_id = args.task_id or ready[0]
3140
+ tasks = {str(task.get("id")): task for task in plan.get("tasks", [])}
3141
+ task = tasks.get(task_id)
3142
+ if not task:
3143
+ raise SystemExit(f"Task not found: {task_id}")
3144
+
3145
+ log = JobEventLog(job_dir / "events.jsonl", state.job_id or job_dir.name)
3146
+ creator = state.creator_config or {}
3147
+ health = endpoint_health(str(creator.get("endpoint") or "http://127.0.0.1:11439/v1"))
3148
+ expected_model = str(creator.get("expected_served_model_id") or Path(str(creator.get("model") or "")).name)
3149
+ if expected_model:
3150
+ served_ids = [str(item) for item in health.get("model_ids") or []]
3151
+ health["expected_served_model_id"] = expected_model
3152
+ health["model_identity_ok"] = expected_model in served_ids
3153
+ health["served_model_ids"] = served_ids
3154
+ log.append("creator_endpoint_health_checked", {"task_id": task_id, **health})
3155
+ model_backed = task_id != "task-001"
3156
+ if model_backed and (not health.get("ok") or not health.get("model_identity_ok")):
3157
+ error = "creator_endpoint_unavailable" if not health.get("ok") else "creator_model_identity_mismatch"
3158
+ log.append(
3159
+ "task_blocked",
3160
+ {
3161
+ "task_id": task_id,
3162
+ "error": error,
3163
+ "endpoint_health": health,
3164
+ "policy": "model_backed_tasks_require_live_endpoint_and_exact_model_identity",
3165
+ },
3166
+ )
3167
+ events = read_events(job_dir / "events.jsonl")
3168
+ state = replay(events)
3169
+ plan = materialize_plan_from_events(events, load_plan(job_dir))
3170
+ write_json(job_dir / "plan.json", plan)
3171
+ write_state_snapshot(job_dir / "job.json", state)
3172
+ write_handoff(job_dir, state_to_json(state), plan)
3173
+ print(f"Blocked task: {task_id}")
3174
+ print(f"Reason: {error}")
3175
+ print(f"Endpoint: {health.get('url')}")
3176
+ print(f"Error: {health.get('error')}")
3177
+ print(f"Expected model: {health.get('expected_served_model_id')}")
3178
+ print(f"Served models: {', '.join(health.get('served_model_ids') or []) or 'none'}")
3179
+ return 2
3180
+ if task_id != "task-001":
3181
+ declared_paths = [str(path) for path in task.get("declared_files", [])]
3182
+ if len(declared_paths) > MAX_JSON_CONTRACT_DECLARED_FILES:
3183
+ log.append(
3184
+ "task_blocked",
3185
+ {
3186
+ "task_id": task_id,
3187
+ "error": "task_shape_requires_replan",
3188
+ "declared_file_count": len(declared_paths),
3189
+ "max_json_contract_declared_files": MAX_JSON_CONTRACT_DECLARED_FILES,
3190
+ "declared_files": declared_paths,
3191
+ "policy": "mumpix_admin_stress_v1_observed_three_file_json_contract_unreliable; split into <=2 file module tasks or single-file raw tasks",
3192
+ },
3193
+ )
3194
+ events = read_events(job_dir / "events.jsonl")
3195
+ state = replay(events)
3196
+ plan = materialize_plan_from_events(events, load_plan(job_dir))
3197
+ write_json(job_dir / "plan.json", plan)
3198
+ write_state_snapshot(job_dir / "job.json", state)
3199
+ write_handoff(job_dir, state_to_json(state), plan)
3200
+ print(f"Blocked task: {task_id}")
3201
+ print("Reason: task_shape_requires_replan")
3202
+ print(f"Declared files: {len(declared_paths)} > {MAX_JSON_CONTRACT_DECLARED_FILES}")
3203
+ return 2
3204
+ task_wall_start = time.monotonic()
3205
+ attempt = int(task.get("attempt_count") or 0) + 1
3206
+ log.append(
3207
+ "task_started",
3208
+ {
3209
+ "task_id": task_id,
3210
+ "attempt": attempt,
3211
+ "creator_config": creator,
3212
+ "execution_mode": "model_creator_json_contract_sandbox_only",
3213
+ "declared_files": task.get("declared_files", []),
3214
+ "prompt_layout": CREATOR_PROMPT_LAYOUT,
3215
+ },
3216
+ )
3217
+ artifact_dir = job_dir / "artifacts" / "tasks" / task_id
3218
+ artifact_dir.mkdir(parents=True, exist_ok=True)
3219
+ effective_sources = latest_passed_artifacts_by_path(job_dir, plan)
3220
+ file_context = read_declared_file_context(project, task, effective_sources)
3221
+ retry_feedback = previous_attempt_feedback(job_dir, events, task_id)
3222
+ context_sources = [
3223
+ {
3224
+ "path": item.get("path"),
3225
+ "source": item.get("source"),
3226
+ "source_task_id": item.get("source_task_id"),
3227
+ "sandbox_path": item.get("sandbox_path"),
3228
+ "sha256": item.get("sha256"),
3229
+ "exists": item.get("exists"),
3230
+ }
3231
+ for item in file_context
3232
+ ]
3233
+ log.append(
3234
+ "task_effective_context_resolved",
3235
+ {
3236
+ "task_id": task_id,
3237
+ "declared_files": task.get("declared_files", []),
3238
+ "context_sources": context_sources,
3239
+ "policy": "latest_passed_sandbox_artifact_wins",
3240
+ "prompt_layout": CREATOR_PROMPT_LAYOUT,
3241
+ },
3242
+ )
3243
+ retrieval_mode = str(task.get("retrieval_mode") or "exact")
3244
+ if retrieval_mode == "disabled":
3245
+ retrieval = {"status": "retrieval_disabled_control", "fired": False}
3246
+ else:
3247
+ retrieval = retrieve_validated_behavior(project, task)
3248
+ recurrence = task.get("recurrence") if isinstance(task.get("recurrence"), dict) else {}
3249
+ validated_behavior = None
3250
+ retrieval_selection_sha256 = None
3251
+ retrieval_fired_payload = None
3252
+ context_budget_tokens = int(creator.get("context_budget_tokens") or 0)
3253
+ reserved_output_tokens = int(creator.get("max_output_tokens") or 0)
3254
+ output_contract = select_creator_output_contract(task, file_context, reserved_output_tokens)
3255
+ log.append("creator_output_contract_selected", {"task_id": task_id, **output_contract})
3256
+ base_messages = build_creator_messages(
3257
+ job_dir, project, task, file_context, retry_feedback, None, output_contract
3258
+ )
3259
+ base_input_tokens_estimate = (len(json.dumps(base_messages, ensure_ascii=True)) + 3) // 4
3260
+ injection_tokens_estimate = 0
3261
+ if retrieval.get("fired"):
3262
+ gene = retrieval.get("gene") if isinstance(retrieval.get("gene"), dict) else {}
3263
+ validated_behavior = gene.get("preference") if isinstance(gene.get("preference"), dict) else None
3264
+ if validated_behavior is None:
3265
+ raise RuntimeError("retrieval_fired_without_behavior_preference")
3266
+ injection_tokens_estimate = (len(recurrence_canonical_json(validated_behavior)) + 3) // 4
3267
+ budget_decision = injection_budget_decision(
3268
+ context_budget_tokens,
3269
+ reserved_output_tokens,
3270
+ base_input_tokens_estimate,
3271
+ injection_tokens_estimate,
3272
+ )
3273
+ if not budget_decision["fits"]:
3274
+ retrieval = {
3275
+ **retrieval,
3276
+ "status": "injection_budget_unavailable",
3277
+ "fired": False,
3278
+ "available_injection_tokens": budget_decision["available_injection_tokens"],
3279
+ }
3280
+ validated_behavior = None
3281
+ else:
3282
+ selection = {
3283
+ "schema_version": 1,
3284
+ "task_id": task_id,
3285
+ "mode": retrieval_mode,
3286
+ "gene_id": gene.get("gene_id"),
3287
+ "source_central_event_sha256": retrieval.get("source_central_event_sha256"),
3288
+ "recurrence_key_sha256": gene.get("recurrence_key_sha256"),
3289
+ "preference": validated_behavior,
3290
+ "preference_sha256": gene.get("preference_sha256"),
3291
+ "injection_policy": "prefer_behavior_only_no_patch_or_source",
3292
+ "paired_control": "rerun the same frozen task with --retrieval disabled",
3293
+ }
3294
+ selection_path = artifact_dir / "retrieval-selection.json"
3295
+ write_json(selection_path, selection)
3296
+ retrieval_selection_sha256 = sha256_file(selection_path)
3297
+ retrieval_fired_payload = {
3298
+ "task_id": task_id,
3299
+ "mode": retrieval_mode,
3300
+ "gene_id": gene.get("gene_id"),
3301
+ "source_central_event_sha256": retrieval.get("source_central_event_sha256"),
3302
+ "recurrence_key_sha256": gene.get("recurrence_key_sha256"),
3303
+ "preference_sha256": gene.get("preference_sha256"),
3304
+ "selection": str(selection_path.relative_to(job_dir)),
3305
+ "selection_sha256": retrieval_selection_sha256,
3306
+ "injected_chars": len(recurrence_canonical_json(validated_behavior)),
3307
+ "injected_tokens_estimate": injection_tokens_estimate,
3308
+ "base_input_tokens_estimate": base_input_tokens_estimate,
3309
+ "context_budget_tokens": context_budget_tokens,
3310
+ "reserved_output_tokens": reserved_output_tokens,
3311
+ "input_budget_only": True,
3312
+ "output_cap_unchanged": True,
3313
+ "paired_control_supported": True,
3314
+ }
3315
+ log.append(
3316
+ "retrieval_considered",
3317
+ {
3318
+ "task_id": task_id,
3319
+ "mode": retrieval_mode,
3320
+ "status": retrieval.get("status"),
3321
+ "fired": bool(retrieval.get("fired")),
3322
+ "match_count": retrieval.get("match_count"),
3323
+ "project_scope_id": retrieval.get("project_scope_id"),
3324
+ "recurrence_key_sha256": recurrence.get("recurrence_key_sha256"),
3325
+ "base_input_tokens_estimate": base_input_tokens_estimate,
3326
+ "injection_tokens_estimate": injection_tokens_estimate,
3327
+ "context_budget_tokens": context_budget_tokens,
3328
+ "reserved_output_tokens": reserved_output_tokens,
3329
+ "policy": "exact_key_equality_or_abstain_no_approximate_fallback",
3330
+ },
3331
+ )
3332
+ if retrieval_fired_payload:
3333
+ log.append("retrieval_fired", retrieval_fired_payload)
3334
+ messages = (
3335
+ build_creator_messages(
3336
+ job_dir, project, task, file_context, retry_feedback, validated_behavior, output_contract
3337
+ )
3338
+ if validated_behavior
3339
+ else base_messages
3340
+ )
3341
+ allowed = declared_file_set(task)
3342
+ response_schema = (
3343
+ anchored_edit_response_schema(allowed, int(output_contract.get("max_edits") or MAX_ANCHORED_EDITS))
3344
+ if output_contract.get("mode") == "anchored_edits"
3345
+ else None
3346
+ )
3347
+ response_schema_sha256 = recurrence_sha256_json(response_schema) if response_schema is not None else None
3348
+ request_path = artifact_dir / "creator-request.json"
3349
+ request_path.write_text(
3350
+ json.dumps(
3351
+ {
3352
+ "messages": messages,
3353
+ "structured_output": (
3354
+ {
3355
+ "mode": "json_schema",
3356
+ "schema": response_schema,
3357
+ "schema_sha256": response_schema_sha256,
3358
+ "transport": "llama_cpp_response_format_json_object_schema",
3359
+ "thinking_disabled": True,
3360
+ }
3361
+ if response_schema is not None
3362
+ else None
3363
+ ),
3364
+ },
3365
+ indent=2,
3366
+ )
3367
+ + "\n",
3368
+ encoding="utf-8",
3369
+ )
3370
+ log.append(
3371
+ "creator_request_written",
3372
+ {
3373
+ "task_id": task_id,
3374
+ "path": str(request_path.relative_to(job_dir)),
3375
+ "sha256": sha256_file(request_path),
3376
+ "declared_files": task.get("declared_files", []),
3377
+ "context_sources": context_sources,
3378
+ "retry_feedback_count": len(retry_feedback),
3379
+ "retrieval_status": retrieval.get("status"),
3380
+ "retrieval_fired": bool(retrieval.get("fired")),
3381
+ "retrieval_selection_sha256": retrieval_selection_sha256,
3382
+ "prompt_layout": CREATOR_PROMPT_LAYOUT,
3383
+ "structured_output_mode": "json_schema" if response_schema is not None else None,
3384
+ "structured_output_schema_sha256": response_schema_sha256,
3385
+ "structured_output_transport": (
3386
+ "llama_cpp_response_format_json_object_schema" if response_schema is not None else None
3387
+ ),
3388
+ "thinking_disabled_for_schema": response_schema is not None,
3389
+ },
3390
+ )
3391
+
3392
+ creator_call_ms = 0
3393
+ creator_call_start = None
3394
+ try:
3395
+ creator_call_start = time.monotonic()
3396
+ response = call_creator_model(
3397
+ creator,
3398
+ messages,
3399
+ json_mode=output_contract.get("mode") == "anchored_edits" or len(allowed) != 1,
3400
+ response_schema=response_schema,
3401
+ )
3402
+ creator_call_ms = elapsed_ms(creator_call_start)
3403
+ except Exception as exc: # noqa: BLE001 - local model failures are durable task failures.
3404
+ if creator_call_start is not None:
3405
+ creator_call_ms = elapsed_ms(creator_call_start)
3406
+ log.append(
3407
+ "task_failed",
3408
+ attempt_failure_payload(AttemptFailure.ENDPOINT_ERROR, **{
3409
+ "task_id": task_id,
3410
+ "error": "creator_call_failed",
3411
+ "message": str(exc),
3412
+ "attempt": attempt,
3413
+ "task_wall_ms": elapsed_ms(task_wall_start),
3414
+ "creator_call_ms": creator_call_ms,
3415
+ }),
3416
+ )
3417
+ events = read_events(job_dir / "events.jsonl")
3418
+ state = replay(events)
3419
+ plan = materialize_plan_from_events(events, load_plan(job_dir))
3420
+ write_json(job_dir / "plan.json", plan)
3421
+ write_state_snapshot(job_dir / "job.json", state)
3422
+ write_handoff(job_dir, state_to_json(state), plan)
3423
+ print(f"Failed task: {task_id}")
3424
+ print(f"Reason: creator_call_failed")
3425
+ print(str(exc))
3426
+ return 2
3427
+
3428
+ response_path = artifact_dir / "creator-response.json"
3429
+ response_path.write_text(json.dumps(response, indent=2) + "\n", encoding="utf-8")
3430
+ allowed = declared_file_set(task)
3431
+ parser_repair: dict[str, Any] | None = None
3432
+ try:
3433
+ creator_payload, parser_repair = parse_creator_json_with_repair(str(response.get("content") or ""))
3434
+ if output_contract.get("mode") == "anchored_edits":
3435
+ anchored_payload = artifact_dir / "creator-anchored-edits.json"
3436
+ anchored_payload.write_text(json.dumps(creator_payload, indent=2) + "\n", encoding="utf-8")
3437
+ creator_payload, anchored_metadata = materialize_anchored_edits(creator_payload, file_context, allowed)
3438
+ output_mode = "anchored_edit_json"
3439
+ log.append(
3440
+ "creator_anchored_edits_materialized",
3441
+ {
3442
+ "task_id": task_id,
3443
+ "path": str(anchored_payload.relative_to(job_dir)),
3444
+ "sha256": sha256_file(anchored_payload),
3445
+ "edit_count": len(anchored_metadata),
3446
+ "edits": anchored_metadata,
3447
+ "policy": "exact_unique_nonoverlapping_anchors_fail_closed",
3448
+ },
3449
+ )
3450
+ else:
3451
+ if "edits" in creator_payload and "files" not in creator_payload:
3452
+ raise ValueError("creator_output_contract_violation: full replacement mode requires files")
3453
+ output_mode = "json_contract"
3454
+ except ValueError as exc:
3455
+ repaired_payload = (
3456
+ coerce_single_file_creator_output(str(response.get("content") or ""), allowed)
3457
+ if output_contract.get("mode") != "anchored_edits"
3458
+ else None
3459
+ )
3460
+ if repaired_payload is not None:
3461
+ creator_payload, output_mode = repaired_payload
3462
+ parser_repair = {"applied": True, "types": ["single_declared_file_raw_code_output_wrapped_as_json"]}
3463
+ log.append(
3464
+ "creator_output_repaired",
3465
+ {
3466
+ "task_id": task_id,
3467
+ "attempt": attempt,
3468
+ "reason": "single_declared_file_raw_code_output_wrapped_as_json",
3469
+ "original_error": str(exc),
3470
+ "path": next(iter(allowed)),
3471
+ "output_mode": output_mode,
3472
+ "response_path": str(response_path.relative_to(job_dir)),
3473
+ },
3474
+ )
3475
+ else:
3476
+ log.append(
3477
+ "creator_output_received",
3478
+ {
3479
+ "task_id": task_id,
3480
+ "path": str(response_path.relative_to(job_dir)),
3481
+ "sha256": sha256_file(response_path),
3482
+ "request_model": response.get("request_model"),
3483
+ "response_model": response.get("response_model"),
3484
+ "content_sha256": sha256_text(str(response.get("content") or "")),
3485
+ "content_chars": len(str(response.get("content") or "")),
3486
+ "reasoning_chars": len(str(response.get("reasoning_content") or "")),
3487
+ "finish_reason": response.get("finish_reason"),
3488
+ "usage": response.get("usage") if isinstance(response.get("usage"), dict) else {},
3489
+ "request_max_tokens": response.get("request_max_tokens"),
3490
+ "request_temperature": response.get("request_temperature"),
3491
+ "json_mode": response.get("json_mode"),
3492
+ "structured_output_mode": response.get("structured_output_mode"),
3493
+ "structured_output_schema_sha256": response.get("structured_output_schema_sha256"),
3494
+ "structured_output_transport": response.get("structured_output_transport"),
3495
+ "thinking_disabled_for_schema": response.get("thinking_disabled_for_schema"),
3496
+ "creator_call_ms": creator_call_ms,
3497
+ "prompt_layout": CREATOR_PROMPT_LAYOUT,
3498
+ "parser_repair_applied": False,
3499
+ "parser_repair": None,
3500
+ },
3501
+ )
3502
+ log.append(
3503
+ "task_failed",
3504
+ attempt_failure_payload(
3505
+ classify_creator_finish(str(response.get("finish_reason") or "")) or AttemptFailure.MALFORMED_OUTPUT,
3506
+ **{
3507
+ "task_id": task_id,
3508
+ "error": "creator_output_malformed",
3509
+ "message": str(exc),
3510
+ "attempt": attempt,
3511
+ "response_path": str(response_path.relative_to(job_dir)),
3512
+ "content_chars": len(str(response.get("content") or "")),
3513
+ "reasoning_chars": len(str(response.get("reasoning_content") or "")),
3514
+ "finish_reason": response.get("finish_reason"),
3515
+ "task_wall_ms": elapsed_ms(task_wall_start),
3516
+ "creator_call_ms": creator_call_ms,
3517
+ },
3518
+ ),
3519
+ )
3520
+ events = read_events(job_dir / "events.jsonl")
3521
+ state = replay(events)
3522
+ plan = materialize_plan_from_events(events, load_plan(job_dir))
3523
+ write_json(job_dir / "plan.json", plan)
3524
+ write_state_snapshot(job_dir / "job.json", state)
3525
+ write_handoff(job_dir, state_to_json(state), plan)
3526
+ print(f"Failed task: {task_id}")
3527
+ print("Reason: creator_output_malformed")
3528
+ print(str(exc))
3529
+ return 2
3530
+
3531
+ log.append(
3532
+ "creator_output_received",
3533
+ {
3534
+ "task_id": task_id,
3535
+ "path": str(response_path.relative_to(job_dir)),
3536
+ "sha256": sha256_file(response_path),
3537
+ "request_model": response.get("request_model"),
3538
+ "response_model": response.get("response_model"),
3539
+ "content_sha256": sha256_text(str(response.get("content") or "")),
3540
+ "content_chars": len(str(response.get("content") or "")),
3541
+ "reasoning_chars": len(str(response.get("reasoning_content") or "")),
3542
+ "finish_reason": response.get("finish_reason"),
3543
+ "usage": response.get("usage") if isinstance(response.get("usage"), dict) else {},
3544
+ "request_max_tokens": response.get("request_max_tokens"),
3545
+ "request_temperature": response.get("request_temperature"),
3546
+ "json_mode": response.get("json_mode"),
3547
+ "structured_output_mode": response.get("structured_output_mode"),
3548
+ "structured_output_schema_sha256": response.get("structured_output_schema_sha256"),
3549
+ "structured_output_transport": response.get("structured_output_transport"),
3550
+ "thinking_disabled_for_schema": response.get("thinking_disabled_for_schema"),
3551
+ "creator_call_ms": creator_call_ms,
3552
+ "prompt_layout": CREATOR_PROMPT_LAYOUT,
3553
+ "parser_repair_applied": bool(parser_repair),
3554
+ "parser_repair": parser_repair,
3555
+ },
3556
+ )
3557
+
3558
+ creator_payload, hygiene_changes = preserve_existing_final_newlines(creator_payload, file_context)
3559
+ if hygiene_changes:
3560
+ log.append(
3561
+ "creator_output_hygiene_normalized",
3562
+ {
3563
+ "task_id": task_id,
3564
+ "changes": hygiene_changes,
3565
+ "semantic_repair": False,
3566
+ "policy": "deterministic_existing_file_byte_hygiene_only",
3567
+ },
3568
+ )
3569
+
3570
+ parsed_path = artifact_dir / "creator-output-parsed.json"
3571
+ parsed_path.write_text(json.dumps(creator_payload, indent=2) + "\n", encoding="utf-8")
3572
+ violations = validate_creator_declared_files(creator_payload, allowed)
3573
+ if violations:
3574
+ log.append(
3575
+ "declared_file_violation",
3576
+ {
3577
+ "task_id": task_id,
3578
+ "attempt": attempt,
3579
+ "violations": violations,
3580
+ "allowed_declared_files": sorted(allowed),
3581
+ "parsed_output": str(parsed_path.relative_to(job_dir)),
3582
+ },
3583
+ )
3584
+ log.append(
3585
+ "task_failed",
3586
+ attempt_failure_payload(AttemptFailure.MALFORMED_OUTPUT, **{
3587
+ "task_id": task_id,
3588
+ "error": "declared_file_violation",
3589
+ "violations": violations,
3590
+ "attempt": attempt,
3591
+ "task_wall_ms": elapsed_ms(task_wall_start),
3592
+ "creator_call_ms": creator_call_ms,
3593
+ }),
3594
+ )
3595
+ events = read_events(job_dir / "events.jsonl")
3596
+ state = replay(events)
3597
+ plan = materialize_plan_from_events(events, load_plan(job_dir))
3598
+ write_json(job_dir / "plan.json", plan)
3599
+ write_state_snapshot(job_dir / "job.json", state)
3600
+ write_handoff(job_dir, state_to_json(state), plan)
3601
+ print(f"Failed task: {task_id}")
3602
+ print("Reason: declared_file_violation")
3603
+ print(f"Violations: {', '.join(violations)}")
3604
+ return 2
3605
+
3606
+ sandbox_dir, written = atomic_write_creator_sandbox(job_dir, task_id, creator_payload)
3607
+ log.append(
3608
+ "task_artifacts",
3609
+ {
3610
+ "task_id": task_id,
3611
+ "artifacts": [
3612
+ {
3613
+ "sandbox_path": str((sandbox_dir / row["path"]).relative_to(job_dir)),
3614
+ **{key: value for key, value in row.items() if key != "absolute_path"},
3615
+ }
3616
+ for row in written
3617
+ ],
3618
+ "sandbox_dir": str(sandbox_dir.relative_to(job_dir)),
3619
+ "parsed_output": str(parsed_path.relative_to(job_dir)),
3620
+ "output_mode": output_mode,
3621
+ "parser_repair_applied": bool(parser_repair),
3622
+ "parser_repair": parser_repair,
3623
+ },
3624
+ )
3625
+ review_ok, review_errors, review_level, regression_checks, infrastructure_errors = review_creator_sandbox(
3626
+ project, task, written, output_mode, file_context, job_dir
3627
+ )
3628
+ log.append(
3629
+ "review_completed",
3630
+ {
3631
+ "task_id": task_id,
3632
+ "ok": review_ok,
3633
+ "errors": review_errors,
3634
+ "reviewer": "static_sandbox_artifact_contract_v1",
3635
+ "review_level": review_level,
3636
+ "output_mode": output_mode,
3637
+ "regression_checks": regression_checks,
3638
+ "infrastructure_errors": infrastructure_errors,
3639
+ "outcome": "infrastructure_unavailable" if infrastructure_errors else ("passed" if review_ok else "failed"),
3640
+ },
3641
+ )
3642
+ if infrastructure_errors:
3643
+ log.append(
3644
+ "task_blocked",
3645
+ {
3646
+ "task_id": task_id,
3647
+ "error": "static_checker_unavailable",
3648
+ "infrastructure_errors": infrastructure_errors,
3649
+ "attempt": attempt,
3650
+ "task_wall_ms": elapsed_ms(task_wall_start),
3651
+ "creator_call_ms": creator_call_ms,
3652
+ "policy": "a checker that cannot run is infrastructure-unavailable, not evidence that the patch failed",
3653
+ },
3654
+ )
3655
+ events = read_events(job_dir / "events.jsonl")
3656
+ state = replay(events)
3657
+ plan = materialize_plan_from_events(events, load_plan(job_dir))
3658
+ write_json(job_dir / "plan.json", plan)
3659
+ write_state_snapshot(job_dir / "job.json", state)
3660
+ write_handoff(job_dir, state_to_json(state), plan)
3661
+ print(f"Blocked task: {task_id}")
3662
+ print("Reason: static_checker_unavailable")
3663
+ print("; ".join(infrastructure_errors))
3664
+ return 2
3665
+ if not review_ok:
3666
+ log.append(
3667
+ "task_failed",
3668
+ attempt_failure_payload(AttemptFailure.GATE_FAILED, **{
3669
+ "task_id": task_id,
3670
+ "error": "review_failed",
3671
+ "review_errors": review_errors,
3672
+ "attempt": attempt,
3673
+ "task_wall_ms": elapsed_ms(task_wall_start),
3674
+ "creator_call_ms": creator_call_ms,
3675
+ }),
3676
+ )
3677
+ events = read_events(job_dir / "events.jsonl")
3678
+ state = replay(events)
3679
+ plan = materialize_plan_from_events(events, load_plan(job_dir))
3680
+ write_json(job_dir / "plan.json", plan)
3681
+ write_state_snapshot(job_dir / "job.json", state)
3682
+ write_handoff(job_dir, state_to_json(state), plan)
3683
+ print(f"Failed task: {task_id}")
3684
+ print("Reason: review_failed")
3685
+ print("; ".join(review_errors))
3686
+ return 2
3687
+
3688
+ checkpoint_dir = job_dir / "checkpoints" / task_id
3689
+ checkpoint_dir.mkdir(parents=True, exist_ok=True)
3690
+ checkpoint_manifest = {
3691
+ "task_id": task_id,
3692
+ "mode": "model_creator_json_contract_sandbox_only",
3693
+ "output_mode": output_mode,
3694
+ "review_level": review_level,
3695
+ "sandbox_dir": str(sandbox_dir.relative_to(job_dir)),
3696
+ "declared_files": sorted(allowed),
3697
+ "artifacts": [{key: value for key, value in row.items() if key != "absolute_path"} for row in written],
3698
+ "creator_response": str(response_path.relative_to(job_dir)),
3699
+ "creator_output": str(parsed_path.relative_to(job_dir)),
3700
+ "parser_repair_applied": bool(parser_repair),
3701
+ "parser_repair": parser_repair,
3702
+ }
3703
+ write_json(checkpoint_dir / "manifest.json", checkpoint_manifest)
3704
+ log.append("checkpoint_written", {"task_id": task_id, "path": str((checkpoint_dir / "manifest.json").relative_to(job_dir)), "sha256": sha256_file(checkpoint_dir / "manifest.json")})
3705
+ log.append(
3706
+ "task_passed",
3707
+ {
3708
+ "task_id": task_id,
3709
+ "acceptance": task.get("acceptance", []),
3710
+ "notes": "Creator output parsed or safely repaired, stayed within declared files, passed reviewer checks, and was written atomically to the job sandbox. No project source promotion was performed.",
3711
+ "output_mode": output_mode,
3712
+ "review_level": review_level,
3713
+ "sandbox_dir": str(sandbox_dir.relative_to(job_dir)),
3714
+ "task_wall_ms": elapsed_ms(task_wall_start),
3715
+ "creator_call_ms": creator_call_ms,
3716
+ "prompt_layout": CREATOR_PROMPT_LAYOUT,
3717
+ "parser_repair_applied": bool(parser_repair),
3718
+ "parser_repair": parser_repair,
3719
+ },
3720
+ )
3721
+
3722
+ events = read_events(job_dir / "events.jsonl")
3723
+ state = replay(events)
3724
+ plan = materialize_plan_from_events(events, load_plan(job_dir))
3725
+ write_json(job_dir / "plan.json", plan)
3726
+ write_state_snapshot(job_dir / "job.json", state)
3727
+ write_handoff(job_dir, state_to_json(state), plan)
3728
+
3729
+ print(f"Ran task: {task_id}")
3730
+ print("Execution mode: model_creator_json_contract_sandbox_only")
3731
+ print(f"Endpoint health: {'ok' if health.get('ok') else 'unavailable'}")
3732
+ print(f"Sandbox artifacts: {sandbox_dir}")
3733
+ print(f"Files written: {len(written)}")
3734
+ print(f"Ready tasks now: {', '.join(ready_task_ids(plan)[:8]) or 'none'}")
3735
+ return 0
3736
+
3737
+ log.append(
3738
+ "task_started",
3739
+ {
3740
+ "task_id": task_id,
3741
+ "attempt": int(task.get("attempt_count") or 0) + 1,
3742
+ "creator_config": creator,
3743
+ "execution_mode": "deterministic_terminal_inventory_no_model_call",
3744
+ },
3745
+ )
3746
+ artifact_dir = job_dir / "artifacts" / "tasks" / task_id
3747
+ artifacts = inventory_declared_files(project, task, artifact_dir)
3748
+ log.append("task_artifacts", {"task_id": task_id, "artifacts": artifacts})
3749
+ checkpoint_dir = job_dir / "checkpoints" / task_id
3750
+ checkpoint_dir.mkdir(parents=True, exist_ok=True)
3751
+ checkpoint_manifest = {
3752
+ "task_id": task_id,
3753
+ "declared_files": [
3754
+ {
3755
+ "path": rel,
3756
+ "sha256": sha256_file(project / rel) if (project / rel).exists() and (project / rel).is_file() else None,
3757
+ }
3758
+ for rel in task.get("declared_files", [])
3759
+ ],
3760
+ "artifacts": artifacts,
3761
+ }
3762
+ write_json(checkpoint_dir / "manifest.json", checkpoint_manifest)
3763
+ log.append("checkpoint_written", {"task_id": task_id, "path": str((checkpoint_dir / "manifest.json").relative_to(job_dir)), "sha256": sha256_file(checkpoint_dir / "manifest.json")})
3764
+ log.append(
3765
+ "task_passed",
3766
+ {
3767
+ "task_id": task_id,
3768
+ "acceptance": task.get("acceptance", []),
3769
+ "notes": "Inventory completed from declared files. No project source edits were made.",
3770
+ },
3771
+ )
3772
+
3773
+ events = read_events(job_dir / "events.jsonl")
3774
+ state = replay(events)
3775
+ plan = materialize_plan_from_events(events, load_plan(job_dir))
3776
+ write_json(job_dir / "plan.json", plan)
3777
+ write_state_snapshot(job_dir / "job.json", state)
3778
+ write_handoff(job_dir, state_to_json(state), plan)
3779
+
3780
+ print(f"Ran task: {task_id}")
3781
+ print(f"Execution mode: deterministic_terminal_inventory_no_model_call")
3782
+ print(f"Endpoint health: {'ok' if health.get('ok') else 'unavailable'}")
3783
+ print(f"Artifacts: {artifact_dir}")
3784
+ print(f"Ready tasks now: {', '.join(ready_task_ids(plan)[:8]) or 'none'}")
3785
+ return 0