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
83 changes: 83 additions & 0 deletions .github/workflows/check-canon-compatibility.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
name: Check ASET Compatibility Standard

on:
repository_dispatch:
types:
- aset-standard-released
workflow_dispatch:
inputs:
aset_tag:
description: Exact published ASET Seed release tag to inspect
required: true
type: string

permissions:
contents: read

jobs:
check-standard:
runs-on: ubuntu-24.04
env:
ASET_TAG: >-
${{ github.event_name == 'repository_dispatch'
&& github.event.client_payload.tag
|| inputs.aset_tag }}
steps:
- uses: actions/checkout@v4

- name: Validate exact release tag
shell: bash
run: |
if [[ "$ASET_TAG" =~ ^seed-[0-9A-Za-z._-]+$ ]]; then
printf 'ASET_TAG=%s\n' "$ASET_TAG"
else
printf 'INVALID_ASET_TAG=%s\n' "$ASET_TAG" >&2
false
fi

- name: Download candidate Compatibility Standard identity
env:
GH_TOKEN: ${{ github.token }}
shell: bash
run: |
mkdir -p .aset-candidate
gh release download "$ASET_TAG" \
--repo attractor-set/ASET \
--dir .aset-candidate \
--pattern 'ASET-Seed-*-Compatibility-Standard.json'

- name: Compare candidate with pinned standard
shell: bash
run: |
python - <<'PY'
import json
from pathlib import Path

lock = json.loads(Path("canon.lock.json").read_text(encoding="utf-8"))
paths = list(Path(".aset-candidate").glob("ASET-Seed-*-Compatibility-Standard.json"))
if len(paths) != 1:
raise SystemExit(f"candidate standard identity count={len(paths)}")
candidate = json.loads(paths[0].read_text(encoding="utf-8"))
pinned = lock["standard"]

print("PINNED_STANDARD=" + pinned["standard_id"])
print("PINNED_PACKAGE_DIGEST=" + lock["required_package_digest"])
print("CANDIDATE_STANDARD=" + candidate["standard_id"])
print("CANDIDATE_PACKAGE_DIGEST=" + candidate["canonical_package_digest"])

if candidate["standard_id"] == pinned["standard_id"]:
print("STANDARD_COMPATIBILITY=EXACT_PINNED_STANDARD")
print("LOCK_UPDATE_REQUIRED=false")
elif candidate["canonical_package_digest"] == lock["required_package_digest"]:
print("STANDARD_COMPATIBILITY=SAME_CANON_NEW_RELEASE_IDENTITY")
print("LOCK_UPDATE_REQUIRED=explicit_release_rebind")
else:
print("STANDARD_COMPATIBILITY=SEMANTIC_REBASE_REQUIRED")
print("LOCK_UPDATE_REQUIRED=review_required")
PY

- name: Policy reminder
run: |
printf '%s\n' \
'Compatibility Standard lock updates are never applied automatically.' \
'A new release identity requires explicit rebind; a changed canonical package requires semantic rebase and full conformance review.'
122 changes: 106 additions & 16 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,31 +15,121 @@ jobs:
steps:
- name: Checkout implementation
uses: actions/checkout@v4
- name: Read locked ASET canon reference
id: canon-lock
shell: bash
run: |
CANON_REF="$(python -c 'import json; print(json.load(open("canon.lock.json"))["source"]["ref"])')"
printf 'ref=%s\n' "$CANON_REF" >> "$GITHUB_OUTPUT"
printf 'Locked ASET canon ref: %s\n' "$CANON_REF"
- name: Checkout ASET specification
uses: actions/checkout@v4
with:
repository: attractor-set/ASET
ref: ${{ steps.canon-lock.outputs.ref }}
path: .aset-spec

- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip

- run: python -m pip install -r requirements-dev.txt
- run: python -m pip install --no-deps --editable .

- name: Read locked Compatibility Standard release
id: standard-lock
shell: bash
run: |
python - <<'PY' >> "$GITHUB_OUTPUT"
import json
from pathlib import Path

lock = json.loads(Path("canon.lock.json").read_text(encoding="utf-8"))
standard = lock["standard"]
version = standard["release_version"]
print("tag=" + lock["source"]["tag"])
print("release_version=" + version)
print("kit_sha256=" + standard["conformance_kit_sha256"].removeprefix("sha256:"))
print("kit=ASET-Seed-" + version + "-Conformance-Kit.zip")
print("checksum=ASET-Seed-" + version + "-Conformance-Kit.zip.sha256")
print("identity=ASET-Seed-" + version + "-Compatibility-Standard.json")
print("manifest=ASET-Seed-" + version + "-Conformance-Kit.manifest.json")
PY

- name: Download immutable ASET Compatibility Standard assets
env:
GH_TOKEN: ${{ github.token }}
ASET_TAG: ${{ steps.standard-lock.outputs.tag }}
KIT: ${{ steps.standard-lock.outputs.kit }}
CHECKSUM: ${{ steps.standard-lock.outputs.checksum }}
IDENTITY: ${{ steps.standard-lock.outputs.identity }}
MANIFEST: ${{ steps.standard-lock.outputs.manifest }}
shell: bash
run: |
mkdir -p .aset-standard-assets
gh release download "$ASET_TAG" \
--repo attractor-set/ASET \
--dir .aset-standard-assets \
--pattern "$KIT" \
--pattern "$CHECKSUM" \
--pattern "$IDENTITY" \
--pattern "$MANIFEST"

- name: Verify and materialize exact Conformance Kit
env:
VERSION: ${{ steps.standard-lock.outputs.release_version }}
KIT: ${{ steps.standard-lock.outputs.kit }}
CHECKSUM: ${{ steps.standard-lock.outputs.checksum }}
IDENTITY: ${{ steps.standard-lock.outputs.identity }}
KIT_SHA256: ${{ steps.standard-lock.outputs.kit_sha256 }}
shell: bash
run: |
python - <<'PY'
import hashlib
import json
import os
import shutil
import zipfile
from pathlib import Path, PurePosixPath

assets = Path(".aset-standard-assets")
kit = assets / os.environ["KIT"]
checksum = assets / os.environ["CHECKSUM"]
identity = assets / os.environ["IDENTITY"]
expected = os.environ["KIT_SHA256"]
actual = hashlib.sha256(kit.read_bytes()).hexdigest()
if actual != expected:
raise SystemExit(f"locked Conformance Kit SHA-256 mismatch: {actual}")
checksum_value = checksum.read_text(encoding="utf-8").split()[0]
if checksum_value != expected:
raise SystemExit("published checksum asset does not match canon.lock.json")

target = Path(".aset-standard")
if target.exists():
shutil.rmtree(target)
target.mkdir()
root_name = f"ASET-Seed-{os.environ['VERSION']}-Conformance-Kit"
with zipfile.ZipFile(kit) as archive:
for info in archive.infolist():
path = PurePosixPath(info.filename)
if path.is_absolute() or ".." in path.parts or not path.parts or path.parts[0] != root_name:
raise SystemExit(f"unsafe Conformance Kit member: {info.filename}")
destination = target.joinpath(*path.parts)
if info.is_dir():
destination.mkdir(parents=True, exist_ok=True)
else:
destination.parent.mkdir(parents=True, exist_ok=True)
destination.write_bytes(archive.read(info))
embedded = target / root_name / "STANDARD.json"
if embedded.read_bytes() != identity.read_bytes():
raise SystemExit("published standard identity differs from Conformance Kit STANDARD.json")
print("SEED_CONFORMANCE_KIT_SHA256=" + actual)
print("SEED_CONFORMANCE_KIT_RELEASE_ASSET=PASS")
PY

- name: Verify repository manifest
run: python tools/rebuild_manifest.py --check
- name: Verify canon lock
run: python tools/verify_canon_lock.py --canon-root .aset-spec

- name: Verify exact Compatibility Standard binding
run: >-
python tools/verify_canon_lock.py
--canon-root .aset-standard/ASET-Seed-${{ steps.standard-lock.outputs.release_version }}-Conformance-Kit
--standard-identity .aset-standard-assets/${{ steps.standard-lock.outputs.identity }}
--standard-kit .aset-standard-assets/${{ steps.standard-lock.outputs.kit }}

- name: Run complete profile and release gate
run: python tools/profile_gate.py --canon-root .aset-spec
run: >-
python tools/profile_gate.py
--canon-root .aset-standard/ASET-Seed-${{ steps.standard-lock.outputs.release_version }}-Conformance-Kit

- uses: actions/upload-artifact@v4
if: always()
with:
Expand Down
Loading
Loading