Skip to content

feat(shard): resolve from-commit and from-pr lines against their trees - #22

Merged
cursor[bot] merged 47 commits into
mainfrom
eb/shard-commit-trees-v2
Nov 5, 2026
Merged

cursor[bot] merged 47 commits into
mainfrom
eb/shard-commit-trees-v2

Conversation

@HaoZeke

@HaoZeke HaoZeke commented Nov 4, 2026 •

Copy link
Copy Markdown
Collaborator

What and why

stack shard planned a --from-commit line with no tree as its own shard.
petsc4py-3.25.0-foss-2026.1.eb then sat apart from the PETSc line it depends on.
The fixture list reads easybuild-easyconfigs 0d6b80c34f3553774e93162cf89088e2423ecf89 through --commit-cache.
PETSc and petsc4py share a shard, and PETSc is first.
A cache download that never receives an HTTP status exits 1 and writes nothing.

Before and after

34 lines. 11 --from-commit, 10 --include-easyblocks-from-commit only, 13 plain.
The robot path is that commit with p/petsc4py/petsc4py-3.25.0-foss-2026.1.eb removed.

Before, no --commit-cache:

shards=4 prelude=173 lines=34 opaque=1

PETSc is shard-03. petsc4py is shard-04 and opaque.

After, --commit-cache set:

shards=4 prelude=173 lines=34

PETSc and petsc4py are both shard-03. PETSc is before petsc4py.
No module is in two shards.
A second run with the same --generated-at writes the same files.

Tests

cargo +stable fmt --all --check exited 0.
cargo +stable clippy --locked --all-targets -- -D warnings exited 0.
cargo +stable test --locked --all-targets exited 0.
1529 passed, 0 failed, 10 ignored.
uvx prek run -a exited 0.
python3 scripts/gen-llms-txt.py --check exited 0.
The local mirror is the codeload tar.gz of commit 0d6b80c34f3553774e93162cf89088e2423ecf89 (8436966 bytes).
The shard command still downloads that URL into the cache.


Note

High Risk
Introduces cryptographic logging (Ed25519, Merkle proofs, file locking) and network-backed shard planning with shared caches—both are security- and correctness-sensitive operational paths.

Overview
Adds a per-stack software ledger (ledger append, head, show, verify): canonical JSON Lines, RFC 9162 Merkle leaves, Ed25519 signed heads under eb-stack-ledger-v1, POSIX writer locks, idempotent retries on build.event_id, and inclusion/consistency proofs. The same surface is exposed as four MCP tools; CI runs a dedicated ledger job.

stack shard now resolves --from-commit / --from-pr lines like EasyBuild: fetched easyconfig trees via --commit-cache, --commit-tree, and --pr-tree (codeload + GitHub API), places shared dependencies in the prelude with fetch flags preserved, and plans still-unresolved fetched lines as opaque with plan.json metadata and stderr warnings. Transport failures before HTTP status exit 1 without writing outputs.

Smaller fixes: packaging gate accepts Bundle recipes whose components carry complete checksums; max_parallel is recognized in recipe lint vocab; .gitignore ignores all hidden dirs except .github/; docs/indexes point at CONTRIBUTING.md instead of AGENTS.md; workspace path resolution handles symlinked roots.

Reviewed by Cursor Bugbot for commit 07787c1. Bugbot is set up for automated code reviews on this repo. Configure here.

`ledger append`, `ledger head`, `ledger show`, and `ledger verify` keep
one JSON Lines file per stack. Each record is written as canonical JSON
(keys sorted, no whitespace, integers only) with `index` and `id`
filled in, and its leaf is rfc9162::leaf_hash over the line. Heads are
signed with Ed25519 under the domain eb-stack-ledger-v1; the key is a
PEM path on the command line and is refused inside the log's directory.
`verify --record N --head HEAD` checks an inclusion proof and
`verify --from HEAD1 --to HEAD2` a consistency proof.

rfc9162 is a git dependency on the deedar branch until it is published.
stack shard resolves a build-list line that carries --from-commit or --from-pr against that commit's or pull request's easyconfigs first, then the robot path, the order EasyBuild uses.
Trees come from --commit-tree COMMIT=DIR, --pr-tree N=DIR, or --commit-cache DIR, which downloads a missing commit from codeload.github.com (its .eb files only) and a pull request's changed easyconfigs at its head.
A shared module that is not installed goes to the prelude as for any other line; one only a fetched tree provides keeps the line's source options there.
A fetched line that still cannot be resolved stays opaque, with the reason in plan.json and on stderr.
A line whose dependency only another line's commit tree provides resolves through that line: the module is installed by that line when it builds, so the lines share a shard and that line builds first.
Each fetched line's own easyconfig sits under every universe at the lowest precedence.
Without a tree for its --from-commit, the petsc4py line was planned opaque, without a closure, and the planner warned about it on stderr.
The fixture reads that commit from a cache, so PETSc and petsc4py share a shard.
A cache download that never gets an HTTP status exits 1 and writes nothing.

@cursor cursor Bot 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.

Stale Bugbot comment from a previous run.

Comment thread src/shard_trees.rs Resolved

@cursor cursor Bot 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.

Stale Bugbot comment from a previous run.

Comment thread src/workspace_fs.rs Resolved

@cursor cursor Bot 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.

Stale Bugbot comment from a previous run.

Comment thread src/site_commands.rs Resolved

@cursor cursor Bot 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.

Stale Bugbot comment from a previous run.

Comment thread src/site_commands.rs Resolved

@cursor cursor Bot 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.

Stale Bugbot comment from a previous run.

Comment thread src/eb_style/mod.rs Resolved

@cursor cursor Bot 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.

Stale Bugbot comment from a previous run.

Comment thread tests/stack_shard.rs Resolved

@cursor cursor Bot 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.

Stale Bugbot comment from a previous run.

Comment thread src/ledger.rs Resolved

@cursor cursor Bot 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.

Stale Bugbot comment from a previous run.

Comment thread README.md Resolved

@cursor cursor Bot 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.

Stale Bugbot comment from a previous run.

Comment thread tests/bundle_component_checksums.rs Resolved

@cursor cursor Bot 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.

Stale Bugbot comment from a previous run.

Comment thread src/eb_parse.rs Resolved
The extract_easyconfigs comment puts `<repo>-<sha>/` in a backtick span on the same line as `dest`.
The docs postprocessor skips a span that contains angle brackets, so that closing backtick is paired with the next span and Sphinx -W reports an inline literal that never closes.
The archive prefix is now a double-backtick literal, which the postprocessor leaves alone.
EasyBuild's check_checksums_for treats postinstallpatches as artifacts that need a digest, and a component dict may spell that list post_install_patches.
Bundle component checksum tests accept a recipe when every source, patch, and postinstall patch has a digest, and packaging_gate rejects it for missing checksums when one is absent.
The shard howto and the CLI reference showed a codeload archive
address whose SHA was empty or the literal SHA. Lychee requests
those addresses and they 404. The examples now give the host and
the path without a single https URL, and the shell example still
downloads that commit.
@cursor
cursor Bot force-pushed the eb/shard-commit-trees-v2 branch from 79f7f8a to 07787c1 Compare November 5, 2026 20:32
@cursor
cursor Bot merged commit 0fb4c66 into main Nov 5, 2026
20 checks passed

@cursor cursor Bot 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.

Cursor Bugbot has reviewed your changes using default effort and found 3 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 07787c1. Configure here.

Comment thread src/pr_overlap.rs
}
let url = raw_url(repo, &pull.head.sha, &file.filename);
let text = get_raw(http, &url)?
.ok_or_else(|| anyhow::anyhow!("pull request {number}: {url}: HTTP 404"))?;

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.

Fork PR files fetch from wrong host

High Severity

--from-pr trees load file text from raw.githubusercontent.com on the base repo plus the pull head SHA. ListedHead keeps only sha, so a fork commit is requested from easybuilders/easybuild-easyconfigs and typically 404s. Those lines then plan as opaque and never share a shard with the modules they need.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 07787c1. Configure here.

Comment thread src/shard_trees.rs
let entry = cache.join(format!("pr-{number}-{}", head.to_ascii_lowercase()));
if entry.is_dir() {
return Ok(entry);
}

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.

PR cache unused until live fetch

Medium Severity

pr_tree always calls pull_head_easyconfigs (API plus every raw file) before it looks at --commit-cache. A complete pr-N-HEAD directory is ignored unless that live fetch succeeds, so an outage or rate limit makes --from-pr lines opaque even when the tree is already on disk. Commit trees check the cache first.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 07787c1. Configure here.

Comment thread src/eb_parse.rs
let expected = sources + patches + postinstall;
let Ok(checksums) = value_list_as_slice(fields.get("checksums")) else {
return false;
};

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.

Component checksums reject string sources

Medium Severity

component_checksums_complete counts sources, patches, and checksums only when each value is a list. EasyBuild treats a bare string as one entry. A Bundle component that writes sources or checksums as a string is marked incomplete, so packaging_gate still reports missing checksums for a fully hashed recipe.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 07787c1. Configure here.

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.

1 participant