@arbiterforge/ca-pi 0.15.2 → 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.
- package/README.md +2 -2
- package/package.json +1 -1
- package/plugins/ca-pi/CHANGELOG.md +62 -0
- package/plugins/ca-pi/COMMANDS.md +3 -9
- package/plugins/ca-pi/SKILLS.md +16 -22
- package/plugins/ca-pi/agents/coverage-auditor.md +16 -2
- package/plugins/ca-pi/generated/command-catalog.json +13 -22
- 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 +383 -32
- package/plugins/ca-pi/hooks/_artifactlib.py +3 -1
- package/plugins/ca-pi/hooks/_host.py +1 -1
- package/plugins/ca-pi/hooks/artifact-authority-hook.py +4 -21
- package/plugins/ca-pi/hooks/artifact-authority.py +11 -0
- package/plugins/ca-pi/hooks/hostapi.py +1 -1
- package/plugins/ca-pi/includes/command-compatibility.md +31 -0
- package/plugins/ca-pi/includes/farm.md +187 -36
- package/plugins/ca-pi/includes/maturity-coverage.md +57 -17
- package/plugins/ca-pi/includes/routing-table.md +10 -11
- package/plugins/ca-pi/includes/safety-core.md +2 -2
- package/plugins/ca-pi/package.json +1 -1
- package/plugins/ca-pi/routines/INDEX.md +12 -11
- package/plugins/ca-pi/routines/context-check/SKILL.md +17 -1
- package/plugins/ca-pi/routines/context-creation/SKILL.md +8 -1
- package/plugins/ca-pi/routines/debug/SKILL.md +18 -2
- package/plugins/ca-pi/routines/decision-lifecycle/SKILL.md +72 -118
- package/plugins/ca-pi/routines/decision-lifecycle/references/authoring.md +152 -0
- package/plugins/ca-pi/routines/decision-variance/SKILL.md +107 -133
- package/plugins/ca-pi/routines/decision-variance/references/analysis.md +136 -0
- package/plugins/ca-pi/routines/decompose/SKILL.md +8 -1
- package/plugins/ca-pi/routines/finishing-a-development-branch/SKILL.md +85 -7
- package/plugins/ca-pi/routines/post-merge-cleanup/SKILL.md +19 -2
- package/plugins/ca-pi/routines/refactor/SKILL.md +27 -8
- package/plugins/ca-pi/routines/release/SKILL.md +2 -1
- package/plugins/ca-pi/routines/security-architecture/SKILL.md +14 -3
- package/plugins/ca-pi/routines/tdd/SKILL.md +9 -5
- package/plugins/ca-pi/routines/tribunal/SKILL.md +28 -14
- package/plugins/ca-pi/skills/ca-adr/SKILL.md +103 -18
- package/plugins/ca-pi/skills/ca-adr-status/SKILL.md +6 -24
- package/plugins/ca-pi/skills/ca-cleanup/SKILL.md +241 -52
- package/plugins/ca-pi/skills/ca-context-check/SKILL.md +87 -24
- package/plugins/ca-pi/skills/ca-create-context/SKILL.md +159 -19
- package/plugins/ca-pi/skills/ca-debug/SKILL.md +100 -27
- package/plugins/ca-pi/skills/ca-decompose/SKILL.md +156 -17
- package/plugins/ca-pi/skills/ca-pr/SKILL.md +161 -26
- package/plugins/ca-pi/skills/ca-reconcile/SKILL.md +108 -30
- package/plugins/ca-pi/skills/ca-refactor/SKILL.md +96 -30
- package/plugins/ca-pi/skills/ca-release/SKILL.md +614 -39
- package/plugins/ca-pi/skills/ca-status/SKILL.md +1 -1
- package/plugins/ca-pi/skills/ca-threat-model/SKILL.md +56 -27
- package/plugins/ca-pi/skills/ca-tribunal/SKILL.md +105 -26
- package/plugins/ca-pi/routines/skill-author/SKILL.md +0 -129
- package/plugins/ca-pi/routines/skill-author/references/skill-template.md +0 -58
- package/plugins/ca-pi/skills/ca-new-skill/SKILL.md +0 -129
|
@@ -76,6 +76,14 @@ CLAUDE_REVIEWER = "ca:authority-reviewer"
|
|
|
76
76
|
CLAUDE_REVIEWER_MODELS = frozenset({"opus", "sonnet", "haiku"})
|
|
77
77
|
CLAUDE_REVIEW_PROFILE = "claude-review/0.1.0"
|
|
78
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
|
|
79
87
|
# None means the real ~/.claude/agents (plus CLAUDE_CONFIG_DIR/agents); tests
|
|
80
88
|
# point it at a fixture directory.
|
|
81
89
|
CLAUDE_USER_AGENT_DIR: Path | None = None
|
|
@@ -389,7 +397,7 @@ def _load(
|
|
|
389
397
|
"observation_sha256", "receipt", "integrity_sha256", "payload",
|
|
390
398
|
"authority_source", "dispatch_prompt", "review_contract_sha256",
|
|
391
399
|
"required_coverage", "wrapper", "command_bindings", "recovery",
|
|
392
|
-
"launch_envelope", "workspace_roots", "host",
|
|
400
|
+
"launch_envelope", "workspace_roots", "host", "codex_review_profile",
|
|
393
401
|
}
|
|
394
402
|
if (
|
|
395
403
|
not isinstance(value, dict)
|
|
@@ -410,6 +418,12 @@ def _load(
|
|
|
410
418
|
or SHA256_RE.fullmatch(value.get("context_sha256", "")) is None
|
|
411
419
|
):
|
|
412
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")
|
|
413
427
|
if len(raw) > MAX_STATE:
|
|
414
428
|
try:
|
|
415
429
|
_validate_legacy_review_recovery(root, value)
|
|
@@ -571,6 +585,15 @@ def _validate_legacy_review_recovery(root: Path, request: dict[str, Any]) -> Non
|
|
|
571
585
|
# Recovery validates the retained snapshot; it neither recreates absent
|
|
572
586
|
# evidence files nor upgrades the legacy prompt into launchable authority.
|
|
573
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
|
|
574
597
|
if not isinstance(request.get("launch_envelope"), dict):
|
|
575
598
|
raise AuthorityError("INVALID_AUTHORITY_STATE", "legacy launch envelope is malformed")
|
|
576
599
|
if request.get("host", "codex") == "claude":
|
|
@@ -1097,11 +1120,17 @@ def _dispatch_prompt(request: dict[str, Any], *, legacy_inline: bool = False) ->
|
|
|
1097
1120
|
"Do not pass if the evidence is missing, unreadable or inconsistent. The locator "
|
|
1098
1121
|
"is a reference to the complete immutable evidence, not replacement authority.\n"
|
|
1099
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
|
+
)
|
|
1100
1129
|
return (
|
|
1101
1130
|
f"[CODEARBITER_AUTHORITY_REQUEST:{request['request_id']}]\n"
|
|
1102
1131
|
f"Target repository: {request['repository']['path']}\n"
|
|
1103
1132
|
f"Frozen context: {request['context_ref']} sha256={request['context_sha256']}\n"
|
|
1104
|
-
+ target
|
|
1133
|
+
+ target + profile
|
|
1105
1134
|
+ "Act as a fresh read-only independent codeArbiter reviewer. Do not edit files or "
|
|
1106
1135
|
"delegate. Review only the frozen target and return exactly one JSON object using "
|
|
1107
1136
|
f"format {DECISION_FORMAT}. Bind request_id={request['request_id']}, "
|
|
@@ -1126,6 +1155,7 @@ def arm_request(
|
|
|
1126
1155
|
workspace_roots: dict[str, str | Path] | None = None,
|
|
1127
1156
|
host: str = "codex",
|
|
1128
1157
|
reviewer_model: str = "opus",
|
|
1158
|
+
codex_review_profile: str | None = None,
|
|
1129
1159
|
**unexpected: Any,
|
|
1130
1160
|
) -> dict[str, Any]:
|
|
1131
1161
|
if unexpected:
|
|
@@ -1139,6 +1169,10 @@ def arm_request(
|
|
|
1139
1169
|
raise AuthorityError("INVALID_AUTHORITY_REQUEST", "host is unsupported")
|
|
1140
1170
|
if host == "claude" and reviewer_model not in CLAUDE_REVIEWER_MODELS:
|
|
1141
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")
|
|
1142
1176
|
root = _real_root(root)
|
|
1143
1177
|
if host == "claude" and activity in REVIEW_ACTIVITIES:
|
|
1144
1178
|
_refuse_shadowed_reviewer(root)
|
|
@@ -1164,6 +1198,8 @@ def arm_request(
|
|
|
1164
1198
|
}
|
|
1165
1199
|
if host != "codex":
|
|
1166
1200
|
seed_value["host"] = host
|
|
1201
|
+
if codex_review_profile is not None:
|
|
1202
|
+
seed_value["codex_review_profile"] = CODEX_NATIVE_V1
|
|
1167
1203
|
seed = _canonical(seed_value)
|
|
1168
1204
|
request_id = _digest(seed)
|
|
1169
1205
|
request = {
|
|
@@ -1187,6 +1223,8 @@ def arm_request(
|
|
|
1187
1223
|
}
|
|
1188
1224
|
if host != "codex":
|
|
1189
1225
|
request["host"] = host
|
|
1226
|
+
if codex_review_profile is not None:
|
|
1227
|
+
request["codex_review_profile"] = CODEX_NATIVE_V1
|
|
1190
1228
|
if activity in REVIEW_ACTIVITIES:
|
|
1191
1229
|
request["review_contract_sha256"], request["required_coverage"] = _review_binding(context)
|
|
1192
1230
|
request["dispatch_prompt"] = _dispatch_prompt(request)
|
|
@@ -1197,6 +1235,10 @@ def arm_request(
|
|
|
1197
1235
|
"subagent_type": CLAUDE_REVIEWER,
|
|
1198
1236
|
"model": reviewer_model,
|
|
1199
1237
|
}
|
|
1238
|
+
elif codex_review_profile is not None:
|
|
1239
|
+
request["launch_envelope"] = {
|
|
1240
|
+
"message": request["dispatch_prompt"], "fork_context": False,
|
|
1241
|
+
}
|
|
1200
1242
|
else:
|
|
1201
1243
|
request["launch_envelope"] = {
|
|
1202
1244
|
"message": request["dispatch_prompt"],
|
|
@@ -1215,6 +1257,8 @@ def arm_request(
|
|
|
1215
1257
|
result["review_contract_sha256"] = request["review_contract_sha256"]
|
|
1216
1258
|
result["required_coverage"] = request["required_coverage"]
|
|
1217
1259
|
result["launch_envelope"] = request["launch_envelope"]
|
|
1260
|
+
if codex_review_profile is not None:
|
|
1261
|
+
result["codex_review_profile"] = CODEX_NATIVE_V1
|
|
1218
1262
|
return result
|
|
1219
1263
|
|
|
1220
1264
|
|
|
@@ -2041,8 +2085,8 @@ def _parse_decision(raw: Any, request: dict[str, Any]) -> dict[str, Any]:
|
|
|
2041
2085
|
if not isinstance(raw, str) or len(raw.encode("utf-8")) > MAX_DECISION:
|
|
2042
2086
|
raise AuthorityError("INVALID_REVIEW_DECISION", "review decision is absent or oversized")
|
|
2043
2087
|
try:
|
|
2044
|
-
value = json.loads(raw)
|
|
2045
|
-
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:
|
|
2046
2090
|
raise AuthorityError("INVALID_REVIEW_DECISION", "review decision is not JSON") from exc
|
|
2047
2091
|
expected = {
|
|
2048
2092
|
"format", "request_id", "target_sha256", "contract_sha256", "decision",
|
|
@@ -2060,6 +2104,7 @@ def _parse_decision(raw: Any, request: dict[str, Any]) -> dict[str, Any]:
|
|
|
2060
2104
|
or not isinstance(value.get("assessment"), str)
|
|
2061
2105
|
or not value["assessment"].strip()
|
|
2062
2106
|
or not isinstance(value.get("coverage"), list)
|
|
2107
|
+
or any(not isinstance(item, str) or not item for item in value["coverage"])
|
|
2063
2108
|
or len(set(value["coverage"])) != len(value["coverage"])
|
|
2064
2109
|
or not isinstance(value.get("findings"), list)
|
|
2065
2110
|
):
|
|
@@ -2081,13 +2126,329 @@ def _parse_decision(raw: Any, request: dict[str, Any]) -> dict[str, Any]:
|
|
|
2081
2126
|
return value
|
|
2082
2127
|
|
|
2083
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
|
+
|
|
2084
2423
|
def observe_codex_hook(root: str | Path, event: dict[str, Any]) -> dict[str, Any]:
|
|
2085
2424
|
_real_root(root) # Invocation repository is observed but cannot select authority.
|
|
2086
2425
|
if not isinstance(event, dict):
|
|
2087
2426
|
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "hook event is malformed")
|
|
2088
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
|
|
2089
2433
|
session_id = _host_id(event.get("session_id"), "session_id")
|
|
2090
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
|
+
|
|
2091
2452
|
if name == "PreToolUse":
|
|
2092
2453
|
if event.get("tool_name") != "spawn_agent":
|
|
2093
2454
|
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "only spawn_agent can launch a reviewer")
|
|
@@ -2109,7 +2470,7 @@ def observe_codex_hook(root: str | Path, event: dict[str, Any]) -> dict[str, Any
|
|
|
2109
2470
|
or request["state"] != "ARMED"
|
|
2110
2471
|
):
|
|
2111
2472
|
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "review request is not launchable")
|
|
2112
|
-
if tool_input != request.get("launch_envelope"):
|
|
2473
|
+
if _canonical(tool_input) != _canonical(request.get("launch_envelope")):
|
|
2113
2474
|
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "review launch envelope was changed")
|
|
2114
2475
|
_require_frozen_context(root, request)
|
|
2115
2476
|
if _ordinary_spawn_marker(session_id, turn_id).exists():
|
|
@@ -2127,9 +2488,14 @@ def observe_codex_hook(root: str | Path, event: dict[str, Any]) -> dict[str, Any
|
|
|
2127
2488
|
"parent_session_id": session_id, "parent_turn_id": turn_id,
|
|
2128
2489
|
"tool_use_id": tool_use_id, "post_confirmed": False,
|
|
2129
2490
|
"agent_id": None, "agent_type": None,
|
|
2130
|
-
"task_name": tool_input["task_name"],
|
|
2131
|
-
"fork_turns": tool_input["fork_turns"],
|
|
2132
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"])
|
|
2133
2499
|
request["state"] = "LAUNCHING"
|
|
2134
2500
|
_save(root, request)
|
|
2135
2501
|
return {"request_id": request_id, "state": "LAUNCHING"}
|
|
@@ -2139,12 +2505,8 @@ def observe_codex_hook(root: str | Path, event: dict[str, Any]) -> dict[str, Any
|
|
|
2139
2505
|
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "post-tool event is not reviewer launch")
|
|
2140
2506
|
tool_use_id = _host_id(event.get("tool_use_id"), "tool_use_id")
|
|
2141
2507
|
turn_id = _host_id(event.get("turn_id"), "turn_id")
|
|
2142
|
-
response = event.get("tool_response")
|
|
2143
|
-
if not isinstance(response, dict):
|
|
2144
|
-
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "spawn result is malformed")
|
|
2145
|
-
task_name = _host_id(response.get("task_name"), "task_name")
|
|
2146
2508
|
matches = []
|
|
2147
|
-
for candidate_root, candidate in _registered_requests():
|
|
2509
|
+
for candidate_root, candidate in _registered_requests(strict=True):
|
|
2148
2510
|
launch = candidate.get("launch") or {}
|
|
2149
2511
|
if (
|
|
2150
2512
|
candidate["state"] in {"LAUNCHING", "RUNNING"}
|
|
@@ -2158,6 +2520,12 @@ def observe_codex_hook(root: str | Path, event: dict[str, Any]) -> dict[str, Any
|
|
|
2158
2520
|
if len(matches) != 1:
|
|
2159
2521
|
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "review launch correlation is ambiguous")
|
|
2160
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")
|
|
2161
2529
|
if task_name != request["launch"].get("task_name"):
|
|
2162
2530
|
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "spawn result task_name differs")
|
|
2163
2531
|
request["launch"]["post_confirmed"] = True
|
|
@@ -2170,6 +2538,8 @@ def observe_codex_hook(root: str | Path, event: dict[str, Any]) -> dict[str, Any
|
|
|
2170
2538
|
matches = []
|
|
2171
2539
|
same_turn = []
|
|
2172
2540
|
for candidate_root, candidate in _registered_requests():
|
|
2541
|
+
if candidate.get("codex_review_profile") == CODEX_NATIVE_V1:
|
|
2542
|
+
continue
|
|
2173
2543
|
launch = candidate.get("launch") or {}
|
|
2174
2544
|
if (
|
|
2175
2545
|
launch.get("parent_session_id") == session_id
|
|
@@ -2215,26 +2585,7 @@ def observe_codex_hook(root: str | Path, event: dict[str, Any]) -> dict[str, Any
|
|
|
2215
2585
|
request["state"] = "REJECTED"
|
|
2216
2586
|
_save(root, request)
|
|
2217
2587
|
raise
|
|
2218
|
-
|
|
2219
|
-
"input_sha256": request["context"]["input_sha256"],
|
|
2220
|
-
"spec_sha256": request["context"]["spec_sha256"],
|
|
2221
|
-
"assessment": decision["assessment"],
|
|
2222
|
-
}
|
|
2223
|
-
if request["activity"] == "spec_review":
|
|
2224
|
-
payload["task_sha256"] = request["context"]["task_sha256"]
|
|
2225
|
-
else:
|
|
2226
|
-
payload["base_input_sha256"] = request["context"]["base_input_sha256"]
|
|
2227
|
-
payload["task_hashes"] = request["context"]["task_hashes"]
|
|
2228
|
-
producer_result = {"launch": request["launch"], "decision": decision}
|
|
2229
|
-
observation = _closed_observation(
|
|
2230
|
-
request, payload, CODEX_REVIEW_PROFILE, agent_id,
|
|
2231
|
-
producer_result,
|
|
2232
|
-
)
|
|
2233
|
-
_observation(root, request, observation)
|
|
2234
|
-
request["payload"] = payload
|
|
2235
|
-
request["state"] = "COMPLETED"
|
|
2236
|
-
_save(root, request)
|
|
2237
|
-
return {"request_id": request["request_id"], "state": "COMPLETED"}
|
|
2588
|
+
return _codex_review_complete(root, request, agent_id, decision)
|
|
2238
2589
|
|
|
2239
2590
|
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "hook event is not supported")
|
|
2240
2591
|
|
|
@@ -345,7 +345,9 @@ def _installed_workflow_preflight(installation: Path) -> dict[str, object]:
|
|
|
345
345
|
|
|
346
346
|
required = [("UserPromptSubmit", None, "prompt-submit.py")]
|
|
347
347
|
tools = ("Bash", "Agent") if host == "claude" else (
|
|
348
|
-
"Bash", "shell_command", "exec_command", "unified_exec", "spawn_agent"
|
|
348
|
+
"Bash", "shell_command", "exec_command", "unified_exec", "spawn_agent",
|
|
349
|
+
"collaborationspawn_agent", "multi_agent_v1send_input", "multi_agent_v1resume_agent",
|
|
350
|
+
"multi_agent_v1close_agent")
|
|
349
351
|
required += [(event, tool, "artifact-authority-hook.py")
|
|
350
352
|
for event in ("PreToolUse", "PostToolUse") for tool in tools]
|
|
351
353
|
required += [(event, None, "artifact-authority-hook.py")
|
|
@@ -11,7 +11,7 @@ import hostapi # noqa: E402
|
|
|
11
11
|
class PiHost(hostapi.Host):
|
|
12
12
|
name = "pi"
|
|
13
13
|
adapter_name = "@arbiterforge/ca-pi"
|
|
14
|
-
adapter_version = "0.15.
|
|
14
|
+
adapter_version = "0.15.3"
|
|
15
15
|
update_target = "ca-pi"
|
|
16
16
|
update_tag_prefix = "ca-pi-v"
|
|
17
17
|
update_command = "pi update npm:@arbiterforge/ca-pi"
|
|
@@ -25,7 +25,7 @@ def main() -> int:
|
|
|
25
25
|
tool = payload.get("tool_name")
|
|
26
26
|
root = _hooklib.project_root(payload)
|
|
27
27
|
try:
|
|
28
|
-
if event in {"PreToolUse", "PostToolUse"} and tool
|
|
28
|
+
if event in {"PreToolUse", "PostToolUse"} and tool not in _artifactauthoritylib.CODEX_REVIEW_TOOLS:
|
|
29
29
|
result = _artifactauthoritylib.observe_verifier_hook(root, payload)
|
|
30
30
|
if result is None:
|
|
31
31
|
return 0
|
|
@@ -40,26 +40,9 @@ def main() -> int:
|
|
|
40
40
|
else:
|
|
41
41
|
sys.stderr.write(reason + "\n")
|
|
42
42
|
return 0
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
"reason": (
|
|
47
|
-
"codeArbiter authorized this wrapper for the exact resolved child set: "
|
|
48
|
-
+ json.dumps(result["command_bindings"], sort_keys=True)
|
|
49
|
-
),
|
|
50
|
-
}))
|
|
51
|
-
if event == "SubagentStop" and result.get("state") == "COMPLETED":
|
|
52
|
-
print(json.dumps({
|
|
53
|
-
"continue": True,
|
|
54
|
-
"hookSpecificOutput": {
|
|
55
|
-
"hookEventName": "SubagentStop",
|
|
56
|
-
"additionalContext": (
|
|
57
|
-
"codeArbiter: independent review observation recorded for request "
|
|
58
|
-
+ result["request_id"]
|
|
59
|
-
+ "; publish it through the production authority adapter."
|
|
60
|
-
),
|
|
61
|
-
},
|
|
62
|
-
}))
|
|
43
|
+
# Saved observations are the result. Success is silent: Codex's closed
|
|
44
|
+
# SubagentStop schema has no additionalContext, and an explicit approval
|
|
45
|
+
# would unnecessarily override the host's normal permission processing.
|
|
63
46
|
return 0
|
|
64
47
|
|
|
65
48
|
|
|
@@ -46,6 +46,10 @@ def main(argv=None) -> int:
|
|
|
46
46
|
"--reviewer-model", choices=sorted(_artifactauthoritylib.CLAUDE_REVIEWER_MODELS),
|
|
47
47
|
default="opus", help="Claude reviewer model pinned into the launch envelope",
|
|
48
48
|
)
|
|
49
|
+
arm.add_argument(
|
|
50
|
+
"--codex-review-profile", choices=("native-v1",), default=None,
|
|
51
|
+
help="select only after qualifying the actual registered native V1 UUID interface",
|
|
52
|
+
)
|
|
49
53
|
verify = sub.add_parser("verify")
|
|
50
54
|
verify.add_argument("--root", required=True)
|
|
51
55
|
verify.add_argument("--request-id", required=True)
|
|
@@ -68,10 +72,17 @@ def main(argv=None) -> int:
|
|
|
68
72
|
raise _artifactauthoritylib.AuthorityError(
|
|
69
73
|
"UNSUPPORTED_HOST_SEAM", f"host {host} has no verification or review authority"
|
|
70
74
|
)
|
|
75
|
+
if (host == "codex" and args.activity in _artifactauthoritylib.REVIEW_ACTIVITIES
|
|
76
|
+
and args.codex_review_profile is None):
|
|
77
|
+
raise _artifactauthoritylib.AuthorityError(
|
|
78
|
+
"UNSUPPORTED_HOST_SEAM",
|
|
79
|
+
"Codex review requires explicit --codex-review-profile native-v1 after live interface qualification",
|
|
80
|
+
)
|
|
71
81
|
result = _artifactauthoritylib.arm_request(
|
|
72
82
|
root, client, args.artifact_id, args.record_id, args.activity,
|
|
73
83
|
workspace_roots=workspaces or None, host=host,
|
|
74
84
|
reviewer_model=args.reviewer_model,
|
|
85
|
+
codex_review_profile=args.codex_review_profile,
|
|
75
86
|
)
|
|
76
87
|
if host == "claude" and args.activity == "verification":
|
|
77
88
|
# The Claude verifier hook pins this exact shipped script and
|