@arbiterforge/ca-pi 0.17.4 → 0.17.6
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 +1 -1
- package/package.json +1 -1
- package/plugins/ca-pi/CHANGELOG.md +21 -0
- package/plugins/ca-pi/helpers/artifacts/ca-artifact-darwin-amd64 +0 -0
- package/plugins/ca-pi/helpers/artifacts/ca-artifact-darwin-arm64 +0 -0
- package/plugins/ca-pi/helpers/artifacts/ca-artifact-linux-amd64 +0 -0
- package/plugins/ca-pi/helpers/artifacts/ca-artifact-linux-arm64 +0 -0
- package/plugins/ca-pi/helpers/artifacts/ca-artifact-windows-amd64.exe +0 -0
- package/plugins/ca-pi/helpers/artifacts/ca-artifact-windows-arm64.exe +0 -0
- package/plugins/ca-pi/helpers/artifacts/release.json +36 -36
- package/plugins/ca-pi/hooks/_artifactauthoritylib.py +469 -33
- package/plugins/ca-pi/hooks/_artifactlib.py +2 -1
- package/plugins/ca-pi/hooks/_host.py +1 -1
- package/plugins/ca-pi/hooks/artifact-authority.py +5 -4
- package/plugins/ca-pi/hooks/hostapi.py +1 -1
- package/plugins/ca-pi/includes/artifacts.md +22 -7
- package/plugins/ca-pi/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ and durable project context. You decide. codeArbiter enforces.
|
|
|
10
10
|
<img alt="Claude Code plugin" src="https://img.shields.io/badge/Claude_Code-plugin-d97757">
|
|
11
11
|
<img alt="Codex plugin" src="https://img.shields.io/badge/OpenAI_Codex-plugin-10a37f">
|
|
12
12
|
<img alt="Pi Feature Forge preview" src="https://img.shields.io/badge/ca--pi-Feature_Forge_preview-d97757">
|
|
13
|
-
<img alt="version 2.25.
|
|
13
|
+
<img alt="version 2.25.5" src="https://img.shields.io/badge/version-2.25.5-2b7489">
|
|
14
14
|
<img alt="core lanes" src="https://img.shields.io/badge/core_lanes-18-555">
|
|
15
15
|
<img alt="skills" src="https://img.shields.io/badge/skills-22-555">
|
|
16
16
|
<img alt="agents" src="https://img.shields.io/badge/agents-20-555">
|
package/package.json
CHANGED
|
@@ -4,6 +4,27 @@ All notable changes to `ca-pi` are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.17.6] - 2026-10-10
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Preserve the shared engine's Codex native V2 review bindings and bounded
|
|
12
|
+
verification context storage. Pi verification and review authority remain
|
|
13
|
+
unsupported; this shared payload update does not enable those host seams.
|
|
14
|
+
|
|
15
|
+
## [0.17.5] - 2026-10-10
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- Preserve the shared adapter's bounded native completion-review context
|
|
20
|
+
references and complete review-result prompt contract (#921).
|
|
21
|
+
- Fingerprint complete completion-review workspaces up to 1 GiB independently
|
|
22
|
+
of the 256 MiB selected verification-input budget. Preserve full content
|
|
23
|
+
hashing, freshness checks, and the 32 MiB member and 50,000 entry limits.
|
|
24
|
+
- Preserve the shared engine's distinct fresh-verification completion profile
|
|
25
|
+
and historical receipt validation. Pi verification and review authority remain
|
|
26
|
+
unsupported; this shared payload change does not enable those host seams.
|
|
27
|
+
|
|
7
28
|
## [0.17.4] - 2026-10-09
|
|
8
29
|
|
|
9
30
|
### Changed
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -6,43 +6,43 @@
|
|
|
6
6
|
"binaries": {
|
|
7
7
|
"darwin/amd64": {
|
|
8
8
|
"file": "ca-artifact-darwin-amd64",
|
|
9
|
-
"sha256": "
|
|
9
|
+
"sha256": "7b9e95dba78bda2962d1b879c373a835466fe59aa082a3afb6efbc1fb3de454d",
|
|
10
10
|
"native_tested": true
|
|
11
11
|
},
|
|
12
12
|
"darwin/arm64": {
|
|
13
13
|
"file": "ca-artifact-darwin-arm64",
|
|
14
|
-
"sha256": "
|
|
14
|
+
"sha256": "2469bd38df4e241f19bb443c4388283aaadc0638951a77331b25be81929d4f4c",
|
|
15
15
|
"native_tested": true
|
|
16
16
|
},
|
|
17
17
|
"linux/amd64": {
|
|
18
18
|
"file": "ca-artifact-linux-amd64",
|
|
19
|
-
"sha256": "
|
|
19
|
+
"sha256": "cf4116c08afa66426c3b9d78853dfc09b608e05412054b6fe18817a65db96a76",
|
|
20
20
|
"native_tested": true
|
|
21
21
|
},
|
|
22
22
|
"linux/arm64": {
|
|
23
23
|
"file": "ca-artifact-linux-arm64",
|
|
24
|
-
"sha256": "
|
|
24
|
+
"sha256": "b1e92f0d279fe04b61dd287a4b20fc1db4abc3722c0325f5294a55bb1a972895",
|
|
25
25
|
"native_tested": true
|
|
26
26
|
},
|
|
27
27
|
"windows/amd64": {
|
|
28
28
|
"file": "ca-artifact-windows-amd64.exe",
|
|
29
|
-
"sha256": "
|
|
29
|
+
"sha256": "d8fa783dc8d30c5d48a8c336bca8ee5fd85be4c72629d7ac7246842b89c5135c",
|
|
30
30
|
"native_tested": true
|
|
31
31
|
},
|
|
32
32
|
"windows/arm64": {
|
|
33
33
|
"file": "ca-artifact-windows-arm64.exe",
|
|
34
|
-
"sha256": "
|
|
34
|
+
"sha256": "2df30c81eb58d502aa9a146563b9dd725176cac9f907ebf36b73c157e5de2124",
|
|
35
35
|
"native_tested": true
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
38
|
"context_qualification": {
|
|
39
39
|
"darwin/amd64": {
|
|
40
|
-
"source_commit": "
|
|
40
|
+
"source_commit": "7ef1feead876e08e9ad63d5e507a01650488719f",
|
|
41
41
|
"workflow": ".github/workflows/ci.yml",
|
|
42
|
-
"run_id": "
|
|
43
|
-
"binary_sha256": "
|
|
44
|
-
"observation_sha256": "
|
|
45
|
-
"module_sha256": "
|
|
42
|
+
"run_id": "38042731230",
|
|
43
|
+
"binary_sha256": "7b9e95dba78bda2962d1b879c373a835466fe59aa082a3afb6efbc1fb3de454d",
|
|
44
|
+
"observation_sha256": "c359781d36118baf2ebe6b73bb0ecefb59f4ca500b31a6f7e5baf52474e1127c",
|
|
45
|
+
"module_sha256": "fb635481a9bf9a88dbe052c2d738c28c1e3d2a6d741514fc28ac51b6fc8a47b4",
|
|
46
46
|
"native_cases": [
|
|
47
47
|
"TestContextBoundedRender",
|
|
48
48
|
"TestContextDocumentContract",
|
|
@@ -53,12 +53,12 @@
|
|
|
53
53
|
]
|
|
54
54
|
},
|
|
55
55
|
"darwin/arm64": {
|
|
56
|
-
"source_commit": "
|
|
56
|
+
"source_commit": "7ef1feead876e08e9ad63d5e507a01650488719f",
|
|
57
57
|
"workflow": ".github/workflows/ci.yml",
|
|
58
|
-
"run_id": "
|
|
59
|
-
"binary_sha256": "
|
|
60
|
-
"observation_sha256": "
|
|
61
|
-
"module_sha256": "
|
|
58
|
+
"run_id": "38042731230",
|
|
59
|
+
"binary_sha256": "2469bd38df4e241f19bb443c4388283aaadc0638951a77331b25be81929d4f4c",
|
|
60
|
+
"observation_sha256": "2d4c75ec50210c0bbf4b8c9a754a0fd89ee81dc60c9607066cf17c4e1af2050a",
|
|
61
|
+
"module_sha256": "fb635481a9bf9a88dbe052c2d738c28c1e3d2a6d741514fc28ac51b6fc8a47b4",
|
|
62
62
|
"native_cases": [
|
|
63
63
|
"TestContextBoundedRender",
|
|
64
64
|
"TestContextDocumentContract",
|
|
@@ -69,12 +69,12 @@
|
|
|
69
69
|
]
|
|
70
70
|
},
|
|
71
71
|
"linux/amd64": {
|
|
72
|
-
"source_commit": "
|
|
72
|
+
"source_commit": "7ef1feead876e08e9ad63d5e507a01650488719f",
|
|
73
73
|
"workflow": ".github/workflows/ci.yml",
|
|
74
|
-
"run_id": "
|
|
75
|
-
"binary_sha256": "
|
|
76
|
-
"observation_sha256": "
|
|
77
|
-
"module_sha256": "
|
|
74
|
+
"run_id": "38042731230",
|
|
75
|
+
"binary_sha256": "cf4116c08afa66426c3b9d78853dfc09b608e05412054b6fe18817a65db96a76",
|
|
76
|
+
"observation_sha256": "0a57cde8e4f251ee5faf2cd2dc256638622464030cda79988c11295dec6dfec9",
|
|
77
|
+
"module_sha256": "fb635481a9bf9a88dbe052c2d738c28c1e3d2a6d741514fc28ac51b6fc8a47b4",
|
|
78
78
|
"native_cases": [
|
|
79
79
|
"TestContextBoundedRender",
|
|
80
80
|
"TestContextDocumentContract",
|
|
@@ -85,12 +85,12 @@
|
|
|
85
85
|
]
|
|
86
86
|
},
|
|
87
87
|
"linux/arm64": {
|
|
88
|
-
"source_commit": "
|
|
88
|
+
"source_commit": "7ef1feead876e08e9ad63d5e507a01650488719f",
|
|
89
89
|
"workflow": ".github/workflows/ci.yml",
|
|
90
|
-
"run_id": "
|
|
91
|
-
"binary_sha256": "
|
|
92
|
-
"observation_sha256": "
|
|
93
|
-
"module_sha256": "
|
|
90
|
+
"run_id": "38042731230",
|
|
91
|
+
"binary_sha256": "b1e92f0d279fe04b61dd287a4b20fc1db4abc3722c0325f5294a55bb1a972895",
|
|
92
|
+
"observation_sha256": "926d97ef85d2cd9caa658db620c7f135ea5a07873a188f289181a37ae636921d",
|
|
93
|
+
"module_sha256": "fb635481a9bf9a88dbe052c2d738c28c1e3d2a6d741514fc28ac51b6fc8a47b4",
|
|
94
94
|
"native_cases": [
|
|
95
95
|
"TestContextBoundedRender",
|
|
96
96
|
"TestContextDocumentContract",
|
|
@@ -101,12 +101,12 @@
|
|
|
101
101
|
]
|
|
102
102
|
},
|
|
103
103
|
"windows/amd64": {
|
|
104
|
-
"source_commit": "
|
|
104
|
+
"source_commit": "7ef1feead876e08e9ad63d5e507a01650488719f",
|
|
105
105
|
"workflow": ".github/workflows/ci.yml",
|
|
106
|
-
"run_id": "
|
|
107
|
-
"binary_sha256": "
|
|
108
|
-
"observation_sha256": "
|
|
109
|
-
"module_sha256": "
|
|
106
|
+
"run_id": "38042731230",
|
|
107
|
+
"binary_sha256": "d8fa783dc8d30c5d48a8c336bca8ee5fd85be4c72629d7ac7246842b89c5135c",
|
|
108
|
+
"observation_sha256": "70827706a7d231df640f9d4b27b4250f81311bfd89bee11b65ff48b985c1085c",
|
|
109
|
+
"module_sha256": "fb635481a9bf9a88dbe052c2d738c28c1e3d2a6d741514fc28ac51b6fc8a47b4",
|
|
110
110
|
"native_cases": [
|
|
111
111
|
"TestContextBoundedRender",
|
|
112
112
|
"TestContextDocumentContract",
|
|
@@ -117,12 +117,12 @@
|
|
|
117
117
|
]
|
|
118
118
|
},
|
|
119
119
|
"windows/arm64": {
|
|
120
|
-
"source_commit": "
|
|
120
|
+
"source_commit": "7ef1feead876e08e9ad63d5e507a01650488719f",
|
|
121
121
|
"workflow": ".github/workflows/ci.yml",
|
|
122
|
-
"run_id": "
|
|
123
|
-
"binary_sha256": "
|
|
124
|
-
"observation_sha256": "
|
|
125
|
-
"module_sha256": "
|
|
122
|
+
"run_id": "38042731230",
|
|
123
|
+
"binary_sha256": "2df30c81eb58d502aa9a146563b9dd725176cac9f907ebf36b73c157e5de2124",
|
|
124
|
+
"observation_sha256": "f15d9d9894d0ae845e14c3bfba1cf1528f1debe166850e9fb0b46aee27d99dbd",
|
|
125
|
+
"module_sha256": "fb635481a9bf9a88dbe052c2d738c28c1e3d2a6d741514fc28ac51b6fc8a47b4",
|
|
126
126
|
"native_cases": [
|
|
127
127
|
"TestContextBoundedRender",
|
|
128
128
|
"TestContextDocumentContract",
|
|
@@ -82,6 +82,18 @@ REQUEST_LOCK_WAIT_SECONDS = 5.0
|
|
|
82
82
|
CLAUDE_JOIN_LOCK_WAIT_SECONDS = 30.0
|
|
83
83
|
MAX_OUTPUT = 8 << 20
|
|
84
84
|
MAX_DECISION = 65536
|
|
85
|
+
# Verification and native completion reviews retain the complete immutable context by
|
|
86
|
+
# exact reference instead of duplicating it in every mutable request state.
|
|
87
|
+
CONTEXT_STORAGE = "frozen-reference/0.1.0"
|
|
88
|
+
CONTEXT_REFERENCE = {"format": "codearbiter.evidence-context-reference/0.1.0"}
|
|
89
|
+
# The native canonical decoder already bounds each complete context at 8 MiB.
|
|
90
|
+
MAX_EVIDENCE_CONTEXT = 8 << 20
|
|
91
|
+
# Frozen evidence reads can outlast the ordinary client's 30-second budget.
|
|
92
|
+
EVIDENCE_ENGINE_TIMEOUT_SECONDS = 120
|
|
93
|
+
# ASCII canonical JSON can expand each input UTF-8 byte to at most six bytes.
|
|
94
|
+
# Reserve that closed decision budget plus 32 bounded 256-byte lifecycle slots
|
|
95
|
+
# (host identities, content-addressed locators, their keys and delimiters).
|
|
96
|
+
REVIEW_RESULT_RESERVE = 6 * MAX_DECISION + 32 * 256
|
|
85
97
|
MAX_COMPLETION_FILES = 16
|
|
86
98
|
MAX_COMPLETION_FILE_BYTES = 64 << 10
|
|
87
99
|
MAX_COMPLETION_TOTAL_BYTES = 256 << 10
|
|
@@ -104,9 +116,13 @@ CLAUDE_REVIEWER_MODELS = frozenset({"opus", "sonnet", "haiku"})
|
|
|
104
116
|
CLAUDE_REVIEW_PROFILE = "claude-review/0.1.0"
|
|
105
117
|
CODEX_REVIEW_PROFILE = "codex-review/0.1.0"
|
|
106
118
|
CODEX_NATIVE_V1 = "codex-native-v1/0.145.0"
|
|
119
|
+
CODEX_NATIVE_V2 = "codex-native-v2/0.162.0-alpha.2"
|
|
120
|
+
CODEX_NATIVE_PROFILES = frozenset({CODEX_NATIVE_V1, CODEX_NATIVE_V2})
|
|
107
121
|
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}")
|
|
122
|
+
CODEX_AGENT_PATH_RE = re.compile(r"/root(?:/[a-z0-9_]+)*")
|
|
108
123
|
CODEX_STEERING_TOOLS = frozenset({
|
|
109
124
|
"multi_agent_v1send_input", "multi_agent_v1resume_agent", "multi_agent_v1close_agent",
|
|
125
|
+
"collaborationsend_message", "collaborationfollowup_task", "collaborationinterrupt_agent",
|
|
110
126
|
})
|
|
111
127
|
CODEX_REVIEW_TOOLS = frozenset({"spawn_agent", "collaborationspawn_agent"}) | CODEX_STEERING_TOOLS
|
|
112
128
|
CODEX_CHILD_FORMAT = "codearbiter.codex-native-child/0.1.0"
|
|
@@ -404,9 +420,129 @@ def _request_lock(spool: Path, relative: Path, wait: float | None = None):
|
|
|
404
420
|
# Do not unlink: a peer may already be waiting on this same inode.
|
|
405
421
|
|
|
406
422
|
|
|
423
|
+
def _referenced_review_state(value: dict[str, Any]) -> dict[str, Any]:
|
|
424
|
+
if (value.get("context_storage") != CONTEXT_STORAGE
|
|
425
|
+
or value.get("codex_review_profile") not in CODEX_NATIVE_PROFILES
|
|
426
|
+
or value.get("host", "codex") != "codex"
|
|
427
|
+
or value.get("activity") not in REVIEW_ACTIVITIES
|
|
428
|
+
or "completion" not in value["context"] or "handback" in value):
|
|
429
|
+
raise AuthorityError("INVALID_AUTHORITY_STATE", "context reference is not a native completion review")
|
|
430
|
+
base = dict(value, state="ARMED", launch=None, observation_ref=None,
|
|
431
|
+
observation_sha256=None, receipt=None, recovery=None)
|
|
432
|
+
for field in ("payload", "authority_source"):
|
|
433
|
+
base.pop(field, None)
|
|
434
|
+
base["integrity_sha256"] = "0" * 64
|
|
435
|
+
stored_base = dict(base, context=CONTEXT_REFERENCE)
|
|
436
|
+
context = value["context"]
|
|
437
|
+
# Quality review additionally copies its exact task hashes into the result.
|
|
438
|
+
static_payload = {"input_sha256": context["input_sha256"],
|
|
439
|
+
"spec_sha256": context["spec_sha256"],
|
|
440
|
+
"completion_sha256": context["completion_sha256"]}
|
|
441
|
+
if value["activity"] == "spec_review":
|
|
442
|
+
static_payload["task_sha256"] = context["task_sha256"]
|
|
443
|
+
else:
|
|
444
|
+
static_payload.update(base_input_sha256=context["base_input_sha256"],
|
|
445
|
+
task_hashes=context["task_hashes"])
|
|
446
|
+
if len(_canonical(stored_base)) + len(_canonical(static_payload)) + REVIEW_RESULT_RESERVE > MAX_STATE:
|
|
447
|
+
raise AuthorityError("INVALID_AUTHORITY_STATE", "review result has no bounded lifecycle reserve")
|
|
448
|
+
return dict(value, context=CONTEXT_REFERENCE)
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def _valid_verification_binding_values(binding: dict[str, Any]) -> bool:
|
|
452
|
+
def path(value: Any) -> bool:
|
|
453
|
+
return isinstance(value, str) and bool(value) and "\0" not in value and Path(value).is_absolute()
|
|
454
|
+
|
|
455
|
+
def identity(value: Any) -> bool:
|
|
456
|
+
return isinstance(value, str) and re.fullmatch(r"[0-9]+:[0-9]+", value) is not None
|
|
457
|
+
|
|
458
|
+
def digest(value: Any) -> bool:
|
|
459
|
+
return isinstance(value, str) and SHA256_RE.fullmatch(value) is not None
|
|
460
|
+
|
|
461
|
+
argv, files, collector = binding["argv"], binding["launch_files"], binding["collector_profile"]
|
|
462
|
+
if (not isinstance(argv, list) or not argv
|
|
463
|
+
or any(not isinstance(item, str) or not item or "\0" in item for item in argv)
|
|
464
|
+
or not path(argv[0])
|
|
465
|
+
or not isinstance(collector, str) or collector not in COLLECTORS
|
|
466
|
+
or any(not path(binding[field]) for field in ("cwd", "workspace_root", "git_common_dir"))
|
|
467
|
+
or any(not identity(binding[field]) for field in (
|
|
468
|
+
"cwd_filesystem_id", "workspace_filesystem_id", "git_common_filesystem_id"))
|
|
469
|
+
or any(not digest(binding[field]) for field in ("definition_sha256", "executable_sha256"))
|
|
470
|
+
or not isinstance(files, list) or not files):
|
|
471
|
+
return False
|
|
472
|
+
# These are the roles emitted by _native_launch, _bind_commands and its
|
|
473
|
+
# nested npm qualifier. Validate serialization without replacing rebinding.
|
|
474
|
+
roles = {"declared-executable", "node-runtime", "npm-cli", "runner-entrypoint",
|
|
475
|
+
"npm-manifest", "npm-workspace-manifest", "npm-prefix"}
|
|
476
|
+
return all(isinstance(item, dict) and set(item) == {"path", "sha256", "filesystem_id", "role"}
|
|
477
|
+
and path(item["path"]) and digest(item["sha256"]) and identity(item["filesystem_id"])
|
|
478
|
+
and isinstance(item["role"], str) and item["role"] in roles for item in files)
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
def _referenced_verification_state(value: dict[str, Any]) -> dict[str, Any]:
|
|
482
|
+
context = value["context"]
|
|
483
|
+
subject = context.get("subject", {})
|
|
484
|
+
_validate_context(context, subject.get("artifact_id"), subject.get("record_id"), "verification")
|
|
485
|
+
binding_fields = {"definition_sha256", "argv", "collector_profile", "launch_files", "cwd",
|
|
486
|
+
"cwd_filesystem_id", "workspace_root", "workspace_filesystem_id", "git_common_dir",
|
|
487
|
+
"git_common_filesystem_id", "executable_sha256"}
|
|
488
|
+
if (value.get("context_storage") != CONTEXT_STORAGE
|
|
489
|
+
or value.get("activity") != "verification"
|
|
490
|
+
or value.get("host", "codex") not in HOSTS
|
|
491
|
+
or value.get("state") not in STATES
|
|
492
|
+
or value.get("launch") is not None
|
|
493
|
+
or any(field in value for field in (
|
|
494
|
+
"codex_review_profile", "handback", "dispatch_prompt", "review_contract_sha256",
|
|
495
|
+
"required_coverage", "launch_envelope"))
|
|
496
|
+
or not isinstance(value.get("command_bindings"), list)
|
|
497
|
+
or len(value["command_bindings"]) != len(context.get("commands", []))
|
|
498
|
+
or any(not isinstance(binding, dict)
|
|
499
|
+
or set(binding) != binding_fields
|
|
500
|
+
or not _valid_verification_binding_values(binding)
|
|
501
|
+
or binding.get("definition_sha256") != command["definition_sha256"]
|
|
502
|
+
for binding, command in zip(value["command_bindings"], context.get("commands", [])))):
|
|
503
|
+
raise AuthorityError("INVALID_AUTHORITY_STATE", "context reference is not closed verification")
|
|
504
|
+
# Reserve the exact closed result (including every required test name), a
|
|
505
|
+
# wrapper with the bound commands/workspaces, and bounded host/lifecycle IDs.
|
|
506
|
+
# Large manifests stay immutable; large mutable results must still refuse.
|
|
507
|
+
commands = [{
|
|
508
|
+
"definition_sha256": command["definition_sha256"], "exit": command["definition"]["expected_exit"],
|
|
509
|
+
"tests": [{"name": name, "status": "pass"} for name in command["definition"]["required_tests"]],
|
|
510
|
+
"stdout_sha256": "0" * 64, "stderr_sha256": "0" * 64,
|
|
511
|
+
} for command in context["commands"]]
|
|
512
|
+
workspaces = {binding["workspace_root"]: {
|
|
513
|
+
"root": binding["workspace_root"], "filesystem_id": binding["workspace_filesystem_id"],
|
|
514
|
+
"git_common_dir": binding["git_common_dir"], "git_common_filesystem_id": binding["git_common_filesystem_id"],
|
|
515
|
+
"head": "0" * 64, "status_sha256": "0" * 64, "content_sha256": "0" * 64,
|
|
516
|
+
} for binding in value["command_bindings"]}
|
|
517
|
+
base = dict(value, context=CONTEXT_REFERENCE, state="CAPTURED", attempt="0" * 32,
|
|
518
|
+
observation_ref=None, observation_sha256=None, receipt=None, recovery=None)
|
|
519
|
+
base.pop("authority_source", None)
|
|
520
|
+
base["integrity_sha256"] = "0" * 64
|
|
521
|
+
base["payload"] = {field: context[field] for field in ("input_sha256", "spec_sha256", "task_sha256")}
|
|
522
|
+
base["payload"]["commands"] = commands
|
|
523
|
+
base["wrapper"] = {
|
|
524
|
+
"state": "CORROBORATED", "session_id": "s" * 256, "tool_use_id": "t" * 256,
|
|
525
|
+
"prompt_id" if value.get("host", "codex") == "claude" else "turn_id": "p" * 256,
|
|
526
|
+
"command_bindings": value["command_bindings"], "workspace_before": list(workspaces.values()),
|
|
527
|
+
}
|
|
528
|
+
if len(_canonical(base)) + 32 * 256 > MAX_STATE:
|
|
529
|
+
raise AuthorityError("INVALID_AUTHORITY_STATE", "verification result has no bounded lifecycle reserve")
|
|
530
|
+
return dict(value, context=CONTEXT_REFERENCE)
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
def _referenced_state(value: dict[str, Any]) -> dict[str, Any]:
|
|
534
|
+
if value.get("activity") == "verification":
|
|
535
|
+
return _referenced_verification_state(value)
|
|
536
|
+
return _referenced_review_state(value)
|
|
537
|
+
|
|
538
|
+
|
|
407
539
|
def _save(root: Path, value: dict[str, Any]) -> None:
|
|
408
540
|
expected = value.get("integrity_sha256")
|
|
409
541
|
updated = dict(value)
|
|
542
|
+
if "context_storage" in updated:
|
|
543
|
+
if _read_context(root, updated["context_ref"], updated["context_sha256"]) != updated["context"]:
|
|
544
|
+
raise AuthorityError("INVALID_EVIDENCE_CONTEXT", "stored context changed")
|
|
545
|
+
updated = _referenced_state(updated)
|
|
410
546
|
updated["integrity_sha256"] = _integrity(updated)
|
|
411
547
|
legacy_abandonment = (
|
|
412
548
|
value["state"] == "ABANDONED"
|
|
@@ -468,7 +604,7 @@ def _load(
|
|
|
468
604
|
"authority_source", "dispatch_prompt", "review_contract_sha256",
|
|
469
605
|
"required_coverage", "wrapper", "command_bindings", "recovery",
|
|
470
606
|
"launch_envelope", "workspace_roots", "host", "codex_review_profile",
|
|
471
|
-
"handback",
|
|
607
|
+
"handback", "context_storage", "codex_native_trace",
|
|
472
608
|
}
|
|
473
609
|
if (
|
|
474
610
|
not isinstance(value, dict)
|
|
@@ -491,11 +627,21 @@ def _load(
|
|
|
491
627
|
):
|
|
492
628
|
raise AuthorityError("INVALID_AUTHORITY_STATE", "request state failed validation")
|
|
493
629
|
if "codex_review_profile" in value and (
|
|
494
|
-
value["codex_review_profile"]
|
|
630
|
+
value["codex_review_profile"] not in CODEX_NATIVE_PROFILES
|
|
495
631
|
or value.get("host", "codex") != "codex"
|
|
496
632
|
or value["activity"] not in REVIEW_ACTIVITIES
|
|
497
633
|
):
|
|
498
634
|
raise AuthorityError("INVALID_AUTHORITY_STATE", "Codex review profile is unsupported")
|
|
635
|
+
if "context_storage" in value:
|
|
636
|
+
if value["context"] != CONTEXT_REFERENCE:
|
|
637
|
+
raise AuthorityError("INVALID_AUTHORITY_STATE", "stored context reference is malformed")
|
|
638
|
+
value["context"] = _read_context(root, value["context_ref"], value["context_sha256"])
|
|
639
|
+
subject = value["context"].get("subject", {})
|
|
640
|
+
_validate_context(value["context"], subject.get("artifact_id"),
|
|
641
|
+
subject.get("record_id"), value["activity"])
|
|
642
|
+
_referenced_state(value)
|
|
643
|
+
elif value["context"] == CONTEXT_REFERENCE:
|
|
644
|
+
raise AuthorityError("INVALID_AUTHORITY_STATE", "context reference lacks its closed storage profile")
|
|
499
645
|
if len(raw) > MAX_STATE:
|
|
500
646
|
if recover_armed_review and value["state"] == "ABANDONED":
|
|
501
647
|
# Only an abandonment can have retained this; recovering it again is
|
|
@@ -526,15 +672,25 @@ def _validate_hash(value: Any, field: str) -> str:
|
|
|
526
672
|
return value
|
|
527
673
|
|
|
528
674
|
|
|
529
|
-
def _read_context(
|
|
675
|
+
def _read_context(
|
|
676
|
+
root: Path, context_ref: str, context_hash: str
|
|
677
|
+
) -> dict[str, Any]:
|
|
530
678
|
_validate_hash(context_hash, "context_sha256")
|
|
531
679
|
expected_ref = f".codearbiter/.artifacts/evidence-contexts/{context_hash}.json"
|
|
532
680
|
if context_ref != expected_ref:
|
|
533
681
|
raise AuthorityError("INVALID_EVIDENCE_CONTEXT", "context locator is not content addressed")
|
|
534
682
|
try:
|
|
535
|
-
|
|
683
|
+
path = root / expected_ref
|
|
684
|
+
_safe_directory(root, Path(expected_ref).parent, create=False)
|
|
685
|
+
_path_identity(path)
|
|
686
|
+
if not stat.S_ISREG(path.lstat().st_mode):
|
|
687
|
+
raise ValueError("context is not a regular file")
|
|
688
|
+
with path.open("rb") as stream:
|
|
689
|
+
raw = stream.read(MAX_EVIDENCE_CONTEXT + 1)
|
|
690
|
+
if len(raw) > MAX_EVIDENCE_CONTEXT:
|
|
691
|
+
raise ValueError("context exceeds the native canonical budget")
|
|
536
692
|
context = json.loads(raw.decode("utf-8"))
|
|
537
|
-
except (OSError, UnicodeError, ValueError) as exc:
|
|
693
|
+
except (OSError, UnicodeError, ValueError, AuthorityError) as exc:
|
|
538
694
|
raise AuthorityError("INVALID_EVIDENCE_CONTEXT", "context bytes are unavailable") from exc
|
|
539
695
|
if not isinstance(context, dict) or _digest(raw) != context_hash or raw != _native_context_canonical(context):
|
|
540
696
|
raise AuthorityError("INVALID_EVIDENCE_CONTEXT", "context bytes changed or are not canonical")
|
|
@@ -567,7 +723,10 @@ def _completion_client(root: Path) -> Any:
|
|
|
567
723
|
"""Use the native engine shipped with this adapter, never PATH or a caller."""
|
|
568
724
|
import _artifactlib
|
|
569
725
|
|
|
570
|
-
return _artifactlib.ArtifactClient(
|
|
726
|
+
return _artifactlib.ArtifactClient(
|
|
727
|
+
root, _artifactlib.helper_installation(__file__),
|
|
728
|
+
timeout=EVIDENCE_ENGINE_TIMEOUT_SECONDS,
|
|
729
|
+
)
|
|
571
730
|
|
|
572
731
|
|
|
573
732
|
def _context(
|
|
@@ -1020,6 +1179,7 @@ def _reject_overlapping_authority(session_id: str, turn_id: str) -> None:
|
|
|
1020
1179
|
launch = request.get("launch") or {}
|
|
1021
1180
|
if (
|
|
1022
1181
|
request["state"] in {"LAUNCHING", "RUNNING"}
|
|
1182
|
+
and request.get("codex_review_profile") != CODEX_NATIVE_V2
|
|
1023
1183
|
and launch.get("parent_session_id") == session_id
|
|
1024
1184
|
and launch.get("parent_turn_id") == turn_id
|
|
1025
1185
|
):
|
|
@@ -1340,7 +1500,12 @@ def _dispatch_prompt(request: dict[str, Any], *, legacy_inline: bool = False) ->
|
|
|
1340
1500
|
"these settings do not provide an OS sandbox.\n"
|
|
1341
1501
|
if request.get("codex_review_profile") == CODEX_NATIVE_V1 else ""
|
|
1342
1502
|
)
|
|
1503
|
+
if request.get("codex_review_profile") == CODEX_NATIVE_V2:
|
|
1504
|
+
profile = (f"Native review profile: {CODEX_NATIVE_V2}. This review was dispatched through "
|
|
1505
|
+
"collaboration.spawn_agent with default role and fork_turns=none. Read-only conduct "
|
|
1506
|
+
"is cooperative; these settings do not provide an OS sandbox.\n")
|
|
1343
1507
|
completion = ""
|
|
1508
|
+
completion_fields = ""
|
|
1344
1509
|
if "completion" in context:
|
|
1345
1510
|
completion = (
|
|
1346
1511
|
"This explicit completion review assesses performed work against every selected task's "
|
|
@@ -1366,11 +1531,28 @@ def _dispatch_prompt(request: dict[str, Any], *, legacy_inline: bool = False) ->
|
|
|
1366
1531
|
"done_when:<1-based index>. Each object has task_id, obligation, status (substantiated or "
|
|
1367
1532
|
"missing), assessment (nonempty reasons with concrete source locations, including file paths "
|
|
1368
1533
|
"and lines), and evidence_refs (nonempty unique exact selected receipt/source/event/observation/"
|
|
1369
|
-
"context refs, mapped workspace_root
|
|
1534
|
+
f"context refs, {'mapped workspace_root' if legacy_inline else 'raw workspace_root path'}, "
|
|
1535
|
+
"or material source_path). Reference the selected facts "
|
|
1370
1536
|
"that support each conclusion. Missing or unreadable facts require status=missing and "
|
|
1371
1537
|
"decision=changes_requested. A passing decision requires every completion obligation "
|
|
1372
1538
|
"substantiated; a claimed coverage list is insufficient.\n"
|
|
1539
|
+
"Separate required task/obligation pairs: "
|
|
1540
|
+
+ json.dumps(sorted(_completion_obligations(context)), ensure_ascii=True) + ".\n"
|
|
1373
1541
|
)
|
|
1542
|
+
completion_fields = ", completion_sha256 (exact selected packet hash), completion_assessment (separate obligation rows)"
|
|
1543
|
+
if not legacy_inline:
|
|
1544
|
+
references = {task_id: sorted(refs) for task_id, refs in _completion_references(context).items()}
|
|
1545
|
+
completion += (
|
|
1546
|
+
"Exact allowed evidence_refs by task_id (JSON):\n"
|
|
1547
|
+
+ _canonical(references).decode("ascii") + "\n"
|
|
1548
|
+
"Copy decoded JSON string values exactly into evidence_refs; choose only entries for "
|
|
1549
|
+
"that row's task_id, never another task's references. Do not add labels or prefixes, "
|
|
1550
|
+
"line numbers, descriptions, or alter path spelling. A raw workspace_root path is the "
|
|
1551
|
+
"listed path itself, without a mapped workspace_root: prefix. The verification-context "
|
|
1552
|
+
"refs listed here belong to selected published verifications; the current review container "
|
|
1553
|
+
"shown as Frozen context above is not an allowed evidence ref unless listed for that task. "
|
|
1554
|
+
"Put concrete source locations and lines in assessment, not in evidence_refs.\n"
|
|
1555
|
+
)
|
|
1374
1556
|
return (
|
|
1375
1557
|
f"[CODEARBITER_AUTHORITY_REQUEST:{request['request_id']}]\n"
|
|
1376
1558
|
f"Target repository: {request['repository']['path']}\n"
|
|
@@ -1384,7 +1566,8 @@ def _dispatch_prompt(request: dict[str, Any], *, legacy_inline: bool = False) ->
|
|
|
1384
1566
|
+ json.dumps(request["required_coverage"], ensure_ascii=True)
|
|
1385
1567
|
+ ". Fields: format, request_id, target_sha256, contract_sha256, decision "
|
|
1386
1568
|
"(pass or changes_requested), coverage (unique strings), findings (objects with "
|
|
1387
|
-
"severity, code, message), assessment (non-empty string)
|
|
1569
|
+
"severity, code, message), assessment (non-empty string)"
|
|
1570
|
+
+ completion_fields + ". Reply with the JSON "
|
|
1388
1571
|
"object only: no code fence and no other text."
|
|
1389
1572
|
)
|
|
1390
1573
|
|
|
@@ -1417,7 +1600,7 @@ def arm_request(
|
|
|
1417
1600
|
if host == "claude" and reviewer_model not in CLAUDE_REVIEWER_MODELS:
|
|
1418
1601
|
raise AuthorityError("INVALID_AUTHORITY_REQUEST", "reviewer model is unsupported")
|
|
1419
1602
|
if codex_review_profile is not None and (
|
|
1420
|
-
codex_review_profile
|
|
1603
|
+
codex_review_profile not in {"native-v1", "native-v2"} or host != "codex" or activity not in REVIEW_ACTIVITIES
|
|
1421
1604
|
):
|
|
1422
1605
|
raise AuthorityError("INVALID_AUTHORITY_REQUEST", "Codex review profile is unsupported")
|
|
1423
1606
|
completion_selection = None
|
|
@@ -1466,8 +1649,12 @@ def arm_request(
|
|
|
1466
1649
|
}
|
|
1467
1650
|
if host != "codex":
|
|
1468
1651
|
seed_value["host"] = host
|
|
1652
|
+
if activity == "verification":
|
|
1653
|
+
seed_value["context_storage"] = CONTEXT_STORAGE
|
|
1469
1654
|
if codex_review_profile is not None:
|
|
1470
|
-
seed_value["codex_review_profile"] = CODEX_NATIVE_V1
|
|
1655
|
+
seed_value["codex_review_profile"] = CODEX_NATIVE_V1 if codex_review_profile == "native-v1" else CODEX_NATIVE_V2
|
|
1656
|
+
if completion_selection is not None:
|
|
1657
|
+
seed_value["context_storage"] = CONTEXT_STORAGE
|
|
1471
1658
|
seed = _canonical(seed_value)
|
|
1472
1659
|
request_id = _digest(seed)
|
|
1473
1660
|
request = {
|
|
@@ -1491,8 +1678,12 @@ def arm_request(
|
|
|
1491
1678
|
}
|
|
1492
1679
|
if host != "codex":
|
|
1493
1680
|
request["host"] = host
|
|
1681
|
+
if activity == "verification":
|
|
1682
|
+
request["context_storage"] = CONTEXT_STORAGE
|
|
1494
1683
|
if codex_review_profile is not None:
|
|
1495
|
-
request["codex_review_profile"] =
|
|
1684
|
+
request["codex_review_profile"] = seed_value["codex_review_profile"]
|
|
1685
|
+
if completion_selection is not None:
|
|
1686
|
+
request["context_storage"] = CONTEXT_STORAGE
|
|
1496
1687
|
if activity in REVIEW_ACTIVITIES:
|
|
1497
1688
|
request["review_contract_sha256"], request["required_coverage"] = _review_binding(context)
|
|
1498
1689
|
request["dispatch_prompt"] = _dispatch_prompt(request)
|
|
@@ -1503,7 +1694,7 @@ def arm_request(
|
|
|
1503
1694
|
"subagent_type": CLAUDE_REVIEWER,
|
|
1504
1695
|
"model": reviewer_model,
|
|
1505
1696
|
}
|
|
1506
|
-
elif codex_review_profile
|
|
1697
|
+
elif codex_review_profile == "native-v1":
|
|
1507
1698
|
request["launch_envelope"] = {
|
|
1508
1699
|
"message": request["dispatch_prompt"], "fork_context": False,
|
|
1509
1700
|
}
|
|
@@ -1526,7 +1717,7 @@ def arm_request(
|
|
|
1526
1717
|
result["required_coverage"] = request["required_coverage"]
|
|
1527
1718
|
result["launch_envelope"] = request["launch_envelope"]
|
|
1528
1719
|
if codex_review_profile is not None:
|
|
1529
|
-
result["codex_review_profile"] =
|
|
1720
|
+
result["codex_review_profile"] = request["codex_review_profile"]
|
|
1530
1721
|
if completion_selection is not None:
|
|
1531
1722
|
result["completion_sha256"] = context["completion_sha256"]
|
|
1532
1723
|
return result
|
|
@@ -2388,6 +2579,24 @@ def run_verification(
|
|
|
2388
2579
|
request["context"], request["context_ref"], request["context_sha256"],
|
|
2389
2580
|
):
|
|
2390
2581
|
raise AuthorityError("WORKSPACE_DRIFT", "repository content changed after arm")
|
|
2582
|
+
def normalized_workspace():
|
|
2583
|
+
result = client.call("evidence-context", {
|
|
2584
|
+
"artifact_id": request["context"]["subject"]["artifact_id"],
|
|
2585
|
+
"record_id": request["context"]["subject"]["record_id"],
|
|
2586
|
+
"activity": "verification",
|
|
2587
|
+
"verification_command_bindings": request["command_bindings"],
|
|
2588
|
+
})
|
|
2589
|
+
if (not isinstance(result, dict)
|
|
2590
|
+
or result.get("context_ref") != request["context_ref"]
|
|
2591
|
+
or result.get("context_sha256") != request["context_sha256"]
|
|
2592
|
+
or not isinstance(result.get("completion_workspace_after"), list)
|
|
2593
|
+
or not result["completion_workspace_after"]):
|
|
2594
|
+
raise AuthorityError("WORKSPACE_DRIFT", "engine completion closure is absent or changed context")
|
|
2595
|
+
return result["completion_workspace_after"]
|
|
2596
|
+
|
|
2597
|
+
# The engine alone normalizes validated native bookkeeping. Raw snapshots
|
|
2598
|
+
# still freeze every byte across the actual command execution interval.
|
|
2599
|
+
completion_workspace_before = normalized_workspace()
|
|
2391
2600
|
workspace_before = _workspace_snapshots(request["command_bindings"])
|
|
2392
2601
|
if workspace_before != wrapper.get("workspace_before"):
|
|
2393
2602
|
raise AuthorityError("WORKSPACE_DRIFT", "workspace changed after wrapper authorization")
|
|
@@ -2422,6 +2631,9 @@ def run_verification(
|
|
|
2422
2631
|
workspace_after = _workspace_snapshots(request["command_bindings"])
|
|
2423
2632
|
if workspace_after != workspace_before:
|
|
2424
2633
|
raise AuthorityError("WORKSPACE_DRIFT", "verification changed the frozen workspace")
|
|
2634
|
+
completion_workspace_after = normalized_workspace()
|
|
2635
|
+
if completion_workspace_after != completion_workspace_before:
|
|
2636
|
+
raise AuthorityError("WORKSPACE_DRIFT", "verification changed the normalized completion workspace")
|
|
2425
2637
|
final_context, final_ref, final_hash = _context(
|
|
2426
2638
|
root, client, request["context"]["subject"]["artifact_id"],
|
|
2427
2639
|
request["context"]["subject"]["record_id"], "verification",
|
|
@@ -2435,6 +2647,7 @@ def run_verification(
|
|
|
2435
2647
|
"command_bindings": request["command_bindings"],
|
|
2436
2648
|
"workspace_before": workspace_before,
|
|
2437
2649
|
"workspace_after": workspace_after,
|
|
2650
|
+
"completion_workspace_after": completion_workspace_after,
|
|
2438
2651
|
"commands": commands,
|
|
2439
2652
|
}
|
|
2440
2653
|
payload = {
|
|
@@ -2444,7 +2657,7 @@ def run_verification(
|
|
|
2444
2657
|
"commands": commands,
|
|
2445
2658
|
}
|
|
2446
2659
|
observation = _closed_observation(
|
|
2447
|
-
request, payload, "declared-command/0.
|
|
2660
|
+
request, payload, "declared-command/0.3.0", request["attempt"],
|
|
2448
2661
|
producer_result,
|
|
2449
2662
|
)
|
|
2450
2663
|
_observation(root, request, observation)
|
|
@@ -2730,6 +2943,145 @@ def _codex_uuid(value: Any) -> str:
|
|
|
2730
2943
|
return value
|
|
2731
2944
|
|
|
2732
2945
|
|
|
2946
|
+
@contextmanager
|
|
2947
|
+
def _codex_transcript(path: Any):
|
|
2948
|
+
"""Open the hook-owned native file, never a caller-selected linked path."""
|
|
2949
|
+
if not isinstance(path, str) or not Path(path).is_absolute():
|
|
2950
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "native transcript path must be absolute")
|
|
2951
|
+
target = Path(path)
|
|
2952
|
+
try:
|
|
2953
|
+
for component in (*reversed(target.parents), target):
|
|
2954
|
+
info = component.lstat()
|
|
2955
|
+
if stat.S_ISLNK(info.st_mode) or getattr(info, "st_file_attributes", 0) & 0x400:
|
|
2956
|
+
raise OSError("linked transcript component")
|
|
2957
|
+
if not stat.S_ISREG(info.st_mode):
|
|
2958
|
+
raise OSError("nonregular transcript")
|
|
2959
|
+
flags = os.O_RDONLY | getattr(os, "O_BINARY", 0) | getattr(os, "O_NOFOLLOW", 0)
|
|
2960
|
+
with os.fdopen(os.open(target, flags), "rb") as stream:
|
|
2961
|
+
opened = os.fstat(stream.fileno())
|
|
2962
|
+
if (opened.st_dev, opened.st_ino) != (info.st_dev, info.st_ino):
|
|
2963
|
+
raise OSError("changed transcript")
|
|
2964
|
+
yield stream, opened
|
|
2965
|
+
except OSError as exc:
|
|
2966
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "native transcript is unavailable or unsafe") from exc
|
|
2967
|
+
|
|
2968
|
+
|
|
2969
|
+
def _codex_metadata(stream) -> tuple[bytes, dict[str, Any]]:
|
|
2970
|
+
stream.seek(0)
|
|
2971
|
+
raw = stream.readline(MAX_DECISION + 1)
|
|
2972
|
+
try:
|
|
2973
|
+
value = json.loads(raw.decode("utf-8"), object_pairs_hook=_unique_json_pairs,
|
|
2974
|
+
parse_constant=_invalid_json_constant)
|
|
2975
|
+
if (len(raw) > MAX_DECISION or not raw.endswith(b"\n") or not isinstance(value, dict)
|
|
2976
|
+
or value.get("type") != "session_meta" or not isinstance(value.get("payload"), dict)):
|
|
2977
|
+
raise ValueError("unsupported metadata")
|
|
2978
|
+
return raw, value["payload"]
|
|
2979
|
+
except (ValueError, UnicodeError, RecursionError) as exc:
|
|
2980
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "native session metadata is malformed or oversized") from exc
|
|
2981
|
+
|
|
2982
|
+
|
|
2983
|
+
def _codex_v2_trace_anchor(event: dict[str, Any], task_name: str) -> dict[str, Any]:
|
|
2984
|
+
with _codex_transcript(event.get("transcript_path")) as (stream, info):
|
|
2985
|
+
raw, meta = _codex_metadata(stream)
|
|
2986
|
+
thread = _codex_uuid(meta.get("id"))
|
|
2987
|
+
session = meta.get("session_id", thread)
|
|
2988
|
+
parent_path = meta.get("agent_path") or "/root"
|
|
2989
|
+
if (session != event.get("session_id") or not isinstance(parent_path, str)
|
|
2990
|
+
or CODEX_AGENT_PATH_RE.fullmatch(parent_path) is None
|
|
2991
|
+
or not re.fullmatch(r"authority_[0-9a-f]{12}", task_name)):
|
|
2992
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "native parent session or task path differs")
|
|
2993
|
+
offset = info.st_size
|
|
2994
|
+
tail_start = max(0, offset - MAX_DECISION)
|
|
2995
|
+
stream.seek(tail_start)
|
|
2996
|
+
tail = stream.read(offset - tail_start)
|
|
2997
|
+
if not tail.endswith(b"\n"):
|
|
2998
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "native parent transcript is not materialized")
|
|
2999
|
+
return {"path": event["transcript_path"], "device": info.st_dev, "inode": info.st_ino,
|
|
3000
|
+
"offset": offset, "tail_start": tail_start, "tail_sha256": _digest(tail),
|
|
3001
|
+
"metadata_sha256": _digest(raw), "parent_thread_id": thread,
|
|
3002
|
+
"parent_agent_path": parent_path, "agent_path": parent_path + "/" + task_name}
|
|
3003
|
+
|
|
3004
|
+
|
|
3005
|
+
def _codex_v2_activity(event: dict[str, Any], request: dict[str, Any]) -> dict[str, Any]:
|
|
3006
|
+
anchor = request.get("codex_native_trace")
|
|
3007
|
+
launch = request["launch"]
|
|
3008
|
+
if not isinstance(anchor, dict) or event.get("transcript_path") != anchor.get("path"):
|
|
3009
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "native parent transcript locator changed")
|
|
3010
|
+
with _codex_transcript(anchor["path"]) as (stream, info):
|
|
3011
|
+
raw, _ = _codex_metadata(stream)
|
|
3012
|
+
if ((info.st_dev, info.st_ino) != (anchor["device"], anchor["inode"])
|
|
3013
|
+
or info.st_size < anchor["offset"] or _digest(raw) != anchor["metadata_sha256"]):
|
|
3014
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "native parent transcript identity changed")
|
|
3015
|
+
stream.seek(anchor["tail_start"])
|
|
3016
|
+
if _digest(stream.read(anchor["offset"] - anchor["tail_start"])) != anchor["tail_sha256"]:
|
|
3017
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "native parent transcript prefix changed")
|
|
3018
|
+
stream.seek(anchor["offset"])
|
|
3019
|
+
suffix = stream.read(MAX_OUTPUT + 1)
|
|
3020
|
+
if len(suffix) > MAX_OUTPUT or not suffix.endswith(b"\n"):
|
|
3021
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "native activity suffix is absent, incomplete or oversized")
|
|
3022
|
+
matches = []
|
|
3023
|
+
try:
|
|
3024
|
+
# JSONL records end at LF; Unicode line separators are valid string data.
|
|
3025
|
+
for line in suffix.decode("utf-8").split("\n")[:-1]:
|
|
3026
|
+
value = json.loads(line, object_pairs_hook=_unique_json_pairs, parse_constant=_invalid_json_constant)
|
|
3027
|
+
payload = value.get("payload") if isinstance(value, dict) else None
|
|
3028
|
+
item = payload.get("item") if isinstance(payload, dict) else None
|
|
3029
|
+
if (value.get("type") == "event_msg" and isinstance(item, dict)
|
|
3030
|
+
and item.get("type") == "SubAgentActivity" and payload.get("type") == "item_completed"
|
|
3031
|
+
and item.get("id") == launch["tool_use_id"]):
|
|
3032
|
+
matches.append(payload)
|
|
3033
|
+
except (ValueError, UnicodeError, AttributeError, RecursionError) as exc:
|
|
3034
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "native activity suffix is not strict JSONL") from exc
|
|
3035
|
+
if len(matches) != 1:
|
|
3036
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "native activity for exact call is missing or ambiguous")
|
|
3037
|
+
activity = matches[0]
|
|
3038
|
+
item = activity["item"]
|
|
3039
|
+
if (set(activity) != {"type", "thread_id", "turn_id", "item", "started_at_ms", "completed_at_ms"}
|
|
3040
|
+
or set(item) != {"type", "id", "kind", "agent_thread_id", "agent_path"}
|
|
3041
|
+
or activity["thread_id"] != anchor["parent_thread_id"] or activity["turn_id"] != launch["parent_turn_id"]
|
|
3042
|
+
or item["kind"] != "started" or item["agent_path"] != anchor["agent_path"]
|
|
3043
|
+
or any(type(activity[key]) is not int or activity[key] < 0 for key in ("started_at_ms", "completed_at_ms"))
|
|
3044
|
+
or activity["completed_at_ms"] < activity["started_at_ms"]):
|
|
3045
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "native activity binding differs from the exact launch")
|
|
3046
|
+
_codex_uuid(item["agent_thread_id"])
|
|
3047
|
+
return activity
|
|
3048
|
+
|
|
3049
|
+
|
|
3050
|
+
def _codex_v2_child(event: dict[str, Any]) -> dict[str, Any] | None:
|
|
3051
|
+
# Historical V1 lifecycle fixtures/hosts need no native rollout metadata.
|
|
3052
|
+
if event.get("transcript_path") is None:
|
|
3053
|
+
return None
|
|
3054
|
+
try:
|
|
3055
|
+
with _codex_transcript(event["transcript_path"]) as (stream, _):
|
|
3056
|
+
raw, meta = _codex_metadata(stream)
|
|
3057
|
+
except AuthorityError:
|
|
3058
|
+
# A V1 Start did not promise native metadata. V2 binding below requires
|
|
3059
|
+
# qualified metadata; missing/unsafe bytes still cannot pass V2.
|
|
3060
|
+
return None
|
|
3061
|
+
if meta.get("cli_version") != "0.162.0-alpha.2":
|
|
3062
|
+
return None
|
|
3063
|
+
identity = {"cli_version": meta["cli_version"], "thread_id": _codex_uuid(meta.get("id")),
|
|
3064
|
+
"parent_thread_id": _codex_uuid(meta.get("parent_thread_id")),
|
|
3065
|
+
"session_id": _host_id(meta.get("session_id"), "session_id"),
|
|
3066
|
+
"agent_path": meta.get("agent_path"), "metadata_sha256": _digest(raw)}
|
|
3067
|
+
if (identity["thread_id"] != event["agent_id"] or identity["session_id"] != event.get("session_id")
|
|
3068
|
+
or not isinstance(identity["agent_path"], str)
|
|
3069
|
+
or CODEX_AGENT_PATH_RE.fullmatch(identity["agent_path"]) is None):
|
|
3070
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "native child metadata differs from its lifecycle")
|
|
3071
|
+
return identity
|
|
3072
|
+
|
|
3073
|
+
|
|
3074
|
+
def _codex_v2_bind_child(request: dict[str, Any], child: dict[str, Any]) -> None:
|
|
3075
|
+
identity = child.get("native_v2")
|
|
3076
|
+
launch = request["launch"]
|
|
3077
|
+
if (not isinstance(identity, dict) or identity.get("parent_thread_id") != launch["parent_thread_id"]
|
|
3078
|
+
or identity.get("thread_id") != launch["agent_id"]
|
|
3079
|
+
or identity.get("session_id") != launch["parent_session_id"]
|
|
3080
|
+
or identity.get("agent_path") != launch["task_name"]):
|
|
3081
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "native V2 child runtime or parent association is unqualified")
|
|
3082
|
+
launch["native_child"] = identity
|
|
3083
|
+
|
|
3084
|
+
|
|
2733
3085
|
def _codex_native_marker(agent_id: str) -> Path:
|
|
2734
3086
|
# UUID, not arrival order or session, is the direct host-observed join key.
|
|
2735
3087
|
# Retaining this marker also prevents reuse across requests and sessions.
|
|
@@ -2794,8 +3146,10 @@ def _read_codex_child(path: Path, agent_id: str, session_id: str) -> dict[str, A
|
|
|
2794
3146
|
start = value.get("start") if isinstance(value, dict) else None
|
|
2795
3147
|
if (
|
|
2796
3148
|
len(raw) > MAX_CHILD_MARKER or not isinstance(value, dict)
|
|
2797
|
-
or set(value)
|
|
2798
|
-
|
|
3149
|
+
or set(value) - {"format", "agent_id", "session_id", "request_id", "start", "stop_seen",
|
|
3150
|
+
"rejected", "integrity_sha256", "native_v2"}
|
|
3151
|
+
or not {"format", "agent_id", "session_id", "request_id", "start", "stop_seen",
|
|
3152
|
+
"rejected", "integrity_sha256"} <= set(value)
|
|
2799
3153
|
or value.get("format") != CODEX_CHILD_FORMAT or value.get("agent_id") != agent_id
|
|
2800
3154
|
or not isinstance(value.get("session_id"), str) or not HOST_ID_RE.fullmatch(value["session_id"])
|
|
2801
3155
|
or (value.get("request_id") is not None and (
|
|
@@ -2808,6 +3162,19 @@ def _read_codex_child(path: Path, agent_id: str, session_id: str) -> dict[str, A
|
|
|
2808
3162
|
or not CODEX_UUID_RE.fullmatch(start["turn_id"])))
|
|
2809
3163
|
):
|
|
2810
3164
|
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "native Codex child marker failed validation")
|
|
3165
|
+
if "native_v2" in value:
|
|
3166
|
+
identity = value["native_v2"]
|
|
3167
|
+
if (not isinstance(identity, dict) or set(identity) != {
|
|
3168
|
+
"cli_version", "thread_id", "parent_thread_id", "session_id", "agent_path", "metadata_sha256"}
|
|
3169
|
+
or identity.get("cli_version") != "0.162.0-alpha.2"
|
|
3170
|
+
or identity.get("thread_id") != agent_id or identity.get("session_id") != value["session_id"]
|
|
3171
|
+
or not isinstance(identity.get("parent_thread_id"), str)
|
|
3172
|
+
or CODEX_UUID_RE.fullmatch(identity["parent_thread_id"]) is None
|
|
3173
|
+
or not isinstance(identity.get("agent_path"), str)
|
|
3174
|
+
or CODEX_AGENT_PATH_RE.fullmatch(identity["agent_path"]) is None
|
|
3175
|
+
or not isinstance(identity.get("metadata_sha256"), str)
|
|
3176
|
+
or SHA256_RE.fullmatch(identity["metadata_sha256"]) is None):
|
|
3177
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "native child runtime metadata failed validation")
|
|
2811
3178
|
return value
|
|
2812
3179
|
|
|
2813
3180
|
|
|
@@ -2846,10 +3213,13 @@ def _codex_native_post(event: dict[str, Any], root: Path, request: dict[str, Any
|
|
|
2846
3213
|
_reject(root, current, "invalid-first-native-review-post")
|
|
2847
3214
|
if isinstance(exc, AuthorityError):
|
|
2848
3215
|
raise
|
|
2849
|
-
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "native
|
|
3216
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "native spawn result is malformed") from exc
|
|
2850
3217
|
|
|
2851
3218
|
|
|
2852
3219
|
def _codex_native_post_join(event: dict[str, Any], root: Path, request: dict[str, Any]) -> dict[str, Any]:
|
|
3220
|
+
expected_tool = "collaborationspawn_agent" if request.get("codex_review_profile") == CODEX_NATIVE_V2 else "spawn_agent"
|
|
3221
|
+
if event.get("tool_name") != expected_tool:
|
|
3222
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "native review PostToolUse tool differs from its launch profile")
|
|
2853
3223
|
if _canonical(event.get("tool_input")) != _canonical(request["launch_envelope"]):
|
|
2854
3224
|
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "native review PostToolUse envelope was changed")
|
|
2855
3225
|
_require_frozen_context(root, request)
|
|
@@ -2860,11 +3230,20 @@ def _codex_native_post_join(event: dict[str, Any], root: Path, request: dict[str
|
|
|
2860
3230
|
response = json.loads(raw, object_pairs_hook=_unique_json_pairs, parse_constant=_invalid_json_constant)
|
|
2861
3231
|
except ValueError as exc:
|
|
2862
3232
|
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "native-v1 spawn result is not strict JSON") from exc
|
|
2863
|
-
|
|
3233
|
+
activity = None
|
|
3234
|
+
if request.get("codex_review_profile") == CODEX_NATIVE_V2:
|
|
3235
|
+
if (not isinstance(response, dict) or set(response) not in ({"task_name"}, {"task_name", "nickname"})
|
|
3236
|
+
or response.get("task_name") != request["codex_native_trace"]["agent_path"]
|
|
3237
|
+
or ("nickname" in response and (not isinstance(response["nickname"], str) or len(response["nickname"]) > 256))):
|
|
3238
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "native-v2 spawn result fields or canonical task path differ")
|
|
3239
|
+
activity = _codex_v2_activity(event, request)
|
|
3240
|
+
agent_id = _codex_uuid(activity["item"]["agent_thread_id"])
|
|
3241
|
+
elif (not isinstance(response, dict) or set(response) != {"agent_id", "nickname"}
|
|
2864
3242
|
or (response["nickname"] is not None and (
|
|
2865
3243
|
not isinstance(response["nickname"], str) or len(response["nickname"]) > 256))):
|
|
2866
3244
|
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "native-v1 spawn result fields are unsupported")
|
|
2867
|
-
|
|
3245
|
+
else:
|
|
3246
|
+
agent_id = _codex_uuid(response.get("agent_id"))
|
|
2868
3247
|
session_id = request["launch"]["parent_session_id"]
|
|
2869
3248
|
with _codex_child_lock(agent_id, session_id) as (path, child):
|
|
2870
3249
|
request = _load(root, request["request_id"])
|
|
@@ -2880,7 +3259,11 @@ def _codex_native_post_join(event: dict[str, Any], root: Path, request: dict[str
|
|
|
2880
3259
|
_codex_native_refuse(root, request, "native reviewer stopped or conflicted before association")
|
|
2881
3260
|
launch = request["launch"]
|
|
2882
3261
|
launch.update(agent_id=agent_id, post_confirmed=True)
|
|
3262
|
+
if activity is not None:
|
|
3263
|
+
launch.update(native_activity=activity, native_activity_sha256=_engine_digest(activity))
|
|
2883
3264
|
if child["start"] is not None:
|
|
3265
|
+
if request.get("codex_review_profile") == CODEX_NATIVE_V2:
|
|
3266
|
+
_codex_v2_bind_child(request, child)
|
|
2884
3267
|
launch.update(child_turn_id=child["start"]["turn_id"], agent_type="default")
|
|
2885
3268
|
request["state"] = "RUNNING"
|
|
2886
3269
|
_save(root, request)
|
|
@@ -2921,7 +3304,7 @@ def _codex_native_lifecycle(event: dict[str, Any]) -> dict[str, Any] | None:
|
|
|
2921
3304
|
except (AuthorityError, OSError) as exc:
|
|
2922
3305
|
_claim_codex_event(agent_id, "failed")
|
|
2923
3306
|
for root, request in _registered_requests():
|
|
2924
|
-
if (request.get("codex_review_profile")
|
|
3307
|
+
if (request.get("codex_review_profile") in CODEX_NATIVE_PROFILES
|
|
2925
3308
|
and request["state"] in {"LAUNCHING", "RUNNING"}
|
|
2926
3309
|
and (request.get("launch") or {}).get("agent_id") == agent_id):
|
|
2927
3310
|
_reject(root, request, "failed-native-review-lifecycle")
|
|
@@ -2949,6 +3332,9 @@ def _codex_native_lifecycle_join(event: dict[str, Any]) -> dict[str, Any] | None
|
|
|
2949
3332
|
if name == "SubagentStart":
|
|
2950
3333
|
valid = valid and start is None
|
|
2951
3334
|
if valid:
|
|
3335
|
+
identity = _codex_v2_child(event)
|
|
3336
|
+
if identity is not None:
|
|
3337
|
+
child["native_v2"] = identity
|
|
2952
3338
|
child["start"] = {"turn_id": event["turn_id"], "agent_type": "default"}
|
|
2953
3339
|
else:
|
|
2954
3340
|
valid = (valid and start is not None and start["turn_id"] == event.get("turn_id")
|
|
@@ -2962,7 +3348,7 @@ def _codex_native_lifecycle_join(event: dict[str, Any]) -> dict[str, Any] | None
|
|
|
2962
3348
|
return None
|
|
2963
3349
|
root, request = _registered_request(child["request_id"])
|
|
2964
3350
|
launch = request.get("launch") or {}
|
|
2965
|
-
if (not valid or request.get("codex_review_profile")
|
|
3351
|
+
if (not valid or request.get("codex_review_profile") not in CODEX_NATIVE_PROFILES
|
|
2966
3352
|
or launch.get("parent_session_id") != child["session_id"]
|
|
2967
3353
|
or launch.get("agent_id") != agent_id or launch.get("post_confirmed") is not True
|
|
2968
3354
|
or event.get("turn_id") == launch.get("parent_turn_id")):
|
|
@@ -2970,6 +3356,8 @@ def _codex_native_lifecycle_join(event: dict[str, Any]) -> dict[str, Any] | None
|
|
|
2970
3356
|
if name == "SubagentStart":
|
|
2971
3357
|
if request["state"] != "LAUNCHING":
|
|
2972
3358
|
_codex_native_refuse(root, request, "native reviewer start was repeated")
|
|
3359
|
+
if request.get("codex_review_profile") == CODEX_NATIVE_V2:
|
|
3360
|
+
_codex_v2_bind_child(request, child)
|
|
2973
3361
|
launch.update(child_turn_id=event["turn_id"], agent_type="default")
|
|
2974
3362
|
request["state"] = "RUNNING"
|
|
2975
3363
|
_save(root, request)
|
|
@@ -3010,6 +3398,43 @@ def _codex_native_steering(event: dict[str, Any]) -> None:
|
|
|
3010
3398
|
if not isinstance(tool_input, dict):
|
|
3011
3399
|
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "native reviewer steering input is malformed")
|
|
3012
3400
|
agent_id = _codex_steering_uuid(tool_input.get(field))
|
|
3401
|
+
if event["tool_name"].startswith("collaboration"):
|
|
3402
|
+
target = tool_input.get("target")
|
|
3403
|
+
canonical_path = None
|
|
3404
|
+
if isinstance(target, str) and CODEX_AGENT_PATH_RE.fullmatch(target):
|
|
3405
|
+
canonical_path = target
|
|
3406
|
+
child_identity = None
|
|
3407
|
+
if agent_id is not None:
|
|
3408
|
+
_claim_codex_event(agent_id, "failed")
|
|
3409
|
+
with _codex_child_lock(agent_id, _host_id(event.get("session_id"), "session_id")) as (path, child):
|
|
3410
|
+
child_identity = child.get("native_v2")
|
|
3411
|
+
child["rejected"] = True
|
|
3412
|
+
_write_codex_child(path, child)
|
|
3413
|
+
for root, request in _registered_requests(strict=True):
|
|
3414
|
+
launch = request.get("launch") or {}
|
|
3415
|
+
if (request.get("codex_review_profile") != CODEX_NATIVE_V2
|
|
3416
|
+
or request["state"] not in {"LAUNCHING", "RUNNING"}
|
|
3417
|
+
or launch.get("parent_session_id") != event.get("session_id")):
|
|
3418
|
+
continue
|
|
3419
|
+
# Caller-relative identity matters only to an active review here.
|
|
3420
|
+
if canonical_path is None and isinstance(target, str) and re.fullmatch(r"[a-z0-9_]+(?:/[a-z0-9_]+)*", target):
|
|
3421
|
+
with _codex_transcript(event.get("transcript_path")) as (stream, _):
|
|
3422
|
+
_, meta = _codex_metadata(stream)
|
|
3423
|
+
caller_path = meta.get("agent_path") or "/root"
|
|
3424
|
+
if isinstance(caller_path, str) and CODEX_AGENT_PATH_RE.fullmatch(caller_path):
|
|
3425
|
+
canonical_path = caller_path + "/" + target
|
|
3426
|
+
matches = canonical_path == launch.get("task_name") or (
|
|
3427
|
+
agent_id is not None and (launch.get("agent_id") == agent_id or
|
|
3428
|
+
isinstance(child_identity, dict) and child_identity.get("agent_path") == launch.get("task_name")
|
|
3429
|
+
and child_identity.get("parent_thread_id") == launch.get("parent_thread_id")))
|
|
3430
|
+
if not matches and agent_id is not None and launch.get("agent_id") is None:
|
|
3431
|
+
try:
|
|
3432
|
+
matches = _codex_v2_activity(event, request)["item"]["agent_thread_id"] == agent_id
|
|
3433
|
+
except AuthorityError:
|
|
3434
|
+
pass # Missing direct evidence never supplies an association.
|
|
3435
|
+
if matches:
|
|
3436
|
+
_codex_native_refuse(root, request, "native reviewer cannot be steered or interrupted")
|
|
3437
|
+
return None
|
|
3013
3438
|
if agent_id is None:
|
|
3014
3439
|
return None
|
|
3015
3440
|
_claim_codex_event(agent_id, "failed")
|
|
@@ -3033,25 +3458,28 @@ def observe_codex_hook(root: str | Path, event: dict[str, Any]) -> dict[str, Any
|
|
|
3033
3458
|
return native
|
|
3034
3459
|
session_id = _host_id(event.get("session_id"), "session_id")
|
|
3035
3460
|
|
|
3036
|
-
if name
|
|
3461
|
+
if name == "PreToolUse" and event.get("tool_name") == "collaborationspawn_agent":
|
|
3037
3462
|
tool_input = event.get("tool_input")
|
|
3038
3463
|
message = tool_input.get("message") if isinstance(tool_input, dict) else None
|
|
3039
3464
|
if isinstance(message, str) and message.startswith("[CODEARBITER_AUTHORITY_REQUEST:"):
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3465
|
+
_, candidate = _registered_request(_request_id_from_prompt(message))
|
|
3466
|
+
if candidate.get("codex_review_profile") != CODEX_NATIVE_V2:
|
|
3467
|
+
raise AuthorityError(
|
|
3468
|
+
"UNSUPPORTED_HOST_SEAM",
|
|
3469
|
+
"Codex v2 requires a fresh qualified native-v2 request with direct activity UUID binding; "
|
|
3470
|
+
"otherwise qualify native-v1 and arm a fresh request",
|
|
3471
|
+
)
|
|
3472
|
+
else:
|
|
3045
3473
|
turn_id = _host_id(event.get("turn_id"), "turn_id")
|
|
3046
3474
|
_record_ordinary_spawn(session_id, turn_id)
|
|
3047
3475
|
_reject_overlapping_authority(session_id, turn_id)
|
|
3048
|
-
|
|
3476
|
+
return None
|
|
3049
3477
|
|
|
3050
3478
|
if name in {"PreToolUse", "PostToolUse"} and event.get("tool_name") in CODEX_STEERING_TOOLS:
|
|
3051
3479
|
return _codex_native_steering(event)
|
|
3052
3480
|
|
|
3053
3481
|
if name == "PreToolUse":
|
|
3054
|
-
if event.get("tool_name")
|
|
3482
|
+
if event.get("tool_name") not in {"spawn_agent", "collaborationspawn_agent"}:
|
|
3055
3483
|
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "only spawn_agent can launch a reviewer")
|
|
3056
3484
|
tool_use_id = _host_id(event.get("tool_use_id"), "tool_use_id")
|
|
3057
3485
|
turn_id = _host_id(event.get("turn_id"), "turn_id")
|
|
@@ -3073,8 +3501,11 @@ def observe_codex_hook(root: str | Path, event: dict[str, Any]) -> dict[str, Any
|
|
|
3073
3501
|
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "review request is not launchable")
|
|
3074
3502
|
if _canonical(tool_input) != _canonical(request.get("launch_envelope")):
|
|
3075
3503
|
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "review launch envelope was changed")
|
|
3504
|
+
is_v2 = request.get("codex_review_profile") == CODEX_NATIVE_V2
|
|
3505
|
+
if is_v2 != (event["tool_name"] == "collaborationspawn_agent"):
|
|
3506
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "native review profile differs from launch tool")
|
|
3076
3507
|
_require_frozen_context(root, request)
|
|
3077
|
-
if _ordinary_spawn_marker(session_id, turn_id).exists():
|
|
3508
|
+
if not is_v2 and _ordinary_spawn_marker(session_id, turn_id).exists():
|
|
3078
3509
|
request["state"] = "REJECTED"
|
|
3079
3510
|
request["recovery"] = {
|
|
3080
3511
|
"mode": "overlapping-subagent-launch",
|
|
@@ -3095,6 +3526,11 @@ def observe_codex_hook(root: str | Path, event: dict[str, Any]) -> dict[str, Any
|
|
|
3095
3526
|
codex_review_profile=CODEX_NATIVE_V1, child_turn_id=None,
|
|
3096
3527
|
fork_context=False,
|
|
3097
3528
|
)
|
|
3529
|
+
elif is_v2:
|
|
3530
|
+
anchor = _codex_v2_trace_anchor(event, tool_input["task_name"])
|
|
3531
|
+
request["codex_native_trace"] = anchor
|
|
3532
|
+
request["launch"].update(codex_review_profile=CODEX_NATIVE_V2, child_turn_id=None,
|
|
3533
|
+
parent_thread_id=anchor["parent_thread_id"], task_name=anchor["agent_path"], fork_turns="none")
|
|
3098
3534
|
else:
|
|
3099
3535
|
request["launch"].update(task_name=tool_input["task_name"], fork_turns=tool_input["fork_turns"])
|
|
3100
3536
|
request["state"] = "LAUNCHING"
|
|
@@ -3102,7 +3538,7 @@ def observe_codex_hook(root: str | Path, event: dict[str, Any]) -> dict[str, Any
|
|
|
3102
3538
|
return {"request_id": request_id, "state": "LAUNCHING"}
|
|
3103
3539
|
|
|
3104
3540
|
if name == "PostToolUse":
|
|
3105
|
-
if event.get("tool_name")
|
|
3541
|
+
if event.get("tool_name") not in {"spawn_agent", "collaborationspawn_agent"}:
|
|
3106
3542
|
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "post-tool event is not reviewer launch")
|
|
3107
3543
|
tool_use_id = _host_id(event.get("tool_use_id"), "tool_use_id")
|
|
3108
3544
|
turn_id = _host_id(event.get("turn_id"), "turn_id")
|
|
@@ -3121,7 +3557,7 @@ def observe_codex_hook(root: str | Path, event: dict[str, Any]) -> dict[str, Any
|
|
|
3121
3557
|
if len(matches) != 1:
|
|
3122
3558
|
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "review launch correlation is ambiguous")
|
|
3123
3559
|
root, request = matches[0]
|
|
3124
|
-
if request.get("codex_review_profile")
|
|
3560
|
+
if request.get("codex_review_profile") in CODEX_NATIVE_PROFILES:
|
|
3125
3561
|
return _codex_native_post(event, root, request)
|
|
3126
3562
|
response = event.get("tool_response")
|
|
3127
3563
|
if not isinstance(response, dict):
|
|
@@ -3139,7 +3575,7 @@ def observe_codex_hook(root: str | Path, event: dict[str, Any]) -> dict[str, Any
|
|
|
3139
3575
|
matches = []
|
|
3140
3576
|
same_turn = []
|
|
3141
3577
|
for candidate_root, candidate in _registered_requests():
|
|
3142
|
-
if candidate.get("codex_review_profile")
|
|
3578
|
+
if candidate.get("codex_review_profile") in CODEX_NATIVE_PROFILES:
|
|
3143
3579
|
continue
|
|
3144
3580
|
launch = candidate.get("launch") or {}
|
|
3145
3581
|
if (
|
|
@@ -447,7 +447,8 @@ def _installed_workflow_preflight(installation: Path) -> dict[str, object]:
|
|
|
447
447
|
tools = ("Bash", "Agent") if host == "claude" else (
|
|
448
448
|
"Bash", "shell_command", "exec_command", "unified_exec", "spawn_agent",
|
|
449
449
|
"collaborationspawn_agent", "multi_agent_v1send_input", "multi_agent_v1resume_agent",
|
|
450
|
-
"multi_agent_v1close_agent"
|
|
450
|
+
"multi_agent_v1close_agent", "collaborationsend_message", "collaborationfollowup_task",
|
|
451
|
+
"collaborationinterrupt_agent")
|
|
451
452
|
required += [(event, tool, "artifact-authority-hook.py")
|
|
452
453
|
for event in ("PreToolUse", "PostToolUse") for tool in tools]
|
|
453
454
|
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.17.
|
|
14
|
+
adapter_version = "0.17.6"
|
|
15
15
|
update_target = "ca-pi"
|
|
16
16
|
update_tag_prefix = "ca-pi-v"
|
|
17
17
|
update_command = "pi update npm:@arbiterforge/ca-pi"
|
|
@@ -52,8 +52,8 @@ def main(argv=None) -> int:
|
|
|
52
52
|
default="opus", help="Claude reviewer model pinned into the launch envelope",
|
|
53
53
|
)
|
|
54
54
|
arm.add_argument(
|
|
55
|
-
"--codex-review-profile", choices=("native-v1",), default=None,
|
|
56
|
-
help="select only after qualifying the actual registered native
|
|
55
|
+
"--codex-review-profile", choices=("native-v1", "native-v2"), default=None,
|
|
56
|
+
help="select only after qualifying the actual registered native interface and lifecycle",
|
|
57
57
|
)
|
|
58
58
|
context_arm = sub.add_parser("arm-context")
|
|
59
59
|
context_arm.add_argument("--root", required=True)
|
|
@@ -80,7 +80,8 @@ def main(argv=None) -> int:
|
|
|
80
80
|
print(json.dumps(_artifactauthoritylib.cancel_context_preview(root, args.document_id), sort_keys=True))
|
|
81
81
|
return 0
|
|
82
82
|
client = _artifactlib.ArtifactClient(
|
|
83
|
-
root, _artifactlib.helper_installation(__file__)
|
|
83
|
+
root, _artifactlib.helper_installation(__file__),
|
|
84
|
+
timeout=_artifactauthoritylib.EVIDENCE_ENGINE_TIMEOUT_SECONDS,
|
|
84
85
|
)
|
|
85
86
|
if args.command == "arm":
|
|
86
87
|
workspaces = _workspace_map(args.workspace)
|
|
@@ -93,7 +94,7 @@ def main(argv=None) -> int:
|
|
|
93
94
|
and args.codex_review_profile is None):
|
|
94
95
|
raise _artifactauthoritylib.AuthorityError(
|
|
95
96
|
"UNSUPPORTED_HOST_SEAM",
|
|
96
|
-
"Codex review requires explicit
|
|
97
|
+
"Codex review requires an explicit native-v1 or native-v2 profile after live interface qualification",
|
|
97
98
|
)
|
|
98
99
|
result = _artifactauthoritylib.arm_request(
|
|
99
100
|
root, client, args.artifact_id, args.record_id, args.activity,
|
|
@@ -313,7 +313,7 @@ class Host:
|
|
|
313
313
|
|
|
314
314
|
name = "claude"
|
|
315
315
|
adapter_name = "ca"
|
|
316
|
-
adapter_version = "2.25.
|
|
316
|
+
adapter_version = "2.25.5"
|
|
317
317
|
|
|
318
318
|
# Update-notifier descriptor. Each independently versioned host overrides
|
|
319
319
|
# these three values in its per-plugin _host.py. Keeping the target,
|
|
@@ -287,9 +287,11 @@ options. Local executables shadowing nested npm are rejected.
|
|
|
287
287
|
Direct Node script entrypoints are also pinned. These launch-file bindings do
|
|
288
288
|
not establish transitive dependency closure for deliberately excluded inputs.
|
|
289
289
|
|
|
290
|
-
New observations use the closed `declared-command/0.
|
|
291
|
-
retain collector and launch-file identities
|
|
292
|
-
|
|
290
|
+
New observations use the closed `declared-command/0.3.0` producer profile to
|
|
291
|
+
retain collector and launch-file identities plus an engine-produced normalized
|
|
292
|
+
completion closure. Raw workspace fingerprints must still match before and
|
|
293
|
+
after command execution. Historical `0.1.0` and `0.2.0` bindings retain their
|
|
294
|
+
original contracts and cannot be relabelled as newer evidence.
|
|
293
295
|
|
|
294
296
|
For `spec_review` or `quality_review`, arm the corresponding activity without
|
|
295
297
|
workspace mappings and dispatch the returned `launch_envelope` unchanged to one
|
|
@@ -332,10 +334,13 @@ acceptance. Scope review uses validated task completion bindings. If verificatio
|
|
|
332
334
|
is rerun, obtain and record its completed task review before selecting that run
|
|
333
335
|
for scope review; do not infer equivalence between receipts.
|
|
334
336
|
|
|
335
|
-
Creating
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
337
|
+
Creating a completion context for a `0.3.0` verification recomputes the normalized
|
|
338
|
+
worktree closure bound during that verification. Only validated native outputs
|
|
339
|
+
and execution bookkeeping are normalized; their publication must not invalidate
|
|
340
|
+
unchanged source. Historical `0.2.0` verification retains its initial exact raw
|
|
341
|
+
fingerprint comparison. Later freshness checks allow native task-status and
|
|
342
|
+
execution bookkeeping updates in validated canonical artifacts while retaining
|
|
343
|
+
their normative content hashes.
|
|
339
344
|
Changes to task definitions, other HTML or other governance files still affect
|
|
340
345
|
freshness under the normal snapshot policy. The closure covers
|
|
341
346
|
tracked working-tree bytes and non-ignored untracked files, not ignored
|
|
@@ -349,6 +354,16 @@ the scope reaches `REVIEW`, reverify against one current source snapshot, run th
|
|
|
349
354
|
combined quality review, and use `accept-scope` once. Do not accept tasks
|
|
350
355
|
individually.
|
|
351
356
|
|
|
357
|
+
Current Codex native V1 completion reviews retain the full frozen context in
|
|
358
|
+
its immutable, content-addressed file and keep only its closed reference in
|
|
359
|
+
mutable request state. Loading validates the exact context locator, hash,
|
|
360
|
+
canonical bytes and review kind. The native context budget remains 8 MiB;
|
|
361
|
+
mutable request state remains bounded at 1 MiB. Before dispatch, the adapter
|
|
362
|
+
reserves the exact fixed result identities, six times the 64 KiB decision
|
|
363
|
+
budget for canonical JSON escaping, and 32 bounded 256-byte lifecycle slots.
|
|
364
|
+
A request without that capacity is refused before child launch. Historical
|
|
365
|
+
inline requests retain their existing limits and abandonment-only recovery.
|
|
366
|
+
|
|
352
367
|
An ARMED review that never launched can be abandoned through the adapter:
|
|
353
368
|
|
|
354
369
|
```sh
|