Commit Graph

222 Commits

Author SHA1 Message Date
ethernet
e64351c8e7 fix(release): support the pre-stable development graph 2026-09-22 12:19:01 -04:00
ethernet
7e9d4239c6 fix(release): preserve reachable version identity in CI 2026-09-22 12:00:06 -04:00
ethernet
8da6c5f386 Merge branch 'ethie/release-machinery' into ethie/pm-clean
# Conflicts:
#	tests/ci/test_desktop_store_eligibility.py
2026-09-22 11:05:14 -04:00
ethernet
093c45325c fix(pm): keep platform detection out of bootstrap imports 2026-09-22 10:58:05 -04:00
ethernet
dcd678d6fd fix(release): close publication custody gaps 2026-09-22 02:19:38 -04:00
ethernet
75a13386fb fix: make macOS shell and mode contracts portable
The macOS runner uses Bash 3.2, which rejects parameter case conversion.
Normalize the GHCR owner with portable tr and verify mixed-case input.

The macOS runner can clear setgid from a directory when chmod applies 2770.
Compare scratch permissions with the native chmod result while preserving all
bits the host accepts.

Tests: scripts/run_tests.sh tests/scripts/test_termux_build_driver.py tests/test_scratch_dir.py
Shell: bash -n scripts/termux/build_builder_image.sh
2026-09-22 00:09:09 -04:00
ethernet
95e03fd701 fix(release): preserve stable ordering through publication 2026-09-21 23:36:19 -04:00
ethernet
0d02aee1b9 feat(release): tag completed channel and commit builds 2026-09-21 23:14:39 -04:00
ethernet
1201949d30 feat(release): sequence stable publication oldest first 2026-09-21 23:03:04 -04:00
ethernet
dd72571178 feat(release): finalize stable claims into versioned builds 2026-09-21 22:39:16 -04:00
ethernet
9642882bab fix(release): create stable drafts before dispatch 2026-09-21 22:04:34 -04:00
ethernet
14b1e7220b feat(release): derive canaries from stable build identity 2026-09-21 22:02:46 -04:00
ethernet
75dacfcba3 feat(release): admit a stable release from its claim tag
main carries 0.0.0, so comparing the tag against pyproject.toml refuses
every release. Admission reads the version from the -rc claim and checks
only that the commit is on main.
2026-09-21 20:47:14 -04:00
ethernet
9cc319f9a1 feat(version): main carries 0.0.0 and dev distance counts from release tags
The committed version is a placeholder now; a build stamps the real one.
Dev installs report their distance from the highest reachable release tag,
so an -rc claim sitting on the same commit never becomes the version.
2026-09-21 20:41:59 -04:00
ethernet
092be885a3 feat(release): stamp a build tree instead of committing a version bump
The version lives in the ref now, so the mirrors writer takes a tree and
rewrites a copy. The checkout it was invoked from stays at the placeholder,
and the bootstrap installer's Cargo.lock finally moves with the rest.
2026-09-21 20:38:04 -04:00
ethernet
7e63cd3a71 feat(release): claim a version and dispatch the gate from one command
`release.py release --commit --bump` derives the next version, pushes an
annotated -rc tag as exactly that ref, and treats a dispatch that never
starts as an error. A commit behind an outstanding claim is refused, and
abandon deletes the draft while the claim tag stays as the burn mark.
2026-09-21 20:36:21 -04:00
ethernet
e10ce050af feat(release): derive the next version from the release family
The family is the published stable head, then outstanding -rc claims, then
the seed 0.21.4 when both are empty, so the first release is 0.21.5. CalVer
tags are excluded rather than sorted: v2026.9.21 is a valid three-component
version and would win every max(). A +canary identity compares equal to its
stable; the legacy -canary prerelease still sorts before its stable so feeds
published before the migration keep their order.
2026-09-21 20:22:35 -04:00
ethernet
5f1d3294b9 Merge branch 'rev/tests-infra' into ethie/pm-clean 2026-09-21 19:52:58 -04:00
ethernet
843a0095ec Merge branch 'rev/termux' into ethie/pm-clean 2026-09-21 19:52:58 -04:00
ethernet
59624b7ace tests: replace every bare host skipif with platforms()
The OS lanes are marker-driven: list_os_marked_tests.py picks the files
a lane imports from their platforms() specs and the lane selects with
-m platforms. A test gated with skipif(sys.platform != "win32") is
therefore never imported on the Windows lane and skipped everywhere else
— it runs on no host. skipif(sys.platform == "win32") tests were merely
invisible to the lane bookkeeping, but the rule the tree now follows is
one host marker, never a bare skipif.

Mechanical mapping, semantics preserved: skip-on-Windows → "posix",
skip-off-Windows → "windows", skip-off-Linux → "linux", skip-on-macOS →
"not macos". The former skip reasons stay as trailing comments. A
non-host condition (os.geteuid() == 0) stays a separate skipif beside
the marker, spelled getattr(os, "geteuid", ...) so the decorator still
imports on Windows.

Where the conversion would stack two platforms() marks on one test (the
conftest rejects that at collection) the narrower mark wins:
- test_update_wedged_gateway: the class is already platforms("linux");
  its per-test "needs UNIX sockets" marks were redundant and are gone.
- test_process_registry.TestSystemdCgroupIsolation: the class-level
  skip-on-Windows moves onto the 11 methods that had no host mark; the
  11 platforms("linux") methods keep theirs.
- test_file_ops_single_roundtrip: the two fifo tests drop their
  platforms("linux") in favour of the module's "posix" (mkfifo exists on
  macOS; both tests already skip when it does not).
- test_linux_desktop_entry / test_gateway_job_teardown_live: duplicate
  or wider marks removed.
2026-09-21 19:18:15 -04:00
ethernet
4fda664b0f tests: resolve platforms() specs in the lane selector and runner note
scripts/ci/list_os_marked_tests.py matched the literal lane word inside a
platforms() string, so 80 files gated platforms("posix") never reached the
macOS lane ("posix = linux or macOS" was false for CI), and "any" files
reached none. The selector now resolves specs the way the conftest gate
does (posix ⊇ linux+macos, any ⊇ all, "not X" admits the rest) and only
looks inside mark.platforms(...) calls, dropping a false positive whose
only "macos" was inside a generated-file string.

scripts/run_tests_parallel.py still grepped the retired linux_only/
macos_only/windows_only names, so its "N files SKIPPED on this host" note
had been silent since the migration. It now shares the selector's
resolver and names the spec and the lane(s) it runs on.

Restore the platforms("windows") mark that
test_suppress_platform_ver_console_stubs_syscmd_ver lost in the
windows_only migration (its docstring still declared it); it passed
vacuously on Linux and was deselected on the Windows lane.
2026-09-21 18:53:24 -04:00
ethernet
3c53fbdc8b install.ps1: emit the -Json failure frame when Fail ends a stage
Fail ends the script with `exit 1`, which unwinds past the stage
dispatcher's try/catch, so the catch that frames failures as JSON never
ran for the installer's own fatal errors: a `-Stage repository -Json`
run whose clone failed printed the reason via Write-Host only and put
zero frames on stdout (verified on Windows: 0 frames before, 1 after).
Only thrown exceptions were framed.

Fail now emits the failure frame itself when running under -Stage -Json,
so every fatal path yields exactly one frame carrying the original
reason, matching install.sh's EXIT-trap framing.
2026-09-21 18:44:30 -04:00
ethernet
d7466aa3ba release: one strict stable-tag grammar shared by every selector
scripts/releases/semver.STABLE_TAG accepted any-width majors, so
is_valid_version('2026.9.21') was True and docker.require_stable_tag /
stable.py / release.py admitted the legacy CalVer tags that
hermes_cli.source_releases and get_last_tag() already refused. A
workflow_call carrying GitHub's current 'latest' (v2026.9.21) would have
passed the docker publish gate.

hermes_cli.update_channel already owns the canary tag shape; it now owns
STABLE_TAG_RE too (three-digit major cap, no leading zeros, no suffix)
and every stable selector imports it. release.py drops its private
_SEMVER_TAG_RE + CalVer exclusion pair, which the capped major makes
redundant.
2026-09-21 18:42:01 -04:00
ethernet
5e8fbd4919 install.sh: refuse Termux hosts and point at the APT package
check_platform accepted Termux as plain Linux, so `curl | bash` on a
phone walked the source-install ladder: a glibc uv, a lock whose CPython
is the bundled bionic build with no Android wheels, and on-device sdist
builds. The signed APT package is the only supported Termux shape, so
detect Termux the way the runtime does (TERMUX_VERSION or the com.termux
PREFIX) and stop before any stage with `pkg install hermes-agent` and
the setup docs URL. --json surfaces the reason in the stage frame.
2026-09-21 18:41:42 -04:00
ethernet
04bfa58d47 install.sh: refuse to clone over an unrelated destination
The clone publication step did `mv <staged>/tree "$INSTALL_DIR"`. When
INSTALL_DIR already existed as a non-git directory, mv moved the
checkout INSIDE it as INSTALL_DIR/tree and the stage reported success,
leaving later stages to read pm/lock.json from a directory that holds
the user's files instead of a checkout. An existing file made mv fail
with a generic "cannot publish" error.

Before staging the clone, refuse a destination that exists and is not a
Hermes checkout (non-empty dir, file, or symlink) and say what to do; an
empty directory is taken over so the checkout lands AT the path.
2026-09-21 18:40:30 -04:00
ethernet
6a6771e5e7 termux: stamp the deb as a self-contained runtime, not a bundled payload
build_deb.sh wrote HERMES_DESKTOP_VARIANT=bundled, so the Termux stamp
carried payload=bundled and every 'bundled' reader treated the tree as
the repo/ of an Electron payload. hermes uninstall --data then called
resolve_bundle_layout on it and rejected the plan: the deb has no
enclosing app to protect, so data-only removal was impossible on Termux.

Add a 'runtime' variant to write_install_stamp.py for a sealed CLI
runtime with no desktop app around it. It stays sealed for the update
gate and channel identity (source_check, update_channel accept it next
to bundled/light) while is_bundled_payload keeps answering False, so
cleanup planning protects the APT-owned package tree the ordinary way
and never asks where the app is.
2026-09-21 18:37:21 -04:00
ethernet
14b3232cbd fix(test-runner): key the scratch root by user, not a shared literal
The runner's per-run temp roots live under a fixed `/var/tmp/hermes-pytest`, chosen for
real reasons (disk-backed, not hidden, short enough for AF_UNIX sun_path). But a fixed
literal in a world-writable sticky dir belongs to whoever creates it first: a root-owned
root — a container or system-service run — makes every later `makedirs`/`mkdtemp` there
fail with EPERM for every other user on the host, with no way back that does not need
root. That is exactly what happened on luna: /var/tmp/hermes-pytest is root:root 755, so
every local suite run by the login user died at `runner crashed: PermissionError(13)`
before collecting a single test.

Key the name by uid (with the same non-/var/tmp fallback), so no run can be blocked by
another user's leftovers. Invariant test: two uids never share a scratch root, and the
root is created under the expected parent.
2026-09-21 17:24:29 -04:00
ethernet
b265e87678 test: synchronize observable state and reuse prepared PM fixtures 2026-09-21 14:49:05 -04:00
ethernet
d2c60dbdf8 fix(test-runner): preserve PATHEXT for native PowerShell children 2026-09-21 14:03:12 -04:00
ethernet
9b3e84d909 test: align upstream fixtures with PM and isolated service ownership 2026-09-21 13:26:17 -04:00
ethernet
bc2721f17b test: migrate upstream host gates to platforms markers 2026-09-21 13:18:39 -04:00
ethernet
77fad51181 test: pwsh probe reports the launcher's PE subsystem and the base interpreter's exit before the stamp step
Under both pwsh 7.6 and Windows PowerShell 5.1 on the lane `& python.exe ...` returns with
$LASTEXITCODE unset and no side effect while Start-Process sees a real exit code — the shape
PowerShell gives a non-console image. Narrate what the venv launcher is and whether the base
interpreter it points at behaves, so the next red run names the cause.
2026-09-21 11:47:17 -04:00
ethernet
f78f2b14f9 fix: round-1 CI backlog (Windows lane probe, obligation test, generated contract)
- test_source_build_env: the pwsh probe dumps the env through a script file, not
  `-c 'import json, ...'` — pwsh 7.6 on the Windows lane re-quotes native argv and the
  venv launcher receives a truncated -c body (`import` → SyntaxError, no stamp). Drop the
  spawn-path diagnostics that answered that question. On nt prefer Windows PowerShell,
  the shell install-e2e-windows-run.yml actually drives the asset with.
- test_update_fleet_completion: the obligation is host-scoped now; assert through
  `_fleet_restart_obligation_armed()`, not the legacy per-home marker path.
- gateway-contract.generated.ts: regenerated (main's `npm run fix` sweep stripped the
  eslint-disable line the generator emits).
2026-09-21 11:36:13 -04:00
ethernet
7b61a92e28 test: pwsh probe compares native call, Start-Process and cmd.exe for the python child
On the Windows lane `& python ...` under pwsh 7.6.5 returns $?=True with $LASTEXITCODE unset and
no side effect: the child is not started. Exercise the three spawn paths so the next run says which
of them reach the interpreter.
2026-09-21 09:57:52 -04:00
ethernet
00d691ccaa test: pwsh probe reports whether the python child even starts
The Windows lane runs the scriptblock (every narration line prints) but $LASTEXITCODE stays
unset after `& $env:PROBE_PYTHON`: the native call itself produced nothing. Log path existence,
run a bare child first, and print $? and the stamp presence right after the call.
2026-09-21 09:24:12 -04:00
ethernet
744ffcfe36 test: launchd survive-collection execs the osascript child; pwsh probe traps and narrates
- test_source_launcher_publication: launchd ProgramArguments are `/usr/bin/osascript -e` since #71206;
  on Linux exec the child argv the script would spawn (minus the shell redirection tail).
- test_source_build_env: pwsh 7.6.5 on the Windows lane exits 0 without running the child; the probe
  now traps any terminating error (exit 97 + stack) and narrates each step to stderr.
2026-09-21 08:52:49 -04:00
ethernet
61ebb55e7e test: source-build-env probe reports the PowerShell it drove
The Windows lane runs the -File probe to exit 0 with no stamp and no output. Log the shell,
PowerShell version and inputs to stderr, close stdin, and include the return code so the next
red run explains itself.
2026-09-21 08:10:09 -04:00
ethernet
d0c91f47f1 fix: round-10 — PowerShell probe via -File; enable-grant test follows #64228; stage-hold fixture requests access
- test_source_build_env: Windows PowerShell 5.1 runs a multi-line -Command argument only up to
  the first line break and exits 0; the probe is a -File script now (the child never ran, hence
  the empty stamp).
- test_plugins_cmd_enable_disable_nested: `enable` no longer prompts for or writes an undeclared
  allow_tool_override grant (09bcf17801); the test asserts that and uses --no-allow-tool-override
  for the persisted False.
- test_stage_only: the directory hold opens with FILE_LIST_DIRECTORY, not access 0 — a
  zero-access handle does not oppose the rename the repin performs.
2026-09-21 07:31:22 -04:00
ethernet
9ee4397408 fix: round-9 Windows lane — retry argv keeps --force; mint fixture carries venv_sync; stamp probe reports the child
- desktop-update/windows.ps1: the legacy-install retry re-sends the identical request
  (--force included); the contract test compares both attempts.
- test_mint_launchers: the bootstrap imports hermes_cli.venv_sync/steward before
  prepare_launch can return early for a fixture repo; copy them into the tree.
- test_source_build_env: when the pwsh child writes no stamp, fail with the child's
  stdout/stderr instead of a bare FileNotFoundError (the Windows lane hides the cause).
2026-09-21 06:48:59 -04:00
ethernet
f7561667c7 fix: round-8 CI backlog (Windows-only lane)
- tests/conftest.py: `real_bash` fixture — the Windows runners resolve `bash` to System32's
  WSL launcher (UTF-16 "no installed distributions", exit 1); prefer Git for Windows'. Used
  by the setup-pin, install stage-frame and source-launcher shell tests.
- source-build-env.ps1: Test-Path before Remove-Item — under $ErrorActionPreference='Stop'
  a missing identity variable aborted the try block before the child ran (Windows PS 5.1
  raised where pwsh on Unix did not).
- desktop-update/windows.ps1: `--force` precedes the target arguments (the hand-off contract
  test reads argv in that order).
- test_install_ps1_desktop_stage: assert the current contract — the shared completion tail
  (source_completion.py --desktop) builds the products and -IncludeDesktop selects the desktop
  product inside `products` rather than adding a stage. The test predated the completion-tail
  refactor and had been red on the Windows lane since.
- test_windows_native_support: the restart watcher argv is `runtime_command` shaped
  ([python, -I, -c, bootstrap, pid, delay, ...]).
- test_mint_launchers: create the fixture repo's pm/ dir before copying pm/environments.py.
- test_browser_use_pm: console-script launchers report sys.argv[0] without `.exe`.
- test_update_stale_gateway_yield (from main): `_verify_fleet_after_update` has no
  `node_failures` here (PM owns node).
2026-09-21 06:03:05 -04:00
ethernet
1d602a04e3 fix: round-6 CI backlog (32 python, docker arm64, win32-arm64, windows lane, desktop lint)
- pm.environment: the `--no-install-package <name>` root read parses [project].name without
  tomllib on the pre-3.11 bootstrap python (Docker arm64 stage_runtime). Both parsers proven
  to agree on the real pyproject.
- windows-build-deps.ps1 / run_tests.sh: with DISTUTILS_USE_SDK, setuptools takes link.exe
  from PATH; under a bash-hosted step Git for Windows' coreutils `link` shadowed MSVC's.
  The MSVC linker directory now leads PATH (cl.exe was already found — this was the next
  failure in the ruamel-yaml-clib build).
- tests/install/e2e-assets/source-build-env.ps1: clear the identity variables through the
  env: drive — [Environment]::SetEnvironmentVariable(..., 'Process') on .NET/Unix does not
  reach spawned children, so the stamp child still saw GITHUB_SHA. Red→green under nix pwsh.
- old-updater surface: warm_agent_browser_npx_cache is a permanent def in both facade and
  sibling (the frozen surface names both); its compat pointer is retired, and the shims test's
  __module__ check holds.
- tests re-seamed / de-faked: import guard tests use the probe_root fixture (CI has no
  editable finder), the takeover child tree gets a hermes_constants stub, posix.sh hand-off
  test pins HERMES_HOME (our script honours the ambient one), the two Windows-layout
  PYTHONPATH tests are platforms("windows") (they fake `Lib/site-packages` on Linux; pm's
  site_packages() is host-correct), setup-pin test picks Git bash over System32's WSL stub,
  expose_cli's Windows test asserts the installer-convention convergence (the branch retired
  "windows-installer-owned"), plugin-manifest satisfied-dep fixture uses a core dep (pyyaml is
  gone), housekeeping test yaml imports go through hermes_yaml.
- apps/desktop main.ts: three imports restored in round 4 whose users main removed.
2026-09-21 03:44:00 -04:00
ethernet
f3b1399211 fix: round-5 CI backlog after the 779-commit main merge
Merge fallout (my resolution errors, all caught by CI):
- hermes_cli/backup.py + gateway.py: `theirs` on those hunks re-imported clusters HEAD had
  already moved to backup_restore.py / kept in the facade. backup.py loses the 349-line
  duplicate (main's #110179 fix is ported into backup_restore._import_db_member); the
  systemd service-unit cluster returns to gateway.py (PM's _prepare_service_launcher /
  _pm_managed_node_dirs / _systemd_command have no home in main's extraction) with main's
  utf-8-sig read. gateway_service_unit.py is dropped.
- gateway/run.py: main's plugin-update chore is not profile-scoped (the housekeeping
  ordering test pins the scope/drain sequence).
- pyproject + 30 test files: `import yaml` -> `import hermes_yaml as yaml` (pm-clean has no
  pyyaml); gateway/config._bundled_platform_manifest_name reads through hermes_yaml.
- tests re-seamed onto pm-clean's shape: residency admission (installed_engine),
  supervisor child env (binary is a constructor argument), update import guard
  (update_cmd_deps is gone; our probe already scrubs PYTHONPATH — both #115032 invariants
  pass), shallow-count git responses (stash path asks `status --porcelain -z`); dropped
  tests for retired code (_run_node_bootstrap/_ensure_tui_node, Windows resume demotion).
- tests/tools/test_local_env_blocklist.py: restore the two helpers the suite-reduction
  commit dropped and the blocklist import.

Real fixes:
- pm: classify_uv_failure/ResolutionConflict move beside the uv runner (pm.environment,
  stdlib-only). pm.workspace imports tomllib at module level and cannot load on the 3.10
  bootstrap python that streams uv output in the Docker arm64 image.
- tools/browser_tool.warm_agent_browser_npx_cache: back as a permanent definition — it is on
  the frozen old-updater surface, and the revert-scheduled compat pointer does not count.
- hermes_cli/memory_setup: the dashboard's pip row uses pm.environments.
  running_from_selected_environment for installed vs restart_required.
- scripts/windows-build-deps.ps1: export DISTUTILS_USE_SDK/MSSdk so setuptools trusts the
  primed MSVC environment instead of asking vswhere (`env -i` test runner on win32-arm64
  compiling ruamel-yaml-clib); run_tests.sh forwards them.
- tests/pm/test_windows_build_deps.py: start the protocol test from a parent env without the
  toolchain variables the runner job already exports.
- tests/conftest.py scrubs HERMES_BUNDLED_PLUGINS (Nix-wrapped hermes on the dev host);
  tests/home_io_guard.py treats sys.path site-packages under the real home as the
  interpreter's installation (PM-activated developer shell).
- tests-js: four `curly` lint errors from main's new scripts.
2026-09-21 02:47:48 -04:00
ethernet
f130c79b9a ci: bash shebangs in two tests from main; regenerate the google-workspace skill doc 2026-09-21 01:13:59 -04:00
ethernet
9f2ba1b74d merge origin/main (779 commits) into ethie/pm-clean
Branch semantics kept where main and PM disagree: update_cmd_deps.py,
constraints-termux.txt, the Electron update-api-check module and the
post-swap hand-off test stay deleted; the pending-fleet-restart catch-up
and the local_runtime tag/download ladder stay retired (PM owns engines).

Ported from main onto the branch's shape: profile_scoped_chore for the
auto-archive and plugin-update housekeeping chores, the local-runtime
cross-process boot lock and residency cap, the checkpoint tmp_pack sweep,
the cua daemon-liveness status probe, the remote-served Desktop update
flag (posix.sh / windows.ps1), sign-in for env-pinned remote gateways
(urlDisabled on RemoteSetupFields), the uvloop extra split (uvicorn
without [standard]), and the umask-scoping spawn test.

uv.lock regenerated with pm.build_env --lock-only; new utf-8 reads from
main switched to utf-8-sig (check-windows-footguns).
2026-09-21 00:58:39 -04:00
teknium1
10c273813d feat(plugin-catalog): screenshots and readme entry fields for the plugin pages
Two optional, submitter-controlled fields on a catalog entry feed the
entry's own page at /docs/plugins/<name>:

- `screenshots:` — up to 6 https URLs on GitHub hosts (same host rule as
  `image`, so the site never fetches from third-party hosts and a raw URL
  pinned to the sha is as immutable as the code).
- `readme: true` — the docs build renders the README from the PINNED
  commit (raw.githubusercontent.com / gitlab.com raw at <sha>), never live
  content, so what a user reads is what the reviewer read.

Validator rejects malformed values (admission), the loader parses and
drops off-host screenshots with a warning (client), and the extractor
emits `screenshots`, `readme`, `readmeUrl` and a `maintainerSlug` for the
author pages. Tests on all three.
2026-09-20 20:43:13 -07:00
liuhao1024
04845f5f3e fix(desktop): skip the local gateway restart on update when the Desktop is remote-served
A Desktop whose active connection is remote (SSH/remote/cloud, including the
registry primary) owns no local messaging gateway, yet the update hand-off
always ran `hermes update --gateway`. On hosts where launchd/service recovery
fails, the updater falls back to a detached local `gateway run --replace`;
with the same Telegram bot token as the remote VPS gateway, the two processes
compete for getUpdates and Telegram rejects one consumer, taking the
production bot offline (#117529).

Pass the ownership down the hand-off: globalRemoteActive() now adds
--no-gateway (posix) / -NoGateway (windows) when the Desktop is remote-served,
and both orchestrators drop --gateway from every update invocation (initial +
retry). The local-ownership default keeps --gateway exactly as before.
2026-09-20 19:30:16 -07:00
fangliquan
6d92f105fe test(install): preserve bash path in probe regression 2026-09-20 15:22:22 -07:00
fangliquan
075158134c test(install): run dependency probe regression on Linux 2026-09-20 15:22:22 -07:00
fangliquan
8c5a5deb79 fix(install): probe the command link directory for dependencies 2026-09-20 15:22:22 -07:00
teknium1
11975e61db test: trim --files-from coverage to two invariants
Keep the two tests that pin the user-facing contract (a file-backed
list bypasses discovery; `-` reads stdin). The mutual-exclusion and
unreadable-path error tests exercised argparse plumbing already covered
by the runner's flag-routing contracts and pushed the suite past the
salvage bar of two invariant tests per fix.
2026-09-20 10:51:40 -07:00