omnilane 0.42.8 → 0.44.0

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.
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env python3
2
+ """Who signed a vendor CLI, and whether a changed one may be re-signed unattended.
3
+
4
+ The transport overlay pins each vendor's executable by hash. Vendor CLIs update
5
+ themselves, so that pin goes stale as routine traffic. Re-pinning on sight would
6
+ turn the pin into a rubber stamp; this module is what stands in between. A
7
+ changed executable may be re-probed without an operator only when it still
8
+ carries the signer recorded when the overlay was last signed and still lives in
9
+ the same install location. Anything else is a notification, not a re-sign.
10
+ """
11
+ from __future__ import annotations
12
+
13
+ import re
14
+ import subprocess
15
+ from pathlib import Path
16
+
17
+ ADHOC = "adhoc"
18
+ UNSIGNED = "unsigned"
19
+ # The operator's statement that this vendor's executable is expected to be adhoc
20
+ # here (a local patch step re-signs it), so an adhoc update in the same install
21
+ # location may be re-probed unattended. Nothing else is waived.
22
+ TRUST_ADHOC = "adhoc-in-install-location"
23
+ # 1.0.25 -> 1.0.30 inside a path is an update; a different directory is not.
24
+ VERSION_SEGMENT = re.compile(r"\d+(?:\.\d+)+(?:[-+][0-9A-Za-z.]+)*")
25
+
26
+
27
+ def facts(path: Path | str, runner=subprocess.run) -> dict:
28
+ """codesign's view of one executable: the signing team, or why there is none."""
29
+ target = str(path)
30
+ try:
31
+ verified = runner(["codesign", "--verify", "--strict", target],
32
+ capture_output=True, text=True, timeout=60)
33
+ described = runner(["codesign", "-dv", "--verbose=2", target],
34
+ capture_output=True, text=True, timeout=60)
35
+ except (OSError, subprocess.SubprocessError) as error:
36
+ return {"signer": UNSIGNED, "identifier": None, "valid": False,
37
+ "detail": f"codesign unavailable: {error.__class__.__name__}"}
38
+ fields = {}
39
+ for line in described.stderr.splitlines():
40
+ key, separator, value = line.partition("=")
41
+ if separator and key not in fields:
42
+ fields[key] = value
43
+ valid = verified.returncode == 0
44
+ team = fields.get("TeamIdentifier")
45
+ if not valid:
46
+ signer = UNSIGNED
47
+ elif fields.get("Signature") == "adhoc" or team in (None, "not set"):
48
+ signer = ADHOC
49
+ else:
50
+ signer = team
51
+ return {"signer": signer, "identifier": fields.get("Identifier"), "valid": valid}
52
+
53
+
54
+ def family(path: Path | str) -> str:
55
+ """An install location with its version numbers blanked out."""
56
+ return VERSION_SEGMENT.sub("<version>", str(path))
57
+
58
+
59
+ def verdict(recorded: dict | None, recorded_path: str | None, current: dict,
60
+ current_path: Path | str) -> tuple[bool, str]:
61
+ """May this changed executable be re-probed unattended? And the reason either way."""
62
+ if current["signer"] == UNSIGNED:
63
+ return False, "the executable is unsigned, so nothing ties it to the vendor; an operator has to approve it"
64
+ if current["signer"] == ADHOC:
65
+ if (recorded or {}).get("operator_trust") == TRUST_ADHOC and recorded_path \
66
+ and family(recorded_path) == family(current_path):
67
+ return True, "adhoc, which the operator trusts in this install location"
68
+ if (recorded or {}).get("operator_trust") == TRUST_ADHOC:
69
+ return False, (f"adhoc and installed at {current_path}, outside the location the operator "
70
+ f"trusts, {family(recorded_path)}")
71
+ return False, ("the executable is adhoc, so nothing ties it to the vendor; an operator has to "
72
+ "approve it (or, for a local patch step, trust it here: omnilane resign --trust-adhoc VENDOR)")
73
+ if not recorded or not recorded.get("signer"):
74
+ return False, ("the live overlay recorded no signer for this vendor, so there is nothing "
75
+ "to compare against; an operator approves the first signer")
76
+ if recorded["signer"] != current["signer"]:
77
+ return False, (f"signed by {current['signer']}, but the overlay was signed against "
78
+ f"{recorded['signer']}")
79
+ if recorded_path and family(recorded_path) != family(current_path):
80
+ return False, (f"installed at {current_path}, outside the recorded location "
81
+ f"{family(recorded_path)}")
82
+ return True, f"still signed by {current['signer']} in the recorded install location"
@@ -91,7 +91,11 @@ def capability_context(path):
91
91
  ctx = read_json(path)
92
92
  fields(ctx, ("schema_version", "harness", "vendor", "capabilities", "requirements"),
93
93
  ("current_model", "current_effort", "agent_strategy", "existing_agent",
94
- "preserve_existing_context", "new_agent_capacity"))
94
+ "preserve_existing_context", "new_agent_capacity", "inherits_caller_runtime",
95
+ "caller_identity_verified"))
96
+ for flag in ("inherits_caller_runtime", "caller_identity_verified"):
97
+ if flag in ctx:
98
+ check(type(ctx[flag]) is bool, "invalid " + flag)
95
99
  check(type(ctx["schema_version"]) is int and ctx["schema_version"] == 1, "unsupported context version")
96
100
  identifier(ctx["harness"], "harness")
97
101
  identifier(ctx["vendor"], "vendor")
@@ -210,6 +214,89 @@ def choose(args, ctx):
210
214
  return "native", "exact-idle-reuse-match" if strategy == "reuse" else "exact-capability-match", model
211
215
 
212
216
 
217
+ def choose_inherited(args, ctx, identity):
218
+ """A worker spawned with no model override. There is no CLI to fall back to."""
219
+ if ctx is None:
220
+ return None, "no-native-context"
221
+ if ctx.get("inherits_caller_runtime") is not True:
222
+ return None, "host-does-not-assert-runtime-inheritance"
223
+ if args.background or args.session != "auto" or args.thread:
224
+ return None, "cli-session-lifecycle"
225
+ if args.job_timeout or args.idle_timeout:
226
+ return None, "cli-watchdog-required"
227
+ if args.mode == "sysops":
228
+ return None, "sysops-requires-cli"
229
+ if ctx["vendor"] != identity["vendor"]:
230
+ return None, "vendor-mismatch"
231
+ if ctx.get("current_model", identity["model"]) != identity["model"]:
232
+ return None, "current-model-mismatch"
233
+ if ctx.get("agent_strategy", "new") != "new" or "existing_agent" in ctx:
234
+ return None, "inherit-spawns-a-new-agent"
235
+ if ctx.get("new_agent_capacity") == "exhausted":
236
+ return None, "new-agent-capacity-exhausted"
237
+ req = ctx["requirements"]
238
+ if req["isolation"] != "shared-inherited" or req["lifecycle"] != "single-shot":
239
+ return None, "unsupported-isolation-or-lifecycle"
240
+ # Effort is deliberately not matched: inheriting it is the whole point.
241
+ caps = [cap for cap in ctx["capabilities"]
242
+ if cap["model"] == identity["model"] and cap.get("agent_strategy", "new") == "new"
243
+ and args.mode in cap["modes"] and args.workdir in cap["workdirs"]
244
+ and set(req["tools"]) <= set(cap["tools"])
245
+ and "shared-inherited" in cap["isolations"] and "single-shot" in cap["lifecycles"]]
246
+ if not caps:
247
+ return None, "no-capability-row-for-caller-model-mode-and-workdir"
248
+ return "native", "inherited-caller-runtime"
249
+
250
+
251
+ def route_inherited(args, ctx, registry, registry_sha):
252
+ caller = caller_sha = None
253
+ if args.caller_context:
254
+ caller, caller_sha = aa_policy.load_caller(
255
+ args.caller_context, registry, args.expected_caller_sha256)
256
+ host = None
257
+ if caller is None and ctx is not None and "current_model" in ctx:
258
+ host = {"vendor": ctx["vendor"], "model": ctx["current_model"]}
259
+ decision = aa_policy.decide_inherited(
260
+ registry, registry_sha, caller=caller, caller_sha256=caller_sha,
261
+ operator_asserted_human=args.operator_asserted_human, host_asserted=host)
262
+ if not decision["allowed"]:
263
+ print(aa_policy._json_line(decision), end="", file=sys.stderr)
264
+ return 3
265
+ identity = caller["caller"] if caller is not None else host
266
+ executor, reason = choose_inherited(args, ctx, identity)
267
+ if executor is None:
268
+ # An inherited worker exists only inside the harness; the CLI is not a fallback for it.
269
+ decision.update(allowed=False, code="native-inherit-unavailable",
270
+ message="this harness cannot take an inherited native worker: " + reason,
271
+ failed_gate="native-capability", reason=reason, child_context=None,
272
+ next_command="omnilane native-context --workdir " + args.workdir)
273
+ print(aa_policy._json_line(decision), end="", file=sys.stderr)
274
+ return 3
275
+ plan = {"schema_version": 1, "executor": executor, "executor_reason": reason, "inherit": True,
276
+ "caller_identity_verified": decision["caller_identity_verified"],
277
+ "caller_identity_source": decision["caller_identity_source"],
278
+ "vendor": identity["vendor"], "model": identity["model"], "effort": "inherited",
279
+ "harness": ctx["harness"], "lane": args.lane, "mode": args.mode,
280
+ "workdir": args.workdir, "task": args.task,
281
+ "requirements": ctx["requirements"], "timeout": args.timeout,
282
+ "worker_contract": {
283
+ "no_nested_dispatch": True,
284
+ "isolation": ctx["requirements"]["isolation"],
285
+ "mode_is_task_intent": True,
286
+ "caller_enforces_deadline": True,
287
+ "model_override": False,
288
+ "inherit_caller_runtime": True,
289
+ "satisfies_lane_target": False,
290
+ },
291
+ "aa_policy": decision,
292
+ "state": "planned" if args.dry_run else "pending",
293
+ "job_id": None, "task_id": None, "agent_id": None,
294
+ "provider_invoked": False, "job_state_created": False,
295
+ "agent_strategy": "new", "existing_agent_id": None, "preserve_existing_context": False,
296
+ "new_agent_capacity": ctx.get("new_agent_capacity", "unknown")}
297
+ return publish_plan(args, plan, decision, registry, caller)
298
+
299
+
213
300
  def stamp():
214
301
  return datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
215
302
 
@@ -302,6 +389,8 @@ def route(args):
302
389
  registry, registry_sha = aa_policy.load_registry(
303
390
  args.policy, args.expected_registry_sha256
304
391
  )
392
+ if getattr(args, "inherit", False):
393
+ return route_inherited(args, ctx, registry, registry_sha)
305
394
  caller = None
306
395
  caller_sha = None
307
396
  if args.caller_context:
@@ -355,6 +444,10 @@ def route(args):
355
444
  plan["worker_contract"].update(backend="collaboration.followup_task",
356
445
  preserve_existing_context=True,
357
446
  caller_rechecks_idle_before_followup=True)
447
+ return publish_plan(args, plan, policy_decision, registry, caller)
448
+
449
+
450
+ def publish_plan(args, plan, policy_decision, registry, caller):
358
451
  if args.dry_run:
359
452
  print(json_text(plan), end="")
360
453
  return 0
@@ -393,6 +486,8 @@ def validate_completion(value, state):
393
486
  for key in ("vendor", "model", "effort", "harness", "backend"):
394
487
  text_value(runtime[key], "runtime " + key, 256)
395
488
  for key in ("vendor", "model", "effort", "harness"):
489
+ if key == "effort" and state.get("inherit") is True:
490
+ continue # the host reports what it observed; nothing was requested
396
491
  check(runtime[key] == state[key], "runtime " + key + " mismatch")
397
492
  strategy = state.get("agent_strategy", "new")
398
493
  if strategy == "reuse":
@@ -448,6 +543,10 @@ def job_command(args):
448
543
  summary.update(agent_strategy=state.get("agent_strategy", "new"),
449
544
  existing_agent_id=state.get("existing_agent_id"),
450
545
  preserve_existing_context=state.get("preserve_existing_context", False))
546
+ if state.get("inherit"):
547
+ summary.update(inherit=True,
548
+ caller_identity_verified=state.get("caller_identity_verified", True),
549
+ satisfies_lane_target=False)
451
550
  if args.action == "result":
452
551
  summary["completion"] = state.get("completion")
453
552
  if args.json:
@@ -463,8 +562,12 @@ def main():
463
562
  parser = argparse.ArgumentParser(description=__doc__)
464
563
  sub = parser.add_subparsers(dest="command", required=True)
465
564
  p = sub.add_parser("route")
466
- for key in ("home", "lane", "vendor", "model", "effort", "workdir", "task"):
565
+ for key in ("home", "lane", "workdir", "task"):
467
566
  p.add_argument("--" + key, required=True)
567
+ for key in ("vendor", "model", "effort"):
568
+ p.add_argument("--" + key, default="")
569
+ p.add_argument("--inherit", action="store_true",
570
+ help="spawn a worker that inherits the caller's own model and effort")
468
571
  p.add_argument("--executor", choices=("auto", "native", "cli"), required=True)
469
572
  p.add_argument("--mode", choices=("advise", "work", "sysops"), required=True)
470
573
  p.add_argument("--context")
@@ -488,6 +591,8 @@ def main():
488
591
  p.add_argument("job_id")
489
592
  p.add_argument("input", nargs="?")
490
593
  args = parser.parse_args()
594
+ if args.command == "route" and not args.inherit and not args.vendor:
595
+ parser.error("--vendor is required unless --inherit is given")
491
596
  try:
492
597
  return route(args) if args.command == "route" else job_command(args)
493
598
  except (ValueError, OSError, RecursionError, TypeError, KeyError) as error:
@@ -0,0 +1,145 @@
1
+ #!/usr/bin/env python3
2
+ """Write the native capability file for the harness this runs under.
3
+
4
+ Dispatch only hands work to a harness's own sub-agent tool when the harness says
5
+ what that tool can do; without such a file every same-harness target goes out
6
+ through an external CLI. This writes the file from what is observable: the
7
+ caller's vendor, model and effort are read exactly as `omnilane whoami` reads
8
+ them. What cannot be observed from a shell is stated by the host that runs this,
9
+ with flags: `--inherits-caller-runtime` says its sub-agent tool, given no model
10
+ override, runs the caller's own model and effort. Where the identity cannot be
11
+ read at all, `--vendor` and `--model` are the host's statement of what it is; the
12
+ file then says so, and serves an inherited worker only. Prints the file's path.
13
+ """
14
+ from __future__ import annotations
15
+
16
+ import argparse
17
+ import hashlib
18
+ import json
19
+ import os
20
+ import re
21
+ import sys
22
+ from pathlib import Path
23
+
24
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
25
+ import caller_identity # noqa: E402
26
+
27
+ HARNESSES = {"codex": "codex", "claude": "claude-code", "grok": "grok-build", "gemini": "antigravity"}
28
+ UNVERIFIED_EFFORT = "unverified"
29
+ HOST_STATEMENT_HINT = ("omnilane: if this harness cannot be read, state what it is: omnilane native-context "
30
+ "--vendor VENDOR --model MODEL --inherits-caller-runtime (serves --inherit only)")
31
+
32
+
33
+ def build(vendor: str, model: str, effort: str | None, *, harness: str, workdirs: list[str],
34
+ modes: list[str], inherits: bool, verified: bool = True) -> dict:
35
+ value = {
36
+ "schema_version": 1,
37
+ "harness": harness,
38
+ "vendor": vendor,
39
+ "current_model": model,
40
+ "requirements": {"tools": [], "isolation": "shared-inherited", "lifecycle": "single-shot"},
41
+ "capabilities": [{
42
+ "model": model,
43
+ # An unrecorded effort matches no lane target; only --inherit can use this row.
44
+ "efforts": [effort or UNVERIFIED_EFFORT],
45
+ "modes": modes,
46
+ "workdirs": workdirs,
47
+ "tools": [],
48
+ "isolations": ["shared-inherited"],
49
+ "lifecycles": ["single-shot"],
50
+ }],
51
+ }
52
+ if effort:
53
+ value["current_effort"] = effort
54
+ if inherits:
55
+ value["inherits_caller_runtime"] = True
56
+ if not verified:
57
+ value["caller_identity_verified"] = False
58
+ return value
59
+
60
+
61
+ def main(argv: list[str] | None = None, read_caller=caller_identity.read_caller) -> int:
62
+ parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
63
+ parser.add_argument("--workdir", action="append", type=Path,
64
+ help="directory a native worker may be given; may repeat (default: cwd)")
65
+ parser.add_argument("--mode", action="append", choices=("advise", "work"),
66
+ help="task intent the host accepts; may repeat (default: advise)")
67
+ parser.add_argument("--harness", help="harness name (default: derived from the caller's vendor)")
68
+ parser.add_argument("--inherits-caller-runtime", action="store_true",
69
+ help="the host states that a sub-agent spawned with no model override "
70
+ "runs the caller's own model and effort")
71
+ parser.add_argument("--registry",
72
+ default=os.environ.get("OMNILANE_AA_POLICY_FILE")
73
+ or str(caller_identity.REPO / "config" / "aa-model-policy.json"))
74
+ parser.add_argument("--vendor", help="host-asserted vendor, used only when the identity cannot be read")
75
+ parser.add_argument("--model", help="host-asserted current model, used only when the identity cannot be read")
76
+ parser.add_argument("--out", type=Path, help="file to write (default: under ~/.omnilane)")
77
+ args = parser.parse_args(argv)
78
+ if bool(args.vendor) != bool(args.model):
79
+ print("omnilane: --vendor and --model go together", file=sys.stderr)
80
+ return 2
81
+ for name in (args.vendor, args.model):
82
+ if name and not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._-]{0,127}", name):
83
+ print(f"omnilane: invalid name: {name}", file=sys.stderr)
84
+ return 2
85
+ verified, recorded = True, None
86
+ try:
87
+ registry, _ = caller_identity.load_registry(args.registry)
88
+ _, (vendor, model, effort), _ = read_caller(os.getpid())
89
+ except (ValueError, OSError) as error:
90
+ if not args.vendor:
91
+ print(f"omnilane: cannot read the caller identity: {error}\n{HOST_STATEMENT_HINT}", file=sys.stderr)
92
+ return 3
93
+ print(f"omnilane: caller identity not verified ({error}); using the host's statement",
94
+ file=sys.stderr)
95
+ verified, vendor, model = False, args.vendor, args.model
96
+ else:
97
+ if args.vendor and (args.vendor != vendor or (model is not None and args.model != model)):
98
+ print(f"omnilane: this harness reads as {vendor}/{model}, not {args.vendor}/{args.model}",
99
+ file=sys.stderr)
100
+ return 2
101
+ row, reason = caller_identity.resolve(registry, vendor, model, effort)
102
+ if row is not None:
103
+ # Codex spells its reasoning-off effort "none"; the registry stores it as null.
104
+ recorded = row["effort"] or ("none" if vendor == "codex" else None)
105
+ elif not (vendor == "codex" and model and effort is None
106
+ and caller_identity.floor_row(registry, vendor, model)):
107
+ if not args.vendor:
108
+ print(f"omnilane: cannot describe this harness: {reason}\n{HOST_STATEMENT_HINT}",
109
+ file=sys.stderr)
110
+ return 3
111
+ print(f"omnilane: caller identity not verified ({reason}); using the host's statement",
112
+ file=sys.stderr)
113
+ verified, model = False, args.model
114
+ workdirs = []
115
+ for path in args.workdir or [Path.cwd()]:
116
+ resolved = path.expanduser().resolve()
117
+ if not resolved.is_dir():
118
+ print(f"omnilane: not a directory: {path}", file=sys.stderr)
119
+ return 2
120
+ workdirs.append(str(resolved))
121
+ value = build(vendor, model, recorded,
122
+ harness=args.harness or HARNESSES.get(vendor, vendor),
123
+ workdirs=sorted(set(workdirs)), modes=sorted(set(args.mode or ["advise"])),
124
+ inherits=args.inherits_caller_runtime, verified=verified)
125
+ text = json.dumps(value, indent=2, sort_keys=True) + "\n"
126
+ out = args.out
127
+ if out is None:
128
+ home = Path(os.environ.get("OMNILANE_HOME") or Path.home() / ".omnilane")
129
+ digest = hashlib.sha256(text.encode()).hexdigest()[:12]
130
+ out = home / "native-context" / f"{value['harness']}--{model.replace('.', '-')}--{digest}.json"
131
+ out.parent.mkdir(parents=True, exist_ok=True)
132
+ staging = out.with_name(f".{out.name}.{os.getpid()}.tmp")
133
+ staging.write_text(text)
134
+ os.replace(staging, out)
135
+ note = "" if args.inherits_caller_runtime else (
136
+ "; pass --inherits-caller-runtime if this harness's sub-agent tool inherits your runtime")
137
+ print(f"omnilane: native context for {value['harness']} {model} "
138
+ f"(effort {recorded or 'unrecorded'}{'' if verified else ', identity host-asserted'}){note}",
139
+ file=sys.stderr)
140
+ print(out)
141
+ return 0
142
+
143
+
144
+ if __name__ == "__main__":
145
+ raise SystemExit(main())
@@ -53,7 +53,10 @@ def offenders(overlay_path: Path) -> list[str]:
53
53
  def main() -> None:
54
54
  overlay_path = os.environ.get("OMNILANE_AA_TRANSPORT_OVERLAY", "")
55
55
  if not overlay_path:
56
- emit("PASS", "no overlay configured; every runtime mapping stays unverified")
56
+ if os.environ.get("OMNILANE_AA_OPERATOR_ASSERTED_HUMAN") == "1":
57
+ emit("PASS", "no overlay configured; fine for a human operator, but a model caller "
58
+ "would be refused on every lane (README, 'Let your AI assistant drive omnilane')")
59
+ emit("WARN", "no overlay configured, so a model caller is refused on every lane (runtime-mapping-unverified). First install: probe_sweep.py --root ROOT, build_overlay.py --root ROOT, then export OMNILANE_AA_TRANSPORT_OVERLAY in local.sh; see the README, 'Let your AI assistant drive omnilane'")
57
60
  if not Path(overlay_path).exists():
58
61
  emit("FAIL", f"configured overlay is missing: {overlay_path}")
59
62
 
@@ -87,10 +90,29 @@ def main() -> None:
87
90
  "record who answered")
88
91
 
89
92
  stale = registry.get("_stale_transport_vendors", [])
93
+ # The gate hashes the path the overlay recorded. An update that installs beside
94
+ # the old executable leaves that path intact, so only this comparison sees it.
95
+ moved = []
96
+ try:
97
+ import resign
98
+ report = resign.detect(overlay, resign.current_anchors())
99
+ for vendor, entry in report.items():
100
+ # Only what the overlay actually pinned can have moved.
101
+ reasons = [reason for reason in entry["reasons"]
102
+ if reason.startswith("runs ") or reason.endswith(" changed")]
103
+ if reasons and entry["recorded_cli_path"] and vendor not in stale:
104
+ moved.append(f"{vendor}: {'; '.join(reasons)}")
105
+ except Exception: # noqa: BLE001 - a health line must never raise
106
+ moved = []
107
+ if moved and not stale:
108
+ emit("WARN", f"the runners no longer execute what the overlay pinned ({' | '.join(moved)}); "
109
+ f"run `omnilane resign`; still loading: {summary}{extra}")
90
110
  if stale:
91
111
  detail = "; ".join(o for o in offenders(Path(overlay_path))) or "unknown cause"
112
+ if moved:
113
+ detail += "; also moved without going stale: " + " | ".join(moved)
92
114
  emit("WARN", f"stale vendor(s) {', '.join(stale)} degraded to unverified "
93
- f"({detail}); still verified: {summary}{extra}")
115
+ f"({detail}); run `omnilane resign`; still verified: {summary}{extra}")
94
116
  emit("PASS", f"verified mappings: {summary}{extra}")
95
117
 
96
118
 
@@ -6,6 +6,8 @@ descriptor with the original command/stream fields plus a vendor-specific
6
6
  verdict, its reason, the observed model, and the tier of evidence that model
7
7
  rests on.
8
8
  """
9
+ from __future__ import annotations
10
+
9
11
  import argparse
10
12
  import glob
11
13
  import json
@@ -0,0 +1,176 @@
1
+ #!/usr/bin/env python3
2
+ """Probe every selector build_overlay.py knows, one vendor at a time.
3
+
4
+ probe.py runs a single command; this derives the whole set from PROVEN and the
5
+ frozen registry, so a sweep is reproducible on any host without carrying a list
6
+ of commands from the last one. Each vendor is probed independently and reports
7
+ one of three outcomes: done, unprobeable (nobody is logged in, or the session
8
+ cannot reach the keychain), or failed.
9
+ """
10
+ from __future__ import annotations
11
+
12
+ import argparse
13
+ import json
14
+ import os
15
+ import re
16
+ import subprocess
17
+ import sys
18
+ from pathlib import Path
19
+
20
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
21
+ import build_overlay # noqa: E402
22
+ import probe as probe_module # noqa: E402
23
+
24
+ VENDORS = ("claude", "codex", "gemini", "grok")
25
+ TOKENS = {"claude": "CLAUDE_SELECTOR_OK", "codex": "CODEX_SELECTOR_OK",
26
+ "gemini": "GEMINI_SELECTOR_OK", "grok": "GROK_SELECTOR_OK"}
27
+ # The keychain-backed CLIs cannot authenticate from a launchd Background session
28
+ # (an ssh login is one); probing there records "not logged in" as if it were a
29
+ # finding about the selector.
30
+ KEYCHAIN_VENDORS = ("claude", "gemini", "grok")
31
+ NOT_AUTHENTICATED = re.compile(
32
+ r"not logged in|not signed in|authentication required|please run /login|grok login"
33
+ r"|failed to authenticate|oauth (session|token) (has )?expired|could not be refreshed"
34
+ r"|invalid api key|unauthorized|\b401\b",
35
+ re.IGNORECASE)
36
+ TRANSIENT = re.compile(r"\b(403|429|500|502|503|529)\b|permission-denied|overloaded|timed? ?out",
37
+ re.IGNORECASE)
38
+
39
+
40
+ def prompt(vendor: str) -> str:
41
+ return f"Reply exactly {TOKENS[vendor]}. Do not use tools or delegate."
42
+
43
+
44
+ def command(vendor: str, model: str, effort: str | None, app_data: str | None = None) -> list[str]:
45
+ text = prompt(vendor)
46
+ if vendor == "claude":
47
+ selector = ["--model", model] + (["--effort", effort] if effort else [])
48
+ return ["claude", "--disable-slash-commands", *selector, "--permission-mode", "dontAsk",
49
+ "--tools", "", "--output-format", "json", "-p", text]
50
+ if vendor == "codex":
51
+ return ["codex", "exec", "--json", "--skip-git-repo-check", "-m", model,
52
+ "-c", f'model_reasoning_effort="{effort or "none"}"',
53
+ "-c", 'approval_policy="never"', "-c", 'sandbox_mode="read-only"', text]
54
+ if vendor == "grok":
55
+ return ["grok", "--no-memory", "--no-subagents", "--no-plan", "--no-alt-screen",
56
+ "--output-format", "json", "--verbatim", "--permission-mode", "dontAsk",
57
+ "--tools", "Read", "--deny", "Bash", "--deny", "Edit", "--disable-web-search",
58
+ "-m", model, "--reasoning-effort", effort or "high", "-p", text]
59
+ if vendor == "gemini":
60
+ return ["agy", f"--app_data_dir={app_data}", "--model", model, "-p", text]
61
+ raise ValueError(f"unsupported vendor: {vendor}")
62
+
63
+
64
+ def plan(vendor: str) -> list[dict]:
65
+ """One entry per evidence file this vendor's PROVEN rows point at."""
66
+ entries: dict[str, dict] = {}
67
+ for config_id, (_, runtime_model, evidence) in sorted(build_overlay.PROVEN.items()):
68
+ row = build_overlay.ROWS[config_id]
69
+ if row["vendor"] != vendor or evidence in entries:
70
+ continue
71
+ entries[evidence] = {"name": evidence, "config_id": config_id, "model": runtime_model,
72
+ "effort": None if vendor == "gemini" else row["effort"]}
73
+ return list(entries.values())
74
+
75
+
76
+ def session_manager(runner=subprocess.run) -> str:
77
+ try:
78
+ return runner(["launchctl", "managername"], capture_output=True, text=True,
79
+ timeout=10).stdout.strip()
80
+ except (OSError, subprocess.SubprocessError):
81
+ return ""
82
+
83
+
84
+ def _text(record: dict) -> str:
85
+ parts = [str(record.get("verdict_reason") or "")]
86
+ for key in ("stdout", "stderr"):
87
+ try:
88
+ parts.append(Path(record[key]).read_text(errors="replace")[:4000])
89
+ except (KeyError, OSError):
90
+ pass
91
+ return "\n".join(parts)
92
+
93
+
94
+ def sweep(vendor: str, root: Path, *, repo: Path = build_overlay.REPO, home: Path | None = None,
95
+ run_probe=probe_module.probe, manager=session_manager, log=print) -> dict:
96
+ """Probe one vendor into root/evidence. Never raises for a probe that merely failed."""
97
+ home = home or Path.home()
98
+ entries = plan(vendor)
99
+ report = {"vendor": vendor, "outcome": "done", "passed": [], "failed": [], "detail": ""}
100
+ if (vendor in KEYCHAIN_VENDORS and sys.platform == "darwin"
101
+ and os.environ.get("OMNILANE_PROBE_ANY_SESSION") != "1"):
102
+ name = manager()
103
+ if name and name != "Aqua":
104
+ report.update(outcome="unprobeable",
105
+ detail=(f"this is a launchd {name} session (an ssh login is one); "
106
+ f"{vendor}'s credentials are in the login keychain, which only an "
107
+ "Aqua (GUI) session can read"))
108
+ return report
109
+ work = root / "work"
110
+ work.mkdir(parents=True, exist_ok=True)
111
+ cli = "agy" if vendor == "gemini" else vendor
112
+ for index, entry in enumerate(entries):
113
+ app_root = None
114
+ app_data = None
115
+ if vendor == "gemini":
116
+ # prepare-agy-mode binds its private root to one workdir, so it is per sweep.
117
+ app_root = home / ".omnilane" / "agy-app" / f"probe-{root.name}-{entry['name']}"
118
+ prepared = subprocess.run(
119
+ [sys.executable, str(repo / "scripts/lib/prepare-agy-mode.py"), "--mode", "advise",
120
+ "--workdir", str(work), "--app-root", str(app_root),
121
+ "--gemini-dir", str(home / ".gemini")],
122
+ capture_output=True, text=True)
123
+ if prepared.returncode != 0:
124
+ report.update(outcome="failed",
125
+ detail=f"prepare-agy-mode failed: {prepared.stderr.strip()[-200:]}")
126
+ return report
127
+ app_data = prepared.stdout.strip()
128
+ argv = command(vendor, entry["model"], entry["effort"], app_data)
129
+ record = {}
130
+ for attempt in (1, 2):
131
+ record = run_probe(entry["name"], argv, root=root, vendor=cli,
132
+ expected_token=TOKENS[vendor], app_root=app_root)
133
+ if record.get("verdict") == "pass" or attempt == 2 or not TRANSIENT.search(_text(record)):
134
+ break
135
+ log(f"{entry['name']}: transient failure, retrying once")
136
+ if record.get("verdict") == "pass":
137
+ report["passed"].append(entry["config_id"])
138
+ log(f"{entry['name']} pass {record.get('evidence_tier')}")
139
+ continue
140
+ if NOT_AUTHENTICATED.search(_text(record)):
141
+ # Not a finding about the selector: keep it out of the evidence.
142
+ for suffix in ("json", "stdout", "stderr", "rollout", "cli_log"):
143
+ (root / "evidence" / f"{entry['name']}.{suffix}").unlink(missing_ok=True)
144
+ report.update(outcome="unprobeable",
145
+ detail=f"{cli} is not logged in on this host; log in, then re-run")
146
+ if index:
147
+ report["detail"] += f" ({index} earlier probe(s) had already answered)"
148
+ return report
149
+ report["failed"].append(entry["config_id"])
150
+ log(f"{entry['name']} fail {str(record.get('verdict_reason'))[:120]}")
151
+ if not report["passed"]:
152
+ report.update(outcome="failed", detail="no selector of this vendor passed")
153
+ return report
154
+
155
+
156
+ def main(argv: list[str] | None = None) -> int:
157
+ parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
158
+ parser.add_argument("--root", type=Path, required=True, help="sweep root to write evidence into")
159
+ parser.add_argument("--vendor", action="append", choices=VENDORS,
160
+ help="vendor to probe; may repeat (default: all four)")
161
+ parser.add_argument("--plan", action="store_true", help="print the commands and probe nothing")
162
+ args = parser.parse_args(argv)
163
+ vendors = args.vendor or list(VENDORS)
164
+ if args.plan:
165
+ for vendor in vendors:
166
+ for entry in plan(vendor):
167
+ print(json.dumps({"name": entry["name"], "argv": command(
168
+ vendor, entry["model"], entry["effort"], "<app-data>")}, ensure_ascii=False))
169
+ return 0
170
+ reports = [sweep(vendor, args.root.expanduser()) for vendor in vendors]
171
+ print(json.dumps(reports, ensure_ascii=False, indent=2))
172
+ return 0 if all(report["outcome"] == "done" for report in reports) else 1
173
+
174
+
175
+ if __name__ == "__main__":
176
+ raise SystemExit(main())