Skip to content

build: make capsule archives reproducible - #1474

Merged
joshuajbouw merged 1 commit into
mainfrom
codex/reproducible-capsule-archives
Aug 10, 2026
Merged

build: make capsule archives reproducible#1474
joshuajbouw merged 1 commit into
mainfrom
codex/reproducible-capsule-archives

Conversation

@joshuajbouw

@joshuajbouw joshuajbouw commented Aug 10, 2026

Copy link
Copy Markdown
Member

Linked Issue

Closes #1475

Related to #555

Summary

Makes .capsule archive 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

  • Preserve the established package phases while sorting filesystem entries by canonical archive path within each phase.
  • Normalize tar ownership, file and directory modes, timestamps, and header fields for both filesystem and synthesized entries.
  • Replace nondeterministic gzip headers with fixed metadata.
  • Preserve the existing .capsule archive structure and signature/digest verification behavior.
  • Add regressions proving independently constructed signed archives remain byte-identical across input ordering, filesystem mtime, and ordinary permission differences when the signing key is the same.
  • Add a regression proving a different signing identity intentionally changes signed archive bytes.
  • Add the change under 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 warnings passed.
  • cargo fmt --all --check and git diff --check passed.
  • Regression coverage compares complete signed archive bytes and validates canonical metadata and modes for filesystem and synthesized entries.
  • Existing sign/verify, tamper detection, archive/directory digest parity, symlink handling, and opaque-asset tests remain green.

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

  • Linked to an issue
  • CHANGELOG.md updated (entry under [Unreleased] — or [Unreleased] rolled into a version section for a release PR; not applicable to docs/CI-only changes)
  • I understand every change in this PR and can explain its design, risks, and validation.
  • I reviewed and tested any meaningful tool-generated output included in this PR.
  • Every non-bot, non-merge commit has a matching Signed-off-by trailer.

Copilot AI balanced review requested due to automatic review settings August 10, 2026 08:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread crates/astrid-build/src/archiver.rs
Comment thread crates/astrid-build/src/archiver.rs
Comment thread CHANGELOG.md Outdated
Comment thread CHANGELOG.md Outdated
@joshuajbouw
joshuajbouw force-pushed the codex/reproducible-capsule-archives branch from 22fd5dc to a37911e Compare August 10, 2026 10:56
@joshuajbouw
joshuajbouw requested a balanced review from Copilot August 10, 2026 10:58

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 0o111 as executable intent (archiver.rs:263). A valid unsigned archive whose file is group- or other-executable would therefore be silently rewritten to 0644 when 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 {

@joshuajbouw
joshuajbouw force-pushed the codex/reproducible-capsule-archives branch from a37911e to 1f88458 Compare August 10, 2026 11:32
@joshuajbouw
joshuajbouw requested a balanced review from Copilot August 10, 2026 11:33

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

@joshuajbouw
joshuajbouw force-pushed the codex/reproducible-capsule-archives branch from 1f88458 to 4be2d1e Compare August 10, 2026 12:02
@joshuajbouw
joshuajbouw requested a balanced review from Copilot August 10, 2026 12:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.toml from the unsorted read_dir iteration in mcp.rs:262-279. Two independently created, identical commands/ 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));

@joshuajbouw
joshuajbouw force-pushed the codex/reproducible-capsule-archives branch from 4be2d1e to bde3106 Compare August 10, 2026 12:30
@joshuajbouw
joshuajbouw requested a balanced review from Copilot August 10, 2026 12:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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, which File::open does 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();

@joshuajbouw
joshuajbouw force-pushed the codex/reproducible-capsule-archives branch from bde3106 to b0c26e9 Compare August 10, 2026 13:56
@joshuajbouw
joshuajbouw requested a balanced review from Copilot August 10, 2026 13:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 expecting run.sh to be executable only under cfg!(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 from Metadata.
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>
@joshuajbouw
joshuajbouw force-pushed the codex/reproducible-capsule-archives branch from b0c26e9 to a752a16 Compare August 10, 2026 14:23
@joshuajbouw
joshuajbouw requested a balanced review from Copilot August 10, 2026 14:23

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

@joshuajbouw
joshuajbouw marked this pull request as ready for review August 10, 2026 20:01
@joshuajbouw
joshuajbouw merged commit 78ce303 into main Aug 10, 2026
29 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Make signed capsule archive packaging byte-reproducible

2 participants