Project task list (ordered milestones)
Work top to bottom. Each milestone leaves the repo buildable. Per-crate
detail is in installer/TASKS.md and server/TASKS.md; what a task must
satisfy before it counts as done is in requirements.md.
M0 — Repo bootstrap
- Lay out the workspace:
shared/,installer/,server/ - Write the CLAUDE.md files (root, installer, server)
- Copy the legacy C# sources into
legacy/for reference -
git initand a first commit - Pin every dependency to an exact version and commit
Cargo.lock - Port the legacy GitHub Actions workflows (
ci.yml, plus the threecd-*deploy workflows)- Give the installer its own CI job: it is excluded from the workspace, so the server job’s fmt and clippy never reached it
- Run fmt, clippy, tests, version sync and the size gate there
- Leave the GUI tests out of CI — they need an interactive desktop
M1 — Shared protocol crate
- Make
PackageInfo,ManifestandApiErrorcompile - Unit-test the JSON round trip and
Manifest::visible_tofiltering - Get
cargo test -p pacenotes-sharedgreen
M1a — Protocol v2: install metadata
See root CLAUDE.md, “Install model”.
- Extend
PackageInfowithcategory,display_name,exclusive_groupandrequires- Add
gameas well, so one server can serve four games without every package appearing under every one
- Add
- Unit-test that old manifests still deserialize, and that absent optionals are not serialized
- Add
shared::PackageRef,PackageInfo::label(),required_refs()andManifest::get()
M2 — Server MVP (real files, no auth yet)
- Work through
server/TASKS.mdS1..S4 - Serve
GET /health,GET /packagesandGET /download/... - Check by hand with curl: health, anonymous listing, a byte-identical download, a 404 for an unknown package, and a rejected traversal
M3 — Server auth
- Work through
server/TASKS.mdS5..S7 - Add the libSQL schema and migrations, token hashing and the directory-grant check
- Add the admin CLI: mint and revoke credentials
- Add the admin CLI: grant and ungrant directories
- Integration-test the listing and download matrix across
{anon, no grants, one directory, several}
- Cover expired, revoked, ungranted, 404 and traversal while the harness is there
M4 — Installer skeleton (Windows)
- Work through
installer/TASKS.mdI1 (toolchain, resources, size gate) - Work through I2 and I2a (the UI per
installer/DESIGN.md) - Work through I3 (the WinHTTP client and
--selftest-http) - Open a Win32 dialog and pass the release size gate
- Fetch
GET /packagesthrough WinHTTP against a real server- Cover bearer auth and the 401, 404 and unreachable surfaces
- Keep it desktop-free so CI can run it
M5 — Installer cache and install engine
- Work through I4 (worker thread, progress and cancel)
- Work through I5 (api, std-only sha256, sha-keyed cache, verified downloads)
- Build the download scene from where packages sit on the server, and show each leaf’s state
- Build the configuration, progress and summary scenes
- Work through I6 (zip reader)
- Work through I7: the ledger, overlay extraction, one-time backup of files the ledger does not own, and uninstall from the ledger
- Make Apply a diff of selection against ledger, honouring exclusive
groups and enforcing
requires - Give each game its own configuration step, with no shared
abstraction over them
- RBR: edit the existing Pacenote config, preserving the rest
- Split downloading and installing into two screens with the
configuration step between them, and offer both routes on scene 3
- Not in the original plan: a download fills a cache, an install writes to the game folder, and a user should be able to do either without the other
M6 — Installer auth and polish
- Work through I8: use the access key as the bearer for every request
- Validate it with
GET /whoami, the only endpoint that can - Warn when a key is close to expiring, naming the date
- Validate it with
- Build the restore scene: go back to any earlier state, or remove everything, after seeing exactly what that would do
- Work through
installer/TASKS.mdI9 (polish) and I10 (release gate) - Audit the final size against the 700 kB gate
- Save a
cargo bloatreport todocs/bloat.txt
- Save a
M7 — Ship
- Write the deploy set: Containerfile, Podman Quadlet, Caddy site
block and
deploy/README.md - Build and push the image from CI, tagged with the git sha, so the
Quadlet pins a tag and a restart never rebuilds
- Build both the server and the installer in CI
- Store the build artifacts
- Installer: GitHub Release asset
- Server: private GHCR image only, no workflow artifact
- Source the executable from CI when the Quadlet deploys
-
deploy/pacenotes-update, the deploy key’s forced command - Roll back to
:previouswhen the new container is unhealthy - Fix
podman untagstripping:current(found by the test)
-
- Make publishing content mean running
manifest genalone, and say so indeploy/README.md - First tagged release through the whole pipeline: secrets set, GHCR package private, deploy job green
- Author the sidecar
.tomlfiles for the real packages: display names,exclusive_groupon the config variants,requireson the voice packs - Write reproducible release-build instructions for both binaries
- Smoke-test on a fresh Windows VM: download, install a subset, switch a config variant, uninstall, restore
- Rewrite
README.mdto match the application as it is - Publish every document in the repository as one mdBook on GitHub
Pages from CI, with the rustdoc API reference and the architecture
diagrams redrawn in D2 and rendered to SVG; no Python anywhere
- Copy
tools/into the server image build: the new workspace member broke it at v/2.0.1, and CI now loads the workspace from what the Containerfile copies
- Copy
Supply chain and operations
From the design review’s period 2. The findings themselves stay in
DESIGN-REVIEW.md.
- Sign the
.exewith Authenticode in CD and publish its SHA-256 (O2) - Pin the toolchain and the base images by digest (O2)
- Publish a versioned server artifact rather than a branch tip (O2)
- Bind Caddy’s plain-HTTP block to loopback, or remove it (O2)
- Make the
EnvironmentFilepath agree withdeploy/README.md(O2) - Confirm the default hostname stays under the project’s control, and never ship a provider hostname (C3)
- Back up
auth.dband the packages themselves (S9) - Document publishing by copy-to-
.tmp-then-rename (S4) - Document that renaming or moving a package folder is a breaking change, because the folder path is the package’s identity (P13)