Skip to content

feat: add cascade determinism checker#4

Merged
fatherlinux merged 1 commit into
mainfrom
feat/cascade-determinism-checker
Jun 10, 2026
Merged

feat: add cascade determinism checker#4
fatherlinux merged 1 commit into
mainfrom
feat/cascade-determinism-checker

Conversation

@fatherlinux

Copy link
Copy Markdown
Member

Adds validate-cascade.py plus a GHA workflow that runs it.

The checker diffs the FROM-graph (Containerfile inheritance across crunchtools repos) against the dispatch-graph (repository_dispatch wiring in build.yml). FAILS when a direct FROM edge is missing a matching dispatch edge — that's the bug that lets downstream images stop rebuilding when their parent updates. Catches future drift.

Currently passes on the live org state. Two WARNs (both non-blocking, documented in script):

  • acquacotta has FROM quay.io/crunchtools/acquacotta-base but the repo is missing from the org. Acquacotta's build is broken until this is resolved (restore the repo, or rebase acquacotta on another base).
  • ubi10-core dispatches rotv even though rotv's FROM is ${BASE_IMAGE} (parameterized, doesn't resolve to ubi10-core literally). Intentional over-dispatch — rotv's effective base often is ubi10-core.

Workflow runs on push/PR and workflow_dispatch. Deliberately no schedule: trigger — avoids disabled_inactivity (the failure mode this whole rework was driven by).

🤖 Generated with Claude Code

Compares the FROM-graph (Containerfile inheritance across crunchtools
repos) with the dispatch-graph (repository_dispatch wiring in build.yml).
FAILS when any direct FROM edge is missing a matching dispatch edge —
that's the bug that lets downstream images stop rebuilding when their
parent updates. Catches future drift between the two sources of truth.

Broken FROMs (e.g. acquacotta-base missing from the org) are demoted to
WARN: they break one specific repo's build but don't impair cascade
correctness for the rest of the org.

Over-dispatch (a repo dispatched without a matching FROM) is also WARN —
usually intentional (e.g. rotv uses BASE_IMAGE ARG that resolves to
ubi10-core, so the ubi10-core->rotv dispatch is correct in practice
even though the FROM line is parameterized).

Runs on push/PR to constitution and via workflow_dispatch from Hermes
weekly. No schedule: trigger by design — avoids disabled_inactivity.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@fatherlinux fatherlinux merged commit 482ca4e into main Jun 10, 2026
@fatherlinux fatherlinux deleted the feat/cascade-determinism-checker branch June 10, 2026 22:35

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces validate-cascade.py, a script that validates the determinism of the crunchtools image-rebuild cascade by comparing the FROM-graph of container images with the dispatch-graph of GitHub Actions workflows. The review comments suggest three valuable improvements: enhancing the regular expression for dispatch loops to support newlines as well as semicolons, safely checking the API response structure in fetch_text to prevent crashes on directories or missing keys, and catching network or JSON parsing exceptions at the entry point to guarantee the script exits with the documented code of 2.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread validate-cascade.py
Comment on lines +57 to +59
DISPATCH_LOOP_RE = re.compile(
r"for\s+repo\s+in\s+([A-Za-z0-9._\- ]+?)\s*;\s*do",
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The current regular expression for matching dispatch loops expects a semicolon before the do keyword. In bash/sh, it is very common to write loops with a newline instead of a semicolon (e.g., for repo in foo bar\ndo). Changing the regex to support both semicolons and newlines makes the parser more robust against formatting variations.

Suggested change
DISPATCH_LOOP_RE = re.compile(
r"for\s+repo\s+in\s+([A-Za-z0-9._\- ]+?)\s*;\s*do",
)
DISPATCH_LOOP_RE = re.compile(
r"for\s+repo\s+in\s+([A-Za-z0-9._\- ]+?)\s*(?:;|[\r\n]+)\s*do",
)

Comment thread validate-cascade.py
Comment on lines +86 to +87
import base64
return base64.b64decode(data["content"]).decode("utf-8", errors="replace")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

If the specified path is a directory rather than a file, the GitHub API returns a list of items instead of a dictionary. Accessing data["content"] directly will raise a TypeError. Additionally, if the file is a symlink or submodule, the "content" key might be missing, raising a KeyError. We should safely check that data is a dictionary and contains the "content" key before decoding.

Suggested change
import base64
return base64.b64decode(data["content"]).decode("utf-8", errors="replace")
if isinstance(data, dict) and "content" in data:
import base64
return base64.b64decode(data["content"]).decode("utf-8", errors="replace")
return None

Comment thread validate-cascade.py
Comment on lines +199 to +200
if __name__ == "__main__":
sys.exit(main())

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The script documentation states that exit code 2 is returned on network, auth, or API errors. However, if any unhandled urllib.error.URLError or json.JSONDecodeError is raised during the API calls, the script will crash with a traceback and exit with code 1. Wrapping the main execution block to catch these exceptions ensures the documented exit code contract is respected.

Suggested change
if __name__ == "__main__":
sys.exit(main())
if __name__ == "__main__":
try:
sys.exit(main())
except (urllib.error.URLError, json.JSONDecodeError) as e:
print(f"ERROR: GitHub API communication failed: {e}", file=sys.stderr)
sys.exit(2)

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