flake-status: compare each pin to the branch its lock names #177

Merged
vnprc merged 2 commits from agent/flake-status-locked-ref into master 2026-09-09 19:59:56 +01:00
Member

flake-status --upstream now compares each pin to the head of the branch its own lock names, so a nixpkgs pin on nixos-26.05 is measured against nixos-26.05 and a home-manager pin at the tip of release-26.05 shows no arrow and is left out of the Outdated (external) line.
A pin whose lock names no branch is still compared to the remote's default branch, and a pin that names its own revision is never behind.
Nothing changes for a running machine: this is a read-only inspection tool, and the no-argument and named-input tables print exactly as before.
Three other things become visible: the named-input upstream: verdict is judged against the branch the majority pins rather than the first repo's; the suggested flake-update-cascade command lists inputs in sorted order instead of hash order; and an input the tool cannot read the way the cascade does (dir, host, submodules, or a non-string ref) shows no arrow where it used to.
tests/flake/flake-status.sh now pins the behaviour (35 tests, 14 new) and runs under nix flake check; a live --upstream over the workspace and over a checkout with a stale lock are below.
If a pin is arrowed wrongly, compare by hand with git ls-remote <url> refs/heads/<ref> for the ref in the lock's original entry, and revert this PR: the old and new scripts read the same lock, so nothing else depends on it.

Closes allod/tools#170

What changed

  • flake/flake-status: get_upstream_url becomes get_upstream_source, returning url\tref. The ref is original.ref, or HEAD when the lock names none. A declaration carrying rev, or one that is not a plain github or git reference (registry, tarball, path:, or a reference with dir, host or submodules), yields nothing and gets no arrow. These are the classification rules of internal/flakelock/source.go, unchanged; the cascade is out of scope and untouched.
  • Heads are read with timeout 60 git ls-remote --exit-code <url> refs/heads/<ref> refs/tags/<ref> (or HEAD / a full refs/... as given), picking the first spec present and preferring a tag's peeled ^{} commit, as cmd/flake-update-cascade/heads.go does. GIT_TERMINAL_PROMPT=0 and stdin from /dev/null so a remote that wants credentials fails instead of hanging. Resolution is deduplicated by url\tref rather than by URL, so nixos-26.05 and nixos-unstable on the same repository are two reads.
  • Named-input mode's single upstream: line compares the majority revision to the branch named by a repo that holds that revision. Before, it took the first repo's URL, so a workspace whose first repo pins a different branch of the same repository than the majority got a wrong verdict in exactly the way the issue describes.
  • The Outdated (local) and Outdated (external) lines sort their names; Bash associative-array order is unspecified, and the new fixture made that visible.
  • flake/README.md and --help describe the comparison as against the branch the lock names, and list what the tool cannot read.
  • flake.nix: a flake-status-suite check runs the Bash suite in the sandbox with a mock git, so the regression test has a standing witness. The suite's mock git names its interpreter outright, as the other sandboxed suites do, because the sandbox has no /usr/bin/env.

Risk

  • Inputs the tool previously arrowed but now leaves blank: a github or git reference carrying dir, host or submodules, or with a non-string ref. No workspace lock has one. The README says which inputs show no arrow.
  • An unreachable remote, a read that hits the 60-second bound, or a ref the remote does not carry prints no arrow, as before; the tool cannot distinguish that from "at head". The live run below was cross-checked with direct git ls-remote calls for that reason.
  • Remaining divergences from the Go cascade, all pre-existing: the arrow compares 7-character prefixes where Go compares full revisions, and output printed before a non-zero ls-remote exit is still parsed (with --exit-code there is none).
  • Issue allod/tools#158 treats this Bash suite as the oracle for the Go port; the oracle now encodes the fixed behaviour, which is the divergence that issue allows for an open correctness bug.

Validation

  • bash tests/flake/flake-status.sh: 35 passed. Each "no arrow" assertion follows one proving the row exists, the tag fixture is behind its tag so only the peeled commit is a passing target, and the Outdated line is matched exactly. The suite against the pre-change script fails at test 21 (stable arrowed to the default branch); against the first commit of this PR it fails at test 32 (the named-input verdict), so both fixes have an assertion that can fail.
  • nix flake check: all checks passed, including the new flake-status-suite.
  • Live flake-status --upstream over the workspace from this branch: no arrows and no Outdated section. Cross-checked by hand: nixos-26.05 is at 6aefcda, nixos-unstable at d6524aa, home-manager release-26.05 at fd0956c, agenix and disko default branches at b027ee2 and ff8702b, each equal to the corresponding lock. nixpkgs master is at 01b3b32, which the old script would have arrowed every nixpkgs row toward.
  • Live flake-status nixpkgs --upstream over the workspace: INCONSISTENT (1 repo differs) with allod/tools (which pins nixos-unstable) marked stale and upstream: up to date at 6aefcda, the nixos-26.05 head.
  • Live run over a scratch workspace holding allod/vm at commit f1a6fe0, whose lock predates today's cascade:
==> allod/vm
  agenix                b027ee2  2026-02-04
  disko                 ff8702b  2026-06-11
  home-manager          fd0956c  2026-09-05
  nixpkgs               93108a5  2026-09-07  → 6aefcda
  nixpkgs-unstable      dc5d91f  2026-09-07  → d6524aa

Outdated (external):
  flake-update-cascade nixpkgs nixpkgs-unstable

The pre-change script over the same workspace arrows all three toward master (→ 01b3b32 for both nixpkgs rows, → 14179ae for home-manager) and suggests flake-update-cascade nixpkgs-unstable home-manager nixpkgs.

  • A separate read-only review pass over the first commit found the named-input defect and the three loose assertions; the second commit answers it. The review's findings are recorded in a comment below.

Follow-up

allod/memory allod.md records the old behaviour as a gotcha under flake-status; that line should be dropped once this merges.

`flake-status --upstream` now compares each pin to the head of the branch its own lock names, so a `nixpkgs` pin on `nixos-26.05` is measured against `nixos-26.05` and a `home-manager` pin at the tip of `release-26.05` shows no arrow and is left out of the `Outdated (external)` line. A pin whose lock names no branch is still compared to the remote's default branch, and a pin that names its own revision is never behind. Nothing changes for a running machine: this is a read-only inspection tool, and the no-argument and named-input tables print exactly as before. Three other things become visible: the named-input `upstream:` verdict is judged against the branch the majority pins rather than the first repo's; the suggested `flake-update-cascade` command lists inputs in sorted order instead of hash order; and an input the tool cannot read the way the cascade does (`dir`, `host`, `submodules`, or a non-string `ref`) shows no arrow where it used to. `tests/flake/flake-status.sh` now pins the behaviour (35 tests, 14 new) and runs under `nix flake check`; a live `--upstream` over the workspace and over a checkout with a stale lock are below. If a pin is arrowed wrongly, compare by hand with `git ls-remote <url> refs/heads/<ref>` for the `ref` in the lock's `original` entry, and revert this PR: the old and new scripts read the same lock, so nothing else depends on it. Closes allod/tools#170 ## What changed - `flake/flake-status`: `get_upstream_url` becomes `get_upstream_source`, returning `url\tref`. The ref is `original.ref`, or `HEAD` when the lock names none. A declaration carrying `rev`, or one that is not a plain `github` or `git` reference (registry, tarball, `path:`, or a reference with `dir`, `host` or `submodules`), yields nothing and gets no arrow. These are the classification rules of `internal/flakelock/source.go`, unchanged; the cascade is out of scope and untouched. - Heads are read with `timeout 60 git ls-remote --exit-code <url> refs/heads/<ref> refs/tags/<ref>` (or `HEAD` / a full `refs/...` as given), picking the first spec present and preferring a tag's peeled `^{}` commit, as `cmd/flake-update-cascade/heads.go` does. `GIT_TERMINAL_PROMPT=0` and stdin from `/dev/null` so a remote that wants credentials fails instead of hanging. Resolution is deduplicated by `url\tref` rather than by URL, so `nixos-26.05` and `nixos-unstable` on the same repository are two reads. - Named-input mode's single `upstream:` line compares the majority revision to the branch named by a repo that holds that revision. Before, it took the first repo's URL, so a workspace whose first repo pins a different branch of the same repository than the majority got a wrong verdict in exactly the way the issue describes. - The `Outdated (local)` and `Outdated (external)` lines sort their names; Bash associative-array order is unspecified, and the new fixture made that visible. - `flake/README.md` and `--help` describe the comparison as against the branch the lock names, and list what the tool cannot read. - `flake.nix`: a `flake-status-suite` check runs the Bash suite in the sandbox with a mock git, so the regression test has a standing witness. The suite's mock git names its interpreter outright, as the other sandboxed suites do, because the sandbox has no `/usr/bin/env`. ## Risk - Inputs the tool previously arrowed but now leaves blank: a `github` or `git` reference carrying `dir`, `host` or `submodules`, or with a non-string `ref`. No workspace lock has one. The README says which inputs show no arrow. - An unreachable remote, a read that hits the 60-second bound, or a ref the remote does not carry prints no arrow, as before; the tool cannot distinguish that from "at head". The live run below was cross-checked with direct `git ls-remote` calls for that reason. - Remaining divergences from the Go cascade, all pre-existing: the arrow compares 7-character prefixes where Go compares full revisions, and output printed before a non-zero `ls-remote` exit is still parsed (with `--exit-code` there is none). - Issue allod/tools#158 treats this Bash suite as the oracle for the Go port; the oracle now encodes the fixed behaviour, which is the divergence that issue allows for an open correctness bug. ## Validation - `bash tests/flake/flake-status.sh`: 35 passed. Each "no arrow" assertion follows one proving the row exists, the tag fixture is behind its tag so only the peeled commit is a passing target, and the `Outdated` line is matched exactly. The suite against the pre-change script fails at test 21 (`stable` arrowed to the default branch); against the first commit of this PR it fails at test 32 (the named-input verdict), so both fixes have an assertion that can fail. - `nix flake check`: all checks passed, including the new `flake-status-suite`. - Live `flake-status --upstream` over the workspace from this branch: no arrows and no `Outdated` section. Cross-checked by hand: `nixos-26.05` is at `6aefcda`, `nixos-unstable` at `d6524aa`, home-manager `release-26.05` at `fd0956c`, agenix and disko default branches at `b027ee2` and `ff8702b`, each equal to the corresponding lock. nixpkgs `master` is at `01b3b32`, which the old script would have arrowed every nixpkgs row toward. - Live `flake-status nixpkgs --upstream` over the workspace: `INCONSISTENT (1 repo differs)` with `allod/tools` (which pins `nixos-unstable`) marked stale and `upstream: up to date at 6aefcda`, the `nixos-26.05` head. - Live run over a scratch workspace holding `allod/vm` at commit `f1a6fe0`, whose lock predates today's cascade: ``` ==> allod/vm agenix b027ee2 2026-02-04 disko ff8702b 2026-06-11 home-manager fd0956c 2026-09-05 nixpkgs 93108a5 2026-09-07 → 6aefcda nixpkgs-unstable dc5d91f 2026-09-07 → d6524aa Outdated (external): flake-update-cascade nixpkgs nixpkgs-unstable ``` The pre-change script over the same workspace arrows all three toward `master` (`→ 01b3b32` for both nixpkgs rows, `→ 14179ae` for home-manager) and suggests `flake-update-cascade nixpkgs-unstable home-manager nixpkgs`. - A separate read-only review pass over the first commit found the named-input defect and the three loose assertions; the second commit answers it. The review's findings are recorded in a comment below. ## Follow-up `allod/memory` `allod.md` records the old behaviour as a gotcha under `flake-status`; that line should be dropped once this merges.
--upstream read every remote's default branch and compared each pin to
it, so a nixpkgs pin on nixos-26.05 or a home-manager pin at the tip of
release-26.05 read as behind master on every run, and the Outdated
(external) line kept suggesting a cascade that nix flake update would
turn into a no-op.

Each pin is now compared to the head of the branch its lock names:
original.ref, or the default branch only when the lock names none, read
with git ls-remote <url> refs/heads/<ref> and then refs/tags/<ref>,
preferring a tag's peeled commit. A pin that names its own revision is
never behind, and only plain github and git references are read. These
are the rules flake-update-cascade reads heads by, so an arrow is a move
that command can make. The outdated lists are sorted so the suggested
command is stable.

The suite gains a fixture repo with a release-branch pin at its head,
one behind its branch, a rev-only pin and an annotated-tag pin, with a
mock remote that answers per ref and logs what it was asked, and the
suite now runs under nix flake check.

Refs allod/tools#170
Named-input mode judged the majority revision against the branch of the
first repo carrying the input, which may pin a different branch of the
same repository; it now takes the branch from a repo holding the
majority revision. One ls-remote is bounded to 60 seconds, as the
cascade bounds it.

The suite's no-arrow assertions each follow one proving the row is
present, the tag fixture is behind its tag so the peeled commit is the
only passing target, the Outdated line is matched exactly, the ls-remote
log is truncated before the run it is read for, and two repos pinning
stable on different branches pin the named-input fix.

Refs allod/tools#170
Author
Member

Read-only review pass over the first commit (1ec997b), and what the second commit (a3891f2) does about each finding.

Fixed:

  • Named-input mode judged the majority revision against the branch of the first repo carrying the input. With one repo pinning the default branch ahead of two pinning a release branch, upstream: said the pins were behind although each was at its own head. It now takes the branch from a repo holding the majority revision, and the suite has that exact fixture (charlie, epsilon, zeta on stable).
  • Three assertions could pass vacuously: row() returning nothing satisfied assert_not_contains … "→" for pinned and tagged, and the tag test could not tell "picked the peeled commit" from "never read tags". Every no-arrow assertion now follows one proving the row exists, the tag fixture is behind its tag so → 1111111 is the only passing target, and the remote log is asserted for acme/tagged refs/heads/v1 refs/tags/v1.
  • The Outdated (external) assertion was a substring match, so an extra name passed. It is now an exact line.
  • One git ls-remote had no bound where the cascade uses 60 seconds; it now runs under timeout 60.
  • The remote log was not truncated between runs; it is now cleared before the run it is read for.
  • README usage block showed --upstream only with a named input and wrote the arrow as ->; both corrected.

Not changed, pre-existing and named in the body under Risk: the arrow compares 7-character prefixes where the Go cascade compares full revisions, and output printed before a non-zero ls-remote exit is parsed rather than discarded (empty with --exit-code).

Verified by the reviewer: 32 suite tests passing at the time, the pre-change script failing the new assertions, nix flake check including the new flake-status-suite derivation, and the live workspace run against direct git ls-remote reads. The scratch-workspace run over allod/vm at f1a6fe0 was not reproduced by the reviewer and is reported as I ran it.

Read-only review pass over the first commit (1ec997b), and what the second commit (a3891f2) does about each finding. Fixed: - Named-input mode judged the majority revision against the branch of the first repo carrying the input. With one repo pinning the default branch ahead of two pinning a release branch, `upstream:` said the pins were behind although each was at its own head. It now takes the branch from a repo holding the majority revision, and the suite has that exact fixture (charlie, epsilon, zeta on `stable`). - Three assertions could pass vacuously: `row()` returning nothing satisfied `assert_not_contains … "→"` for `pinned` and `tagged`, and the tag test could not tell "picked the peeled commit" from "never read tags". Every no-arrow assertion now follows one proving the row exists, the tag fixture is behind its tag so `→ 1111111` is the only passing target, and the remote log is asserted for `acme/tagged refs/heads/v1 refs/tags/v1`. - The `Outdated (external)` assertion was a substring match, so an extra name passed. It is now an exact line. - One `git ls-remote` had no bound where the cascade uses 60 seconds; it now runs under `timeout 60`. - The remote log was not truncated between runs; it is now cleared before the run it is read for. - README usage block showed `--upstream` only with a named input and wrote the arrow as `->`; both corrected. Not changed, pre-existing and named in the body under Risk: the arrow compares 7-character prefixes where the Go cascade compares full revisions, and output printed before a non-zero `ls-remote` exit is parsed rather than discarded (empty with `--exit-code`). Verified by the reviewer: 32 suite tests passing at the time, the pre-change script failing the new assertions, `nix flake check` including the new `flake-status-suite` derivation, and the live workspace run against direct `git ls-remote` reads. The scratch-workspace run over `allod/vm` at `f1a6fe0` was not reproduced by the reviewer and is reported as I ran it.
vnprc approved these changes 2026-09-09 19:59:51 +01:00
vnprc merged commit a3891f2cec into master 2026-09-09 19:59:56 +01:00
vnprc deleted branch agent/flake-status-locked-ref 2026-09-09 19:59:56 +01:00
Sign in to join this conversation.
No description provided.