omnilane 0.42.2 → 0.42.5

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,184 @@
1
+ #!/usr/bin/env python3
2
+ """Request-selector probe harness for the omnilane AA transport overlay.
3
+
4
+ Runs one CLI invocation, captures raw stdout/stderr to files, and writes a
5
+ descriptor with the original command/stream fields plus a vendor-specific
6
+ verdict, its reason, and the observed model when the response proves it.
7
+ """
8
+ import argparse
9
+ import json
10
+ import os
11
+ import subprocess
12
+ import time
13
+ from datetime import datetime, timezone
14
+ from pathlib import Path
15
+
16
+ SWEEP_ID = os.environ.get("OMNILANE_TRANSPORT_SWEEP_ID", "overlay-reprobe-20260909")
17
+ DEFAULT_ROOT = Path.home() / ".omnilane" / "transport-evidence" / SWEEP_ID
18
+
19
+
20
+ def verdict(
21
+ evidence_json: dict,
22
+ stdout_text: str,
23
+ stderr_text: str,
24
+ vendor: str,
25
+ expected_token: str | None,
26
+ ) -> tuple[str, str, str | None]:
27
+ """Judge raw evidence without reading files, running commands or mutating it.
28
+
29
+ Only Claude's billed modelUsage keys currently prove the responding model.
30
+ A requested selector (including the Codex banner) is not observed identity.
31
+ """
32
+ if evidence_json.get("timed_out"):
33
+ return "fail", "timeout", None
34
+ if not expected_token:
35
+ return "fail", "missing-expected-token", None
36
+
37
+ exit_code = evidence_json.get("exit_code")
38
+ if vendor == "claude":
39
+ try:
40
+ response = json.loads(stdout_text)
41
+ except (json.JSONDecodeError, TypeError):
42
+ return "fail", "invalid-json", None
43
+ if not isinstance(response, dict):
44
+ return "fail", "invalid-json-result", None
45
+ usage = response.get("modelUsage")
46
+ models = sorted(usage) if isinstance(usage, dict) else []
47
+ observed_model = ", ".join(models) or None
48
+ result = response.get("result", "")
49
+ if not isinstance(result, str):
50
+ result = str(result)
51
+ if response.get("is_error"):
52
+ lower_result = result.lower()
53
+ if "limit" in lower_result or "quota" in lower_result:
54
+ reason = "quota-exhausted"
55
+ elif "api error" in lower_result:
56
+ reason = "api-error"
57
+ else:
58
+ reason = "result-error"
59
+ return "fail", f"{reason}: {result[:120]}", observed_model
60
+ if not models:
61
+ return "fail", "missing-model-usage", None
62
+ command = evidence_json.get("command", [])
63
+ requested_model = None
64
+ for index, argument in enumerate(command):
65
+ if argument == "--model" and index + 1 < len(command):
66
+ requested_model = command[index + 1]
67
+ elif isinstance(argument, str) and argument.startswith("--model="):
68
+ requested_model = argument.split("=", 1)[1]
69
+ if not requested_model:
70
+ return "fail", "missing-requested-model", observed_model
71
+ if models != [requested_model]:
72
+ return "fail", "model-mismatch", observed_model
73
+ if "unknown --effort" in stderr_text.lower():
74
+ return "fail", "effort-silently-defaulted", observed_model
75
+ if exit_code != 0:
76
+ return "fail", f"exit-code: {exit_code}", observed_model
77
+ if expected_token not in result:
78
+ return "fail", "missing-expected-token", observed_model
79
+ return "pass", "expected-token-and-model-matched", observed_model
80
+
81
+ if vendor in ("grok", "agy"):
82
+ if exit_code != 0:
83
+ return "fail", f"exit-code: {exit_code}: {stderr_text[:120]}", None
84
+ if stderr_text:
85
+ return "fail", f"unexpected-stderr: {stderr_text[:120]}", None
86
+ if expected_token not in stdout_text:
87
+ return "fail", "missing-expected-token", None
88
+ return "pass", "expected-token-and-clean-stderr", None
89
+
90
+ if vendor == "codex":
91
+ diagnostics = [line[:120] for line in stderr_text.splitlines()
92
+ if "error" in line.lower() or "warning" in line.lower()]
93
+ review = "; stderr-review: " + " | ".join(diagnostics) if diagnostics else ""
94
+ if exit_code != 0:
95
+ return "fail", f"exit-code: {exit_code}{review}", None
96
+ if expected_token not in stdout_text:
97
+ return "fail", f"missing-expected-token{review}", None
98
+ return "pass", f"expected-token-matched{review}", None
99
+
100
+ return "fail", f"unsupported-vendor: {vendor}", None
101
+
102
+
103
+ def probe(
104
+ name: str,
105
+ argv: list[str],
106
+ timeout: int = 180,
107
+ cwd: Path | None = None,
108
+ root: Path | None = None,
109
+ *,
110
+ vendor: str | None = None,
111
+ expected_token: str | None = None,
112
+ ) -> dict:
113
+ if not expected_token:
114
+ raise ValueError("expected_token is required before running a probe")
115
+ if not argv:
116
+ raise ValueError("command is required")
117
+ vendor = vendor or Path(argv[0]).name
118
+ if vendor not in ("claude", "grok", "agy", "codex"):
119
+ raise ValueError(f"unsupported vendor: {vendor}; pass vendor explicitly")
120
+ root = (root or DEFAULT_ROOT).expanduser()
121
+ evidence = root / "evidence"
122
+ work = root / "work"
123
+ evidence.mkdir(parents=True, exist_ok=True)
124
+ work.mkdir(parents=True, exist_ok=True)
125
+ out_path = evidence / f"{name}.stdout"
126
+ err_path = evidence / f"{name}.stderr"
127
+ started = time.time()
128
+ timed_out = False
129
+ env = dict(os.environ)
130
+ # The runners drop the API key so the subscription OAuth path is used.
131
+ env.pop("XAI_API_KEY", None)
132
+ with open(out_path, "wb") as out, open(err_path, "wb") as err:
133
+ proc = subprocess.Popen(argv, stdout=out, stderr=err, stdin=subprocess.DEVNULL,
134
+ cwd=str(cwd or work), env=env)
135
+ try:
136
+ rc = proc.wait(timeout=timeout)
137
+ except subprocess.TimeoutExpired:
138
+ timed_out = True
139
+ proc.kill()
140
+ rc = proc.wait()
141
+ record = {
142
+ "command": argv,
143
+ "cwd": str(cwd or work),
144
+ "exit_code": rc,
145
+ "timed_out": timed_out,
146
+ "elapsed_seconds": round(time.time() - started, 3),
147
+ "probed_at": datetime.fromtimestamp(started, timezone.utc).isoformat(),
148
+ "stdout": str(out_path),
149
+ "stderr": str(err_path),
150
+ }
151
+ record["verdict"], record["verdict_reason"], record["observed_model"] = verdict(
152
+ record, out_path.read_text(errors="replace"), err_path.read_text(errors="replace"),
153
+ vendor, expected_token,
154
+ )
155
+ (evidence / f"{name}.json").write_text(json.dumps(record, indent=2) + "\n")
156
+ return record
157
+
158
+
159
+ def read(
160
+ name: str,
161
+ stream: str = "stdout",
162
+ limit: int = 4000,
163
+ root: Path | None = None,
164
+ ) -> str:
165
+ path = (root or DEFAULT_ROOT).expanduser() / "evidence" / f"{name}.{stream}"
166
+ if not path.exists():
167
+ return ""
168
+ return path.read_text(errors="replace")[:limit]
169
+
170
+
171
+ if __name__ == "__main__":
172
+ parser = argparse.ArgumentParser(description=__doc__)
173
+ parser.add_argument("--root", type=Path, default=DEFAULT_ROOT)
174
+ parser.add_argument("--expect", required=True, help="expected response token")
175
+ parser.add_argument("--vendor", choices=("claude", "grok", "agy", "codex"),
176
+ help="defaults to the command executable's basename")
177
+ parser.add_argument("name")
178
+ parser.add_argument("command", nargs=argparse.REMAINDER)
179
+ args = parser.parse_args()
180
+ if not args.command:
181
+ parser.error("command is required")
182
+ rec = probe(args.name, args.command, root=args.root,
183
+ vendor=args.vendor, expected_token=args.expect)
184
+ print(json.dumps(rec, ensure_ascii=False))
@@ -17,7 +17,12 @@ You (the main loop) may be Claude, GPT, Grok, or Gemini. The procedure is identi
17
17
  reading public results, acceptance, operator replies, git commit/push and
18
18
  governance edits. Workers execute the assigned task and never delegate again.
19
19
  Read-only work uses advise; edits require `--mode work --workdir <repo>`.
20
- `<repo>/scripts/dispatch.sh [--executor auto|native|cli] [--native-context FILE] [--vendor V] [--mode work] [--workdir DIR] <lane> "<task>"`
20
+ `<repo>/scripts/dispatch.sh --caller-context FILE [--executor auto|native|cli] [--native-context FILE] [--vendor V] [--mode work] [--workdir DIR] <lane> "<task>"`
21
+
22
+ A model caller MUST pass `--caller-context`; without it every dispatch is
23
+ refused with `missing-caller-context` before a job exists. Build that file
24
+ before the first dispatch — see **Frozen exact-AA downward gate** for the
25
+ schema, a worked example, and what to do when your own effort is unverifiable.
21
26
 
22
27
  Add `--background` for long tasks; poll with `scripts/jobs.sh status|result <id>`.
23
28
  Use `--thread NAME` when later claude, codex, grok or gemini dispatches
@@ -332,6 +337,90 @@ that exact frozen score and the inherited ceiling. Targets at or below it are al
332
337
  unknown identities and unresolved request-selector mappings fail closed. No family,
333
338
  displayed grade, highest-effort assumption, retry or fallback grants an uplift.
334
339
 
340
+ Build the file before the first dispatch, not after a refusal. Every field is an
341
+ exact identity: `caller` must reproduce one `scored_configs` row byte-for-byte,
342
+ and `snapshot_id` must equal the registry's own `snapshot.id`.
343
+
344
+ ```json
345
+ {
346
+ "schema_version": 1,
347
+ "snapshot_id": "<registry snapshot.id>",
348
+ "kind": "model",
349
+ "caller": {"vendor": "claude", "model": "claude-opus-5", "effort": "high",
350
+ "reasoning": "adaptive", "fallback": null},
351
+ "inherited_ceiling": 52
352
+ }
353
+ ```
354
+
355
+ Set `inherited_ceiling` to your own row's score when you are the root caller, or
356
+ to the ceiling you were handed when you are a child.
357
+
358
+ **When your harness names a model but no effort, look before you guess.** The
359
+ launching process usually carries the exact flags. Walk your own ancestor chain
360
+ (`ps -o ppid=,comm= -p <pid>` upward, then `ps -o args= -p <ancestor>`) and read
361
+ its `--model` / `--effort`. Match the ancestor chain rather than the first
362
+ matching process on the host — a second session of the same CLI is common and
363
+ its flags are not yours. This is request-selector evidence, the same class the
364
+ transport overlay carries, and it does not certify upstream identity.
365
+
366
+ Only when that genuinely yields nothing: ask the operator, or declare the
367
+ lowest-scoring row of your model and say so in your report. Understating only
368
+ narrows what you may dispatch to, so it fails in the safe direction — but it is
369
+ the fallback, not the first move, and an unnecessarily low ceiling silently
370
+ closes lanes and pushes the question back onto the operator. Never raise the
371
+ declared effort to unblock a refused target, and never assert
372
+ `--operator-asserted-human` on your own behalf.
373
+
374
+ Three refusal codes mean different things and need different fixes.
375
+ `missing-caller-context` means you passed no file — write one.
376
+ `runtime-mapping-unverified` means the file is fine but the *target* has no proven
377
+ host-local request selector; that is fixed by a `--transport-overlay` entry backed
378
+ by real evidence, never by editing the frozen registry (its sha256 is pinned in
379
+ `scripts/lib/aa_policy.py`, so any edit fails the whole gate closed).
380
+ `invalid-policy-input` with "transport contract evidence changed" is neither: the
381
+ overlay itself will not load, so nothing about your caller or your target is wrong.
382
+ Run `omnilane doctor` first — its `transport-overlay` check names the offending
383
+ file and the vendor it belongs to. Do not go hunting by hand.
384
+
385
+ Upgrading a vendor CLI is the usual cause. The overlay pins the sha256 of each
386
+ vendor's executable and runner script, so a new release invalidates that vendor's
387
+ selector evidence. Evidence entries carry a `vendor` tag: a tagged entry that
388
+ drifts marks only its own vendor stale, and the other three keep dispatching.
389
+ Untagged evidence — `probe-manifest.json`, and any overlay built before the tags
390
+ existed — still fails the whole gate closed, which is what an unpatched host
391
+ looks like. Codex and Claude evidence paths embed version directories
392
+ (`releases/0.153.4-…`, `versions/2.1.263`), so their upgrades remove the file
393
+ rather than change its digest; both are treated as staleness, not corruption.
394
+
395
+ Re-signing is a probe, a rebuild, and an install, in that order. Back up
396
+ `~/.omnilane/transport-contracts.local.json` first; restoring it is the rollback.
397
+ `scripts/lib/probe.py --expect TOKEN [--vendor V] NAME COMMAND…` invokes the CLI
398
+ directly through `subprocess`, so it works while the gate is refusing everything —
399
+ this is what breaks the deadlock. `scripts/provider-probe.sh` goes through
400
+ `dispatch.sh` and therefore through the gate, so it is useless in this state.
401
+ Then `scripts/lib/build_overlay.py` rebuilds, and you copy the result over the
402
+ live overlay. Verify with a real dispatch on a lane belonging to the vendor you
403
+ re-probed; loading the registry in Python is not the runtime surface.
404
+
405
+ Keep the sweep where its default `--root` puts it,
406
+ `~/.omnilane/transport-evidence/<sweep-id>/`. The rebuilt overlay anchors
407
+ `probe-manifest.json` by absolute path as untagged evidence, so a sweep parked
408
+ inside a repository is one `git clean -fdx` away from taking every vendor down
409
+ at once — the same global refusal a re-signing session is usually trying to end.
410
+
411
+ Never sign a probe you did not read. `probe.py` records a `verdict` because exit
412
+ status alone is not evidence: the Claude CLI answers a quota refusal with a JSON
413
+ body carrying `is_error`, and it accepts an unknown `--effort` by silently using
414
+ the default, returning exit 0, the right `modelUsage`, and the expected token
415
+ with only a stderr warning to show for it. Effort is half of a scored identity,
416
+ so that path would certify a mapping at the wrong tier. Configurations whose
417
+ probes failed are recorded in the overlay's `unproven[]` and surfaced by doctor
418
+ instead of vanishing — six Fable rows sat unusable for two days in September
419
+ 2026 because a 429 quota refusal left no trace anywhere. A refused probe is not
420
+ always transient: re-probing those six two days later returned the same 429, so
421
+ an `unproven[]` entry can mean the account, not the moment. Read the reason
422
+ before assuming a retry will clear it.
423
+
335
424
  A `--transport-overlay /absolute/overlay.json` may prove a small set of host-local
336
425
  request selectors using exact identities and hashed local contract evidence. It does
337
426
  not change frozen AA scores or certify upstream provider identity. The explicit
@@ -1,31 +0,0 @@
1
- # Omnilane 0.42.2
2
-
3
- This patch repairs the Grok single-shot reasoning-effort transport. It does not raise caller scores or remove the exact-AA downward-delegation gate.
4
-
5
- ## Changes
6
-
7
- - Explicit Grok effort is forwarded as `--reasoning-effort VALUE`. The supported selector spellings are `low`, `medium`, `high`, and `xhigh`; invalid values fail before provider startup.
8
- - Grok 4.6 entries in the default routing table explicitly select `high`.
9
- - A scored Grok target requires a verified `cli_reasoning_effort` mapping and the exact `--reasoning-effort` flag. The existing host-local transport overlay validates host, snapshot, exact identity, evidence hashes, vendor, flag, and effort.
10
- - Explicit effort on the live ACP path is rejected rather than discarded. This release does not add Grok work-mode network isolation on macOS.
11
-
12
- ## Verification boundary
13
-
14
- The frozen AA registry and its approved SHA remain unchanged. Transport evidence belongs to the local host; the package does not ship a blanket assertion that every Grok model/effort combination is verified.
15
-
16
- `request-selector-contract` proves how Omnilane selects the model and effort. It does not independently authenticate the upstream model's internal identity: `upstream_identity_verified` remains false. A successful reply alone is not an identity attestation.
17
-
18
- For an existing local overlay, use `selector_type: cli_reasoning_effort`, `cli_flag: --reasoning-effort`, and matching model/effort identity only after checking the installed CLI and runner. Retain absolute evidence paths and SHA-256 hashes. Changed evidence requires re-verification; do not merely relabel an old mapping as verified. Select it with `--transport-overlay /absolute/overlay.json` or `OMNILANE_AA_TRANSPORT_OVERLAY`.
19
-
20
- ## Upgrade
21
-
22
- After npm publication:
23
-
24
- ```sh
25
- npm i -g omnilane@0.42.2
26
- omnilane --version
27
- ```
28
-
29
- Repo-symlink installations use the updated checkout. Do not rerun `install.sh` just to update the version. Local routing overrides take precedence; review any Grok entries still using `-`.
30
-
31
- GitHub release, npm publication, local provider smoke, and Linux CI are separate verification surfaces. Release evidence must identify each result rather than equating one with the others.