From b0b4dfd53dbe01f0c6112885de4c55f5d3766b22 Mon Sep 17 00:00:00 2001 From: David Baker Effendi Date: Wed, 26 Aug 2026 12:42:48 +0200 Subject: [PATCH] Stage every artifact a promotion manifest binds The v0.3.1 legacy freeze ran all eleven candidate shards green and then failed in aggregation: Error: canonicalize promotion artifact docs/legacy-promotion-selection-policy.md Caused by: No such file or directory (os error 2) freeze-manifest resolves a promotion manifest's bound artifacts relative to the staged corpus, and the legacy manifest binds its eligibility policy under docs/. Staging copies benchmarks, fixtures, adapters, schema, src, containers, and scripts plus a few root files, so that document never reached the bundle. A bundle that cannot canonicalize an artifact its own manifest hash-binds is not self-verifying, which is the property the tier exists to provide. Copy exactly the bound files, derived by reading the promotion manifests rather than naming docs/ here, so a newly bound artifact is staged without editing this script. Not all of docs/: that carries node_modules and build output, which would bloat the bundle and hand the analyzer a large unrelated tree to index. Verified against the failed run's own evidence. Staging now emits the policy at the manifest's expected path with sha256 1ac2968892c97af5..., matching the binding, and docs/ contributes that one file and nothing else. Re-running the exact aggregation with all eleven reports downloaded from that run succeeds, satisfies the workflow's jq gate, and generates both result pages. Co-Authored-By: Claude Opus 5 --- scripts/stage-release-bundle.sh | 29 ++++++++++++++++++++ tests/test_freeze_shards.py | 47 +++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/scripts/stage-release-bundle.sh b/scripts/stage-release-bundle.sh index 9d8b39b..0e543b5 100755 --- a/scripts/stage-release-bundle.sh +++ b/scripts/stage-release-bundle.sh @@ -43,6 +43,35 @@ cp \ "$source_root/.dockerignore" \ "$destination/" +# Promotion manifests hash-bind an eligibility policy that lives under docs/, +# which is otherwise outside the staged tree. A bundle has to contain every +# artifact its manifests bind or freeze-manifest cannot canonicalize them, so +# copy exactly those files. Not all of docs/: that also carries node_modules +# and build output, which bloats the bundle and gives the analyzer a large +# unrelated tree to index. +promotion_root="$source_root/benchmarks/promotion" +if [[ -d "$promotion_root" ]]; then + bound_list="$(mktemp)" + trap 'rm -f "$bound_list"' EXIT + find "$promotion_root" -type f -name '*.json' -print | sort | while IFS= read -r promotion_manifest; do + jq -r '.. | objects | select(has("file")) | .file' "$promotion_manifest" + done | sort -u | grep -Ev '^(benchmarks|fixtures|adapters|schema|src|containers|scripts)/' > "$bound_list" || true + while IFS= read -r bound_artifact; do + [[ -n "$bound_artifact" ]] || continue + [[ "$bound_artifact" != /* && "$bound_artifact" != *..* ]] || { + echo "promotion manifest binds an unsafe artifact path: $bound_artifact" >&2 + exit 1 + } + [[ -f "$source_root/$bound_artifact" ]] || { + echo "promotion manifest binds a missing artifact: $bound_artifact" >&2 + exit 1 + } + mkdir -p "$destination/$(dirname "$bound_artifact")" + cp -- "$source_root/$bound_artifact" "$destination/$bound_artifact" + echo "staged bound promotion artifact: $bound_artifact" + done < "$bound_list" +fi + jq -n \ --arg releaseTag "$release_tag" \ --arg releaseVersion "${release_tag#v}" \ diff --git a/tests/test_freeze_shards.py b/tests/test_freeze_shards.py index 0dc12e1..beccf51 100644 --- a/tests/test_freeze_shards.py +++ b/tests/test_freeze_shards.py @@ -160,6 +160,53 @@ def test_ruby_lsp_native_shard_provisions_ruby_three_four_keg(self): self.assertNotIn('brew list ruby >/dev/null', workflow) self.assertNotIn('ruby\\ 3\\.[4-9]', workflow) + def test_release_bundle_stages_every_artifact_a_promotion_manifest_binds(self): + """A bundle must contain what its manifests hash-bind. + + The v0.3.1 freeze reached aggregation and failed on `canonicalize + promotion artifact docs/legacy-promotion-selection-policy.md`: the + legacy manifest binds an eligibility policy under docs/, and the + staging step copies benchmarks, fixtures, adapters, schema, src, + containers, and scripts but not docs. + """ + + staging = (ROOT / "scripts/stage-release-bundle.sh").read_text() + # Derived from the manifests rather than a hardcoded docs/ path, so a + # newly bound artifact is staged without editing this script. + self.assertIn( + "jq -r '.. | objects | select(has(\"file\")) | .file' \"$promotion_manifest\"", + staging, + ) + self.assertIn('cp -- "$source_root/$bound_artifact" "$destination/$bound_artifact"', staging) + # All of docs/ would drag in node_modules and build output. + self.assertNotIn('"$source_root/docs" \\', staging) + + promotion_root = ROOT / "benchmarks/promotion" + staged_prefixes = ( + "benchmarks/", "fixtures/", "adapters/", "schema/", "src/", + "containers/", "scripts/", + ) + bound = set() + + def walk(node): + if isinstance(node, dict): + value = node.get("file") + if isinstance(value, str): + bound.add(value) + for child in node.values(): + walk(child) + elif isinstance(node, list): + for child in node: + walk(child) + + for manifest in sorted(promotion_root.rglob("*.json")): + walk(json.loads(manifest.read_text())) + outside = sorted(p for p in bound if not p.startswith(staged_prefixes)) + # Every such artifact must exist, or the freeze fails at aggregation. + for relative in outside: + self.assertTrue((ROOT / relative).is_file(), f"bound artifact missing: {relative}") + self.assertIn("docs/legacy-promotion-selection-policy.md", outside) + def test_legacy_scope_is_bound_to_the_110_case_manifest(self): scope = subprocess.run( [ROOT / "scripts/resolve-freeze-scope.sh", "legacy-promoted"],