An empty ls-remote advertisement cannot tell "merged and deleted upstream"
from "never pushed". The source check now re-pins a missing branch to main
only when the local branch was once published (remote-tracking ref or a
configured upstream) and git cherry finds no commits missing from main.
Otherwise it keeps the branch and reports branch-local-only.
Diagnosis from #105053 by @0xalydev and #105055 by @kokhlo, which targeted
the Electron healer since moved into hermes_cli/source_check.py.
Fixes#105042
check_for_updates had grown to ~CC 75 over 128 lines, mixing install gating,
checkout probing, cache freshness, channel resolution, remote-tip fetch and
result shaping. It now reads as orchestration over cohesive helpers:
_unsupported_reason, _read_checkout (_Checkout facts), _cached_status /
_write_cache, _resolve_channel, _check_branch (_branch_remote,
_heal_deleted_branch, _behind_count). Every function is below CC 30
(check_for_updates 26).
Behaviour, public signature and return shape are unchanged; module-level
seams tests patch (_git_stdout, _git_ok, _branch_tip, resolve_source_target,
time, ...) are still read from this module at call time.
Conflict resolutions and semantic fixups:
- utils.py / hermes_yaml.py: main widened ruamel's round-trip emitter so a long
double-quoted scalar is never folded after an escaped backslash. pm-clean builds
every rt emitter through hermes_yaml.roundtrip_yaml(), so the width lives there
(ROUNDTRIP_YAML_WIDTH moves with it); xai_retirement imports it from hermes_yaml.
- hermes_cli/banner.py: keep pm-clean's removal of the banner update check. Main's
GIT_NO_LAZY_FETCH fix for it applies to its replacement, source_check: every
read-only probe (source_git_env) now refuses promisor lazy fetches, and the
partial-clone test targets that probe (red without the flag).
- .github/workflows/tests.yml: keep setup-pm; main's uv pin bump does not apply.
Main's WAL-capable SQLite gates are kept, run against $HERMES_PYTHON (the
PM-pinned interpreter, SQLite 3.53.1). The e2e step takes main's
--include-integration invocation.
- apps/desktop: package.json has no build block here, so main's macOS locale-marker
restore joins the darwin branch of the existing after-pack.mjs, and its test
loads the hook from electron-builder.config.cjs and imports PlatformPackager
from app-builder-lib's root (electron-builder 27 exports no ./out paths). The
win32 row is dropped: this hook sanitizes and signs PE trees on win32 by design.
- reconciliation.ts: main's rowId hydration (#119326) was merged into the first of
pm-clean's split helpers only; the resolver is now one helper both halves use.
- en.ts: both sides' keys kept. tests/tools/test_lazy_deps.py stays deleted.
- Tests main added with `import yaml` use hermes_yaml, like the rest of the tree.
The passive source check moved into hermes_cli/source_check.py during the
pm/ rewrite, and with it the GitHub calls left the desktop — but the four
main fixes that lived on the desktop side (gh-CLI token fallback
c61a11474b, anonymous retry e79ba2d7f2, rate-limit headers
c7d4d32800, failure copy 66a13703b3) did not follow. `_request` sent no
Authorization, so every client behind one NAT/VPN exit shared the anonymous
60/hour budget, and `_branch_tip` collapsed every failure into "Could not
resolve the remote branch tip." — a 403 rate limit read as a Hermes bug.
hermes_cli/github_api.py owns the credential ladder (GITHUB_TOKEN, GH_TOKEN,
`gh auth token` cached per process, anonymous; a 401 on a token retries
anonymously) and the failure copy (rate limit with reset time and the
GITHUB_TOKEN remedy, 5xx as GitHub trouble, else the status/transport
error). `_branch_tip` returns that reason and check_for_updates puts it in
the message.
apps/desktop/electron/github-api-auth.ts had no caller left (the desktop no
longer talks to api.github.com) — removed with its test; the behaviour now
lives where the request is made.
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.
Use the exact target root and profile for branch, release, and cache decisions. Keep the desktop as a transport and handoff adapter. Remove the competing TypeScript checker and switch the banner, dashboard, and updater count consumers.
Preserve fork origins, unknown counts, publication checks, old-probe recovery, and official SSH branch healing through public HTTPS. Keep the historical unstamped-root policy unchanged.