Review finding "Exit can drop queued metrics" (Greptile P2 + independent review W2): the smoke's
docstring claimed stopping the auto-started gateway settles its asynchronous rows, but the gateway's
graceful exit ends in os._exit, which skips the shared-metrics flush. Poll the probe home's store
(read-only, so polling never creates it) with a 30 s deadline until the gateway turn rows validate,
then stop the gateway; on expiry the store validation reports what is missing.
Review finding "Cleanup repeats weaker process checks" (Greptile P2 + independent review W1): the
smoke's gateway stop trusted the probe home's gateway.pid record (kind + home) and signalled whatever
process held that PID, so a live non-gateway process or a recycled PID named by a stale record was
terminated. Resolve the PID with gateway.status.live_gateway_pid_for_home (start-time reuse guard,
gateway command line, home ownership) and do nothing on a miss; psutil binds the create time so
terminate() refuses a PID reused in between. The driver puts --hermes-repo first on sys.path so the
check comes from the tree under test.
The smoke's expected counters predate three merges, so on origin/main it stopped at the first
assertion:
- #106742 (unified gateway runtime): `hermes chat -q` is now served by a gateway it auto-starts
for the home, which adds hermes.gateway.reply_latency and hermes.platform.delivery (platform
local) and a second hermes.startup.latency row (surface gateway_boot).
- eb2edce53a: the turn's read_file call now also records hermes.tool_batch.count.
- e76fb951a1: a failed update counts the stage it died in, so the fetch-failed receipt adds an
(apply, failed) hermes.update.stage row.
Expectations now live in one place: a single EXPECTED_METRIC_NAMES set for the SQLite and the
package checks, exact dimension dicts for the new rows, the update stages derived from the receipt
the probe feeds (UPDATE_RECEIPT, also used to build the probe), and both startup surfaces.
The smoke also stops the gateway the chat auto-started (pid record of its own home, then wait),
so a run no longer leaves a detached `gateway run --idle-exit` behind for 10 minutes, and the
gateway's asynchronous rows land before the package export.
The smoke is not run by CI, so the test file now pins its expectations to the contract: every
expected name is a contract counter, every exact dimension dict has the metric's current field set
and passes counter_dimensions_are_valid, the startup surfaces are contract surfaces, and the
expected update rows equal update_receipt_fields(UPDATE_RECEIPT).
Two regressions from earlier commits on this branch, caught by CI's Python tests:
- 8d7bff7615 dropped `get_plugin_oauth_auth_status` from hermes_cli.auth's imports as unused, but
`_STATUS_BY_AUTH_TYPE` dispatches oauth_device_code / oauth_external plugin providers through
`globals()[name]`, so every OAuth plugin provider's status raised KeyError
(test_plugin_provider_picker_residue). Restored with a noqa naming the dynamic lookup.
- 7131eadb77 registered scripts/check_config_yaml_readers.py as the `config-readers` guard, but the
code-health CLI tests copy only the listed guard scripts into their temp repos, so the installed
hooks failed on the missing script. Added it to the fixture's _GUARD_SCRIPTS.
Carried from BearHuddleston's #93508 onto main. The Webapp relay and the
profile-incarnation fence stay out: carried code follows main's
named-profile rule and refuses a missing or deleted profile home.
- Webapp routes: one-use launch tickets, window handoff, and gateway
WebSocket tickets minted for the signed-in browser
- Browser surface policy and the SPA document policy
- A host terminal for the browser that survives reconnects
- Staged browser uploads and file tickets; file.attach takes a staged upload
- hermes sessions adopt
- Webapp bundle build helpers and the renderer freshness check
- Regenerated gateway contracts
Co-authored-by: emozilla <emozilla@nousresearch.com>
A test file that raised at import (e.g. a decorator using pytest without
`import pytest`) exited 2 with "collected 0 items / 1 error" and no
failed-test count, so the Summary line read "2 tests passed, 0 failed"
while the run exited 1, and the file was filed under a generic "no tests
ran" bucket. A positional test path that did not exist was dropped by
discovery and the run exited 0 without testing it.
- mark a failing file whose output shows a collection error and give it
its own failure bucket, "FAILED AT IMPORT/COLLECTION", with pytest's
one-line cause (the last traceback E line)
- the Summary line names failing files: "; FAILED: 1 of 2 files
(1 at import/collection)"; error/crash/skip notes move into one helper
- the failure buckets become a cause -> (header, detail) table
- a positional path that does not exist exits 2 with
"error: no such test path: ..."; --paths defaults keep skipping missing
roots
- per-file pytest exit 5 (nothing collected, e.g. platform-gated) stays a
pass, as deliberately added with the run-level NO TESTS RAN guard
A file whose tests pass but whose fixtures error at setup ("41 passed,
7 errors") exits 1, yet the Summary read "41 tests passed, 0 failed" and
the file was filed under "all tests passed but pytest exited non-zero".
- accumulate summary["errors"] into tests_errored and print ", N errors"
on the Summary line when non-zero (same pattern as crashed_note)
- dedicated failure bucket "with pytest ERRORs" listing passed/error
counts per file; such files no longer land in the passed-but-nonzero
or no-tests-ran buckets
- tests: parser returns errors for "41 passed, 7 errors"; end-to-end
probe with a failing fixture asserts the Summary shows "3 errors";
clean run has no errors note
install.sh --skip-browser / --no-playwright and install.ps1 -SkipBrowser
now hand PM `--without agent-browser` again instead of `--without chromium`.
The downloaded installer is the newest script, but by default it installs
the stable tag, whose PM (v0.22.5) rejects `--without chromium` with
"--without accepts only optional default packages (agent-browser,
cua-driver)" and exits 1, failing the whole install. The new PM already
reads `agent-browser` as `chromium` (pm.defaults.RETIRED_DEFAULTS), so the
legacy spelling works against both the stable tag and main. Direct
`hermes pm install --without chromium` stays valid on the new PM.
- pm: AgentBrowser class + lock entry gone; Chromium default=True and its env
exports only PLAYWRIGHT_BROWSERS_PATH (Playwright launches without an
executable path still need it). A recorded or passed --without agent-browser
maps to chromium (pm.defaults.RETIRED_DEFAULTS), and pm gc forgets the stale
agent-browser fact so its store entry is reclaimed.
- install.sh --skip-browser/--no-playwright and install.ps1 -SkipBrowser pass
--without chromium; undo hint is hermes pm install chromium.
- Docker bakes /etc/hermes/chromium-executable-path; stage2-hook validates it
and exports nothing; the sandbox-desktop image drops the agent-browser npm
install and keeps its Playwright Chromium.
Review follow-up: short_alias now creates the junction's parent directory,
so preparation never depends on the caller having made the work dir first
(the existing prepare_tools test failed on both Windows lanes with WinError 3).
That test now checks the invariant the Windows path keeps: UV_CACHE_DIR
reaches the cache directory, through the short work-local junction on Windows.
With a cold desktop-inputs cache, Windows desktop preparation builds sdists
that have no cp314 Windows wheel (obstore on win32-x64, dependency-injector on
win32-arm64). uv builds each one in
<cache>\python\runtime\<toolchain id>\sdists-v10\pypi\<pkg>\<ver>\<id>\src,
about 200 characters below the deep .cache\desktop-inputs root, so MSVC
link.exe outputs passed MAX_PATH and the build failed with LNK1104.
On Windows, prepare_tools now hands uv a short job-local junction
(<work>\uv) that points at the same transported cache directory. uv keeps the
spelling it is given, so builds run under the short path while the bytes are
still restored and saved by the desktop-inputs cache. The native toolchain id
is read through the junction, and prepare_native keeps the absolute spelling
instead of resolving the junction back to the long path.
CARGO_TARGET_DIR is also owned by the preparation environment and points at
<work>\cargo, so Rust sdists link under the work root on every target even
if a tool resolves the junction.
Requirements builds (Termux deb assembly and wheelhouse gate, PM runtime
wheelhouse install) install the lock's export with uv pip install, which
re-resolves and rejects the overridden graph. Pass the lock: its manifest
overrides become --overrides and the closing check accepts them.
FORCE_COLOR/CLICOLOR_FORCE made uv pip check emit ANSI the report parser
cannot read, failing every install with an expected override mismatch.
When a checkout's .git exists but git rejects it (empty HEAD, a gitdir:
file pointing nowhere), every `hermes update` failed at the fetch with a
generic "Failed to fetch updates" and, on Windows, left the paused
gateways stopped with "the next `hermes` command restarts them". No
later command could: the whole-tree gate held the set while HEAD was
unreadable, so bots stayed down until someone repaired the checkout by
hand.
- tree gate: a set whose every pause already found HEAD unreadable and
that never bound a tree move resumes. The gateways were serving that
exact tree and no update step wrote it. A recorded move, or a HEAD
that was readable at the pause, still holds the set.
- fetch failure: "not a git repository" now says the checkout's git data
is damaged and prints the installer one-liner that repairs it.
- install.sh: a dangling `.git` file is moved aside and re-cloned like a
commit-less one, matching install.ps1 (Test-Path already matched it);
both installers' warning names a damaged .git, not only an
interrupted clone.
The bind-mount ownership test probes `docker info` / `docker image inspect`
with a 15 s timeout but only skipped on a non-zero exit, so a CI runner
daemon that hangs past the timeout raised TimeoutExpired and failed the
test (main run 39560831204 on 9ec17f7f4f, both parametrizations). A daemon
that does not answer is as unavailable as one that refuses: skip, the same
way tests/docker/conftest.py::_docker_available already treats a timeout.
Only the assembly run itself may fail this test.
stable-store commits the Store submission held (targetPublishMode Manual)
instead of leaving an uncommitted draft, so certification runs before the
release gate and a run whose stable-publish fails still shows customers
nothing. The publication pass then PUTs the held submission with
Immediate and commits it again, so it goes live without a Partner Center
click.
Microsoft documents no call that releases a held submission and does not
say whether update accepts a committed one (409 is documented only as
"the current state of the app"). A 409 from the switch leaves the
submission held and prints the Publish now instruction; the release
stays green. The first real release shows which answer the API gives.
Reconcile receipt-bound Store work independently of channel advancement, including
already-current published releases, so an interruption cannot strand a held
submission. Record each upload's claim in certification notes and replace a held
Manual predecessor only after its exact remote abandon marker matches the claim
commit. Unknown owners and publishing submissions remain protected.
candidate-manifest spent ~4 of its 5.2 min downloading 8.7 GB (stable run
38491090442: both MSIX bundles, including the 4.3 GB Store bundle) only to
re-hash them, and each stage-receipt job re-downloaded its group's ZIPs and
bundle (60-256 s). The same bytes are hashed by the build job that uploaded
them, bound by digest in its handoff receipt, and downloaded and digest-
checked again by release_artifacts.publish (materialize) before anything
goes live.
Candidate staging and receipts now download only the metadata JSONs; package
rows still carry the receipt digests. The one check that read package
contents, the universal bundle identity against its per-arch rows, moves to
publish, after materialize has verified the bytes. A Store-only claim has no
metadata to fetch, so candidate-manifest reads its receipts alone.
urlopen() builds its default opener once per process, capturing the SSL
context from SSL_CERT_FILE at that moment. Every https_origin fixture mints
its own self-signed CA, so whichever test first called urlopen() pinned its
CA for every later test in the same file: later origins failed with
CERTIFICATE_VERIFY_FAILED. Today's tests happen to avoid the order that
trips it; a new publish test in the release stack hit it. Reset the cached
opener per fixture so each origin's CA is the one in use.
On win32-arm64, native_cache_path deliberately treats identity without the
provider's OpenSSL (OPENSSL_DIR) as incomplete, so the test got a fresh
partition on the arm64 Windows lane while x64 passed. Release builds always
hand vcpkg's static OpenSSL in; the test now does too.
The Store build used to be committed at green with targetPublishMode
Manual, and the publication pass could only print "click Publish now":
the submission API has no call that releases a held submission (update
and commit return 409 once it is committed). Every stable release needed a
person in Partner Center.
stable-store now uploads the verified bundle as an uncommitted draft
(msstore publish --noCommit) and stops. The publication pass, which runs
only when the release really goes out, reads the receipt-bound candidate
manifest, checks the pending draft carries that release's Store bundle,
sets targetPublishMode Immediate and commits it. Certification starts
then, and the build goes live when it passes.
- A draft from a newer green run is left for its own release; a committed
or live submission is a no-op, so reruns are safe.
- submit refuses to replace a committed submission still on its way live
(an earlier release); msstore publish would delete it.
- complete's Reconcile step now receives the MS_STORE_* credentials; it had
none, so the success path silently skipped the Store.
- A failed or unreadable Store step stays a warning: the release publishes.
Windows CI on #141651:
- footguns lint: Microsoft.VCToolsVersion.default.txt is read with
'utf-8', which keeps a BOM in the version string. Use 'utf-8-sig'
(and write a BOM in the test fixture, as Visual Studio does).
- Windows-only test: locate_command joins the known dir with mixed
separators (".../Installer\vswhere.exe"), so the stub's string
comparison never matched, vswhere looked absent, and both calls fell
back to a random partition. Compare as paths.
Review (#141651): _msvc_identity hand-wrote the vswhere.exe install path,
which is the known-path table the hermes_platform rule keeps in one place.
Add visual_studio_installer_dirs() to resolver/known_dirs.py and locate
vswhere with locate_command (PATH first, then the installer directory).
Return None explicitly when vswhere reports no installations, before selecting
the newest instance.
Warn on stderr with the reason whenever MSVC identity detection returns None:
missing vswhere, no matching installations, or a failed probe/version read.
test_script_killed_before_publishing_the_delegate_runs_no_update killed the
hand-off script, slept a fixed 3 s and then asked tasklist whether the
suspended update child was gone. The child dies through its job: the script
held the only handle to a KILL_ON_JOB_CLOSE job, and the kernel terminates
the members when the dying process's handle table is torn down. That
termination is asynchronous to the script's own exit, so on a loaded
Windows runner the 3 s guess loses and tasklist still lists the child.
Open a SYNCHRONIZE handle on the child while it lives (which also pins the
pid against reuse) and wait for it to be signalled, bounded at 60 s. The
'ran' check moves after the wait: once the child has exited it can never
write the marker file, so the assertion is final instead of a snapshot.
node-deps runs npm ci on every prepare (the receipt never matched across
runs in any sampled job), and npm ci deletes node_modules first. The
restored trees were GBs of tar/untar for nothing; npm's content cache
(npm/_cacache) is what lets npm ci skip downloads. The workspace path
validation only existed to guard those paths, so it goes too.
Two caches were restored every run and then never read.
uv's native-wheel partition (native_cache_path) needs a toolchain identity
to be stable. win32-x64 never runs VsDevCmd, so VCToolsVersion is unset and
the identity fell back to a random uuid: every run compiled cryptography,
obstore, davey and friends from source into a fresh partition (all 6
sampled x64 runs had a new partition). Identify MSVC the way rustc's cc
crate finds it, through vswhere and the default VCToolsVersion file.
vcpkg's default binary cache lives under LOCALAPPDATA, which the desktop
preparation moves into the job-local home, so OpenSSL rebuilt every arm64
run ("Restored 0 package(s)", ~3 min). Point VCPKG_DEFAULT_BINARY_CACHE at
native/vcpkg-archives, inside the transported native domain.
posix.sh and windows.ps1 appended "Run hermes gateway restart" whenever
the gateway_restart step was owed. Since this PR, that step can be owed
for a serve/dashboard unit alone (a system hermes-dashboard the updater had
no root for), and the hint then sends the user to a command that never
touches that unit. Both hand-offs now drop the hint only when the owed
reason is serve/dashboard-only: it starts with "serve/dashboard" and does
not mention `hermes gateway restart`. Every other gateway_restart reason
keeps the hint, including the raw exception text update_finish owes when
the restart phase itself crashes and reasons from older CLIs. A
dashboard-only owed restart points to the update log, which names the
unit's own command. The step id and owed-step parsing are unchanged.
Fixes#141071
Refs #141021
hermes_constants.mkdir_under_hermes_home and update_host_obligation import
hermes_dir_acl, so the takeover preparation and the fresh-install acceptance
snapshots (hand-picked root modules, not the whole tree) died with
"No module named 'hermes_dir_acl'". The product ships every root *.py
(setup.py _root_py_modules), so only these fixtures needed the file.
Refs #134889 (W8)
Refs #140337
Verified entries come from the company that makes what the plugin integrates,
confirmed by the Nous team. Only maintainers set official/verified (CODEOWNERS
review; no CI check inspects the value). Tier order everywhere: official,
verified, community. Desktop's installed-plugin pill now reads the same
catalogInstall.tier map as the install card, dropping the duplicate
skills.plugins.tierOfficial/tierCommunity keys.
stage_prerequisites sent every Mac whose git_runs failed into
install_macos_git, and git_runs is also false when git is missing from
PATH. So a Mac with no git got the Command Line Tools message instead of
"git is required", contradicting the "no git on PATH: unchanged" row and
failing test_real_install_sh_ladder_leaves_only_closed_tokens on macOS.
Gate the macOS path on git being on PATH.
That ladder test was gated with a bare skipif, so the macOS lane never
imported it; give it platforms("posix") per tests/AGENTS.md.
Also from review:
- Check the developer tools' git before Homebrew, so a git that fails
with the tools present (unaccepted Xcode license, broken ~/.gitconfig)
reports git's own error instead of running brew install git first.
- Log why xcode-select --install failed (e.g. no GUI session over SSH)
before the wait, instead of discarding its output.
- Parametrize the dialog-then-continue and fail-fast cases.
A macOS upgrade can leave /Library/Developer/CommandLineTools behind with its contents gone; xcode-select -p still names it, so the folder-exists check skipped the install dialog for the most common broken-git Mac. Require $dev/usr/bin/git instead, which both the CLT folder and Xcode's Contents/Developer hold.
When xcode-select --install refuses over such a leftover folder, no dialog opens: fail at once with the reinstall remedy instead of waiting 15 minutes.
When xcode-select -p names an existing developer directory (e.g. Xcode installed, license never accepted), no install dialog will open: skip the dialog and the wait and fail with git's own error instead of waiting 15 minutes and suggesting the wrong fix.
Hermes-Setup drives stages with --non-interactive --json; it has no stage timeout, a Cancel button, and streams log lines as progress, so wait for the Command Line Tools there too. Only a plain headless --non-interactive run fails fast.
Run Homebrew with HOMEBREW_NO_AUTO_UPDATE=1 (auto-update runs the broken git and can take minutes) through run_logged instead of discarding its output.
Move the prerequisite tests to tests/scripts/install/ and drop the uname stub: macOS cases are platforms("macos"), the Linux case platforms("linux"). The probe PATH holds only system dirs so a host Homebrew never runs.
Review (unsupportedpastels) on #139311: a rerun over a main checkout moved
it to v0.22.2, which was cut on release/0.22.1 and diverged from main, so
the move crossed the gateway runtime cutover backwards without the downgrade
guard `hermes update` runs before the same move (update_downgrade_guard
refuses when a state.db holds session_admissions rows). The linear test
history could not show it: there, "does not contain the release" always
meant "behind it".
A rerun now moves only when the release descends from HEAD. A checkout that
contains it, has diverged from it, or cannot tell (shallow) stays, and
`hermes update` decides that move with its guard; an explicit
--channel stable is saved and left to `hermes update` the same way. The
stable-default tests gain the release-branch shape (v0.22.2's): a rerun
from main's tip keeps HEAD, a rerun from before the fork still moves.
Review (jquesnelle, Greptile) on #139311: when the release lookup succeeded
but its tag could not be fetched, a fresh install had already cloned with
--no-checkout, the fallback cleared the release, and no step wrote the files:
the installer published an empty checkout and exited 0. A rerun in the same
state jumped to main's tip, against the PR's own keep-the-checkout rule.
Stable that applies but cannot be reached now fails a fresh install with a
retryable message (naming --channel main), and keeps a rerun's checkout,
unless stable was asked for by name, which fails. Lookup gains a third
source, origin's highest vX.Y.Z tag over the same transport the clone uses
(the publication run pushes final tags), so a lookup failure with git
working is no longer a separate case. The pytest upgrade harness, whose
origin carries no release tags, pins its fresh installs to main the way the
install E2E harnesses already do.
`hermes update` has followed the stable channel for official source checkouts
since #135847, but install.sh and install.ps1 still cloned main's tip, so every
new user (CLI one-liners and Hermes-Setup, which bakes `-Branch main`) started
on unreleased code and only reached stable at the next release.
For the official repository's main line, with no explicit --commit:
- a fresh install resolves the latest published vX.Y.Z release (GitHub API,
then the github.com /releases/latest redirect, which needs no API quota),
fetches its tag (releases can sit on release/* branches) and checks it out
detached, where `hermes update` puts a stable checkout;
- a rerun with no recorded channel moves forward to that release and keeps a
checkout that already contains it (forward-only, like the update default);
if the lookup fails a rerun keeps its checkout instead of jumping to main;
- `--channel main` / `-Channel main` / HERMES_INSTALL_CHANNEL=main installs
main's tip and saves the choice with `hermes update --set-channel`, so later
updates keep following main. Forks, mirrors and other branches are unchanged.
The install E2E harnesses that serve unreleased commits from a tagged local
mirror pin themselves to main through the same variable.
The probe runs a cold Windows PowerShell 5.1 that spawns git and Python
children, twice, each capped at 30 s. On a loaded Windows arm64 runner
that cap expired with the probe healthy (TimeoutExpired, no assertion
reached). The cap is a liveness bound, not a speed assertion: 180 s.
Stable builds each Windows arch in its own desktop-bundled-release call and
assembles the universal bundle in a third call that needs both. Each per-arch
call also ran that arch's MSIX install-and-chat smoke, so assembly waited for
the slower arch's smoke after its build: on rc.2-v0.22.2 the bundle sat behind
the arm64 smoke from 07:16 to 07:28.
A new windows-smokes-with-bundle input moves both MSIX smokes into the
win32-bundle call. The smokes already fetch the staged package from the R2
handoff, so they run there beside assembly. The per-arch calls then only
build, the bundle call's phase result requires both smokes, and the candidate
manifest records the Windows smoke results from the bundle call, so a failed
smoke still blocks the release. Callers that build everything in one call
(canary, channel, commit builds) leave the input off and are unchanged.
The release entrypoint and the stable gate both required the claimed commit
to be an ancestor of main. Both now accept an ancestor of main or of any
release/* branch, via one shared git query (stable.release_line_contains).
The entrypoint prunes its mirror of release/* so a deleted branch stops
admitting commits. The termux previous-package ancestry check follows the
same rule. Workflows still dispatch from main.
48 commits from main (e8c8e46471). One conflict: hermes_cli/runtime_provider_custom.py, where both sides
added the `config=` keyword to _get_named_custom_provider; main's signature (config + resolve_credentials)
is taken whole.
- The Windows build step passes AZURE_* empty when skip-signed is set.
after-pack and electron-builder sign whenever the vars exist, and the
Azure login is skipped with the signed lane, so a Store-only build with
the vars configured would sign without credentials.
- candidate_manifest records a smoke result only for calls whose package is
a target. `--without windows` runs the Windows candidate calls for the
Store bundle; their success is not a smoke, and counting it made assemble
refuse the manifest.
`windows` used to build the signed MSIX, the universal bundle and the Store
bundle together, so skipping the signed pipeline meant skipping the Store too.
Now `windows` is the signed pipeline and `windows-store` the unsigned Store
bundle; both default on and the shared per-arch builds only make what the
lanes ask for.
- lanes: new `windows-store` lane and derived `windows-build` (either lane),
which gates the shared candidates-win32-* jobs and handoffs.
- desktop-bundled-release: `skip-signed` builds only the Store variant and
needs no Azure login, signing, metadata, smoke or receipt; `skip-store`
builds and submits no Store bundle (replaces the old `store` input).
- manifest: a Store-only release has no package rows, only the Store bundle
file; assemble() now takes the claim's lanes and checks each Windows bundle
against its lane.
- claims record `lanesRevision: 2`. A lanes claim without it was cut when
`windows` also meant the Store bundle (rc.2-v0.22.1), so it reads as
windows + windows-store.
- release_artifacts: drop the unused `assemble` CLI command, which could not
name the lanes.
Two review findings on the lanes change:
- candidate-manifest judged only the desktop calls, and when a claim built
none (--only termux) it called require_success with an empty list and could
never finish. It now requires every candidate call the claim built, Termux
included, and judges smoke results only for the desktop calls that have one.
- `complete` rendered the shared releases/stable/index.html for any release
with bundles, so a windows-only release replaced the previous full release's
macOS links. admit now emits a derived `desktop-channel` flag and the
renderer takes --no-channel-page, leaving the tag page and release table
alone while the shared page waits for a release that serves the channel.
The skip and opt-in flags (--skip-bundles, --skip-tests, --include-macos-intel,
--include-termux) were four booleans in the claim, and every consumer
re-derived what they meant. A release is now a set of lanes, defined once in
scripts/releases/lanes.py: five build lanes (docker, windows, macos-arm64,
macos-intel, termux) and ten test lanes (ci, nix, docker-tests, pm-bundle,
windows-live, install-e2e, bootstrap-version, termux-checks, smoke, upgrade).
The default is every lane except macos-intel and termux.
release.py release --only windows,docker # the Store bundle + image, untested
release.py release --with macos-intel --without install-e2e
- The claim records `lanes`. A claim cut before lanes existed still parses:
skipBundles/skipTests/include* translate to the lane set that gated the
same jobs, because the sequencer reads every claim tag ever cut.
- `admit` emits `lanes` and `jobs` (a run flag per lane-controlled job). Each
job's `if` reads its own `jobs` entry and the gates derive their expected
skips from the same table, so the workflow and the gates cannot drift.
tests/ci/test_stable_release_graph.py evaluates every `if` against 37 lane
sets.
- A release may build any subset: the candidate manifest carries exactly the
claim's targets, and the final tag records null for the docker digest or the
manifest digest when the claim built neither.
- The protected desktop channel, feeds, APT index and upgrade baseline move
only when the claim built windows and macos-arm64. A partial set is staged,
verified and (with windows) submitted to the Store, and leaves the channels
on the previous release. A release that moves no channel is finished once
public.
- publish-bundles passes `store` so a claim without windows submits nothing
to the Microsoft Store.
Not exercised end to end: no real release was cut with a partial set.
375 commits from main (0e427d788d). 24 files conflicted; the resolutions that carry a decision:
- /initiate-setup: main removed the command (25bf722063). Its gateway rewrite, CLI handler and the
Desktop kickoff go with main; this branch's command-layer admission of it is dropped too
(gateway/session_commands.py _PROMPT_COMMANDS, its slash.exec test row, the parity doc row), and
the Desktop slash dispatcher loses the `hidden` option that only the kickoff used.
- Code this branch moved out of its old file, carrying main's edits to the moved copy:
first-contact notes (gateway/run_turn.py -> run_turn_prepare.py: no message/setup_command),
the TUI agent factory (tui_gateway/server.py -> agent_factory.py: onboarding toolsets at build,
setup_beat/set_theme lifecycle cards, no cli platform extras on a coding selection), the serve
lifespan (web_server.py -> web_server_app.py standalone_lifespan: first-install probe before the
state.db reconcile, onboarding due answer after the setup bootstrap), and the compression child
publish (the shared _publish_compression_child_on_conn reads the parent's model_config).
- agent/auxiliary_client.py: both import changes (_route_key from main; this branch's move of
_without_structured_output_format).
- Desktop: main's onboarding run (decideOnboarding, OnboardingEnd, card-waiting Send) replaces the
kickoff; this branch's prepared-submission slash path, request_id on session.create and
PreparedImageRecovery stay. Pool-limits imports are dropped (this branch removed the pool).
createBackendSessionForSend takes main's (preview, overrides) signature.
- ci.yaml detect job: main's `ref: inputs.commit` plus this branch's sparse checkout.
- package.json / lock: main's vitest 4.1.11 plus this branch's ws 8.21.1.
- Generated: gateway-contract.openrpc.json regenerated; parity doc regenerated.