diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 169c149d..b19b296c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -5,5 +5,5 @@ .github/workflows/ @agentrust-io/maintainers @lywinged @rajnisht7 # Conformance suite retains its existing maintainer review ownership. -/conformance/ @agentrust-io/maintainers -/.github/workflows/conformance-*.yml @agentrust-io/maintainers +/conformance/ @agentrust-io/maintainers @lywinged @rajnisht7 +/.github/workflows/conformance-*.yml @agentrust-io/maintainers @lywinged @rajnisht7 diff --git a/.github/maintainers.json b/.github/maintainers.json new file mode 100644 index 00000000..0b75bfdc --- /dev/null +++ b/.github/maintainers.json @@ -0,0 +1,14 @@ +{ + "schema": 1, + "repository": "trace-spec", + "source": "agentrust-io/.github:maintainers/roster.json", + "maintainers": [ + "imran-siddique", + "lywinged", + "pforest", + "Qiang-Xu", + "rajnisht7" + ], + "security_paths": [], + "security_approvals": 1 +} diff --git a/.github/workflows/conformance-package.yml b/.github/workflows/conformance-package.yml index 4410a174..e1eed02f 100644 --- a/.github/workflows/conformance-package.yml +++ b/.github/workflows/conformance-package.yml @@ -1,6 +1,7 @@ name: Conformance package build on: + workflow_call: workflow_dispatch: pull_request: paths: diff --git a/.github/workflows/conformance-publish.yml b/.github/workflows/conformance-publish.yml new file mode 100644 index 00000000..5ee68d10 --- /dev/null +++ b/.github/workflows/conformance-publish.yml @@ -0,0 +1,69 @@ +name: Conformance publisher + +on: + release: + types: [published] + workflow_dispatch: + inputs: + target: + description: Build only, or publish to a verified TestPyPI publisher + type: choice + options: [build, testpypi] + default: build + +permissions: + contents: read + +jobs: + build: + if: github.event_name == 'workflow_dispatch' || startsWith(github.event.release.tag_name, 'conformance-v') + uses: ./.github/workflows/conformance-package.yml + tag: + if: github.event_name == 'release' && startsWith(github.event.release.tag_name, 'conformance-v') + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: '3.12' + - name: Match conformance tag to package version + env: + RELEASE_TAG: ${{ github.event.release.tag_name }} + run: | + python - <<'PY' + import os, tomllib + from pathlib import Path + version = tomllib.loads(Path("conformance/pyproject.toml").read_text())["project"]["version"] + if os.environ["RELEASE_TAG"] != "conformance-v" + version: + raise SystemExit("Conformance release tag does not match package version") + PY + testpypi: + if: github.event_name == 'workflow_dispatch' && inputs.target == 'testpypi' && vars.CONFORMANCE_TESTPYPI_ENABLED == 'true' + needs: build + runs-on: ubuntu-latest + environment: conformance-testpypi + permissions: + id-token: write + steps: + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: conformance-dist + path: dist/ + - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2 + with: + repository-url: https://test.pypi.org/legacy/ + pypi: + if: github.event_name == 'release' && startsWith(github.event.release.tag_name, 'conformance-v') && vars.CONFORMANCE_PYPI_ENABLED == 'true' + needs: [build, tag] + runs-on: ubuntu-latest + environment: conformance-pypi + permissions: + id-token: write + steps: + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: conformance-dist + path: dist/ + - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2 diff --git a/.github/workflows/require-maintainer-approval.yml b/.github/workflows/require-maintainer-approval.yml index bf72a4ea..2549fecd 100644 --- a/.github/workflows/require-maintainer-approval.yml +++ b/.github/workflows/require-maintainer-approval.yml @@ -28,54 +28,70 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | - const MAINTAINERS = ['imran-siddique', 'lywinged', 'pforest', 'Qiang-Xu', 'rajnisht7']; - - const author = context.payload.pull_request.user.login; - if (MAINTAINERS.includes(author)) { - core.info(`Author ${author} is a maintainer, skipping gate`); - return; - } - - // Both pull_request_target and pull_request_review payloads - // carry the PR at context.payload.pull_request. - const prNumber = context.payload.pull_request.number; - - // Re-fetch the PR so we compare against the head SHA at - // evaluation time, not a possibly stale SHA from the payload. const { data: pr } = await github.rest.pulls.get({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: prNumber, + owner: context.repo.owner, repo: context.repo.repo, + pull_number: context.payload.pull_request.number, }); - const headSha = pr.head.sha; - - // Paginate: listReviews otherwise returns only the first 30. + // Read only reviewed base-branch data. Never execute or read policy from a PR head. + let file; + try { + ({ data: file } = await github.rest.repos.getContent({ + owner: context.repo.owner, repo: context.repo.repo, + path: '.github/maintainers.json', ref: pr.base.sha, + })); + } catch (error) { + // One-commit bootstrap: the initial policy PR must be reviewable before its + // policy file exists on main. Missing policy on any later base fails closed. + if (error.status !== 404 || pr.base.sha !== "c5cfa00e1c62ca1b4da2b999fc16e943dad09d90") throw error; + file = { encoding: 'base64', content: Buffer.from(JSON.stringify({"schema":1,"repository":"trace-spec","source":"agentrust-io/.github:maintainers/roster.json","maintainers":["imran-siddique","lywinged","pforest","Qiang-Xu","rajnisht7"],"security_paths":[],"security_approvals":1})).toString('base64') }; + } + if (Array.isArray(file) || file.encoding !== 'base64') { + throw new Error('Missing base-branch maintainer policy'); + } + const policy = JSON.parse(Buffer.from(file.content, 'base64').toString('utf8')); + if (policy.schema !== 1 || policy.repository !== context.repo.repo || + !Array.isArray(policy.maintainers) || policy.maintainers.length < 2 || + !policy.maintainers.every(x => typeof x === 'string' && /^[A-Za-z0-9-]+$/.test(x)) || + new Set(policy.maintainers.map(x => x.toLowerCase())).size !== policy.maintainers.length || + !Array.isArray(policy.security_paths) || + !policy.security_paths.every(x => typeof x === 'string' && x.endsWith('/') && !x.startsWith('/') && !x.includes('..')) || + policy.security_approvals !== (policy.security_paths.length ? 2 : 1)) { + throw new Error('Invalid base-branch maintainer policy'); + } + const maintainers = new Set(policy.maintainers.map(x => x.toLowerCase())); + let securityChange = false; + if (policy.security_paths.length) { + const files = await github.paginate(github.rest.pulls.listFiles, { + owner: context.repo.owner, repo: context.repo.repo, pull_number: pr.number, + }); + if (files.length !== pr.changed_files) throw new Error('Incomplete changed-file inventory'); + // Renaming a security file out of its directory must still require two reviews. + securityChange = files.some(f => [f.filename, f.previous_filename] + .some(p => typeof p === 'string' && policy.security_paths.some(prefix => p.startsWith(prefix)))); + } + const author = pr.user.login.toLowerCase(); + if (!securityChange && maintainers.has(author)) { + core.info('Maintainer-authored routine change: branch rules still require independent review.'); + return; + } const reviews = await github.paginate(github.rest.pulls.listReviews, { - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: prNumber, + owner: context.repo.owner, repo: context.repo.repo, pull_number: pr.number, }); - - // Only a maintainer's most recent non-comment review counts, - // and it must approve the current head commit. An approval of - // an older commit does not clear the gate after a later push - // (prevents approve-then-swap). - const latestByMaintainer = new Map(); - for (const review of reviews) { - if ( - review.user && - review.user.type === 'User' && - MAINTAINERS.includes(review.user.login) && - review.state !== 'COMMENTED' - ) { - latestByMaintainer.set(review.user.login, review); + const latest = new Map(); + for (const review of reviews.slice().sort((a, b) => + Date.parse(a.submitted_at || 0) - Date.parse(b.submitted_at || 0) || a.id - b.id)) { + const login = review.user?.login?.toLowerCase(); + if (review.user?.type === 'User' && maintainers.has(login) && + login !== author && review.state !== 'COMMENTED') { + latest.set(login, review); } } - - const approved = [...latestByMaintainer.values()].some( - (r) => r.state === 'APPROVED' && r.commit_id === headSha - ); - - if (!approved) { - core.setFailed('Waiting for maintainer approval of the current head commit before merging.'); + const approvals = [...latest.values()].filter(r => + r.state === 'APPROVED' && r.commit_id === pr.head.sha); + const required = securityChange ? policy.security_approvals : 1; + if (approvals.length < required) { + core.setFailed('Awaiting ' + required + ' independent maintainer approval(s) of current head ' + + pr.head.sha + '; found ' + approvals.length + '. Branch rules and specification governance also apply.'); + } else { + core.info(approvals.length + ' maintainer(s) approved current head ' + pr.head.sha); } diff --git a/CHARTER.md b/CHARTER.md index d8a21dab..365aa3d2 100644 --- a/CHARTER.md +++ b/CHARTER.md @@ -18,7 +18,7 @@ The project includes: - **The TRACE Specification**: normative text defining the Trust Record schema, wire format, signing and key management protocol, verification rules, hardware root profiles, and conformance requirements. - **JSON Schema**: machine-readable schema for Trust Record validation. -- **Conformance test suite**: the canonical tests validating compliance (in [agentrust-io/trace-tests](https://github.com/agentrust-io/trace-tests)). +- **Conformance test suite**: the canonical tests validating compliance (in [trace-spec/conformance](https://github.com/agentrust-io/trace-spec/tree/main/conformance)). - **Vendor platform annexes**: informative, vendor-co-authored claim-mapping documents for each silicon and cloud attestation surface. - **Reference examples**: example Trust Records for each supported hardware platform. @@ -55,7 +55,7 @@ Use of "TRACE-conformant" to describe an implementation is permitted only when t ## 6. Conformance -An implementation may claim TRACE conformance only by passing the conformance test suite in [agentrust-io/trace-tests](https://github.com/agentrust-io/trace-tests) at the level being claimed (Level 0, 1, or 2). Conformance claims must reference the test suite version and include a link to a passing run. +An implementation may claim TRACE conformance only by passing the conformance test suite in [trace-spec/conformance](https://github.com/agentrust-io/trace-spec/tree/main/conformance) at the level being claimed (Level 0, 1, or 2). Conformance claims must reference the test suite version and include a link to a passing run. Test suite changes that would invalidate previously conformant implementations require a spec version increment. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c0a5728d..bafad21a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributing to TRACE -TRACE is an open specification. Contributions are welcome in four areas: the specification text, the JSON Schema, the examples, and the conformance test suite (in [agentrust-io/trace-tests](https://github.com/agentrust-io/trace-tests)). +TRACE is an open specification. Contributions are welcome in four areas: the specification text, the JSON Schema, the examples, and the conformance test suite (in [trace-spec/conformance](https://github.com/agentrust-io/trace-spec/tree/main/conformance)). ## Running the reference-library checks diff --git a/ROADMAP.md b/ROADMAP.md index 737b0985..f33ba74b 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,6 +1,6 @@ # Roadmap -Status as of September 2026. Spec **v0.2** is current ([`spec/trace-v0.2.md`](spec/trace-v0.2.md)); the `agentrust-trace` reference SDK is at **0.11.0** and the conformance suite (`agentrust-io/trace-tests`) at **0.5.1**. +Status as of September 2026. Spec **v0.2** is current ([`spec/trace-v0.2.md`](spec/trace-v0.2.md)); the `agentrust-trace` reference SDK is at **0.11.0** and the conformance suite (`trace-spec/conformance`) at **0.5.1**. ## Shipped: v0.2 (July to August 2026) diff --git a/docs/repository-consolidation.md b/docs/repository-consolidation.md index 73ec1c53..10db6de6 100644 --- a/docs/repository-consolidation.md +++ b/docs/repository-consolidation.md @@ -33,3 +33,19 @@ python -m pytest -v --tb=short 3. Configure a trusted PyPI publisher for the existing package from trace-spec with a separate workflow and release policy. Root library tags must not release the conformance package accidentally. Verify a release from the new publisher before retiring the original release workflow. 4. Move the tests.agentrust-io.com deployment to a host supporting a second site from this repository, or a dedicated deployment repository. A second gh-deploy in trace-spec would overwrite the specification site, so this PR only builds the tests documentation. Keep the existing source deployment active until the replacement is verified. 5. Add a moved notice to the original README and archive trace-tests only after the consumer, release and site cutovers. Retain its Git history, releases and issues; never delete the repository. + +## Prepared publisher handover + +`conformance-publish.yml` reuses the installed-artifact package build. Its manual default only builds. Root library release tags are ignored; conformance releases use `conformance-v` and must match `conformance/pyproject.toml`. Publication is disabled unless its explicit repository variable is `true`. + +Before enabling either publisher: + +1. Configure the existing `agentrust-trace-tests` project with repository `agentrust-io/trace-spec`, workflow `conformance-publish.yml`, and the matching environment: `conformance-testpypi` or `conformance-pypi`. +2. Configure independent environment approval and verify two available package owners/release operators. Louie and Rajnish are the proposed operators; their package access is not yet verified. +3. Set `CONFORMANCE_TESTPYPI_ENABLED=true` only after its TestPyPI binding and approvals are ready. Run the manual TestPyPI target and verify the installed artifact. +4. Enable `CONFORMANCE_PYPI_ENABLED=true` only after the production binding and approval requirements are verified. Publish an approved, previously unpublished conformance version; do not republish an existing version or bump versions solely for migration. +5. Link the successful production run and installed-package verification before retiring the old publisher. + +The workflow is prepared, not evidence that a trusted publisher or environment has been configured. Keep the original release path active until verification. + +For documentation, use a host supporting a separate project/site from the same repository, with `conformance/` as its project root. Existing `conformance-docs.yml` builds the site artifact. Verify the separate deployment and custom domain before changing DNS or retiring the original site. Do not deploy that artifact to the specification's GitHub Pages site.