fix(release): bind package and manifest coherence (010 draft, stacked 007-010)#128
Open
donbeave wants to merge 4 commits into
Open
fix(release): bind package and manifest coherence (010 draft, stacked 007-010)#128donbeave wants to merge 4 commits into
donbeave wants to merge 4 commits into
Conversation
OFFLINE DRAFT for plan 007 — code + tests only, not deployed/pushed.
Carry source_path and derive CacheActionKind {Root,Restore,Save} in the
native invocation; dispatch phases and outputs by kind (root restore+post,
restore-only, save-only with no prior restore); scope cache entries by a
deterministic version segment H(action_ref || RUNNER_OS || RUNNER_ARCH ||
normalized declared paths) below <trust>/caches/<repo>; replace the save
string-sniff with an explicit SaveOutcome enum plus lock-wait/copy/total
timing.
Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
OFFLINE DRAFT for plan 008 — code + tests only, not deployed/pushed. Builds
on the 007 cache draft.
Strict fail-closed mise policy (new crate::mise): exact YYYY.M.D version rule
(omitted -> fleet pin 2026.7.7, no live 'latest' lookup; 008-R5), install_args
constrained to tool keys committed in the adjacent lock, and locked-config
resolution requiring a tracked+clean adjacent lock with [settings] lockfile=true
(toml-parsed, never regex). Wire the version+install_args shape rules into the
capability manifest via InputRule::Predicate; gate native_mise install fail-
closed before any command.
Persistent per-version mise binary store mise/binaries/<trust>/<repo>/<os-arch>/
<version>/mise (+metadata.json), mounted at /opt/velnor/mise-binaries, added to
leases and the mise GC budget. setup_mise_script now copies the read-only baked
/opt/mise/bin bootstrap to same-fs staging, self-updates to the exact version
with project config disabled, verifies version+SHA-256, atomically publishes
0555, and reuses on version/hash match (corruption fails closed).
Locked installs only: mise install --locked --yes under MISE_LOCKED/
MISE_LOCKED_VERIFY_PROVENANCE; delete the mise.run bootstrap and the cosign
curl / mold apt / just apt+cargo fallbacks — mold/just/cosign become admitted
locked mise keys verified by version. Add docker/job-mise.{toml,lock}
(linux-x64/arm64) and rewrite the Dockerfile to install the whole toolchain
fail-closed; add lockfile=true to root mise.toml.
Add toml workspace dep. Tests: mise policy unit tests; a shell-level reuse
test (isolated stores + fake updater proving one publish, identical hash/path,
two locked installs, zero prompts, no bootstrap write) + corruption fail-closed;
manifest/container/cache coverage; updated the mise-self-update, curl/apt 5-exec
integration, mold, just and cosign script tests to the new locked contract.
Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
Replace the split validation boundary (flat top-level check + local-only
recursive preflight + redundant post-download re-resolution, all sharing one
bypass switch) with a single typed, read-only admission graph completed before
any step side effect.
- manifest.rs (v6): structurally immutable identities. assert_manifest_integrity()
at startup rejects any non-__native ref that is neither a full 40-hex SHA nor an
explicit plan-041 fixture-transition tag, plus duplicate identities and unsafe
subpaths. Adds action subpaths (actions/cache: root/restore/save) and a
reusable-workflow schema; validate_resolved_action now takes source_path.
- admission.rs (new): ActionIdentity/AdmissionNode/Edge/Ancestry, injectable
ActionMetadataSource (Contents API in prod, fake in tests). admit_job() resolves
and validates every root (local and remote), recurses nested local+remote,
resolves defaults and ${{ inputs.* }} before child validation, bounds
depth/nodes, guards cycles, redacts received values, and admits the one approved
reusable workflow by repository/path/full-SHA and dispatched inputs.
- runner.rs: admit at the gate before lease renewal/leases/checkout/download;
planning consumes the graph and never re-resolves; delete the local-only
preflight and the post-download re-resolution; add side-effect counters.
- cli.rs/main.rs/debian: delete skip_capability_validation,
diagnostic_node_sidecar, allow_unknown_action_diagnostics and the unknown-JS
execution branch (keep the hard bail); pre-dispatch strict-env enforcement;
both .service units pin VELNOR_CAPABILITY_VALIDATION=strict; document only
strict in velnor.env and the strict-capability contract.
Draft only: not pushed, not deployed, no B17/sentry changes.
Co-authored-by: Codex <codex@openai.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
OFFLINE DRAFT for plan 010 — code + tests + drafted workflow/host-script files only. Not pushed, not deployed; no B17/sentry/host mutation, no APT publication. Builds on the 007/008/009 runtime drafts. Non-circular release identity model (new src/release.rs, mod release): - build.rs + `release-build` feature embed the exact 40-hex source SHA and `v*` tag; the build fails unless tag == crate version == Cargo.lock and the tree is clean/unambiguous. Default (feature-off) builds stamp `development` and cannot emit a publishable record (proven by unit test). - Canonical serde schemas BuildIdentity/ArchitectureIdentity/ReleaseRecord/ PublicationRecord/DeployedIdentity with validated SourceSha/Sha256Hex/ OciDigest newtypes (40/64-hex, sha256: prefix) and deny_unknown_fields. Deterministic canonical emit + digest; the record never contains its own digest (acyclic — the digest lives in the checksum sidecar, publication record, and deployed pointer). verify()/verify_record_bytes()/ verify_installed()/verify_publication_binds() map every single-field defect to a distinct, value-redacted CoherenceError. Atomic fsync+rename ReleaseStore activate/rollback keeps the exact prior tuple; no-clobber on divergent bytes. - New `release emit|assemble|verify-record|verify-installed|activate|rollback| export` CLI (cli.rs + main.rs). `capabilities export` gains the embedded source SHA + crate version. Manifest v6 -> v7: ExportManifest adds source_sha + crate_version identity; 009's structural integrity checks are unchanged. Version assertions updated. Single producer workflow (drafted, not run): release.yml is the sole tag coordinator — peels the tag to one commit; rejects ambiguity/non-HEAD/reused- tag/dirty/crate-tag drift; builds one multi-platform GHCR image with version/ revision/source/manifest-hash labels + captured digests; builds per-arch tar/deb under release-build with the folded empty-deb guards (binary present, canonical Dockerfile present, size>1MB, arch/version match); assembles + re- verifies ONE release record; creates the release once, accepting an existing tag only on exact digest match (never --clobber). release-deb.yml is deleted and the source holds NO APT credential (removes GH_VELNOR_APT_TOKEN — N6). Transactional host activation (drafted): debian/preinst refuses an upgrade unless daemons are drained and an exact prior rollback tuple exists; postinst never builds/never restarts — it validates installed binary/package/manifest identity offline and stages; both .service units run `release verify-installed` as ExecStartPre so a mixed old/new tuple never starts. Tests: determinism, malformed SHA/digest/version, crate/tag drift, per-arch completeness, every single-field mismatch, missing checksum/record, non- canonical bytes, OCI-label mismatch, publication binding, coherent rollback, redacted diagnostics — all from fixed fixture bytes (no real tagged build). fmt + clippy(-D warnings) + actionlint clean; nextest 790 passed. Co-authored-by: Codex <codex@openai.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Plan 010 — bind Velnor package and manifest coherence
One release commit binds, in one acyclic chain: source SHA → crate version →
per-arch binary/deb digests → OCI image digest → compiled-manifest hash → APT
coordinate → deployed export.
Rust core (offline-testable)
build.rs+release-buildfeature: embeds the 40-hex source SHA +v*tag; build fails unless tag == crate version ==
Cargo.lockon a clean,unambiguous tree. Default builds stamp
developmentand cannot emit a record.src/release.rs(+src/release/tests.rs): validatedSourceSha/Sha256Hex/OciDigestnewtypes;BuildIdentity/ArchitectureIdentity/ReleaseRecord/PublicationRecord/DeployedIdentityserde schemas (
deny_unknown_fields); deterministic canonical emit + digest(the record never contains its own digest — acyclic);
verify/verify_record_bytes/verify_installed/verify_publication_bindsmap every single-field defect to a distinct redacted error; atomic
fsync+rename activate/rollback with no-clobber.
cli.rs/main.rs:release emit|assemble|verify-record|verify-installed|activate|rollback|export.manifest.rsv6 → v7: export addssource_sha+crate_version; 009integrity checks intact.
Drafted infra (not executed)
release.ymlis the sole tag coordinator: peel/ambiguity/non-HEAD/reused/dirty/crate-drift gate; one multi-platform GHCR image with
version/revision/source/manifest-hash labels + captured digests; per-arch
tar/deb under
release-buildwith the folded empty-deb guards; assemble +re-verify one record; create-once/no-clobber.
release-deb.ymldeleted;GH_VELNOR_APT_TOKENremoved (N6).debian/preinst(new) refuses an upgrade unless daemons are drained and aprior rollback tuple exists;
postinstnever builds/never restarts;both
.serviceunits runrelease verify-installedasExecStartPre.Gates
fmt / clippy
-D warnings/ actionlint /git diff --checkclean; nextest790 passed.
Divergences to reconcile before real execution
0.1.58while tags are0.1.121— a real taggedrelease-buildfails the coherence gate until crate +Cargo.lockarebumped (the gate working as designed).
verify-installedExecStartPre is a hard gate: a plainapt install+systemctl startwithout a staged coherent record will not start.legacy-observedbootstraptuple from B17 (not synthesized offline).
the deb ships only acyclic
build-identity.json+manifest.json, stagedout-of-band by
release activate.🤖 Generated with Claude Code