Conflicts:
- scripts/releases/stamping.py, tests/scripts/test_version_stamping.py:
took ethie/pm-clean. The release branch's side was only its base's copy
of "stamping a payload snapshot skips the bootstrap-installer check"
(8411fdb333, same patch-id as 8d34601f47 here); the install-stamp
refactor c13ea774e6 supersedes the rest.
- tests/ci/test_stable_release_graph.py: kept pm-clean's release-epoch
contract (no HERMES_RELEASE_EPOCH on termux-deb, version on docker and
nix only) and the release branch's per-group receipt wiring.
Semantic conflict: the dispatch log step (6e64e961d8) read
inputs.termux_only, which the jobs input replaced. It reads JOBS now, and a
dispatch that selects only some groups has no release.py replay, as a
termux-only one had none before.
Runtime identity resolved through hermes_cli.__version__ (a static 0.0.0
on source installs, rewritten by release stamping) leaked v0.0.0 into
About, /api/health, User-Agents, and plugin compat, and source updates
showed "couldn't reach update server" because identity and channel
authority disagreed with the checkout.
Now: get_version_info() resolves install stamp -> live git -> unknown,
never pyproject metadata, never a package constant. Source checkouts
derive identity from their reachable release tag; the completion tail of
every successful install/update/historical takeover atomically rewrites
install-stamp.json with that identity; a stale source stamp whose commit
no longer matches HEAD defers to live git. ACP/TUI use derived_version
for display and base_version for protocol fields; all ~44 runtime
__version__ consumers migrated; hermes_cli.__version__ and generated
_version.py are gone; release stamping only touches the native manifests
external builders consume (nix/tauri/cargo) and passes release identity
straight into write_install_stamp.py; pyproject.toml stays inert 0.0.0.
Desktop no longer synthesizes a competing install-stamp.json: the
checkout owns its stamp, and desktop-bootstrap classification keys on
the bootstrap-complete marker. verify-bootstrap-version-stamp.py now
cross-checks the checkout's stamp (baseVersion + commit == HEAD).
Validation: 31-file focused suite green (version identity, stamping,
adoption, providers, gateway, acp/tui runtime identity, api server via
extras env, release graph); desktop tsc + 25 vitest green; real-repo
probe: base=unknown derived=git.0635606.dirty source=git on this
checkout; clean-env imports resolve entirely from this tree; windows
footgun + compat-pointer scans clean.
The pool upload is immutable with a one-year cache header, so a recut
of the same version must not reuse a pool key. The candidate phase now
prefixes the pool path (and the Packages Filename field) with the
attempt ref; without --pool-subdir the layout is unchanged.
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
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.
- check_no_tmp_literals: resolve scratch via tempfile/os.tmpdir; the termux
container mount point is one marked variable per script
- ruff TID251: desktop E2E fixtures may reach PM internals like tests do;
the pm.runtime_stage ban message no longer names a module that never existed
- auth_codex: build the capped httpx stream subclass on first use so importing
hermes_cli.auth_codex no longer forces httpx (the lazy proxy in auth_constants
was defeated by a module-scope base class; broke lanes without httpx)
- desktop-smoke: launchApp is a parameter; the bundle-env test substitutes a
refusing launcher instead of letting Playwright spawn a dying binary
(3 unhandled rejections failed the tests-js lane)
Root-level tests/ is for root-level modules; 19 files testing scripts/, 3 testing
pm/ and 5 testing hermes_cli/ move to the mirrored directory (workflow file lists
and cross-imports updated; path arithmetic bumped one level).
tests/pm gains a conftest with the isolated_machine_home fixture that seven
modules had copy-pasted verbatim.
The canary tag regex capped the major at 3 digits while the stable
shape had no cap. The repo's current stable line is CalVer (v2026.9.14),
so canary_tag_for_date cuts v2026.9.15-canary.<ts> — which
handoff.validate_identity then rejected as 'Invalid release handoff
identity', killing every tag-mode stage leg (Windows and Darwin) while
the same-minor stable staged fine.
Lift the cap in the canonical _CANARY_TAG_RE and the Termux mirror
regex; add the stable-vs-canary shape-parity invariant, proven red on
the base regexes.
The termux-main pool deletes a package's previous archive when it rebuilds, so
the runtime-lib pin table and the bionic lock rows rot without warning. The last
rotation broke a build on eight rows at once, and the stager's concurrent
downloads only surfaced whichever 404 won the race.
pm now owns the pin table it repairs: scripts/termux/runtime_libs.json moves to
pm/termux_runtime_libs.json, so pins live in pm/ and scripts consume them — the
direction scripts/ci/archive_inputs.py already reads pm/lock.json in.
`hermes pm update --termux` repins exactly the rows whose archive the pool has
replaced, hashing each replacement against the index SHA256 before writing
url/version/hash together. `--check` reports without writing and exits 1, so a
retired pin can fail a cheap preflight instead of a payload build.
It is a repair, not an update: an alive pin is never moved, because a repin can
land a rebuilt library under a moved soname and the table is the payload's
recursive DT_NEEDED closure. A pin whose package the pool has dropped outright
is reported and left alone. `--termux` runs alone — names/--target/--uv/--npm
are ignored, since repairing foreign-target pins is not a version resolution.
Verified: `pm update --termux --check` against the live pool reports 89 rows
served; a table deliberately pinned to the retired libiconv 1.18-1 repins to
1.19 with the pool's hash through the real network path; 19 new tests; the
tests/pm, tests/ci and tests/scripts suites have the same failure set as the
base commit (91 pre-existing Windows environment failures, none new).
pillow-heif's sdist build failed with RequiredDependencyException: the
container toolchain installs pillow's libs but not libheif, so
pkg-config libheif had nothing to answer with. Termux's libheif 1.23.4
ships the headers and libheif.pc directly (no -dev split).
Runtime: the built _pillow_heif extension DT_NEEDEDs libheif.so, which
pulls libx265/libde265/libaom/libx264/libc++_shared. All but libheif
and libde265 were already staged in runtime_libs.json (verified by
reading the .so's DT_NEEDED, not the deb Depends line, which also
lists gdk-pixbuf/glib/rav1e the linker never loads); pin those two.
Import gate: import pillow_heif never fails on a dead link -- its
__init__ swallows the _pillow_heif ImportError into a DeferredError
that fires only on first use. The gate now imports _pillow_heif
directly so a dlopen failure fails the build instead of the phone.
Commit builds have no update channel, even when installed through dpkg. Read the installed stamp before checking the refusal message and require exit code 2 for both artifact kinds.
Verified real CLI refusal paths, negative controls, and related tests: 15 passed. Ruff and type checks passed. Full deb validation was not rerun.
Reuse a matching ARM64 Visual Studio environment instead of growing its paths on each initialization. Preserve Rust and Cargo homes before isolating bundle state. Hand the private Termux assembly directory to its container user and restore host ownership afterward.
Verified targeted tests, real ARM64 SDK/OpenSSL execution, Rust compilation, and container facts publication. Full release packages were not rebuilt. The Windows x64 source-build timeout and the unchanged Termux linkage fixture failure remain unresolved.
Build TUI, web, desktop UI and runnable agent products from explicit
prepared inputs. Keep dependency preparation separate from distribution
packaging, with PM and native builds sharing uv environment construction.
Docker copies compiled frontend products instead of build dependencies.
Nix retains uv2nix environments and consumes shared assembly through store
references. Native desktop and Termux use the same launcher and frontend
contracts. Preserve the independent PM runtime and source imports from
arbitrary working directories.
Keep failed frontend builds from replacing the previous product, reject
source/output overlap, and bound dependency-process output draining.
Include hermes_wisdom in the Nix wheel: real CLI smoke tests exposed its
missing package declaration on the base revision too.
Verified focused Python and JavaScript suites, Docker build/runtime checks,
Nix desktop and CLI/ACP checks, standalone TUI and packaged Electron PTY,
and real full-Chromium interaction. Native signed installers, Android device
installation and the full repository suite remain CI verification.
Activation reaches plugin discovery before the application dependencies
exist. Give PM its own locked Python project and runtime so it can install
or repair the application without importing that dependency tree.
Keep PM outside the application workspace. A shared uv workspace resolves
the application graph and cannot provide this isolation. Route mutations
through an isolated worker and preserve transaction callbacks, cancellation,
custom package registrations, and correlated receipts.
Use the same runtime builder for source installs and packaged payloads.
Keep offline wheelhouse support in that builder. Nix builds the independent
PM lock as a separate derivation. Refuse lazy-disabled bootstrap before
installing tools or dependencies.
Move first-party YAML readers and writers to ruamel. Keep the application
lock's transitive PyYAML requirements for third-party packages.
Verification:
- Focused canonical Python suite: 177 passed, 1 host-gated skip.
- Electron backend probes: 12 passed. Electron typecheck passed.
- Both uv locks, scoped lint, Bash syntax, and whitespace checks passed.
- Cold activation, corrupt-app repair, offline staging, and relocation ran.
- Built and exercised the Nix PM runtime and standalone YAML merge script.
Six broader caller test files retain the same 24 failing test IDs as an
archive of HEAD. The existing real-home guard blocks those tests before
they can exercise the affected paths. No full-suite pass is claimed.
Native Windows signing and full Bionic package execution remain unverified.
Termux removes old package files, so a pinned URL and hash do not keep
build inputs available. Preserve the exact bytes without changing pins.
Archive every PM HTTP artifact and the Termux runtime inputs by SHA256.
CI reads R2 first. Only a missing object permits an upstream download,
hash verification, immutable upload, and verified readback. Seed the
actual toolchain and payload stores before their consumers run.
Use the public archive as a pinned fallback in PM, bootstrap installers,
and Nix fetchers. Keep network retries bounded and report attempted URLs.
Keep publication credentials in protected CI jobs, not installed clients.
Verification:
- 283 targeted tests passed; five POSIX tests skipped on Windows.
- All 87 preserved Termux packages passed local archive miss/hit checks.
- Native ARM64 ripgrep installed through the mirror and ran successfully.
- Wheel import, workflow lint, Python lint, shell syntax, and pins passed.
Live R2 publication, POSIX tests, and Nix builds remain for native CI.
The real-byte archive checks used loopback HTTP, not the live bucket.
Keep commit admission on the trusted workflow checkout and reject mixed
release inputs before loading repository code. Stage every built product
under its commit with receipt-bound summary links, never channel writes.
Build both Windows universal bundles through the existing SDK scripts.
Keep Store calendar versions separate from sideload app versions so zero-
major app versions remain packageable. Reject invalid arguments before
modifying bundles. Bind desktop and Termux versions to the source commit,
and record Termux cache provenance without labeling commits as tags.
Verification: 77 Python tests and 36 JS tests passed. Real makeappx packed
and unpacked disposable per-arch and universal packages. Seven official
workflow-expression checks, actionlint, syntax, lint and prose passed.
No signing, installed-app update, Android build, or remote dispatch ran.
Termux removed libffi 3.5.2 from its rolling package pool. The pinned URL
returns 404 and stops runtime-library staging. Pin the available 3.8.0
archive with its downloaded SHA-256, which matches the package index.
Name the package, version and URL when a download fails. Flush staging
progress so piped build logs preserve the failure order. Update the
bionic Python tests to follow the current supplier and binary layout.
Verified all 83 library/license archives and 265 staged shared libraries.
The complete staging step and its verified cache hit both returned 0.
The pinned Python ctypes extension finds its required libffi symbols.
Focused tests: 19 passed, 2 skipped. Bionic execution remains a CI gate.
Pin Android psutil to upstream commit
380bd2b59c67b0e1b04bbf3a90b11744f4f96644. It contains the unreleased
platform recognition and disk_partitions fixes. Other platforms keep 7.2.2.
Remove the local psutil source patch.
Retain the Git source through requirement normalization and wheel builds.
Use its locked version for offline installation. Provision Git in the
builder and host parsing dependencies through an isolated uv environment.
Wire the source-pin regression tests into Termux verification.
Targeted tests, lock checks, Ruff and shell/workflow checks passed. A real
wheel from the pinned source built and ran on Windows ARM64. Cold-cache
host normalization also passed without packaging installed on the host.
Full bionic compilation remains unverified.
Bring in the Python 3.14 runtime pins and wake-engine changes while
preserving the staged stable-release gate and review fixes.
The merge has no conflicts. Targeted tests on the existing Python 3.11
dev environment passed: 130 passed, 8 skipped. The lock check passed
with Python 3.14.7. Workflow lint and shell syntax checks also passed.
Full Python 3.14 runtime and native release acceptance remain for CI.
Run the entire CI workflow before Docker build and tests. Require Nix,
native payload smoke tests, install/update E2E and signed-package upgrade
acceptance before publishing. Keep Desktop Playwright E2E deferred.
Archive tested Docker images and signed bundle candidates with provenance
and hashes. Publishers consume those exact artifacts without rebuilding.
Advance stable channels only after all required publications succeed.
Keep canaries on their separate path and reject direct stable-builder
publication that bypasses the gate.
Move shared release transport, manifests and gates to Python. Keep native
Electron adapters in JS and share feed/MIME facts as JSON. Replace the
R2/feed JS implementation and move its protocol tests to Python.
Verified targeted Python and JS tests, real loopback transport and CLI
execution, temporary Git admission, workflow graph lint, and typechecks.
No live stable release was run. Native signing, package upgrades and real
registry/Store promotion still need their release-run receipts. Separate
services cannot promote atomically. A promotion failure keeps the run red.
pm python node: 3.14.7+20260901 (freshest python-build-standalone 3.14
build) for the 6 desktop targets; the bionic row moves from the third-party
TUR python3.11 deb to the official termux-main python_3.14.6-1 deb (which
lags PBS by one patch — pinned manually, documented). All 7 digests fetched
from the live sources (PBS release API + termux-main Packages index).
pm/packages.py: main_bin_rel python3.14, deb_package python, bionic fetch
constant, latest_versions guards bionic (no PBS build exists).
termux lane: PYTHON_ABI cp311->cp314, python3.11->python3.14 paths,
libpython3.11.so->3.14, TARGET_ENV 3.11.15->3.14.6 AND sys_platform
linux->android (CPython 3.13+ reports 'android', docs-verified) — linux-
gated markers no longer admit the termux target. runtime_libs.json needs no
change: every python 3.14.6-1 Depends is already staged.
CI: python-version/--python 3.11->3.14 across all 11 workflows incl. the
uv lockfile-check lane. Installers derive the minor from the lock already;
fallbacks bumped. Sandbox images nikolaik/python-nodejs:python3.11-nodejs20
-> python3.14-nodejs22 (tag exists). runtime_repair fall-forward cap now
tracks the <3.15 requires-python window. Docs/README python version claims
updated.
Merge ethie/cli-bundles at 0765ad689b.
Keep PM runtime publication, install identity, TLS policy, and module
boundaries from pm-clean.
Resolve the Node version-discovery method in its owning class. Preserve
staged tools if a repin download or publication fails. Carry extra-only
memory-provider setup through PM and retain restart-required reporting.
Keep target-specific TUI path assertions and discard obsolete self-lock
fixtures and the orphaned Windows service handler.
Verified locally with the canonical Python runner, root JS checks, TUI
checks/build, shell parsing, and workflow YAML parsing. Existing platform
pins and executable modes are unchanged. No full-suite CI, new bionic
bundle, or phone acceptance is claimed for this merge.
Cloudflare cached Packages.gz while serving a new signed Release. Advertise standard Acquire-By-Hash, publish SHA256/SHA512 index objects first, mark mutable APT metadata no-store, and run a real public APT install after upload.
Real CLI, ffmpeg, pm, APT refusal and TUI startup passed. A detached gateway child still wrote pycache after SIGTERM; use the existing tree terminator and wait for all descendants before deleting the test home.
Use a successful admitted release run as the upgrade source. Install its actual deb, upgrade through the signed APT repository, and check user data retention before publishing.
Remove the one-run linkage diagnostic and its expiring artifact dependency. The production wheel import gate and real ELF regression remain. Check the TUI in isolated CI and assert pm exports by behavior, not reloaded function identity.
A changed runtime table must discard all prior package-owned files, including copyright symlinks. Also isolate updater tests from previously collected native modules and exercise the post-merge deferral ordering instead of inspecting retired source symbols.
The clean non-root install passed native imports but ffmpeg needed libvulkan.so. Bundle Termux's real generic loader and exercise media conversion in the bare runtime. Restore doctor imports, pm-venv recognition, and current diagnostics seams.
Use one requirements writer for the wheelhouse and installed venv. Do not retry failed hashes or paused downloads. Skip pure-wheel decompression, preserve runtime library notices, and keep the current working directory off the launcher import path. Document the prerelease canary package without migration or downgrade guidance.
The POSIX runner had a blank line after exec env, so it printed the environment and never ran pytest. Keep the command attached and prove failure propagation. The actual payload records ANDROID_API_LEVEL in sysconfig; use that instead of looking for text in a guessed libc file. Rebuild unproven runtime-library extracts from verified archives.
Stage npm, ffmpeg and its bionic runtime libraries, and static ARM ripgrep. Bind caches to actual build inputs. Generate entrypoints from the project manifest and verify real CLI/TUI startup and media conversion offline. Keep versions unchanged. Windows service work remains out of scope.
Real bionic CI reproduces anydoc's missing _Py_NoneStruct symbol. The symbol exists in the shipped library, but the wheel has no libpython dependency. Link extensions that use Python symbols explicitly and rebuild RECORD before the unchanged offline import gate. Keep abi3 intact.
main's uv.lock added firecrawl-anydoc (a native dep); the gate's
dist-to-module mapping lacked it and the dash-to-underscore fallback
guessed firecrawl_anydoc, which is not the package's module (anydoc).
The gate merge dropped the commas from the -c payload: split('') turned
the comma-separated module list into one unimportable name. Restore the
exact original payload (split(','), quoted print label) and verify it
executes.
The digest-reader consolidation dropped the $( ) around the python
one-liner in build_builder_image.sh (the heredoc form did not carry it
visibly), so DIGEST held the literal command text and the builder image
tag became an invalid reference.
Pure-stdlib stager for the static apt layout: Packages(+gz), an
apt-valid Release (Date field, checksums in the same deb822 stanza --
learned from a real device rejecting the first shape), InRelease +
Release.gpg signed with the repo key (passphrase support via env), the
signing pubkey exported alongside, per-suite immutability, and nightly
versions that sort below stable. Control members are xz (our dpkg-deb
builds -Zxz; the stager also tolerates gz and zstd-via-binary).
Assemble the sealed hermes-agent aarch64 .deb: deps-only venv built
AT its on-device path (per-subdir mounts, staged uv, no app wheel --
Hermes is not pip-installable by design), trampolines that resolve their
symlink chain and export the payload linker path (runtime libs derived
from the suppliers' own Depends metadata), the prebuilt TUI bundle, and
the code-scoped .install_method stamp (restoring the 'apt' lane this
distribution needs). Validation runs in the BARE pinned base -- real
extraction, real postinst, C-extension imports, bundled node, TUI syntax
check, and the steward-refusal contract. A derived builder image
(toolchain pre-baked at uid 1000, content-addressed off the lock digest)
keeps cache-miss runs fast.