Root, CI, docs and packaging describe the project to a stranger (#6225)
Android / Build, or is the channel already serving this? (push) Successful in 3s
CI & Build / Python lint (push) Successful in 2s
CI & Build / Build now, or wait for Android? (push) Successful in 2s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
CI & Build / Web typecheck and unit tests (push) Successful in 12s
CI & Build / Python tests (push) Successful in 23s
Android / Core and FFI clippy and tests (push) Successful in 51s
CI & Build / integration (push) Successful in 1m49s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Web tests, clippy, Rust tests and rustfmt (push) Successful in 2m20s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 3m6s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 3m46s
Desktop (Tauri) / Update manifest (push) Successful in 3s
Android / Kotlin + Rust (APK) (push) Successful in 8m31s
Android / Build the server image (push) Successful in 1s

#6225 "Comment standard sweep: root, config, CI workflows, docs and
packaging", milestone 546 "Bring the repo to the comment standard (rule 32)
and compose defaults (rule 198)".

- README restructured in newcomer order: what it is, quick start,
  configuration, setup pitfalls, updating and backups, features,
  troubleshooting, development, licence. Its quick-start compose carries
  no restart policy.
- Dockerfile and .env.example trimmed to one or two lines per setting.
- Workflows, packaging scripts and docs lose issue, run, note, rule and
  milestone references and their history narration. Comments that still
  described path filters and "step 6" as future work now state what the
  workflows do.
- ci-requirements.md rewritten against the current workflows. It said the
  build gated on typecheck and lint only, the integration lane did not
  gate, a v* tag published :latest, and ci-tauri lacked bsdtar and zstd;
  none of those is true now (the last checked against run 9630's log).
- Echo strings in guard-forward.sh and version.sh drop their note
  citations; every string a test asserts on is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-21 20:44:02 -04:00
co-authored by Claude Opus 5.5
parent 01b53118b9
commit f16224b07c
18 changed files with 666 additions and 843 deletions
+4 -5
View File
@@ -8,11 +8,10 @@
# The channel is what a person picks and sees (`install.sh --channel dev`, the app's
# settings); the tag is where CI publishes and where every reader fetches from.
#
# `dev` used to be both. A tag named `dev` shadowed the branch named `dev`, so once
# a clone had fetched tags, `git push origin dev` failed with "src refspec dev
# matches more than one" (Scribe #2184). The tag is `dev-rolling` now, matching
# roundtable-android. Never spell a channel tag like a branch — tests/test_channel_tag.py
# fails if one is.
# A tag named `dev` would shadow the branch named `dev`: once a clone had fetched
# tags, `git push origin dev` would fail with "src refspec dev matches more than
# one". So the dev channel's tag is `dev-rolling`. Never spell a channel tag like a
# branch — tests/test_channel_tag.py fails if one is.
#
# The ONE mapping CI reads. Two copies exist because their readers cannot run this:
# `desktop/src-tauri/src/update.rs` (compiled into the app) and
+3 -3
View File
@@ -39,14 +39,14 @@ esac
SERVER="${GITHUB_SERVER_URL:-https://git.fabledsword.com}"
REPO="${GITHUB_REPOSITORY:-bvandeusen/inkwell}"
# The channel's release tag, not its name — `dev` lives on `dev-rolling`
# (packaging/channel-tag.sh, Scribe #2184).
# (packaging/channel-tag.sh).
tag="$(sh "$(dirname "$0")/channel-tag.sh" "$channel")"
BASE="$SERVER/$REPO/releases/download/$tag"
mkdir -p "$dest"
# Authenticated when we have a token — these releases are private (issue 2091), so
# on this instance we always do. Anonymous still works against a public fork.
# Authenticated when we have a token, which a private repo's releases need.
# Anonymous still works against a public fork.
fetch() {
if [ -n "${GITHUB_TOKEN:-}" ]; then
curl -fsSL -H "Authorization: token $GITHUB_TOKEN" -o "$2" "$1"
+20 -22
View File
@@ -6,18 +6,17 @@
# guard-forward.sh compare <a> <b> exit 0 iff a sorts below b
# guard-forward.sh published <artifact> <channel> print what the channel serves
#
# Note 3127 §6.3. Everything else in this milestone derives a number and trusts it;
# this is the one thing that checks the answer against reality before a user gets it.
# Everything else in the versioning derives a number and trusts it; this is the one
# thing that checks the answer against reality before a user gets it.
#
# WHAT IT CATCHES that nothing else does:
#
# * A SQUASH OR REBASE MERGE (§6.2). Both rewrite the committer date, so `main`
# could stamp a value unrelated to the dev commit it merged. Rule 153 mandates
# plain merge commits — but that rule governs people, and a forge UI's squash
# button does not read it.
# * A SQUASH OR REBASE MERGE. Both rewrite the committer date, so `main` could
# stamp a value unrelated to the dev commit it merged. This repo merges with
# plain merge commits, but a forge UI's squash button is still one click away.
# * A REBUILD OF AN OLDER COMMIT. Commit time can go backwards; this is the entire
# mitigation for the desktop key's clock choice (step 4), and the thing to
# revisit first if this repo ever starts rebuilding old commits routinely.
# mitigation for the desktop key's clock choice, and the thing to revisit first
# if this repo ever starts rebuilding old commits routinely.
# * CLOCK SKEW between runners, for a build-time key.
#
# What it does NOT catch, because something better does: a shallow clone. That is
@@ -26,8 +25,8 @@
#
# TOO-LOW IS THE UNRECOVERABLE DIRECTION. A version below what is published means
# every installed client reports "up to date" forever and there is no build you can
# ship to fix it — you have to get back ABOVE the bad number. That is #2183 and
# #2993's shared symptom, and it is why this fails the lane rather than warning.
# ship to fix it — you have to get back ABOVE the bad number. That is why this fails
# the lane rather than warning.
set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
@@ -54,8 +53,8 @@ version_lt() {
return 1 # equal
}
# Auth if we have it, anonymous if not — the releases are public, but a token costs
# nothing and keeps this working if that ever changes.
# Auth if we have it, anonymous if not — a token costs nothing, and a private repo's
# releases need one.
#
# MISSING CURL IS FATAL, not empty. Every fetch here ends in `|| true` so a network
# blip reads as "nothing published yet" and passes — which is right for a genuinely
@@ -78,7 +77,7 @@ fetch() {
# What the channel is serving, per artifact. ONE definition of where to look, shared
# with `should-build.sh` — the skip decision and the guard must agree about what is
# published, and two readers of one fact is how this repo keeps producing #2181-2183.
# published, and two readers of one fact drift apart.
#
# EVERY LOOKUP HERE MUST SUCCEED EVEN WHEN IT FINDS NOTHING. That is what the `|| true`
# on each pipeline is for, and it is load-bearing rather than defensive noise.
@@ -89,15 +88,14 @@ fetch() {
# exits non-zero kills the script before that branch is ever reached. Silently, too:
# everything the pipeline would have said went into the capture rather than the log.
#
# WHICH COMMAND THE PIPELINE HAPPENS TO END ON decides whether that fires, which is the
# part worth remembering. `sed` on empty input exits 0; `grep` exits 1. Three of these
# four lookups end in `sed` and were fine. The one that ends in `grep -oE '[0-9]+$'` —
# Android's version_code — was not, and it failed the whole Android lane on the first
# merge to `main` (run 4857): exit 1, no output, 0.16 seconds, on the one channel that
# had no APK published yet. Its three neighbours hid it until then.
# WHICH COMMAND THE PIPELINE ENDS ON decides whether that fires, which is the part
# worth remembering. `sed` on empty input exits 0; `grep` exits 1. Three of these four
# lookups end in `sed`; the one that ends in `grep -oE '[0-9]+$'` — Android's
# version_code — would fail the lane, silently, on any channel with no APK published
# yet.
published_for() {
# The channel's release TAG, not its name — `dev` lives on `dev-rolling`
# (packaging/channel-tag.sh, Scribe #2184). `|| exit 2` rather than letting an
# (packaging/channel-tag.sh). `|| exit 2` rather than letting an
# empty tag through: a lookup against `.../download//latest.json` would find
# nothing and read as "nothing published", which this guard PASSES.
_tag="$(sh "$ROOT/packaging/channel-tag.sh" "$2")" || exit 2
@@ -173,7 +171,7 @@ case "$artifact" in
published="$(published_for desktop "$channel")"
# COMMIT time, so EQUALITY IS THE ORDINARY CASE: an unchanged source derives
# exactly what it derived last time, and `<=` would fail every no-change build.
# §6.3 says *strictly* less for exactly this reason.
# So this fails only on *strictly* less.
strict=""
;;
android)
@@ -212,7 +210,7 @@ if version_lt "$derived" "$published"; then
echo " forever, and no later build fixes that until one climbs back above the" >&2
echo " bad number. Do not force past this." >&2
echo "" >&2
echo " Usual causes (note 3127 §6.2, §6.3):" >&2
echo " Usual causes:" >&2
echo " - a squash or rebase merge rewrote the committer date" >&2
echo " - this build is a rebuild of an older commit" >&2
echo " - clock skew between runners (build-time keys)" >&2
+3 -3
View File
@@ -1,7 +1,7 @@
"""Every icon Inkwell ships, from one drawing.
The mark is a black inkpot with a quill standing in it, on the brand yellow
(Scribe note 5071). It is drawn ONCE below, in a 512 box, and each variant places it
The mark is a black inkpot with a quill standing in it, on the brand yellow.
It is drawn ONCE below, in a 512 box, and each variant places it
with its own scale, because each platform crops differently:
- icon.svg the rounded tile: web favicon/PWA "any", desktop app icon
@@ -70,7 +70,7 @@ def svg(body, label=True):
def tile(scale=0.92):
return svg(f''' <!-- Rounded-tile app icon: brand yellow, and the inkwell — a black inkpot with a
quill standing in it. Source for every raster icon; see step 5 of milestone 481. -->
quill standing in it. Source for every raster icon. -->
<rect width="512" height="512" rx="112" fill="{GOLD}"/>
<g transform="{place(scale)}">{GLYPH}
</g>''')
+4 -5
View File
@@ -4,12 +4,11 @@
#
# release-notes.sh <tag>
#
# A release BUILDS NOTHING now (M314 step 7). The merge to `main` already published
# `:latest`, `:<sha>` and both channel feeds, so a tag rebuilding that same source
# would produce identical artifacts and re-push `:<sha>` with different bytes —
# which rule 145 forbids even when the bytes match.
# A release BUILDS NOTHING. The merge to `main` already published `:latest`,
# `:<sha>` and both channel feeds, and a tag rebuilding that same source would
# re-push `:<sha>`, which a published tag never allows.
#
# So what is a release FOR? Note 3127 §5 answers it: the changelog. There are two
# What a release is FOR is the changelog. There are two
# halves to "what am I running" and the version answers only the first —
#
# which build is this the footer, /api/config, the APK's versionName
+11 -19
View File
@@ -5,27 +5,21 @@
#
# should-build.sh <desktop|android> <dev|stable>
#
# Note 3127 §4, skip-if-exists — adapted, because §4 assumes a registry keyed by
# VERSION and rule 145 removed exactly that. There is no `:<version>` tag to ask
# about. What there IS, for both clients, is a channel that publishes the version it
# is serving, and that answers the same question: if the channel already serves what
# this source derives, the artifact would be byte-identical and there is nothing to
# build.
# Skip-if-exists, keyed on what the channel serves rather than on a registry tag:
# there are no `:<version>` tags to ask about. Both clients' channels publish the
# version they serve, so if the channel already serves what this source derives,
# the artifact would be byte-identical and there is nothing to build.
#
# WHAT THIS REPLACES, and why that matters more here than the cost saving: the
# `paths:` filters in the workflows were a SECOND, independent statement of each
# artifact's file set, hand-kept beside the one in `version.sh`. They disagreed
# within a day of the sets being written — `packaging/` was added to the sets and
# not to the filters, so the commit that fixed a derivation bug never ran on the two
# lanes it fixed (85ead4d). §3 warns about exactly this duplication; one definition
# with one reader is the fix, and the cost saving is a bonus.
# This is also why the workflows carry no `paths:` filters: a second, hand-kept
# statement of each artifact's file set beside the one in `version.sh` drifts from
# it, and a lane whose filter misses a file never runs on the commit that changed
# it. One definition with one reader; the cost saving is a bonus.
#
# THE SERVER IS NOT LISTED HERE, DELIBERATELY. Its image build is ~15 seconds against
# 6 and 9 minutes for the clients, so there is little to save — and always building
# it is strictly better for a server that can face the internet, because it picks up
# `python:3.12-slim` base updates on every push. That is also why the base-image
# tension in §4 does not bite this project: the artifact most exposed to it never
# skips. The clients' bases are CI runner images, pinned deliberately.
# `python:3.12-slim` base updates on every push. The clients' bases are CI runner
# images, pinned deliberately.
set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
@@ -64,9 +58,7 @@ if [ "$derived" = "$published" ]; then
# produce the same artifact under the same name and republish it for nothing.
#
# Skipping is safe here in a way it would not be if anything pinned: there is no
# immutable tag to re-push with different bytes (rule 145 removed version tags),
# so the immutability argument in §4.2 does not apply and this stands on cost
# alone — which is the smaller, honest claim.
# version tag to re-push with different bytes, so this stands on cost alone.
echo "should-build: $channel already serves $artifact $derived — skipping." >&2
echo false
exit 0
+60 -84
View File
@@ -2,15 +2,12 @@
#
# What version an artifact carries, derived from its OWN shipped files.
#
# Replaces desktop/packaging/build-version.sh, which was one generator feeding the
# desktop bundles AND the Android APK off `GITHUB_RUN_NUMBER`. A Kotlin-only commit
# re-versioned the desktop; a Rust-only commit re-versioned the phone. It read as
# tidy — one definition, no drift — which is exactly why it survived review. One
# definition of HOW to derive is right; one VALUE for unrelated artifacts is not.
# (Note 3127 §3, which cites this repo as its example of the failure.)
# One definition of HOW to derive, applied per artifact. One VALUE for unrelated
# artifacts would re-version the desktop on a Kotlin-only commit and the phone on a
# Rust-only one.
#
# Lives at the repo root, not under desktop/, because it now serves three artifacts
# and a shared thing filed under one consumer is how it ends up owned by that one.
# Lives at the repo root, not under desktop/, because it serves three artifacts and
# a shared thing filed under one consumer ends up owned by that one.
#
# version.sh display <artifact> the human-readable version — 2026.08.28.1815
# version.sh key <artifact> the ordering key a comparator reads
@@ -20,7 +17,7 @@
#
# "is this the same code?" -> display. A dev build and the main build of one
# commit read identically, because they ARE the
# same bytes (note 3127 §2, reason 4).
# same bytes.
# "may this replace that?" -> key. What an updater or an install gate
# compares, and never shown to a person.
#
@@ -29,14 +26,12 @@
# the spec allows three, and `08` is a leading zero, which it forbids outright. A
# non-semver string does not sort low: the feed fails to DESERIALIZE and every client
# reports "no update available" forever. So the platform's field takes an opaque key
# and the display version lives beside it. See #3142's spike.
# and the display version lives beside it.
#
# WHY NOT A `-dev.N` PRERELEASE for the dev channel — carried over from the script
# this replaces, because it is a real finding and the reasoning is not obvious:
# a prerelease sorts BELOW the release it qualifies (`0.1.0-dev.5` < `0.1.0`), so a
# dev build could never be offered as an update to a tagged one, and Windows
# installer metadata wants a numeric X.Y.Z anyway. The channel goes in a sibling
# field, never in the version — note 3127 §7, and rule 149.
# WHY NOT A `-dev.N` PRERELEASE for the dev channel: a prerelease sorts BELOW the
# release it qualifies (`0.1.0-dev.5` < `0.1.0`), so a dev build could never be
# offered as an update to a tagged one, and Windows installer metadata wants a
# numeric X.Y.Z anyway. The channel goes in a sibling field, never in the version.
set -eu
# ANCHOR AT THE REPO ROOT BEFORE ANYTHING ELSE.
@@ -46,47 +41,37 @@ set -eu
# `desktop/src-tauri`, the Android build from `android`, the manifest job from the
# root — so without this the same request answers differently per caller.
#
# It is not a tidy failure. Measured on run 4796, one push produced THREE versions:
# the desktop build (cwd `desktop/src-tauri`) said 1.0.3494522, while the pacman
# packager and the manifest job both said 1.0.3502131. The build's pathspec had
# matched `desktop/src-tauri/Cargo.toml` — a real file — so git returned the newest
# commit touching THAT, six days stale. Non-empty, so the guard below could not fire;
# the manifest then found no bundle matching its own answer and the lane went red for
# a reason two steps removed from the cause.
#
# The Android job failed loudly in the same run only because its pathspec happened to
# match nothing from `android/`. Same bug, louder symptom, pure luck.
# It is not a tidy failure. From `desktop/src-tauri`, the pathspec `Cargo.toml`
# matches `desktop/src-tauri/Cargo.toml`, a real file, so git returns the newest
# commit touching THAT: a stale but non-empty answer the guard below cannot catch,
# and a version that disagrees with what every other job derives for the same push.
cd "$(git rev-parse --show-toplevel)"
# A SHALLOW CLONE IS FATAL, and asked directly rather than inferred.
#
# Landmine §6.1: depth-1 sees one commit, so `git log -- <paths>` answers about
# whatever happens to be in that commit and the result is a too-LOW version — the
# unrecoverable direction, arrived at silently with every lane green.
# Depth-1 sees one commit, so `git log -- <paths>` answers about whatever happens to
# be in that commit and the result is a too-LOW version — the unrecoverable
# direction, arrived at silently with every lane green.
#
# The empty-result guard below catches only the case where NOTHING matches. It missed
# the worse one: on run 4796 a partial match returned a real, six-days-stale answer.
# `--is-shallow-repository` tests the actual hazard instead of a symptom of it, costs
# no network, and covers every artifact including the ones with no published value to
# compare against.
# The empty-result guard below catches only the case where NOTHING matches, not a
# partial match returning a real, stale answer. `--is-shallow-repository` tests the
# actual hazard instead of a symptom of it, costs no network, and covers every
# artifact including the ones with no published value to compare against.
if [ "$(git rev-parse --is-shallow-repository)" = "true" ]; then
echo "version.sh: this is a SHALLOW clone — any version derived here would be" >&2
echo " too low, silently. Add 'fetch-depth: 0' to the checkout." >&2
echo " (note 3127 §6.1)" >&2
exit 1
fi
# 2020-01-01T00:00:00Z. The counter epoch, and it must NEVER move: shifting it
# renumbers every artifact downwards, which is the one direction you cannot recover
# from (note 3127 §6.4).
# from.
EPOCH=1577836800
# --- the shipped file sets ---------------------------------------------------
#
# ONE definition, read by every consumer. The `paths:` filters in the three
# workflows are a second, independent statement of the same fact today; they come
# out in step 6 when skip-if-exists replaces them. Until then, a change here that is
# not mirrored there means a lane that does not fire — check both.
# ONE definition, read by every consumer: `should-build.sh` decides from it whether
# a client lane builds, and the workflows carry no `paths:` filters of their own.
#
# Read off what actually PACKAGES each artifact, not off intuition. Miss a file and
# a stale build keeps its version; include one that does not ship and you re-version
@@ -94,10 +79,11 @@ EPOCH=1577836800
#
# THE BUILD DEFINITION IS IN THE SET, and it is the part that is easy to leave out.
# A workflow file is not "shipped" — but change a Gradle flag or a `cargo tauri
# build` argument and the bytes change while the source does not. Once step 6 skips
# a build whose version already exists, that combination serves the OLD artifact on
# a green run: exactly the "miss a file and a stale build keeps its version" failure,
# arriving through the build recipe rather than the source. Same reason `packaging/`
# build` argument and the bytes change while the source does not. `should-build.sh`
# skips a build whose version the channel already serves, so that combination would
# serve the OLD artifact on a green run: exactly the "miss a file and a stale build
# keeps its version" failure, arriving through the build recipe rather than the
# source. Same reason `packaging/`
# is in every set: this script decides identity, so a change to it is a change to
# what each artifact claims to be.
paths_for() {
@@ -110,14 +96,11 @@ paths_for() {
# BUNDLED ARTIFACT: the image bakes in the Android client (ci.yml fetches the APK
# from the channel release and copies it into the package). So the image's set
# must contain the APK's set — an APK-only change genuinely changes what this
# image ships. Note 3127 §3 names this trap; FC's web image embeds the extension
# the same way.
# image ships.
#
# The base images are NOT listed and do not need to be: `Dockerfile` is in the
# set, so pinning `FROM` by digest (step 6) puts the base inside the set for
# free. Resolving a digest at derive time would work too and is WRONG — it is an
# external lookup, which §7's corollary forbids because it makes the value depend
# on when it was computed.
# The base images are NOT listed. `Dockerfile` is in the set, so a `FROM` pinned
# by digest would bring the base in for free. Resolving a digest at derive time
# is WRONG: an external lookup makes the value depend on when it was computed.
server) echo "src frontend alembic alembic.ini Dockerfile pyproject.toml .forgejo/workflows/ci.yml android core Cargo.toml Cargo.lock .forgejo/workflows/android.yml packaging" ;;
*) echo "version.sh: unknown artifact '$1'" >&2; exit 2 ;;
esac
@@ -126,23 +109,23 @@ paths_for() {
# Sets TS to the newest commit timestamp touching this artifact's files, or exits.
#
# EMPTY IS FATAL, deliberately. A shallow clone sees one commit and derives a
# too-low value with every lane green — the failure landmine §6.1 exists for, and
# the unrecoverable direction. Every job that calls this needs `fetch-depth: 0`;
# too-low value with every lane green — the unrecoverable direction. Every job that
# calls this needs `fetch-depth: 0`;
# this is what turns forgetting it into a red lane instead of a stranded channel.
#
# SETS A GLOBAL RATHER THAN ECHOING, and that is not a style preference. Written as
# `$(commit_ts desktop)` the function runs in a SUBSHELL, so its `exit` ends only
# that subshell and the caller continues with an empty string. Measured before this
# was fixed: `key desktop` on a repo with no matching history printed the error to
# stderr and then emitted `1.0.-26297280` and exited ZERO. A guard that reports a
# problem and does not stop is worse than none — it looks like it is working.
# `$(commit_ts desktop)` the function would run in a SUBSHELL, so its `exit` would end
# only that subshell and the caller would continue with an empty string: `key
# desktop` on a repo with no matching history would print the error and then emit
# `1.0.-26297280` and exit ZERO. A guard that reports a problem and does not stop is
# worse than none — it looks like it is working.
resolve_ts() {
# Unquoted on purpose: the path list is several words.
# shellcheck disable=SC2046
TS="$(git log --format=%ct -1 HEAD -- $(paths_for "$1"))"
if [ -z "$TS" ]; then
echo "version.sh: no commit touches $1's file set — is this a shallow clone?" >&2
echo " (needs fetch-depth: 0; see note 3127 §6.1)" >&2
echo " (needs fetch-depth: 0)" >&2
exit 1
fi
}
@@ -154,18 +137,12 @@ artifact="${2:?usage: version.sh <display|key|paths> <desktop|android|server>}"
# VALIDATED HERE, in the parent shell, and not left to `paths_for`'s default arm.
#
# Third instance of one trap in this script, so it is worth stating plainly: `exit`
# inside a function called as `$(...)` ends the SUBSHELL, not the script. `paths_for`
# is reached through `$(paths_for "$1")`, so its `exit 2` printed the error and
# returned an EMPTY pathspec — and an empty pathspec matches everything, so
# `version.sh display nope` answered `2026.08.28.0900` and exited 0. A confident
# version for an artifact that does not exist.
#
# The other two were the shallow-clone guard on the `key` path (emitted
# `1.0.-26297280`, exit 0) and the same guard on `display` (which failed only because
# `date` then choked on an empty string — luck, not design). Each was found by a
# different mechanism; none by reading the code. If you add a guard to this file,
# make sure it runs where the script does.
# The trap this whole script works around: `exit` inside a function called as
# `$(...)` ends the SUBSHELL, not the script. `paths_for` is reached through
# `$(paths_for "$1")`, so its `exit 2` alone would print the error and return an
# EMPTY pathspec — and an empty pathspec matches everything, so `version.sh display
# nope` would answer a confident version for an artifact that does not exist. If you
# add a guard to this file, make sure it runs where the script does.
case "$artifact" in
desktop|android|server) : ;;
*)
@@ -181,7 +158,7 @@ case "$what" in
display)
# One shape for every human-readable version in this repo, and for the release
# tag: YYYY.MM.DD.HHMM, zero-padded, UTC (note 3127 §1). Padded so it sorts as
# tag: YYYY.MM.DD.HHMM, zero-padded, UTC. Padded so it sorts as
# text as well as numerically, and so two lanes cannot emit forms one character
# apart.
resolve_ts "$artifact"
@@ -193,18 +170,17 @@ case "$what" in
desktop)
# COMMIT time. The desktop is a one-value system to Tauri — its comparator
# reads the version name — so this key is also what lands in bundle
# filenames and .deb metadata. Commit time buys the property in §2 reason
# (4): the last dev build before a PR and the main build from it are the
# same bytes and derive the same key, so the artifact is reused rather than
# rebuilt and re-signed under a new name.
# filenames and .deb metadata. With commit time, the last dev build before
# a PR and the main build from it are the same bytes and derive the same
# key, so the artifact is reused rather than rebuilt and re-signed under a
# new name.
#
# Commit time CAN go backwards (rebuild an older commit). The backwards
# guard in step 5 is the whole mitigation, and the desktop's failure there
# is soft: an update is not offered. Contrast Android below.
# Commit time CAN go backwards (rebuild an older commit). guard-forward.sh
# is the whole mitigation, and the desktop's failure there is soft: an
# update is not offered. Contrast Android below.
#
# `1.0.` and not `0.0.`: the minor must clear the installed `0.2.<run>` line
# or every dev user is stranded on "up to date" permanently. Checked against
# the live feed (0.2.466), not against what we thought we had published.
# `1.0.` and not `0.0.`: the minor must clear the `0.2.<run>` line older
# installs carry, or they are stranded on "up to date" permanently.
resolve_ts desktop
echo "1.0.$(minutes_since_epoch "$TS")"
;;
@@ -222,8 +198,8 @@ case "$what" in
server)
# NO ORDERING KEY. Nothing compares the server image: no updater, no install
# gate, and `:latest` is moved by the registry rather than chosen by a
# client. §2 is explicit that an artifact with nothing to compare needs only
# a name — do not add one because the other two have one.
# client. An artifact with nothing to compare needs only a name — do not
# add one because the other two have one.
echo "version.sh: the server has no ordering key; use 'display'" >&2
exit 2
;;