Commits
Use this shape:feat, fix, perf, refactor, docs, test, build,
and chore. Scopes name the affected area, such as mail, ui, or api.
Use ! and a BREAKING CHANGE: footer when a commit breaks a documented
interface.
Keep subjects brief and bodies focused.
Repository and tag authority
GitHubgh hosts the release workflow, artifacts, images, and release notes. A
release is not ready to dispatch until the signed annotated tag resolves to the
same commit locally and on GitHub. Push the release commit and that exact tag;
never recreate a tag independently after pushing it.
Changelog and versions
User-visible changes update the[Unreleased] section of
CHANGELOG.md
in the same pull request. Write for operators and call out required migration
steps directly.
A published core migration is immutable. A release normally adds at most one
numbered core migration; never rewrite or renumber one that shipped.
Suchi follows SemVer:
MAJOR: incompatible stable-interface changes after 1.0MINOR: features and potentially breaking pre-1.0 changesPATCH: fixes and internal changes
Stable-v1 database compatibility
This section is the release authority for accepted core database states. The signed pre-v0.1.0 SQL, tags and release artifacts remain byte-identical. v0.1.0 embeds frozen compatibility SQL incore/db/compatibility/0002–0005; its declarative baseline is active migration
0001_baseline.sql, user_version = 1, lineage stable-v1. v0.2.0 adds active
0002_metadata_cleanup.sql and 0003_automation_asks.sql and targets
user_version = 3. v0.3.0 adds 0004_product_foundations.sql and targets
user_version = 4. v0.4.0 adds 0005_product_refinements.sql and targets
user_version = 5 without changing that lineage.
The exact stable schema-1 fingerprint is
9d2a6320eae1582b0f294caa6ed518b5a73ab3dbe6597c9ca1a36cc563e03240.
The development schema-2 fingerprint is
645e7798d663fff83d120182f661e7148fbd68d263517de9d44783d80aaebae3.
The v0.2.0 schema-3 fingerprint is
cfbdda9e3e80f35be33f763ad14c1c0d83a22ead62e91d04f6dc900331742a82.
The v0.3.0 schema-4 fingerprint is
360611dcca51a4ce23d0114e58c82b5208ce72e7575afa25e0c350be7d3100b4.
The current schema-5 fingerprint is
8f13f6a89fc56a04e027b0cdb19156a405445b8ee6bf6a80629f32e54c1e3d04.
The one admitted pre-identity schema-4 boundary remains
5d6ac98308eb644b030092178792a46f36e6f8f5041411f020ed2dcf216f46c2.
Startup admits only these exact states:
“Canonical” means the complete normalized core
sqlite_schema fingerprint, not
only the integer version or a claimed lineage row. Intermediate, partial,
spoofed, corrupt and future schemas are rejected before mutation and do not get
an adoption snapshot. Extension objects created after
_suchi_extension_migrations are outside the core fingerprint; core objects
rebuilt during adoption remain part of it regardless of their replacement
rowids.
Frozen compatibility migration 0005 rebuilds users with nullable
issuer/subject and rebuilds audit_events with retained-event metadata. It
copies numeric user and audit IDs exactly, so foreign-key and logical owner
references stay stable. Active migration 0002 adds version-family storage and
indexes, validates the predecessor graph, assigns deterministic family keys only
to existing multi-row histories, and leaves standalone documents without a
family key. Active migration 0004 adds title ownership and its revision guard.
Active migration 0005 adds sealed local-auth TOTP seeds and digested recovery
codes. All accepted paths reach the same fresh schema-5 fingerprint.
Every admitted pre-stable database passes integrity and foreign-key checks, gets
an exclusive mode-0600 SQLite snapshot beside the live database, and applies
the remaining compatibility steps, active migrations 0002–0005, stable lineage
and schema version 5 in one immediate writer transaction. Startup then checks
integrity, foreign keys and equality with the fresh schema-5 manifest. Failure
rolls back the transaction and retains the snapshot. Fresh and already-current
databases do not create adoption snapshots. Stable-lineage forward upgrades also
do not create an adoption snapshot; keep the operator’s complete backup before
upgrading.
Before release, verify fresh initialization and restart, all five pre-stable
paths, stable schema-1/2/3 forward upgrades, data/ID/family/title-ownership
preservation, snapshot creation and failure, fault-injected rollback,
foreign-key restoration, cycle rejection without a version bump, invalid-state
rejection without writes, and final manifest equality. Keep the operator’s
complete backup containing SQLite, blobs, the credential key and configuration;
an adoption snapshot is database-only. See
Storage and transaction invariants
for runtime invariants and Backup and restore
for operator procedure.
Major-version schema epochs
Suchi resets migration history at major releases instead of carrying every old upgrade path forever:- The final release of the outgoing major is the bridge. It accepts the last
canonical schema, applies one reviewed conversion, and writes an
application-owned marker such as
schema_state(epoch, version). - The new major starts from one declarative baseline. Fresh installs create it directly; the normal runtime drops older migrations, fingerprints, and release-specific checksums. Git tags retain that history.
- Migrations within the new major use the application-owned epoch/version
marker rather than
PRAGMA user_version. Some explicit compatibility marker remains mandatory so partial or hand-edited databases fail closed. - Older databases upgrade through the bridge release. If direct legacy conversion becomes necessary, it belongs in a separate offline upgrade command, not normal server startup.
- Repeat only at the next major boundary: v1 carries v1 migrations, v2 starts a new epoch, and so on.
Already-adopted pre-identity archives
An untagged archive may already carryuser_version = 1 and stable-v1 while
still matching the pre-identity fingerprint. Automatic startup deliberately
rejects that state: it has crossed the old adoption boundary and does not have a
new automatic pre-adoption snapshot.
Use only the reviewed manual path:
- Preserve a live browser session before the window if an existing OIDC account must be bound; verified-email fallback is intentionally absent.
- Stop Suchi and every CLI/import writer. Require the exact pre-identity fingerprint, valid lineage/version, clean integrity and foreign keys, and the reviewed 0005 checksum before changing the database.
- Copy the complete quiescent data directory and effective configuration to
verified storage outside
DATA_DIR; keep the old binary/config. A normal live database copy is not rollback. - Apply the exact 0005 SQL through the table-rebuild transaction, restore
user_version = 1andstable-v1, then require the exact stable schema-1 fingerprint, integrity, foreign keys, stable user IDs, and expected counts before starting any writer. - Start the current binary. It admits that exact schema-1 state, applies active
migrations 0002–0005, and requires the schema-5 manifest. From the preserved
session, use Sync from identity provider and verify the same user ID gains
issuer/subject, a retained
user.oidc_boundrow, a replacement cookie, and unchanged ownership.
Release checklist
Suchi Companion has an independent version and signed tag. Its release gates, artifact checks and coordinated store publication order live in the Companion release runbook; do not duplicate them here.-
Build and verify the web app from the release sources:
core/ui/spa/distremains ignored. CI builds it once from the immutable release revision, passes that artifact to every tagged binary build, and rebuilds it in the pinned Bun stage for container images. -
Move the
[Unreleased]entries under the new version and date. UpdatedevelopmentVersionindistro/cmd/suchi/main.goto the current source version line with a-devsuffix. -
Review every OCR, content and LLM pipeline revision changed since the previous
release.
distro/cmd/suchi/pipeline_proposals.gois the tagged automatic reminder policy:- Keep
0when that pipeline has no recommended automatic upgrade. - Set a field to the current pipeline revision when existing successful results should receive a Processing update reminder.
- Leave an earlier positive recommendation unchanged when a newer processing change is not worth prompting. This preserves the earlier recommendation for archives that skipped releases without opting in the newer revision.
- Keep
- Confirm the shipped Microsoft OAuth registration still permits personal and work/school device-code sign-in.
-
Run the local verification suite:
- Manually exercise behavior that depends on external services or accounts, including Microsoft sign-in, configured email providers, and hosted LLMs affected by the release.
- Recheck public size, memory, startup, network, and feature claims against the accepted benchmark and the final build.
-
Commit the release metadata on
main, create a signed annotated tag, and push the exact commit and tag to GitHub: -
Dispatch the manual release workflow:
This verifies that the local and GitHub tags resolve to the same commit. Use
PUBLISH=falseto validate the signed tag and release gates without packaging or publishing release artifacts. The checked-in proposal policy remains identical across every published binary and image. Zero thresholds do not cancel existing reminders, and explicit rescans remain available.
anydoc export target. Those binaries and licenses are packaged for standalone
downloads. The image job downloads only that run’s AnyDoc artifacts, verifies
their checksums, and supplies them as an anydoc named build context for both
runtime variants. Docker selects the matching amd64 or arm64 directory;
missing or damaged artifacts fail the build instead of triggering recompilation.
Plain local Docker builds omit that context and still compile from pinned source.
Release builds inject the version and source revision into the Go binary.
Settings reads the identity from /api/whoami, showing only the version for
tagged production releases. Unversioned builds use the source’s
developmentVersion and append the short revision
when available. suchi version likewise retains an injected release version and
adds the revision for development builds. Docker has no .git directory, so
manual builds must pass REVISION to retain source identification and VERSION
for a tagged release. Local VCS builds without an injected version use the source
version line, not Go’s tag-derived module pseudo-version. Missing development
revisions are labeled unavailable, not guessed.
Publishing container images without Actions
GHCR images can be built and pushed directly when Actions is unavailable. Build from the release tag, never from a different branch with the release version injected: the image contents must agree with the Git tag. The Dockerfile pins base-image digests, the AnyDoc source commit and checked third-party archives. Go and Cargo dependency checksums verify downloaded source. Alpine and Debian runtime packages still resolve from their live repositories, so these controls provide traceable inputs, not byte-for-byte reproducibility from a source tag. The signed published image digest and its provenance/SBOM are the release artifacts to verify and deploy. Set the release coordinates on every build machine:write:packages. The login username is the personal account that owns the
token, which may differ from the organization in IMAGE:
Build on native architecture hosts
Rust compilation foranydoc is very slow through QEMU. A cancelled
cargo build step is not cached because its Docker layer never completes. For
a faster multi-architecture release, build AMD64 on an x86-64 Linux host and
ARM64 on an Apple Silicon or Linux ARM64 host, push temporary architecture
tags, and combine them into the public manifest.
On the x86-64 Linux host:
$IMAGE:$VERSION-full.
Complete image signing, checksums, SBOMs, release artifacts, and the GitHub
Release before changing a moving channel. Only then promote the stable versioned
manifests:
rc / rc-full. Never create a moving alias in the same command
that first publishes the version manifest.
The first successful native build seeds its architecture’s registry cache.
Later local releases import those caches, while the release workflow imports
both native caches for runtime layers and maintains its own GitHub Actions cache.
Release images obtain AnyDoc from the standalone artifacts described above.
For local source builds, the anydoc source commit and build instructions are
pinned independently of the Suchi version, so the completed Rust layer is reused
until that pin or recipe changes. Cargo registry and target cache mounts also
retain incremental work in the local BuildKit builder when a compile is
interrupted before its image layer can be exported.
The full image uses --target full and temporary tags ending in
-full-amd64 and -full-arm64.
The first command-line push creates a private, unlinked GHCR package by
default. In the package settings, make it public, connect it to the Suchi
repository, and grant that repository Actions access so later workflow pushes
can update it. Direct local publication does not reproduce the workflow’s
keyless Cosign signatures or GitHub release artifacts; handle those separately
before calling the release complete.
Automation
checks.ymlruns the complete repository validation graph on pushes tomain, pull requests and manual dispatch.ci.ymlruns formatting, tests, vet, lint, documentation release-metadata checks, UI checks and browser tests when called by it.smoke.ymltests the built images and ingest paths when called by it. Both variants are rebuilt with the exported AnyDoc context and an unusable Rust base to catch accidental recompilation; binary and license bytes must match the export before the usual ingestion checks run.restore-drill.ymlvalidates backup restoration when called by it.release.ymlpublishes only when manually dispatched from an existing signed annotated tag. Its preflight repeats the complete reusable validation graph, race tests, security scans, local smoke and benchmark gates, Docker checks and documentation validation. Only jobs that publish releases, packages or keyless signatures receive the corresponding write permissions.
.github/release-signing-key.asc and the expected full fingerprint in
hack/verify-release-tag.sh. It rejects missing, lightweight, unsigned,
wrong-key, locally divergent and remotely divergent tags.
Images wait for both binary matrices before publication and reuse the AnyDoc
matrix’s verified binaries rather than rebuilding them. Release runs are
serialized because moving image channels are shared mutable state.
Validation-only tag runs execute the reusable checks and release preflight, then
skip release notes, binary and image packaging, the artifact collector, GitHub
Release creation, and channel promotion. Reusable checks may carry their
short-lived SPA build between jobs; it is not a release artifact.
For a security hotfix, branch from the affected tag, land the fix and changelog
entry, publish the patch release, then merge the fix back into main.