My previous commit added the codegen step to this branch's OLD
android-signed release-signed.yml. But dev has since replaced that file
with a 5-job pipeline (#342/#346), so editing the stale copy made both
sides diverge -> PR #348 went CONFLICTING and CI stopped running.
release-signed.yml only runs on v* tags, never on this PR, so it does
not belong in this PR at all. Restored to the merge-base version so it
merges cleanly (dev's version wins). The codegen for the release
pipeline will be added to DEV's release-signed.yml in a follow-up, AFTER
this merges (build_runner isn't a dep on dev until then).
The PR-relevant codegen (build.yml, flutter_dart.yml, deploy-web.yml) +
the .g.dart untracking are unchanged and still green the PR.
For #335 / PR #348. Agent: SapphireCompass (session 8d755b5e)
Replaces the stopgap (53f87f5) that committed
lib/storage/drift/offband_database.g.dart to green CI. Restores the
repo's "generated files are not committed" convention:
- dropped the .gitignore negation; the .g.dart is gitignored + untracked
(git rm --cached, working copy kept)
- added `dart run build_runner build --delete-conflicting-outputs`
immediately after `flutter pub get` in every Dart job: flutter_dart
(analyze), build.yml (all 6), release-signed (android-signed),
deploy-web (before build_pipe)
- keeps the drift_dev 2.34.0 / drift 2.34.2 / build_runner ^2.15.1 pins
from the stopgap for deterministic generation
Verified locally on the bench toolchain (3.44.1): deleted the working
.g.dart, ran build_runner from scratch (regenerated cleanly), confirmed
the generated file passes `dart format --set-exit-if-changed` and full
`flutter analyze --fatal-infos --fatal-warnings` is clean with the file
generated (not tracked).
For #335 / PR #348. Agent: SapphireCompass (session 8d755b5e)
Rebased onto dev after #331 landed. Brings the signing workflow in line
with the rest of the matrix:
- Pinned to Flutter 3.44.1, matching the bench and every other
workflow. A release artifact in particular must not be built by
whatever `stable` happens to point at that day, which was the whole
argument for #331.
- Generates dart_defines.json and passes --dart-define-from-file, so
the signed release does not ship with a dead GIF picker.
- Cleanup step now removes dart_defines.json alongside the keystore
material, since it holds the Giphy key.
Verified that a job pinned to an environment still receives repository
secrets (precedence is org < repo < environment, environment winning on
a name collision), so the repo-scoped GIPHY_API_KEY reaches this job
while the keystore secrets stay environment-scoped behind the reviewer
gate.
Part of epic #312, plan #321 (Phase 5).
Rejecting only CN=Android Debug catches the #111 fallback but not a
DIFFERENT key being substituted, which breaks cross-channel updates
against Play just as badly. Now asserts the exact fingerprint.
Established from artifacts users have already installed, with no access
to the keystore password (reading a certificate off a signed APK needs
none):
release b55 (2026-07-06) e7da8cd5...
release b58 (2026-07-10) e7da8cd5...
local release build e7da8cd5...
Three independent sources agree, and the owner confirmed the same
strycher-personal.jks is enrolled for Play App Signing, so this is the
fingerprint that must hold for a GitHub download to update over a Play
install.
Found while establishing it: published release b59 (2026-07-20) is
DEBUG-SIGNED (765fb469, CN=Android Debug), unlike b55 and b58. That is
#111 occurring in production. Filed separately.
Part of epic #312, plan #321 (Phase 5).
CI has only ever produced debug-signed Android artifacts, because
build.gradle.kts:63-70 falls back to signingConfigs "debug" when
key.properties is absent, which is exactly the CI condition (#111).
Debug-signed builds cannot be installed over a Play install or over a
properly signed GitHub release, so users hit "App not installed".
Adds a separate workflow that signs with the real keystore.
Security posture, given this repo is PUBLIC and the keystore is the one
credential that cannot be replaced if leaked:
- No pull_request trigger. A PR, including from a fork, must never run
a job that can read these secrets.
- Pinned to the release-signing Environment, which carries a required
reviewer and only accepts main or v* tags. The signing secrets are
scoped to that environment, NOT to the repo, so a workflow that
omits the environment key cannot read them at all.
- Keystore decoded to disk only for the build, removed in a step with
if: always() so a failed build leaves nothing behind.
- Secrets passed via env: and printf'd into a file, never placed on a
command line where they would appear in process listings, and never
echoed.
Includes an apksigner check that fails the job if the built APK carries
CN=Android Debug. A green build alone does not prove release signing,
because the gradle fallback is silent; that check is what makes this
verifiable rather than assumed.
The keystore is PKCS12 despite its .jks extension, and PKCS12 cannot
carry a key password distinct from the store password, so one secret
correctly populates both fields.
Part of epic #312, plan #321 (Phase 5).