Commit Graph
2 Commits
Author SHA1 Message Date
bvandeusenandClaude Opus 5 85ead4d66b versioning: anchor at the repo root — a pathspec is relative to the caller's cwd
CI & Build / Build now, or wait for Android? (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 6s
CI & Build / Python lint (push) Successful in 4s
CI & Build / Python tests (push) Successful in 10s
CI & Build / integration (push) Successful in 14s
CI & Build / Build & push image (push) Successful in 15s
Three failures on c504433, two root causes, and the interesting one is that
`git log -- <paths>` resolves pathspecs against the CURRENT DIRECTORY.

Callers run from wherever suits them: the desktop build from
`desktop/src-tauri`, the Android build from `android`, the manifest job from
the root. So one push produced THREE versions:

  desktop build      1.0.3494522     <- six days stale
  pacman packager    1.0.3502131
  manifest job       1.0.3502131

The build's pathspec had matched `desktop/src-tauri/Cargo.toml` — a real file
— so git answered with the newest commit touching THAT. Non-empty, so the
shallow-clone guard could not fire; the manifest then found no bundle matching
its own answer and the lane went red two steps from the cause. The Android job
failed loudly in the same run only because ITS pathspec happened to match
nothing from `android/`. Same bug, luckier symptom.

The script `cd`s to `git rev-parse --show-toplevel` before doing anything now,
and the test asserts every artifact answers identically from four directories.

## And a third instance of the trap that bit yesterday

The unit test caught it: `version.sh display nope` printed "unknown artifact"
to stderr and then answered `2026.08.28.0900` with exit 0. `paths_for` is
reached through `$(paths_for "$1")`, so its `exit 2` ended the subshell,
returned an EMPTY pathspec — and an empty pathspec matches everything.

That is now three occurrences of one mistake in one file: the shallow-clone
guard on `key` (emitted `1.0.-26297280`, exit 0), the same guard on `display`
(which failed only because `date` then choked on the empty string), and this.
Each was found by a different mechanism and none by reading the code. The
artifact is validated in the parent shell now, and the file says so where the
next guard would be written.

Both tests assert on STDOUT as well as the exit code. The exit code alone
passed for `display nope` while stdout carried a lie.

#3144

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 21:03:04 -04:00
bvandeusenandClaude Opus 5 c5044339a1 versioning: each artifact derives from its own files, with the clock picked per value
CI & Build / Python lint (push) Successful in 4s
CI & Build / Python tests (push) Canceled after 13s
CI & Build / integration (push) Canceled after 13s
CI & Build / Build & push image (push) Canceled after 0s
Android / Kotlin + Rust (APK) (push) Failing after 14s
CI & Build / Build now, or wait for Android? (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 7s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 2m19s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 4m24s
Desktop (Tauri) / Update manifest (push) Failing after 4s
Step 4 of M314. `desktop/packaging/build-version.sh` was one generator feeding
the desktop bundles AND the Android APK off `GITHUB_RUN_NUMBER`, so a
Kotlin-only commit re-versioned the desktop and a Rust-only commit
re-versioned the phone. Note 3127 §3 cites this repo as its example of that
failure. It is replaced by `packaging/version.sh` — one definition of HOW to
derive, three file sets, and the sets in one place.

Lives at the repo root rather than under desktop/, because it serves three
artifacts now and a shared thing filed under one consumer ends up owned by it.

## Two values, and the clock chosen per value (§2)

  desktop  key      1.0.<minutes since 2020-01-01>   commit time
  desktop  display  2026.08.28.0900                  commit time  (#3181 shows it)
  android  versionName                               commit time
  android  versionCode  <minutes since 2020>         BUILD time
  server   version  2026.08.28.0900                  commit time, no ordering key

Every human-readable version in the repo is now one shape. The two exceptions
are not version names at all — they are bare monotonic integers a comparator
reads and nobody quotes.

The desktop needs a separate key because Tauri parses `latest.json` with the
semver crate and `2026.08.28.0900` fails it twice (four segments, and `08` is a
leading zero). `1.0.` and not `0.0.`: the minor has to clear the installed
`0.2.466` line or every dev user is stranded on "up to date" permanently.

Android's code comes from BUILD time while the desktop's key comes from COMMIT
time, deliberately. Android hard-fails a downgrade with
INSTALL_FAILED_VERSION_DOWNGRADE and leaves a channel you cannot get out of, so
its key must be monotonic by construction; the desktop merely declines to offer
an update, which a guard can catch.

## The bug this found in itself

The shallow-clone guard `exit 1`-ed inside a function called as `$(...)` —
which ends the SUBSHELL, not the script. `display` still failed, but only
because `date` then choked on the empty string. `key` printed the error to
stderr, emitted `1.0.-26297280`, and exited ZERO.

That is precisely the failure the guard exists to prevent: a too-low version on
a green lane, and too-low is the direction you cannot recover from. It resolves
into a global in the parent shell now. The test is parametrized over both
requests, because one path was covered and the other was broken in exactly the
way the covered one was meant to rule out.

## Also

`fetch-depth: 0` on every job that derives — four of them, and only ci.yml's
gate had it. Depth-1 is silently wrong rather than loudly broken (§6.1).

The file sets include each artifact's BUILD RECIPE (its workflow, and
`packaging/`). A workflow file is not shipped, but change a Gradle flag and the
bytes change while the source does not — and once step 6 skips a build whose
version already exists, that serves the OLD artifact on a green run.

The base images are deliberately NOT resolved at derive time: that is an
external lookup, which §7's corollary forbids. `Dockerfile` is already in the
server's set, so pinning `FROM` by digest in step 6 puts the base inside the set
for free.

`build-version.sh` is deleted, its last consumer (the pacman packager) moved
over, and the one finding worth keeping out of its header — why not a `-dev.N`
prerelease — is preserved in the successor.

#3144

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 20:51:39 -04:00