Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-27T23:37:39.620Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

collect-omega-full-auto-installed-evidence

605 lines · 27.8 KB · text
1#!/usr/bin/env python3
2"""Collect public-safe Full Auto receipts from one exact installed Omega candidate."""
3
4from __future__ import annotations
5
6import argparse
7import hashlib
8import json
9import os
10import plistlib
11import re
12import stat
13import subprocess
14import sys
15import tempfile
16from datetime import datetime
17from pathlib import Path
18from typing import Any
19
20
21SCHEMA = "openagents.omega.full-auto-installed-run-evidence.v1"
22RUNS_SCHEMA = "openagents.desktop.full_auto_run_registry.v1"
23REPORTS_SCHEMA = "openagents.desktop.full_auto_run_report.v1"
24HANDOFFS_SCHEMA = "openagents.desktop.provider_handoff_transition.v1"
25NATIVE_BINDINGS_SCHEMA = "openagents.omega.full_auto_native_binding.v1"
26REGISTRY_FILES = {
27    "runs": ("runs.json", RUNS_SCHEMA, "runs"),
28    "reports": ("run-reports.json", REPORTS_SCHEMA, "reports"),
29    "handoffs": ("provider-handoffs.json", HANDOFFS_SCHEMA, "transitions"),
30    "native_bindings": ("native-bindings.json", NATIVE_BINDINGS_SCHEMA, "bindings"),
31}
32RUN_REF_RE = re.compile(r"run\.full-auto\.[a-z0-9.-]+")
33REF_RE = re.compile(r"[A-Za-z0-9][A-Za-z0-9._:-]{0,255}")
34SHA256_RE = re.compile(r"[0-9a-f]{64}")
35COMMIT_RE = re.compile(r"[0-9a-f]{40}")
36ALLOWED_STATES = {
37    "draft", "running", "pausing", "paused", "stalled", "stopping",
38    "stopped", "completed", "failed",
39}
40ALLOWED_ACTORS = {"control_api", "effectd", "host", "owner", "system"}
41ALLOWED_LANES = {"codex-local", "claude-local"}
42ALLOWED_DISPOSITIONS = {
43    "complete_within_bounds", "completed", "failed", "interrupted",
44    "stopped", "cancelled", "timed_out", "unknown",
45}
46SENSITIVE_KEY_PARTS = {
47    "objective", "prompt", "message", "reason", "title", "path", "content",
48    "credential", "token", "secret", "password", "transcript", "summary",
49}
50SECRET_PATTERNS = (
51    re.compile(r"sk-[A-Za-z0-9_-]{12,}"),
52    re.compile(r"(?i)bearer\s+[A-Za-z0-9._~+/-]{12,}"),
53    re.compile(r"-----BEGIN [A-Z ]*PRIVATE KEY-----"),
54)
55
56
57class EvidenceError(RuntimeError):
58    pass
59
60
61def duplicate_key_guard(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
62    result: dict[str, Any] = {}
63    for key, value in pairs:
64        if key in result:
65            raise EvidenceError(f"duplicate JSON key: {key}")
66        result[key] = value
67    return result
68
69
70def canonical_bytes(value: Any) -> bytes:
71    return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode()
72
73
74def digest_bytes(value: bytes) -> str:
75    return hashlib.sha256(value).hexdigest()
76
77
78def digest_file(path: Path) -> str:
79    digest = hashlib.sha256()
80    with path.open("rb") as handle:
81        for chunk in iter(lambda: handle.read(1024 * 1024), b""):
82            digest.update(chunk)
83    return digest.hexdigest()
84
85
86def digest_value(value: Any) -> str:
87    return digest_bytes(canonical_bytes(value))
88
89
90def digest_ref(value: str, label: str) -> str:
91    require_ref(value, label)
92    return digest_bytes(value.encode())
93
94
95def require_ref(value: Any, label: str) -> str:
96    if not isinstance(value, str) or REF_RE.fullmatch(value) is None:
97        raise EvidenceError(f"{label} is not a safe public reference")
98    return value
99
100
101def require_sha256(value: Any, label: str) -> str:
102    if not isinstance(value, str) or SHA256_RE.fullmatch(value) is None:
103        raise EvidenceError(f"{label} must be a lowercase SHA-256 digest")
104    return value
105
106
107def require_time(value: Any, label: str) -> str:
108    if not isinstance(value, str):
109        raise EvidenceError(f"{label} must be an ISO-8601 timestamp")
110    try:
111        parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
112    except ValueError as error:
113        raise EvidenceError(f"{label} must be an ISO-8601 timestamp") from error
114    if parsed.tzinfo is None:
115        raise EvidenceError(f"{label} must include a timezone")
116    return value
117
118
119def require_integer(value: Any, label: str) -> int:
120    if isinstance(value, bool) or not isinstance(value, int) or value < 0:
121        raise EvidenceError(f"{label} must be a nonnegative integer")
122    return value
123
124
125def require_no_symlink_components(path: Path, label: str) -> None:
126    absolute = path.absolute()
127    current = Path(absolute.anchor)
128    for component in absolute.parts[1:]:
129        current /= component
130        if current.is_symlink():
131            raise EvidenceError(f"{label} traverses a symbolic link")
132
133
134def require_regular(path: Path, label: str, *, private: bool = False) -> os.stat_result:
135    require_no_symlink_components(path, label)
136    try:
137        metadata = path.stat()
138    except OSError as error:
139        raise EvidenceError(f"cannot inspect {label}: {error}") from error
140    if not stat.S_ISREG(metadata.st_mode):
141        raise EvidenceError(f"{label} must be a regular non-symlink file")
142    mode = stat.S_IMODE(metadata.st_mode)
143    if private and mode & 0o077:
144        raise EvidenceError(f"{label} must not grant group or other permissions")
145    if mode & 0o022:
146        raise EvidenceError(f"{label} must not be group- or world-writable")
147    return metadata
148
149
150def load_json(path: Path, label: str, *, private: bool = False) -> dict[str, Any]:
151    metadata = require_regular(path, label, private=private)
152    if metadata.st_size > 16 * 1024 * 1024:
153        raise EvidenceError(f"{label} is unexpectedly large")
154    try:
155        value = json.loads(path.read_text(encoding="utf-8"), object_pairs_hook=duplicate_key_guard)
156    except (OSError, UnicodeError, json.JSONDecodeError, EvidenceError) as error:
157        raise EvidenceError(f"cannot read {label}: {error}") from error
158    if not isinstance(value, dict):
159        raise EvidenceError(f"{label} must be a JSON object")
160    return value
161
162
163def exact_object(value: Any, keys: set[str], label: str) -> dict[str, Any]:
164    if not isinstance(value, dict):
165        raise EvidenceError(f"{label} must be an object")
166    if set(value) != keys:
167        raise EvidenceError(f"{label} keys are not exact")
168    return value
169
170
171def validate_registry_root(root: Path, release: dict[str, Any]) -> Path:
172    if not root.is_absolute():
173        raise EvidenceError("registry root must be absolute")
174    require_no_symlink_components(root, "registry root")
175    try:
176        metadata = root.stat()
177    except OSError as error:
178        raise EvidenceError(f"cannot inspect registry root: {error}") from error
179    if not stat.S_ISDIR(metadata.st_mode) or stat.S_IMODE(metadata.st_mode) & 0o077:
180        raise EvidenceError("registry root must be a private non-symlink directory")
181    if sys.platform == "darwin":
182        volume_name = release.get("volume_name")
183        if not isinstance(volume_name, str) or not volume_name.startswith("Omega"):
184            raise EvidenceError("release record has no valid Omega release namespace")
185        expected = Path.home() / "Library/Application Support" / volume_name / "openagents/full-auto"
186        if root != expected:
187            raise EvidenceError("registry root is outside the release-record namespace")
188    return root
189
190
191def candidate_bindings(release_path: Path, identity_path: Path, app: Path) -> dict[str, Any]:
192    release = load_json(release_path, "release record")
193    identity = load_json(identity_path, "identity candidate evidence")
194    if app != Path("/Applications/Omega.app"):
195        raise EvidenceError("installed application must be exactly /Applications/Omega.app")
196    require_no_symlink_components(app, "installed application")
197    if not app.is_dir():
198        raise EvidenceError("installed application is missing")
199    if stat.S_IMODE(app.stat().st_mode) & 0o022:
200        raise EvidenceError("installed application must not be group- or world-writable")
201    info_path = app / "Contents/Info.plist"
202    binary_path = app / "Contents/MacOS/omega"
203    require_regular(info_path, "installed Info.plist")
204    require_regular(binary_path, "installed Omega executable")
205    try:
206        with info_path.open("rb") as handle:
207            info = plistlib.load(handle)
208    except (OSError, plistlib.InvalidFileException) as error:
209        raise EvidenceError(f"cannot read installed Info.plist: {error}") from error
210    if info.get("CFBundleIdentifier") != release.get("bundle_identifier"):
211        raise EvidenceError("installed bundle identifier differs from release record")
212    bundle_version = release.get("bundle_version", release.get("version"))
213    if info.get("CFBundleShortVersionString") != bundle_version:
214        raise EvidenceError("installed version differs from release record")
215    binary_sha256 = digest_file(binary_path)
216    expected_binary = release.get("digests", {}).get("omega_binary_sha256")
217    if binary_sha256 != expected_binary:
218        raise EvidenceError("installed executable digest differs from release record")
219    artifact_sha256 = require_sha256(
220        release.get("digests", {}).get("package_sha256"), "release artifact digest"
221    )
222    omega_commit = release.get("source", {}).get("commit")
223    if not isinstance(omega_commit, str) or COMMIT_RE.fullmatch(omega_commit) is None:
224        raise EvidenceError("release record source commit is invalid")
225    candidate = identity.get("candidate")
226    candidate_digest = identity.get("candidate_digest", {}).get("value")
227    if not isinstance(candidate, dict):
228        raise EvidenceError("identity evidence has no candidate object")
229    require_sha256(candidate_digest, "identity candidate digest")
230    if digest_value(candidate) != candidate_digest:
231        raise EvidenceError("identity candidate digest is not canonical")
232    artifact = candidate.get("artifact", {})
233    if (
234        candidate.get("omega", {}).get("commit") != omega_commit
235        or artifact.get("sha256") != artifact_sha256
236        or artifact.get("release_record_sha256") != digest_file(release_path)
237    ):
238        raise EvidenceError("identity evidence binds a different candidate")
239    if sys.platform == "darwin":
240        result = subprocess.run(
241            ["codesign", "--verify", "--deep", "--strict", str(app)],
242            stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False,
243        )
244        if result.returncode != 0:
245            raise EvidenceError("installed application fails strict code-signature verification")
246    return {
247        "candidate_digest": candidate_digest,
248        "artifact_sha256": artifact_sha256,
249        "release_record_sha256": digest_file(release_path),
250        "omega_commit": omega_commit,
251        "installed_app_name": "Omega.app",
252        "installed_binary_sha256": binary_sha256,
253        "bundle_identifier": release["bundle_identifier"],
254        "release_version": release["version"],
255        "bundle_version": bundle_version,
256    }
257
258
259def transition_projection(value: Any, label: str) -> dict[str, Any]:
260    transition = exact_object(value, {"from", "to", "actor", "at", "reason"}, label)
261    source = transition["from"]
262    target = transition["to"]
263    actor = transition["actor"]
264    if source not in ALLOWED_STATES or target not in ALLOWED_STATES:
265        raise EvidenceError(f"{label} has an unknown state")
266    if actor not in ALLOWED_ACTORS:
267        raise EvidenceError(f"{label} has an unknown actor")
268    if not isinstance(transition["reason"], str):
269        raise EvidenceError(f"{label}.reason must be a string")
270    return {
271        "from": source, "to": target, "actor": actor,
272        "at": require_time(transition["at"], f"{label}.at"),
273        "record_sha256": digest_value(transition),
274    }
275
276
277def handoff_projection(value: Any, label: str, run_ref: str) -> dict[str, Any]:
278    keys = {
279        "handoffRef", "runRef", "threadRef", "sourceThreadRef", "targetThreadRef",
280        "from", "to", "actor", "at", "reason", "disposition", "truncated",
281        "envelopeSchema",
282    }
283    handoff = exact_object(value, keys, label)
284    if handoff["runRef"] != run_ref:
285        raise EvidenceError(f"{label} binds a different run")
286    if handoff["from"] not in ALLOWED_LANES or handoff["to"] not in ALLOWED_LANES:
287        raise EvidenceError(f"{label} has an unknown provider lane")
288    if handoff["from"] == handoff["to"]:
289        raise EvidenceError(f"{label} does not change provider lane")
290    if handoff["actor"] not in ALLOWED_ACTORS:
291        raise EvidenceError(f"{label} has an unknown actor")
292    if handoff["disposition"] not in ALLOWED_DISPOSITIONS:
293        raise EvidenceError(f"{label} has an unknown disposition")
294    if not isinstance(handoff["truncated"], bool) or not isinstance(handoff["reason"], str):
295        raise EvidenceError(f"{label} has invalid typed fields")
296    if handoff["envelopeSchema"] != "openagents.desktop.provider_handoff_envelope.v1":
297        raise EvidenceError(f"{label} has an unknown envelope schema")
298    return {
299        "handoff_ref": require_ref(handoff["handoffRef"], f"{label}.handoffRef"),
300        "from": handoff["from"], "to": handoff["to"], "actor": handoff["actor"],
301        "at": require_time(handoff["at"], f"{label}.at"),
302        "disposition": handoff["disposition"], "truncated": handoff["truncated"],
303        "envelope_schema": handoff["envelopeSchema"],
304        "thread_ref_sha256": digest_ref(handoff["threadRef"], f"{label}.threadRef"),
305        "source_thread_ref_sha256": digest_ref(handoff["sourceThreadRef"], f"{label}.sourceThreadRef"),
306        "target_thread_ref_sha256": digest_ref(handoff["targetThreadRef"], f"{label}.targetThreadRef"),
307        "record_sha256": digest_value(handoff),
308    }
309
310
311def one_by_run(records: list[Any], run_ref: str, label: str) -> dict[str, Any]:
312    matches = [record for record in records if isinstance(record, dict) and record.get("runRef") == run_ref]
313    if len(matches) != 1:
314        raise EvidenceError(f"{label} must contain exactly one record for {run_ref}")
315    return matches[0]
316
317
318def require_unique_registry_refs(records: list[Any], key: str, label: str) -> None:
319    observed: set[str] = set()
320    for index, record in enumerate(records):
321        if not isinstance(record, dict):
322            raise EvidenceError(f"{label}[{index}] must be an object")
323        value = require_ref(record.get(key), f"{label}[{index}].{key}")
324        if value in observed:
325            raise EvidenceError(f"{label} contains duplicate {key}: {value}")
326        observed.add(value)
327
328
329def collect(release_path: Path, identity_path: Path, app: Path, root: Path, run_refs: list[str]) -> dict[str, Any]:
330    if not run_refs or len(run_refs) != len(set(run_refs)):
331        raise EvidenceError("run refs must be a nonempty unique list")
332    for run_ref in run_refs:
333        if RUN_REF_RE.fullmatch(run_ref) is None:
334            raise EvidenceError(f"invalid run ref: {run_ref}")
335    bindings = candidate_bindings(release_path, identity_path, app)
336    release = load_json(release_path, "release record")
337    root = validate_registry_root(root, release)
338    registries: dict[str, dict[str, Any]] = {}
339    registry_evidence: dict[str, Any] = {}
340    for key, (filename, schema_name, array_key) in REGISTRY_FILES.items():
341        path = root / filename
342        document = load_json(path, f"{key} registry", private=True)
343        exact_object(document, {"schema", array_key}, f"{key} registry")
344        if document["schema"] != schema_name or not isinstance(document[array_key], list):
345            raise EvidenceError(f"{key} registry schema is invalid")
346        registries[key] = document
347        registry_evidence[key] = {
348            "schema": schema_name,
349            "sha256": digest_file(path),
350            "mode": "0600",
351        }
352    require_unique_registry_refs(registries["runs"]["runs"], "runRef", "runs registry")
353    require_unique_registry_refs(registries["reports"]["reports"], "runRef", "reports registry")
354    require_unique_registry_refs(
355        registries["native_bindings"]["bindings"], "runRef", "native bindings registry"
356    )
357    require_unique_registry_refs(
358        registries["handoffs"]["transitions"], "handoffRef", "handoffs registry"
359    )
360    runs = []
361    for run_ref in run_refs:
362        run = one_by_run(registries["runs"]["runs"], run_ref, "runs registry")
363        report = one_by_run(registries["reports"]["reports"], run_ref, "reports registry")
364        native = one_by_run(registries["native_bindings"]["bindings"], run_ref, "native bindings registry")
365        state = run.get("state")
366        if state not in ALLOWED_STATES or report.get("state") != state:
367            raise EvidenceError(f"run/report state mismatch for {run_ref}")
368        thread_ref = require_ref(run.get("threadRef"), f"{run_ref}.threadRef")
369        if report.get("threadRef") != thread_ref:
370            raise EvidenceError(f"run/report thread mismatch for {run_ref}")
371        transitions = run.get("transitions")
372        if not isinstance(transitions, list) or not transitions:
373            raise EvidenceError(f"{run_ref} has no lifecycle transitions")
374        projected_transitions = [
375            transition_projection(value, f"{run_ref}.transitions[{index}]")
376            for index, value in enumerate(transitions)
377        ]
378        handoffs = [
379            value for value in registries["handoffs"]["transitions"]
380            if isinstance(value, dict) and value.get("runRef") == run_ref
381        ]
382        projected_handoffs = [
383            handoff_projection(value, f"{run_ref}.handoffs[{index}]", run_ref)
384            for index, value in enumerate(handoffs)
385        ]
386        report_handoffs = report.get("providerTransitions")
387        if not isinstance(report_handoffs, list):
388            raise EvidenceError(f"{run_ref} report has invalid provider transitions")
389        if [digest_value(value) for value in report_handoffs] != [digest_value(value) for value in handoffs]:
390            raise EvidenceError(f"{run_ref} report and handoff registry disagree")
391        workspace_ref = require_ref(run.get("workspaceRef"), f"{run_ref}.workspaceRef")
392        if report.get("workspaceRef") != workspace_ref or native.get("workspaceRef") != workspace_ref:
393            raise EvidenceError(f"{run_ref} workspace binding mismatch")
394        if native.get("runRef") != run_ref or not isinstance(native.get("rebaseUnsafe"), bool):
395            raise EvidenceError(f"{run_ref} native binding is invalid")
396        worktree_path_digest = native.get("worktreePathDigest")
397        if worktree_path_digest is not None:
398            require_sha256(worktree_path_digest, f"{run_ref}.worktreePathDigest")
399        git_head = native.get("gitHead")
400        if git_head is not None and (not isinstance(git_head, str) or COMMIT_RE.fullmatch(git_head) is None):
401            raise EvidenceError(f"{run_ref}.gitHead is invalid")
402        objective_digest = require_sha256(report.get("objectiveDigest"), f"{run_ref}.objectiveDigest")
403        done_digest = require_sha256(report.get("doneConditionDigest"), f"{run_ref}.doneConditionDigest")
404        run_projection = {
405            "run_ref": run_ref,
406            "thread_ref_sha256": digest_ref(thread_ref, f"{run_ref}.threadRef"),
407            "workspace_ref_sha256": digest_ref(workspace_ref, f"{run_ref}.workspaceRef"),
408            "objective_sha256": objective_digest,
409            "done_condition_sha256": done_digest,
410            "state": state,
411            "state_revision": require_integer(run.get("stateRevision"), f"{run_ref}.stateRevision"),
412            "turn_cap": require_integer(run.get("turnCap"), f"{run_ref}.turnCap"),
413            "successful_attempts": require_integer(run.get("successfulAttempts"), f"{run_ref}.successfulAttempts"),
414            "failed_attempts": require_integer(run.get("failedAttempts"), f"{run_ref}.failedAttempts"),
415            "created_at": require_time(run.get("createdAt"), f"{run_ref}.createdAt"),
416            "started_at": require_time(run.get("startedAt"), f"{run_ref}.startedAt"),
417            "updated_at": require_time(report.get("updatedAt"), f"{run_ref}.updatedAt"),
418            "lifecycle_transitions": projected_transitions,
419            "provider_handoffs": projected_handoffs,
420            "native_binding": {
421                "project_ref_sha256": digest_ref(native.get("projectRef"), f"{run_ref}.projectRef"),
422                "worktree_ref_sha256": digest_ref(native.get("worktreeRef"), f"{run_ref}.worktreeRef"),
423                "worktree_path_sha256": worktree_path_digest,
424                "git_head": git_head,
425                "rebase_unsafe": native["rebaseUnsafe"],
426                "bound_at": require_time(native.get("boundAt"), f"{run_ref}.boundAt"),
427                "record_sha256": digest_value(native),
428            },
429            "run_record_sha256": digest_value(run),
430            "report_record_sha256": digest_value(report),
431        }
432        runs.append(run_projection)
433    evidence = {
434        "schema": SCHEMA,
435        **bindings,
436        "collector": {
437            "name": "collect-omega-full-auto-installed-evidence",
438            "version": 1,
439            "sha256": digest_file(Path(__file__)),
440        },
441        "registry_namespace": release["volume_name"],
442        "registries": registry_evidence,
443        "selected_run_count": len(runs),
444        "runs": runs,
445    }
446    validate_public_output(evidence)
447    evidence["evidence_sha256"] = digest_value(evidence)
448    return evidence
449
450
451def validate_public_output(value: Any, path: str = "$") -> None:
452    if isinstance(value, dict):
453        for key, child in value.items():
454            lowered = key.lower()
455            if any(part in lowered for part in SENSITIVE_KEY_PARTS):
456                allowed = key in {"objective_sha256", "done_condition_sha256", "worktree_path_sha256"}
457                if not allowed:
458                    raise EvidenceError(f"public output contains forbidden field at {path}.{key}")
459            validate_public_output(child, f"{path}.{key}")
460    elif isinstance(value, list):
461        for index, child in enumerate(value):
462            validate_public_output(child, f"{path}[{index}]")
463    elif isinstance(value, str):
464        for pattern in SECRET_PATTERNS:
465            if pattern.search(value):
466                raise EvidenceError(f"public output contains secret-shaped text at {path}")
467        if value.startswith("/") or re.search(r"(?:^|/)Users/", value):
468            raise EvidenceError(f"public output contains a raw path at {path}")
469
470
471def write_output(path: Path, evidence: dict[str, Any]) -> None:
472    if not path.is_absolute():
473        raise EvidenceError("output path must be absolute")
474    require_no_symlink_components(path.parent, "output parent")
475    try:
476        path.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
477    except OSError as error:
478        raise EvidenceError(f"cannot create output directory: {error}") from error
479    if path.is_symlink():
480        raise EvidenceError("output must not be a symbolic link")
481    temporary = path.with_name(f".{path.name}.{os.getpid()}.tmp")
482    try:
483        try:
484            descriptor = os.open(temporary, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
485            with os.fdopen(descriptor, "w", encoding="utf-8") as handle:
486                json.dump(evidence, handle, indent=2, sort_keys=True)
487                handle.write("\n")
488                handle.flush()
489                os.fsync(handle.fileno())
490            os.replace(temporary, path)
491            os.chmod(path, 0o600)
492        except OSError as error:
493            raise EvidenceError(f"cannot write output: {error}") from error
494    finally:
495        if temporary.exists():
496            temporary.unlink()
497
498
499def self_test() -> None:
500    base_transition = {
501        "from": "running", "to": "paused", "actor": "control_api",
502        "at": "2026-07-25T00:00:00Z", "reason": "owner request",
503    }
504    projected = transition_projection(base_transition, "test transition")
505    if "reason" in projected or projected["to"] != "paused":
506        raise EvidenceError("transition projection leaked a reason")
507    if projected != transition_projection(dict(base_transition), "test transition"):
508        raise EvidenceError("transition projection is not deterministic")
509    handoff = {
510        "handoffRef": "handoff.provider.test", "runRef": "run.full-auto.test",
511        "threadRef": "thread.test", "sourceThreadRef": "thread.source",
512        "targetThreadRef": "thread.target", "from": "codex-local", "to": "claude-local",
513        "actor": "control_api", "at": "2026-07-25T00:00:01Z", "reason": "switch",
514        "disposition": "complete_within_bounds", "truncated": False,
515        "envelopeSchema": "openagents.desktop.provider_handoff_envelope.v1",
516    }
517    projected_handoff = handoff_projection(handoff, "test handoff", "run.full-auto.test")
518    if "reason" in projected_handoff or "thread.test" in canonical_bytes(projected_handoff).decode():
519        raise EvidenceError("handoff projection leaked private text or opaque refs")
520    for forged in (
521        '{"schema":"x","schema":"y"}',
522        '{"token":"sk-testSecretValue123456789"}',
523    ):
524        try:
525            value = json.loads(forged, object_pairs_hook=duplicate_key_guard)
526            validate_public_output(value)
527        except EvidenceError:
528            pass
529        else:
530            raise EvidenceError("duplicate-key or redaction forgery was accepted")
531    with tempfile.TemporaryDirectory() as temporary_directory:
532        root = Path(temporary_directory)
533        private = root / "private.json"
534        private.write_text('{"schema":"x"}\n', encoding="utf-8")
535        private.chmod(0o644)
536        try:
537            load_json(private, "private fixture", private=True)
538        except EvidenceError:
539            pass
540        else:
541            raise EvidenceError("weak registry mode was accepted")
542        target = root / "target.json"
543        target.write_text('{}\n', encoding="utf-8")
544        link = root / "link.json"
545        link.symlink_to(target)
546        try:
547            load_json(link, "symlink fixture")
548        except EvidenceError:
549            pass
550        else:
551            raise EvidenceError("symlink registry was accepted")
552        tampered = dict(handoff)
553        tampered["disposition"] = "secret: sk-testSecretValue123456789"
554        try:
555            handoff_projection(tampered, "tampered handoff", "run.full-auto.test")
556        except EvidenceError:
557            pass
558        else:
559            raise EvidenceError("tampered handoff was accepted")
560    print("Full Auto installed evidence collector self-test passed")
561
562
563def parse_args() -> argparse.Namespace:
564    parser = argparse.ArgumentParser(description=__doc__)
565    parser.add_argument("--release-record", type=Path)
566    parser.add_argument("--identity-evidence", type=Path)
567    parser.add_argument("--app", type=Path, default=Path("/Applications/Omega.app"))
568    parser.add_argument("--registry-root", type=Path)
569    parser.add_argument("--run-ref", action="append", dest="run_refs")
570    parser.add_argument("--output", type=Path)
571    parser.add_argument("--self-test", action="store_true")
572    return parser.parse_args()
573
574
575def main() -> int:
576    args = parse_args()
577    try:
578        if args.self_test:
579            self_test()
580            return 0
581        required = {
582            "--release-record": args.release_record,
583            "--identity-evidence": args.identity_evidence,
584            "--registry-root": args.registry_root,
585            "--run-ref": args.run_refs,
586            "--output": args.output,
587        }
588        missing = [name for name, value in required.items() if not value]
589        if missing:
590            raise EvidenceError(f"missing required arguments: {', '.join(missing)}")
591        evidence = collect(
592            args.release_record.absolute(), args.identity_evidence.absolute(),
593            args.app, args.registry_root, args.run_refs,
594        )
595        write_output(args.output, evidence)
596        print(f"wrote public-safe Full Auto installed evidence: {args.output}")
597        return 0
598    except EvidenceError as error:
599        print(f"Full Auto installed evidence refused: {error}", file=sys.stderr)
600        return 2
601
602
603if __name__ == "__main__":
604    raise SystemExit(main())
605
Served at tenant.openagents/omega Member data and write actions are omitted.