Skip to content

feat(catalog): mirror artifact WASM binaries for CORS-enabled fetch (closes #304) - #308

Merged
enricopiovesan merged 2 commits into
mainfrom
claude/issue-304-cors-artifact-mirror
Aug 23, 2026
Merged

feat(catalog): mirror artifact WASM binaries for CORS-enabled fetch (closes #304)#308
enricopiovesan merged 2 commits into
mainfrom
claude/issue-304-cors-artifact-mirror

Conversation

@enricopiovesan

Copy link
Copy Markdown
Contributor

Summary

#304 confirmed (via direct curl) that catalog.json on registry.traverse-framework.com is served with access-control-allow-origin: *, but the published capability WASM artifacts it points at (GitHub Release assets, spec 007-artifact-hosting) redirect to signed Azure Blob URLs with no CORS header at all — a browser-side consumer can discover a capability through the CORS-enabled catalog but its fetch() of the artifact bytes fails outright.

Verified the feasibility of mirroring before scoping the fix: 79 unique artifacts across all artifacts/* releases, 1.6MB total, ~17KB average — well inside GitHub Pages' size/bandwidth limits.

Changes

  • scripts/ci/mirror_artifacts.py (new): walks the whole capabilities/**/contract.json tree (deprecated versions included, so yanked artifacts stay fetchable too), downloads each unique artifact.url, re-verifies the bytes against artifact.digest (fails the CI run closed on a mismatch — this repo's first runtime digest verification of artifact bytes; existing checks are format-only), and writes each to catalog/artifacts/<id>-<version>/<asset-name> — the exact path suffix artifact.url already carries after .../releases/download/, so the CORS-enabled mirror URL is always a fixed prefix swap, never new state to keep in sync.
  • .github/workflows/ci.yml: wires the new script into the existing build-catalog job (push-to-main only), after static-page generation and before the Pages upload.
  • scripts/ci/generate_catalog_pages.py: renders the derived mirror URL as a second "Artifact (CORS mirror)" link on every capability's detail page, computed with the identical regex the mirror script uses (kept in explicit lockstep via a code comment on both sides).
  • scripts/ci/tests/test_mirror_artifacts.py (new): URL-recognition, digest-match write path, digest-mismatch fail-closed path (mocked at the network boundary).
  • scripts/ci/tests/test_generate_catalog_pages.py (new, first test file for this script): the new artifact_mirror_url helper only.
  • docs/decision-log.md: entry 66 records the finding, feasibility check, chosen approach, and why contract.json/spec 007 stay untouched (the mirror is a convenience read-path, regenerated fresh every build like catalog.json itself — carries none of spec 007's immutability obligations).

Governing Spec

  • 001-registry-foundation

Project Item

Registry Project — issue #304

Definition of Done

  • Artifact WASM binaries are mirrored to a CORS-enabled location on every catalog build.
  • Mirrored bytes are digest-verified against the authoritative contract.json record before being served.
  • The mirror URL is discoverable from each capability's catalog detail page.
  • Decision log records the finding and the chosen approach.

Validation

  • python3 -m unittest discover -s scripts/ci/tests -p "test_*.py" — 91 passed
  • Whole-tree python3 scripts/ci/capability_validation.py — passes clean
  • python3 -c "import yaml; yaml.safe_load(open('.github/workflows/ci.yml'))" — valid YAML
  • Ran mirror_artifacts.py against this repo's real, live release assets (not fixtures): 79/79 mirrored, digests matched, 0 skipped
  • Ran generate_catalog_pages.py against a representative fixture and confirmed the rendered page's mirror link resolves to the exact path the mirror script writes to

…loses #304)

catalog.json is served with access-control-allow-origin: * from GitHub
Pages, but the WASM artifacts it points at (GitHub Release assets that
redirect to signed Azure Blob URLs) have no CORS header at all, blocking
any browser-side consumer from fetching bytes it just discovered via the
catalog.

Adds scripts/ci/mirror_artifacts.py, run as a build-catalog CI step: walks
the whole capabilities/ tree, downloads each unique artifact, verifies it
against artifact.digest (fails closed on mismatch), and writes it to
catalog/artifacts/<id>-<version>/<asset-name> -- the exact path suffix
artifact.url already carries after '.../releases/download/', so the
CORS-enabled mirror URL is always a fixed prefix swap, never new state to
keep in sync. generate_catalog_pages.py renders the derived mirror link
alongside the existing artifact link on each capability's detail page.

contract.json's artifact.digest/url remain the sole authoritative record;
this mirror is a convenience read-path regenerated fresh on every build,
same as catalog.json itself.
@enricopiovesan
enricopiovesan enabled auto-merge (squash) August 23, 2026 01:57
@enricopiovesan
enricopiovesan merged commit 9eef4ba into main Aug 23, 2026
9 checks passed
@enricopiovesan
enricopiovesan deleted the claude/issue-304-cors-artifact-mirror branch August 23, 2026 01:59
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