@arbiterforge/ca-pi 0.15.1 → 0.15.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 (60) hide show
  1. package/README.md +2 -2
  2. package/package.json +1 -1
  3. package/plugins/ca-pi/CHANGELOG.md +71 -0
  4. package/plugins/ca-pi/COMMANDS.md +3 -9
  5. package/plugins/ca-pi/SKILLS.md +16 -22
  6. package/plugins/ca-pi/agents/coverage-auditor.md +16 -2
  7. package/plugins/ca-pi/generated/command-catalog.json +13 -22
  8. package/plugins/ca-pi/helpers/artifacts/ca-artifact-darwin-amd64 +0 -0
  9. package/plugins/ca-pi/helpers/artifacts/ca-artifact-darwin-arm64 +0 -0
  10. package/plugins/ca-pi/helpers/artifacts/ca-artifact-linux-amd64 +0 -0
  11. package/plugins/ca-pi/helpers/artifacts/ca-artifact-linux-arm64 +0 -0
  12. package/plugins/ca-pi/helpers/artifacts/ca-artifact-windows-amd64.exe +0 -0
  13. package/plugins/ca-pi/helpers/artifacts/ca-artifact-windows-arm64.exe +0 -0
  14. package/plugins/ca-pi/helpers/artifacts/release.json +6 -6
  15. package/plugins/ca-pi/hooks/_artifactauthoritylib.py +618 -58
  16. package/plugins/ca-pi/hooks/_artifactlib.py +3 -1
  17. package/plugins/ca-pi/hooks/_host.py +1 -1
  18. package/plugins/ca-pi/hooks/artifact-authority-hook.py +4 -21
  19. package/plugins/ca-pi/hooks/artifact-authority.py +11 -0
  20. package/plugins/ca-pi/hooks/hostapi.py +1 -1
  21. package/plugins/ca-pi/includes/artifacts.md +20 -1
  22. package/plugins/ca-pi/includes/command-compatibility.md +31 -0
  23. package/plugins/ca-pi/includes/farm.md +187 -36
  24. package/plugins/ca-pi/includes/maturity-coverage.md +57 -17
  25. package/plugins/ca-pi/includes/routing-table.md +10 -11
  26. package/plugins/ca-pi/includes/safety-core.md +2 -2
  27. package/plugins/ca-pi/package.json +1 -1
  28. package/plugins/ca-pi/routines/INDEX.md +12 -11
  29. package/plugins/ca-pi/routines/context-check/SKILL.md +17 -1
  30. package/plugins/ca-pi/routines/context-creation/SKILL.md +8 -1
  31. package/plugins/ca-pi/routines/debug/SKILL.md +18 -2
  32. package/plugins/ca-pi/routines/decision-lifecycle/SKILL.md +72 -118
  33. package/plugins/ca-pi/routines/decision-lifecycle/references/authoring.md +152 -0
  34. package/plugins/ca-pi/routines/decision-variance/SKILL.md +107 -133
  35. package/plugins/ca-pi/routines/decision-variance/references/analysis.md +136 -0
  36. package/plugins/ca-pi/routines/decompose/SKILL.md +8 -1
  37. package/plugins/ca-pi/routines/finishing-a-development-branch/SKILL.md +85 -7
  38. package/plugins/ca-pi/routines/post-merge-cleanup/SKILL.md +19 -2
  39. package/plugins/ca-pi/routines/refactor/SKILL.md +27 -8
  40. package/plugins/ca-pi/routines/release/SKILL.md +2 -1
  41. package/plugins/ca-pi/routines/security-architecture/SKILL.md +14 -3
  42. package/plugins/ca-pi/routines/tdd/SKILL.md +9 -5
  43. package/plugins/ca-pi/routines/tribunal/SKILL.md +28 -14
  44. package/plugins/ca-pi/skills/ca-adr/SKILL.md +103 -18
  45. package/plugins/ca-pi/skills/ca-adr-status/SKILL.md +6 -24
  46. package/plugins/ca-pi/skills/ca-cleanup/SKILL.md +241 -52
  47. package/plugins/ca-pi/skills/ca-context-check/SKILL.md +87 -24
  48. package/plugins/ca-pi/skills/ca-create-context/SKILL.md +159 -19
  49. package/plugins/ca-pi/skills/ca-debug/SKILL.md +100 -27
  50. package/plugins/ca-pi/skills/ca-decompose/SKILL.md +156 -17
  51. package/plugins/ca-pi/skills/ca-pr/SKILL.md +161 -26
  52. package/plugins/ca-pi/skills/ca-reconcile/SKILL.md +108 -30
  53. package/plugins/ca-pi/skills/ca-refactor/SKILL.md +96 -30
  54. package/plugins/ca-pi/skills/ca-release/SKILL.md +614 -39
  55. package/plugins/ca-pi/skills/ca-status/SKILL.md +1 -1
  56. package/plugins/ca-pi/skills/ca-threat-model/SKILL.md +56 -27
  57. package/plugins/ca-pi/skills/ca-tribunal/SKILL.md +105 -26
  58. package/plugins/ca-pi/routines/skill-author/SKILL.md +0 -129
  59. package/plugins/ca-pi/routines/skill-author/references/skill-template.md +0 -58
  60. package/plugins/ca-pi/skills/ca-new-skill/SKILL.md +0 -129
@@ -26,6 +26,8 @@ than one SubagentStop; only the first is a review result.
26
26
 
27
27
  from __future__ import annotations
28
28
 
29
+ from contextlib import contextmanager
30
+ import errno
29
31
  import hashlib
30
32
  import json
31
33
  import os
@@ -63,6 +65,9 @@ REQUEST_RE = re.compile(r"[0-9a-f]{64}")
63
65
  TOKEN_RE = re.compile(r"[A-Za-z0-9_-]{12,128}")
64
66
  HOST_ID_RE = re.compile(r"[A-Za-z0-9][A-Za-z0-9_.:-]{0,255}")
65
67
  MAX_STATE = 1 << 20
68
+ # Only terminal abandonment can read a legacy inline-context review this large.
69
+ MAX_RECOVERY_STATE = 2 << 20
70
+ REQUEST_LOCK_WAIT_SECONDS = 5.0
66
71
  MAX_OUTPUT = 8 << 20
67
72
  MAX_DECISION = 65536
68
73
  REGISTRY_PARENT = Path(tempfile.gettempdir())
@@ -71,6 +76,14 @@ CLAUDE_REVIEWER = "ca:authority-reviewer"
71
76
  CLAUDE_REVIEWER_MODELS = frozenset({"opus", "sonnet", "haiku"})
72
77
  CLAUDE_REVIEW_PROFILE = "claude-review/0.1.0"
73
78
  CODEX_REVIEW_PROFILE = "codex-review/0.1.0"
79
+ CODEX_NATIVE_V1 = "codex-native-v1/0.145.0"
80
+ CODEX_UUID_RE = re.compile(r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}")
81
+ CODEX_STEERING_TOOLS = frozenset({
82
+ "multi_agent_v1send_input", "multi_agent_v1resume_agent", "multi_agent_v1close_agent",
83
+ })
84
+ CODEX_REVIEW_TOOLS = frozenset({"spawn_agent", "collaborationspawn_agent"}) | CODEX_STEERING_TOOLS
85
+ CODEX_CHILD_FORMAT = "codearbiter.codex-native-child/0.1.0"
86
+ MAX_CHILD_MARKER = 4096
74
87
  # None means the real ~/.claude/agents (plus CLAUDE_CONFIG_DIR/agents); tests
75
88
  # point it at a fixture directory.
76
89
  CLAUDE_USER_AGENT_DIR: Path | None = None
@@ -253,27 +266,129 @@ def _spool_root(root: Path) -> Path:
253
266
  raise AuthorityError("AUTHORITY_BUSY", "resolved git-common authority spool is unavailable") from exc
254
267
 
255
268
 
269
+ @contextmanager
270
+ def _request_lock(spool: Path, relative: Path):
271
+ """Keep one OS-owned lock inode for all writers of this request."""
272
+ path = spool / f"{relative.name}.lock"
273
+ fd = None
274
+
275
+ def check_path() -> None:
276
+ info = path.lstat()
277
+ if (
278
+ stat.S_ISLNK(info.st_mode)
279
+ or getattr(info, "st_file_attributes", 0) & 0x400
280
+ or not stat.S_ISREG(info.st_mode)
281
+ ):
282
+ raise OSError("request lock path is unsafe")
283
+ if fd is not None:
284
+ opened = os.fstat(fd)
285
+ if (info.st_dev, info.st_ino) != (opened.st_dev, opened.st_ino):
286
+ raise OSError("request lock path changed")
287
+
288
+ try:
289
+ check_path()
290
+ except FileNotFoundError:
291
+ pass
292
+ flags = (
293
+ os.O_RDWR | os.O_CREAT | getattr(os, "O_BINARY", 0)
294
+ | getattr(os, "O_NOFOLLOW", 0) | getattr(os, "O_NONBLOCK", 0)
295
+ )
296
+ fd = os.open(path, flags, 0o600)
297
+ locked = False
298
+ try:
299
+ check_path()
300
+ deadline = time.monotonic() + REQUEST_LOCK_WAIT_SECONDS
301
+ if os.name == "nt":
302
+ import msvcrt
303
+ else:
304
+ import fcntl
305
+ while True:
306
+ try:
307
+ if os.name == "nt":
308
+ # Windows permits a byte-range lock beyond EOF. Never seed
309
+ # or truncate the lock file, including on an unsafe path.
310
+ os.lseek(fd, 0, os.SEEK_SET)
311
+ msvcrt.locking(fd, msvcrt.LK_NBLCK, 1)
312
+ else:
313
+ fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
314
+ locked = True
315
+ break
316
+ except OSError as exc:
317
+ if (
318
+ exc.errno not in {errno.EACCES, errno.EAGAIN, errno.EDEADLK, 36}
319
+ and getattr(exc, "winerror", None) not in {32, 33}
320
+ ):
321
+ raise
322
+ if time.monotonic() >= deadline:
323
+ raise AuthorityError("AUTHORITY_BUSY", "request transition lock timed out") from exc
324
+ time.sleep(0.01)
325
+ check_path()
326
+ yield
327
+ finally:
328
+ try:
329
+ if locked:
330
+ if os.name == "nt":
331
+ os.lseek(fd, 0, os.SEEK_SET)
332
+ msvcrt.locking(fd, msvcrt.LK_UNLCK, 1)
333
+ else:
334
+ fcntl.flock(fd, fcntl.LOCK_UN)
335
+ finally:
336
+ os.close(fd)
337
+ # Do not unlink: a peer may already be waiting on this same inode.
338
+
339
+
256
340
  def _save(root: Path, value: dict[str, Any]) -> None:
257
- value = dict(value)
258
- value["integrity_sha256"] = _integrity(value)
341
+ expected = value.get("integrity_sha256")
342
+ updated = dict(value)
343
+ updated["integrity_sha256"] = _integrity(updated)
259
344
  try:
260
- _atomic_replace(_spool_root(root), _request_path(value["request_id"]), _canonical(value))
261
- if value["state"] in TERMINAL_STATES:
262
- (_registry_root() / f"{value['request_id']}.json").unlink(missing_ok=True)
345
+ spool = _spool_root(root)
346
+ relative = _request_path(value["request_id"])
347
+ with _request_lock(spool, relative):
348
+ try:
349
+ (spool / relative).lstat()
350
+ except FileNotFoundError:
351
+ if expected is not None:
352
+ raise AuthorityError("STALE_AUTHORITY_REQUEST", "loaded request state disappeared")
353
+ else:
354
+ if expected is None:
355
+ raise AuthorityError("STALE_AUTHORITY_REQUEST", "request state already exists")
356
+ legacy_abandonment = (
357
+ value["state"] == "ABANDONED"
358
+ and value["activity"] in REVIEW_ACTIVITIES
359
+ and value.get("recovery") == {
360
+ "disposition": "abandoned", "previous_attempt": None, "rerun_permitted": False,
361
+ }
362
+ )
363
+ current = _load(root, value["request_id"], recover_armed_review=legacy_abandonment)
364
+ if current["integrity_sha256"] != expected:
365
+ raise AuthorityError("STALE_AUTHORITY_REQUEST", "request state changed since it was loaded")
366
+ _atomic_replace(spool, relative, _canonical(updated))
367
+ # A live operation can save several times without loading again.
368
+ value["integrity_sha256"] = updated["integrity_sha256"]
369
+ if value["state"] in TERMINAL_STATES:
370
+ (_registry_root() / f"{value['request_id']}.json").unlink(missing_ok=True)
263
371
  except OSError as exc:
264
372
  raise AuthorityError("AUTHORITY_BUSY", "request state could not be written") from exc
265
373
 
266
374
 
267
- def _load(root: Path, request_id: str) -> dict[str, Any]:
375
+ def _load(
376
+ root: Path, request_id: str, *, recover_armed_review: bool = False
377
+ ) -> dict[str, Any]:
268
378
  path = _spool_root(root) / _request_path(request_id)
379
+ limit = MAX_RECOVERY_STATE if recover_armed_review else MAX_STATE
269
380
  try:
270
381
  info = path.lstat()
271
382
  reparse = getattr(info, "st_file_attributes", 0) & 0x400
272
383
  if stat.S_ISLNK(info.st_mode) or reparse or not stat.S_ISREG(info.st_mode):
273
384
  raise OSError("not regular")
274
- if info.st_size > MAX_STATE:
385
+ if info.st_size > limit:
386
+ raise OSError("oversized")
387
+ with path.open("rb") as stream:
388
+ raw = stream.read(limit + 1)
389
+ if len(raw) > limit:
275
390
  raise OSError("oversized")
276
- value = json.loads(path.read_text(encoding="utf-8"))
391
+ value = json.loads(raw.decode("utf-8"))
277
392
  except (OSError, UnicodeError, ValueError) as exc:
278
393
  raise AuthorityError("INVALID_AUTHORITY_STATE", "request state is unreadable") from exc
279
394
  allowed = {
@@ -282,7 +397,7 @@ def _load(root: Path, request_id: str) -> dict[str, Any]:
282
397
  "observation_sha256", "receipt", "integrity_sha256", "payload",
283
398
  "authority_source", "dispatch_prompt", "review_contract_sha256",
284
399
  "required_coverage", "wrapper", "command_bindings", "recovery",
285
- "launch_envelope", "workspace_roots", "host",
400
+ "launch_envelope", "workspace_roots", "host", "codex_review_profile",
286
401
  }
287
402
  if (
288
403
  not isinstance(value, dict)
@@ -303,6 +418,17 @@ def _load(root: Path, request_id: str) -> dict[str, Any]:
303
418
  or SHA256_RE.fullmatch(value.get("context_sha256", "")) is None
304
419
  ):
305
420
  raise AuthorityError("INVALID_AUTHORITY_STATE", "request state failed validation")
421
+ if "codex_review_profile" in value and (
422
+ value["codex_review_profile"] != CODEX_NATIVE_V1
423
+ or value.get("host", "codex") != "codex"
424
+ or value["activity"] not in REVIEW_ACTIVITIES
425
+ ):
426
+ raise AuthorityError("INVALID_AUTHORITY_STATE", "Codex review profile is unsupported")
427
+ if len(raw) > MAX_STATE:
428
+ try:
429
+ _validate_legacy_review_recovery(root, value)
430
+ except (AuthorityError, KeyError, TypeError, ValueError) as exc:
431
+ raise AuthorityError("INVALID_AUTHORITY_STATE", "legacy review recovery binding failed") from exc
306
432
  return value
307
433
 
308
434
 
@@ -312,6 +438,27 @@ def _validate_hash(value: Any, field: str) -> str:
312
438
  return value
313
439
 
314
440
 
441
+ def _read_context(root: Path, context_ref: str, context_hash: str) -> dict[str, Any]:
442
+ _validate_hash(context_hash, "context_sha256")
443
+ expected_ref = f".codearbiter/.artifacts/evidence-contexts/{context_hash}.json"
444
+ if context_ref != expected_ref:
445
+ raise AuthorityError("INVALID_EVIDENCE_CONTEXT", "context locator is not content addressed")
446
+ try:
447
+ raw = (root / expected_ref).read_bytes()
448
+ context = json.loads(raw.decode("utf-8"))
449
+ except (OSError, UnicodeError, ValueError) as exc:
450
+ raise AuthorityError("INVALID_EVIDENCE_CONTEXT", "context bytes are unavailable") from exc
451
+ if not isinstance(context, dict) or _digest(raw) != context_hash or raw != _canonical(context):
452
+ raise AuthorityError("INVALID_EVIDENCE_CONTEXT", "context bytes changed or are not canonical")
453
+ return context
454
+
455
+
456
+ def _require_frozen_context(root: Path, request: dict[str, Any]) -> None:
457
+ context = _read_context(root, request["context_ref"], request["context_sha256"])
458
+ if context != request["context"]:
459
+ raise AuthorityError("INVALID_EVIDENCE_CONTEXT", "frozen context differs from the request")
460
+
461
+
315
462
  def _context(
316
463
  root: Path, client: Any, artifact_id: str, record_id: str, activity: str
317
464
  ) -> tuple[dict[str, Any], str, str]:
@@ -322,16 +469,14 @@ def _context(
322
469
  raise AuthorityError("INVALID_EVIDENCE_CONTEXT", "engine context result is malformed")
323
470
  context_hash = _validate_hash(result.get("context_sha256"), "context_sha256")
324
471
  context_ref = result.get("context_ref")
325
- expected_ref = f".codearbiter/.artifacts/evidence-contexts/{context_hash}.json"
326
- if context_ref != expected_ref:
327
- raise AuthorityError("INVALID_EVIDENCE_CONTEXT", "context locator is not content addressed")
328
- try:
329
- raw = (root / expected_ref).read_bytes()
330
- context = json.loads(raw.decode("utf-8"))
331
- except (OSError, UnicodeError, ValueError) as exc:
332
- raise AuthorityError("INVALID_EVIDENCE_CONTEXT", "context bytes are unavailable") from exc
333
- if _digest(raw) != context_hash or raw != _canonical(context):
334
- raise AuthorityError("INVALID_EVIDENCE_CONTEXT", "context bytes changed or are not canonical")
472
+ context = _read_context(root, context_ref, context_hash)
473
+ _validate_context(context, artifact_id, record_id, activity)
474
+ return context, context_ref, context_hash
475
+
476
+
477
+ def _validate_context(
478
+ context: dict[str, Any], artifact_id: str, record_id: str, activity: str
479
+ ) -> None:
335
480
  subject = context.get("subject") if isinstance(context, dict) else None
336
481
  if (
337
482
  not isinstance(context, dict)
@@ -339,7 +484,9 @@ def _context(
339
484
  or not isinstance(subject, dict)
340
485
  or subject.get("artifact_id") != artifact_id
341
486
  or subject.get("record_id") != record_id
342
- or context["activity"] != activity
487
+ or context.get("activity") != activity
488
+ or not isinstance(artifact_id, str)
489
+ or not isinstance(record_id, str)
343
490
  or not ID_RE.fullmatch(artifact_id)
344
491
  or not ID_RE.fullmatch(record_id)
345
492
  ):
@@ -380,7 +527,6 @@ def _context(
380
527
  _validate_hash(context.get("base_input_sha256"), "base_input_sha256")
381
528
  if not isinstance(context.get("task_hashes"), dict) or not isinstance(context.get("tasks"), list):
382
529
  raise AuthorityError("INVALID_EVIDENCE_CONTEXT", "quality-review scope is incomplete")
383
- return context, context_ref, context_hash
384
530
 
385
531
 
386
532
  def _review_binding(context: dict[str, Any]) -> tuple[str, list[str]]:
@@ -403,6 +549,69 @@ def _review_binding(context: dict[str, Any]) -> tuple[str, list[str]]:
403
549
  return _digest(_canonical(contract)), coverage
404
550
 
405
551
 
552
+ def _never_launched_review(request: dict[str, Any]) -> bool:
553
+ return (
554
+ request["state"] == "ARMED"
555
+ and request["activity"] in REVIEW_ACTIVITIES
556
+ and all(field in request and request[field] is None for field in (
557
+ "attempt", "launch", "observation_ref", "observation_sha256", "receipt", "wrapper", "recovery",
558
+ ))
559
+ and "payload" not in request
560
+ and "authority_source" not in request
561
+ )
562
+
563
+
564
+ def _validate_legacy_review_recovery(root: Path, request: dict[str, Any]) -> None:
565
+ """Admit only the old duplicate-context shape, solely to retain and abandon it."""
566
+ if not _never_launched_review(request) or request["repository"] != _repository_identity(root):
567
+ raise AuthorityError("INVALID_AUTHORITY_STATE", "legacy request is not an unlaunched review")
568
+ context = request["context"]
569
+ subject = context["subject"]
570
+ _validate_context(context, subject["artifact_id"], subject["record_id"], request["activity"])
571
+ context_hash = _digest(_canonical(context))
572
+ records = [context["task"]] if request["activity"] == "spec_review" else context["tasks"]
573
+ if any(not isinstance(record, dict) for record in records):
574
+ raise AuthorityError("INVALID_AUTHORITY_STATE", "legacy review records are malformed")
575
+ contract_hash, coverage = _review_binding(context)
576
+ if (
577
+ request["context_sha256"] != context_hash
578
+ or request["context_ref"] != f".codearbiter/.artifacts/evidence-contexts/{context_hash}.json"
579
+ or request.get("review_contract_sha256") != contract_hash
580
+ or request.get("required_coverage") != coverage
581
+ or request.get("command_bindings") != []
582
+ or request.get("workspace_roots") != {}
583
+ ):
584
+ raise AuthorityError("INVALID_AUTHORITY_STATE", "legacy review context binding differs")
585
+ # Recovery validates the retained snapshot; it neither recreates absent
586
+ # evidence files nor upgrades the legacy prompt into launchable authority.
587
+ prompt = _dispatch_prompt(request, legacy_inline=True)
588
+ # Qualified Codex 0.13.11 predates both the host field and JSON-only suffix.
589
+ # Admit that one exact earlier form, without normalizing retained text.
590
+ original_prompt = request.get("dispatch_prompt")
591
+ if (
592
+ "host" not in request
593
+ and isinstance(original_prompt, str)
594
+ and original_prompt + " Reply with the JSON object only: no code fence and no other text." == prompt
595
+ ):
596
+ prompt = original_prompt
597
+ if not isinstance(request.get("launch_envelope"), dict):
598
+ raise AuthorityError("INVALID_AUTHORITY_STATE", "legacy launch envelope is malformed")
599
+ if request.get("host", "codex") == "claude":
600
+ model = request["launch_envelope"].get("model")
601
+ if model not in CLAUDE_REVIEWER_MODELS:
602
+ raise AuthorityError("INVALID_AUTHORITY_STATE", "legacy reviewer model is unsupported")
603
+ envelope = {
604
+ "description": f"codeArbiter {request['activity']} {request['request_id'][:12]}",
605
+ "prompt": prompt, "subagent_type": CLAUDE_REVIEWER, "model": model,
606
+ }
607
+ else:
608
+ envelope = {
609
+ "message": prompt, "task_name": f"authority_{request['request_id'][:12]}", "fork_turns": "none",
610
+ }
611
+ if request.get("dispatch_prompt") != prompt or request.get("launch_envelope") != envelope:
612
+ raise AuthorityError("INVALID_AUTHORITY_STATE", "legacy launch envelope differs")
613
+
614
+
406
615
  def _repository_identity(root: Path) -> dict[str, str]:
407
616
  info = os.stat(root)
408
617
  return {
@@ -895,15 +1104,34 @@ def _workspace_snapshots(bindings: list[dict[str, Any]]) -> list[dict[str, str]]
895
1104
  return [unique[key] for key in sorted(unique)]
896
1105
 
897
1106
 
898
- def _dispatch_prompt(request: dict[str, Any]) -> str:
1107
+ def _dispatch_prompt(request: dict[str, Any], *, legacy_inline: bool = False) -> str:
899
1108
  context = request["context"]
1109
+ if legacy_inline:
1110
+ # Used only to validate an oversized historical request for abandonment.
1111
+ target = "Frozen target data follows as canonical JSON:\n" + _canonical(context).decode("ascii") + "\n"
1112
+ else:
1113
+ target = (
1114
+ f"Review activity: {request['activity']}; subject: "
1115
+ f"{context['subject']['artifact_id']}/{context['subject']['record_id']}\n"
1116
+ "Read the frozen context from that repository-relative path before reviewing. "
1117
+ "The adapter verifies its exact digest before launch and publication. Inspect its "
1118
+ "activity, subject, input_sha256, task definitions and criterion coverage against "
1119
+ "these bindings, then inspect the input manifest and referenced target files. "
1120
+ "Do not pass if the evidence is missing, unreadable or inconsistent. The locator "
1121
+ "is a reference to the complete immutable evidence, not replacement authority.\n"
1122
+ )
1123
+ profile = (
1124
+ f"Native review profile: {CODEX_NATIVE_V1}. This review was dispatched through "
1125
+ "multi_agent_v1.spawn_agent with default role and fork_context=false. Read-only conduct is cooperative; "
1126
+ "these settings do not provide an OS sandbox.\n"
1127
+ if request.get("codex_review_profile") == CODEX_NATIVE_V1 else ""
1128
+ )
900
1129
  return (
901
1130
  f"[CODEARBITER_AUTHORITY_REQUEST:{request['request_id']}]\n"
902
1131
  f"Target repository: {request['repository']['path']}\n"
903
1132
  f"Frozen context: {request['context_ref']} sha256={request['context_sha256']}\n"
904
- "Frozen target data follows as canonical JSON:\n"
905
- + _canonical(context).decode("ascii") + "\n"
906
- "Act as a fresh read-only independent codeArbiter reviewer. Do not edit files or "
1133
+ + target + profile
1134
+ + "Act as a fresh read-only independent codeArbiter reviewer. Do not edit files or "
907
1135
  "delegate. Review only the frozen target and return exactly one JSON object using "
908
1136
  f"format {DECISION_FORMAT}. Bind request_id={request['request_id']}, "
909
1137
  f"target_sha256={context['input_sha256']}, "
@@ -927,6 +1155,7 @@ def arm_request(
927
1155
  workspace_roots: dict[str, str | Path] | None = None,
928
1156
  host: str = "codex",
929
1157
  reviewer_model: str = "opus",
1158
+ codex_review_profile: str | None = None,
930
1159
  **unexpected: Any,
931
1160
  ) -> dict[str, Any]:
932
1161
  if unexpected:
@@ -940,6 +1169,10 @@ def arm_request(
940
1169
  raise AuthorityError("INVALID_AUTHORITY_REQUEST", "host is unsupported")
941
1170
  if host == "claude" and reviewer_model not in CLAUDE_REVIEWER_MODELS:
942
1171
  raise AuthorityError("INVALID_AUTHORITY_REQUEST", "reviewer model is unsupported")
1172
+ if codex_review_profile is not None and (
1173
+ codex_review_profile != "native-v1" or host != "codex" or activity not in REVIEW_ACTIVITIES
1174
+ ):
1175
+ raise AuthorityError("INVALID_AUTHORITY_REQUEST", "Codex review profile is unsupported")
943
1176
  root = _real_root(root)
944
1177
  if host == "claude" and activity in REVIEW_ACTIVITIES:
945
1178
  _refuse_shadowed_reviewer(root)
@@ -965,6 +1198,8 @@ def arm_request(
965
1198
  }
966
1199
  if host != "codex":
967
1200
  seed_value["host"] = host
1201
+ if codex_review_profile is not None:
1202
+ seed_value["codex_review_profile"] = CODEX_NATIVE_V1
968
1203
  seed = _canonical(seed_value)
969
1204
  request_id = _digest(seed)
970
1205
  request = {
@@ -988,6 +1223,8 @@ def arm_request(
988
1223
  }
989
1224
  if host != "codex":
990
1225
  request["host"] = host
1226
+ if codex_review_profile is not None:
1227
+ request["codex_review_profile"] = CODEX_NATIVE_V1
991
1228
  if activity in REVIEW_ACTIVITIES:
992
1229
  request["review_contract_sha256"], request["required_coverage"] = _review_binding(context)
993
1230
  request["dispatch_prompt"] = _dispatch_prompt(request)
@@ -998,6 +1235,10 @@ def arm_request(
998
1235
  "subagent_type": CLAUDE_REVIEWER,
999
1236
  "model": reviewer_model,
1000
1237
  }
1238
+ elif codex_review_profile is not None:
1239
+ request["launch_envelope"] = {
1240
+ "message": request["dispatch_prompt"], "fork_context": False,
1241
+ }
1001
1242
  else:
1002
1243
  request["launch_envelope"] = {
1003
1244
  "message": request["dispatch_prompt"],
@@ -1016,6 +1257,8 @@ def arm_request(
1016
1257
  result["review_contract_sha256"] = request["review_contract_sha256"]
1017
1258
  result["required_coverage"] = request["required_coverage"]
1018
1259
  result["launch_envelope"] = request["launch_envelope"]
1260
+ if codex_review_profile is not None:
1261
+ result["codex_review_profile"] = CODEX_NATIVE_V1
1019
1262
  return result
1020
1263
 
1021
1264
 
@@ -1842,8 +2085,8 @@ def _parse_decision(raw: Any, request: dict[str, Any]) -> dict[str, Any]:
1842
2085
  if not isinstance(raw, str) or len(raw.encode("utf-8")) > MAX_DECISION:
1843
2086
  raise AuthorityError("INVALID_REVIEW_DECISION", "review decision is absent or oversized")
1844
2087
  try:
1845
- value = json.loads(raw)
1846
- except ValueError as exc:
2088
+ value = json.loads(raw, object_pairs_hook=_unique_json_pairs, parse_constant=_invalid_json_constant)
2089
+ except (ValueError, RecursionError) as exc:
1847
2090
  raise AuthorityError("INVALID_REVIEW_DECISION", "review decision is not JSON") from exc
1848
2091
  expected = {
1849
2092
  "format", "request_id", "target_sha256", "contract_sha256", "decision",
@@ -1861,6 +2104,7 @@ def _parse_decision(raw: Any, request: dict[str, Any]) -> dict[str, Any]:
1861
2104
  or not isinstance(value.get("assessment"), str)
1862
2105
  or not value["assessment"].strip()
1863
2106
  or not isinstance(value.get("coverage"), list)
2107
+ or any(not isinstance(item, str) or not item for item in value["coverage"])
1864
2108
  or len(set(value["coverage"])) != len(value["coverage"])
1865
2109
  or not isinstance(value.get("findings"), list)
1866
2110
  ):
@@ -1882,13 +2126,329 @@ def _parse_decision(raw: Any, request: dict[str, Any]) -> dict[str, Any]:
1882
2126
  return value
1883
2127
 
1884
2128
 
2129
+ def _codex_uuid(value: Any) -> str:
2130
+ if not isinstance(value, str) or CODEX_UUID_RE.fullmatch(value) is None:
2131
+ raise AuthorityError("UNSUPPORTED_HOST_SEAM", "native Codex child identity is not a UUID")
2132
+ return value
2133
+
2134
+
2135
+ def _codex_native_marker(agent_id: str) -> Path:
2136
+ # UUID, not arrival order or session, is the direct host-observed join key.
2137
+ # Retaining this marker also prevents reuse across requests and sessions.
2138
+ key = _digest(_canonical({"profile": CODEX_NATIVE_V1, "agent_id": _codex_uuid(agent_id)}))
2139
+ return _registry_root() / f"codex-native-{key}.marker"
2140
+
2141
+
2142
+ def _codex_claimed(agent_id: str, kind: str) -> bool:
2143
+ path = _codex_native_marker(agent_id).with_suffix("." + kind)
2144
+ try:
2145
+ info = path.lstat()
2146
+ except FileNotFoundError:
2147
+ return False
2148
+ if (stat.S_ISLNK(info.st_mode) or getattr(info, "st_file_attributes", 0) & 0x400
2149
+ or not stat.S_ISREG(info.st_mode) or info.st_size != 0):
2150
+ raise AuthorityError("UNSUPPORTED_HOST_SEAM", "native child event claim is unsafe")
2151
+ return True
2152
+
2153
+
2154
+ def _claim_codex_event(agent_id: str, kind: str) -> bool:
2155
+ """Atomic, single-use refusal evidence independent of the child mutex.
2156
+
2157
+ Zero-byte claims grant no authority. They retain the first lifecycle attempt
2158
+ even when its marker lock times out or its process dies before joining.
2159
+ """
2160
+ path = _codex_native_marker(agent_id).with_suffix("." + kind)
2161
+ try:
2162
+ fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, "O_NOFOLLOW", 0), 0o600)
2163
+ except FileExistsError:
2164
+ _codex_claimed(agent_id, kind)
2165
+ return False
2166
+ except OSError as exc:
2167
+ raise AuthorityError("AUTHORITY_BUSY", "native child first event could not be retained") from exc
2168
+ try:
2169
+ os.fsync(fd)
2170
+ finally:
2171
+ os.close(fd)
2172
+ return True
2173
+
2174
+
2175
+ def _read_codex_child(path: Path, agent_id: str, session_id: str) -> dict[str, Any]:
2176
+ try:
2177
+ info = path.lstat()
2178
+ except FileNotFoundError:
2179
+ return {
2180
+ "format": CODEX_CHILD_FORMAT, "agent_id": agent_id, "session_id": session_id,
2181
+ "request_id": None, "start": None, "stop_seen": False, "rejected": False,
2182
+ }
2183
+ if (stat.S_ISLNK(info.st_mode) or getattr(info, "st_file_attributes", 0) & 0x400
2184
+ or not stat.S_ISREG(info.st_mode) or info.st_size > MAX_CHILD_MARKER):
2185
+ raise AuthorityError("UNSUPPORTED_HOST_SEAM", "native Codex child marker is unsafe")
2186
+ flags = os.O_RDONLY | getattr(os, "O_BINARY", 0) | getattr(os, "O_NOFOLLOW", 0)
2187
+ with os.fdopen(os.open(path, flags), "rb") as stream:
2188
+ opened = os.fstat(stream.fileno())
2189
+ if (opened.st_dev, opened.st_ino) != (info.st_dev, info.st_ino):
2190
+ raise AuthorityError("UNSUPPORTED_HOST_SEAM", "native Codex child marker changed")
2191
+ raw = stream.read(MAX_CHILD_MARKER + 1)
2192
+ try:
2193
+ value = json.loads(raw, object_pairs_hook=_unique_json_pairs, parse_constant=_invalid_json_constant)
2194
+ except (ValueError, UnicodeError) as exc:
2195
+ raise AuthorityError("UNSUPPORTED_HOST_SEAM", "native Codex child marker is unreadable") from exc
2196
+ start = value.get("start") if isinstance(value, dict) else None
2197
+ if (
2198
+ len(raw) > MAX_CHILD_MARKER or not isinstance(value, dict)
2199
+ or set(value) != {"format", "agent_id", "session_id", "request_id", "start", "stop_seen",
2200
+ "rejected", "integrity_sha256"}
2201
+ or value.get("format") != CODEX_CHILD_FORMAT or value.get("agent_id") != agent_id
2202
+ or not isinstance(value.get("session_id"), str) or not HOST_ID_RE.fullmatch(value["session_id"])
2203
+ or (value.get("request_id") is not None and (
2204
+ not isinstance(value["request_id"], str) or not REQUEST_RE.fullmatch(value["request_id"])))
2205
+ or type(value.get("stop_seen")) is not bool or type(value.get("rejected")) is not bool
2206
+ or value.get("integrity_sha256") != _integrity(value)
2207
+ or (start is not None and (
2208
+ not isinstance(start, dict) or set(start) != {"turn_id", "agent_type"}
2209
+ or start.get("agent_type") != "default" or not isinstance(start.get("turn_id"), str)
2210
+ or not CODEX_UUID_RE.fullmatch(start["turn_id"])))
2211
+ ):
2212
+ raise AuthorityError("UNSUPPORTED_HOST_SEAM", "native Codex child marker failed validation")
2213
+ return value
2214
+
2215
+
2216
+ def _write_codex_child(path: Path, child: dict[str, Any]) -> None:
2217
+ child["integrity_sha256"] = _integrity(child)
2218
+ raw = _canonical(child)
2219
+ if len(raw) > MAX_CHILD_MARKER:
2220
+ raise AuthorityError("UNSUPPORTED_HOST_SEAM", "native Codex child marker is oversized")
2221
+ _atomic_replace(path.parent, Path(path.name), raw)
2222
+
2223
+
2224
+ @contextmanager
2225
+ def _codex_child_lock(agent_id: str, session_id: str):
2226
+ path = _codex_native_marker(agent_id)
2227
+ try:
2228
+ with _request_lock(path.parent, Path(path.name)):
2229
+ yield path, _read_codex_child(path, agent_id, session_id)
2230
+ except OSError as exc:
2231
+ raise AuthorityError("AUTHORITY_BUSY", "native Codex child transition is unavailable") from exc
2232
+
2233
+
2234
+ def _codex_native_refuse(root: Path, request: dict[str, Any], reason: str) -> None:
2235
+ if request["state"] in {"LAUNCHING", "RUNNING"}:
2236
+ _reject(root, request, reason)
2237
+ raise AuthorityError("UNSUPPORTED_HOST_SEAM", reason)
2238
+
2239
+
2240
+ def _codex_native_post(event: dict[str, Any], root: Path, request: dict[str, Any]) -> dict[str, Any]:
2241
+ try:
2242
+ return _codex_native_post_join(event, root, request)
2243
+ except (AuthorityError, ValueError, TypeError, RecursionError) as exc:
2244
+ # S/P/C has already identified this exact call. Its first failed Post
2245
+ # cannot be discarded in favor of a later successful replacement.
2246
+ current = _load(root, request["request_id"])
2247
+ if current["state"] in {"LAUNCHING", "RUNNING"}:
2248
+ _reject(root, current, "invalid-first-native-review-post")
2249
+ if isinstance(exc, AuthorityError):
2250
+ raise
2251
+ raise AuthorityError("UNSUPPORTED_HOST_SEAM", "native-v1 spawn result is malformed") from exc
2252
+
2253
+
2254
+ def _codex_native_post_join(event: dict[str, Any], root: Path, request: dict[str, Any]) -> dict[str, Any]:
2255
+ if _canonical(event.get("tool_input")) != _canonical(request["launch_envelope"]):
2256
+ raise AuthorityError("UNSUPPORTED_HOST_SEAM", "native review PostToolUse envelope was changed")
2257
+ _require_frozen_context(root, request)
2258
+ raw = event.get("tool_response")
2259
+ if not isinstance(raw, str) or len(raw.encode("utf-8")) > MAX_DECISION:
2260
+ raise AuthorityError("UNSUPPORTED_HOST_SEAM", "native-v1 spawn result must be a bounded JSON string")
2261
+ try:
2262
+ response = json.loads(raw, object_pairs_hook=_unique_json_pairs, parse_constant=_invalid_json_constant)
2263
+ except ValueError as exc:
2264
+ raise AuthorityError("UNSUPPORTED_HOST_SEAM", "native-v1 spawn result is not strict JSON") from exc
2265
+ if (not isinstance(response, dict) or set(response) != {"agent_id", "nickname"}
2266
+ or (response["nickname"] is not None and (
2267
+ not isinstance(response["nickname"], str) or len(response["nickname"]) > 256))):
2268
+ raise AuthorityError("UNSUPPORTED_HOST_SEAM", "native-v1 spawn result fields are unsupported")
2269
+ agent_id = _codex_uuid(response.get("agent_id"))
2270
+ session_id = request["launch"]["parent_session_id"]
2271
+ with _codex_child_lock(agent_id, session_id) as (path, child):
2272
+ request = _load(root, request["request_id"])
2273
+ if request["state"] != "LAUNCHING" or request["launch"].get("post_confirmed"):
2274
+ _codex_native_refuse(root, request, "native reviewer launch was already associated")
2275
+ if child["request_id"] is not None or child["session_id"] != session_id:
2276
+ _codex_native_refuse(root, request, "native reviewer UUID was already used")
2277
+ child["request_id"] = request["request_id"]
2278
+ _write_codex_child(path, child)
2279
+ if (child["rejected"] or child["stop_seen"] or _codex_claimed(agent_id, "failed")
2280
+ or _codex_claimed(agent_id, "stop") or (child["start"] is not None
2281
+ and child["start"]["turn_id"] == request["launch"]["parent_turn_id"])):
2282
+ _codex_native_refuse(root, request, "native reviewer stopped or conflicted before association")
2283
+ launch = request["launch"]
2284
+ launch.update(agent_id=agent_id, post_confirmed=True)
2285
+ if child["start"] is not None:
2286
+ launch.update(child_turn_id=child["start"]["turn_id"], agent_type="default")
2287
+ request["state"] = "RUNNING"
2288
+ _save(root, request)
2289
+ return {"request_id": request["request_id"], "state": request["state"]}
2290
+
2291
+
2292
+ def _codex_review_complete(root: Path, request: dict[str, Any], agent_id: str, decision: dict[str, Any]) -> dict[str, Any]:
2293
+ payload = {
2294
+ "input_sha256": request["context"]["input_sha256"],
2295
+ "spec_sha256": request["context"]["spec_sha256"], "assessment": decision["assessment"],
2296
+ }
2297
+ if request["activity"] == "spec_review":
2298
+ payload["task_sha256"] = request["context"]["task_sha256"]
2299
+ else:
2300
+ payload["base_input_sha256"] = request["context"]["base_input_sha256"]
2301
+ payload["task_hashes"] = request["context"]["task_hashes"]
2302
+ observation = _closed_observation(
2303
+ request, payload, request.get("codex_review_profile", CODEX_REVIEW_PROFILE), agent_id,
2304
+ {"launch": request["launch"], "decision": decision},
2305
+ )
2306
+ _observation(root, request, observation)
2307
+ request["payload"] = payload
2308
+ request["state"] = "COMPLETED"
2309
+ _save(root, request)
2310
+ return {"request_id": request["request_id"], "state": "COMPLETED"}
2311
+
2312
+
2313
+ def _codex_native_lifecycle(event: dict[str, Any]) -> dict[str, Any] | None:
2314
+ agent_id = _codex_uuid(event.get("agent_id"))
2315
+ try:
2316
+ kind = "start" if event["hook_event_name"] == "SubagentStart" else "stop"
2317
+ if not _claim_codex_event(agent_id, kind):
2318
+ _claim_codex_event(agent_id, "failed")
2319
+ return _codex_native_lifecycle_join(event)
2320
+ except (AuthorityError, OSError) as exc:
2321
+ _claim_codex_event(agent_id, "failed")
2322
+ for root, request in _registered_requests():
2323
+ if (request.get("codex_review_profile") == CODEX_NATIVE_V1
2324
+ and request["state"] in {"LAUNCHING", "RUNNING"}
2325
+ and (request.get("launch") or {}).get("agent_id") == agent_id):
2326
+ _reject(root, request, "failed-native-review-lifecycle")
2327
+ if isinstance(exc, AuthorityError):
2328
+ raise
2329
+ raise AuthorityError("AUTHORITY_BUSY", "native child lifecycle could not be retained") from exc
2330
+
2331
+
2332
+ def _codex_native_lifecycle_join(event: dict[str, Any]) -> dict[str, Any] | None:
2333
+ agent_id = _codex_uuid(event.get("agent_id"))
2334
+ observed_session = event.get("session_id")
2335
+ session_valid = isinstance(observed_session, str) and HOST_ID_RE.fullmatch(observed_session) is not None
2336
+ # A malformed first event with a known child UUID still consumes that
2337
+ # child's evidence. Do not discard it and accept a later replacement Stop.
2338
+ session_id = observed_session if session_valid else "invalid-session"
2339
+ name = event["hook_event_name"]
2340
+ with _codex_child_lock(agent_id, session_id) as (path, child):
2341
+ start = child["start"]
2342
+ valid = (
2343
+ session_valid and child["session_id"] == session_id and not child["rejected"] and not child["stop_seen"]
2344
+ and not _codex_claimed(agent_id, "failed")
2345
+ and event.get("agent_type") == "default" and isinstance(event.get("turn_id"), str)
2346
+ and CODEX_UUID_RE.fullmatch(event["turn_id"]) is not None
2347
+ )
2348
+ if name == "SubagentStart":
2349
+ valid = valid and start is None
2350
+ if valid:
2351
+ child["start"] = {"turn_id": event["turn_id"], "agent_type": "default"}
2352
+ else:
2353
+ valid = (valid and start is not None and start["turn_id"] == event.get("turn_id")
2354
+ and event.get("stop_hook_active") is False and child["request_id"] is not None)
2355
+ # First stop is durable even before Post associates the UUID. A later
2356
+ # follow-up turn/result can never replace evidence we could not join.
2357
+ child["stop_seen"] = True
2358
+ child["rejected"] = child["rejected"] or not valid
2359
+ _write_codex_child(path, child)
2360
+ if child["request_id"] is None:
2361
+ return None
2362
+ root, request = _registered_request(child["request_id"])
2363
+ launch = request.get("launch") or {}
2364
+ if (not valid or request.get("codex_review_profile") != CODEX_NATIVE_V1
2365
+ or launch.get("parent_session_id") != child["session_id"]
2366
+ or launch.get("agent_id") != agent_id or launch.get("post_confirmed") is not True
2367
+ or event.get("turn_id") == launch.get("parent_turn_id")):
2368
+ _codex_native_refuse(root, request, "native reviewer lifecycle is conflicting or out of order")
2369
+ if name == "SubagentStart":
2370
+ if request["state"] != "LAUNCHING":
2371
+ _codex_native_refuse(root, request, "native reviewer start was repeated")
2372
+ launch.update(child_turn_id=event["turn_id"], agent_type="default")
2373
+ request["state"] = "RUNNING"
2374
+ _save(root, request)
2375
+ return {"request_id": request["request_id"], "state": "RUNNING"}
2376
+ if (request["state"] != "RUNNING" or launch.get("child_turn_id") != event["turn_id"]
2377
+ or launch.get("agent_type") != "default"):
2378
+ _codex_native_refuse(root, request, "native reviewer stop is out of order")
2379
+ try:
2380
+ _require_frozen_context(root, request)
2381
+ decision = _parse_decision(event.get("last_assistant_message"), request)
2382
+ except (AuthorityError, TypeError, ValueError, RecursionError) as exc:
2383
+ _reject(root, request, "invalid-first-native-review-stop")
2384
+ if isinstance(exc, AuthorityError):
2385
+ raise
2386
+ raise AuthorityError("INVALID_REVIEW_DECISION", "native first review decision is malformed") from exc
2387
+ launch["first_stop"] = True
2388
+ return _codex_review_complete(root, request, agent_id, decision)
2389
+
2390
+
2391
+ def _codex_steering_uuid(value: Any) -> str | None:
2392
+ # Pinned Codex uses uuid 1.20.0 parse_str: simple, hyphenated, braced,
2393
+ # and lowercase urn:uuid: prefix; hex digits may use either case.
2394
+ # This normalization is solely for denying steering, never lifecycle proof.
2395
+ if not isinstance(value, str):
2396
+ return None
2397
+ if len(value) == 38 and value.startswith("{") and value.endswith("}"):
2398
+ value = value[1:-1]
2399
+ elif len(value) == 45 and value.startswith("urn:uuid:"):
2400
+ value = value[9:]
2401
+ if len(value) == 32 and re.fullmatch(r"[0-9a-fA-F]{32}", value):
2402
+ value = "-".join((value[:8], value[8:12], value[12:16], value[16:20], value[20:]))
2403
+ return value.lower() if CODEX_UUID_RE.fullmatch(value.lower()) else None
2404
+
2405
+
2406
+ def _codex_native_steering(event: dict[str, Any]) -> None:
2407
+ tool_input = event.get("tool_input")
2408
+ field = "id" if event["tool_name"] == "multi_agent_v1resume_agent" else "target"
2409
+ if not isinstance(tool_input, dict):
2410
+ raise AuthorityError("UNSUPPORTED_HOST_SEAM", "native reviewer steering input is malformed")
2411
+ agent_id = _codex_steering_uuid(tool_input.get(field))
2412
+ if agent_id is None:
2413
+ return None
2414
+ _claim_codex_event(agent_id, "failed")
2415
+ with _codex_child_lock(agent_id, _host_id(event.get("session_id"), "session_id")) as (path, child):
2416
+ child["rejected"] = True
2417
+ _write_codex_child(path, child)
2418
+ if child["request_id"] is not None:
2419
+ root, request = _registered_request(child["request_id"])
2420
+ _codex_native_refuse(root, request, "native reviewer cannot be resumed, steered or closed")
2421
+
2422
+
1885
2423
  def observe_codex_hook(root: str | Path, event: dict[str, Any]) -> dict[str, Any]:
1886
2424
  _real_root(root) # Invocation repository is observed but cannot select authority.
1887
2425
  if not isinstance(event, dict):
1888
2426
  raise AuthorityError("UNSUPPORTED_HOST_SEAM", "hook event is malformed")
1889
2427
  name = event.get("hook_event_name")
2428
+ if (name in {"SubagentStart", "SubagentStop"} and isinstance(event.get("agent_id"), str)
2429
+ and CODEX_UUID_RE.fullmatch(event["agent_id"])):
2430
+ native = _codex_native_lifecycle(event)
2431
+ if native is not None:
2432
+ return native
1890
2433
  session_id = _host_id(event.get("session_id"), "session_id")
1891
2434
 
2435
+ if name in {"PreToolUse", "PostToolUse"} and event.get("tool_name") == "collaborationspawn_agent":
2436
+ tool_input = event.get("tool_input")
2437
+ message = tool_input.get("message") if isinstance(tool_input, dict) else None
2438
+ if isinstance(message, str) and message.startswith("[CODEARBITER_AUTHORITY_REQUEST:"):
2439
+ raise AuthorityError(
2440
+ "UNSUPPORTED_HOST_SEAM",
2441
+ "default Codex v2 has no direct child UUID binding; qualify the native-v1 interface and arm a fresh request",
2442
+ )
2443
+ if name == "PreToolUse":
2444
+ turn_id = _host_id(event.get("turn_id"), "turn_id")
2445
+ _record_ordinary_spawn(session_id, turn_id)
2446
+ _reject_overlapping_authority(session_id, turn_id)
2447
+ return None
2448
+
2449
+ if name in {"PreToolUse", "PostToolUse"} and event.get("tool_name") in CODEX_STEERING_TOOLS:
2450
+ return _codex_native_steering(event)
2451
+
1892
2452
  if name == "PreToolUse":
1893
2453
  if event.get("tool_name") != "spawn_agent":
1894
2454
  raise AuthorityError("UNSUPPORTED_HOST_SEAM", "only spawn_agent can launch a reviewer")
@@ -1910,8 +2470,9 @@ def observe_codex_hook(root: str | Path, event: dict[str, Any]) -> dict[str, Any
1910
2470
  or request["state"] != "ARMED"
1911
2471
  ):
1912
2472
  raise AuthorityError("UNSUPPORTED_HOST_SEAM", "review request is not launchable")
1913
- if tool_input != request.get("launch_envelope"):
2473
+ if _canonical(tool_input) != _canonical(request.get("launch_envelope")):
1914
2474
  raise AuthorityError("UNSUPPORTED_HOST_SEAM", "review launch envelope was changed")
2475
+ _require_frozen_context(root, request)
1915
2476
  if _ordinary_spawn_marker(session_id, turn_id).exists():
1916
2477
  request["state"] = "REJECTED"
1917
2478
  request["recovery"] = {
@@ -1927,9 +2488,14 @@ def observe_codex_hook(root: str | Path, event: dict[str, Any]) -> dict[str, Any
1927
2488
  "parent_session_id": session_id, "parent_turn_id": turn_id,
1928
2489
  "tool_use_id": tool_use_id, "post_confirmed": False,
1929
2490
  "agent_id": None, "agent_type": None,
1930
- "task_name": tool_input["task_name"],
1931
- "fork_turns": tool_input["fork_turns"],
1932
2491
  }
2492
+ if request.get("codex_review_profile") == CODEX_NATIVE_V1:
2493
+ request["launch"].update(
2494
+ codex_review_profile=CODEX_NATIVE_V1, child_turn_id=None,
2495
+ fork_context=False,
2496
+ )
2497
+ else:
2498
+ request["launch"].update(task_name=tool_input["task_name"], fork_turns=tool_input["fork_turns"])
1933
2499
  request["state"] = "LAUNCHING"
1934
2500
  _save(root, request)
1935
2501
  return {"request_id": request_id, "state": "LAUNCHING"}
@@ -1939,12 +2505,8 @@ def observe_codex_hook(root: str | Path, event: dict[str, Any]) -> dict[str, Any
1939
2505
  raise AuthorityError("UNSUPPORTED_HOST_SEAM", "post-tool event is not reviewer launch")
1940
2506
  tool_use_id = _host_id(event.get("tool_use_id"), "tool_use_id")
1941
2507
  turn_id = _host_id(event.get("turn_id"), "turn_id")
1942
- response = event.get("tool_response")
1943
- if not isinstance(response, dict):
1944
- raise AuthorityError("UNSUPPORTED_HOST_SEAM", "spawn result is malformed")
1945
- task_name = _host_id(response.get("task_name"), "task_name")
1946
2508
  matches = []
1947
- for candidate_root, candidate in _registered_requests():
2509
+ for candidate_root, candidate in _registered_requests(strict=True):
1948
2510
  launch = candidate.get("launch") or {}
1949
2511
  if (
1950
2512
  candidate["state"] in {"LAUNCHING", "RUNNING"}
@@ -1958,6 +2520,12 @@ def observe_codex_hook(root: str | Path, event: dict[str, Any]) -> dict[str, Any
1958
2520
  if len(matches) != 1:
1959
2521
  raise AuthorityError("UNSUPPORTED_HOST_SEAM", "review launch correlation is ambiguous")
1960
2522
  root, request = matches[0]
2523
+ if request.get("codex_review_profile") == CODEX_NATIVE_V1:
2524
+ return _codex_native_post(event, root, request)
2525
+ response = event.get("tool_response")
2526
+ if not isinstance(response, dict):
2527
+ raise AuthorityError("UNSUPPORTED_HOST_SEAM", "spawn result is malformed")
2528
+ task_name = _host_id(response.get("task_name"), "task_name")
1961
2529
  if task_name != request["launch"].get("task_name"):
1962
2530
  raise AuthorityError("UNSUPPORTED_HOST_SEAM", "spawn result task_name differs")
1963
2531
  request["launch"]["post_confirmed"] = True
@@ -1970,6 +2538,8 @@ def observe_codex_hook(root: str | Path, event: dict[str, Any]) -> dict[str, Any
1970
2538
  matches = []
1971
2539
  same_turn = []
1972
2540
  for candidate_root, candidate in _registered_requests():
2541
+ if candidate.get("codex_review_profile") == CODEX_NATIVE_V1:
2542
+ continue
1973
2543
  launch = candidate.get("launch") or {}
1974
2544
  if (
1975
2545
  launch.get("parent_session_id") == session_id
@@ -2015,26 +2585,7 @@ def observe_codex_hook(root: str | Path, event: dict[str, Any]) -> dict[str, Any
2015
2585
  request["state"] = "REJECTED"
2016
2586
  _save(root, request)
2017
2587
  raise
2018
- payload = {
2019
- "input_sha256": request["context"]["input_sha256"],
2020
- "spec_sha256": request["context"]["spec_sha256"],
2021
- "assessment": decision["assessment"],
2022
- }
2023
- if request["activity"] == "spec_review":
2024
- payload["task_sha256"] = request["context"]["task_sha256"]
2025
- else:
2026
- payload["base_input_sha256"] = request["context"]["base_input_sha256"]
2027
- payload["task_hashes"] = request["context"]["task_hashes"]
2028
- producer_result = {"launch": request["launch"], "decision": decision}
2029
- observation = _closed_observation(
2030
- request, payload, CODEX_REVIEW_PROFILE, agent_id,
2031
- producer_result,
2032
- )
2033
- _observation(root, request, observation)
2034
- request["payload"] = payload
2035
- request["state"] = "COMPLETED"
2036
- _save(root, request)
2037
- return {"request_id": request["request_id"], "state": "COMPLETED"}
2588
+ return _codex_review_complete(root, request, agent_id, decision)
2038
2589
 
2039
2590
  raise AuthorityError("UNSUPPORTED_HOST_SEAM", "hook event is not supported")
2040
2591
 
@@ -2116,6 +2667,7 @@ def observe_claude_hook(root: str | Path, event: dict[str, Any]) -> dict[str, An
2116
2667
  if tool_input != request.get("launch_envelope"):
2117
2668
  _reject(root, request, "changed-launch-envelope")
2118
2669
  raise AuthorityError("UNSUPPORTED_HOST_SEAM", "review launch envelope was changed")
2670
+ _require_frozen_context(root, request)
2119
2671
  try:
2120
2672
  _refuse_shadowed_reviewer(root, session_root)
2121
2673
  except AuthorityError:
@@ -2317,6 +2869,10 @@ def publish_request(
2317
2869
  wrapper = request.get("wrapper")
2318
2870
  if not isinstance(wrapper, dict) or wrapper.get("state") != "CORROBORATED":
2319
2871
  raise AuthorityError("AUTHORITY_NOT_COMPLETE", "verifier wrapper completion is uncorroborated")
2872
+ else:
2873
+ # Check the original reference before the engine can recreate missing
2874
+ # derived bytes while computing the current context.
2875
+ _require_frozen_context(root, request)
2320
2876
  current, current_ref, current_hash = _context(
2321
2877
  root, client, request["context"]["subject"]["artifact_id"],
2322
2878
  request["context"]["subject"]["record_id"],
@@ -2354,11 +2910,11 @@ def publish_request(
2354
2910
 
2355
2911
 
2356
2912
  def recover_request(root: str | Path, request_id: str, disposition: str) -> dict[str, Any]:
2357
- """Retain an uncertain attempt and close it without ever rerunning commands."""
2913
+ """Retain an uncertain attempt or abandon a review that never launched."""
2358
2914
  root = _real_root(root)
2359
2915
  if disposition not in {"failed", "abandoned"}:
2360
2916
  raise AuthorityError("INVALID_RECOVERY", "recovery disposition must be failed or abandoned")
2361
- request = _load(root, request_id)
2917
+ request = _load(root, request_id, recover_armed_review=disposition == "abandoned")
2362
2918
  recoverable_completed = (
2363
2919
  request["state"] == "COMPLETED"
2364
2920
  and request["activity"] == "verification"
@@ -2371,7 +2927,11 @@ def recover_request(root: str | Path, request_id: str, disposition: str) -> dict
2371
2927
  and isinstance(request.get("wrapper"), dict)
2372
2928
  and request["wrapper"].get("state") in {"AUTHORIZED", "FAILED"}
2373
2929
  )
2374
- if request["state"] not in {"RUNNING", "LAUNCHING"} and not recoverable_completed and not recoverable_authorized:
2930
+ abandonable_review = disposition == "abandoned" and _never_launched_review(request)
2931
+ if (
2932
+ request["state"] not in {"RUNNING", "LAUNCHING"}
2933
+ and not recoverable_completed and not recoverable_authorized and not abandonable_review
2934
+ ):
2375
2935
  raise AuthorityError("INVALID_RECOVERY", "only an interrupted active attempt can be recovered")
2376
2936
  request["state"] = disposition.upper()
2377
2937
  request["recovery"] = {