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
98 changes: 98 additions & 0 deletions .github/workflows/modules-ref-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
name: modules.json ref drift check

# Guards modules.json against the failure class that broke it three times in one
# week (node-sdk, go-sdk, and core / AAASM-5047): a referenced repo renames its
# default branch (e.g. master->main) but the module's pinned `ref` is left stale,
# so `git clone -b <ref>` in aggregate.yml fails with "pathspec did not match"
# and the Aggregate job breaks on every docs PR and scheduled main run — with no
# aggregated Pages deploy shipping until someone notices.
#
# Nothing else validates the `ref` values against reality. This gate resolves
# each referenced repo's LIVE default branch with `git ls-remote --symref <url>
# HEAD` at check time and fails loudly if a pinned `ref` no longer matches — so
# a default-branch rename is caught here instead of in the aggregation job.

on:
push:
branches: [main]
paths:
- modules.json
- .github/workflows/modules-ref-check.yml
pull_request:
branches: [main]
paths:
- modules.json
- .github/workflows/modules-ref-check.yml
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
modules-ref-check:
name: Verify modules.json refs match live default branches
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout hub
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7

- name: Check each module ref against its repo's default branch
run: |
python3 - <<'PY'
import json
import subprocess
import sys

with open("modules.json", encoding="utf-8") as fh:
registry = json.load(fh)

failures = []
for module in registry.get("modules", []):
name = module.get("name", "<unnamed>")
repo = module.get("repo")
ref = module.get("ref")
if not repo or not ref:
failures.append(f"{name}: entry is missing 'repo' or 'ref'")
continue

url = f"https://github.com/{repo}.git"
# --symref makes HEAD resolve to the symbolic default branch, e.g.
# ref: refs/heads/main\tHEAD
result = subprocess.run(
["git", "ls-remote", "--symref", url, "HEAD"],
capture_output=True,
text=True,
)
if result.returncode != 0:
failures.append(
f"{name}: could not query {url}: {result.stderr.strip()}"
)
continue

default_branch = None
for line in result.stdout.splitlines():
if line.startswith("ref:") and line.rstrip().endswith("HEAD"):
default_branch = line.split()[1].removeprefix("refs/heads/")
break

if default_branch is None:
failures.append(f"{name}: could not resolve default branch for {repo}")
elif ref != default_branch:
failures.append(
f"{name}: modules.json pins ref '{ref}' but {repo}'s live "
f"default branch is '{default_branch}'"
)
else:
print(f"OK {name}: '{ref}' matches {repo} default branch")

if failures:
print("\nmodules.json ref drift detected:", file=sys.stderr)
for failure in failures:
print(f" - {failure}", file=sys.stderr)
sys.exit(1)

print("\nAll module refs match their repo's live default branch.")
PY
4 changes: 2 additions & 2 deletions modules.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
{
"name": "core",
"repo": "ai-agent-assembly/agent-assembly",
"ref": "master",
"ref": "main",
"generator": "mdbook",
"subpath": "core",
"doc_dir": "docs",
"channel": "FULL version set: master HEAD under /core/latest/ PLUS every release git tag rebuilt into /core/<tag>/, with the channels+archived manifest at /core/versions.json (AAASM-3753)",
"channel": "FULL version set: main HEAD under /core/latest/ PLUS every release git tag rebuilt into /core/<tag>/, with the channels+archived manifest at /core/versions.json (AAASM-3753)",
"base_url_strategy": "mdBook emits root-relative asset paths, so it is subpath-safe with no flag. The in-theme #aaasm-version-selector derives the site root from path_to_root at runtime and fetches /core/versions.json, so each book lives under /core/latest/ and /core/<tag>/ with the manifest + a site-root redirect at /core/.",
"build": "cd docs && mdbook build -d <out>/latest; then for each 'v*.*.*' git tag: git worktree add + mdbook build -d <out>/<tag> (logging extra-archived.txt); then python3 docs/ci/build_versions.py latest latest <out>/versions.json (archived[] seeded from the rebuilt tags = git source of truth; moving pre-release/stable channels seeded from the live deployed manifest); cp docs/site-root-index.html <out>/index.html",
"out_glob": ["index.html", "latest/index.html", "<tag>/index.html", "versions.json"]
Expand Down
Loading