@arbiterforge/ca-pi 0.14.12 → 0.14.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -12
- package/package.json +2 -1
- package/plugins/ca-pi/CHANGELOG.md +46 -0
- package/plugins/ca-pi/LICENSE +661 -0
- package/plugins/ca-pi/THIRD_PARTY_NOTICES.md +40 -0
- package/plugins/ca-pi/helpers/artifacts/ca-artifact-darwin-amd64 +0 -0
- package/plugins/ca-pi/helpers/artifacts/ca-artifact-darwin-arm64 +0 -0
- package/plugins/ca-pi/helpers/artifacts/ca-artifact-linux-amd64 +0 -0
- package/plugins/ca-pi/helpers/artifacts/ca-artifact-linux-arm64 +0 -0
- package/plugins/ca-pi/helpers/artifacts/ca-artifact-windows-amd64.exe +0 -0
- package/plugins/ca-pi/helpers/artifacts/ca-artifact-windows-arm64.exe +0 -0
- package/plugins/ca-pi/helpers/artifacts/release.json +6 -6
- package/plugins/ca-pi/hooks/_artifactauthoritylib.py +486 -37
- package/plugins/ca-pi/hooks/_artifactlib.py +150 -3
- package/plugins/ca-pi/hooks/_host.py +1 -1
- package/plugins/ca-pi/hooks/_intentlib.py +6 -1
- package/plugins/ca-pi/hooks/_releaselib.py +345 -111
- package/plugins/ca-pi/hooks/artifact-authority-hook.py +57 -2
- package/plugins/ca-pi/hooks/artifact-authority.py +23 -1
- package/plugins/ca-pi/hooks/hostapi.py +1 -1
- package/plugins/ca-pi/hooks/releasehash.py +36 -23
- package/plugins/ca-pi/includes/artifacts.md +26 -6
- package/plugins/ca-pi/package.json +1 -1
- package/plugins/ca-pi/routines/release/SKILL.md +220 -70
- package/plugins/ca-pi/skills/ca-release/SKILL.md +6 -4
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# codeArbiter — durable closed authority producers for structured artifacts.
|
|
3
|
-
"""Supervise verification and correlate independent Codex review evidence.
|
|
3
|
+
"""Supervise verification and correlate independent Codex or Claude review evidence.
|
|
4
4
|
|
|
5
5
|
arm_request(...) -> dict
|
|
6
6
|
run_verification(...) -> dict
|
|
7
7
|
observe_codex_hook(...) -> dict
|
|
8
|
+
observe_claude_hook(...) -> dict
|
|
8
9
|
publish_request(...) -> dict
|
|
9
10
|
|
|
10
11
|
The artifact engine owns every target binding. Callers select only an activity
|
|
11
12
|
and record; they cannot supply an authority kind, verdict, evidence payload, or
|
|
12
13
|
reviewer identity. External work is journaled before launch, observations are
|
|
13
14
|
immutable and content-addressed, and replay never silently reruns a completed
|
|
14
|
-
attempt. Codex review correlation
|
|
15
|
-
|
|
15
|
+
attempt. Codex and Claude review correlation use stable hook fields and
|
|
16
|
+
deliberately ignore transcript bytes whose format is not a public contract.
|
|
17
|
+
|
|
18
|
+
Claude Code correlation (recorded Claude Code 2.1.281 payloads): a Bash or
|
|
19
|
+
Agent PreToolUse/PostToolUse pair shares session_id, prompt_id and tool_use_id;
|
|
20
|
+
the Agent PostToolUse tool_response carries the child's agentId; SubagentStart
|
|
21
|
+
and SubagentStop carry only agent_id and the parent's *current* prompt_id, so
|
|
22
|
+
child lifecycle events are bound by agent_id alone. One agent can emit more
|
|
23
|
+
than one SubagentStop; only the first is a review result.
|
|
16
24
|
"""
|
|
17
25
|
|
|
18
26
|
from __future__ import annotations
|
|
@@ -57,6 +65,23 @@ MAX_STATE = 1 << 20
|
|
|
57
65
|
MAX_OUTPUT = 8 << 20
|
|
58
66
|
MAX_DECISION = 65536
|
|
59
67
|
REGISTRY_PARENT = Path(tempfile.gettempdir())
|
|
68
|
+
HOSTS = frozenset({"codex", "claude"})
|
|
69
|
+
CLAUDE_REVIEWER = "ca:authority-reviewer"
|
|
70
|
+
CLAUDE_REVIEWER_MODELS = frozenset({"opus", "sonnet", "haiku"})
|
|
71
|
+
CLAUDE_REVIEW_PROFILE = "claude-review/0.1.0"
|
|
72
|
+
CODEX_REVIEW_PROFILE = "codex-review/0.1.0"
|
|
73
|
+
# None means the real ~/.claude/agents (plus CLAUDE_CONFIG_DIR/agents); tests
|
|
74
|
+
# point it at a fixture directory.
|
|
75
|
+
CLAUDE_USER_AGENT_DIR: Path | None = None
|
|
76
|
+
# Managed (organization policy) agent locations; their definitions outrank
|
|
77
|
+
# every other source.
|
|
78
|
+
CLAUDE_MANAGED_AGENT_DIRS: list[Path] = [
|
|
79
|
+
Path(os.environ.get("ProgramData", r"C:\ProgramData")) / "ClaudeCode" / "agents",
|
|
80
|
+
Path(os.environ.get("ProgramFiles", r"C:\Program Files")) / "ClaudeCode" / "agents",
|
|
81
|
+
] if os.name == "nt" else [
|
|
82
|
+
Path("/Library/Application Support/ClaudeCode/agents"),
|
|
83
|
+
Path("/etc/claude-code/agents"),
|
|
84
|
+
]
|
|
60
85
|
|
|
61
86
|
|
|
62
87
|
class AuthorityError(RuntimeError):
|
|
@@ -230,9 +255,12 @@ def _spool_root(root: Path) -> Path:
|
|
|
230
255
|
def _save(root: Path, value: dict[str, Any]) -> None:
|
|
231
256
|
value = dict(value)
|
|
232
257
|
value["integrity_sha256"] = _integrity(value)
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
258
|
+
try:
|
|
259
|
+
_atomic_replace(_spool_root(root), _request_path(value["request_id"]), _canonical(value))
|
|
260
|
+
if value["state"] in TERMINAL_STATES:
|
|
261
|
+
(_registry_root() / f"{value['request_id']}.json").unlink(missing_ok=True)
|
|
262
|
+
except OSError as exc:
|
|
263
|
+
raise AuthorityError("AUTHORITY_BUSY", "request state could not be written") from exc
|
|
236
264
|
|
|
237
265
|
|
|
238
266
|
def _load(root: Path, request_id: str) -> dict[str, Any]:
|
|
@@ -253,7 +281,7 @@ def _load(root: Path, request_id: str) -> dict[str, Any]:
|
|
|
253
281
|
"observation_sha256", "receipt", "integrity_sha256", "payload",
|
|
254
282
|
"authority_source", "dispatch_prompt", "review_contract_sha256",
|
|
255
283
|
"required_coverage", "wrapper", "command_bindings", "recovery",
|
|
256
|
-
"launch_envelope", "workspace_roots",
|
|
284
|
+
"launch_envelope", "workspace_roots", "host",
|
|
257
285
|
}
|
|
258
286
|
if (
|
|
259
287
|
not isinstance(value, dict)
|
|
@@ -262,6 +290,7 @@ def _load(root: Path, request_id: str) -> dict[str, Any]:
|
|
|
262
290
|
or value.get("request_id") != request_id
|
|
263
291
|
or value.get("activity") not in ACTIVITIES
|
|
264
292
|
or value.get("state") not in STATES
|
|
293
|
+
or value.get("host", "codex") not in HOSTS
|
|
265
294
|
or not isinstance(value.get("context"), dict)
|
|
266
295
|
or not isinstance(value.get("repository"), dict)
|
|
267
296
|
or not isinstance(value.get("command_bindings", []), list)
|
|
@@ -431,7 +460,9 @@ def _register_request(root: Path, request: dict[str, Any]) -> None:
|
|
|
431
460
|
raise AuthorityError("AUTHORITY_COLLISION", "request registry entry differs")
|
|
432
461
|
|
|
433
462
|
|
|
434
|
-
def _registered_requests() -> list[tuple[Path, dict[str, Any]]]:
|
|
463
|
+
def _registered_requests(strict: bool = False) -> list[tuple[Path, dict[str, Any]]]:
|
|
464
|
+
"""Live registered requests. Strict callers refuse, rather than skip, a
|
|
465
|
+
request whose pointer or state exists but cannot be read."""
|
|
435
466
|
results = []
|
|
436
467
|
for path in _registry_root().glob("*.json"):
|
|
437
468
|
try:
|
|
@@ -456,7 +487,17 @@ def _registered_requests() -> list[tuple[Path, dict[str, Any]]]:
|
|
|
456
487
|
path.unlink(missing_ok=True)
|
|
457
488
|
continue
|
|
458
489
|
results.append((root, request))
|
|
459
|
-
except (AuthorityError, OSError, UnicodeError, ValueError):
|
|
490
|
+
except (AuthorityError, OSError, UnicodeError, ValueError) as exc:
|
|
491
|
+
unreadable = (
|
|
492
|
+
exc.code == "INVALID_AUTHORITY_STATE"
|
|
493
|
+
if isinstance(exc, AuthorityError) else REQUEST_RE.fullmatch(path.stem) is not None
|
|
494
|
+
)
|
|
495
|
+
cause = exc.__cause__ if isinstance(exc, AuthorityError) else exc
|
|
496
|
+
if strict and unreadable and not isinstance(cause, FileNotFoundError):
|
|
497
|
+
raise AuthorityError(
|
|
498
|
+
"AUTHORITY_BUSY",
|
|
499
|
+
f"authority request {path} is unreadable; remove it if no review is running",
|
|
500
|
+
) from exc
|
|
460
501
|
continue
|
|
461
502
|
return results
|
|
462
503
|
|
|
@@ -468,6 +509,85 @@ def _registered_request(request_id: str) -> tuple[Path, dict[str, Any]]:
|
|
|
468
509
|
return matches[0]
|
|
469
510
|
|
|
470
511
|
|
|
512
|
+
def _claude_start_marker(agent_id: str) -> Path:
|
|
513
|
+
key = _digest(_canonical({"host": "claude", "agent_id": agent_id}))
|
|
514
|
+
return _registry_root() / f"claude-start-{key}.json"
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
def _claude_agent_dirs(root: Path, sessions: tuple[Path, ...] = ()) -> list[Path]:
|
|
518
|
+
"""Every agent directory whose definitions outrank a plugin agent.
|
|
519
|
+
|
|
520
|
+
A session opened outside the artifact repository loads project agents
|
|
521
|
+
from its own directory, so the observing session roots are scanned too.
|
|
522
|
+
"""
|
|
523
|
+
directories = [root / ".claude" / "agents"]
|
|
524
|
+
directories.extend(Path(session) / ".claude" / "agents" for session in sessions)
|
|
525
|
+
try:
|
|
526
|
+
common = Path(_git_text(root, "rev-parse", "--path-format=absolute", "--git-common-dir"))
|
|
527
|
+
directories.append(common.parent / ".claude" / "agents")
|
|
528
|
+
except AuthorityError:
|
|
529
|
+
pass
|
|
530
|
+
if CLAUDE_USER_AGENT_DIR is not None:
|
|
531
|
+
directories.append(CLAUDE_USER_AGENT_DIR)
|
|
532
|
+
else:
|
|
533
|
+
directories.append(Path.home() / ".claude" / "agents")
|
|
534
|
+
configured = os.environ.get("CLAUDE_CONFIG_DIR")
|
|
535
|
+
if configured:
|
|
536
|
+
directories.append(Path(configured) / "agents")
|
|
537
|
+
directories.extend(CLAUDE_MANAGED_AGENT_DIRS)
|
|
538
|
+
return directories
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
def _raise_walk_error(exc: OSError) -> None:
|
|
542
|
+
raise exc
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
def _refuse_shadowed_reviewer(root: Path, *sessions: Path) -> None:
|
|
546
|
+
"""Project, user and managed agents outrank plugin agents; refuse any shadow.
|
|
547
|
+
|
|
548
|
+
Fail closed rather than parse YAML: a definition shadows the reviewer when
|
|
549
|
+
its file name, or anything in its front matter, names the reviewer in any
|
|
550
|
+
case. A description merely mentioning the name is a false positive the
|
|
551
|
+
error message explains; a missed shadow would let a parent-authored agent
|
|
552
|
+
grade its own work.
|
|
553
|
+
"""
|
|
554
|
+
short = CLAUDE_REVIEWER.split(":", 1)[1].casefold()
|
|
555
|
+
for directory in _claude_agent_dirs(root, sessions):
|
|
556
|
+
candidates = []
|
|
557
|
+
try:
|
|
558
|
+
if directory.is_dir():
|
|
559
|
+
for base, dirs, files in os.walk(directory, onerror=_raise_walk_error, followlinks=False):
|
|
560
|
+
for name in dirs:
|
|
561
|
+
# A linked subdirectory is not walked, so it cannot be cleared.
|
|
562
|
+
info = (Path(base) / name).lstat()
|
|
563
|
+
if stat.S_ISLNK(info.st_mode) or getattr(info, "st_file_attributes", 0) & 0x400:
|
|
564
|
+
raise AuthorityError(
|
|
565
|
+
"SHADOWED_REVIEWER", f"{Path(base) / name} is a link that cannot be inspected",
|
|
566
|
+
)
|
|
567
|
+
candidates.extend(Path(base) / name for name in files if name.casefold().endswith(".md"))
|
|
568
|
+
except OSError as exc:
|
|
569
|
+
raise AuthorityError("SHADOWED_REVIEWER", f"agent definitions under {directory} cannot be inspected") from exc
|
|
570
|
+
for candidate in candidates:
|
|
571
|
+
if short in candidate.stem.casefold():
|
|
572
|
+
raise AuthorityError("SHADOWED_REVIEWER", f"{candidate} shadows {CLAUDE_REVIEWER}")
|
|
573
|
+
try:
|
|
574
|
+
# A linked definition is followed, but only to a regular file:
|
|
575
|
+
# a pipe or device would hang the hook.
|
|
576
|
+
if not stat.S_ISREG(os.stat(candidate).st_mode):
|
|
577
|
+
raise AuthorityError("SHADOWED_REVIEWER", f"{candidate} is not a regular file")
|
|
578
|
+
with candidate.open("rb") as stream:
|
|
579
|
+
raw = stream.read(1 << 20)
|
|
580
|
+
except OSError as exc:
|
|
581
|
+
raise AuthorityError("SHADOWED_REVIEWER", "agent definition is unreadable") from exc
|
|
582
|
+
text = raw.decode("utf-8", errors="replace")
|
|
583
|
+
front = text
|
|
584
|
+
match = re.match(r"\A?\s*---[^\n]*\n(.*?)^---", text, re.S | re.M)
|
|
585
|
+
if match:
|
|
586
|
+
front = match.group(1)
|
|
587
|
+
if short in front.casefold():
|
|
588
|
+
raise AuthorityError("SHADOWED_REVIEWER", f"{candidate} shadows {CLAUDE_REVIEWER}")
|
|
589
|
+
|
|
590
|
+
|
|
471
591
|
def _ordinary_spawn_marker(session_id: str, turn_id: str) -> Path:
|
|
472
592
|
key = _digest(_canonical({"session_id": session_id, "turn_id": turn_id}))
|
|
473
593
|
return _registry_root() / f"ordinary-spawn-{key}.marker"
|
|
@@ -718,7 +838,8 @@ def _dispatch_prompt(request: dict[str, Any]) -> str:
|
|
|
718
838
|
+ json.dumps(request["required_coverage"], ensure_ascii=True)
|
|
719
839
|
+ ". Fields: format, request_id, target_sha256, contract_sha256, decision "
|
|
720
840
|
"(pass or changes_requested), coverage (unique strings), findings (objects with "
|
|
721
|
-
"severity, code, message), assessment (non-empty string)."
|
|
841
|
+
"severity, code, message), assessment (non-empty string). Reply with the JSON "
|
|
842
|
+
"object only: no code fence and no other text."
|
|
722
843
|
)
|
|
723
844
|
|
|
724
845
|
|
|
@@ -731,6 +852,8 @@ def arm_request(
|
|
|
731
852
|
*,
|
|
732
853
|
request_nonce: str | None = None,
|
|
733
854
|
workspace_roots: dict[str, str | Path] | None = None,
|
|
855
|
+
host: str = "codex",
|
|
856
|
+
reviewer_model: str = "opus",
|
|
734
857
|
**unexpected: Any,
|
|
735
858
|
) -> dict[str, Any]:
|
|
736
859
|
if unexpected:
|
|
@@ -740,7 +863,13 @@ def arm_request(
|
|
|
740
863
|
)
|
|
741
864
|
if activity not in ACTIVITIES:
|
|
742
865
|
raise AuthorityError("INVALID_AUTHORITY_REQUEST", "activity is unsupported")
|
|
866
|
+
if host not in HOSTS:
|
|
867
|
+
raise AuthorityError("INVALID_AUTHORITY_REQUEST", "host is unsupported")
|
|
868
|
+
if host == "claude" and reviewer_model not in CLAUDE_REVIEWER_MODELS:
|
|
869
|
+
raise AuthorityError("INVALID_AUTHORITY_REQUEST", "reviewer model is unsupported")
|
|
743
870
|
root = _real_root(root)
|
|
871
|
+
if host == "claude" and activity in REVIEW_ACTIVITIES:
|
|
872
|
+
_refuse_shadowed_reviewer(root)
|
|
744
873
|
nonce = request_nonce or secrets.token_urlsafe(24)
|
|
745
874
|
if not isinstance(nonce, str) or TOKEN_RE.fullmatch(nonce) is None:
|
|
746
875
|
raise AuthorityError("INVALID_AUTHORITY_REQUEST", "request nonce is malformed")
|
|
@@ -756,11 +885,14 @@ def arm_request(
|
|
|
756
885
|
raise AuthorityError("UNSUPPORTED_WORKSPACE", "workspace map must exactly cover declared cwd labels")
|
|
757
886
|
frozen_workspaces = {label: str(_real_root(path)) for label, path in workspace_roots.items()}
|
|
758
887
|
command_bindings = _bind_commands(root, context, frozen_workspaces) if activity == "verification" else []
|
|
759
|
-
|
|
888
|
+
seed_value = {
|
|
760
889
|
"repository": _repository_identity(root), "context": context, "nonce": nonce,
|
|
761
890
|
"command_bindings": command_bindings,
|
|
762
891
|
"workspace_roots": frozen_workspaces,
|
|
763
|
-
}
|
|
892
|
+
}
|
|
893
|
+
if host != "codex":
|
|
894
|
+
seed_value["host"] = host
|
|
895
|
+
seed = _canonical(seed_value)
|
|
764
896
|
request_id = _digest(seed)
|
|
765
897
|
request = {
|
|
766
898
|
"format": STATE_FORMAT,
|
|
@@ -781,14 +913,24 @@ def arm_request(
|
|
|
781
913
|
"command_bindings": command_bindings,
|
|
782
914
|
"workspace_roots": frozen_workspaces,
|
|
783
915
|
}
|
|
916
|
+
if host != "codex":
|
|
917
|
+
request["host"] = host
|
|
784
918
|
if activity in REVIEW_ACTIVITIES:
|
|
785
919
|
request["review_contract_sha256"], request["required_coverage"] = _review_binding(context)
|
|
786
920
|
request["dispatch_prompt"] = _dispatch_prompt(request)
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
921
|
+
if host == "claude":
|
|
922
|
+
request["launch_envelope"] = {
|
|
923
|
+
"description": f"codeArbiter {activity} {request_id[:12]}",
|
|
924
|
+
"prompt": request["dispatch_prompt"],
|
|
925
|
+
"subagent_type": CLAUDE_REVIEWER,
|
|
926
|
+
"model": reviewer_model,
|
|
927
|
+
}
|
|
928
|
+
else:
|
|
929
|
+
request["launch_envelope"] = {
|
|
930
|
+
"message": request["dispatch_prompt"],
|
|
931
|
+
"task_name": f"authority_{request_id[:12]}",
|
|
932
|
+
"fork_turns": "none",
|
|
933
|
+
}
|
|
792
934
|
_save(root, request)
|
|
793
935
|
_register_request(root, request)
|
|
794
936
|
result = {
|
|
@@ -1162,9 +1304,11 @@ def run_verification(
|
|
|
1162
1304
|
if request["activity"] != "verification":
|
|
1163
1305
|
raise AuthorityError("INVALID_AUTHORITY_REQUEST", "request is not verification")
|
|
1164
1306
|
if request["state"] == "COMPLETED":
|
|
1307
|
+
# Marked so Claude corroboration, which binds evidence to the
|
|
1308
|
+
# observed call, can tell a replay from the run it authorized.
|
|
1165
1309
|
return {
|
|
1166
1310
|
"request_id": request_id, "state": "COMPLETED",
|
|
1167
|
-
"commands": request["payload"]["commands"],
|
|
1311
|
+
"commands": request["payload"]["commands"], "replayed": True,
|
|
1168
1312
|
}
|
|
1169
1313
|
if request["state"] != "ARMED":
|
|
1170
1314
|
raise AuthorityError("INTERRUPTED_ATTEMPT", "verification attempt cannot be rerun")
|
|
@@ -1265,7 +1409,7 @@ def _host_id(value: Any, field: str) -> str:
|
|
|
1265
1409
|
return value
|
|
1266
1410
|
|
|
1267
1411
|
|
|
1268
|
-
def _verification_selector(event: dict[str, Any]) -> tuple[str, Path] | None:
|
|
1412
|
+
def _verification_selector(event: dict[str, Any]) -> tuple[str, Path, str] | None:
|
|
1269
1413
|
"""Recognize only the closed wrapper selector, never caller-authored child argv."""
|
|
1270
1414
|
if event.get("tool_name") not in {
|
|
1271
1415
|
"Bash", "shell_command", "exec_command", "unified_exec",
|
|
@@ -1277,10 +1421,13 @@ def _verification_selector(event: dict[str, Any]) -> tuple[str, Path] | None:
|
|
|
1277
1421
|
command = tool_input.get("command", tool_input.get("cmd"))
|
|
1278
1422
|
if not isinstance(command, str):
|
|
1279
1423
|
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "exec command is absent")
|
|
1280
|
-
compound = any(marker in command for marker in ("\n", "\r", ";", "
|
|
1424
|
+
compound = any(marker in command for marker in ("\n", "\r", ";", "&", "|", ">", "<", "`", "$"))
|
|
1281
1425
|
try:
|
|
1282
1426
|
tokens = [token.strip('"') for token in shlex.split(command, posix=False)]
|
|
1283
1427
|
except ValueError as exc:
|
|
1428
|
+
# An ordinary command with unbalanced quoting is not ours to deny.
|
|
1429
|
+
if "artifact-authority" not in command.casefold():
|
|
1430
|
+
return None
|
|
1284
1431
|
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "verifier wrapper command is malformed") from exc
|
|
1285
1432
|
wrapper_start = (
|
|
1286
1433
|
len(tokens) >= 3
|
|
@@ -1307,26 +1454,52 @@ def _verification_selector(event: dict[str, Any]) -> tuple[str, Path] | None:
|
|
|
1307
1454
|
or REQUEST_RE.fullmatch(tokens[6]) is None
|
|
1308
1455
|
):
|
|
1309
1456
|
return None
|
|
1310
|
-
return tokens[6], _real_root(tokens[4])
|
|
1457
|
+
return tokens[6], _real_root(tokens[4]), tokens[1]
|
|
1311
1458
|
|
|
1312
1459
|
|
|
1313
1460
|
def observe_verifier_hook(
|
|
1314
|
-
invocation_root: str | Path, event: dict[str, Any]
|
|
1461
|
+
invocation_root: str | Path, event: dict[str, Any], host: str = "codex",
|
|
1315
1462
|
) -> dict[str, Any] | None:
|
|
1316
1463
|
"""Authorize/corroborate the wrapper; native exit/output remain result evidence."""
|
|
1317
|
-
|
|
1318
|
-
|
|
1464
|
+
if host not in HOSTS:
|
|
1465
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "verifier host is unsupported")
|
|
1466
|
+
events = {"PreToolUse", "PostToolUse"} | ({"PostToolUseFailure"} if host == "claude" else set())
|
|
1467
|
+
if not isinstance(event, dict) or event.get("hook_event_name") not in events:
|
|
1319
1468
|
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "verifier hook event is unsupported")
|
|
1320
1469
|
selected = _verification_selector(event)
|
|
1321
1470
|
if selected is None:
|
|
1322
1471
|
return None
|
|
1323
|
-
|
|
1472
|
+
# Only an authority call is ever refused for an unavailable invocation root.
|
|
1473
|
+
_real_root(invocation_root)
|
|
1474
|
+
request_id, selected_root, script = selected
|
|
1324
1475
|
root, request = _registered_request(request_id)
|
|
1325
|
-
if
|
|
1476
|
+
if host == "claude":
|
|
1477
|
+
shipped = (Path(__file__).resolve().parent / "artifact-authority.py").resolve()
|
|
1478
|
+
try:
|
|
1479
|
+
named = Path(script).resolve(strict=True)
|
|
1480
|
+
except OSError:
|
|
1481
|
+
named = None
|
|
1482
|
+
if named != shipped:
|
|
1483
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "verifier wrapper is not the shipped artifact-authority.py")
|
|
1484
|
+
if (
|
|
1485
|
+
root != selected_root
|
|
1486
|
+
or request["activity"] != "verification"
|
|
1487
|
+
or request.get("host", "codex") != host
|
|
1488
|
+
):
|
|
1326
1489
|
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "verifier target does not match its request")
|
|
1327
1490
|
session_id = _host_id(event.get("session_id"), "session_id")
|
|
1328
|
-
turn_id
|
|
1491
|
+
# Codex names the parent turn turn_id; Claude Code names it prompt_id.
|
|
1492
|
+
turn_field = "prompt_id" if host == "claude" else "turn_id"
|
|
1493
|
+
turn_id = _host_id(event.get(turn_field), turn_field)
|
|
1329
1494
|
tool_use_id = _host_id(event.get("tool_use_id"), "tool_use_id")
|
|
1495
|
+
if host == "claude" and event["hook_event_name"] == "PreToolUse":
|
|
1496
|
+
tool_input = event["tool_input"]
|
|
1497
|
+
# A background run returns no stdout to corroborate; an unsandboxed
|
|
1498
|
+
# run escapes the host boundary the evidence assumes.
|
|
1499
|
+
if tool_input.get("run_in_background") or tool_input.get("dangerouslyDisableSandbox"):
|
|
1500
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "verifier wrapper must run in the foreground and sandbox")
|
|
1501
|
+
if set(tool_input) - {"command", "description", "timeout"}:
|
|
1502
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "verifier wrapper input carries unexpected options")
|
|
1330
1503
|
if event["hook_event_name"] == "PreToolUse":
|
|
1331
1504
|
if request["state"] != "ARMED" or request.get("wrapper") is not None:
|
|
1332
1505
|
raise AuthorityError("INTERRUPTED_ATTEMPT", "verifier wrapper is not armable")
|
|
@@ -1339,7 +1512,7 @@ def observe_verifier_hook(
|
|
|
1339
1512
|
workspace_before = _workspace_snapshots(current_bindings)
|
|
1340
1513
|
request["wrapper"] = {
|
|
1341
1514
|
"state": "AUTHORIZED", "session_id": session_id,
|
|
1342
|
-
|
|
1515
|
+
turn_field: turn_id, "tool_use_id": tool_use_id,
|
|
1343
1516
|
"command_bindings": current_bindings,
|
|
1344
1517
|
"workspace_before": workspace_before,
|
|
1345
1518
|
}
|
|
@@ -1352,11 +1525,14 @@ def observe_verifier_hook(
|
|
|
1352
1525
|
if (
|
|
1353
1526
|
not isinstance(wrapper, dict)
|
|
1354
1527
|
or wrapper.get("session_id") != session_id
|
|
1355
|
-
or wrapper.get(
|
|
1528
|
+
or wrapper.get(turn_field) != turn_id
|
|
1356
1529
|
or wrapper.get("tool_use_id") != tool_use_id
|
|
1357
|
-
or request["state"] != "COMPLETED"
|
|
1358
1530
|
):
|
|
1359
1531
|
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "verifier completion is not correlated")
|
|
1532
|
+
if host == "claude":
|
|
1533
|
+
return _claude_verifier_result(root, request, wrapper, event)
|
|
1534
|
+
if request["state"] != "COMPLETED":
|
|
1535
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "verifier completion is not correlated")
|
|
1360
1536
|
response = event.get("tool_response")
|
|
1361
1537
|
if isinstance(response, dict):
|
|
1362
1538
|
if response.get("exit_code") != 0:
|
|
@@ -1379,6 +1555,41 @@ def observe_verifier_hook(
|
|
|
1379
1555
|
return {"request_id": request_id, "state": "CORROBORATED"}
|
|
1380
1556
|
|
|
1381
1557
|
|
|
1558
|
+
def _claude_verifier_result(
|
|
1559
|
+
root: Path, request: dict[str, Any], wrapper: dict[str, Any], event: dict[str, Any]
|
|
1560
|
+
) -> dict[str, Any]:
|
|
1561
|
+
"""Claude reports no exit code: success and failure are distinct events."""
|
|
1562
|
+
request_id = request["request_id"]
|
|
1563
|
+
if wrapper.get("state") != "AUTHORIZED":
|
|
1564
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "verifier completion was already observed")
|
|
1565
|
+
if event["hook_event_name"] == "PostToolUseFailure":
|
|
1566
|
+
wrapper["state"] = "FAILED"
|
|
1567
|
+
_save(root, request)
|
|
1568
|
+
return {"request_id": request_id, "state": "FAILED"}
|
|
1569
|
+
response = event.get("tool_response")
|
|
1570
|
+
if request["state"] != "COMPLETED":
|
|
1571
|
+
raise AuthorityError("FAILED_VERIFICATION", "verifier request did not complete")
|
|
1572
|
+
if not isinstance(response, dict) or response.get("interrupted") is not False:
|
|
1573
|
+
raise AuthorityError("FAILED_VERIFICATION", "verifier wrapper was interrupted or has no result")
|
|
1574
|
+
stdout = response.get("stdout")
|
|
1575
|
+
if not isinstance(stdout, str) or len(stdout.encode("utf-8")) > MAX_OUTPUT:
|
|
1576
|
+
raise AuthorityError("FAILED_VERIFICATION", "verifier wrapper output is unavailable")
|
|
1577
|
+
try:
|
|
1578
|
+
completed = json.loads(stdout.strip())
|
|
1579
|
+
except ValueError:
|
|
1580
|
+
completed = None
|
|
1581
|
+
if (
|
|
1582
|
+
not isinstance(completed, dict)
|
|
1583
|
+
or set(completed) != {"request_id", "state", "commands"}
|
|
1584
|
+
or completed.get("request_id") != request_id
|
|
1585
|
+
or completed.get("state") != "COMPLETED"
|
|
1586
|
+
):
|
|
1587
|
+
raise AuthorityError("FAILED_VERIFICATION", "verifier wrapper output did not corroborate completion")
|
|
1588
|
+
wrapper["state"] = "CORROBORATED"
|
|
1589
|
+
_save(root, request)
|
|
1590
|
+
return {"request_id": request_id, "state": "CORROBORATED"}
|
|
1591
|
+
|
|
1592
|
+
|
|
1382
1593
|
def _parse_decision(raw: Any, request: dict[str, Any]) -> dict[str, Any]:
|
|
1383
1594
|
if not isinstance(raw, str) or len(raw.encode("utf-8")) > MAX_DECISION:
|
|
1384
1595
|
raise AuthorityError("INVALID_REVIEW_DECISION", "review decision is absent or oversized")
|
|
@@ -1445,7 +1656,11 @@ def observe_codex_hook(root: str | Path, event: dict[str, Any]) -> dict[str, Any
|
|
|
1445
1656
|
return None
|
|
1446
1657
|
request_id = _request_id_from_prompt(message)
|
|
1447
1658
|
root, request = _registered_request(request_id)
|
|
1448
|
-
if
|
|
1659
|
+
if (
|
|
1660
|
+
request.get("host", "codex") != "codex"
|
|
1661
|
+
or request["activity"] not in REVIEW_ACTIVITIES
|
|
1662
|
+
or request["state"] != "ARMED"
|
|
1663
|
+
):
|
|
1449
1664
|
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "review request is not launchable")
|
|
1450
1665
|
if tool_input != request.get("launch_envelope"):
|
|
1451
1666
|
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "review launch envelope was changed")
|
|
@@ -1564,7 +1779,7 @@ def observe_codex_hook(root: str | Path, event: dict[str, Any]) -> dict[str, Any
|
|
|
1564
1779
|
payload["task_hashes"] = request["context"]["task_hashes"]
|
|
1565
1780
|
producer_result = {"launch": request["launch"], "decision": decision}
|
|
1566
1781
|
observation = _closed_observation(
|
|
1567
|
-
request, payload,
|
|
1782
|
+
request, payload, CODEX_REVIEW_PROFILE, agent_id,
|
|
1568
1783
|
producer_result,
|
|
1569
1784
|
)
|
|
1570
1785
|
_observation(root, request, observation)
|
|
@@ -1576,19 +1791,253 @@ def observe_codex_hook(root: str | Path, event: dict[str, Any]) -> dict[str, Any
|
|
|
1576
1791
|
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "hook event is not supported")
|
|
1577
1792
|
|
|
1578
1793
|
|
|
1794
|
+
def _claude_requests(state: set[str], strict: bool = False) -> list[tuple[Path, dict[str, Any]]]:
|
|
1795
|
+
return [
|
|
1796
|
+
(root, request) for root, request in _registered_requests(strict)
|
|
1797
|
+
if request.get("host") == "claude" and request["activity"] in REVIEW_ACTIVITIES
|
|
1798
|
+
and request["state"] in state
|
|
1799
|
+
]
|
|
1800
|
+
|
|
1801
|
+
|
|
1802
|
+
def _reject(root: Path, request: dict[str, Any], mode: str) -> None:
|
|
1803
|
+
request["state"] = "REJECTED"
|
|
1804
|
+
request["recovery"] = {"mode": mode, "rerun_permitted": False}
|
|
1805
|
+
_save(root, request)
|
|
1806
|
+
|
|
1807
|
+
|
|
1808
|
+
def _claude_bind_child(root: Path, request: dict[str, Any]) -> dict[str, Any]:
|
|
1809
|
+
"""Complete the order-independent launch/child join when both sides exist."""
|
|
1810
|
+
launch = request["launch"]
|
|
1811
|
+
marker = _claude_start_marker(launch["agent_id"])
|
|
1812
|
+
try:
|
|
1813
|
+
started = json.loads(marker.read_text(encoding="utf-8"))
|
|
1814
|
+
except FileNotFoundError:
|
|
1815
|
+
_save(root, request)
|
|
1816
|
+
return {"request_id": request["request_id"], "state": request["state"]}
|
|
1817
|
+
except (OSError, UnicodeError, ValueError) as exc:
|
|
1818
|
+
_reject(root, request, "unreadable-subagent-start")
|
|
1819
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "subagent start record is unreadable") from exc
|
|
1820
|
+
if not isinstance(started, dict) or started.get("agent_type") != CLAUDE_REVIEWER:
|
|
1821
|
+
_reject(root, request, "unpinned-reviewer-agent")
|
|
1822
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "subagent is not the pinned reviewer")
|
|
1823
|
+
launch["agent_type"] = CLAUDE_REVIEWER
|
|
1824
|
+
request["state"] = "RUNNING"
|
|
1825
|
+
_save(root, request)
|
|
1826
|
+
marker.unlink(missing_ok=True)
|
|
1827
|
+
return {"request_id": request["request_id"], "state": "RUNNING"}
|
|
1828
|
+
|
|
1829
|
+
|
|
1830
|
+
def observe_claude_hook(root: str | Path, event: dict[str, Any]) -> dict[str, Any] | None:
|
|
1831
|
+
"""Correlate a Claude Code reviewer launch, child lifecycle, and first result.
|
|
1832
|
+
|
|
1833
|
+
The invocation repository is observed but never selects authority, and an
|
|
1834
|
+
ordinary (non-authority) event is never refused.
|
|
1835
|
+
"""
|
|
1836
|
+
if not isinstance(event, dict):
|
|
1837
|
+
return None
|
|
1838
|
+
name = event.get("hook_event_name")
|
|
1839
|
+
tool = event.get("tool_name")
|
|
1840
|
+
|
|
1841
|
+
if name == "PreToolUse" and tool == "SendMessage":
|
|
1842
|
+
# The message shape is not a recorded contract, so fail closed: any
|
|
1843
|
+
# message from the reviewer's parent session while it runs could steer it.
|
|
1844
|
+
session = event.get("session_id")
|
|
1845
|
+
rejected = None
|
|
1846
|
+
for candidate_root, candidate in _claude_requests({"LAUNCHING", "RUNNING"}, strict=True):
|
|
1847
|
+
if session is not None and session == (candidate.get("launch") or {}).get("parent_session_id"):
|
|
1848
|
+
_reject(candidate_root, candidate, "reviewer-steered-by-message")
|
|
1849
|
+
rejected = {"request_id": candidate["request_id"], "state": "REJECTED"}
|
|
1850
|
+
return rejected
|
|
1851
|
+
|
|
1852
|
+
if name == "PreToolUse" and tool == "Agent":
|
|
1853
|
+
tool_input = event.get("tool_input")
|
|
1854
|
+
prompt = tool_input.get("prompt") if isinstance(tool_input, dict) else None
|
|
1855
|
+
if not isinstance(prompt, str) or not prompt.startswith("[CODEARBITER_AUTHORITY_REQUEST:"):
|
|
1856
|
+
return None
|
|
1857
|
+
session_root = _real_root(root)
|
|
1858
|
+
session_id = _host_id(event.get("session_id"), "session_id")
|
|
1859
|
+
prompt_id = _host_id(event.get("prompt_id"), "prompt_id")
|
|
1860
|
+
tool_use_id = _host_id(event.get("tool_use_id"), "tool_use_id")
|
|
1861
|
+
root, request = _registered_request(_request_id_from_prompt(prompt))
|
|
1862
|
+
if (
|
|
1863
|
+
request.get("host") != "claude"
|
|
1864
|
+
or request["activity"] not in REVIEW_ACTIVITIES
|
|
1865
|
+
or request["state"] != "ARMED"
|
|
1866
|
+
):
|
|
1867
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "review request is not launchable")
|
|
1868
|
+
if tool_input != request.get("launch_envelope"):
|
|
1869
|
+
_reject(root, request, "changed-launch-envelope")
|
|
1870
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "review launch envelope was changed")
|
|
1871
|
+
try:
|
|
1872
|
+
_refuse_shadowed_reviewer(root, session_root)
|
|
1873
|
+
except AuthorityError:
|
|
1874
|
+
_reject(root, request, "shadowed-reviewer")
|
|
1875
|
+
raise
|
|
1876
|
+
request["launch"] = {
|
|
1877
|
+
"parent_session_id": session_id, "parent_prompt_id": prompt_id,
|
|
1878
|
+
"tool_use_id": tool_use_id, "post_confirmed": False,
|
|
1879
|
+
"agent_id": None, "agent_type": None,
|
|
1880
|
+
"subagent_type": tool_input["subagent_type"], "model": tool_input["model"],
|
|
1881
|
+
"resolved_model": None, "first_stop": False,
|
|
1882
|
+
}
|
|
1883
|
+
request["state"] = "LAUNCHING"
|
|
1884
|
+
_save(root, request)
|
|
1885
|
+
return {"request_id": request["request_id"], "state": "LAUNCHING"}
|
|
1886
|
+
|
|
1887
|
+
if name == "PostToolUse" and tool == "Agent":
|
|
1888
|
+
session_id = event.get("session_id")
|
|
1889
|
+
tool_use_id = event.get("tool_use_id")
|
|
1890
|
+
matches = [
|
|
1891
|
+
(candidate_root, candidate)
|
|
1892
|
+
for candidate_root, candidate in _claude_requests({"LAUNCHING"})
|
|
1893
|
+
if (candidate.get("launch") or {}).get("tool_use_id") == tool_use_id
|
|
1894
|
+
and candidate["launch"].get("parent_session_id") == session_id
|
|
1895
|
+
]
|
|
1896
|
+
if not matches:
|
|
1897
|
+
return None
|
|
1898
|
+
if len(matches) != 1:
|
|
1899
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "review launch correlation is ambiguous")
|
|
1900
|
+
root, request = matches[0]
|
|
1901
|
+
response = event.get("tool_response")
|
|
1902
|
+
try:
|
|
1903
|
+
prompt_id = _host_id(event.get("prompt_id"), "prompt_id")
|
|
1904
|
+
if request["launch"].get("parent_prompt_id") != prompt_id or request["launch"]["post_confirmed"]:
|
|
1905
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "review launch result is out of order")
|
|
1906
|
+
if not isinstance(response, dict):
|
|
1907
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "agent result is malformed")
|
|
1908
|
+
agent_id = _host_id(response.get("agentId"), "agentId")
|
|
1909
|
+
resolved = _host_id(response.get("resolvedModel"), "resolvedModel")
|
|
1910
|
+
except AuthorityError:
|
|
1911
|
+
_reject(root, request, "uncorrelated-launch-result")
|
|
1912
|
+
raise
|
|
1913
|
+
request["launch"].update(post_confirmed=True, agent_id=agent_id, resolved_model=resolved)
|
|
1914
|
+
return _claude_bind_child(root, request)
|
|
1915
|
+
|
|
1916
|
+
if name == "SubagentStart":
|
|
1917
|
+
if not _claude_requests({"LAUNCHING"}):
|
|
1918
|
+
return None
|
|
1919
|
+
agent_id = _host_id(event.get("agent_id"), "agent_id")
|
|
1920
|
+
agent_type = _host_id(event.get("agent_type"), "agent_type")
|
|
1921
|
+
for candidate_root, candidate in _claude_requests({"LAUNCHING"}):
|
|
1922
|
+
launch = candidate.get("launch") or {}
|
|
1923
|
+
if launch.get("agent_id") == agent_id:
|
|
1924
|
+
if agent_type != CLAUDE_REVIEWER:
|
|
1925
|
+
_reject(candidate_root, candidate, "unpinned-reviewer-agent")
|
|
1926
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "subagent is not the pinned reviewer")
|
|
1927
|
+
launch["agent_type"] = agent_type
|
|
1928
|
+
candidate["state"] = "RUNNING"
|
|
1929
|
+
_save(candidate_root, candidate)
|
|
1930
|
+
return {"request_id": candidate["request_id"], "state": "RUNNING"}
|
|
1931
|
+
if any(
|
|
1932
|
+
(candidate.get("launch") or {}).get("post_confirmed") is False
|
|
1933
|
+
for _root, candidate in _claude_requests({"LAUNCHING"})
|
|
1934
|
+
):
|
|
1935
|
+
# The launch result may not have arrived yet; remember this start.
|
|
1936
|
+
marker = _claude_start_marker(agent_id)
|
|
1937
|
+
data = _canonical({"agent_id": agent_id, "agent_type": agent_type})
|
|
1938
|
+
# Written aside and linked into place: a concurrent launch result
|
|
1939
|
+
# joining this agent must never read an empty or partial marker.
|
|
1940
|
+
temporary = marker.with_name(f".{marker.name}.{secrets.token_hex(8)}.tmp")
|
|
1941
|
+
fd = os.open(temporary, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
|
|
1942
|
+
try:
|
|
1943
|
+
os.write(fd, data)
|
|
1944
|
+
os.fsync(fd)
|
|
1945
|
+
finally:
|
|
1946
|
+
os.close(fd)
|
|
1947
|
+
try:
|
|
1948
|
+
os.link(temporary, marker)
|
|
1949
|
+
except FileExistsError:
|
|
1950
|
+
return None
|
|
1951
|
+
finally:
|
|
1952
|
+
temporary.unlink(missing_ok=True)
|
|
1953
|
+
# The launch result may have bound this agent while the marker was
|
|
1954
|
+
# being written; finish the join now rather than strand it.
|
|
1955
|
+
for candidate_root, candidate in _claude_requests({"LAUNCHING"}):
|
|
1956
|
+
if (candidate.get("launch") or {}).get("agent_id") == agent_id:
|
|
1957
|
+
return _claude_bind_child(candidate_root, candidate)
|
|
1958
|
+
return None
|
|
1959
|
+
|
|
1960
|
+
if name == "SubagentStop":
|
|
1961
|
+
agent_id = event.get("agent_id")
|
|
1962
|
+
matches = [
|
|
1963
|
+
(candidate_root, candidate)
|
|
1964
|
+
for candidate_root, candidate in _claude_requests({"LAUNCHING", "RUNNING"})
|
|
1965
|
+
if (candidate.get("launch") or {}).get("agent_id") == agent_id
|
|
1966
|
+
]
|
|
1967
|
+
if not matches:
|
|
1968
|
+
# Includes every later Stop for an already-completed reviewer.
|
|
1969
|
+
return None
|
|
1970
|
+
if len(matches) != 1:
|
|
1971
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "subagent correlation is ambiguous")
|
|
1972
|
+
session_root = root
|
|
1973
|
+
root, request = matches[0]
|
|
1974
|
+
launch = request["launch"]
|
|
1975
|
+
if request["state"] == "LAUNCHING" and launch.get("post_confirmed") is True:
|
|
1976
|
+
# A start marker written late in the launch/start race.
|
|
1977
|
+
_claude_bind_child(root, request)
|
|
1978
|
+
request = _load(root, request["request_id"])
|
|
1979
|
+
launch = request["launch"]
|
|
1980
|
+
try:
|
|
1981
|
+
# background_tasks is the parent session's list and includes this
|
|
1982
|
+
# reviewer itself while it runs; it can only corroborate identity.
|
|
1983
|
+
tasks = event.get("background_tasks")
|
|
1984
|
+
own = [
|
|
1985
|
+
task for task in tasks if isinstance(task, dict) and task.get("id") == agent_id
|
|
1986
|
+
] if isinstance(tasks, list) else None
|
|
1987
|
+
if (
|
|
1988
|
+
request["state"] != "RUNNING"
|
|
1989
|
+
or launch.get("post_confirmed") is not True
|
|
1990
|
+
or event.get("agent_type") != CLAUDE_REVIEWER
|
|
1991
|
+
or event.get("stop_hook_active") is not False
|
|
1992
|
+
or own is None
|
|
1993
|
+
or len(own) != 1
|
|
1994
|
+
or own[0].get("agent_type") != CLAUDE_REVIEWER
|
|
1995
|
+
):
|
|
1996
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "reviewer stop is not a first clean stop")
|
|
1997
|
+
_refuse_shadowed_reviewer(root, _real_root(session_root))
|
|
1998
|
+
decision = _parse_decision(event.get("last_assistant_message"), request)
|
|
1999
|
+
except AuthorityError:
|
|
2000
|
+
_reject(root, request, "rejected-first-stop")
|
|
2001
|
+
raise
|
|
2002
|
+
launch["first_stop"] = True
|
|
2003
|
+
payload = {
|
|
2004
|
+
"input_sha256": request["context"]["input_sha256"],
|
|
2005
|
+
"spec_sha256": request["context"]["spec_sha256"],
|
|
2006
|
+
"assessment": decision["assessment"],
|
|
2007
|
+
}
|
|
2008
|
+
if request["activity"] == "spec_review":
|
|
2009
|
+
payload["task_sha256"] = request["context"]["task_sha256"]
|
|
2010
|
+
else:
|
|
2011
|
+
payload["base_input_sha256"] = request["context"]["base_input_sha256"]
|
|
2012
|
+
payload["task_hashes"] = request["context"]["task_hashes"]
|
|
2013
|
+
observation = _closed_observation(
|
|
2014
|
+
request, payload, CLAUDE_REVIEW_PROFILE, agent_id,
|
|
2015
|
+
{"launch": launch, "decision": decision},
|
|
2016
|
+
)
|
|
2017
|
+
_observation(root, request, observation)
|
|
2018
|
+
request["payload"] = payload
|
|
2019
|
+
request["state"] = "COMPLETED"
|
|
2020
|
+
_save(root, request)
|
|
2021
|
+
return {"request_id": request["request_id"], "state": "COMPLETED"}
|
|
2022
|
+
|
|
2023
|
+
return None
|
|
2024
|
+
|
|
2025
|
+
|
|
1579
2026
|
def _event(request: dict[str, Any]) -> dict[str, Any]:
|
|
1580
2027
|
context = request["context"]
|
|
1581
2028
|
activity = request["activity"]
|
|
2029
|
+
host = request.get("host", "codex")
|
|
2030
|
+
host_label = "Claude" if host == "claude" else "Codex"
|
|
1582
2031
|
if activity == "verification":
|
|
1583
2032
|
authority_kind = "verification_runner"
|
|
1584
2033
|
actor = "codeArbiter supervised verifier"
|
|
1585
|
-
origin = f"
|
|
2034
|
+
origin = f"{host}:verification-run:{request['request_id']}"
|
|
1586
2035
|
source_text = "Observed exact declared-command completion and qualified named-test results."
|
|
1587
2036
|
else:
|
|
1588
2037
|
authority_kind = "review_workflow"
|
|
1589
|
-
actor = "independent
|
|
1590
|
-
origin = f"
|
|
1591
|
-
source_text = "Observed a correlated fresh
|
|
2038
|
+
actor = f"independent {host} reviewer"
|
|
2039
|
+
origin = f"{host}:subagent:{request['launch']['agent_id']}"
|
|
2040
|
+
source_text = f"Observed a correlated fresh {host_label} reviewer completion with a closed passing decision."
|
|
1592
2041
|
return {
|
|
1593
2042
|
"format": EVENT_FORMAT,
|
|
1594
2043
|
"kind": activity,
|
|
@@ -1672,7 +2121,7 @@ def recover_request(root: str | Path, request_id: str, disposition: str) -> dict
|
|
|
1672
2121
|
request["state"] == "ARMED"
|
|
1673
2122
|
and request["activity"] == "verification"
|
|
1674
2123
|
and isinstance(request.get("wrapper"), dict)
|
|
1675
|
-
and request["wrapper"].get("state")
|
|
2124
|
+
and request["wrapper"].get("state") in {"AUTHORIZED", "FAILED"}
|
|
1676
2125
|
)
|
|
1677
2126
|
if request["state"] not in {"RUNNING", "LAUNCHING"} and not recoverable_completed and not recoverable_authorized:
|
|
1678
2127
|
raise AuthorityError("INVALID_RECOVERY", "only an interrupted active attempt can be recovered")
|