Skip to repository content359 lines · 13.6 KB · text
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-27T23:36:35.167Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
review-omega-candidate
1#!/usr/bin/env python3
2"""Independently review an installed Omega candidate.
3
4This is the reviewer half of omega#67. It is deliberately NOT given the
5producer's evidence directory. It receives the candidate artifact and a small
6producer *claim*, then reproduces every check itself and compares.
7
8The distinction matters: reading the producer's JSON and confirming it parses
9is not review, it is proofreading. Everything below is recomputed from the
10artifact itself. The application it checks is the one inside the candidate
11disk image, not whatever is installed.
12
13Independence is structural, not declared:
14
15- The reviewer key is read from a path a producer run does not set. If
16 OMEGA_REVIEWER_KEY_FILE is absent, this refuses rather than falling back to
17 any ambient credential.
18- The receipt is signed, so a producer cannot write the reviewer's public key
19 into a document it authored itself.
20- `accepted` requires every reproduction to agree. `inconclusive` exists so a
21 check that cannot run reports honestly instead of being rounded to either
22 approval or rejection.
23
24Usage:
25 OMEGA_REVIEWER_KEY_FILE=~/work/.secrets/omega-independent-reviewer.env \\
26 review-omega-candidate \\
27 --candidate-dmg <dmg> \\
28 --producer-claim <claim.json> --output <receipt.json>
29"""
30
31from __future__ import annotations
32
33import argparse
34import hashlib
35import json
36import os
37import subprocess
38import sys
39import tempfile
40from contextlib import contextmanager
41from datetime import datetime, timezone
42from pathlib import Path
43from typing import Any
44
45SCHEMA = "openagents.assurance.independent-review.v1"
46
47#: Strings that must not appear in a shipped binary. Kept here rather than
48#: imported from the producer's tooling: a reviewer that shares the producer's
49#: definition of "clean" cannot catch a wrong definition.
50FORBIDDEN_STRINGS = [
51 "Unrecognized Project",
52 "Review .zed/settings.json",
53 "Zed's hosted models",
54 "14 day free trial",
55 "Pro trial",
56 "Move Omega to Applications",
57]
58
59
60class ReviewError(Exception):
61 pass
62
63
64def sha256_file(path: Path) -> str:
65 digest = hashlib.sha256()
66 with path.open("rb") as handle:
67 for chunk in iter(lambda: handle.read(1024 * 1024), b""):
68 digest.update(chunk)
69 return digest.hexdigest()
70
71
72def canonical_digest(value: Any) -> str:
73 return hashlib.sha256(
74 json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode(
75 "utf-8"
76 )
77 ).hexdigest()
78
79
80def load_reviewer_key() -> tuple[str, str]:
81 """Read the reviewer identity from a path a producer run does not set.
82
83 There is no fallback on purpose. An ambient-credential fallback would mean
84 a producer that happens to have some key in scope could sign a review.
85 """
86 location = os.environ.get("OMEGA_REVIEWER_KEY_FILE")
87 if not location:
88 raise ReviewError(
89 "OMEGA_REVIEWER_KEY_FILE is not set; a review cannot borrow an "
90 "ambient credential"
91 )
92 path = Path(location).expanduser()
93 if path.is_symlink() or not path.is_file():
94 raise ReviewError("reviewer key file is missing or is not a regular file")
95 mode = path.stat().st_mode & 0o077
96 if mode != 0:
97 raise ReviewError("reviewer key file is readable by group or others")
98 secret = public = ""
99 for line in path.read_text(encoding="utf-8").splitlines():
100 if line.startswith("OMEGA_REVIEWER_SECRET_KEY_HEX="):
101 secret = line.split("=", 1)[1].strip()
102 elif line.startswith("OMEGA_REVIEWER_PUBLIC_KEY_HEX="):
103 public = line.split("=", 1)[1].strip()
104 if len(secret) != 64 or len(public) != 64:
105 raise ReviewError("reviewer key file does not contain a usable keypair")
106 return secret, public
107
108
109def signing_package_root() -> Path:
110 """Locate the package whose `node_modules` carry the signing curves.
111
112 This was derived from the script's own location, three parents up plus
113 `openagents/packages/sarah`. That holds only when the reviewer runs from
114 the canonical omega checkout beside a canonical openagents checkout — and
115 an independent reviewer is supposed to run from an isolated worktree,
116 where it raises `FileNotFoundError` from inside `subprocess`. A reviewer
117 that cannot sign from an isolated tree pushes reviews back into the
118 producer's directory layout, which is the opposite of the point.
119
120 Candidates are tried in order and the first that exists wins, so an
121 unusual layout can be handled by setting the environment variable rather
122 than by moving the checkout.
123 """
124 override = os.environ.get("OMEGA_REVIEW_SIGNING_PACKAGE")
125 candidates = [Path(override).expanduser()] if override else []
126 here = Path(__file__).resolve()
127 candidates += [
128 here.parent.parent.parent / "openagents" / "packages" / "sarah",
129 Path.home() / "work" / "openagents" / "packages" / "sarah",
130 ]
131 for candidate in candidates:
132 if (candidate / "node_modules").is_dir():
133 return candidate
134 raise ReviewError(
135 "cannot find a package providing the signing curves; set "
136 "OMEGA_REVIEW_SIGNING_PACKAGE to a directory whose node_modules has "
137 "@noble/curves"
138 )
139
140
141def sign(message_hex: str, secret_hex: str) -> str:
142 """Schnorr-sign the evidence digest with the reviewer key."""
143 result = subprocess.run(
144 [
145 "node",
146 "--input-type=module",
147 "-e",
148 (
149 "import { schnorr } from '@noble/curves/secp256k1';"
150 "import { bytesToHex, hexToBytes } from '@noble/hashes/utils';"
151 "const [msg, sk] = process.argv.slice(1);"
152 "process.stdout.write(bytesToHex(schnorr.sign(hexToBytes(msg), hexToBytes(sk))));"
153 ),
154 message_hex,
155 secret_hex,
156 ],
157 capture_output=True,
158 text=True,
159 cwd=str(signing_package_root()),
160 )
161 if result.returncode != 0 or len(result.stdout.strip()) != 128:
162 raise ReviewError(f"reviewer signature failed: {result.stderr.strip()[:200]}")
163 return result.stdout.strip()
164
165
166def observe(command: list[str]) -> tuple[bool, str]:
167 """Run a check. Returns (ran, output). A check that cannot run is not a fail."""
168 try:
169 result = subprocess.run(command, capture_output=True, text=True, timeout=1800)
170 except (OSError, subprocess.TimeoutExpired):
171 return False, ""
172 return True, (result.stdout + result.stderr)
173
174
175@contextmanager
176def mounted_candidate(dmg: Path):
177 """Yield the `Omega.app` inside `dmg`, mounted read-only.
178
179 Every check must bind to one artifact. This existed as `--app`, defaulting
180 to `/Applications/Omega.app`, and two of the four checks read it while the
181 other two read the disk image. An independent review run exactly as
182 documented then returned `accepted` with zero disagreements while
183 `notarization` and `forbidden_strings` were reading a *different candidate*
184 that happened to be installed — the reviewer caught it only by comparing
185 `CFBundleVersion`, the host binary digest and the CDHash by hand.
186
187 A review that can pass against an artifact nobody named is worse than no
188 review, so the app is no longer an input. It comes out of the disk image
189 the claim is about.
190 """
191 mount_point = Path(tempfile.mkdtemp(prefix="omega-review-"))
192 attach = subprocess.run(
193 ["hdiutil", "attach", "-nobrowse", "-readonly", "-quiet",
194 "-mountpoint", str(mount_point), str(dmg)],
195 capture_output=True, text=True,
196 )
197 if attach.returncode != 0:
198 raise ReviewError(f"cannot mount the candidate: {attach.stderr.strip()[:200]}")
199 try:
200 apps = sorted(mount_point.glob("*.app"))
201 if len(apps) != 1:
202 raise ReviewError(
203 f"expected exactly one application in the candidate, found {len(apps)}"
204 )
205 yield apps[0]
206 finally:
207 subprocess.run(["hdiutil", "detach", str(mount_point), "-quiet"],
208 capture_output=True)
209
210
211def main() -> int:
212 parser = argparse.ArgumentParser()
213 parser.add_argument("--candidate-dmg", type=Path, required=True)
214 parser.add_argument("--producer-claim", type=Path, required=True)
215 parser.add_argument("--obligation", default="omega#16")
216 parser.add_argument("--output", type=Path, required=True)
217 args = parser.parse_args()
218
219 secret, reviewer_pubkey = load_reviewer_key()
220 claim = json.loads(args.producer_claim.read_text(encoding="utf-8"))
221 producers = claim.get("producerPubkeys") or []
222 if not producers:
223 raise ReviewError("producer claim names no producer")
224 if reviewer_pubkey in producers:
225 raise ReviewError("the reviewer key is also a producer key; this is not a review")
226
227 reproductions: list[dict[str, Any]] = []
228 disagreements: list[dict[str, str]] = []
229 inconclusive = False
230
231 def record(check: str, command: str, observed: str, claimed: str) -> None:
232 agrees = observed == claimed
233 reproductions.append(
234 {
235 "check": check,
236 "command": command,
237 "observedDigest": "sha256:" + hashlib.sha256(observed.encode()).hexdigest(),
238 "agreesWithProducer": agrees,
239 }
240 )
241 if not agrees:
242 disagreements.append(
243 {
244 "criterion": check,
245 "producerClaim": claimed[:200],
246 "reviewerObservation": observed[:200],
247 }
248 )
249
250 # 1. The artifact digest, recomputed from the file rather than read.
251 artifact = sha256_file(args.candidate_dmg)
252 record(
253 "artifact_digest",
254 f"shasum -a 256 {args.candidate_dmg.name}",
255 artifact,
256 claim.get("artifactSha256", ""),
257 )
258
259 # Checks 2 to 4 bind to the application inside the candidate, never to
260 # whatever happens to be installed. See mounted_candidate().
261 with mounted_candidate(args.candidate_dmg) as app:
262 binary = app / "Contents" / "MacOS" / "omega"
263
264 # 2. Gatekeeper's own verdict on the candidate's application.
265 ran, output = observe(["spctl", "-a", "-vvv", "-t", "exec", str(app)])
266 if not ran:
267 inconclusive = True
268 else:
269 notarized = "source=Notarized Developer ID" in output
270 record(
271 "notarization",
272 f"spctl -a -vvv -t exec <candidate>/{app.name}",
273 "notarized" if notarized else "not_notarized",
274 claim.get("notarization", ""),
275 )
276
277 # 3. The stapled ticket on the disk image, because the bundler staples
278 # the image. The application inside it is stapled separately.
279 ran, output = observe(["xcrun", "stapler", "validate", str(args.candidate_dmg)])
280 if not ran:
281 inconclusive = True
282 else:
283 stapled = "The validate action worked" in output
284 record(
285 "stapled_ticket",
286 f"xcrun stapler validate {args.candidate_dmg.name}",
287 "stapled" if stapled else "not_stapled",
288 claim.get("stapled", ""),
289 )
290
291 # 4. Forbidden strings, scanned from the shipped binary. This is the
292 # check that caught two Zed surfaces a source review had passed.
293 ran, output = observe(["strings", str(binary)])
294 if not ran:
295 inconclusive = True
296 else:
297 found = sorted({needle for needle in FORBIDDEN_STRINGS if needle in output})
298 record(
299 "forbidden_strings",
300 f"strings <candidate>/{app.name}/Contents/MacOS/omega | grep -F <forbidden>",
301 ",".join(found) if found else "none",
302 claim.get("forbiddenStrings", ""),
303 )
304
305 if inconclusive:
306 outcome = "inconclusive"
307 disagreements.append(
308 {
309 "criterion": "reviewer environment",
310 "producerClaim": "all checks reproducible",
311 "reviewerObservation": "one or more checks could not run on this host",
312 }
313 )
314 elif disagreements:
315 outcome = "refused"
316 else:
317 outcome = "accepted"
318
319 receipt: dict[str, Any] = {
320 "schema": SCHEMA,
321 "reviewerPubkey": reviewer_pubkey,
322 "producerPubkeys": producers,
323 "candidateDigest": "sha256:" + artifact,
324 "obligationRef": args.obligation,
325 "reviewedAt": datetime.now(timezone.utc)
326 .isoformat(timespec="seconds")
327 .replace("+00:00", "Z"),
328 "outcome": outcome,
329 "reproductions": reproductions,
330 "disagreements": disagreements,
331 }
332 receipt["evidenceSha256"] = "sha256:" + canonical_digest(receipt)
333 receipt["reviewerSignature"] = sign(receipt["evidenceSha256"].split(":", 1)[1], secret)
334
335 args.output.parent.mkdir(parents=True, exist_ok=True)
336 args.output.write_text(
337 json.dumps(receipt, indent=2, sort_keys=True) + "\n", encoding="utf-8"
338 )
339 print(
340 json.dumps(
341 {
342 "schema": SCHEMA,
343 "outcome": outcome,
344 "reproductions": len(reproductions),
345 "disagreements": len(disagreements),
346 "output": str(args.output),
347 }
348 )
349 )
350 return 0 if outcome == "accepted" else 1
351
352
353if __name__ == "__main__":
354 try:
355 sys.exit(main())
356 except ReviewError as error:
357 print(f"error: {error}", file=sys.stderr)
358 sys.exit(2)
359