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
+34 -44
View File
@@ -1,11 +1,7 @@
name: Android
# The native Kotlin/Compose client over the shared Rust core (M12).
#
# Replaces the Tauri-mobile lane deleted in step 2. What changed is what this
# builds, not that Android has a lane: the UI is Compose, and the store and sync
# engine are `inkwell-core` cross-compiled by cargo-ndk and loaded through
# uniffi.
# The native Kotlin/Compose client over the shared Rust core. The store and sync
# engine are `inkwell-core` cross-compiled by cargo-ndk and loaded through uniffi.
#
# CI can only prove this BUILDS. A Linux runner cannot execute an APK, so anything
# about feel, touch or on-device correctness is an operator pass on an emulator or
@@ -15,12 +11,12 @@ name: Android
# unsigned debug one when it is not. That distinction is not cosmetic: two builds
# signed with different keys cannot replace one another, and bridging that gap
# means uninstalling first — which deletes the app's database and every local note
# with it (Scribe issue 2803).
# with it.
on:
push:
# NO `paths:` FILTER — the `decide` job below reads the real file set instead.
# See desktop.yml for why, and 85ead4d for what the duplication cost.
# See desktop.yml for why.
branches: [dev, main]
workflow_dispatch:
@@ -32,15 +28,15 @@ env:
# Silences the JDK 22+ "restricted method in java.lang.System has been called"
# warning that Gradle 9.1's bundled native-platform jar trips at launch. This
# targets the LAUNCHER JVM, which is why org.gradle.jvmargs in
# gradle.properties is not enough on its own (Minstrel hit the same thing).
# gradle.properties is not enough on its own.
JAVA_TOOL_OPTIONS: "--enable-native-access=ALL-UNNAMED"
jobs:
# Does the APK need rebuilding, or is the channel already serving this source?
# See the equivalent job in desktop.yml — same reasoning, same replacement of a
# hand-kept `paths:` filter with the one file set in `packaging/version.sh`.
# See the equivalent job in desktop.yml — same reasoning, the same one file set in
# `packaging/version.sh`.
#
# The guard runs here so it covers the skip path too (§6.3).
# The guard runs here so it covers the skip path too.
#
# NOTE THE COUPLING WITH ci.yml: when this lane builds, its server-image job dispatches
# ci.yml so the image bakes in the APK just published. When it SKIPS, no dispatch
@@ -74,12 +70,12 @@ jobs:
# The core ships inside the APK (through android/ffi), so its checks have to gate
# the APK HERE, in this workflow's graph. desktop.yml runs them too, but a red run
# there cannot stop this lane publishing (rule 177, #5237).
# there cannot stop this lane publishing.
#
# On ci-tauri, not ci-rust-android: these are host-target tests, and on Linux the
# core's native-tls is OpenSSL, whose headers only ci-tauri carries. The Android
# image has OpenSSL vendored for the Android targets alone (run 8663 failed at
# `openssl-sys` trying this there). Same Rust pin, same Cargo.lock.
# image has OpenSSL vendored for the Android targets alone, so `openssl-sys` fails
# there. Same Rust pin, same Cargo.lock.
rust:
name: Core and FFI clippy and tests
needs: [decide]
@@ -100,11 +96,11 @@ jobs:
name: Kotlin + Rust (APK)
needs: [decide, rust]
if: needs.decide.outputs.build == 'true'
# runs-on is only a scheduling label (Label Model B). flutter-ci is the
# runs-on is only a scheduling label. flutter-ci is the
# proven-working label that can pull our container images.
runs-on: flutter-ci
container:
# The image repurposed from ci-tauri-android in M12 step 3: Rust + the four
# Rust + the four
# Android ABIs + cargo-ndk + SDK/NDK + JDK 25 + ktlint + detekt.
image: git.fabledsword.com/bvandeusen/ci-rust-android:1.97
@@ -119,7 +115,7 @@ jobs:
- uses: actions/checkout@v4
with:
# Derives a version, so it needs the whole history — see the note in
# desktop.yml. Depth-1 is silently wrong here, not loudly broken (§6.1).
# desktop.yml. Depth-1 is silently wrong here, not loudly broken.
fetch-depth: 0
- name: Cache Gradle and Cargo
@@ -143,7 +139,7 @@ jobs:
env:
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
run: |
# TWO CLOCKS, ON PURPOSE (note 3127 §2). The NAME answers "is this the
# TWO CLOCKS, ON PURPOSE. The NAME answers "is this the
# same code?", so it comes from the COMMIT and a dev build and the main
# build of one commit read identically. The CODE answers "may this be
# installed over that?" and must be monotonic BY CONSTRUCTION, because
@@ -161,7 +157,7 @@ jobs:
echo "label=release" >> $GITHUB_OUTPUT
# An optimised .so for the APK people install. The cargoNdk task keeps
# the symbols uniffi reads its metadata from, which the release
# profile would strip (run 4077, Scribe #2810).
# profile would strip.
echo "profile=release" >> $GITHUB_OUTPUT
echo "keystore=/tmp/inkwell-release.jks" >> $GITHUB_OUTPUT
echo "apk=android/app/build/outputs/apk/release/app-release.apk" >> $GITHUB_OUTPUT
@@ -190,7 +186,7 @@ jobs:
run: ./gradlew generateUniffiBindings -PINKWELL_CARGO_PROFILE=${{ steps.build.outputs.profile }}
# The image's PINNED CLIs, not Gradle plugins. ci-rust-android carries both
# (M12 step 3) precisely so this lane needs no second image, and going
# precisely so this lane needs no second image, and going
# through Gradle plugins would mean a second version of each tool resolved
# at build time and kept in lockstep with the image's by hand.
#
@@ -210,7 +206,7 @@ jobs:
#
# DEBUG regardless of what is being packaged: AGP creates unit-test tasks
# only for `testBuildType`, which is debug, so `testReleaseUnitTest` does
# not exist (run 4082). It costs one extra Kotlin compile and buys the
# not exist. It costs one extra Kotlin compile and buys the
# type-check on the debug variant, which is the one an emulator build
# would use.
run: ./gradlew testDebugUnitTest -PINKWELL_CARGO_PROFILE=${{ steps.build.outputs.profile }}
@@ -233,16 +229,15 @@ jobs:
# simply refuses to install over the app already on the phone — a failure
# that otherwise only shows up on the device, after the run is green.
#
# And FAILS unless there is exactly one signer and it is the release key
# (family idea #5103, practice 3). Printing alone was not a check: a build
# signed with any other key would have gone on to be staged and published,
# and every phone would have refused it. The steps that publish come after
# this one in the same job, so a failure here stops them.
# And FAILS unless there is exactly one signer and it is the release key.
# Printing alone is not a check: a build signed with any other key would go on
# to be staged and published, and every phone would refuse it. The steps that
# publish come after this one in the same job, so a failure here stops them.
- name: Check the signing certificate
if: steps.build.outputs.keystore != ''
env:
# The release certificate's SHA-256, as apksigner printed it for the
# signed APK in run 8753 (CN=Bryan Van Deusen, O=fabledsword). Every
# The release certificate's SHA-256, as apksigner prints it for a signed
# APK (CN=Bryan Van Deusen, O=fabledsword). Every
# installed copy carries this certificate. It only changes if the key
# does, and then every install has to be replaced by hand anyway.
RELEASE_CERT_SHA256: 408a5835ea1627f329d9fba4f00712b096faf2d30624ec6f3a8b2b5f8bb15caa
@@ -285,10 +280,9 @@ jobs:
# publishing an unsigned APK would offer people something they cannot install
# over what they already have.
#
# `stable` from main is new in M314 step 3, and it is what lets the server image
# bake in a client that matches its own channel: a :latest image fetches the APK
# from `stable`, a :dev image from `dev`. Before this, main published no APK at
# all and every image — stable included — baked in the dev one.
# Publishing `stable` from main is what lets the server image bake in a client
# that matches its own channel: a :latest image fetches the APK from `stable`, a
# :dev image from `dev`.
- name: Publish to the channel for this branch
if: (github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main') && steps.build.outputs.keystore != ''
working-directory: .
@@ -300,18 +294,17 @@ jobs:
*) channel=dev; RELEASE_PRERELEASE=true ;;
esac
# The channel's release TAG, not its name: `dev` publishes on `dev-rolling`
# (packaging/channel-tag.sh). A tag named `dev` shadowed the branch.
# (packaging/channel-tag.sh). A tag named `dev` would shadow the branch.
RELEASE_TAG="$(sh packaging/channel-tag.sh "$channel")"
export RELEASE_TAG RELEASE_PRERELEASE
echo "Publishing the APK to the $RELEASE_TAG channel."
bash desktop/packaging/publish-release.sh
- name: Upload the APK
# Stock action: it works on this forge since the runner moved to
# gitea/runner 3.x, which edits upload-artifact's client-side GHES refusal
# out of the action bundle (Scribe snippet #2271). Never @v3 — it reports
# success while Gitea serves artifacts back only through the v4 API, so the
# upload is stored and invisible (Scribe 2270).
# Stock action: it works on this forge because gitea/runner 3.x edits
# upload-artifact's client-side GHES refusal out of the action bundle. Never
# @v3 — it reports success while Gitea serves artifacts back only through the
# v4 API, so the upload is stored and invisible.
uses: actions/upload-artifact@v7
with:
# The APK's variant, NOT the Cargo profile. They are the same word for
@@ -338,11 +331,8 @@ jobs:
# from here — that is the other half of this.
#
# No `always()`: a red core check or APK build means no image for this
# commit (rule 177, #5419). This used to run regardless, on the reasoning that
# a broken Android lane shouldn't block server delivery, and the image would
# bake in the previous client. Run 8839 is what that meant: `:<sha>` published
# for 0ecdbfe while its APK lane (8835) was red, holding a client that wasn't
# from that commit. The previous `:dev` stays where it is; fix forward.
# commit. Building regardless would publish a `:<sha>` holding a client that
# isn't from that commit. The previous `:dev` stays where it is; fix forward.
#
# Not skipped on tags: every ref that builds an image needs the call, since
# ci.yml stands down on these pushes and nothing else builds one.