Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

RFD-0013 — Bivalent lockfile (content-hash + constructs-hash); workspace-local resolution

Committed Opened 2026-05-03 · Committed 2026-05-03

Question

How does Argon pin dependency versions, what does the lockfile capture, and how do workspaces resolve member packages?

Decision

Bivalent lockfile. Each lockfile entry carries two hashes:

  • Content hash — byte-level identity of the source. Catches “did the file content change.”
  • Constructs hash — Merkle root of the package’s exported declaration signatures. Catches “did the meaning change.”

Two packages with identical content hashes always have identical constructs hashes. Two packages with the same constructs hash but different content hashes mean cosmetic edits (whitespace, comments) without semantic change. Two packages with different constructs hashes always have different content.

Per-workspace lockfile, not per-package. A workspace’s ox.lock pins every member’s transitive dependencies. Workspaces with overlapping members share resolution.

Workspace members ALWAYS resolve locally. When a workspace contains both foo (member) and a transitive resolution that would have pulled foo from the registry, the local member wins. There is no version-pinning override that pulls a registry version of a workspace member.

Merkle root canonical ordering: alphabetical by qualified name. Constructs are sorted by their canonical qualified name before hashing. Reordering source-file declarations does not change the constructs hash.

Rationale

Two hashes for two distinct invariants. Content hash answers “did the bytes change.” That matters for cache invalidation and reproducible builds. Constructs hash answers “did the meaning change.” That matters for “is this package compatible with what depended on it.” A single hash conflates both into either over- or under-invalidation.

Workspace-local always wins. A developer working in a multi-package workspace expects edits to a member package to be visible across the workspace immediately. Resolving a member through the registry instead would silently snapshot the registry version, leading to “why aren’t my edits showing up” debugging cycles.

Alphabetical Merkle ordering. Any deterministic ordering would suffice; alphabetical is the convention with the lowest cognitive overhead. Authors don’t need to reason about declaration order to predict the hash.

Per-workspace, not per-package. Workspace members share dependencies. Per-package lockfiles would pin them independently and produce conflicts. One workspace lockfile means one resolution per workspace, consistent across members.

Consequences

  • ox.lock schema includes both hashes per entry.
  • Cosmetic edits to a package (formatting, comments) bump content hash without bumping constructs hash. Downstream consumers do not need re-resolution.
  • Semantic edits bump both hashes. Downstream consumers re-resolve.
  • Workspace dependency resolution prefers workspace members over registry resolutions for any member package. There is no “pin to registry version” escape hatch for workspace members.
  • Build artifacts go to target/ per-workspace, shared across members.