Skip to content

fix(release): align release inventories with the crates.io publish plan - #1376

Merged
DecisionNerd merged 1 commit into
mainfrom
fix/1373-release-inventory-notice
Sep 17, 2026
Merged

DecisionNerd merged 1 commit into
mainfrom
fix/1373-release-inventory-notice

Conversation

@DecisionNerd

@DecisionNerd DecisionNerd commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor

Problem

python3 scripts/ci/crate-publish-plan.py list emits 20 crates. Every
hand-maintained crates.io inventory carried 17. graphforge-discovery,
graphforge-observability, and graphforge-portable-oci were missing, so the
offline rehearsal rejected the assembled candidate:

release-rehearsal: unexpected crates.io package identity: graphforge-discovery

The same three crates also shipped no NOTICE, which
release_candidate_manifest._validate_crate requires inside every packaged
.crate, so fixing only the inventory moved the failure one step along. Both
halves land here.

Changes

  • release_candidate_manifest.CRATES, clean-env-verify.DEFAULT_CRATES, and
    verify_package_licenses.CARGO_PUBLISH_CRATES now hold all 20 crates,
    restated in the publish plan's topological order.
  • license_check.CARGO_PACKAGE_DIRS covers the three crate directories, which
    also extends NOTICE_COPIES (identical-content check) and the
    license-file.workspace / repository.workspace manifest assertions to them.
  • crates/graphforge-discovery/NOTICE, crates/graphforge-observability/NOTICE,
    and crates/graphforge-portable-oci/NOTICE added, byte-identical to the other
    17 crates and the workspace root (all 20 crate NOTICE files now hash to
    ab29064bf950b34ee9a12d9e9fb2798d).
  • scripts/ci/test-crate-publish-plan.py fails closed on future drift: each
    inventory must equal crate-publish-plan.py list, the license gate must cover
    every publishable crate directory, and every publishable crate must have a
    NOTICE. Verified negatively by deleting one entry — the test fails with a
    diff of expected vs found.
  • scripts/ci/test-clean-env-verify.py keeps its DEFAULT_CRATES shape
    assertion, updated to 20 and naming the three recovered crates.
  • The 26-node / 17-package literals in the release candidate, registry, and
    rehearsal tests are now derived from the inventories, so they track the
    publish set instead of going stale.
  • docs/development/publication-order.md and release-artifact-record.md: the
    release is 29 nodes over 20 crates, and the printed order was missing
    graphforge-value and graphforge-portable-oci.

Deriving the inventories from crate-publish-plan.py at runtime was considered
and rejected: those inventories are the fail-closed identity allowlist for
offline candidate validation, and computing them from the on-disk crates/
tree would let a partial or tampered checkout redefine what the gate accepts.
The explicit lists stay, with a test that fails when they diverge.

Verification

$ python3 scripts/ci/crate-publish-plan.py check
crate publish plan OK (20 crates)
$ python3 scripts/ci/crate-publish-plan.py list | wc -l
20
$ python3 scripts/ci/test-crate-publish-plan.py        crate-publish-plan tests passed
$ python3 scripts/ci/test-clean-env-verify.py          clean-env-verify tests passed
$ python3 scripts/ci/test-release-registry.py          release-registry tests: ok
$ python3 scripts/ci/test-release-rehearsal.py         release-rehearsal tests: ok
$ python3 scripts/ci/test-release-candidate.py         release-candidate tests: ok
$ python3 scripts/ci/test-publish-crates.py            publish crates tests passed
$ python3 scripts/ci/test-crate-authorize-refresh-nodes.py
$ python3 scripts/ci/test-release-publish-preflight.py
$ uv run pytest tests/unit/test_license_check.py       6 passed
$ make gate-registry-check                             Ran 14 tests ... OK
$ python3 scripts/license_check.py --report ...        license-check: compliant
$ python3 scripts/verify_package_licenses.py --report ...
package-licenses: compliant   (20/20 cargo crates, no errors)
$ uv run ruff format --check scripts/ && uv run ruff check scripts/
137 files already formatted / All checks passed!

Packaged archive proof

$ cargo package -p graphforge-discovery -p graphforge-observability -p graphforge-portable-oci --allow-dirty --no-verify
    Packaged 9 files, 90.5KiB (22.0KiB compressed)   # graphforge-discovery
    Packaged 9 files, 92.3KiB (23.9KiB compressed)   # graphforge-observability
    Packaged 9 files, 83.5KiB (22.1KiB compressed)   # graphforge-portable-oci

$ tar -tzf target/package/graphforge-discovery-0.5.2.crate
graphforge-discovery-0.5.2/.cargo_vcs_info.json
graphforge-discovery-0.5.2/BUILD.bazel
graphforge-discovery-0.5.2/Cargo.lock
graphforge-discovery-0.5.2/Cargo.toml
graphforge-discovery-0.5.2/Cargo.toml.orig
graphforge-discovery-0.5.2/LICENSE
graphforge-discovery-0.5.2/NOTICE
graphforge-discovery-0.5.2/src/lib.rs
graphforge-discovery-0.5.2/tests/contract_artifacts.rs

$ tar -tzf target/package/graphforge-observability-0.5.2.crate
graphforge-observability-0.5.2/.cargo_vcs_info.json
graphforge-observability-0.5.2/BUILD.bazel
graphforge-observability-0.5.2/Cargo.lock
graphforge-observability-0.5.2/Cargo.toml
graphforge-observability-0.5.2/Cargo.toml.orig
graphforge-observability-0.5.2/LICENSE
graphforge-observability-0.5.2/NOTICE
graphforge-observability-0.5.2/src/lib.rs
graphforge-observability-0.5.2/tests/disabled_allocations.rs

$ tar -tzf target/package/graphforge-portable-oci-0.5.2.crate
graphforge-portable-oci-0.5.2/.cargo_vcs_info.json
graphforge-portable-oci-0.5.2/BUILD.bazel
graphforge-portable-oci-0.5.2/Cargo.lock
graphforge-portable-oci-0.5.2/Cargo.toml
graphforge-portable-oci-0.5.2/Cargo.toml.orig
graphforge-portable-oci-0.5.2/LICENSE
graphforge-portable-oci-0.5.2/NOTICE
graphforge-portable-oci-0.5.2/src/lib.rs
graphforge-portable-oci-0.5.2/src/tests.rs

Each archive was then run through the real gate that rejected them before:

release_candidate_manifest._validate_crate: graphforge-discovery OK
release_candidate_manifest._validate_crate: graphforge-observability OK
release_candidate_manifest._validate_crate: graphforge-portable-oci OK

The .crate files were deleted afterwards.

Other inventories checked

  • scripts/publish_dry_run.py:FALLBACK_CARGO_ORDER holds a stale 16-crate list
    (missing graphforge-value, graphforge-discovery,
    graphforge-observability, graphforge-portable-oci). It is only reached
    when scripts/ci/crate-publish-plan.py is absent from the branch, which is no
    longer possible, so it is dead and left untouched rather than becoming a fifth
    list to maintain. Worth deleting separately.
  • config/gate-registry.json does not enumerate crates; make gate-registry-check
    passes unchanged.
  • The Bazel migration ledger (docs/development/bazel-migration-ledger.md) and
    tools/bazel/parity/migration_target_map.json already map all three crates.

Closes #1373

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

`crate-publish-plan.py list` emits 20 crates, but every hand-maintained
crates.io inventory carried only 17. `graphforge-discovery`,
`graphforge-observability`, and `graphforge-portable-oci` were missing, so
offline rehearsal rejected the assembled candidate with "unexpected crates.io
package identity". The same three crates also shipped no NOTICE, which
`release_candidate_manifest._validate_crate` requires inside every packaged
`.crate`, so both halves land together.

- add the three crates to `release_candidate_manifest.CRATES`,
  `clean-env-verify.DEFAULT_CRATES`, and
  `verify_package_licenses.CARGO_PUBLISH_CRATES`, restating each inventory in
  the publish plan's topological order
- add the three crate directories to `license_check.CARGO_PACKAGE_DIRS`, which
  also extends NOTICE_COPIES coverage
- add `crates/graphforge-{discovery,observability,portable-oci}/NOTICE`, byte
  identical to the other 17 crates and the workspace root
- fail closed on future drift: `test-crate-publish-plan.py` now asserts every
  inventory equals `crate-publish-plan.py list`, that the license gate covers
  each publishable crate directory, and that each one has a NOTICE
- keep the `DEFAULT_CRATES` shape assertion in `test-clean-env-verify.py` and
  update it to 20, naming the three recovered crates
- derive the 26/17 node and package counts in the release candidate,
  registry, and rehearsal tests from the inventories instead of hardcoding
- refresh publication-order.md and release-artifact-record.md: the release is
  29 nodes over 20 crates, and the printed order was missing
  `graphforge-value` and `graphforge-portable-oci`

Closes #1373

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 17, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 5a960350-cd34-42ee-bb0e-38940fb5573c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Warning

Billing warning: we have not been able to collect payment for this subscription for more than 72 hours. Please update the payment method or pay any pending invoices in Billing to avoid service interruption.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added core Core source code changes documentation Improvements or additions to documentation tooling Developer tooling and automation labels Sep 17, 2026
@DecisionNerd
DecisionNerd added this pull request to the merge queue Sep 17, 2026
Merged via the queue into main with commit d2a5f59 Sep 17, 2026
23 checks passed
@DecisionNerd
DecisionNerd deleted the fix/1373-release-inventory-notice branch September 17, 2026 18:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core Core source code changes documentation Improvements or additions to documentation tooling Developer tooling and automation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(release): three crates are in the publish plan but missing from every release inventory

1 participant