main independently wrote its own docs/handoff/20260615-handoff-70.md the same day, so my global-sequence handoff-70 was an add/add conflict waiting to merge. Relocate it to docs/ci/handoff/20260615-handoff-ci-01.md (its own namespace, like docs/ci/adr) + a README index. main's handoff-70 is untouched; the merge becomes conflict-free.
6.0 KiB
CI subproject handoff — 2026-06-15 (ci-01)
First handover for the CI / release subproject (the ci branch). Kept in
docs/ci/handoff/, a namespace separate from the project's global
docs/handoff/ session sequence so it can't collide with main's numbering —
the same split as docs/ci/adr/, and needed for the same reason: main
independently wrote its own handoff-70 this same day (just as it took
ADR-0049), which would have collided.
A dedicated infrastructure session that built the project's entire CI/CD
pipeline on the self-hosted Gitea Actions runner — from nothing to a live
gate plus a six-target cross-platform release. Net: the CI /
requirements.md TT5 item and D1/D2 are now done; D3 and a
couple of TT5 tails remain. Decisions are recorded in the sibling ADR namespace
docs/ci/adr/ (ADR-ci-001/002/003).
§1. State at handoff
Branch: ci (worktree). main has been merged into ci (commit
138e766, clean — ci and main touched disjoint files) so the gate runs
against current main before CI lands there. Working tree clean except the
in-progress doc updates from this handoff. Pushes/promotion are the user's
step.
Gate verified locally on the merged code: clippy -D warnings clean;
cargo test 2488 passing / 0 failing / 1 ignored (the long-standing
friendly doctest). main's features came in with their tests (2424 → 2488).
Pipeline (.gitea/workflows/):
build-ci-image.yaml— builds + pushes the CI image (node:22-bookworm-slim- single-user nix + the flake's devShell pre-warmed) to the Gitea registry. Triggers only on image-input changes (Dockerfile / flake / toolchain).
ci.yaml— the gate:clippy -D warnings+cargo test, branch pushes + PRs (docs-only changes skipped).release.yaml— on av*tag:test→buildmatrix over the four non-macOS targets viacargo-zigbuild, upload to the Gitea release.release-macos.yaml— workflow_dispatch (tag input) on the Tart Apple-Silicon runner (runs-on: macos): test → build both*-apple-darwin→ de-nixlibiconv+ ad-hoc re-sign → upload.
Verified live this session: the 4-target release published 8 assets
(binary + .sha256 each) for tag v.0.0.0-citest3; the macOS build was proven
portable (system-only deps) + signed + launches on the runner.
§2. What was built (and the non-obvious bits)
- Nix flake (ADR-ci-002, relocated from a would-be
mainADR-0049): one pinned toolchain (1.95.0) for dev and CI;cargo-zigbuild+zig(Linux only) for the cross targets;apple-sdkon darwin. - Runner facts (ADR-ci-001): jobs run inside a container (
ci-public→catthehacker/ubuntu), so host nix is unreachable — hence the baked image. The Mac runner is host execution; its label ismacos(:hostin the registration is the act_runner backend, not part of the label). - Cross-compile (ADR-ci-003):
cargo-zigbuildfor the 4 non-macOS targets. Windows needs an emptylibsynchronization.astub (ci/winstub/, wired via.cargo/config.toml) — std links-lsynchronization, absent from rust-overlay's toolchain + zig's mingw, but forwarded bykernel32. - macOS (ADR-ci-003 amendment): built on real Apple hardware (Tart), so
the SDK is fully licensed — no osxcross grey area. The darwin stdenv bakes a
/nix/storelibiconvpath into the binary; the build rewrites it to/usr/lib/libiconv.2.dylib(install_name_tool) and re-signs ad-hoc (codesign -f -s -;install_name_toolinvalidates the signature, arm64 refuses unsigned). A guard fails the build on any remaining/nix/storedep. - Cache hygiene (Mac): the runner wipes the workspace each run, so cargo
target/never accumulates; the persistent nix store is bounded by generation (record the devShell in a persistent profile, keep the 2 newest vianix-env --delete-generations +2, GC the rest). First sweep reclaimed a ~3.8 GB one-time backlog of build scaffolding (source + build-only deps, not re-installed toolchains).
§3. Immediate next steps (user)
- Push
ci→ the gate re-runs in CI (should be green; no image rebuild — the merge didn't touch the flake/Dockerfile). - Promote:
git checkout main && git merge ci— a fast-forward (cialready containsmain) — then pushmain. CI goes live;release-macosbecomes dispatchable (workflow_dispatch needs the default branch). - First real release: tag
v0.1.0(auto-builds the 4 Linux/Windows assets), then dispatchrelease-macosforv0.1.0with the Mac up (adds the 2 macOS assets) → a full 6-binary release. - Cleanup: delete the
v.0.0.0-citest*test tags + their releases. - Runner-side: add
min-free/max-freeto the Mac's/etc/nix/nix.confas a hands-off nix-store backstop.
§4. Known gaps / follow-ups
- Versioning is not wired into the binary (flagged by the user). The release
git tag is nowhere in the produced binary — there is no
--versionflag, noCARGO_PKG_VERSIONuse anywhere insrc/, and the release workflows use the tag only for the release name + asset filenames (rdbms-playground-<tag>-<target>).Cargo.tomlis a staticversion = "0.1.0", decoupled from the tag. So av0.5.0tag yields a…-v0.5.0-…asset whose binary knows nothing of "0.5.0". To fix later: add a--versionflag, and inject the tag at build time (e.g. abuild.rsreading a CI-provided env, or bumpingCargo.tomlas part of tagging) so the binary and the release agree. - D3 packaging — Homebrew / Scoop / winget /
cargo binstallmanifests (asset naming is already binstall-friendly). - TT5 tails — Windows is build-only (no execution runner); Tier-4 PTY (TT4) is unwired in CI.
fmtgate — deliberately off (tree isn't stock-rustfmt-clean); revisit onmain.- Website → Cloudflare deploy — the separate, simpler workflow, still to do.