build: make capsule archives reproducible - #1474
Conversation
There was a problem hiding this comment.
Pull request overview
Makes capsule packaging more deterministic by canonicalizing archive metadata and ordering.
Changes:
- Normalizes tar and gzip metadata.
- Sorts filesystem entries and adds reproducibility tests.
- Documents the change under
[Unreleased].
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
crates/astrid-build/src/artifact.rs |
Canonicalizes signed archive headers and gzip metadata. |
crates/astrid-build/src/archiver.rs |
Adds deterministic packaging and regression tests. |
CHANGELOG.md |
Documents reproducible capsule archives. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
22fd5dc to
a37911e
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Suppressed comments (1)
crates/astrid-build/src/artifact.rs:384
- The signing rewrite preserves only the owner-execute bit, while the packer treats any of
0o111as executable intent (archiver.rs:263). A valid unsigned archive whose file is group- or other-executable would therefore be silently rewritten to0644when signed, contrary to the executable-intent contract. Check all three execute bits here as well.
let mode = if entry_type.is_dir() || source_mode & 0o100 != 0 {
a37911e to
1f88458
Compare
1f88458 to
4be2d1e
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Suppressed comments (1)
crates/astrid-build/src/archiver.rs:129
- This only stabilizes archive-member order, but the production MCP packaging path still synthesizes
Capsule.tomlfrom the unsortedread_diriteration inmcp.rs:262-279. Two independently created, identicalcommands/trees can therefore emit different[[command]]order, changing the manifest and the final signed archive bytes despite identical source inputs. Sort those command entries before constructing the manifest and cover the actual conversion path with the reproducibility regression.
ordered_files.sort_unstable_by(|left, right| left.1.cmp(&right.1));
4be2d1e to
bde3106
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.
Suppressed comments (1)
crates/astrid-build/src/archiver.rs:378
- This unguarded test opens both files read-only before calling
File::set_times. On Windows, changing timestamps requires a handle with write-attributes access, whichFile::opendoes not request, so this regression fails there. Open both handles with write access (or gate the timestamp mutation).
File::open(&first)
.unwrap()
.set_times(std::fs::FileTimes::new().set_modified(changed_time))
.unwrap();
bde3106 to
b0c26e9
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.
Suppressed comments (1)
crates/astrid-build/src/archiver.rs:267
- On non-Unix targets this always returns
0o644, so packaging on Windows drops executable intent entirely; the new header test even codifies that by expectingrun.shto be executable only undercfg!(unix). This contradicts #1475's requirement that executable intent remain represented (and can make the same logical asset package differ between Windows and Unix). Please pass a platform-independent executable-intent input into the archiver, or otherwise avoid claiming/supporting this guarantee on targets where it cannot be recovered fromMetadata.
fn normalized_file_mode(metadata: &fs::Metadata) -> u32 {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
if metadata.permissions().mode() & 0o111 != 0 {
return 0o755;
}
}
0o644
Signed-off-by: Joshua J. Bouw <jjb@unicity-labs.com>
b0c26e9 to
a752a16
Compare
Linked Issue
Closes #1475
Related to #555
Summary
Makes
.capsulearchive bytes reproducible when the package inputs and signing identity are identical. Entry order within each package phase, recursive path order, tar metadata, permissions, and gzip metadata are canonicalized without changing the existing archive layout or install-time reader contract.This PR addresses deterministic capsule packaging. It does not claim cross-host reproducibility of every upstream compiler output, identity-independent signed bytes, or implement the broader future source-rebuild and distribution-verification mechanisms discussed in #555.
Changes
.capsulearchive structure and signature/digest verification behavior.CHANGELOG.md[Unreleased].Verification
cargo test -p astrid-build— 29 tests passed, including group-execute preservation through signing and deterministic MCP command manifest ordering.cargo clippy --workspace --all-features --all-targets --locked -- -D warningspassed.cargo fmt --all --checkandgit diff --checkpassed.AI / Tool Assistance
Assisted-by: Codex:GPT-5.6
Codex assisted with implementation analysis, deterministic archive construction, regression tests, compatibility review, review remediation, and local validation. Joshua reviewed the complete diff, scope, and validation before publication.
Checklist
[Unreleased]— or[Unreleased]rolled into a version section for a release PR; not applicable to docs/CI-only changes)Signed-off-bytrailer.