Skip to content
Merged

Dev #687

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
143 commits
Select commit Hold shift + click to select a range
f290bfa
Add authored evidence requirements
Brad-Edwards Jun 24, 2026
3a68861
Fix SonarCloud findings
Brad-Edwards Jun 24, 2026
3560808
Merge remote-tracking branch 'origin/dev' into 337-dsl-124-evidence
Brad-Edwards Jun 24, 2026
b41584b
Merge pull request #591 from Brad-Edwards/337-dsl-124-evidence
Brad-Edwards Jun 24, 2026
26fca36
Add behavior specifications to SDL
Brad-Edwards Jun 25, 2026
b55ff3f
Merge origin/dev into behavior specifications
Brad-Edwards Jun 25, 2026
69a703e
Fix SonarCloud findings (cycle 1)
Brad-Edwards Jun 25, 2026
f83fd97
Fix SonarCloud findings (cycle 2)
Brad-Edwards Jun 25, 2026
039b019
Merge pull request #594 from Brad-Edwards/206-act-606-behavior-specs
Brad-Edwards Jun 25, 2026
e0814b4
Add repo-side PR title guard against agent-branded titles
Brad-Edwards Jun 25, 2026
d43cc26
Merge pull request #595 from Brad-Edwards/567-pr-title-guard
Brad-Edwards Jun 25, 2026
70762a2
Add ACT-607 authority scope runtime metadata
Brad-Edwards Jun 25, 2026
510b979
Fix CodeQL dependency assertions
Brad-Edwards Jun 25, 2026
65132b4
Fix SonarCloud findings (cycle 1)
Brad-Edwards Jun 25, 2026
f039ff1
Merge remote-tracking branch 'origin/dev' into 207-act-607-boundaries
Brad-Edwards Jun 25, 2026
bed09cf
docs: define experiment replication and replay claims
Brad-Edwards Jun 25, 2026
7d3bc95
Merge pull request #596 from Brad-Edwards/207-act-607-boundaries
Brad-Edwards Jun 25, 2026
1a3be5b
Merge branch 'dev' into 105-exp-706-trial-replay
Brad-Edwards Jun 25, 2026
09d56df
Merge pull request #597 from Brad-Edwards/105-exp-706-trial-replay
Brad-Edwards Jun 25, 2026
5a66865
Add paper agent loop scenario
Brad-Edwards Jun 25, 2026
8c01263
Add libvirt provisioning backend
Brad-Edwards Jun 26, 2026
9919f03
Broaden paper agent loop scenario
Brad-Edwards Jun 26, 2026
f95959d
Fix SonarCloud findings (cycle 1)
Brad-Edwards Jun 26, 2026
2eebc47
Fix SonarCloud findings (cycle 2)
Brad-Edwards Jun 26, 2026
54739e5
Drive TechVault scenario through libvirt provisioning
Brad-Edwards Jun 27, 2026
5f7675c
Drive full TechVault operational scenario through libvirt
Brad-Edwards Jun 27, 2026
1542be0
Record TechVault live smoke evidence
Brad-Edwards Jun 27, 2026
671b8fd
Add TechVault operational resource defaults
Brad-Edwards Jun 27, 2026
c94b6a4
Drive TechVault live startup through ACES libvirt
Brad-Edwards Jun 27, 2026
b50ee9e
Verify TechVault SOC readback through ACES libvirt
Brad-Edwards Jun 27, 2026
20ea04a
Harden TechVault live operations gate
Brad-Edwards Jun 27, 2026
c1f8a55
Format TechVault operations gate
Brad-Edwards Jun 27, 2026
e1b4699
Fix TechVault operations Sonar findings
Brad-Edwards Jun 27, 2026
4bb7599
Reduce TechVault live gate complexity
Brad-Edwards Jun 27, 2026
43a4d5b
Type TechVault APTL lifecycle helper
Brad-Edwards Jun 27, 2026
18ea653
Record final TechVault live validation
Brad-Edwards Jun 27, 2026
cb29216
Refactor TechVault libvirt live gate to native substrate
Brad-Edwards Jun 27, 2026
c873022
Add native libvirt TechVault live evidence
Brad-Edwards Jun 27, 2026
4dff78d
Clean up native TechVault scenario variants
Brad-Edwards Jun 27, 2026
818f328
Address native TechVault Sonar findings
Brad-Edwards Jun 27, 2026
4b86880
Fix TechVault libvirt Sonar cleanup
Brad-Edwards Jun 27, 2026
7da3293
Merge pull request #612 from Brad-Edwards/601-libvirt-provisioning
Brad-Edwards Jun 28, 2026
d6047e9
Merge remote-tracking branch 'origin/dev' into 598-paper-sdl-agent-loop
Brad-Edwards Jun 28, 2026
eacc887
changed: refine paper participant evidence scenario
Brad-Edwards Jun 28, 2026
258b327
Merge pull request #611 from Brad-Edwards/598-paper-sdl-agent-loop
Brad-Edwards Jun 28, 2026
5eae35f
Add ACT-608 behavior mode scope validation
Brad-Edwards Jun 28, 2026
19f9405
Merge remote-tracking branch 'origin/dev' into 208-act-608-modes
Brad-Edwards Jun 28, 2026
6cf217e
Merge pull request #616 from Brad-Edwards/208-act-608-modes
Brad-Edwards Jun 28, 2026
dc98b41
Add participant implementation binding
Brad-Edwards Jun 28, 2026
b975b36
Resolve participant binding sonar findings
Brad-Edwards Jun 28, 2026
1f8f5b7
Fix participant binding sonar line length
Brad-Edwards Jun 28, 2026
f916412
Merge remote-tracking branch 'origin/dev' into 599-bind-participant-a…
Brad-Edwards Jun 28, 2026
57ab34b
chore: enable review-cap disposition gate in shadow mode (judge on)
Brad-Edwards Jun 28, 2026
d28d314
Merge pull request #617 from Brad-Edwards/599-bind-participant-agent
Brad-Edwards Jun 28, 2026
3028a5e
Add observability evidence conformance
Brad-Edwards Jun 28, 2026
7fe96ab
Merge branch 'dev' into enable-review-disposition-shadow
Brad-Edwards Jun 28, 2026
41185c5
Merge pull request #618 from Brad-Edwards/enable-review-disposition-s…
Brad-Edwards Jun 28, 2026
a64455a
Merge branch 'dev' into 128-run-316-observability
Brad-Edwards Jun 28, 2026
3543dbb
Merge pull request #619 from Brad-Edwards/128-run-316-observability
Brad-Edwards Jun 28, 2026
c2167d0
Publish conformance acceptance bar for libvirt provisioning-only mani…
Brad-Edwards Jun 28, 2026
f9af972
Merge pull request #620 from Brad-Edwards/602-libvirt-backend-manifest
Brad-Edwards Jun 28, 2026
df06762
Add libvirt participant runtime for the paper scenario
Brad-Edwards Jun 29, 2026
0121679
Resolve SonarCloud code smells in participant domain adapter
Brad-Edwards Jun 29, 2026
eb7af18
Share libvirt participant test fixtures to clear Sonar duplication
Brad-Edwards Jun 29, 2026
90841e4
Share RUN-311 lifecycle with stub backend; retire stubs.py oversized …
Brad-Edwards Jun 29, 2026
6a1e8d1
Merge branch 'dev' into 614-libvirt-participant-runtime
Brad-Edwards Jun 29, 2026
554a139
Merge pull request #621 from Brad-Edwards/614-libvirt-participant-run…
Brad-Edwards Jun 29, 2026
cf9afd3
Add libvirt paper-proof evaluator-evidence artifact producer (#615)
Brad-Edwards Jun 30, 2026
e1141d3
Resolve SonarCloud findings in libvirt paper-evidence modules
Brad-Edwards Jun 30, 2026
7532721
Reduce _run_participant_lifecycle cognitive complexity (SonarCloud)
Brad-Edwards Jun 30, 2026
0437d9e
Collapse _admit_one_action to three returns (SonarCloud)
Brad-Edwards Jun 30, 2026
d0b8a02
Merge pull request #622 from Brad-Edwards/615-libvirt-paper-evidence
Brad-Edwards Jun 30, 2026
87142b2
Realize plan resources on libvirt with full dynamic realization
Brad-Edwards Jun 30, 2026
0dde638
ci: re-trigger CI/SonarCloud for PR #623
Brad-Edwards Jun 30, 2026
795a141
Merge remote-tracking branch 'origin/dev' into 603-realize-plan-libvirt
Brad-Edwards Jun 30, 2026
180715a
fix: clear SonarCloud new-code findings in the libvirt backend
Brad-Edwards Jun 30, 2026
72b87e0
Merge pull request #623 from Brad-Edwards/603-realize-plan-libvirt
Brad-Edwards Jul 1, 2026
122212a
Honor reconciliation and make teardown idempotent on libvirt backend
Brad-Edwards Jul 1, 2026
e37be16
Refactor libvirt driver realize/teardown to clear SonarCloud findings
Brad-Edwards Jul 1, 2026
4dcf01d
Bound OCI import fetches against memory and disk exhaustion
Brad-Edwards Jul 1, 2026
733b235
Fix SonarCloud findings (cycle 1)
Brad-Edwards Jul 1, 2026
cae9a6d
Add real-daemon libvirt smoke harness (tools/real-daemon)
Brad-Edwards Jul 1, 2026
e6d2eaf
Fail closed when convergence cannot stop an owned object (#604)
Brad-Edwards Jul 1, 2026
56657cd
Pre-create host dirs in real-daemon AWS smoke script
Brad-Edwards Jul 1, 2026
3cf6d96
Merge pull request #632 from Brad-Edwards/12-bound-oci-fetches
Brad-Edwards Jul 1, 2026
3c88e0a
Merge branch 'dev' into 604-libvirt-teardown-reconciliation
Brad-Edwards Jul 1, 2026
a2ccc81
Merge pull request #625 from Brad-Edwards/604-libvirt-teardown-reconc…
Brad-Edwards Jul 1, 2026
16880e7
Add offensive behavior vocabularies
Brad-Edwards Jul 1, 2026
1db2597
feat(libvirt): typed capability diagnostics for out-of-envelope plan …
Brad-Edwards Jul 1, 2026
4111ecb
refactor(libvirt): reduce capability_envelope_diagnostics cognitive c…
Brad-Edwards Jul 1, 2026
cb2329a
Add operational apparatus summary
Brad-Edwards Jul 1, 2026
7b298e9
Merge pull request #634 from Brad-Edwards/605-libvirt-envelope-diagno…
Brad-Edwards Jul 1, 2026
2a6a404
Adopt pinned ATT&CK tactic vocabulary
Brad-Edwards Jul 1, 2026
69e2cf2
Merge remote-tracking branch 'origin/dev' into 209-offensive-behavior…
Jul 1, 2026
742d88f
Add CAGE-2 replication architecture
Jul 1, 2026
6d326c4
Merge branch 'dev' into 338-operational-observability
Brad-Edwards Jul 2, 2026
b26876c
Merge pull request #640 from Brad-Edwards/338-operational-observability
Brad-Edwards Jul 2, 2026
8e2540a
Merge branch 'dev' into 635-rep-001-cage-2-design
Brad-Edwards Jul 2, 2026
7ad0276
Merge pull request #641 from Brad-Edwards/635-rep-001-cage-2-design
Brad-Edwards Jul 2, 2026
302cd93
Adopt pinned ATLAS tactic vocabulary
Jul 2, 2026
6dcb37b
Merge remote-tracking branch 'origin/dev' into 209-offensive-behavior…
Jul 2, 2026
feab786
Prove real snapshot mutation in provisioning-only target conformance
Jul 2, 2026
d004bbc
Merge pull request #633 from Brad-Edwards/209-offensive-behavior-voca…
Brad-Edwards Jul 3, 2026
dae2cf3
Fix SonarCloud findings (cycle 1)
Jul 3, 2026
8653ca0
Verify OCI config blob integrity and bind root_file into module signa…
Jul 3, 2026
4521a2d
Document API-419 preflight guardrails
Jul 3, 2026
e6d6082
Merge branch 'dev' into 606-libvirt-conformance
Brad-Edwards Jul 3, 2026
50c2940
Merge pull request #642 from Brad-Edwards/606-libvirt-conformance
Brad-Edwards Jul 3, 2026
103300e
Merge branch 'dev' into 14-oci-config-integrity
Brad-Edwards Jul 3, 2026
c109f81
Merge pull request #643 from Brad-Edwards/14-oci-config-integrity
Brad-Edwards Jul 3, 2026
9cd68b1
Merge branch 'dev' into 339-observation-disclosure-contracts
Brad-Edwards Jul 4, 2026
b61eb02
Merge pull request #644 from Brad-Edwards/339-observation-disclosure-…
Brad-Edwards Jul 4, 2026
e2c48e5
Add advisory OSV-scanner CI job for Python dependency CVEs
Jul 4, 2026
9650832
Add paper demonstration corpus with cross-backend invariant ledger (#…
Jul 4, 2026
8f4a4d9
Merge pull request #664 from Brad-Edwards/34-osv-scanner-ci
Brad-Edwards Jul 4, 2026
6408d55
Fix SonarCloud quality-gate findings in the #600 corpus modules
Jul 4, 2026
8f2faf7
Parameterize target-conformance provisioning probe with a reference s…
Jul 4, 2026
4cb0b55
Merge pull request #669 from Brad-Edwards/663-conformance-probe-capab…
Brad-Edwards Jul 4, 2026
3463484
Merge branch 'dev' into 600-paper-demo-corpus
Brad-Edwards Jul 4, 2026
ce1bc07
Merge pull request #665 from Brad-Edwards/600-paper-demo-corpus
Brad-Edwards Jul 4, 2026
158f3d5
Document realization envelope semantics
Jul 4, 2026
bfe4515
Merge pull request #672 from Brad-Edwards/667-realization-envelope
Brad-Edwards Jul 4, 2026
b1ad706
Rename paper-* identifiers to functional names; relocate corpus (#670)
Jul 4, 2026
d6ebdae
Merge branch 'dev' into 670-rename-paper-identifiers
Brad-Edwards Jul 4, 2026
17b14d9
Merge pull request #677 from Brad-Edwards/670-rename-paper-identifiers
Brad-Edwards Jul 4, 2026
f3a3241
Validate SDL variable names and references
Jul 4, 2026
7a5e2bf
Refactor SDL variable reference traversal
Jul 5, 2026
d1f0e7b
Merge remote-tracking branch 'origin/dev' into 655-dsl-variable-system
Jul 5, 2026
20e0a8d
Publish GOV-913 reusable-asset trust and integrity policy contract
Jul 5, 2026
f850c19
Fix SonarCloud findings (cycle 1)
Jul 5, 2026
aad2f82
Add validation admission profile design
Jul 5, 2026
212ff0f
Merge branch 'dev' into 115-GOV-913-trust-integrity
Brad-Edwards Jul 5, 2026
76ee50a
Merge pull request #679 from Brad-Edwards/115-GOV-913-trust-integrity
Brad-Edwards Jul 5, 2026
da4c5c7
Merge branch 'dev' into 655-dsl-variable-system
Brad-Edwards Jul 5, 2026
1ab73f1
Merge pull request #678 from Brad-Edwards/655-dsl-variable-system
Brad-Edwards Jul 5, 2026
2fb498e
Merge origin/dev into 97-asr-511-validation
Jul 5, 2026
4df4e8e
Merge pull request #680 from Brad-Edwards/97-asr-511-validation
Brad-Edwards Jul 5, 2026
a1fb96e
Add proposed ADR-073 on scoring/reward language scope
Jul 5, 2026
c8bbbad
Merge pull request #681 from Brad-Edwards/671-scoring-reward-scope
Brad-Edwards Jul 5, 2026
b648c99
docs(security): record 2026-07-01 commit authorship anomaly
Brad-Edwards Jul 5, 2026
5eb8078
Merge pull request #683 from Brad-Edwards/security-note-authorship-an…
Brad-Edwards Jul 5, 2026
1982e76
ci: publish aces-sdl to PyPI and auto-release on merge to main (#686)
Brad-Edwards Jul 5, 2026
6d7aa1f
ci: switch releases to a committed __version__ literal + towncrier-dr…
Brad-Edwards Jul 5, 2026
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
16 changes: 13 additions & 3 deletions .gc/plan-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@ These encode the hard rules previously in `AGENTS.md` prose.
aligned with changed code and tests.
- Plans with a user-visible change MUST add a fragment under
`changelog.d/<issue>.<type>.md` (or `changelog.d/+<slug>.<type>.md`
for issue-free entries), where `<type>` is one of `security`, `added`,
`changed`, `deprecated`, `removed`, `fixed`; do not edit `CHANGELOG.md`
directly outside release-collation commits.
for issue-free entries), where `<type>` is one of `breaking`, `security`,
`added`, `changed`, `deprecated`, `removed`, `fixed`. The fragment `<type>`
drives the release version bump (`tools/release.py`): `removed` → major (once
≥ 1.0; pre-1.0 it is a minor), `added`/`changed`/`deprecated` → minor,
`security`/`fixed` → patch; `breaking` is recorded in the changelog but does
NOT auto-bump (force a major with `release.py --version 1.0.0`). Do not edit
`CHANGELOG.md` directly outside release-collation commits.
- Plans MUST NOT hand-edit the version. It is a single committed literal,
`__version__` in `implementations/python/src/aces/__init__.py`, bumped only by
`tools/release.py` from the pending changelog fragments at release time (#684).
The PR title must still pass the `title-guard` conventional-shape / no-branding
gate (`tools/check_pr_title.py`), but the PR title does NOT drive the version —
only the changelog fragment types do.
26 changes: 26 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Dependabot keeps the SHA-pinned GitHub Actions (and Python deps) patched, so
# the pins in .github/workflows/*.yml do not silently rot (#684 release hardening).
version: 2
updates:
# GitHub Actions used across all workflows (release.yml pins are publishing-
# critical: they run in a job with id-token: write). Grouped so a batch of pin
# bumps arrives as one reviewable PR.
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 5
groups:
github-actions:
patterns: ["*"]

# Python package dependencies for the aces-sdl distribution.
- package-ecosystem: "pip"
directory: "/implementations/python"
schedule:
interval: "weekly"
open-pull-requests-limit: 5
groups:
python-minor-patch:
patterns: ["*"]
update-types: ["minor", "patch"]
25 changes: 25 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,31 @@ jobs:
if: steps.runtime.outputs.available == 'true'
run: uv tool run --from 'nox[uv]==2026.4.10' nox -f noxfile.py -s integration_docker

# Advisory OSV-scanner sweep over the Python dependency lockfile (issue #34).
# Non-gating: CVE findings surface as an uploaded JSON artifact and never fail
# the build. The `osv_scan` nox session is kept out of the hermetic `verify`
# graph; genuine scanner/setup failures still fail this job's step (visible as
# a soft failure) so the advisory posture never hides a broken scan.
supply-chain:
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: "3.12"
- name: Install uv
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8
- name: Run OSV-scanner (advisory)
run: uv tool run --from 'nox[uv]==2026.4.10' nox -f noxfile.py -s osv_scan
- name: Upload OSV-scanner report
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: osv-scanner-report
path: implementations/python/osv-scanner-report.json
if-no-files-found: warn

sonar:
runs-on: ubuntu-latest
needs: [verify]
Expand Down
46 changes: 46 additions & 0 deletions .github/workflows/pr-title-lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: PR Title Lint

# Repository-side guard against agent-branded PR titles (e.g. `[codex] ...`)
# and a check that titles follow the conventional shape Ground Control
# documents for /implement Step 9. Enforced by repo automation, not only by
# agent workflow instructions (issue #567).
#
# The PR title is untrusted event data, so it is read from $GITHUB_EVENT_PATH
# by the checker (never shell-interpolated), the token is read-only, and this
# uses `pull_request` (never `pull_request_target`). There is no exemption for
# PRs targeting `dev`.
on:
pull_request:
types: [opened, edited, synchronize, reopened]

permissions:
contents: read

concurrency:
group: pr-title-lint-${{ github.event.pull_request.number }}
cancel-in-progress: true

jobs:
title-guard:
runs-on: ubuntu-latest
steps:
# Check out the BASE ref, not the PR head, so the policy executable is
# the trusted already-merged copy. A PR that edits or removes
# tools/check_pr_title.py must not be able to weaken its own required
# check (codex review finding, issue #567). The shared validator is still
# exercised against the PR's own code by the test suite in ci.yml.
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
ref: ${{ github.event.pull_request.base.sha }}
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: "3.12"
- name: Validate PR title
# The checker reads the title from $GITHUB_EVENT_PATH (set automatically
# by the runner). It is stdlib-only, so no dependency install is needed.
run: |
if [ ! -f tools/check_pr_title.py ]; then
echo "::notice::tools/check_pr_title.py is not on the base ref yet; skipping (bootstrap for the PR that introduces the guard). Enforcement is active for every subsequent PR."
exit 0
fi
python tools/check_pr_title.py
177 changes: 127 additions & 50 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,65 +1,114 @@
name: Release

# Cut a release by pushing a version tag, e.g. `git tag v0.3.0 && git push origin v0.3.0`.
# Builds the corpus-bundled wheel + sdist and publishes a GitHub Release with the
# artifacts attached, so downstream backends can pin a real version instead of a
# `dev` commit SHA. See docs/explain/releasing.md for the full runbook.
# Committed-literal release (#684), built on the corpus-bundled wheel (#537).
#
# Release integrity (issue #537 codex review):
# * The only trigger is a `v*` tag push — there is no `workflow_dispatch`, so a
# manual run can never publish a Release named after a branch from untagged
# code.
# * The build job checks the tag commit is reachable from the protected default
# branch (`main`) BEFORE it runs any repository-controlled build code
# (`uv build` runs the hatch build hook), and checks out with
# `persist-credentials: false` so no write-scoped token sits in the git config
# while that build code executes.
# * Publishing happens in a separate job that only consumes the already-built
# artifacts. The `contents: write` token is scoped to that job alone and is
# used only by `gh release create`, so tag-controlled build hooks never run in
# a context that holds the write token.
# The version is the single committed literal `__version__` in
# src/aces/__init__.py. `tools/release.py` bumps it from the pending towncrier
# changelog fragments and collates CHANGELOG.md on a `release/vX.Y.Z` branch; that
# opens a PR to `main`. Merging it (a normal human-reviewed PR merge) is the only
# thing that puts a new version on `main` — this workflow never commits to `main`,
# it only reads the literal and creates a tag, so no bot/PAT/deploy-key/bypass is
# needed.
#
# On push to `main` the `decide` job publishes iff:
# * the changelog fragments have been collated (none pending) — a real release
# always collates first, so a plain `dev`->`main` promotion with pending
# fragments never publishes a half-baked version; and
# * no tag exists yet for the current `__version__`.
# The `release` job then builds the corpus-bundled wheel/sdist, verifies the
# corpus payload and that the built version matches, attaches a CycloneDX SBOM,
# publishes to PyPI via OIDC trusted publishing, and cuts a GitHub Release whose
# notes are the CHANGELOG.md section. First release + PyPI setup:
# docs/explain/releasing.md.
on:
push:
tags: ["v*"]
branches: [main]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: release
cancel-in-progress: false

env:
VERSION_FILE: implementations/python/src/aces/__init__.py

jobs:
build:
decide:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
outputs:
release: ${{ steps.decide.outputs.release }}
version: ${{ steps.decide.outputs.version }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0
persist-credentials: false
- name: Verify the tag is reachable from the protected default branch
- name: Decide whether to release
id: decide
run: |
set -euo pipefail
git fetch --no-tags origin main:refs/remotes/origin/main
tag_sha="$(git rev-parse HEAD)"
if ! git merge-base --is-ancestor "${tag_sha}" origin/main; then
echo "::error::tag ${GITHUB_REF_NAME} (${tag_sha}) is not reachable from origin/main; refusing to build or publish a release from unreviewed code"
exit 1
version="$(grep -oP '^__version__\s*=\s*"\K[^"]+' "${VERSION_FILE}")"
if [ -z "${version}" ]; then
echo "::error::no __version__ literal in ${VERSION_FILE}"; exit 1
fi
# Guard: uncollated fragments mean this is not a prepared release
# (release.py collates before opening the release PR). Never publish a
# version whose changelog has not been collated.
if find changelog.d -type f -name '*.md' ! -name '_*' ! -name 'README.md' | grep -q .; then
echo "::notice::changelog fragments are still pending; run tools/release.py to prepare a release. Skipping."
echo "release=false" >> "$GITHUB_OUTPUT"; exit 0
fi
echo "tag ${GITHUB_REF_NAME} (${tag_sha}) is an ancestor of origin/main"
if git rev-parse -q --verify "refs/tags/v${version}" >/dev/null; then
echo "::notice::v${version} already tagged; nothing to release"
echo "release=false" >> "$GITHUB_OUTPUT"; exit 0
fi
echo "::notice::releasing v${version}"
echo "release=true" >> "$GITHUB_OUTPUT"
echo "version=${version}" >> "$GITHUB_OUTPUT"

release:
needs: decide
if: needs.decide.outputs.release == 'true'
runs-on: ubuntu-latest
environment: pypi
permissions:
contents: write # create the release tag + the GitHub Release
id-token: write # OIDC trusted publishing to PyPI (no stored token)
env:
VERSION: ${{ needs.decide.outputs.version }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0

- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: "3.12"

- name: Install uv
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8
- name: Build wheel + sdist

- name: Build the corpus-bundled wheel + sdist
run: uv build --out-dir dist implementations/python
- name: Verify the contract corpus is bundled in the wheel

- name: Verify built version matches and the corpus is bundled
run: |
python - <<'PY'
import glob
import os
import sys
import zipfile

expected = os.environ["VERSION"]
wheels = glob.glob("dist/aces_sdl-*.whl")
if len(wheels) != 1:
sys.exit(f"expected exactly one wheel, found {wheels}")
built = wheels[0].split("/")[-1].split("-")[1]
if built != expected:
sys.exit(f"built version {built!r} != __version__ {expected!r}")
names = zipfile.ZipFile(wheels[0]).namelist()
required = [
"aces_contracts/_corpus/profiles/backend/provisioning-only.json",
Expand All @@ -70,31 +119,59 @@ jobs:
missing = [r for r in required if not any(n == r or n.startswith(r) for n in names)]
if missing:
sys.exit(f"wheel is missing corpus payload: {missing}")
print(f"corpus payload present: {sum(n.startswith('aces_contracts/_corpus/') for n in names)} files")
print(f"v{expected}: corpus payload present ({sum(n.startswith('aces_contracts/_corpus/') for n in names)} files)")
PY
- name: Upload built artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: release-dist
path: dist/*
if-no-files-found: error

publish:
needs: build
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Download built artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
- name: Generate CycloneDX SBOM
run: |
set -euo pipefail
python -m pip install --upgrade pip
python -m pip install dist/*.whl cyclonedx-bom
mkdir -p sbom
cyclonedx-py environment --output-format JSON --output-file sbom/aces-sdl.cdx.json

- name: Extract the changelog section for the release notes
run: |
python - <<'PY' > notes.md
import os
import pathlib
import re

ver = os.environ["VERSION"]
lines = pathlib.Path("CHANGELOG.md").read_text(encoding="utf-8").splitlines()
out, capturing = [], False
header = re.compile(r"^##\s*\[" + re.escape(ver) + r"\]")
any_header = re.compile(r"^##\s*\[")
for line in lines:
if header.match(line):
capturing = True
continue
if capturing and any_header.match(line):
break
if capturing:
out.append(line)
body = "\n".join(out).strip()
print(body if body else f"Release v{ver}")
PY

- name: Create + push the release tag (tag-only; main is never committed to)
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git -c tag.gpgSign=false tag -a "v${VERSION}" -m "v${VERSION}"
git push origin "v${VERSION}"

- name: Publish to PyPI (OIDC trusted publishing)
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1
with:
name: release-dist
path: dist
- name: Publish GitHub Release
packages-dir: dist

- name: Create the GitHub Release (notes from the changelog)
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release create "${GITHUB_REF_NAME}" dist/* \
gh release create "v${VERSION}" dist/* sbom/aces-sdl.cdx.json \
--repo "${GITHUB_REPOSITORY}" \
--title "${GITHUB_REF_NAME}" \
--generate-notes
--title "v${VERSION}" \
--notes-file notes.md
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -218,3 +218,6 @@ __marimo__/
# .gc/plan-rules.md and other authored .gc files stay tracked.
.gc/sonar/
.gc/telemetry/

# OSV-scanner advisory report (generated by the `osv_scan` nox session; issue #34).
implementations/python/osv-scanner-report.json
6 changes: 6 additions & 0 deletions .ground-control.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ workflow:
completion_command: uv tool run --from 'nox[uv]==2026.4.10' nox -f noxfile.py -s verify
lint_command: uv tool run --from 'nox[uv]==2026.4.10' nox -f noxfile.py -s lint
format_command: uv tool run --from 'nox[uv]==2026.4.10' nox -f noxfile.py -s hygiene
review_disposition:
enabled: true
mode: authoritative
max_auto_overrides: 1
judge:
enabled: true
docs:
adr_dir: docs/decisions/adrs/
example_paths:
Expand Down
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ repos:
hooks:
- id: nox-pre-commit
name: nox pre-commit gate
entry: bash -c 'uv tool run --from "nox[uv]==2026.4.10" nox -f noxfile.py -s hook-pre-commit -- "$@"' --
entry: bash -c 'unset $(git rev-parse --local-env-vars); uv tool run --from "nox[uv]==2026.4.10" nox -f noxfile.py -s hook-pre-commit -- "$@"' --
language: system
pass_filenames: true
require_serial: true
stages: [pre-commit]
- id: nox-pre-push
name: nox pre-push verify
entry: bash -c 'uv tool run --from "nox[uv]==2026.4.10" nox -f noxfile.py -s hook-pre-push'
entry: bash -c 'unset $(git rev-parse --local-env-vars); uv tool run --from "nox[uv]==2026.4.10" nox -f noxfile.py -s hook-pre-push'
language: system
pass_filenames: false
require_serial: true
Expand Down
Loading
Loading