milestone 14: build, package, sign and publish releases
This commit is contained in:
@@ -16,7 +16,8 @@ PLAN §18 sets five:
|
||||
- blocklist lookup p95 < 1 ms;
|
||||
- cached response p95 < 5 ms;
|
||||
- memory with ~1M blocked domains < 100 MiB;
|
||||
- stripped static binary < 10 MiB per arch, < 15 MiB with the embedded frontend.
|
||||
- stripped static binary ≤ 10,485,760 bytes per arch, ≤ 15,728,640 bytes with
|
||||
the embedded frontend.
|
||||
|
||||
They are household-scale numbers, and they are deliberately unambitious. 100
|
||||
qps is far more than a house generates; the point of the target is not speed
|
||||
@@ -59,17 +60,38 @@ baseline for the machine development happens on, not a claim about the target
|
||||
platform; a Cortex-A76 is far slower and those numbers do not transfer.
|
||||
|
||||
CI does gate on the one performance property that *is* deterministic: binary
|
||||
size. The `cross` job strips the release binaries and asserts them under the
|
||||
§18 budgets. Size is a function of the input, not of the runner's mood, so it
|
||||
is exactly the kind of thing a shared runner can measure honestly.
|
||||
size. The `package` job builds the release artifacts and `zig build verify-dist`
|
||||
asserts both §18 budgets against them. Size is a function of the input, not of
|
||||
the runner's mood, so it is exactly the kind of thing a shared runner can
|
||||
measure honestly.
|
||||
|
||||
The budgets are asserted as exact byte counts, and the asset-free budget gets
|
||||
its own build against a generated empty assets directory rather than against
|
||||
`web/dist-placeholder`. The placeholder is not buildable by `dist` at all —
|
||||
that is the guard against a release shipping a stub admin page — and letting it
|
||||
back in through a size check would have defeated the guard for the sake of one
|
||||
number.
|
||||
|
||||
## What the test suite is
|
||||
|
||||
The blocking CI (Gitea Actions, `.gitea/workflows/ci.yml`) runs five jobs, all
|
||||
required: the Zig suite with `-Dintegration`; the same suite cross-built for
|
||||
aarch64 and executed under qemu-user; the frontend (format, lint, typecheck,
|
||||
121 vitest cases, build); the cross-build with the two stripped-size asserts;
|
||||
and a Docker smoke run that boots the image and polls `/api/health`.
|
||||
Every blocking check lives in `.gitea/workflows/gates.yml`, which is a
|
||||
`workflow_call` workflow with nothing in it but jobs. `ci.yml` calls it on push
|
||||
and pull request for `master`, and `release.yml` calls it before it builds
|
||||
anything publishable. That shape exists for one reason: a check that lived in
|
||||
`ci.yml` alone would be a check a release could skip.
|
||||
|
||||
Five jobs, all required:
|
||||
|
||||
- `test` — the Zig suite with `-Dintegration`.
|
||||
- `test-aarch64` — the same suite cross-built for aarch64 and executed under
|
||||
qemu-user, plain tier only.
|
||||
- `frontend` — format, lint, typecheck, the vitest cases, build.
|
||||
- `package` — `zig build dist` and `zig build verify-dist`, which is where the
|
||||
size budgets, the ELF static-linkage assert and the archive layout checks
|
||||
are.
|
||||
- `container` — builds the image, asserts the binary inside it is byte-identical
|
||||
to the one in the matching tarball, and smoke-tests it by booting the
|
||||
container and polling `/api/health`.
|
||||
|
||||
The Zig suite has three tiers, gated by build flags:
|
||||
|
||||
@@ -159,3 +181,32 @@ Two honest gaps remain, stated so nobody has to rediscover them:
|
||||
- There is no freshness check on `web/dist`. CI cannot embed a stale bundle,
|
||||
because the jobs that pass `-Dweb-dist` rebuild the frontend immediately
|
||||
beforehand. A local build can, and will do it without a warning.
|
||||
|
||||
## What a signed release does not prove either
|
||||
|
||||
The same distinction applies one level out, to the artifacts. A release is
|
||||
signed, and the signature is worth having: it says the artifact came from this
|
||||
project's pipeline and reached you unaltered. It does not say the binary was
|
||||
built from the source in this repository, because the machine that ran the
|
||||
build also held the signing key. An attacker with that machine produces
|
||||
something that verifies cleanly and contains whatever they put in it.
|
||||
|
||||
The control that closes that gap is a reproducibility gate — an independent
|
||||
build, in a different directory on a different machine, landing on the same
|
||||
bytes. It does not exist. It is a recorded deferral (`specs/milestone-14.md`
|
||||
ruling 12), not something nobody thought of, and until it exists no document
|
||||
here describes the build as reproducible: nobody has measured whether it is.
|
||||
The cheap inputs to reproducibility are already in place — `gzip -n`,
|
||||
`--mtime=@0`, `LC_ALL=C`, `TZ=UTC`, exact Zig and Node pins — which makes the
|
||||
gate cheap to add later and proves nothing on its own.
|
||||
|
||||
What the release pipeline is required to hold to is narrower: two runs of
|
||||
`zig build dist` on the same commit **in the same directory** produce
|
||||
byte-identical tarballs. Same-directory determinism is a much weaker property
|
||||
than reproducibility, and conflating the two is exactly the kind of claim this
|
||||
page exists to refuse.
|
||||
|
||||
[Verify a release](../how-to/verify-a-release.md) states the same limits where
|
||||
an operator will actually meet them, and gives the rebuild-and-compare recipe
|
||||
with the caveat that a differing hash is not evidence of tampering while this
|
||||
gap is open.
|
||||
|
||||
Reference in New Issue
Block a user