90bb3538c6b73c2cf8d17de6c154b6c627b03025
2
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
90bb3538c6 |
feat(release): build a dev channel so testing stops requiring a release
test-go / test (push) Successful in 1m11s
release / Build signed APK (releases and dev) (push) Successful in 5m0s
test-go / integration (push) Successful in 5m55s
release / Build + push container image (push) Successful in 1m52s
release / Verify release artifacts (tag releases only) (push) Skipped
There was no test channel at all. release.yml ran only on main and tags, so no :dev image existed and no APK was produced outside a release — the only way to get a build onto a phone was to ship one, which made `main` the staging area by default. A push to dev now builds a signed APK, bundles it, and publishes :dev. Signed with the SAME key as release builds, deliberately. A differently signed APK cannot install over the stable app, so anyone moving between channels would have to uninstall and lose their local data. Same key means both directions work. :dev is published ALONE, with no per-commit tag. A rolling channel is rolling by definition; a commit-addressable image for it would be a rollback target nobody ever pulls, kept forever. Recovery on dev is to fix forward, and that is a deliberate trade rather than an omission. The channel is derived from the REF, not the commit, which is why it is computed in the workflow and not in ci/version.sh. The same commit built on dev and on main reports the same version NAME and differs only in the channel field — that separation is the entire point of keeping the three values apart. What this repo deliberately does NOT get: a cross-repo dispatch to refresh the channel when its bundled APK is rebuilt. That mechanism exists elsewhere in the family because the app and server live in separate repos, and a channel that can only be refreshed by an unrelated commit is not a channel. Minstrel is a monorepo — one push builds the APK and the image in the same run from the same commit, so the channel cannot go stale against its own artifact. The requirement is met structurally; copying the mechanism would add a moving part to fix a problem that does not exist here. Two guards, for the two ways this wiring can fail quietly: A dev push must never move :latest. That would ship untested code to every stable operator on their next pull, with the build green and the image perfectly valid — just the wrong audience. Nothing else in the suite would notice. The two bundling paths must stay mutually exclusive. The rebundle step is now gated to main specifically, not to "not a tag": under the looser condition a dev push would run BOTH steps, staging its fresh APK and then overwriting it with the previous release's. The image still builds, the sidecar still parses, and the channel whose whole job is being current quietly serves stale art. Both falsified against the regressions they name before committing. Scribe task #3819, milestone #390. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SQ31KQpYbStyK5y58UmPLH |
||
|
|
eaf4654c0a |
test(release): make the version derivation executable, and guard it on dev
Steps 1 and 2 of this milestone shipped with no CI coverage at all, and the
reason generalises: release.yml triggers only on main and tags, so nothing
inside it is exercised until a release is already running. That is the worst
place in the repo to be unguarded, because the failure mode is silence — a
version nobody can compare looks exactly like being up to date, and nobody
reports an update they were never offered.
The fix is not a test that reads YAML. The derivation moved into
ci/version.sh, so it can be RUN, and internal/server/release_version_test.go
runs it on every push. release.yml now calls the same script, so the thing
that ships and the thing under test are one artifact rather than two copies
that agree until they don't.
test-go.yml gains 'ci/**' and '.gitea/workflows/release.yml' in its paths.
Without that the guard exists but never fires on the changes it protects,
which is the same nothing it replaces.
What is pinned, and why each one:
- HHMM is zero-padded. A build at 00:42 must emit "0042"; a stripped
leading zero shifts the segment two orders of magnitude and reverses
comparisons against every other build that day. It only bites for a
tenth of the day, so it will not be found by chance.
- The name derives from the COMMIT and the code from the BUILD. Asserted
by holding one clock and moving the other: the name must not move, the
code must.
- The code clears 1895, the highest versionCode the retired commit-count
scheme shipped. Below that Android refuses the upgrade as a downgrade
and the channel becomes a one-way door.
- The tag is the name with a `v`, never chosen.
- release.yml still calls the script, and does not derive a commit count
again. This pins the WIRING: without it every other assertion keeps
passing while the shipped path silently drifts out of coverage.
The script rejects unusable clocks rather than emitting something plausible,
and those rejections are tested — a guard that cannot fail is worse than
none, because it reads as coverage.
Falsified before committing rather than after: ran the script against good
and broken inputs and watched all three failure paths fire; verified every
asserted value by executing it rather than by reading it; and checked the
two workflow predicates catch their regressions while staying immune to a
comment that merely names the old formula.
Step 5 of 5 — Scribe task #3812, milestone #390.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SQ31KQpYbStyK5y58UmPLH
|