Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions docs/decisions/2026-10-02-omniroute-mac-rebuild.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,46 @@ answered 401 too, and its port preflight counted a browser's open dashboard conn
several times before it bound; the new build's preflight counts listeners only (upstream #14812). OmniRoute wrote its
own pre-migration database backup before migrating; it was not needed.

## Update, 2026-10-03

This update answers the first two items of issue 624's LE-18, which are addressed to the Mac OmniRoute service owner.
The details are in `evidence/artifacts/omniroute-mac-rebuild-20261002/le18-20261003.json`.

- **Running build.**
- The listener on port 20128 started at the switch, 2026-10-02T03:44:48Z, from this build.
- The installed package's `dist/BUILD_SHA` reads `6f246e84a`.
- The retained tarball hashes to the build manifest's `d602dc42…`.
- **The process title `omniroute (v16.3.5)`** names the bundled Next.js version, not OmniRoute's. Next sets
`next-server (v16.3.5)`, and OmniRoute's startup instrumentation renames it.
- **Effort read-back is still not done.** One GPT-6.1 Sol call at body effort `max` answered 200.
- The gateway's call-log APIs record each call's model, provider, account, token counts and reasoning source, but no
effort field.
- The first limit below stands.
- **Update check, version 2,** deployed with version 1 kept beside it as the rollback. It still installs nothing. It
adds notifications for:
- a carried upstream PR closed without a merge, once per closure;
- a new official release, with a read of its npm package for the two capabilities this build carries, checked by
structure: a GPT-6.1 Sol model object in the server bundle, and the `/api/v1/alpha/search/route` entry of the
server's route manifest together with its route file. The result is a notice, not a switch gate; a switch still
has to pass the switch tool's probe gate.

The deployed script is the one revised after this change's automated review (10:02Z):
- Reads are bounded per package, per file and in total.
- A notification counts only when it was delivered, and the state that suppresses a repeat is written after delivery.
- A failed package read keeps the release pending.
- A failed PR lookup keeps the PR's previous record.

Tests, retained in `checks/update-check-v2/`:
- A 16-case suite passes. It stops at its first case against the first deployment, whose literal scan also accepted a
decoy package.
- This build's own package carries both capabilities; release v3.8.51 carries neither.
- A dry run against a scratch state folder reports a new release and logs its notification.
- The first real run of the revised script changed nothing.
- The receipt records a completeness critic: what was not observed (a real new release, desktop delivery, the
scheduled run) and what was not read.
- **Not done here:** LE-18's third item, the `omniroute` row of `manifests/stack.json`. It is the lane:shared manifest
owner's change.

## Limits

- Upstream effort on the wire was not read back on this host: the gateway's call log sits in its credential-bearing
Expand Down
3 changes: 3 additions & 0 deletions evidence/artifacts/omniroute-mac-rebuild-20261002/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ check. The decision is [`docs/decisions/2026-10-02-omniroute-mac-rebuild.md`](..
| `checks/switch-1.log`, `checks/switch-2.log` | The first switch (rolled back) and the second (passed) |
| `launchd/*.plist.json` | The gateway's launch agent before and after, and the update-check agent (home paths masked) |
| `scripts/*.txt` | The build, switch, probe, wrapper and update-check scripts as they ran (paths masked) |
| `le18-20261003.json` | The 2026-10-03 follow-up for issue 624, LE-18: running-build fingerprint, the process title, the update-check state, the effort read-back limit, and version 2 of the update check with its tests |
| `scripts/omniroute-update-check.v2.py.txt` | Version 2 of the update check as deployed on 2026-10-03 after review (version 1 stays as `omniroute-update-check.py.txt`) |
| `checks/update-check-v2/` | Version 2's test suite and its output, the red check against the first deployment, the positive and negative capability controls, the dry run and the first real run (paths masked) |

Paths are masked as `<HOME>` and `<build-dir>`. The gateway's data directory and every credential were left
unopened; the placeholder key in the launch agent is the documented non-secret loopback value.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
$ omniroute-update-check.py --scan-file <this build tarball, sha256 d602dc42...>
{
"sol_catalog": true,
"alpha_search_route": true,
"carries_both": true
}
$ omniroute-update-check.py --capability v3.8.51
{
"version": "3.8.51",
"tarball": "https://registry.npmjs.org/omniroute/-/omniroute-3.8.51.tgz",
"integrity": "sha512-VwwSt+bP9lJiPJXFJMz0nNGGuoewPZU3nFe1SLuO11ADgdSwTegGCxhg8Ov75+31m/cocPxHiO63zygn1XQ0MQ==",
"sol_catalog": false,
"alpha_search_route": false,
"carries_both": false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
$ bash dry_test.sh (dry functional run of the version-2 script against a scratch state folder)
{"at": "2026-10-03T10:02:35+00:00", "codex_follow": "would_restart", "installed": "0.160.0", "announced": null, "dry": true}
{"at": "2026-10-03T10:02:40+00:00", "notification": "New OmniRoute release v3.8.51: it lacks a capability of the local build.", "dry": true}
{"at": "2026-10-03T10:02:40+00:00", "upstream": {"checked_at": "2026-10-03T10:02:37+00:00", "check_version": 2, "latest_release": "v3.8.51", "published_at": "2026-09-30T01:41:50Z", "carried": [{"pr": 13788, "state": "open", "merged_at": null, "closed_unmerged": false, "in_latest_release": false, "notified_closed": false}, {"pr": 15167, "state": "open", "merged_at": null, "closed_unmerged": false, "in_latest_release": false, "notified_closed": false}], "official_release_carries_everything": false, "new_release": true, "notified_ready_for": null, "capability": {"version": "3.8.51", "tarball": "https://registry.npmjs.org/omniroute/-/omniroute-3.8.51.tgz", "integrity": "sha512-VwwSt+bP9lJiPJXFJMz0nNGGuoewPZU3nFe1SLuO11ADgdSwTegGCxhg8Ov75+31m/cocPxHiO63zygn1XQ0MQ==", "sol_catalog": false, "alpha_search_route": false, "carries_both": false}, "build": "omniroute-3.8.52-528235175-pr13788-pr15167"}}
exit=0
record: {'check_version': 2, 'latest_release': 'v3.8.51', 'new_release': True, 'official_release_carries_everything': False} capability: {'version': '3.8.51', 'sol_catalog': False, 'alpha_search_route': False, 'carries_both': False} carried: [(13788, 'open', False), (15167, 'open', False)]
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/bash
# Dry functional test of the version-2 update check in a scratch state folder: a previous record names v3.8.50 and
# marks nothing closed, so the run must report a new release (v3.8.51), read its package, find neither capability,
# and log (not post) one notification; the Codex follow step must log a would-restart instead of restarting.
set -u
D=<scratch>/a2/uc2
STATE_ROOT=$D/state-test
rm -rf "$STATE_ROOT"; mkdir -p "$STATE_ROOT/native-agent-stack"
cat > "$STATE_ROOT/native-agent-stack/omniroute-update.json" <<'JSON'
{"latest_release": "v3.8.50", "carried": [{"pr": 13788, "closed_unmerged": false}, {"pr": 15167, "closed_unmerged": false}]}
JSON
cp "$D/omniroute-update-check.py" "$D/run-copy.py"
OMNIROUTE_UPDATE_CHECK_DRY=1 XDG_STATE_HOME="$STATE_ROOT" /usr/bin/python3 "$D/run-copy.py"
echo "exit=$?"
/usr/bin/python3 -c "import json,sys;d=json.load(open(sys.argv[1]));print('record:',{k:d[k] for k in ('check_version','latest_release','new_release','official_release_carries_everything')},'capability:',{k:d.get('capability',{}).get(k) for k in ('version','sol_catalog','alpha_search_route','carries_both')},'carried:',[(c['pr'],c['state'],c['closed_unmerged']) for c in d['carried']])" "$STATE_ROOT/native-agent-stack/omniroute-update.json"
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
$ /usr/bin/python3 <prefix>/omniroute-update-check.py (first real run of the reviewed version 2, 2026-10-03T10:02Z)
{"at": "2026-10-03T10:02:03+00:00", "codex_follow": "unchanged", "installed": "0.160.0", "announced": "0.160.0"}
{"at": "2026-10-03T10:02:06+00:00", "upstream": {"checked_at": "2026-10-03T10:02:06+00:00", "check_version": 2, "latest_release": "v3.8.51", "published_at": "2026-09-30T01:41:50Z", "carried": [{"pr": 13788, "state": "open", "merged_at": null, "closed_unmerged": false, "in_latest_release": false, "notified_closed": false}, {"pr": 15167, "state": "open", "merged_at": null, "closed_unmerged": false, "in_latest_release": false, "notified_closed": false}], "official_release_carries_everything": false, "new_release": false, "notified_ready_for": null, "capability": null, "build": "omniroute-3.8.52-528235175-pr13788-pr15167"}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
$ python3 test_update_check.py <the first deployed version 2> (red: the suite stops at its first case)
File "<scratch>/a2/uc2/test_update_check.py", line 73, in <module>
case("closed PR notifies once", any("15167" in n for n in notes) and rec["carried"][1]["notified_closed"])
KeyError: 'notified_closed'
$ python3 red_scan.py <the first deployed version 2>
old v2 on a decoy package: {'sol_catalog': True, 'alpha_search_route': True, 'carries_both': True}
old v2 has a per-member read limit: False
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""Red check for the scan findings: the first deployed v2's literal scan against a decoy package and an oversize member."""
import importlib.util
import io
import os
import sys
import tarfile

os.environ["OMNIROUTE_UPDATE_CHECK_DRY"] = "1"
spec = importlib.util.spec_from_file_location("old", sys.argv[1])
assert spec and spec.loader
old = importlib.util.module_from_spec(spec)
spec.loader.exec_module(old)


def package(files):
buf = io.BytesIO()
with tarfile.open(fileobj=buf, mode="w:gz") as tar:
for name, body in files.items():
info = tarfile.TarInfo(name)
info.size = len(body)
tar.addfile(info, io.BytesIO(body))
return buf.getvalue()


decoy = package({"package/dist/.build/next/server/chunks/a.js": b'/* gpt-6.1-sol alpha/search */ var x = "gpt-6.1-sol";'})
print("old v2 on a decoy package:", old.scan(decoy))
print("old v2 has a per-member read limit:", hasattr(old, "MAX_MEMBER"))
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
$ OMNIROUTE_UPDATE_CHECK_DRY=1 python3 test_update_check.py <the version-2 script>
PASS closed PR notifies once
PASS closed PR is not notified again
PASS failed delivery is not acknowledged
PASS failed delivery is retried
PASS failed lookup keeps the recorded closure
PASS failed lookup does not re-notify
PASS failed package read keeps the release pending
PASS pending release is read again and then advanced
PASS capability result carried forward
PASS carries-everything notice
PASS carries-everything notice once per tag
PASS decoy strings are not capabilities
PASS model object and manifest route are capabilities
PASS manifest entry without its route file is not a route
PASS member over the read limit is skipped
PASS total decompressed limit stops the scan
all passed
exit=0
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
"""Tests of the version-2 OmniRoute update check (dry mode, stubbed GitHub and npm reads, synthetic packages).

Run: OMNIROUTE_UPDATE_CHECK_DRY=1 python3 test_update_check.py <path to the script under test>
Each case prints one line; the process exits 1 when any case fails."""
import importlib.util
import io
import json
import os
import sys
import tarfile
import tempfile
from contextlib import redirect_stdout
from pathlib import Path

os.environ["OMNIROUTE_UPDATE_CHECK_DRY"] = "1"
spec = importlib.util.spec_from_file_location("uc", sys.argv[1])
assert spec and spec.loader
uc = importlib.util.module_from_spec(spec)
spec.loader.exec_module(uc)
FAILED = []


def case(name, ok, detail=""):
print(("PASS " if ok else "FAIL ") + name + (f" ({detail})" if detail and not ok else ""))
if not ok:
FAILED.append(name)


def package(files):
buf = io.BytesIO()
with tarfile.open(fileobj=buf, mode="w:gz") as tar:
for name, body in files.items():
info = tarfile.TarInfo(name)
info.size = len(body)
tar.addfile(info, io.BytesIO(body))
return buf.getvalue()


def run(prs, previous, tag="v3.8.51", published="2026-09-30T01:41:50Z", cap=None, deliver=True):
state = Path(tempfile.mkdtemp()) / "native-agent-stack"
state.mkdir(parents=True)
if previous is not None:
(state / "omniroute-update.json").write_text(json.dumps(previous))
uc.STATE = state

def fake(path):
if path.endswith("releases/latest"):
return {"tag_name": tag, "published_at": published}
value = prs[int(path.rsplit("/", 1)[1])]
if isinstance(value, Exception):
raise value
return value

uc.gh_json = fake
uc.capability = lambda t: dict(cap or {"version": t, "carries_both": True})
real_notify = uc.notify
if not deliver:
uc.notify = lambda message: (uc.log({"notification_failed": message}), False)[1]
out = io.StringIO()
with redirect_stdout(out):
uc.upstream_notice()
uc.notify = real_notify
notes = [json.loads(line)["notification"] for line in out.getvalue().splitlines() if '"notification"' in line]
return json.loads((state / "omniroute-update.json").read_text()), notes


OPEN = {"state": "open", "merged_at": None}
CLOSED = {"state": "closed", "merged_at": None}
MERGED = {"state": "closed", "merged_at": "2026-09-29T00:00:00Z"}

# Closed without a merge: one notification, none on the next run.
rec, notes = run({13788: OPEN, 15167: CLOSED}, {"latest_release": "v3.8.51"})
case("closed PR notifies once", any("15167" in n for n in notes) and rec["carried"][1]["notified_closed"])
rec2, notes2 = run({13788: OPEN, 15167: CLOSED}, rec)
case("closed PR is not notified again", not any("15167" in n for n in notes2))
# A failed delivery leaves the closure unacknowledged, so the next run notifies again.
rec3, _ = run({13788: OPEN, 15167: CLOSED}, {"latest_release": "v3.8.51"}, deliver=False)
case("failed delivery is not acknowledged", rec3["carried"][1].get("notified_closed") is False, rec3["carried"][1])
_, notes4 = run({13788: OPEN, 15167: CLOSED}, rec3)
case("failed delivery is retried", any("15167" in n for n in notes4))
# A failed PR lookup keeps the PR's previous record (closure history survives).
rec5, notes5 = run({13788: OPEN, 15167: RuntimeError("HTTP 502")}, rec)
case("failed lookup keeps the recorded closure", rec5["carried"][1]["closed_unmerged"] is True
and rec5["carried"][1]["notified_closed"] is True and "lookup_error" in rec5["carried"][1], rec5["carried"][1])
case("failed lookup does not re-notify", not any("15167" in n for n in notes5))
# A new release whose package read fails stays pending and is read again next run.
rec6, notes6 = run({13788: OPEN, 15167: OPEN}, {"latest_release": "v3.8.50"}, cap={"version": "3.8.51", "error": "timeout"})
case("failed package read keeps the release pending", rec6["latest_release"] == "v3.8.50"
and rec6["pending_release"] == "v3.8.51" and not notes6, rec6)
rec7, notes7 = run({13788: OPEN, 15167: OPEN}, rec6, cap={"version": "3.8.51", "carries_both": False})
case("pending release is read again and then advanced", rec7["latest_release"] == "v3.8.51"
and rec7["new_release"] and any("v3.8.51" in n for n in notes7), rec7)
# The capability result is kept on later runs.
rec8, notes8 = run({13788: OPEN, 15167: OPEN}, rec7)
case("capability result carried forward", rec8["capability"] == rec7["capability"] and not rec8["new_release"]
and not notes8, rec8)
# Every carried PR merged before the latest release: one carries-everything notice per release tag.
rec9, notes9 = run({13788: MERGED, 15167: MERGED}, {"latest_release": "v3.8.51"})
case("carries-everything notice", any("carries every PR" in n for n in notes9) and rec9["notified_ready_for"] == "v3.8.51")
_, notes10 = run({13788: MERGED, 15167: MERGED}, rec9)
case("carries-everything notice once per tag", not any("carries every PR" in n for n in notes10))

# Structural scan: decoy strings outside the structures count for nothing.
decoy = package({"package/dist/.build/next/server/chunks/a.js": b'/* gpt-6.1-sol alpha/search */ var x = "gpt-6.1-sol";',
"package/README.md": b"gpt-6.1-sol /v1/alpha/search"})
r = uc.scan(decoy)
case("decoy strings are not capabilities", not r["sol_catalog"] and not r["alpha_search_route"], r)
real = package({"package/dist/.build/next/server/chunks/b.js": b'x=[{id:"gpt-6.1-sol",name:"GPT-6.1 Sol",supportsReasoning:!0}]',
"package/dist/.build/next/server/app-paths-manifest.json":
json.dumps({"/api/v1/alpha/search/route": "app/api/v1/alpha/search/route.js"}).encode(),
"package/dist/.build/next/server/app/api/v1/alpha/search/route.js": b"export {}"})
r = uc.scan(real)
case("model object and manifest route are capabilities", r["sol_catalog"] and r["alpha_search_route"], r)
missing_route_file = package({"package/dist/.build/next/server/app-paths-manifest.json":
json.dumps({"/api/v1/alpha/search/route": "app/api/v1/alpha/search/route.js"}).encode()})
r = uc.scan(missing_route_file)
case("manifest entry without its route file is not a route", not r["alpha_search_route"], r)
# Decompression bounds: a member over the per-file limit is skipped, not read.
uc.MAX_MEMBER = 1024
big = package({"package/dist/.build/next/server/chunks/big.js": b'{id:"gpt-6.1-sol",name:"GPT-6.1 Sol"}' + b" " * 4096})
r = uc.scan(big)
case("member over the read limit is skipped", not r["sol_catalog"] and r.get("skipped_over_member_limit"), r)
uc.MAX_MEMBER, uc.MAX_TOTAL = 64 * 1024 * 1024, 10
r = uc.scan(real)
case("total decompressed limit stops the scan", r.get("error") == "decompressed size over the read limit", r)

print(f"{'FAILED ' + str(len(FAILED)) if FAILED else 'all passed'}")
sys.exit(1 if FAILED else 0)
Loading
Loading