Skip to content

feat(lib): define a complete canonical asset-hash representation #322

Closed

Description

@sakul-learning

Problem

PR #321 explored an opt-in canonicalAssetHashes scheme that frames entry type, relative path, payload size, and payload. That is useful follow-up work, but the current representation does not yet describe every artifact-affecting property:

  • regular-file records omit the Unix mode that archiveSync preserves in ZIP external attributes;
  • directories contribute no records, so adding or removing an empty directory is invisible.

A reproduced mode case changes the emitted archive bytes while retaining the same canonical asset hash:

{
  "canonicalHashBefore": "0D3C9E0875B72106054DC334DF123995",
  "canonicalHashAfter": "0D3C9E0875B72106054DC334DF123995",
  "canonicalEqual": true,
  "archiveBytesEqual": false
}

This work was split conceptually from PR #321 so that its symlink fix can retain the narrower compatibility-preserving approach: keep the historical content hash for trees without symlinks and combine separately framed symlink metadata under a tagged outer digest only for symlink-bearing trees.

Why Git/Nix do not have this hole

The framing idea is sound, but Git and Nix serialize a richer object model than (type, path, payload length, payload) alone:

  • A Git tree records each child's mode, name, and object ID. Directories are represented by tree objects, regular executable files differ through mode 100755 versus 100644, and symlinks use mode 120000.
  • Nix NAR serialization explicitly represents directory entries/node types and an executable marker for regular files.

Therefore copying only the payload framing pattern, without their metadata model, drops properties those formats include in their canonical representation.

Proposed scope

Design and introduce the canonical scheme in a dedicated, feature-flagged PR:

  1. Define which source-tree properties affect emitted ARCHIVE, DIRECTORY, and FILE assets.
  2. Frame directory entries explicitly, including empty directories.
  3. Include relevant preserved mode bits (at minimum executable state; preferably the exact permission mask if that is the emitted contract).
  4. Keep record ordering and path normalization deterministic across supported platforms.
  5. Specify root-symlink behavior.
  6. Add compatibility and behavior tests before enabling the future flag.

Acceptance criteria

  • File rename changes the canonical hash.
  • Entry-boundary shifts change the canonical hash.
  • File/symlink swaps and symlink retargeting change the canonical hash.
  • 0644 → 0755 changes the canonical hash when it changes emitted artifact metadata.
  • Adding/removing an empty directory changes the canonical hash for directory/archive assets where that directory is emitted.
  • Identical logical trees hash deterministically across repeated runs.
  • Legacy hashing remains available until the intended compatibility boundary.

Follow-up from #321.

Activity

  1. sakul-learning commented on Jul 11, 2026

    @sakul-learning
    ContributorAuthor

    @vincenthsh — opened as the feature-flagged follow-up to PR #321 so the current PR can retain only the compatibility-preserving symlink hash fix (option 3). The issue captures the missing Git/Nix metadata semantics and acceptance criteria.

  2. added a commit that references this issue on Jul 18, 2026
    76dd4ff
  3. added a commit that references this issue on Aug 10, 2026
    545d458
  4. github-actions commented on Aug 18, 2026

    @github-actions
    Contributor

    I'm going to lock this issue because it has been closed for 30 days. This helps our maintainers find and focus on the active issues. If you've found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

  5. locked as resolved and limited conversation to collaborators on Aug 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions