@arbiterforge/ca-pi 0.17.5 → 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 +8 -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 +380 -34
- 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/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,14 @@ 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
|
+
|
|
7
15
|
## [0.17.5] - 2026-10-10
|
|
8
16
|
|
|
9
17
|
### Fixed
|
|
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,12 +82,14 @@ 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
|
-
#
|
|
85
|
+
# Verification and native completion reviews retain the complete immutable context by
|
|
86
86
|
# exact reference instead of duplicating it in every mutable request state.
|
|
87
87
|
CONTEXT_STORAGE = "frozen-reference/0.1.0"
|
|
88
88
|
CONTEXT_REFERENCE = {"format": "codearbiter.evidence-context-reference/0.1.0"}
|
|
89
89
|
# The native canonical decoder already bounds each complete context at 8 MiB.
|
|
90
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
|
|
91
93
|
# ASCII canonical JSON can expand each input UTF-8 byte to at most six bytes.
|
|
92
94
|
# Reserve that closed decision budget plus 32 bounded 256-byte lifecycle slots
|
|
93
95
|
# (host identities, content-addressed locators, their keys and delimiters).
|
|
@@ -114,9 +116,13 @@ CLAUDE_REVIEWER_MODELS = frozenset({"opus", "sonnet", "haiku"})
|
|
|
114
116
|
CLAUDE_REVIEW_PROFILE = "claude-review/0.1.0"
|
|
115
117
|
CODEX_REVIEW_PROFILE = "codex-review/0.1.0"
|
|
116
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})
|
|
117
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_]+)*")
|
|
118
123
|
CODEX_STEERING_TOOLS = frozenset({
|
|
119
124
|
"multi_agent_v1send_input", "multi_agent_v1resume_agent", "multi_agent_v1close_agent",
|
|
125
|
+
"collaborationsend_message", "collaborationfollowup_task", "collaborationinterrupt_agent",
|
|
120
126
|
})
|
|
121
127
|
CODEX_REVIEW_TOOLS = frozenset({"spawn_agent", "collaborationspawn_agent"}) | CODEX_STEERING_TOOLS
|
|
122
128
|
CODEX_CHILD_FORMAT = "codearbiter.codex-native-child/0.1.0"
|
|
@@ -416,7 +422,7 @@ def _request_lock(spool: Path, relative: Path, wait: float | None = None):
|
|
|
416
422
|
|
|
417
423
|
def _referenced_review_state(value: dict[str, Any]) -> dict[str, Any]:
|
|
418
424
|
if (value.get("context_storage") != CONTEXT_STORAGE
|
|
419
|
-
or value.get("codex_review_profile")
|
|
425
|
+
or value.get("codex_review_profile") not in CODEX_NATIVE_PROFILES
|
|
420
426
|
or value.get("host", "codex") != "codex"
|
|
421
427
|
or value.get("activity") not in REVIEW_ACTIVITIES
|
|
422
428
|
or "completion" not in value["context"] or "handback" in value):
|
|
@@ -442,13 +448,101 @@ def _referenced_review_state(value: dict[str, Any]) -> dict[str, Any]:
|
|
|
442
448
|
return dict(value, context=CONTEXT_REFERENCE)
|
|
443
449
|
|
|
444
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
|
+
|
|
445
539
|
def _save(root: Path, value: dict[str, Any]) -> None:
|
|
446
540
|
expected = value.get("integrity_sha256")
|
|
447
541
|
updated = dict(value)
|
|
448
542
|
if "context_storage" in updated:
|
|
449
543
|
if _read_context(root, updated["context_ref"], updated["context_sha256"]) != updated["context"]:
|
|
450
|
-
raise AuthorityError("INVALID_EVIDENCE_CONTEXT", "stored
|
|
451
|
-
updated =
|
|
544
|
+
raise AuthorityError("INVALID_EVIDENCE_CONTEXT", "stored context changed")
|
|
545
|
+
updated = _referenced_state(updated)
|
|
452
546
|
updated["integrity_sha256"] = _integrity(updated)
|
|
453
547
|
legacy_abandonment = (
|
|
454
548
|
value["state"] == "ABANDONED"
|
|
@@ -510,7 +604,7 @@ def _load(
|
|
|
510
604
|
"authority_source", "dispatch_prompt", "review_contract_sha256",
|
|
511
605
|
"required_coverage", "wrapper", "command_bindings", "recovery",
|
|
512
606
|
"launch_envelope", "workspace_roots", "host", "codex_review_profile",
|
|
513
|
-
"handback", "context_storage",
|
|
607
|
+
"handback", "context_storage", "codex_native_trace",
|
|
514
608
|
}
|
|
515
609
|
if (
|
|
516
610
|
not isinstance(value, dict)
|
|
@@ -533,7 +627,7 @@ def _load(
|
|
|
533
627
|
):
|
|
534
628
|
raise AuthorityError("INVALID_AUTHORITY_STATE", "request state failed validation")
|
|
535
629
|
if "codex_review_profile" in value and (
|
|
536
|
-
value["codex_review_profile"]
|
|
630
|
+
value["codex_review_profile"] not in CODEX_NATIVE_PROFILES
|
|
537
631
|
or value.get("host", "codex") != "codex"
|
|
538
632
|
or value["activity"] not in REVIEW_ACTIVITIES
|
|
539
633
|
):
|
|
@@ -545,7 +639,7 @@ def _load(
|
|
|
545
639
|
subject = value["context"].get("subject", {})
|
|
546
640
|
_validate_context(value["context"], subject.get("artifact_id"),
|
|
547
641
|
subject.get("record_id"), value["activity"])
|
|
548
|
-
|
|
642
|
+
_referenced_state(value)
|
|
549
643
|
elif value["context"] == CONTEXT_REFERENCE:
|
|
550
644
|
raise AuthorityError("INVALID_AUTHORITY_STATE", "context reference lacks its closed storage profile")
|
|
551
645
|
if len(raw) > MAX_STATE:
|
|
@@ -587,13 +681,16 @@ def _read_context(
|
|
|
587
681
|
raise AuthorityError("INVALID_EVIDENCE_CONTEXT", "context locator is not content addressed")
|
|
588
682
|
try:
|
|
589
683
|
path = root / expected_ref
|
|
684
|
+
_safe_directory(root, Path(expected_ref).parent, create=False)
|
|
590
685
|
_path_identity(path)
|
|
686
|
+
if not stat.S_ISREG(path.lstat().st_mode):
|
|
687
|
+
raise ValueError("context is not a regular file")
|
|
591
688
|
with path.open("rb") as stream:
|
|
592
689
|
raw = stream.read(MAX_EVIDENCE_CONTEXT + 1)
|
|
593
690
|
if len(raw) > MAX_EVIDENCE_CONTEXT:
|
|
594
691
|
raise ValueError("context exceeds the native canonical budget")
|
|
595
692
|
context = json.loads(raw.decode("utf-8"))
|
|
596
|
-
except (OSError, UnicodeError, ValueError) as exc:
|
|
693
|
+
except (OSError, UnicodeError, ValueError, AuthorityError) as exc:
|
|
597
694
|
raise AuthorityError("INVALID_EVIDENCE_CONTEXT", "context bytes are unavailable") from exc
|
|
598
695
|
if not isinstance(context, dict) or _digest(raw) != context_hash or raw != _native_context_canonical(context):
|
|
599
696
|
raise AuthorityError("INVALID_EVIDENCE_CONTEXT", "context bytes changed or are not canonical")
|
|
@@ -626,7 +723,10 @@ def _completion_client(root: Path) -> Any:
|
|
|
626
723
|
"""Use the native engine shipped with this adapter, never PATH or a caller."""
|
|
627
724
|
import _artifactlib
|
|
628
725
|
|
|
629
|
-
return _artifactlib.ArtifactClient(
|
|
726
|
+
return _artifactlib.ArtifactClient(
|
|
727
|
+
root, _artifactlib.helper_installation(__file__),
|
|
728
|
+
timeout=EVIDENCE_ENGINE_TIMEOUT_SECONDS,
|
|
729
|
+
)
|
|
630
730
|
|
|
631
731
|
|
|
632
732
|
def _context(
|
|
@@ -1079,6 +1179,7 @@ def _reject_overlapping_authority(session_id: str, turn_id: str) -> None:
|
|
|
1079
1179
|
launch = request.get("launch") or {}
|
|
1080
1180
|
if (
|
|
1081
1181
|
request["state"] in {"LAUNCHING", "RUNNING"}
|
|
1182
|
+
and request.get("codex_review_profile") != CODEX_NATIVE_V2
|
|
1082
1183
|
and launch.get("parent_session_id") == session_id
|
|
1083
1184
|
and launch.get("parent_turn_id") == turn_id
|
|
1084
1185
|
):
|
|
@@ -1399,6 +1500,10 @@ def _dispatch_prompt(request: dict[str, Any], *, legacy_inline: bool = False) ->
|
|
|
1399
1500
|
"these settings do not provide an OS sandbox.\n"
|
|
1400
1501
|
if request.get("codex_review_profile") == CODEX_NATIVE_V1 else ""
|
|
1401
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")
|
|
1402
1507
|
completion = ""
|
|
1403
1508
|
completion_fields = ""
|
|
1404
1509
|
if "completion" in context:
|
|
@@ -1426,7 +1531,8 @@ def _dispatch_prompt(request: dict[str, Any], *, legacy_inline: bool = False) ->
|
|
|
1426
1531
|
"done_when:<1-based index>. Each object has task_id, obligation, status (substantiated or "
|
|
1427
1532
|
"missing), assessment (nonempty reasons with concrete source locations, including file paths "
|
|
1428
1533
|
"and lines), and evidence_refs (nonempty unique exact selected receipt/source/event/observation/"
|
|
1429
|
-
"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 "
|
|
1430
1536
|
"that support each conclusion. Missing or unreadable facts require status=missing and "
|
|
1431
1537
|
"decision=changes_requested. A passing decision requires every completion obligation "
|
|
1432
1538
|
"substantiated; a claimed coverage list is insufficient.\n"
|
|
@@ -1434,6 +1540,19 @@ def _dispatch_prompt(request: dict[str, Any], *, legacy_inline: bool = False) ->
|
|
|
1434
1540
|
+ json.dumps(sorted(_completion_obligations(context)), ensure_ascii=True) + ".\n"
|
|
1435
1541
|
)
|
|
1436
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
|
+
)
|
|
1437
1556
|
return (
|
|
1438
1557
|
f"[CODEARBITER_AUTHORITY_REQUEST:{request['request_id']}]\n"
|
|
1439
1558
|
f"Target repository: {request['repository']['path']}\n"
|
|
@@ -1481,7 +1600,7 @@ def arm_request(
|
|
|
1481
1600
|
if host == "claude" and reviewer_model not in CLAUDE_REVIEWER_MODELS:
|
|
1482
1601
|
raise AuthorityError("INVALID_AUTHORITY_REQUEST", "reviewer model is unsupported")
|
|
1483
1602
|
if codex_review_profile is not None and (
|
|
1484
|
-
codex_review_profile
|
|
1603
|
+
codex_review_profile not in {"native-v1", "native-v2"} or host != "codex" or activity not in REVIEW_ACTIVITIES
|
|
1485
1604
|
):
|
|
1486
1605
|
raise AuthorityError("INVALID_AUTHORITY_REQUEST", "Codex review profile is unsupported")
|
|
1487
1606
|
completion_selection = None
|
|
@@ -1530,8 +1649,10 @@ def arm_request(
|
|
|
1530
1649
|
}
|
|
1531
1650
|
if host != "codex":
|
|
1532
1651
|
seed_value["host"] = host
|
|
1652
|
+
if activity == "verification":
|
|
1653
|
+
seed_value["context_storage"] = CONTEXT_STORAGE
|
|
1533
1654
|
if codex_review_profile is not None:
|
|
1534
|
-
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
|
|
1535
1656
|
if completion_selection is not None:
|
|
1536
1657
|
seed_value["context_storage"] = CONTEXT_STORAGE
|
|
1537
1658
|
seed = _canonical(seed_value)
|
|
@@ -1557,8 +1678,10 @@ def arm_request(
|
|
|
1557
1678
|
}
|
|
1558
1679
|
if host != "codex":
|
|
1559
1680
|
request["host"] = host
|
|
1681
|
+
if activity == "verification":
|
|
1682
|
+
request["context_storage"] = CONTEXT_STORAGE
|
|
1560
1683
|
if codex_review_profile is not None:
|
|
1561
|
-
request["codex_review_profile"] =
|
|
1684
|
+
request["codex_review_profile"] = seed_value["codex_review_profile"]
|
|
1562
1685
|
if completion_selection is not None:
|
|
1563
1686
|
request["context_storage"] = CONTEXT_STORAGE
|
|
1564
1687
|
if activity in REVIEW_ACTIVITIES:
|
|
@@ -1571,7 +1694,7 @@ def arm_request(
|
|
|
1571
1694
|
"subagent_type": CLAUDE_REVIEWER,
|
|
1572
1695
|
"model": reviewer_model,
|
|
1573
1696
|
}
|
|
1574
|
-
elif codex_review_profile
|
|
1697
|
+
elif codex_review_profile == "native-v1":
|
|
1575
1698
|
request["launch_envelope"] = {
|
|
1576
1699
|
"message": request["dispatch_prompt"], "fork_context": False,
|
|
1577
1700
|
}
|
|
@@ -1594,7 +1717,7 @@ def arm_request(
|
|
|
1594
1717
|
result["required_coverage"] = request["required_coverage"]
|
|
1595
1718
|
result["launch_envelope"] = request["launch_envelope"]
|
|
1596
1719
|
if codex_review_profile is not None:
|
|
1597
|
-
result["codex_review_profile"] =
|
|
1720
|
+
result["codex_review_profile"] = request["codex_review_profile"]
|
|
1598
1721
|
if completion_selection is not None:
|
|
1599
1722
|
result["completion_sha256"] = context["completion_sha256"]
|
|
1600
1723
|
return result
|
|
@@ -2820,6 +2943,145 @@ def _codex_uuid(value: Any) -> str:
|
|
|
2820
2943
|
return value
|
|
2821
2944
|
|
|
2822
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
|
+
|
|
2823
3085
|
def _codex_native_marker(agent_id: str) -> Path:
|
|
2824
3086
|
# UUID, not arrival order or session, is the direct host-observed join key.
|
|
2825
3087
|
# Retaining this marker also prevents reuse across requests and sessions.
|
|
@@ -2884,8 +3146,10 @@ def _read_codex_child(path: Path, agent_id: str, session_id: str) -> dict[str, A
|
|
|
2884
3146
|
start = value.get("start") if isinstance(value, dict) else None
|
|
2885
3147
|
if (
|
|
2886
3148
|
len(raw) > MAX_CHILD_MARKER or not isinstance(value, dict)
|
|
2887
|
-
or set(value)
|
|
2888
|
-
|
|
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)
|
|
2889
3153
|
or value.get("format") != CODEX_CHILD_FORMAT or value.get("agent_id") != agent_id
|
|
2890
3154
|
or not isinstance(value.get("session_id"), str) or not HOST_ID_RE.fullmatch(value["session_id"])
|
|
2891
3155
|
or (value.get("request_id") is not None and (
|
|
@@ -2898,6 +3162,19 @@ def _read_codex_child(path: Path, agent_id: str, session_id: str) -> dict[str, A
|
|
|
2898
3162
|
or not CODEX_UUID_RE.fullmatch(start["turn_id"])))
|
|
2899
3163
|
):
|
|
2900
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")
|
|
2901
3178
|
return value
|
|
2902
3179
|
|
|
2903
3180
|
|
|
@@ -2936,10 +3213,13 @@ def _codex_native_post(event: dict[str, Any], root: Path, request: dict[str, Any
|
|
|
2936
3213
|
_reject(root, current, "invalid-first-native-review-post")
|
|
2937
3214
|
if isinstance(exc, AuthorityError):
|
|
2938
3215
|
raise
|
|
2939
|
-
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "native
|
|
3216
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "native spawn result is malformed") from exc
|
|
2940
3217
|
|
|
2941
3218
|
|
|
2942
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")
|
|
2943
3223
|
if _canonical(event.get("tool_input")) != _canonical(request["launch_envelope"]):
|
|
2944
3224
|
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "native review PostToolUse envelope was changed")
|
|
2945
3225
|
_require_frozen_context(root, request)
|
|
@@ -2950,11 +3230,20 @@ def _codex_native_post_join(event: dict[str, Any], root: Path, request: dict[str
|
|
|
2950
3230
|
response = json.loads(raw, object_pairs_hook=_unique_json_pairs, parse_constant=_invalid_json_constant)
|
|
2951
3231
|
except ValueError as exc:
|
|
2952
3232
|
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "native-v1 spawn result is not strict JSON") from exc
|
|
2953
|
-
|
|
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"}
|
|
2954
3242
|
or (response["nickname"] is not None and (
|
|
2955
3243
|
not isinstance(response["nickname"], str) or len(response["nickname"]) > 256))):
|
|
2956
3244
|
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "native-v1 spawn result fields are unsupported")
|
|
2957
|
-
|
|
3245
|
+
else:
|
|
3246
|
+
agent_id = _codex_uuid(response.get("agent_id"))
|
|
2958
3247
|
session_id = request["launch"]["parent_session_id"]
|
|
2959
3248
|
with _codex_child_lock(agent_id, session_id) as (path, child):
|
|
2960
3249
|
request = _load(root, request["request_id"])
|
|
@@ -2970,7 +3259,11 @@ def _codex_native_post_join(event: dict[str, Any], root: Path, request: dict[str
|
|
|
2970
3259
|
_codex_native_refuse(root, request, "native reviewer stopped or conflicted before association")
|
|
2971
3260
|
launch = request["launch"]
|
|
2972
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))
|
|
2973
3264
|
if child["start"] is not None:
|
|
3265
|
+
if request.get("codex_review_profile") == CODEX_NATIVE_V2:
|
|
3266
|
+
_codex_v2_bind_child(request, child)
|
|
2974
3267
|
launch.update(child_turn_id=child["start"]["turn_id"], agent_type="default")
|
|
2975
3268
|
request["state"] = "RUNNING"
|
|
2976
3269
|
_save(root, request)
|
|
@@ -3011,7 +3304,7 @@ def _codex_native_lifecycle(event: dict[str, Any]) -> dict[str, Any] | None:
|
|
|
3011
3304
|
except (AuthorityError, OSError) as exc:
|
|
3012
3305
|
_claim_codex_event(agent_id, "failed")
|
|
3013
3306
|
for root, request in _registered_requests():
|
|
3014
|
-
if (request.get("codex_review_profile")
|
|
3307
|
+
if (request.get("codex_review_profile") in CODEX_NATIVE_PROFILES
|
|
3015
3308
|
and request["state"] in {"LAUNCHING", "RUNNING"}
|
|
3016
3309
|
and (request.get("launch") or {}).get("agent_id") == agent_id):
|
|
3017
3310
|
_reject(root, request, "failed-native-review-lifecycle")
|
|
@@ -3039,6 +3332,9 @@ def _codex_native_lifecycle_join(event: dict[str, Any]) -> dict[str, Any] | None
|
|
|
3039
3332
|
if name == "SubagentStart":
|
|
3040
3333
|
valid = valid and start is None
|
|
3041
3334
|
if valid:
|
|
3335
|
+
identity = _codex_v2_child(event)
|
|
3336
|
+
if identity is not None:
|
|
3337
|
+
child["native_v2"] = identity
|
|
3042
3338
|
child["start"] = {"turn_id": event["turn_id"], "agent_type": "default"}
|
|
3043
3339
|
else:
|
|
3044
3340
|
valid = (valid and start is not None and start["turn_id"] == event.get("turn_id")
|
|
@@ -3052,7 +3348,7 @@ def _codex_native_lifecycle_join(event: dict[str, Any]) -> dict[str, Any] | None
|
|
|
3052
3348
|
return None
|
|
3053
3349
|
root, request = _registered_request(child["request_id"])
|
|
3054
3350
|
launch = request.get("launch") or {}
|
|
3055
|
-
if (not valid or request.get("codex_review_profile")
|
|
3351
|
+
if (not valid or request.get("codex_review_profile") not in CODEX_NATIVE_PROFILES
|
|
3056
3352
|
or launch.get("parent_session_id") != child["session_id"]
|
|
3057
3353
|
or launch.get("agent_id") != agent_id or launch.get("post_confirmed") is not True
|
|
3058
3354
|
or event.get("turn_id") == launch.get("parent_turn_id")):
|
|
@@ -3060,6 +3356,8 @@ def _codex_native_lifecycle_join(event: dict[str, Any]) -> dict[str, Any] | None
|
|
|
3060
3356
|
if name == "SubagentStart":
|
|
3061
3357
|
if request["state"] != "LAUNCHING":
|
|
3062
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)
|
|
3063
3361
|
launch.update(child_turn_id=event["turn_id"], agent_type="default")
|
|
3064
3362
|
request["state"] = "RUNNING"
|
|
3065
3363
|
_save(root, request)
|
|
@@ -3100,6 +3398,43 @@ def _codex_native_steering(event: dict[str, Any]) -> None:
|
|
|
3100
3398
|
if not isinstance(tool_input, dict):
|
|
3101
3399
|
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "native reviewer steering input is malformed")
|
|
3102
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
|
|
3103
3438
|
if agent_id is None:
|
|
3104
3439
|
return None
|
|
3105
3440
|
_claim_codex_event(agent_id, "failed")
|
|
@@ -3123,25 +3458,28 @@ def observe_codex_hook(root: str | Path, event: dict[str, Any]) -> dict[str, Any
|
|
|
3123
3458
|
return native
|
|
3124
3459
|
session_id = _host_id(event.get("session_id"), "session_id")
|
|
3125
3460
|
|
|
3126
|
-
if name
|
|
3461
|
+
if name == "PreToolUse" and event.get("tool_name") == "collaborationspawn_agent":
|
|
3127
3462
|
tool_input = event.get("tool_input")
|
|
3128
3463
|
message = tool_input.get("message") if isinstance(tool_input, dict) else None
|
|
3129
3464
|
if isinstance(message, str) and message.startswith("[CODEARBITER_AUTHORITY_REQUEST:"):
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
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:
|
|
3135
3473
|
turn_id = _host_id(event.get("turn_id"), "turn_id")
|
|
3136
3474
|
_record_ordinary_spawn(session_id, turn_id)
|
|
3137
3475
|
_reject_overlapping_authority(session_id, turn_id)
|
|
3138
|
-
|
|
3476
|
+
return None
|
|
3139
3477
|
|
|
3140
3478
|
if name in {"PreToolUse", "PostToolUse"} and event.get("tool_name") in CODEX_STEERING_TOOLS:
|
|
3141
3479
|
return _codex_native_steering(event)
|
|
3142
3480
|
|
|
3143
3481
|
if name == "PreToolUse":
|
|
3144
|
-
if event.get("tool_name")
|
|
3482
|
+
if event.get("tool_name") not in {"spawn_agent", "collaborationspawn_agent"}:
|
|
3145
3483
|
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "only spawn_agent can launch a reviewer")
|
|
3146
3484
|
tool_use_id = _host_id(event.get("tool_use_id"), "tool_use_id")
|
|
3147
3485
|
turn_id = _host_id(event.get("turn_id"), "turn_id")
|
|
@@ -3163,8 +3501,11 @@ def observe_codex_hook(root: str | Path, event: dict[str, Any]) -> dict[str, Any
|
|
|
3163
3501
|
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "review request is not launchable")
|
|
3164
3502
|
if _canonical(tool_input) != _canonical(request.get("launch_envelope")):
|
|
3165
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")
|
|
3166
3507
|
_require_frozen_context(root, request)
|
|
3167
|
-
if _ordinary_spawn_marker(session_id, turn_id).exists():
|
|
3508
|
+
if not is_v2 and _ordinary_spawn_marker(session_id, turn_id).exists():
|
|
3168
3509
|
request["state"] = "REJECTED"
|
|
3169
3510
|
request["recovery"] = {
|
|
3170
3511
|
"mode": "overlapping-subagent-launch",
|
|
@@ -3185,6 +3526,11 @@ def observe_codex_hook(root: str | Path, event: dict[str, Any]) -> dict[str, Any
|
|
|
3185
3526
|
codex_review_profile=CODEX_NATIVE_V1, child_turn_id=None,
|
|
3186
3527
|
fork_context=False,
|
|
3187
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")
|
|
3188
3534
|
else:
|
|
3189
3535
|
request["launch"].update(task_name=tool_input["task_name"], fork_turns=tool_input["fork_turns"])
|
|
3190
3536
|
request["state"] = "LAUNCHING"
|
|
@@ -3192,7 +3538,7 @@ def observe_codex_hook(root: str | Path, event: dict[str, Any]) -> dict[str, Any
|
|
|
3192
3538
|
return {"request_id": request_id, "state": "LAUNCHING"}
|
|
3193
3539
|
|
|
3194
3540
|
if name == "PostToolUse":
|
|
3195
|
-
if event.get("tool_name")
|
|
3541
|
+
if event.get("tool_name") not in {"spawn_agent", "collaborationspawn_agent"}:
|
|
3196
3542
|
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "post-tool event is not reviewer launch")
|
|
3197
3543
|
tool_use_id = _host_id(event.get("tool_use_id"), "tool_use_id")
|
|
3198
3544
|
turn_id = _host_id(event.get("turn_id"), "turn_id")
|
|
@@ -3211,7 +3557,7 @@ def observe_codex_hook(root: str | Path, event: dict[str, Any]) -> dict[str, Any
|
|
|
3211
3557
|
if len(matches) != 1:
|
|
3212
3558
|
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "review launch correlation is ambiguous")
|
|
3213
3559
|
root, request = matches[0]
|
|
3214
|
-
if request.get("codex_review_profile")
|
|
3560
|
+
if request.get("codex_review_profile") in CODEX_NATIVE_PROFILES:
|
|
3215
3561
|
return _codex_native_post(event, root, request)
|
|
3216
3562
|
response = event.get("tool_response")
|
|
3217
3563
|
if not isinstance(response, dict):
|
|
@@ -3229,7 +3575,7 @@ def observe_codex_hook(root: str | Path, event: dict[str, Any]) -> dict[str, Any
|
|
|
3229
3575
|
matches = []
|
|
3230
3576
|
same_turn = []
|
|
3231
3577
|
for candidate_root, candidate in _registered_requests():
|
|
3232
|
-
if candidate.get("codex_review_profile")
|
|
3578
|
+
if candidate.get("codex_review_profile") in CODEX_NATIVE_PROFILES:
|
|
3233
3579
|
continue
|
|
3234
3580
|
launch = candidate.get("launch") or {}
|
|
3235
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,
|