Skip to content

release: handle-upstream dispatches publish after it has tagged - #148

Open
luisleo526 wants to merge 1 commit into
mainfrom
lane/cld-cr512-step2c
Open

luisleo526 wants to merge 1 commit into
mainfrom
lane/cld-cr512-step2c

Conversation

@luisleo526

Copy link
Copy Markdown
Contributor

WHAT: handle-upstream dispatches publish.yml on main as the last normal step of its job in bump (incl. retag) and republish, after the tag push, with a second App token limited to Actions write on this one repository; the tag comes only from the job's own step outputs; recovery (retag/republish) of a version OLDER than the latest ready release does not dispatch and says why (manual command kept), equal is allowed (its image is what recovery restores); a failed dispatch fails the job so the producer incident fires; republish now also pushes the existing tag (a missing tag fails on purpose). reconcile reports a hub tag with no candidate and no image after a 30-minute grace period (annotated tagger date) through the existing incident and never dispatches. BEHAVIOUR CHANGE OF AN EXISTING FUNCTION: Registry.manifest now raises Invalid when the ghcr token endpoint is unreadable (it used to propagate Pending, which Registry.digest read as "image absent"); pinned by test_registry_token_error_is_not_an_absent_manifest; no caller caught Pending around it. TOKEN BLAST RADIUS: Actions write on this repository can dispatch, cancel or re-run ANY hub workflow; the control is the pinned step list (mint immediately followed by the dispatch step, failure reporter last), frozen by tests/test_release_workflows.py. publish.yml's concurrency: release-candidate-<tag> (cancel-in-progress false) makes a second dispatch for the same tag queue instead of racing. publish.yml itself verifies (so "a tag exists" is not the control): main-ref dispatch, tag pattern, a tag that peels to a commit reachable from main, tag == VERSION, pins from the annotated tag message, the pairing rule. TESTED: spark2 arm64 differential base vs patched (above), README pins (494 lines, digest of 215-244) intact; the hub's own python-test CI on the PR is the final proof. OPEN (not done by this PR): App Actions write on the hub installation (prerequisite), the release-automation environment's reviewers (a required reviewer turns each dispatch into a click), whether the human dispatch of STABLE releases is an intentional public-image gate (owner decision), and activation of the hub workflows that were disabled_manually (reconcile/finalize/receipts/facts: the reconcile incident is inactive until then).

🤖 Generated with Claude Code

handle-upstream now starts publish.yml itself, as the last normal step of
its job in the bump, retag and republish modes, once the tag push has
succeeded, with a second App token limited to Actions write on this
repository and the tag taken from the job's own step output. A retag or
republish of a version older than the latest ready release does not
dispatch (it says why and succeeds), and a failed dispatch fails the job so
the existing producer incident fires. Reconcile also reports a hub tag that
has neither a candidate nor an image after a 30 minute grace period,
through the existing incident, and never dispatches. The dry-run harness
models the dispatch, the operator notices and docs say it, and the tests
pin all of it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

@graphify-labs graphify-labs Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

No issues found by static checks — no coupling regressions or blocking issues in the code graph. That is not the same as safe to merge: see what was not checked.

Not checked: tests were not run; formal verification proved 0 of 7 changed function(s) (7 not verified).

Formal verification. PR-changed functions: 0/7 verified (0 proven, 0 may-equivalent, 0 distinguished) · 7 not verified (4 vacuous, 3 unsupported).

Not verified on this run: main (vacuous: never exercised), raw (unsupported), reconcile (vacuous: never exercised), \_\_init\_\_ (unsupported), run (unsupported), main (vacuous: never exercised), gh (vacuous: never exercised).


Graphify review — findings

Automates publication after a hub tag is pushed: handle-upstream.yml now dispatches publish.yml on main with its own tag output, instead of leaving it to an operator command. Republish mode now pushes the existing tag idempotently, so a missing tag fails the job and fires the existing producer alert. A new publish-policy step in scripts/release_pipeline.py decides whether dispatch is allowed. Recovery for a version older than the latest ready release is skipped with a notice and the manual gh workflow run command. If the dispatch itself fails, the job errors and prints the same manual fallback. Dispatch runs with a separately minted hub App token scoped to actions: write. release-reconcile.yml now also reports tagged publications that have stalled past a grace period. The README, release-chain docs and tests are updated to match, including new coverage for the release-binding, pairing and workflow guard behaviour (PEP 440 prerelease normalization, fail-closed tag handling, delivery retry back-off).

Review partial — this diff was larger than one review pass covers, so later files were not reviewed; some findings may be missing.

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 893 functions depend on the 840 functions this change touches.

Health — this change adds coupling hotspots:

  • worse: main() — 1 callers, 30 callees
  • worse: Registry — 5 callers, 3 callees
  • worse: reconcile() — 1 callers, 8 callees

Verification — 893 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 881 function(s) in the blast radius were not formally verified this run

Docs that may be stale (advisory)

Formal verification

Could not verify: Could not verify main.

The verifier did not have enough to check main, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: not verifiable: all 1 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly NameError — names the real obstacle, not a sampling gap)

Could not verify: Could not verify raw.

The verifier did not have enough to check raw, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: method — first parameter is `self`, which needs a constructed instance (not synthesizable)

Could not verify: Could not verify reconcile.

The verifier did not have enough to check reconcile, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: not verifiable: all 254 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly NameError — names the real obstacle, not a sampling gap)

Could not verify: Could not verify \_\_init\_\_.

The verifier did not have enough to check \_\_init\_\_, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: method — first parameter is `self`, which needs a constructed instance (not synthesizable)

Could not verify: Could not verify run.

The verifier did not have enough to check run, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: method — first parameter is `self`, which needs a constructed instance (not synthesizable)

Could not verify: Could not verify main.

The verifier did not have enough to check main, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: not verifiable: all 64 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly NameError — names the real obstacle, not a sampling gap)

Could not verify: Could not verify gh.

The verifier did not have enough to check gh, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: not verifiable: all 69 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly TypeError — names the real obstacle, not a sampling gap)

· 1 grounded finding(s) anchored inline below; 2 more finding(s) on lines outside this diff (see the check run).

@@ -878,7 +895,12 @@ def run_link():


def reconcile(client, store):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regression — reconcile()

fans out to 8 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

This branch has not been deployed

No deployments
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