Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,7 @@ jobs:
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: gh release upload "${{ needs.release-target.outputs.tag-name }}" dist/release-assets/codebase-graph-*.tar.gz dist/release-assets/codebase-graph-*.tar.gz.sha256 --clobber
run: gh release upload "${{ needs.release-target.outputs.tag-name }}" dist/release-assets/codebase-graph-*.tar.gz dist/release-assets/codebase-graph-*.tar.gz.sha256 --clobber --repo "$GITHUB_REPOSITORY"

publish-crate:
name: publish crates.io package
Expand Down
27 changes: 26 additions & 1 deletion crates/xtask/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1303,6 +1303,17 @@ fn check_workflow_policy(
}
let publish_assets =
yaml_path(release, &["jobs", "publish-release-assets"]).unwrap_or(&YamlValue::Null);
let release_upload_step = yaml_step_by_name(publish_assets, "Upload complete native asset set")
.unwrap_or(&YamlValue::Null);
if !yaml_path(release_upload_step, &["run"]).is_some_and(|run| {
yaml_contains_string(run, "gh release upload")
&& yaml_contains_string(run, "--repo \"$GITHUB_REPOSITORY\"")
}) {
issues.push(
"FAIL: release-publisher-repository-missing: the checkout-free release upload must explicitly select GITHUB_REPOSITORY."
.to_string(),
);
}
if !yaml_path(publish_assets, &["if"]).is_some_and(|condition| {
[
"always()",
Expand Down Expand Up @@ -2430,7 +2441,9 @@ jobs:
if: ${{ always() && needs.release-target.result == 'success' && needs.validate-artifacts.result == 'success' && needs.release-target.outputs.publish_assets == 'true' }}
permissions: {contents: write}
environment: {name: cargo}
steps: [{run: 'gh release upload'}]
steps:
- name: Upload complete native asset set
run: 'gh release upload v1.2.3 artifact --repo "$GITHUB_REPOSITORY"'
publish-crate:
needs: [release-please, release-target, publish-release-assets]
if: ${{ always() && needs.release-please.result == 'success' && needs.release-target.result == 'success' && needs.publish-release-assets.result == 'success' && needs.release-please.outputs.release-created == 'true' && needs.release-target.outputs.publish_assets == 'true' }}
Expand Down Expand Up @@ -2631,6 +2644,18 @@ jobs:
);
}

#[test]
fn workflow_policy_rejects_repository_implicit_release_upload() {
let broken = valid_release_workflow_text().replace(" --repo \"$GITHUB_REPOSITORY\"", "");
let issues = workflow_policy_issues(&broken);
assert!(
issues
.iter()
.any(|issue| issue.contains("release-publisher-repository-missing")),
"{issues:?}"
);
}

#[test]
fn workflow_policy_rejects_crate_publication_without_skipped_ancestor_override() {
let broken = valid_release_workflow_text().replace(
Expand Down
4 changes: 3 additions & 1 deletion docs/release.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ Pull requests targeting `main` and pushes to `main` run:
`vX.Y.Z` tag, proves that the tag resolves to the triggering CI SHA, validates all four
archives/checksums/provenance records from that exact run, and uploads the public assets from one publisher. Promotion
intentionally skips the rebuild job; the asset and crate publishers explicitly override that skipped ancestor only
after their direct prerequisites succeed, so GitHub cannot silently suppress publication after validation.
after their direct prerequisites succeed, so GitHub cannot silently suppress publication after validation. The
checkout-free asset publisher explicitly selects `GITHUB_REPOSITORY`, so GitHub CLI never depends on local Git
metadata to locate the release.
6. `cargo publish --dry-run --locked` runs at the immutable tag, then the crate publishes automatically after native
assets succeed. Manual recovery never publishes the crate.

Expand Down