@arbiterforge/ca-pi 0.14.9 → 0.14.12
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 +7 -2
- package/package.json +1 -1
- package/plugins/ca-pi/CHANGELOG.md +39 -0
- package/plugins/ca-pi/SKILLS.md +2 -2
- package/plugins/ca-pi/SPRINT.md +47 -0
- package/plugins/ca-pi/generated/command-catalog.json +2 -2
- package/plugins/ca-pi/helpers/artifacts/ca-artifact-darwin-amd64 +0 -0
- package/plugins/ca-pi/helpers/artifacts/ca-artifact-darwin-arm64 +0 -0
- package/plugins/ca-pi/helpers/artifacts/ca-artifact-linux-amd64 +0 -0
- package/plugins/ca-pi/helpers/artifacts/ca-artifact-linux-arm64 +0 -0
- package/plugins/ca-pi/helpers/artifacts/ca-artifact-windows-amd64.exe +0 -0
- package/plugins/ca-pi/helpers/artifacts/ca-artifact-windows-arm64.exe +0 -0
- package/plugins/ca-pi/helpers/artifacts/release.json +6 -6
- package/plugins/ca-pi/hooks/_approvallib.py +58 -22
- package/plugins/ca-pi/hooks/_artifactauthoritylib.py +1686 -0
- package/plugins/ca-pi/hooks/_artifactlib.py +14 -4
- package/plugins/ca-pi/hooks/_artifactpromptlib.py +177 -0
- package/plugins/ca-pi/hooks/_githooks.py +368 -39
- package/plugins/ca-pi/hooks/_host.py +1 -1
- package/plugins/ca-pi/hooks/_overrideappendlib.py +5 -1
- package/plugins/ca-pi/hooks/_prerequisitelib.py +44 -22
- package/plugins/ca-pi/hooks/_reconciliationlib.py +472 -0
- package/plugins/ca-pi/hooks/_sprintapprovallib.py +302 -0
- package/plugins/ca-pi/hooks/artifact-authority-hook.py +72 -0
- package/plugins/ca-pi/hooks/artifact-authority.py +80 -0
- package/plugins/ca-pi/hooks/artifact-reconcile.py +57 -0
- package/plugins/ca-pi/hooks/doctor.py +3 -1
- package/plugins/ca-pi/hooks/hostapi.py +1 -1
- package/plugins/ca-pi/hooks/prompt-submit.py +20 -3
- package/plugins/ca-pi/includes/artifacts.md +99 -12
- package/plugins/ca-pi/includes/sprint-authority.md +69 -0
- package/plugins/ca-pi/package.json +1 -1
- package/plugins/ca-pi/routines/INDEX.md +7 -4
- package/plugins/ca-pi/routines/brainstorming/SKILL.md +97 -29
- package/plugins/ca-pi/routines/commit-gate/SKILL.md +7 -1
- package/plugins/ca-pi/routines/executing-plans/SKILL.md +32 -15
- package/plugins/ca-pi/routines/skill-author/SKILL.md +26 -5
- package/plugins/ca-pi/routines/writing-plans/SKILL.md +22 -10
- package/plugins/ca-pi/skills/ca-commit/SKILL.md +170 -16
- package/plugins/ca-pi/skills/ca-new-skill/SKILL.md +116 -21
|
@@ -0,0 +1,1686 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# codeArbiter — durable closed authority producers for structured artifacts.
|
|
3
|
+
"""Supervise verification and correlate independent Codex review evidence.
|
|
4
|
+
|
|
5
|
+
arm_request(...) -> dict
|
|
6
|
+
run_verification(...) -> dict
|
|
7
|
+
observe_codex_hook(...) -> dict
|
|
8
|
+
publish_request(...) -> dict
|
|
9
|
+
|
|
10
|
+
The artifact engine owns every target binding. Callers select only an activity
|
|
11
|
+
and record; they cannot supply an authority kind, verdict, evidence payload, or
|
|
12
|
+
reviewer identity. External work is journaled before launch, observations are
|
|
13
|
+
immutable and content-addressed, and replay never silently reruns a completed
|
|
14
|
+
attempt. Codex review correlation uses stable hook fields and deliberately
|
|
15
|
+
ignores transcript bytes whose format is not a public contract.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import hashlib
|
|
21
|
+
import json
|
|
22
|
+
import os
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
import re
|
|
25
|
+
import secrets
|
|
26
|
+
import shlex
|
|
27
|
+
import shutil
|
|
28
|
+
import stat
|
|
29
|
+
import subprocess
|
|
30
|
+
import tempfile
|
|
31
|
+
import threading
|
|
32
|
+
import time
|
|
33
|
+
from typing import Any
|
|
34
|
+
|
|
35
|
+
from _gitexec import git_executable, root_bound_git_env
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
OBSERVATION_DIR = Path(".codearbiter/.artifacts/observations")
|
|
39
|
+
SOURCE_DIR = Path(".codearbiter/.artifacts/authority-sources")
|
|
40
|
+
STATE_FORMAT = "codearbiter.authority-run/0.1.0"
|
|
41
|
+
OBSERVATION_FORMAT = "codearbiter.observation/0.2.0"
|
|
42
|
+
EVENT_FORMAT = "codearbiter.workflow-event/0.2.0"
|
|
43
|
+
DECISION_FORMAT = "codearbiter.review-decision/0.1.0"
|
|
44
|
+
ACTIVITIES = frozenset({"verification", "spec_review", "quality_review"})
|
|
45
|
+
REVIEW_ACTIVITIES = frozenset({"spec_review", "quality_review"})
|
|
46
|
+
STATES = frozenset({
|
|
47
|
+
"ARMED", "LAUNCHING", "LAUNCHED", "RUNNING", "COMPLETED", "REJECTED",
|
|
48
|
+
"FAILED", "ABANDONED", "CAPTURED",
|
|
49
|
+
})
|
|
50
|
+
TERMINAL_STATES = frozenset({"REJECTED", "FAILED", "ABANDONED", "CAPTURED"})
|
|
51
|
+
SHA256_RE = re.compile(r"[0-9a-f]{64}")
|
|
52
|
+
ID_RE = re.compile(r"[A-Z][A-Z0-9_-]{0,127}")
|
|
53
|
+
REQUEST_RE = re.compile(r"[0-9a-f]{64}")
|
|
54
|
+
TOKEN_RE = re.compile(r"[A-Za-z0-9_-]{12,128}")
|
|
55
|
+
HOST_ID_RE = re.compile(r"[A-Za-z0-9][A-Za-z0-9_.:-]{0,255}")
|
|
56
|
+
MAX_STATE = 1 << 20
|
|
57
|
+
MAX_OUTPUT = 8 << 20
|
|
58
|
+
MAX_DECISION = 65536
|
|
59
|
+
REGISTRY_PARENT = Path(tempfile.gettempdir())
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class AuthorityError(RuntimeError):
|
|
63
|
+
def __init__(self, code: str, message: str):
|
|
64
|
+
self.code = code
|
|
65
|
+
super().__init__(f"{code}: {message}")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _canonical(value: Any) -> bytes:
|
|
69
|
+
try:
|
|
70
|
+
return json.dumps(
|
|
71
|
+
value, ensure_ascii=True, allow_nan=False, sort_keys=True,
|
|
72
|
+
separators=(",", ":"),
|
|
73
|
+
).encode("utf-8")
|
|
74
|
+
except (TypeError, ValueError) as exc:
|
|
75
|
+
raise AuthorityError("INVALID_AUTHORITY_STATE", "state is not finite JSON") from exc
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _digest(data: bytes) -> str:
|
|
79
|
+
return hashlib.sha256(data).hexdigest()
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _integrity(value: dict[str, Any]) -> str:
|
|
83
|
+
unsigned = dict(value)
|
|
84
|
+
unsigned.pop("integrity_sha256", None)
|
|
85
|
+
return _digest(_canonical(unsigned))
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _real_root(root: str | Path) -> Path:
|
|
89
|
+
try:
|
|
90
|
+
resolved = Path(root).resolve(strict=True)
|
|
91
|
+
info = resolved.lstat()
|
|
92
|
+
except OSError as exc:
|
|
93
|
+
raise AuthorityError("UNSAFE_REPOSITORY", "repository root is unavailable") from exc
|
|
94
|
+
reparse = getattr(info, "st_file_attributes", 0) & 0x400
|
|
95
|
+
if stat.S_ISLNK(info.st_mode) or reparse or not stat.S_ISDIR(info.st_mode):
|
|
96
|
+
raise AuthorityError("UNSAFE_REPOSITORY", "repository root must be a real directory")
|
|
97
|
+
return resolved
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _safe_directory(root: Path, relative: Path, *, create: bool) -> Path:
|
|
101
|
+
current = root
|
|
102
|
+
for part in relative.parts:
|
|
103
|
+
current = current / part
|
|
104
|
+
try:
|
|
105
|
+
info = current.lstat()
|
|
106
|
+
except FileNotFoundError:
|
|
107
|
+
if not create:
|
|
108
|
+
raise
|
|
109
|
+
current.mkdir()
|
|
110
|
+
info = current.lstat()
|
|
111
|
+
reparse = getattr(info, "st_file_attributes", 0) & 0x400
|
|
112
|
+
if stat.S_ISLNK(info.st_mode) or reparse or not stat.S_ISDIR(info.st_mode):
|
|
113
|
+
raise AuthorityError("UNSAFE_AUTHORITY_PATH", "authority directories must be real")
|
|
114
|
+
return current
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _atomic_replace(root: Path, relative: Path, data: bytes) -> None:
|
|
118
|
+
parent = _safe_directory(root, relative.parent, create=True)
|
|
119
|
+
temporary = parent / f".{relative.name}.{secrets.token_hex(8)}.tmp"
|
|
120
|
+
flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
|
|
121
|
+
if hasattr(os, "O_BINARY"):
|
|
122
|
+
flags |= os.O_BINARY
|
|
123
|
+
fd = os.open(temporary, flags, 0o600)
|
|
124
|
+
try:
|
|
125
|
+
view = memoryview(data)
|
|
126
|
+
while view:
|
|
127
|
+
written = os.write(fd, view)
|
|
128
|
+
if written <= 0:
|
|
129
|
+
raise OSError("short authority-state write")
|
|
130
|
+
view = view[written:]
|
|
131
|
+
os.fsync(fd)
|
|
132
|
+
finally:
|
|
133
|
+
os.close(fd)
|
|
134
|
+
try:
|
|
135
|
+
os.replace(temporary, root / relative)
|
|
136
|
+
finally:
|
|
137
|
+
try:
|
|
138
|
+
temporary.unlink()
|
|
139
|
+
except FileNotFoundError:
|
|
140
|
+
pass
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _publish_immutable(root: Path, relative: Path, data: bytes) -> None:
|
|
144
|
+
parent = _safe_directory(root, relative.parent, create=True)
|
|
145
|
+
target = parent / relative.name
|
|
146
|
+
temporary = parent / f".{relative.name}.{secrets.token_hex(8)}.tmp"
|
|
147
|
+
try:
|
|
148
|
+
flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
|
|
149
|
+
if hasattr(os, "O_BINARY"):
|
|
150
|
+
flags |= os.O_BINARY
|
|
151
|
+
fd = os.open(temporary, flags, 0o600)
|
|
152
|
+
view = memoryview(data)
|
|
153
|
+
while view:
|
|
154
|
+
written = os.write(fd, view)
|
|
155
|
+
if written <= 0:
|
|
156
|
+
raise OSError("short immutable observation write")
|
|
157
|
+
view = view[written:]
|
|
158
|
+
os.fsync(fd)
|
|
159
|
+
finally:
|
|
160
|
+
if 'fd' in locals():
|
|
161
|
+
os.close(fd)
|
|
162
|
+
try:
|
|
163
|
+
try:
|
|
164
|
+
os.link(temporary, target)
|
|
165
|
+
except FileExistsError:
|
|
166
|
+
try:
|
|
167
|
+
info = target.lstat()
|
|
168
|
+
reparse = getattr(info, "st_file_attributes", 0) & 0x400
|
|
169
|
+
if stat.S_ISLNK(info.st_mode) or reparse or not stat.S_ISREG(info.st_mode) or info.st_size != len(data):
|
|
170
|
+
raise OSError("unsafe immutable authority target")
|
|
171
|
+
flags = os.O_RDONLY | getattr(os, "O_BINARY", 0) | getattr(os, "O_NOFOLLOW", 0)
|
|
172
|
+
with os.fdopen(os.open(target, flags), "rb") as existing_file:
|
|
173
|
+
opened = os.fstat(existing_file.fileno())
|
|
174
|
+
if (
|
|
175
|
+
not stat.S_ISREG(opened.st_mode) or opened.st_size != len(data)
|
|
176
|
+
or (opened.st_dev, opened.st_ino) != (info.st_dev, info.st_ino)
|
|
177
|
+
):
|
|
178
|
+
raise OSError("immutable authority target changed during read")
|
|
179
|
+
existing = existing_file.read(len(data) + 1)
|
|
180
|
+
after = target.lstat()
|
|
181
|
+
if (after.st_dev, after.st_ino) != (info.st_dev, info.st_ino):
|
|
182
|
+
raise OSError("immutable authority target changed during read")
|
|
183
|
+
except OSError as exc:
|
|
184
|
+
raise AuthorityError("UNSAFE_AUTHORITY_PATH", "immutable authority target is unsafe") from exc
|
|
185
|
+
if existing != data:
|
|
186
|
+
raise AuthorityError("AUTHORITY_COLLISION", "content-addressed bytes differ")
|
|
187
|
+
finally:
|
|
188
|
+
try:
|
|
189
|
+
temporary.unlink()
|
|
190
|
+
except FileNotFoundError:
|
|
191
|
+
pass
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _request_path(request_id: str) -> Path:
|
|
195
|
+
if not isinstance(request_id, str) or REQUEST_RE.fullmatch(request_id) is None:
|
|
196
|
+
raise AuthorityError("INVALID_REQUEST_ID", "request identity is malformed")
|
|
197
|
+
return Path(f"{request_id}.json")
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _spool_root(root: Path) -> Path:
|
|
201
|
+
try:
|
|
202
|
+
# Callers may supply an alias for the same repository (notably /var on
|
|
203
|
+
# macOS). Use the canonical root for both Git and the spool identity.
|
|
204
|
+
root = _real_root(root)
|
|
205
|
+
result = subprocess.run(
|
|
206
|
+
[git_executable(), "-C", str(root), "rev-parse", "--path-format=absolute", "--git-common-dir"],
|
|
207
|
+
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, encoding="utf-8",
|
|
208
|
+
errors="replace", timeout=5, check=False, env=root_bound_git_env(),
|
|
209
|
+
)
|
|
210
|
+
if result.returncode != 0 or not result.stdout.strip():
|
|
211
|
+
raise OSError("git common directory unavailable")
|
|
212
|
+
common = Path(result.stdout.strip()).resolve(strict=True)
|
|
213
|
+
info = common.lstat()
|
|
214
|
+
reparse = getattr(info, "st_file_attributes", 0) & 0x400
|
|
215
|
+
if stat.S_ISLNK(info.st_mode) or reparse or not stat.S_ISDIR(info.st_mode):
|
|
216
|
+
raise OSError("git common directory is unsafe")
|
|
217
|
+
worktree_id = _digest(_canonical(_repository_identity(root)))[:32]
|
|
218
|
+
spool = _safe_directory(
|
|
219
|
+
common, Path("codearbiter") / "authority-runs" / worktree_id, create=True
|
|
220
|
+
)
|
|
221
|
+
spool_info = spool.lstat()
|
|
222
|
+
spool_reparse = getattr(spool_info, "st_file_attributes", 0) & 0x400
|
|
223
|
+
if stat.S_ISLNK(spool_info.st_mode) or spool_reparse or not stat.S_ISDIR(spool_info.st_mode):
|
|
224
|
+
raise OSError("authority spool is unsafe")
|
|
225
|
+
return spool
|
|
226
|
+
except (OSError, subprocess.SubprocessError) as exc:
|
|
227
|
+
raise AuthorityError("AUTHORITY_BUSY", "resolved git-common authority spool is unavailable") from exc
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _save(root: Path, value: dict[str, Any]) -> None:
|
|
231
|
+
value = dict(value)
|
|
232
|
+
value["integrity_sha256"] = _integrity(value)
|
|
233
|
+
_atomic_replace(_spool_root(root), _request_path(value["request_id"]), _canonical(value))
|
|
234
|
+
if value["state"] in TERMINAL_STATES:
|
|
235
|
+
(_registry_root() / f"{value['request_id']}.json").unlink(missing_ok=True)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _load(root: Path, request_id: str) -> dict[str, Any]:
|
|
239
|
+
path = _spool_root(root) / _request_path(request_id)
|
|
240
|
+
try:
|
|
241
|
+
info = path.lstat()
|
|
242
|
+
reparse = getattr(info, "st_file_attributes", 0) & 0x400
|
|
243
|
+
if stat.S_ISLNK(info.st_mode) or reparse or not stat.S_ISREG(info.st_mode):
|
|
244
|
+
raise OSError("not regular")
|
|
245
|
+
if info.st_size > MAX_STATE:
|
|
246
|
+
raise OSError("oversized")
|
|
247
|
+
value = json.loads(path.read_text(encoding="utf-8"))
|
|
248
|
+
except (OSError, UnicodeError, ValueError) as exc:
|
|
249
|
+
raise AuthorityError("INVALID_AUTHORITY_STATE", "request state is unreadable") from exc
|
|
250
|
+
allowed = {
|
|
251
|
+
"format", "request_id", "state", "activity", "repository", "context",
|
|
252
|
+
"context_ref", "context_sha256", "attempt", "launch", "observation_ref",
|
|
253
|
+
"observation_sha256", "receipt", "integrity_sha256", "payload",
|
|
254
|
+
"authority_source", "dispatch_prompt", "review_contract_sha256",
|
|
255
|
+
"required_coverage", "wrapper", "command_bindings", "recovery",
|
|
256
|
+
"launch_envelope", "workspace_roots",
|
|
257
|
+
}
|
|
258
|
+
if (
|
|
259
|
+
not isinstance(value, dict)
|
|
260
|
+
or set(value) - allowed
|
|
261
|
+
or value.get("format") != STATE_FORMAT
|
|
262
|
+
or value.get("request_id") != request_id
|
|
263
|
+
or value.get("activity") not in ACTIVITIES
|
|
264
|
+
or value.get("state") not in STATES
|
|
265
|
+
or not isinstance(value.get("context"), dict)
|
|
266
|
+
or not isinstance(value.get("repository"), dict)
|
|
267
|
+
or not isinstance(value.get("command_bindings", []), list)
|
|
268
|
+
or not isinstance(value.get("workspace_roots", {}), dict)
|
|
269
|
+
or any(not isinstance(key, str) or not isinstance(path, str)
|
|
270
|
+
for key, path in value.get("workspace_roots", {}).items())
|
|
271
|
+
or value.get("integrity_sha256") != _integrity(value)
|
|
272
|
+
or not isinstance(value.get("context_ref"), str)
|
|
273
|
+
or SHA256_RE.fullmatch(value.get("context_sha256", "")) is None
|
|
274
|
+
):
|
|
275
|
+
raise AuthorityError("INVALID_AUTHORITY_STATE", "request state failed validation")
|
|
276
|
+
return value
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _validate_hash(value: Any, field: str) -> str:
|
|
280
|
+
if not isinstance(value, str) or SHA256_RE.fullmatch(value) is None:
|
|
281
|
+
raise AuthorityError("INVALID_EVIDENCE_CONTEXT", f"{field} is malformed")
|
|
282
|
+
return value
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _context(
|
|
286
|
+
root: Path, client: Any, artifact_id: str, record_id: str, activity: str
|
|
287
|
+
) -> tuple[dict[str, Any], str, str]:
|
|
288
|
+
result = client.call("evidence-context", {
|
|
289
|
+
"artifact_id": artifact_id, "record_id": record_id, "activity": activity,
|
|
290
|
+
})
|
|
291
|
+
if not isinstance(result, dict):
|
|
292
|
+
raise AuthorityError("INVALID_EVIDENCE_CONTEXT", "engine context result is malformed")
|
|
293
|
+
context_hash = _validate_hash(result.get("context_sha256"), "context_sha256")
|
|
294
|
+
context_ref = result.get("context_ref")
|
|
295
|
+
expected_ref = f".codearbiter/.artifacts/evidence-contexts/{context_hash}.json"
|
|
296
|
+
if context_ref != expected_ref:
|
|
297
|
+
raise AuthorityError("INVALID_EVIDENCE_CONTEXT", "context locator is not content addressed")
|
|
298
|
+
try:
|
|
299
|
+
raw = (root / expected_ref).read_bytes()
|
|
300
|
+
context = json.loads(raw.decode("utf-8"))
|
|
301
|
+
except (OSError, UnicodeError, ValueError) as exc:
|
|
302
|
+
raise AuthorityError("INVALID_EVIDENCE_CONTEXT", "context bytes are unavailable") from exc
|
|
303
|
+
if _digest(raw) != context_hash or raw != _canonical(context):
|
|
304
|
+
raise AuthorityError("INVALID_EVIDENCE_CONTEXT", "context bytes changed or are not canonical")
|
|
305
|
+
subject = context.get("subject") if isinstance(context, dict) else None
|
|
306
|
+
if (
|
|
307
|
+
not isinstance(context, dict)
|
|
308
|
+
or context.get("format") != "codearbiter.evidence-context/0.1.0"
|
|
309
|
+
or not isinstance(subject, dict)
|
|
310
|
+
or subject.get("artifact_id") != artifact_id
|
|
311
|
+
or subject.get("record_id") != record_id
|
|
312
|
+
or context["activity"] != activity
|
|
313
|
+
or not ID_RE.fullmatch(artifact_id)
|
|
314
|
+
or not ID_RE.fullmatch(record_id)
|
|
315
|
+
):
|
|
316
|
+
raise AuthorityError("INVALID_EVIDENCE_CONTEXT", "engine context changed selection")
|
|
317
|
+
for field in ("input_sha256", "spec_sha256", "plan_sha256"):
|
|
318
|
+
_validate_hash(context.get(field), field)
|
|
319
|
+
_validate_hash(subject.get("normative_sha256"), "normative_sha256")
|
|
320
|
+
if activity in {"verification", "spec_review"}:
|
|
321
|
+
_validate_hash(context.get("task_sha256"), "task_sha256")
|
|
322
|
+
if activity == "verification":
|
|
323
|
+
commands = context.get("commands")
|
|
324
|
+
if not isinstance(commands, list) or not commands:
|
|
325
|
+
raise AuthorityError("INVALID_EVIDENCE_CONTEXT", "verification has no commands")
|
|
326
|
+
for command in commands:
|
|
327
|
+
definition = command.get("definition") if isinstance(command, dict) else None
|
|
328
|
+
if (
|
|
329
|
+
not isinstance(command, dict)
|
|
330
|
+
or set(command) != {"definition_sha256", "definition"}
|
|
331
|
+
or not isinstance(definition, dict)
|
|
332
|
+
or not {"argv", "cwd", "expected_exit", "required_tests"} <= set(definition)
|
|
333
|
+
or not isinstance(definition.get("argv"), list)
|
|
334
|
+
or not definition.get("argv")
|
|
335
|
+
or any(not isinstance(item, str) or not item for item in definition.get("argv", []))
|
|
336
|
+
or not isinstance(definition.get("cwd"), str)
|
|
337
|
+
or not definition.get("cwd")
|
|
338
|
+
or type(definition.get("expected_exit")) is not int
|
|
339
|
+
or not 0 <= definition.get("expected_exit", -1) <= 255
|
|
340
|
+
or not isinstance(definition.get("required_tests"), list)
|
|
341
|
+
or any(not isinstance(item, str) or not item for item in definition.get("required_tests", []))
|
|
342
|
+
or len(set(definition.get("required_tests", []))) != len(definition.get("required_tests", []))
|
|
343
|
+
):
|
|
344
|
+
raise AuthorityError("INVALID_EVIDENCE_CONTEXT", "command definition is malformed")
|
|
345
|
+
_validate_hash(command["definition_sha256"], "definition_sha256")
|
|
346
|
+
elif activity == "spec_review":
|
|
347
|
+
if not isinstance(context.get("task"), dict):
|
|
348
|
+
raise AuthorityError("INVALID_EVIDENCE_CONTEXT", "review task is absent")
|
|
349
|
+
else:
|
|
350
|
+
_validate_hash(context.get("base_input_sha256"), "base_input_sha256")
|
|
351
|
+
if not isinstance(context.get("task_hashes"), dict) or not isinstance(context.get("tasks"), list):
|
|
352
|
+
raise AuthorityError("INVALID_EVIDENCE_CONTEXT", "quality-review scope is incomplete")
|
|
353
|
+
return context, context_ref, context_hash
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def _review_binding(context: dict[str, Any]) -> tuple[str, list[str]]:
|
|
357
|
+
contract = {
|
|
358
|
+
"format": DECISION_FORMAT,
|
|
359
|
+
"decision": ["pass", "changes_requested"],
|
|
360
|
+
"finding_severity": ["BLOCK", "WARN", "INFO"],
|
|
361
|
+
"required_fields": [
|
|
362
|
+
"format", "request_id", "target_sha256", "contract_sha256",
|
|
363
|
+
"decision", "coverage", "findings", "assessment",
|
|
364
|
+
],
|
|
365
|
+
}
|
|
366
|
+
records = [context["task"]] if context["activity"] == "spec_review" else context["tasks"]
|
|
367
|
+
coverage = sorted({
|
|
368
|
+
ref for record in records for ref in record.get("criterion_refs", [])
|
|
369
|
+
if isinstance(ref, str) and ref
|
|
370
|
+
})
|
|
371
|
+
if not coverage:
|
|
372
|
+
raise AuthorityError("INVALID_EVIDENCE_CONTEXT", "review target has no criterion coverage")
|
|
373
|
+
return _digest(_canonical(contract)), coverage
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def _repository_identity(root: Path) -> dict[str, str]:
|
|
377
|
+
info = os.stat(root)
|
|
378
|
+
return {
|
|
379
|
+
"path": str(root),
|
|
380
|
+
"filesystem_id": f"{int(getattr(info, 'st_dev', 0))}:{int(getattr(info, 'st_ino', 0))}",
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def _registry_root() -> Path:
|
|
385
|
+
namespace = f"uid-{os.getuid()}" if hasattr(os, "getuid") else "current-user"
|
|
386
|
+
root = REGISTRY_PARENT.resolve(strict=True) / f"codearbiter-authority-{namespace}"
|
|
387
|
+
try:
|
|
388
|
+
root.mkdir(mode=0o700, exist_ok=True)
|
|
389
|
+
info = root.lstat()
|
|
390
|
+
reparse = getattr(info, "st_file_attributes", 0) & 0x400
|
|
391
|
+
if stat.S_ISLNK(info.st_mode) or reparse or not stat.S_ISDIR(info.st_mode):
|
|
392
|
+
raise OSError("unsafe registry")
|
|
393
|
+
if hasattr(os, "getuid"):
|
|
394
|
+
if info.st_uid != os.getuid():
|
|
395
|
+
raise OSError("foreign registry")
|
|
396
|
+
os.chmod(root, 0o700)
|
|
397
|
+
except OSError as exc:
|
|
398
|
+
raise AuthorityError("AUTHORITY_BUSY", "authority registry is unavailable") from exc
|
|
399
|
+
return root
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def _register_request(root: Path, request: dict[str, Any]) -> None:
|
|
403
|
+
pointer = {
|
|
404
|
+
"format": "codearbiter.authority-pointer/0.1.0",
|
|
405
|
+
"request_id": request["request_id"],
|
|
406
|
+
"repository": request["repository"],
|
|
407
|
+
}
|
|
408
|
+
pointer["integrity_sha256"] = _integrity(pointer)
|
|
409
|
+
registry = _registry_root()
|
|
410
|
+
target = registry / f"{request['request_id']}.json"
|
|
411
|
+
data = _canonical(pointer)
|
|
412
|
+
try:
|
|
413
|
+
existing = target.read_bytes()
|
|
414
|
+
except FileNotFoundError:
|
|
415
|
+
flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
|
|
416
|
+
if hasattr(os, "O_BINARY"):
|
|
417
|
+
flags |= os.O_BINARY
|
|
418
|
+
fd = os.open(target, flags, 0o600)
|
|
419
|
+
try:
|
|
420
|
+
view = memoryview(data)
|
|
421
|
+
while view:
|
|
422
|
+
written = os.write(fd, view)
|
|
423
|
+
if written <= 0:
|
|
424
|
+
raise OSError("short authority-pointer write")
|
|
425
|
+
view = view[written:]
|
|
426
|
+
os.fsync(fd)
|
|
427
|
+
finally:
|
|
428
|
+
os.close(fd)
|
|
429
|
+
else:
|
|
430
|
+
if existing != data:
|
|
431
|
+
raise AuthorityError("AUTHORITY_COLLISION", "request registry entry differs")
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
def _registered_requests() -> list[tuple[Path, dict[str, Any]]]:
|
|
435
|
+
results = []
|
|
436
|
+
for path in _registry_root().glob("*.json"):
|
|
437
|
+
try:
|
|
438
|
+
raw = path.read_bytes()
|
|
439
|
+
if len(raw) > 16384:
|
|
440
|
+
continue
|
|
441
|
+
pointer = json.loads(raw.decode("utf-8"))
|
|
442
|
+
repository = pointer.get("repository") if isinstance(pointer, dict) else None
|
|
443
|
+
if (
|
|
444
|
+
not isinstance(pointer, dict)
|
|
445
|
+
or pointer.get("format") != "codearbiter.authority-pointer/0.1.0"
|
|
446
|
+
or pointer.get("request_id") != path.stem
|
|
447
|
+
or pointer.get("integrity_sha256") != _integrity(pointer)
|
|
448
|
+
or not isinstance(repository, dict)
|
|
449
|
+
):
|
|
450
|
+
continue
|
|
451
|
+
root = _real_root(repository.get("path"))
|
|
452
|
+
if _repository_identity(root) != repository:
|
|
453
|
+
continue
|
|
454
|
+
request = _load(root, path.stem)
|
|
455
|
+
if request["state"] in TERMINAL_STATES:
|
|
456
|
+
path.unlink(missing_ok=True)
|
|
457
|
+
continue
|
|
458
|
+
results.append((root, request))
|
|
459
|
+
except (AuthorityError, OSError, UnicodeError, ValueError):
|
|
460
|
+
continue
|
|
461
|
+
return results
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def _registered_request(request_id: str) -> tuple[Path, dict[str, Any]]:
|
|
465
|
+
matches = [item for item in _registered_requests() if item[1]["request_id"] == request_id]
|
|
466
|
+
if len(matches) != 1:
|
|
467
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "authority request routing is ambiguous")
|
|
468
|
+
return matches[0]
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def _ordinary_spawn_marker(session_id: str, turn_id: str) -> Path:
|
|
472
|
+
key = _digest(_canonical({"session_id": session_id, "turn_id": turn_id}))
|
|
473
|
+
return _registry_root() / f"ordinary-spawn-{key}.marker"
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
def _record_ordinary_spawn(session_id: str, turn_id: str) -> None:
|
|
477
|
+
marker = _ordinary_spawn_marker(session_id, turn_id)
|
|
478
|
+
try:
|
|
479
|
+
fd = os.open(marker, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
|
|
480
|
+
except FileExistsError:
|
|
481
|
+
return
|
|
482
|
+
try:
|
|
483
|
+
os.write(fd, b"ordinary spawn observed\n")
|
|
484
|
+
os.fsync(fd)
|
|
485
|
+
finally:
|
|
486
|
+
os.close(fd)
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
def _reject_overlapping_authority(session_id: str, turn_id: str) -> None:
|
|
490
|
+
for root, request in _registered_requests():
|
|
491
|
+
launch = request.get("launch") or {}
|
|
492
|
+
if (
|
|
493
|
+
request["state"] in {"LAUNCHING", "RUNNING"}
|
|
494
|
+
and launch.get("parent_session_id") == session_id
|
|
495
|
+
and launch.get("parent_turn_id") == turn_id
|
|
496
|
+
):
|
|
497
|
+
request["state"] = "REJECTED"
|
|
498
|
+
request["recovery"] = {
|
|
499
|
+
"mode": "overlapping-subagent-launch",
|
|
500
|
+
"rerun_permitted": False,
|
|
501
|
+
}
|
|
502
|
+
_save(root, request)
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def _git_text(directory: Path, *args: str) -> str:
|
|
506
|
+
try:
|
|
507
|
+
result = subprocess.run(
|
|
508
|
+
[git_executable(), "-C", str(directory), *args],
|
|
509
|
+
stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False,
|
|
510
|
+
timeout=15, env=root_bound_git_env(),
|
|
511
|
+
)
|
|
512
|
+
except (OSError, subprocess.SubprocessError) as exc:
|
|
513
|
+
raise AuthorityError("UNSUPPORTED_WORKSPACE", "git workspace identity is unavailable") from exc
|
|
514
|
+
if result.returncode != 0:
|
|
515
|
+
raise AuthorityError("UNSUPPORTED_WORKSPACE", "git workspace identity is unavailable")
|
|
516
|
+
decoded = result.stdout.decode("utf-8", "strict")
|
|
517
|
+
return decoded if "-z" in args else decoded.strip()
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
def _path_identity(path: Path) -> str:
|
|
521
|
+
info = path.lstat()
|
|
522
|
+
reparse = getattr(info, "st_file_attributes", 0) & 0x400
|
|
523
|
+
if stat.S_ISLNK(info.st_mode) or reparse:
|
|
524
|
+
raise AuthorityError("UNSUPPORTED_WORKSPACE", "workspace path is replaceable indirection")
|
|
525
|
+
return f"{int(getattr(info, 'st_dev', 0))}:{int(getattr(info, 'st_ino', 0))}"
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
def _file_sha256(path: Path) -> str:
|
|
529
|
+
digest = hashlib.sha256()
|
|
530
|
+
try:
|
|
531
|
+
with path.open("rb") as stream:
|
|
532
|
+
while True:
|
|
533
|
+
block = stream.read(1 << 20)
|
|
534
|
+
if not block:
|
|
535
|
+
break
|
|
536
|
+
digest.update(block)
|
|
537
|
+
except OSError as exc:
|
|
538
|
+
raise AuthorityError("UNSUPPORTED_EXECUTABLE", "declared executable is unreadable") from exc
|
|
539
|
+
return digest.hexdigest()
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
def _workspace_for(
|
|
543
|
+
root: Path, label: str, workspace_roots: dict[str, str]
|
|
544
|
+
) -> tuple[Path, Path, Path]:
|
|
545
|
+
if label in workspace_roots:
|
|
546
|
+
cwd = _real_root(workspace_roots[label])
|
|
547
|
+
declared_top = _real_root(_git_text(cwd, "rev-parse", "--show-toplevel"))
|
|
548
|
+
if declared_top != cwd:
|
|
549
|
+
raise AuthorityError("UNSUPPORTED_WORKSPACE", "mapped workspace is not an exact Git worktree root")
|
|
550
|
+
elif label in {".", "current worktree", "candidate worktree"}:
|
|
551
|
+
cwd = root
|
|
552
|
+
else:
|
|
553
|
+
candidate = Path(label)
|
|
554
|
+
if candidate.is_absolute() or ".." in candidate.parts:
|
|
555
|
+
raise AuthorityError("UNSUPPORTED_WORKSPACE", "declared cwd escapes the artifact worktree")
|
|
556
|
+
cwd = _resolved_directory(root / candidate)
|
|
557
|
+
worktree = _real_root(_git_text(cwd, "rev-parse", "--show-toplevel"))
|
|
558
|
+
if label not in workspace_roots and worktree != root:
|
|
559
|
+
raise AuthorityError("UNSUPPORTED_WORKSPACE", "declared cwd is not in the artifact worktree")
|
|
560
|
+
common = _real_root(_git_text(worktree, "rev-parse", "--path-format=absolute", "--git-common-dir"))
|
|
561
|
+
artifact_common = _real_root(_git_text(root, "rev-parse", "--path-format=absolute", "--git-common-dir"))
|
|
562
|
+
if common != artifact_common:
|
|
563
|
+
raise AuthorityError("UNSUPPORTED_WORKSPACE", "workspace does not share the artifact git-common-dir")
|
|
564
|
+
return cwd, worktree, common
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
def _resolve_executable(name: str, cwd: Path) -> Path:
|
|
568
|
+
candidate = Path(name)
|
|
569
|
+
if candidate.is_absolute() or candidate.parent != Path("."):
|
|
570
|
+
path = (cwd / candidate).resolve(strict=True) if not candidate.is_absolute() else candidate.resolve(strict=True)
|
|
571
|
+
else:
|
|
572
|
+
located = shutil.which(name, path=os.environ.get("PATH", ""))
|
|
573
|
+
if not located:
|
|
574
|
+
raise AuthorityError("UNSUPPORTED_EXECUTABLE", "declared executable is not resolvable")
|
|
575
|
+
path = Path(located).resolve(strict=True)
|
|
576
|
+
if not path.is_file():
|
|
577
|
+
raise AuthorityError("UNSUPPORTED_EXECUTABLE", "declared executable is not a regular file")
|
|
578
|
+
_path_identity(path)
|
|
579
|
+
return path
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
def _bind_commands(
|
|
583
|
+
root: Path, context: dict[str, Any], workspace_roots: dict[str, str]
|
|
584
|
+
) -> list[dict[str, Any]]:
|
|
585
|
+
bindings = []
|
|
586
|
+
for command in context["commands"]:
|
|
587
|
+
definition = command["definition"]
|
|
588
|
+
cwd, worktree, common = _workspace_for(root, definition["cwd"], workspace_roots)
|
|
589
|
+
executable = _resolve_executable(definition["argv"][0], cwd)
|
|
590
|
+
bindings.append({
|
|
591
|
+
"definition_sha256": command["definition_sha256"],
|
|
592
|
+
"argv": [str(executable), *definition["argv"][1:]],
|
|
593
|
+
"cwd": str(cwd),
|
|
594
|
+
"cwd_filesystem_id": _path_identity(cwd),
|
|
595
|
+
"workspace_root": str(worktree),
|
|
596
|
+
"workspace_filesystem_id": _path_identity(worktree),
|
|
597
|
+
"git_common_dir": str(common),
|
|
598
|
+
"git_common_filesystem_id": _path_identity(common),
|
|
599
|
+
"executable_sha256": _file_sha256(executable),
|
|
600
|
+
})
|
|
601
|
+
return bindings
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
def _workspace_snapshots(bindings: list[dict[str, Any]]) -> list[dict[str, str]]:
|
|
605
|
+
unique: dict[str, dict[str, str]] = {}
|
|
606
|
+
for binding in bindings:
|
|
607
|
+
cwd = _resolved_directory(binding["cwd"])
|
|
608
|
+
root = _real_root(binding["workspace_root"])
|
|
609
|
+
common = _real_root(binding["git_common_dir"])
|
|
610
|
+
if (
|
|
611
|
+
_path_identity(cwd) != binding["cwd_filesystem_id"]
|
|
612
|
+
or _path_identity(root) != binding["workspace_filesystem_id"]
|
|
613
|
+
or _path_identity(common) != binding["git_common_filesystem_id"]
|
|
614
|
+
or _file_sha256(Path(binding["argv"][0])) != binding["executable_sha256"]
|
|
615
|
+
):
|
|
616
|
+
raise AuthorityError("WORKSPACE_DRIFT", "workspace or executable identity changed")
|
|
617
|
+
status = _git_text(root, "status", "--porcelain=v2", "-z", "--untracked-files=all")
|
|
618
|
+
index = _git_text(root, "ls-files", "--stage", "-z")
|
|
619
|
+
untracked = _git_text(root, "ls-files", "--others", "--exclude-standard", "-z")
|
|
620
|
+
entries: dict[str, str] = {}
|
|
621
|
+
for record in (part for part in index.split("\0") if part):
|
|
622
|
+
try:
|
|
623
|
+
metadata, relative = record.split("\t", 1)
|
|
624
|
+
mode, _object_id, stage = metadata.split(" ")
|
|
625
|
+
except ValueError as exc:
|
|
626
|
+
raise AuthorityError("WORKSPACE_DRIFT", "git index entry cannot be frozen") from exc
|
|
627
|
+
if stage != "0" or mode not in {"100644", "100755", "120000", "160000"} or relative in entries:
|
|
628
|
+
raise AuthorityError("WORKSPACE_DRIFT", "git index entry cannot be frozen")
|
|
629
|
+
entries[relative] = mode
|
|
630
|
+
for relative in (part for part in untracked.split("\0") if part):
|
|
631
|
+
if relative in entries:
|
|
632
|
+
raise AuthorityError("WORKSPACE_DRIFT", "git workspace entry cannot be frozen")
|
|
633
|
+
entries[relative] = "untracked"
|
|
634
|
+
content = hashlib.sha256()
|
|
635
|
+
for relative in sorted(entries):
|
|
636
|
+
encoded = relative.encode("utf-8")
|
|
637
|
+
candidate = root / relative
|
|
638
|
+
try:
|
|
639
|
+
if not candidate.parent.resolve(strict=False).is_relative_to(root):
|
|
640
|
+
raise OSError("workspace member escapes the worktree")
|
|
641
|
+
mode = entries[relative]
|
|
642
|
+
try:
|
|
643
|
+
info = candidate.lstat()
|
|
644
|
+
except FileNotFoundError:
|
|
645
|
+
if mode == "untracked":
|
|
646
|
+
raise
|
|
647
|
+
kind, data = "deleted", b""
|
|
648
|
+
else:
|
|
649
|
+
reparse = getattr(info, "st_file_attributes", 0) & 0x400
|
|
650
|
+
if stat.S_ISLNK(info.st_mode):
|
|
651
|
+
if mode not in {"120000", "untracked"}:
|
|
652
|
+
raise OSError("regular workspace member became a symlink")
|
|
653
|
+
resolved = candidate.resolve(strict=False)
|
|
654
|
+
if not resolved.is_relative_to(root):
|
|
655
|
+
raise OSError("workspace symlink escapes the worktree")
|
|
656
|
+
target = os.fsencode(os.readlink(candidate))
|
|
657
|
+
resolved_path = resolved.relative_to(root).as_posix().encode("utf-8")
|
|
658
|
+
try:
|
|
659
|
+
target_info = resolved.lstat()
|
|
660
|
+
except FileNotFoundError:
|
|
661
|
+
kind, data = "dangling-symlink", target + b"\0" + resolved_path
|
|
662
|
+
else:
|
|
663
|
+
target_reparse = getattr(target_info, "st_file_attributes", 0) & 0x400
|
|
664
|
+
if target_reparse or not stat.S_ISREG(target_info.st_mode):
|
|
665
|
+
raise OSError("workspace symlink target has unsupported type")
|
|
666
|
+
kind, data = "symlink", target + b"\0" + resolved_path + b"\0" + resolved.read_bytes()
|
|
667
|
+
elif reparse:
|
|
668
|
+
raise OSError("workspace member is replaceable indirection")
|
|
669
|
+
elif stat.S_ISREG(info.st_mode) and mode != "160000":
|
|
670
|
+
kind = "materialized-symlink" if mode == "120000" else "file"
|
|
671
|
+
data = candidate.read_bytes()
|
|
672
|
+
elif stat.S_ISDIR(info.st_mode) and mode == "160000":
|
|
673
|
+
nested_top = _git_text(candidate, "rev-parse", "--show-toplevel")
|
|
674
|
+
if _real_root(nested_top) != candidate:
|
|
675
|
+
if any(candidate.iterdir()):
|
|
676
|
+
raise OSError("uninitialized gitlink has unexpected content")
|
|
677
|
+
kind, data = "uninitialized-gitlink", b""
|
|
678
|
+
else:
|
|
679
|
+
if _git_text(candidate, "status", "--porcelain", "--untracked-files=all"):
|
|
680
|
+
raise OSError("dirty gitlink cannot be frozen")
|
|
681
|
+
kind, data = "gitlink", _git_text(candidate, "rev-parse", "HEAD").encode("ascii")
|
|
682
|
+
else:
|
|
683
|
+
raise OSError("workspace member has unsupported type")
|
|
684
|
+
except (OSError, RuntimeError, AuthorityError) as exc:
|
|
685
|
+
raise AuthorityError("WORKSPACE_DRIFT", "workspace content cannot be frozen") from exc
|
|
686
|
+
content.update(len(encoded).to_bytes(8, "big"))
|
|
687
|
+
content.update(encoded)
|
|
688
|
+
typed = f"{mode}:{kind}".encode("ascii")
|
|
689
|
+
content.update(len(typed).to_bytes(8, "big"))
|
|
690
|
+
content.update(typed)
|
|
691
|
+
content.update(len(data).to_bytes(8, "big"))
|
|
692
|
+
content.update(data)
|
|
693
|
+
unique[str(root)] = {
|
|
694
|
+
"root": str(root),
|
|
695
|
+
"filesystem_id": binding["workspace_filesystem_id"],
|
|
696
|
+
"git_common_dir": str(common),
|
|
697
|
+
"git_common_filesystem_id": binding["git_common_filesystem_id"],
|
|
698
|
+
"head": _git_text(root, "rev-parse", "HEAD"),
|
|
699
|
+
"status_sha256": _digest(status.encode("utf-8")),
|
|
700
|
+
"content_sha256": content.hexdigest(),
|
|
701
|
+
}
|
|
702
|
+
return [unique[key] for key in sorted(unique)]
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
def _dispatch_prompt(request: dict[str, Any]) -> str:
|
|
706
|
+
context = request["context"]
|
|
707
|
+
return (
|
|
708
|
+
f"[CODEARBITER_AUTHORITY_REQUEST:{request['request_id']}]\n"
|
|
709
|
+
f"Target repository: {request['repository']['path']}\n"
|
|
710
|
+
f"Frozen context: {request['context_ref']} sha256={request['context_sha256']}\n"
|
|
711
|
+
"Frozen target data follows as canonical JSON:\n"
|
|
712
|
+
+ _canonical(context).decode("ascii") + "\n"
|
|
713
|
+
"Act as a fresh read-only independent codeArbiter reviewer. Do not edit files or "
|
|
714
|
+
"delegate. Review only the frozen target and return exactly one JSON object using "
|
|
715
|
+
f"format {DECISION_FORMAT}. Bind request_id={request['request_id']}, "
|
|
716
|
+
f"target_sha256={context['input_sha256']}, "
|
|
717
|
+
f"contract_sha256={request['review_contract_sha256']}. Required coverage: "
|
|
718
|
+
+ json.dumps(request["required_coverage"], ensure_ascii=True)
|
|
719
|
+
+ ". Fields: format, request_id, target_sha256, contract_sha256, decision "
|
|
720
|
+
"(pass or changes_requested), coverage (unique strings), findings (objects with "
|
|
721
|
+
"severity, code, message), assessment (non-empty string)."
|
|
722
|
+
)
|
|
723
|
+
|
|
724
|
+
|
|
725
|
+
def arm_request(
|
|
726
|
+
root: str | Path,
|
|
727
|
+
client: Any,
|
|
728
|
+
artifact_id: str,
|
|
729
|
+
record_id: str,
|
|
730
|
+
activity: str,
|
|
731
|
+
*,
|
|
732
|
+
request_nonce: str | None = None,
|
|
733
|
+
workspace_roots: dict[str, str | Path] | None = None,
|
|
734
|
+
**unexpected: Any,
|
|
735
|
+
) -> dict[str, Any]:
|
|
736
|
+
if unexpected:
|
|
737
|
+
raise AuthorityError(
|
|
738
|
+
"INVALID_AUTHORITY_REQUEST",
|
|
739
|
+
"authority, verdict, payload, and observations are not caller inputs",
|
|
740
|
+
)
|
|
741
|
+
if activity not in ACTIVITIES:
|
|
742
|
+
raise AuthorityError("INVALID_AUTHORITY_REQUEST", "activity is unsupported")
|
|
743
|
+
root = _real_root(root)
|
|
744
|
+
nonce = request_nonce or secrets.token_urlsafe(24)
|
|
745
|
+
if not isinstance(nonce, str) or TOKEN_RE.fullmatch(nonce) is None:
|
|
746
|
+
raise AuthorityError("INVALID_AUTHORITY_REQUEST", "request nonce is malformed")
|
|
747
|
+
context, context_ref, context_sha256 = _context(
|
|
748
|
+
root, client, artifact_id, record_id, activity
|
|
749
|
+
)
|
|
750
|
+
frozen_workspaces: dict[str, str] = {}
|
|
751
|
+
if workspace_roots is not None:
|
|
752
|
+
if activity != "verification" or not isinstance(workspace_roots, dict):
|
|
753
|
+
raise AuthorityError("INVALID_AUTHORITY_REQUEST", "workspace roots apply only to verification")
|
|
754
|
+
labels = {command["definition"]["cwd"] for command in context["commands"]}
|
|
755
|
+
if set(workspace_roots) != labels:
|
|
756
|
+
raise AuthorityError("UNSUPPORTED_WORKSPACE", "workspace map must exactly cover declared cwd labels")
|
|
757
|
+
frozen_workspaces = {label: str(_real_root(path)) for label, path in workspace_roots.items()}
|
|
758
|
+
command_bindings = _bind_commands(root, context, frozen_workspaces) if activity == "verification" else []
|
|
759
|
+
seed = _canonical({
|
|
760
|
+
"repository": _repository_identity(root), "context": context, "nonce": nonce,
|
|
761
|
+
"command_bindings": command_bindings,
|
|
762
|
+
"workspace_roots": frozen_workspaces,
|
|
763
|
+
})
|
|
764
|
+
request_id = _digest(seed)
|
|
765
|
+
request = {
|
|
766
|
+
"format": STATE_FORMAT,
|
|
767
|
+
"request_id": request_id,
|
|
768
|
+
"state": "ARMED",
|
|
769
|
+
"activity": activity,
|
|
770
|
+
"repository": _repository_identity(root),
|
|
771
|
+
"context": context,
|
|
772
|
+
"context_ref": context_ref,
|
|
773
|
+
"context_sha256": context_sha256,
|
|
774
|
+
"attempt": None,
|
|
775
|
+
"launch": None,
|
|
776
|
+
"observation_ref": None,
|
|
777
|
+
"observation_sha256": None,
|
|
778
|
+
"receipt": None,
|
|
779
|
+
"wrapper": None,
|
|
780
|
+
"recovery": None,
|
|
781
|
+
"command_bindings": command_bindings,
|
|
782
|
+
"workspace_roots": frozen_workspaces,
|
|
783
|
+
}
|
|
784
|
+
if activity in REVIEW_ACTIVITIES:
|
|
785
|
+
request["review_contract_sha256"], request["required_coverage"] = _review_binding(context)
|
|
786
|
+
request["dispatch_prompt"] = _dispatch_prompt(request)
|
|
787
|
+
request["launch_envelope"] = {
|
|
788
|
+
"message": request["dispatch_prompt"],
|
|
789
|
+
"task_name": f"authority_{request_id[:12]}",
|
|
790
|
+
"fork_turns": "none",
|
|
791
|
+
}
|
|
792
|
+
_save(root, request)
|
|
793
|
+
_register_request(root, request)
|
|
794
|
+
result = {
|
|
795
|
+
"request_id": request_id,
|
|
796
|
+
"request_path": str(_spool_root(root) / _request_path(request_id)),
|
|
797
|
+
"activity": activity,
|
|
798
|
+
}
|
|
799
|
+
if activity in REVIEW_ACTIVITIES:
|
|
800
|
+
result["dispatch_prompt"] = request["dispatch_prompt"]
|
|
801
|
+
result["review_contract_sha256"] = request["review_contract_sha256"]
|
|
802
|
+
result["required_coverage"] = request["required_coverage"]
|
|
803
|
+
result["launch_envelope"] = request["launch_envelope"]
|
|
804
|
+
return result
|
|
805
|
+
|
|
806
|
+
|
|
807
|
+
def _bounded_output(value: Any) -> bytes:
|
|
808
|
+
if not isinstance(value, (bytes, bytearray)):
|
|
809
|
+
raise AuthorityError("INVALID_PROCESS_RESULT", "process output must be bytes")
|
|
810
|
+
if len(value) > MAX_OUTPUT:
|
|
811
|
+
raise AuthorityError("PROCESS_OUTPUT_TOO_LARGE", "process output exceeds 8 MiB")
|
|
812
|
+
return bytes(value)
|
|
813
|
+
|
|
814
|
+
|
|
815
|
+
def _unittest_collector(stdout: bytes, stderr: bytes, required: list[str]) -> list[dict[str, str]]:
|
|
816
|
+
text = (stdout + b"\n" + stderr).decode("utf-8", "replace")
|
|
817
|
+
results = []
|
|
818
|
+
for name in required:
|
|
819
|
+
pattern = re.compile(r"(?m)^" + re.escape(name) + r"(?:\s+\([^\r\n]*\))?\s+\.\.\.\s+ok\s*$")
|
|
820
|
+
matches = pattern.findall(text)
|
|
821
|
+
if not matches:
|
|
822
|
+
raise AuthorityError("MISSING_TEST_RESULT", f"required test did not pass: {name}")
|
|
823
|
+
if len(matches) != 1:
|
|
824
|
+
raise AuthorityError("DUPLICATE_TEST_RESULT", f"required test appeared more than once: {name}")
|
|
825
|
+
results.append({"name": name, "status": "pass"})
|
|
826
|
+
return results
|
|
827
|
+
|
|
828
|
+
|
|
829
|
+
def _named_line_collector(stdout: bytes, stderr: bytes, required: list[str]) -> list[dict[str, str]]:
|
|
830
|
+
# Intentional producer gap: current SingedTerra movement, walls, and
|
|
831
|
+
# fall-damage harnesses emit umbrella summaries for some plan aggregates.
|
|
832
|
+
# Those summaries are not semantic evidence; only exact PASS names qualify.
|
|
833
|
+
text = (stdout + b"\n" + stderr).decode("utf-8", "replace")
|
|
834
|
+
passed: dict[str, int] = {}
|
|
835
|
+
for line in text.splitlines():
|
|
836
|
+
stripped = line.strip()
|
|
837
|
+
if stripped.startswith("PASS: "):
|
|
838
|
+
name = stripped[6:].rstrip(".")
|
|
839
|
+
passed[name] = passed.get(name, 0) + 1
|
|
840
|
+
duplicates = [name for name in required if passed.get(name, 0) > 1]
|
|
841
|
+
if duplicates:
|
|
842
|
+
raise AuthorityError("DUPLICATE_TEST_RESULT", "required PASS lines are duplicated: " + ", ".join(duplicates))
|
|
843
|
+
missing = [name for name in required if passed.get(name, 0) != 1]
|
|
844
|
+
if missing:
|
|
845
|
+
raise AuthorityError(
|
|
846
|
+
"MISSING_TEST_RESULT", "required explicit PASS lines are absent: " + ", ".join(missing)
|
|
847
|
+
)
|
|
848
|
+
return [{"name": name, "status": "pass"} for name in required]
|
|
849
|
+
|
|
850
|
+
|
|
851
|
+
def _vitest_verbose_collector(stdout: bytes, stderr: bytes, required: list[str]) -> list[dict[str, str]]:
|
|
852
|
+
text = (stdout + b"\n" + stderr).decode("utf-8", "replace")
|
|
853
|
+
passed: dict[str, int] = {}
|
|
854
|
+
for line in text.splitlines():
|
|
855
|
+
stripped = line.strip()
|
|
856
|
+
if stripped.startswith(("✓ ", "√ ")):
|
|
857
|
+
name = stripped[2:].rsplit(" > ", 1)[-1]
|
|
858
|
+
name = re.sub(r"\s+\d+(?:\.\d+)?m?s$", "", name)
|
|
859
|
+
passed[name] = passed.get(name, 0) + 1
|
|
860
|
+
duplicates = [name for name in required if passed.get(name, 0) > 1]
|
|
861
|
+
if duplicates:
|
|
862
|
+
raise AuthorityError("DUPLICATE_TEST_RESULT", "required Vitest cases are duplicated: " + ", ".join(duplicates))
|
|
863
|
+
missing = [name for name in required if passed.get(name, 0) != 1]
|
|
864
|
+
if missing:
|
|
865
|
+
raise AuthorityError(
|
|
866
|
+
"MISSING_TEST_RESULT", "required exact Vitest case names are absent: " + ", ".join(missing)
|
|
867
|
+
)
|
|
868
|
+
return [{"name": name, "status": "pass"} for name in required]
|
|
869
|
+
|
|
870
|
+
|
|
871
|
+
def _exit_only_collector(_stdout: bytes, _stderr: bytes, required: list[str]) -> list[dict[str, str]]:
|
|
872
|
+
if required:
|
|
873
|
+
raise AuthorityError("UNSUPPORTED_COLLECTOR", "exit-only profile cannot attest named tests")
|
|
874
|
+
return []
|
|
875
|
+
|
|
876
|
+
|
|
877
|
+
COLLECTORS: dict[str, Callable[[bytes, bytes, list[str]], list[dict[str, str]]]] = {
|
|
878
|
+
"python-unittest-text/0.1.0": _unittest_collector,
|
|
879
|
+
"codearbiter-named-lines/0.1.0": _named_line_collector,
|
|
880
|
+
"vitest-verbose/0.1.0": _vitest_verbose_collector,
|
|
881
|
+
"exit-only/0.1.0": _exit_only_collector,
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
|
|
885
|
+
def _collector_profile(argv: list[str], required: list[str]) -> str:
|
|
886
|
+
if not required:
|
|
887
|
+
return "exit-only/0.1.0"
|
|
888
|
+
lowered = [part.casefold() for part in argv]
|
|
889
|
+
if len(lowered) >= 3 and lowered[1:3] == ["-m", "unittest"]:
|
|
890
|
+
return "python-unittest-text/0.1.0"
|
|
891
|
+
if "vitest" in lowered and "--reporter=verbose" in lowered:
|
|
892
|
+
return "vitest-verbose/0.1.0"
|
|
893
|
+
if "tsx" in lowered or lowered[:3] == ["npm", "run", "test:client"] or lowered[:3] == ["npm", "run", "check"]:
|
|
894
|
+
return "codearbiter-named-lines/0.1.0"
|
|
895
|
+
raise AuthorityError("UNSUPPORTED_COLLECTOR", "declared runner has no qualified collector")
|
|
896
|
+
|
|
897
|
+
|
|
898
|
+
def _minimal_environment() -> tuple[dict[str, str], str]:
|
|
899
|
+
names = (
|
|
900
|
+
"PATH", "PATHEXT", "SystemRoot", "WINDIR", "COMSPEC", "TEMP", "TMP",
|
|
901
|
+
"HOME", "USERPROFILE", "APPDATA", "LOCALAPPDATA", "LANG", "LC_ALL",
|
|
902
|
+
)
|
|
903
|
+
environment = {name: os.environ[name] for name in names if name in os.environ}
|
|
904
|
+
return environment, _digest(_canonical(environment))
|
|
905
|
+
|
|
906
|
+
|
|
907
|
+
def _resolved_directory(value: Any) -> Path:
|
|
908
|
+
if not isinstance(value, (str, Path)):
|
|
909
|
+
raise AuthorityError("UNSUPPORTED_WORKSPACE", "workspace resolver returned no directory")
|
|
910
|
+
try:
|
|
911
|
+
path = Path(value).resolve(strict=True)
|
|
912
|
+
info = path.lstat()
|
|
913
|
+
except OSError as exc:
|
|
914
|
+
raise AuthorityError("UNSUPPORTED_WORKSPACE", "workspace directory is unavailable") from exc
|
|
915
|
+
reparse = getattr(info, "st_file_attributes", 0) & 0x400
|
|
916
|
+
if stat.S_ISLNK(info.st_mode) or reparse or not stat.S_ISDIR(info.st_mode):
|
|
917
|
+
raise AuthorityError("UNSUPPORTED_WORKSPACE", "workspace must be a real directory")
|
|
918
|
+
return path
|
|
919
|
+
|
|
920
|
+
|
|
921
|
+
def _run_contained(
|
|
922
|
+
argv: list[str], *, cwd: str, env: dict[str, str], timeout_seconds: float = 1800.0
|
|
923
|
+
) -> subprocess.CompletedProcess:
|
|
924
|
+
"""Bound output and descendants with POSIX sessions or a Windows Job Object."""
|
|
925
|
+
if not isinstance(timeout_seconds, (int, float)) or timeout_seconds <= 0:
|
|
926
|
+
raise AuthorityError("INVALID_PROCESS_TIMEOUT", "process timeout must be positive")
|
|
927
|
+
job = None
|
|
928
|
+
if os.name == "nt":
|
|
929
|
+
import ctypes
|
|
930
|
+
from ctypes import wintypes
|
|
931
|
+
|
|
932
|
+
class BasicLimits(ctypes.Structure):
|
|
933
|
+
_fields_ = [
|
|
934
|
+
("PerProcessUserTimeLimit", ctypes.c_longlong),
|
|
935
|
+
("PerJobUserTimeLimit", ctypes.c_longlong),
|
|
936
|
+
("LimitFlags", wintypes.DWORD),
|
|
937
|
+
("MinimumWorkingSetSize", ctypes.c_size_t),
|
|
938
|
+
("MaximumWorkingSetSize", ctypes.c_size_t),
|
|
939
|
+
("ActiveProcessLimit", wintypes.DWORD),
|
|
940
|
+
("Affinity", ctypes.c_size_t),
|
|
941
|
+
("PriorityClass", wintypes.DWORD),
|
|
942
|
+
("SchedulingClass", wintypes.DWORD),
|
|
943
|
+
]
|
|
944
|
+
|
|
945
|
+
class IoCounters(ctypes.Structure):
|
|
946
|
+
_fields_ = [(name, ctypes.c_ulonglong) for name in (
|
|
947
|
+
"ReadOperationCount", "WriteOperationCount", "OtherOperationCount",
|
|
948
|
+
"ReadTransferCount", "WriteTransferCount", "OtherTransferCount",
|
|
949
|
+
)]
|
|
950
|
+
|
|
951
|
+
class ExtendedLimits(ctypes.Structure):
|
|
952
|
+
_fields_ = [
|
|
953
|
+
("BasicLimitInformation", BasicLimits), ("IoInfo", IoCounters),
|
|
954
|
+
("ProcessMemoryLimit", ctypes.c_size_t), ("JobMemoryLimit", ctypes.c_size_t),
|
|
955
|
+
("PeakProcessMemoryUsed", ctypes.c_size_t), ("PeakJobMemoryUsed", ctypes.c_size_t),
|
|
956
|
+
]
|
|
957
|
+
|
|
958
|
+
kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
|
|
959
|
+
ntdll = ctypes.WinDLL("ntdll")
|
|
960
|
+
kernel32.CreateJobObjectW.restype = wintypes.HANDLE
|
|
961
|
+
kernel32.CreateJobObjectW.argtypes = [ctypes.c_void_p, wintypes.LPCWSTR]
|
|
962
|
+
kernel32.SetInformationJobObject.argtypes = [wintypes.HANDLE, ctypes.c_int, ctypes.c_void_p, wintypes.DWORD]
|
|
963
|
+
kernel32.AssignProcessToJobObject.argtypes = [wintypes.HANDLE, wintypes.HANDLE]
|
|
964
|
+
kernel32.TerminateJobObject.argtypes = [wintypes.HANDLE, wintypes.UINT]
|
|
965
|
+
kernel32.CloseHandle.argtypes = [wintypes.HANDLE]
|
|
966
|
+
ntdll.NtResumeProcess.argtypes = [wintypes.HANDLE]
|
|
967
|
+
job = kernel32.CreateJobObjectW(None, None)
|
|
968
|
+
if not job:
|
|
969
|
+
raise AuthorityError("UNSUPPORTED_PROCESS_CONTAINMENT", "CreateJobObjectW failed")
|
|
970
|
+
limits = ExtendedLimits()
|
|
971
|
+
limits.BasicLimitInformation.LimitFlags = 0x00002000 # KILL_ON_JOB_CLOSE
|
|
972
|
+
if not kernel32.SetInformationJobObject(job, 9, ctypes.byref(limits), ctypes.sizeof(limits)):
|
|
973
|
+
kernel32.CloseHandle(job)
|
|
974
|
+
raise AuthorityError("UNSUPPORTED_PROCESS_CONTAINMENT", "job limits could not be installed")
|
|
975
|
+
try:
|
|
976
|
+
process = subprocess.Popen(
|
|
977
|
+
argv, cwd=cwd, env=env, stdin=subprocess.DEVNULL,
|
|
978
|
+
stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False,
|
|
979
|
+
creationflags=0x00000200 | 0x00000004, # NEW_PROCESS_GROUP | SUSPENDED
|
|
980
|
+
)
|
|
981
|
+
if not kernel32.AssignProcessToJobObject(job, wintypes.HANDLE(int(process._handle))):
|
|
982
|
+
process.kill()
|
|
983
|
+
process.wait()
|
|
984
|
+
raise AuthorityError("UNSUPPORTED_PROCESS_CONTAINMENT", "child could not enter Job Object")
|
|
985
|
+
if ntdll.NtResumeProcess(wintypes.HANDLE(int(process._handle))) != 0:
|
|
986
|
+
kernel32.TerminateJobObject(job, 1)
|
|
987
|
+
process.wait()
|
|
988
|
+
raise AuthorityError("UNSUPPORTED_PROCESS_CONTAINMENT", "suspended child could not resume")
|
|
989
|
+
except Exception:
|
|
990
|
+
kernel32.CloseHandle(job)
|
|
991
|
+
raise
|
|
992
|
+
else:
|
|
993
|
+
process = subprocess.Popen(
|
|
994
|
+
argv, cwd=cwd, env=env, stdin=subprocess.DEVNULL,
|
|
995
|
+
stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False,
|
|
996
|
+
start_new_session=True,
|
|
997
|
+
)
|
|
998
|
+
buffers = {"stdout": bytearray(), "stderr": bytearray()}
|
|
999
|
+
overflow = threading.Event()
|
|
1000
|
+
|
|
1001
|
+
def drain(name: str, stream: Any) -> None:
|
|
1002
|
+
while True:
|
|
1003
|
+
chunk = stream.read(65536)
|
|
1004
|
+
if not chunk:
|
|
1005
|
+
return
|
|
1006
|
+
if len(buffers[name]) + len(chunk) > MAX_OUTPUT:
|
|
1007
|
+
overflow.set()
|
|
1008
|
+
else:
|
|
1009
|
+
buffers[name].extend(chunk)
|
|
1010
|
+
|
|
1011
|
+
threads = [
|
|
1012
|
+
threading.Thread(target=drain, args=("stdout", process.stdout), daemon=True),
|
|
1013
|
+
threading.Thread(target=drain, args=("stderr", process.stderr), daemon=True),
|
|
1014
|
+
]
|
|
1015
|
+
for thread in threads:
|
|
1016
|
+
thread.start()
|
|
1017
|
+
deadline = time.monotonic() + timeout_seconds
|
|
1018
|
+
try:
|
|
1019
|
+
while process.poll() is None:
|
|
1020
|
+
if overflow.is_set() or time.monotonic() >= deadline:
|
|
1021
|
+
if os.name == "nt":
|
|
1022
|
+
kernel32.TerminateJobObject(job, 1)
|
|
1023
|
+
else:
|
|
1024
|
+
import signal
|
|
1025
|
+
os.killpg(process.pid, signal.SIGKILL)
|
|
1026
|
+
process.wait(timeout=10)
|
|
1027
|
+
if overflow.is_set():
|
|
1028
|
+
raise AuthorityError("PROCESS_OUTPUT_TOO_LARGE", "process output exceeds 8 MiB")
|
|
1029
|
+
raise AuthorityError("VERIFICATION_PROCESS_FAILED", "verification process timed out")
|
|
1030
|
+
time.sleep(0.02)
|
|
1031
|
+
finally:
|
|
1032
|
+
if job is not None:
|
|
1033
|
+
kernel32.CloseHandle(job) # KILL_ON_JOB_CLOSE ends surviving descendants.
|
|
1034
|
+
job = None
|
|
1035
|
+
elif process.poll() is not None:
|
|
1036
|
+
try:
|
|
1037
|
+
import signal
|
|
1038
|
+
os.killpg(process.pid, signal.SIGKILL)
|
|
1039
|
+
except (OSError, ProcessLookupError):
|
|
1040
|
+
pass
|
|
1041
|
+
for thread in threads:
|
|
1042
|
+
thread.join(timeout=10)
|
|
1043
|
+
process.stdout.close()
|
|
1044
|
+
process.stderr.close()
|
|
1045
|
+
if overflow.is_set():
|
|
1046
|
+
raise AuthorityError("PROCESS_OUTPUT_TOO_LARGE", "process output exceeds 8 MiB")
|
|
1047
|
+
return subprocess.CompletedProcess(argv, process.returncode, bytes(buffers["stdout"]), bytes(buffers["stderr"]))
|
|
1048
|
+
|
|
1049
|
+
|
|
1050
|
+
def _observation(root: Path, request: dict[str, Any], value: dict[str, Any]) -> None:
|
|
1051
|
+
raw = _canonical(value)
|
|
1052
|
+
digest = _digest(raw)
|
|
1053
|
+
relative = OBSERVATION_DIR / f"{digest}.json"
|
|
1054
|
+
_publish_immutable(root, relative, raw)
|
|
1055
|
+
request["observation_ref"] = relative.as_posix()
|
|
1056
|
+
request["observation_sha256"] = digest
|
|
1057
|
+
|
|
1058
|
+
|
|
1059
|
+
def _closed_observation(
|
|
1060
|
+
request: dict[str, Any], payload: dict[str, Any], producer_profile: str,
|
|
1061
|
+
producer_run_id: str, producer_result: dict[str, Any],
|
|
1062
|
+
) -> dict[str, Any]:
|
|
1063
|
+
return {
|
|
1064
|
+
"format": OBSERVATION_FORMAT,
|
|
1065
|
+
"kind": request["activity"],
|
|
1066
|
+
"subject": request["context"]["subject"],
|
|
1067
|
+
"context_ref": request["context_ref"],
|
|
1068
|
+
"context_sha256": request["context_sha256"],
|
|
1069
|
+
"payload_sha256": _digest(_canonical(payload)),
|
|
1070
|
+
"producer_profile": producer_profile,
|
|
1071
|
+
"producer_run_id": producer_run_id,
|
|
1072
|
+
"producer_result": producer_result,
|
|
1073
|
+
"producer_result_sha256": _digest(_canonical(producer_result)),
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
|
|
1077
|
+
def capture_user_prompt(
|
|
1078
|
+
root: str | Path, client: Any, event: dict[str, Any], *, host: str, session_id: str
|
|
1079
|
+
) -> dict[str, Any]:
|
|
1080
|
+
"""Capture one exact host UserPromptSubmit event through a closed observation."""
|
|
1081
|
+
root = _real_root(root)
|
|
1082
|
+
if (
|
|
1083
|
+
not isinstance(event, dict)
|
|
1084
|
+
or event.get("kind") not in {"approval", "prerequisite", "reconciliation", "farm_authorization"}
|
|
1085
|
+
or event.get("authority_kind") != "user_workflow"
|
|
1086
|
+
or not isinstance(event.get("subject"), dict)
|
|
1087
|
+
or not isinstance(event.get("source_text"), str)
|
|
1088
|
+
or not event["source_text"]
|
|
1089
|
+
):
|
|
1090
|
+
raise AuthorityError("INVALID_PROMPT_EVENT", "user prompt event is malformed")
|
|
1091
|
+
producer_run_id = f"{_host_id(host, 'host')}:{_host_id(session_id, 'session_id')}"
|
|
1092
|
+
prompt_sha256 = _digest(event["source_text"].encode("utf-8"))
|
|
1093
|
+
subject = event["subject"]
|
|
1094
|
+
result = client.call("evidence-context", {
|
|
1095
|
+
"artifact_id": subject.get("artifact_id"),
|
|
1096
|
+
"record_id": subject.get("record_id"),
|
|
1097
|
+
"activity": event["kind"],
|
|
1098
|
+
"prompt_sha256": prompt_sha256,
|
|
1099
|
+
})
|
|
1100
|
+
if not isinstance(result, dict):
|
|
1101
|
+
raise AuthorityError("INVALID_EVIDENCE_CONTEXT", "prompt context result is malformed")
|
|
1102
|
+
context_ref = result.get("context_ref")
|
|
1103
|
+
context_sha256 = _validate_hash(result.get("context_sha256"), "context_sha256")
|
|
1104
|
+
if context_ref != f".codearbiter/.artifacts/evidence-contexts/{context_sha256}.json":
|
|
1105
|
+
raise AuthorityError("INVALID_EVIDENCE_CONTEXT", "prompt context is not content addressed")
|
|
1106
|
+
try:
|
|
1107
|
+
context_raw = (root / context_ref).read_bytes()
|
|
1108
|
+
context = json.loads(context_raw.decode("utf-8"))
|
|
1109
|
+
except (OSError, UnicodeError, ValueError) as exc:
|
|
1110
|
+
raise AuthorityError("INVALID_EVIDENCE_CONTEXT", "prompt context is unavailable") from exc
|
|
1111
|
+
if (
|
|
1112
|
+
_digest(context_raw) != context_sha256
|
|
1113
|
+
or context_raw != _canonical(context)
|
|
1114
|
+
or context.get("activity") != event["kind"]
|
|
1115
|
+
or context.get("subject") != subject
|
|
1116
|
+
or context.get("prompt_sha256") != prompt_sha256
|
|
1117
|
+
):
|
|
1118
|
+
raise AuthorityError("INVALID_EVIDENCE_CONTEXT", "prompt context changed its authority binding")
|
|
1119
|
+
producer_result = {"host": host, "session_id": session_id, "prompt_sha256": prompt_sha256}
|
|
1120
|
+
observation = {
|
|
1121
|
+
"format": OBSERVATION_FORMAT,
|
|
1122
|
+
"kind": event["kind"],
|
|
1123
|
+
"subject": subject,
|
|
1124
|
+
"context_ref": context_ref,
|
|
1125
|
+
"context_sha256": context_sha256,
|
|
1126
|
+
"payload_sha256": _digest(_canonical(event.get("payload"))),
|
|
1127
|
+
"producer_profile": "host-user-prompt/0.1.0",
|
|
1128
|
+
"producer_run_id": producer_run_id,
|
|
1129
|
+
"producer_result": producer_result,
|
|
1130
|
+
"producer_result_sha256": _digest(_canonical(producer_result)),
|
|
1131
|
+
}
|
|
1132
|
+
observation_raw = _canonical(observation)
|
|
1133
|
+
observation_sha256 = _digest(observation_raw)
|
|
1134
|
+
observation_ref = OBSERVATION_DIR / f"{observation_sha256}.json"
|
|
1135
|
+
_publish_immutable(root, observation_ref, observation_raw)
|
|
1136
|
+
current_event = dict(event)
|
|
1137
|
+
current_event["format"] = EVENT_FORMAT
|
|
1138
|
+
current_event["observation_ref"] = observation_ref.as_posix()
|
|
1139
|
+
current_event["observation_sha256"] = observation_sha256
|
|
1140
|
+
event_raw = _canonical(current_event)
|
|
1141
|
+
event_sha256 = _digest(event_raw)
|
|
1142
|
+
source_ref = SOURCE_DIR / f"{event_sha256}.json"
|
|
1143
|
+
_publish_immutable(root, source_ref, event_raw)
|
|
1144
|
+
captured = client.call("capture-observation", {
|
|
1145
|
+
"source_ref": source_ref.as_posix(), "source_sha256": event_sha256,
|
|
1146
|
+
})
|
|
1147
|
+
if not isinstance(captured, dict) or not isinstance(captured.get("receipt"), str):
|
|
1148
|
+
raise AuthorityError("INVALID_RECEIPT", "observed prompt capture returned no receipt")
|
|
1149
|
+
return {
|
|
1150
|
+
**captured, "authority_source": source_ref.as_posix(),
|
|
1151
|
+
"observation_ref": observation_ref.as_posix(),
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
|
|
1155
|
+
def run_verification(
|
|
1156
|
+
root: str | Path,
|
|
1157
|
+
client: Any,
|
|
1158
|
+
request_id: str,
|
|
1159
|
+
) -> dict[str, Any]:
|
|
1160
|
+
root = _real_root(root)
|
|
1161
|
+
request = _load(root, request_id)
|
|
1162
|
+
if request["activity"] != "verification":
|
|
1163
|
+
raise AuthorityError("INVALID_AUTHORITY_REQUEST", "request is not verification")
|
|
1164
|
+
if request["state"] == "COMPLETED":
|
|
1165
|
+
return {
|
|
1166
|
+
"request_id": request_id, "state": "COMPLETED",
|
|
1167
|
+
"commands": request["payload"]["commands"],
|
|
1168
|
+
}
|
|
1169
|
+
if request["state"] != "ARMED":
|
|
1170
|
+
raise AuthorityError("INTERRUPTED_ATTEMPT", "verification attempt cannot be rerun")
|
|
1171
|
+
wrapper = request.get("wrapper")
|
|
1172
|
+
if not isinstance(wrapper, dict) or wrapper.get("state") != "AUTHORIZED":
|
|
1173
|
+
raise AuthorityError(
|
|
1174
|
+
"UNAUTHORIZED_VERIFIER",
|
|
1175
|
+
"verification requires a correlated PreToolUse authorization",
|
|
1176
|
+
)
|
|
1177
|
+
if _bind_commands(root, request["context"], request["workspace_roots"]) != request["command_bindings"]:
|
|
1178
|
+
raise AuthorityError("WORKSPACE_DRIFT", "effective argv or workspace changed after arm")
|
|
1179
|
+
fresh_context, fresh_ref, fresh_hash = _context(
|
|
1180
|
+
root, client, request["context"]["subject"]["artifact_id"],
|
|
1181
|
+
request["context"]["subject"]["record_id"], "verification",
|
|
1182
|
+
)
|
|
1183
|
+
if (fresh_context, fresh_ref, fresh_hash) != (
|
|
1184
|
+
request["context"], request["context_ref"], request["context_sha256"],
|
|
1185
|
+
):
|
|
1186
|
+
raise AuthorityError("WORKSPACE_DRIFT", "repository content changed after arm")
|
|
1187
|
+
workspace_before = _workspace_snapshots(request["command_bindings"])
|
|
1188
|
+
if workspace_before != wrapper.get("workspace_before"):
|
|
1189
|
+
raise AuthorityError("WORKSPACE_DRIFT", "workspace changed after wrapper authorization")
|
|
1190
|
+
|
|
1191
|
+
environment, environment_sha256 = _minimal_environment()
|
|
1192
|
+
request["state"] = "RUNNING"
|
|
1193
|
+
request["attempt"] = secrets.token_hex(16)
|
|
1194
|
+
_save(root, request)
|
|
1195
|
+
commands = []
|
|
1196
|
+
for command, binding in zip(request["context"]["commands"], request["command_bindings"]):
|
|
1197
|
+
definition = command["definition"]
|
|
1198
|
+
collector = COLLECTORS[_collector_profile(definition["argv"], definition["required_tests"])]
|
|
1199
|
+
cwd = _resolved_directory(binding["cwd"])
|
|
1200
|
+
try:
|
|
1201
|
+
completed = _run_contained(list(binding["argv"]), cwd=str(cwd), env=environment)
|
|
1202
|
+
except (OSError, subprocess.SubprocessError) as exc:
|
|
1203
|
+
raise AuthorityError("VERIFICATION_PROCESS_FAILED", "verification process did not complete") from exc
|
|
1204
|
+
stdout = _bounded_output(completed.stdout)
|
|
1205
|
+
stderr = _bounded_output(completed.stderr)
|
|
1206
|
+
if type(completed.returncode) is not int or not 0 <= completed.returncode <= 255:
|
|
1207
|
+
raise AuthorityError("INVALID_PROCESS_RESULT", "process exit is invalid")
|
|
1208
|
+
if completed.returncode != definition["expected_exit"]:
|
|
1209
|
+
raise AuthorityError("FAILED_VERIFICATION", "process exit differs from approved definition")
|
|
1210
|
+
tests = collector(stdout, stderr, list(definition["required_tests"]))
|
|
1211
|
+
commands.append({
|
|
1212
|
+
"definition_sha256": command["definition_sha256"],
|
|
1213
|
+
"exit": completed.returncode,
|
|
1214
|
+
"tests": tests,
|
|
1215
|
+
"stdout_sha256": _digest(stdout),
|
|
1216
|
+
"stderr_sha256": _digest(stderr),
|
|
1217
|
+
})
|
|
1218
|
+
workspace_after = _workspace_snapshots(request["command_bindings"])
|
|
1219
|
+
if workspace_after != workspace_before:
|
|
1220
|
+
raise AuthorityError("WORKSPACE_DRIFT", "verification changed the frozen workspace")
|
|
1221
|
+
final_context, final_ref, final_hash = _context(
|
|
1222
|
+
root, client, request["context"]["subject"]["artifact_id"],
|
|
1223
|
+
request["context"]["subject"]["record_id"], "verification",
|
|
1224
|
+
)
|
|
1225
|
+
if (final_context, final_ref, final_hash) != (
|
|
1226
|
+
request["context"], request["context_ref"], request["context_sha256"],
|
|
1227
|
+
):
|
|
1228
|
+
raise AuthorityError("WORKSPACE_DRIFT", "verification changed repository content")
|
|
1229
|
+
producer_result = {
|
|
1230
|
+
"environment_sha256": environment_sha256,
|
|
1231
|
+
"command_bindings": request["command_bindings"],
|
|
1232
|
+
"workspace_before": workspace_before,
|
|
1233
|
+
"workspace_after": workspace_after,
|
|
1234
|
+
"commands": commands,
|
|
1235
|
+
}
|
|
1236
|
+
payload = {
|
|
1237
|
+
"input_sha256": request["context"]["input_sha256"],
|
|
1238
|
+
"spec_sha256": request["context"]["spec_sha256"],
|
|
1239
|
+
"task_sha256": request["context"]["task_sha256"],
|
|
1240
|
+
"commands": commands,
|
|
1241
|
+
}
|
|
1242
|
+
observation = _closed_observation(
|
|
1243
|
+
request, payload, "declared-command/0.1.0", request["attempt"],
|
|
1244
|
+
producer_result,
|
|
1245
|
+
)
|
|
1246
|
+
_observation(root, request, observation)
|
|
1247
|
+
request["payload"] = payload
|
|
1248
|
+
request["state"] = "COMPLETED"
|
|
1249
|
+
_save(root, request)
|
|
1250
|
+
return {"request_id": request_id, "state": "COMPLETED", "commands": commands}
|
|
1251
|
+
|
|
1252
|
+
|
|
1253
|
+
def _request_id_from_prompt(message: Any) -> str:
|
|
1254
|
+
if not isinstance(message, str):
|
|
1255
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "spawn message is absent")
|
|
1256
|
+
match = re.match(r"\[CODEARBITER_AUTHORITY_REQUEST:([0-9a-f]{64})\]\n", message)
|
|
1257
|
+
if match is None:
|
|
1258
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "spawn message has no authority request")
|
|
1259
|
+
return match.group(1)
|
|
1260
|
+
|
|
1261
|
+
|
|
1262
|
+
def _host_id(value: Any, field: str) -> str:
|
|
1263
|
+
if not isinstance(value, str) or HOST_ID_RE.fullmatch(value) is None:
|
|
1264
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", f"{field} is malformed")
|
|
1265
|
+
return value
|
|
1266
|
+
|
|
1267
|
+
|
|
1268
|
+
def _verification_selector(event: dict[str, Any]) -> tuple[str, Path] | None:
|
|
1269
|
+
"""Recognize only the closed wrapper selector, never caller-authored child argv."""
|
|
1270
|
+
if event.get("tool_name") not in {
|
|
1271
|
+
"Bash", "shell_command", "exec_command", "unified_exec",
|
|
1272
|
+
}:
|
|
1273
|
+
return None
|
|
1274
|
+
tool_input = event.get("tool_input")
|
|
1275
|
+
if not isinstance(tool_input, dict):
|
|
1276
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "exec input is malformed")
|
|
1277
|
+
command = tool_input.get("command", tool_input.get("cmd"))
|
|
1278
|
+
if not isinstance(command, str):
|
|
1279
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "exec command is absent")
|
|
1280
|
+
compound = any(marker in command for marker in ("\n", "\r", ";", "&&", "||", "|", ">", "<", "`"))
|
|
1281
|
+
try:
|
|
1282
|
+
tokens = [token.strip('"') for token in shlex.split(command, posix=False)]
|
|
1283
|
+
except ValueError as exc:
|
|
1284
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "verifier wrapper command is malformed") from exc
|
|
1285
|
+
wrapper_start = (
|
|
1286
|
+
len(tokens) >= 3
|
|
1287
|
+
and Path(tokens[0]).name.lower() in {
|
|
1288
|
+
"python", "python.exe", "python3", "python3.exe", "py", "py.exe",
|
|
1289
|
+
}
|
|
1290
|
+
and Path(tokens[1]).name.lower() == "artifact-authority.py"
|
|
1291
|
+
and tokens[2] == "verify"
|
|
1292
|
+
)
|
|
1293
|
+
if compound:
|
|
1294
|
+
if wrapper_start:
|
|
1295
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "verifier wrapper command is compound")
|
|
1296
|
+
return None
|
|
1297
|
+
if len(tokens) != 7:
|
|
1298
|
+
return None
|
|
1299
|
+
if (
|
|
1300
|
+
Path(tokens[0]).name.lower() not in {
|
|
1301
|
+
"python", "python.exe", "python3", "python3.exe", "py", "py.exe",
|
|
1302
|
+
}
|
|
1303
|
+
or Path(tokens[1]).name.lower() != "artifact-authority.py"
|
|
1304
|
+
or tokens[2] != "verify"
|
|
1305
|
+
or tokens[3] != "--root"
|
|
1306
|
+
or tokens[5] != "--request-id"
|
|
1307
|
+
or REQUEST_RE.fullmatch(tokens[6]) is None
|
|
1308
|
+
):
|
|
1309
|
+
return None
|
|
1310
|
+
return tokens[6], _real_root(tokens[4])
|
|
1311
|
+
|
|
1312
|
+
|
|
1313
|
+
def observe_verifier_hook(
|
|
1314
|
+
invocation_root: str | Path, event: dict[str, Any]
|
|
1315
|
+
) -> dict[str, Any] | None:
|
|
1316
|
+
"""Authorize/corroborate the wrapper; native exit/output remain result evidence."""
|
|
1317
|
+
_real_root(invocation_root)
|
|
1318
|
+
if not isinstance(event, dict) or event.get("hook_event_name") not in {"PreToolUse", "PostToolUse"}:
|
|
1319
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "verifier hook event is unsupported")
|
|
1320
|
+
selected = _verification_selector(event)
|
|
1321
|
+
if selected is None:
|
|
1322
|
+
return None
|
|
1323
|
+
request_id, selected_root = selected
|
|
1324
|
+
root, request = _registered_request(request_id)
|
|
1325
|
+
if root != selected_root or request["activity"] != "verification":
|
|
1326
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "verifier target does not match its request")
|
|
1327
|
+
session_id = _host_id(event.get("session_id"), "session_id")
|
|
1328
|
+
turn_id = _host_id(event.get("turn_id"), "turn_id")
|
|
1329
|
+
tool_use_id = _host_id(event.get("tool_use_id"), "tool_use_id")
|
|
1330
|
+
if event["hook_event_name"] == "PreToolUse":
|
|
1331
|
+
if request["state"] != "ARMED" or request.get("wrapper") is not None:
|
|
1332
|
+
raise AuthorityError("INTERRUPTED_ATTEMPT", "verifier wrapper is not armable")
|
|
1333
|
+
current_bindings = _bind_commands(root, request["context"], request["workspace_roots"])
|
|
1334
|
+
if current_bindings != request["command_bindings"]:
|
|
1335
|
+
raise AuthorityError("WORKSPACE_DRIFT", "effective child argv or cwd changed")
|
|
1336
|
+
for command in request["context"]["commands"]:
|
|
1337
|
+
definition = command["definition"]
|
|
1338
|
+
_collector_profile(definition["argv"], definition["required_tests"])
|
|
1339
|
+
workspace_before = _workspace_snapshots(current_bindings)
|
|
1340
|
+
request["wrapper"] = {
|
|
1341
|
+
"state": "AUTHORIZED", "session_id": session_id,
|
|
1342
|
+
"turn_id": turn_id, "tool_use_id": tool_use_id,
|
|
1343
|
+
"command_bindings": current_bindings,
|
|
1344
|
+
"workspace_before": workspace_before,
|
|
1345
|
+
}
|
|
1346
|
+
_save(root, request)
|
|
1347
|
+
return {
|
|
1348
|
+
"request_id": request_id, "state": "AUTHORIZED",
|
|
1349
|
+
"command_bindings": current_bindings, "workspace_before": workspace_before,
|
|
1350
|
+
}
|
|
1351
|
+
wrapper = request.get("wrapper")
|
|
1352
|
+
if (
|
|
1353
|
+
not isinstance(wrapper, dict)
|
|
1354
|
+
or wrapper.get("session_id") != session_id
|
|
1355
|
+
or wrapper.get("turn_id") != turn_id
|
|
1356
|
+
or wrapper.get("tool_use_id") != tool_use_id
|
|
1357
|
+
or request["state"] != "COMPLETED"
|
|
1358
|
+
):
|
|
1359
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "verifier completion is not correlated")
|
|
1360
|
+
response = event.get("tool_response")
|
|
1361
|
+
if isinstance(response, dict):
|
|
1362
|
+
if response.get("exit_code") != 0:
|
|
1363
|
+
raise AuthorityError("FAILED_VERIFICATION", "verifier wrapper did not report exit 0")
|
|
1364
|
+
elif isinstance(response, str):
|
|
1365
|
+
try:
|
|
1366
|
+
completed = json.loads(response) if len(response.encode("utf-8")) <= MAX_OUTPUT else None
|
|
1367
|
+
except ValueError:
|
|
1368
|
+
completed = None
|
|
1369
|
+
if (
|
|
1370
|
+
not isinstance(completed, dict)
|
|
1371
|
+
or completed.get("request_id") != request_id
|
|
1372
|
+
or completed.get("state") != "COMPLETED"
|
|
1373
|
+
):
|
|
1374
|
+
raise AuthorityError("FAILED_VERIFICATION", "verifier wrapper output did not corroborate completion")
|
|
1375
|
+
else:
|
|
1376
|
+
raise AuthorityError("FAILED_VERIFICATION", "verifier wrapper result is unavailable")
|
|
1377
|
+
wrapper["state"] = "CORROBORATED"
|
|
1378
|
+
_save(root, request)
|
|
1379
|
+
return {"request_id": request_id, "state": "CORROBORATED"}
|
|
1380
|
+
|
|
1381
|
+
|
|
1382
|
+
def _parse_decision(raw: Any, request: dict[str, Any]) -> dict[str, Any]:
|
|
1383
|
+
if not isinstance(raw, str) or len(raw.encode("utf-8")) > MAX_DECISION:
|
|
1384
|
+
raise AuthorityError("INVALID_REVIEW_DECISION", "review decision is absent or oversized")
|
|
1385
|
+
try:
|
|
1386
|
+
value = json.loads(raw)
|
|
1387
|
+
except ValueError as exc:
|
|
1388
|
+
raise AuthorityError("INVALID_REVIEW_DECISION", "review decision is not JSON") from exc
|
|
1389
|
+
expected = {
|
|
1390
|
+
"format", "request_id", "target_sha256", "contract_sha256", "decision",
|
|
1391
|
+
"coverage", "findings", "assessment",
|
|
1392
|
+
}
|
|
1393
|
+
context = request["context"]
|
|
1394
|
+
if (
|
|
1395
|
+
not isinstance(value, dict)
|
|
1396
|
+
or set(value) != expected
|
|
1397
|
+
or value.get("format") != DECISION_FORMAT
|
|
1398
|
+
or value.get("request_id") != request["request_id"]
|
|
1399
|
+
or value.get("target_sha256") != context["input_sha256"]
|
|
1400
|
+
or value.get("contract_sha256") != request["review_contract_sha256"]
|
|
1401
|
+
or value.get("decision") not in {"pass", "changes_requested"}
|
|
1402
|
+
or not isinstance(value.get("assessment"), str)
|
|
1403
|
+
or not value["assessment"].strip()
|
|
1404
|
+
or not isinstance(value.get("coverage"), list)
|
|
1405
|
+
or len(set(value["coverage"])) != len(value["coverage"])
|
|
1406
|
+
or not isinstance(value.get("findings"), list)
|
|
1407
|
+
):
|
|
1408
|
+
raise AuthorityError("INVALID_REVIEW_DECISION", "review decision failed its closed schema")
|
|
1409
|
+
if set(value["coverage"]) != set(request["required_coverage"]):
|
|
1410
|
+
raise AuthorityError("INCOMPLETE_REVIEW", "review coverage is incomplete")
|
|
1411
|
+
for finding in value["findings"]:
|
|
1412
|
+
if (
|
|
1413
|
+
not isinstance(finding, dict)
|
|
1414
|
+
or set(finding) != {"severity", "code", "message"}
|
|
1415
|
+
or finding["severity"] not in {"BLOCK", "WARN", "INFO"}
|
|
1416
|
+
or not all(isinstance(finding[key], str) and finding[key] for key in ("code", "message"))
|
|
1417
|
+
):
|
|
1418
|
+
raise AuthorityError("INVALID_REVIEW_DECISION", "review finding is malformed")
|
|
1419
|
+
if value["decision"] != "pass" or any(
|
|
1420
|
+
finding["severity"] == "BLOCK" for finding in value["findings"]
|
|
1421
|
+
):
|
|
1422
|
+
raise AuthorityError("REVIEW_REJECTED", "independent reviewer did not pass the target")
|
|
1423
|
+
return value
|
|
1424
|
+
|
|
1425
|
+
|
|
1426
|
+
def observe_codex_hook(root: str | Path, event: dict[str, Any]) -> dict[str, Any]:
|
|
1427
|
+
_real_root(root) # Invocation repository is observed but cannot select authority.
|
|
1428
|
+
if not isinstance(event, dict):
|
|
1429
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "hook event is malformed")
|
|
1430
|
+
name = event.get("hook_event_name")
|
|
1431
|
+
session_id = _host_id(event.get("session_id"), "session_id")
|
|
1432
|
+
|
|
1433
|
+
if name == "PreToolUse":
|
|
1434
|
+
if event.get("tool_name") != "spawn_agent":
|
|
1435
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "only spawn_agent can launch a reviewer")
|
|
1436
|
+
tool_use_id = _host_id(event.get("tool_use_id"), "tool_use_id")
|
|
1437
|
+
turn_id = _host_id(event.get("turn_id"), "turn_id")
|
|
1438
|
+
tool_input = event.get("tool_input")
|
|
1439
|
+
if not isinstance(tool_input, dict):
|
|
1440
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "spawn input is malformed")
|
|
1441
|
+
message = tool_input.get("message")
|
|
1442
|
+
if not isinstance(message, str) or not message.startswith("[CODEARBITER_AUTHORITY_REQUEST:"):
|
|
1443
|
+
_record_ordinary_spawn(session_id, turn_id)
|
|
1444
|
+
_reject_overlapping_authority(session_id, turn_id)
|
|
1445
|
+
return None
|
|
1446
|
+
request_id = _request_id_from_prompt(message)
|
|
1447
|
+
root, request = _registered_request(request_id)
|
|
1448
|
+
if request["activity"] not in REVIEW_ACTIVITIES or request["state"] != "ARMED":
|
|
1449
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "review request is not launchable")
|
|
1450
|
+
if tool_input != request.get("launch_envelope"):
|
|
1451
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "review launch envelope was changed")
|
|
1452
|
+
if _ordinary_spawn_marker(session_id, turn_id).exists():
|
|
1453
|
+
request["state"] = "REJECTED"
|
|
1454
|
+
request["recovery"] = {
|
|
1455
|
+
"mode": "overlapping-subagent-launch",
|
|
1456
|
+
"rerun_permitted": False,
|
|
1457
|
+
}
|
|
1458
|
+
_save(root, request)
|
|
1459
|
+
raise AuthorityError(
|
|
1460
|
+
"UNSUPPORTED_HOST_SEAM",
|
|
1461
|
+
"authority review cannot share a parent turn with another subagent launch",
|
|
1462
|
+
)
|
|
1463
|
+
request["launch"] = {
|
|
1464
|
+
"parent_session_id": session_id, "parent_turn_id": turn_id,
|
|
1465
|
+
"tool_use_id": tool_use_id, "post_confirmed": False,
|
|
1466
|
+
"agent_id": None, "agent_type": None,
|
|
1467
|
+
"task_name": tool_input["task_name"],
|
|
1468
|
+
"fork_turns": tool_input["fork_turns"],
|
|
1469
|
+
}
|
|
1470
|
+
request["state"] = "LAUNCHING"
|
|
1471
|
+
_save(root, request)
|
|
1472
|
+
return {"request_id": request_id, "state": "LAUNCHING"}
|
|
1473
|
+
|
|
1474
|
+
if name == "PostToolUse":
|
|
1475
|
+
if event.get("tool_name") != "spawn_agent":
|
|
1476
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "post-tool event is not reviewer launch")
|
|
1477
|
+
tool_use_id = _host_id(event.get("tool_use_id"), "tool_use_id")
|
|
1478
|
+
turn_id = _host_id(event.get("turn_id"), "turn_id")
|
|
1479
|
+
response = event.get("tool_response")
|
|
1480
|
+
if not isinstance(response, dict):
|
|
1481
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "spawn result is malformed")
|
|
1482
|
+
task_name = _host_id(response.get("task_name"), "task_name")
|
|
1483
|
+
matches = []
|
|
1484
|
+
for candidate_root, candidate in _registered_requests():
|
|
1485
|
+
launch = candidate.get("launch") or {}
|
|
1486
|
+
if (
|
|
1487
|
+
candidate["state"] in {"LAUNCHING", "RUNNING"}
|
|
1488
|
+
and launch.get("parent_session_id") == session_id
|
|
1489
|
+
and launch.get("parent_turn_id") == turn_id
|
|
1490
|
+
and launch.get("tool_use_id") == tool_use_id
|
|
1491
|
+
):
|
|
1492
|
+
matches.append((candidate_root, candidate))
|
|
1493
|
+
if not matches:
|
|
1494
|
+
return None
|
|
1495
|
+
if len(matches) != 1:
|
|
1496
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "review launch correlation is ambiguous")
|
|
1497
|
+
root, request = matches[0]
|
|
1498
|
+
if task_name != request["launch"].get("task_name"):
|
|
1499
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "spawn result task_name differs")
|
|
1500
|
+
request["launch"]["post_confirmed"] = True
|
|
1501
|
+
_save(root, request)
|
|
1502
|
+
return {"request_id": request["request_id"], "state": request["state"]}
|
|
1503
|
+
|
|
1504
|
+
if name in {"SubagentStart", "SubagentStop"}:
|
|
1505
|
+
agent_id = _host_id(event.get("agent_id"), "agent_id")
|
|
1506
|
+
turn_id = _host_id(event.get("turn_id"), "turn_id")
|
|
1507
|
+
matches = []
|
|
1508
|
+
same_turn = []
|
|
1509
|
+
for candidate_root, candidate in _registered_requests():
|
|
1510
|
+
launch = candidate.get("launch") or {}
|
|
1511
|
+
if (
|
|
1512
|
+
launch.get("parent_session_id") == session_id
|
|
1513
|
+
and launch.get("parent_turn_id") == turn_id
|
|
1514
|
+
and candidate["state"] in {"LAUNCHING", "RUNNING"}
|
|
1515
|
+
):
|
|
1516
|
+
same_turn.append((candidate_root, candidate))
|
|
1517
|
+
if name == "SubagentStart" and (
|
|
1518
|
+
candidate["state"] == "LAUNCHING"
|
|
1519
|
+
and launch.get("parent_session_id") == session_id
|
|
1520
|
+
and launch.get("parent_turn_id") == turn_id
|
|
1521
|
+
):
|
|
1522
|
+
matches.append((candidate_root, candidate))
|
|
1523
|
+
elif name == "SubagentStop" and (
|
|
1524
|
+
launch.get("parent_session_id") == session_id
|
|
1525
|
+
and launch.get("parent_turn_id") == turn_id
|
|
1526
|
+
and launch.get("agent_id") == agent_id
|
|
1527
|
+
):
|
|
1528
|
+
matches.append((candidate_root, candidate))
|
|
1529
|
+
if not matches and not same_turn:
|
|
1530
|
+
return None
|
|
1531
|
+
if not matches and same_turn:
|
|
1532
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "subagent does not match the active authority launch")
|
|
1533
|
+
if len(matches) != 1:
|
|
1534
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "subagent correlation is ambiguous")
|
|
1535
|
+
root, request = matches[0]
|
|
1536
|
+
agent_type = _host_id(event.get("agent_type"), "agent_type")
|
|
1537
|
+
if name == "SubagentStart":
|
|
1538
|
+
request["launch"]["agent_id"] = agent_id
|
|
1539
|
+
request["launch"]["agent_type"] = agent_type
|
|
1540
|
+
request["state"] = "RUNNING"
|
|
1541
|
+
_save(root, request)
|
|
1542
|
+
return {"request_id": request["request_id"], "state": "RUNNING"}
|
|
1543
|
+
if (
|
|
1544
|
+
request["state"] != "RUNNING"
|
|
1545
|
+
or request["launch"].get("agent_type") != agent_type
|
|
1546
|
+
or request["launch"].get("post_confirmed") is not True
|
|
1547
|
+
):
|
|
1548
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "subagent stop is out of order")
|
|
1549
|
+
try:
|
|
1550
|
+
decision = _parse_decision(event.get("last_assistant_message"), request)
|
|
1551
|
+
except AuthorityError:
|
|
1552
|
+
request["state"] = "REJECTED"
|
|
1553
|
+
_save(root, request)
|
|
1554
|
+
raise
|
|
1555
|
+
payload = {
|
|
1556
|
+
"input_sha256": request["context"]["input_sha256"],
|
|
1557
|
+
"spec_sha256": request["context"]["spec_sha256"],
|
|
1558
|
+
"assessment": decision["assessment"],
|
|
1559
|
+
}
|
|
1560
|
+
if request["activity"] == "spec_review":
|
|
1561
|
+
payload["task_sha256"] = request["context"]["task_sha256"]
|
|
1562
|
+
else:
|
|
1563
|
+
payload["base_input_sha256"] = request["context"]["base_input_sha256"]
|
|
1564
|
+
payload["task_hashes"] = request["context"]["task_hashes"]
|
|
1565
|
+
producer_result = {"launch": request["launch"], "decision": decision}
|
|
1566
|
+
observation = _closed_observation(
|
|
1567
|
+
request, payload, "codex-review/0.1.0", agent_id,
|
|
1568
|
+
producer_result,
|
|
1569
|
+
)
|
|
1570
|
+
_observation(root, request, observation)
|
|
1571
|
+
request["payload"] = payload
|
|
1572
|
+
request["state"] = "COMPLETED"
|
|
1573
|
+
_save(root, request)
|
|
1574
|
+
return {"request_id": request["request_id"], "state": "COMPLETED"}
|
|
1575
|
+
|
|
1576
|
+
raise AuthorityError("UNSUPPORTED_HOST_SEAM", "hook event is not supported")
|
|
1577
|
+
|
|
1578
|
+
|
|
1579
|
+
def _event(request: dict[str, Any]) -> dict[str, Any]:
|
|
1580
|
+
context = request["context"]
|
|
1581
|
+
activity = request["activity"]
|
|
1582
|
+
if activity == "verification":
|
|
1583
|
+
authority_kind = "verification_runner"
|
|
1584
|
+
actor = "codeArbiter supervised verifier"
|
|
1585
|
+
origin = f"codex:verification-run:{request['request_id']}"
|
|
1586
|
+
source_text = "Observed exact declared-command completion and qualified named-test results."
|
|
1587
|
+
else:
|
|
1588
|
+
authority_kind = "review_workflow"
|
|
1589
|
+
actor = "independent codex reviewer"
|
|
1590
|
+
origin = f"codex:subagent:{request['launch']['agent_id']}"
|
|
1591
|
+
source_text = "Observed a correlated fresh Codex reviewer completion with a closed passing decision."
|
|
1592
|
+
return {
|
|
1593
|
+
"format": EVENT_FORMAT,
|
|
1594
|
+
"kind": activity,
|
|
1595
|
+
"authority_kind": authority_kind,
|
|
1596
|
+
"subject": context["subject"],
|
|
1597
|
+
"actor": actor,
|
|
1598
|
+
"origin": origin,
|
|
1599
|
+
"verdict": "passed",
|
|
1600
|
+
"payload": request["payload"],
|
|
1601
|
+
"source_text": source_text,
|
|
1602
|
+
"observation_ref": request["observation_ref"],
|
|
1603
|
+
"observation_sha256": request["observation_sha256"],
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
|
|
1607
|
+
def publish_request(
|
|
1608
|
+
root: str | Path, client: Any, request_id: str
|
|
1609
|
+
) -> dict[str, Any]:
|
|
1610
|
+
root = _real_root(root)
|
|
1611
|
+
request = _load(root, request_id)
|
|
1612
|
+
if request["state"] == "CAPTURED":
|
|
1613
|
+
return {
|
|
1614
|
+
"request_id": request_id, "receipt": request["receipt"],
|
|
1615
|
+
"authority_source": request["authority_source"],
|
|
1616
|
+
}
|
|
1617
|
+
if request["state"] != "COMPLETED":
|
|
1618
|
+
raise AuthorityError("AUTHORITY_NOT_COMPLETE", "successful observation is absent")
|
|
1619
|
+
if request["activity"] == "verification":
|
|
1620
|
+
wrapper = request.get("wrapper")
|
|
1621
|
+
if not isinstance(wrapper, dict) or wrapper.get("state") != "CORROBORATED":
|
|
1622
|
+
raise AuthorityError("AUTHORITY_NOT_COMPLETE", "verifier wrapper completion is uncorroborated")
|
|
1623
|
+
current, current_ref, current_hash = _context(
|
|
1624
|
+
root, client, request["context"]["subject"]["artifact_id"],
|
|
1625
|
+
request["context"]["subject"]["record_id"],
|
|
1626
|
+
request["activity"],
|
|
1627
|
+
)
|
|
1628
|
+
if (
|
|
1629
|
+
current != request["context"]
|
|
1630
|
+
or current_ref != request["context_ref"]
|
|
1631
|
+
or current_hash != request["context_sha256"]
|
|
1632
|
+
):
|
|
1633
|
+
raise AuthorityError("STALE_AUTHORITY_REQUEST", "engine evidence context changed")
|
|
1634
|
+
observation_path = root / request["observation_ref"]
|
|
1635
|
+
try:
|
|
1636
|
+
observation = observation_path.read_bytes()
|
|
1637
|
+
except OSError as exc:
|
|
1638
|
+
raise AuthorityError("INVALID_OBSERVATION", "observation is unavailable") from exc
|
|
1639
|
+
if _digest(observation) != request["observation_sha256"]:
|
|
1640
|
+
raise AuthorityError("INVALID_OBSERVATION", "observation digest differs")
|
|
1641
|
+
event = _event(request)
|
|
1642
|
+
raw = _canonical(event)
|
|
1643
|
+
digest = _digest(raw)
|
|
1644
|
+
relative = SOURCE_DIR / f"{digest}.json"
|
|
1645
|
+
_publish_immutable(root, relative, raw)
|
|
1646
|
+
captured = client.call("capture-observation", {
|
|
1647
|
+
"source_ref": relative.as_posix(), "source_sha256": digest,
|
|
1648
|
+
})
|
|
1649
|
+
receipt = captured.get("receipt")
|
|
1650
|
+
if not isinstance(receipt, str) or not receipt:
|
|
1651
|
+
raise AuthorityError("INVALID_RECEIPT", "capture returned no receipt")
|
|
1652
|
+
request["state"] = "CAPTURED"
|
|
1653
|
+
request["receipt"] = receipt
|
|
1654
|
+
request["authority_source"] = relative.as_posix()
|
|
1655
|
+
_save(root, request)
|
|
1656
|
+
return {"request_id": request_id, "receipt": receipt, "authority_source": relative.as_posix()}
|
|
1657
|
+
|
|
1658
|
+
|
|
1659
|
+
def recover_request(root: str | Path, request_id: str, disposition: str) -> dict[str, Any]:
|
|
1660
|
+
"""Retain an uncertain attempt and close it without ever rerunning commands."""
|
|
1661
|
+
root = _real_root(root)
|
|
1662
|
+
if disposition not in {"failed", "abandoned"}:
|
|
1663
|
+
raise AuthorityError("INVALID_RECOVERY", "recovery disposition must be failed or abandoned")
|
|
1664
|
+
request = _load(root, request_id)
|
|
1665
|
+
recoverable_completed = (
|
|
1666
|
+
request["state"] == "COMPLETED"
|
|
1667
|
+
and request["activity"] == "verification"
|
|
1668
|
+
and isinstance(request.get("wrapper"), dict)
|
|
1669
|
+
and request["wrapper"].get("state") != "CORROBORATED"
|
|
1670
|
+
)
|
|
1671
|
+
recoverable_authorized = (
|
|
1672
|
+
request["state"] == "ARMED"
|
|
1673
|
+
and request["activity"] == "verification"
|
|
1674
|
+
and isinstance(request.get("wrapper"), dict)
|
|
1675
|
+
and request["wrapper"].get("state") == "AUTHORIZED"
|
|
1676
|
+
)
|
|
1677
|
+
if request["state"] not in {"RUNNING", "LAUNCHING"} and not recoverable_completed and not recoverable_authorized:
|
|
1678
|
+
raise AuthorityError("INVALID_RECOVERY", "only an interrupted active attempt can be recovered")
|
|
1679
|
+
request["state"] = disposition.upper()
|
|
1680
|
+
request["recovery"] = {
|
|
1681
|
+
"disposition": disposition,
|
|
1682
|
+
"previous_attempt": request.get("attempt"),
|
|
1683
|
+
"rerun_permitted": False,
|
|
1684
|
+
}
|
|
1685
|
+
_save(root, request)
|
|
1686
|
+
return {"request_id": request_id, "state": request["state"], "rerun_permitted": False}
|