@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.
- package/README.md +2 -2
- package/package.json +1 -1
- package/plugins/ca-pi/CHANGELOG.md +71 -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 +618 -58
- 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/artifacts.md +20 -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
|
@@ -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
|
-
|
|
258
|
-
|
|
341
|
+
expected = value.get("integrity_sha256")
|
|
342
|
+
updated = dict(value)
|
|
343
|
+
updated["integrity_sha256"] = _integrity(updated)
|
|
259
344
|
try:
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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(
|
|
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 >
|
|
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(
|
|
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
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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
|
|
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
|
-
|
|
905
|
-
+
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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"] = {
|