diff --git a/.github/maintainers.json b/.github/maintainers.json new file mode 100644 index 0000000..dbcad46 --- /dev/null +++ b/.github/maintainers.json @@ -0,0 +1,14 @@ +{ + "schema": 1, + "repository": "integrations", + "source": "agentrust-io/.github:maintainers/roster.json", + "maintainers": [ + "imran-siddique", + "pforest", + "podcastinator", + "carloshvp", + "Qiang-Xu" + ], + "security_paths": [], + "security_approvals": 1 +} diff --git a/.github/workflows/require-maintainer-approval.yml b/.github/workflows/require-maintainer-approval.yml index cf1abda..e1fabf7 100644 --- a/.github/workflows/require-maintainer-approval.yml +++ b/.github/workflows/require-maintainer-approval.yml @@ -25,54 +25,70 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | - const MAINTAINERS = ['imran-siddique', 'pforest', 'podcastinator', 'carloshvp', 'Qiang-Xu']; - - 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 !== "06c1dd113d86ae94844bcc8840b5c8c78bc62e83") throw error; + file = { encoding: 'base64', content: Buffer.from(JSON.stringify({"schema":1,"repository":"integrations","source":"agentrust-io/.github:maintainers/roster.json","maintainers":["imran-siddique","pforest","podcastinator","carloshvp","Qiang-Xu"],"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/README.md b/README.md index 0938ca2..4f83ad8 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ project governance. | Location | What belongs there | Who contributes | |---|---|---| -| [cmcp](https://github.com/agentrust-io/cmcp), [agent-manifest](https://github.com/agentrust-io/agent-manifest), [trace-spec](https://github.com/agentrust-io/trace-spec), [trace-tests](https://github.com/agentrust-io/trace-tests) | The standard and reference implementation. Bug fixes and spec feedback welcome; no vendor product code. | Maintainers; community fixes | +| [cmcp](https://github.com/agentrust-io/cmcp), [agent-manifest](https://github.com/agentrust-io/agent-manifest), [trace-spec](https://github.com/agentrust-io/trace-spec), [TRACE conformance](https://github.com/agentrust-io/trace-spec/tree/main/conformance) | The standard and reference implementation. Bug fixes and spec feedback welcome; no vendor product code. | Maintainers; community fixes | | [examples/](examples/) | First-party, end-to-end runnable examples, plus flagship partner examples by invitation. Every line is reviewed and every claim verified before merge. | Maintainers; invited partners | | [integrations/](integrations/) | Your product's integration with cMCP, TRACE, or Agent Manifest: adapters, exporters, dashboards, policy packs, verifiers. Vendor-maintained. | Anyone, self-serve | | [awesome-ai-governance](https://github.com/agentrust-io/awesome-ai-governance) | Neutral listings of notable agent-governance tools, including ones that do not integrate with this stack. | Anyone meeting the listing criteria | diff --git a/examples/agentic-commerce-accountability/released_ucp/README.md b/examples/agentic-commerce-accountability/released_ucp/README.md index f9cdc5d..e2f71b7 100644 --- a/examples/agentic-commerce-accountability/released_ucp/README.md +++ b/examples/agentic-commerce-accountability/released_ucp/README.md @@ -190,14 +190,14 @@ Business-boundary refusal, retry, and concurrency cases are in ## Released sources and nonclaims -- [UCP v2026-08-25 Checkout schema](https://github.com/Universal-Commerce-Protocol/ucp/blob/v2026-08-25/source/schemas/shopping/checkout.json), - [REST binding](https://github.com/Universal-Commerce-Protocol/ucp/blob/v2026-08-25/docs/specification/shopping/checkout/rest.md), - and [HTTP signatures](https://github.com/Universal-Commerce-Protocol/ucp/blob/v2026-08-25/docs/specification/signatures.md). +- [UCP v2026-08-25 Checkout schema](https://raw.githubusercontent.com/Universal-Commerce-Protocol/ucp/v2026-08-25/source/schemas/shopping/checkout.json), + [REST binding](https://raw.githubusercontent.com/Universal-Commerce-Protocol/ucp/v2026-08-25/docs/specification/shopping/checkout/rest.md), + and [HTTP signatures](https://raw.githubusercontent.com/Universal-Commerce-Protocol/ucp/v2026-08-25/docs/specification/signatures.md). - [Official UCP Python SDK release](https://github.com/Universal-Commerce-Protocol/python-sdk/releases/tag/v2026-08-25), installed as `ucp-sdk==0.5.0`; JSON Schema validation complements its models. - [http-message-signatures v2.0.1](https://github.com/pyauth/http-message-signatures/tree/v2.0.1), [joserfc 1.7.5](https://pypi.org/project/joserfc/1.7.5/), and - [TRACE v0.10.0 signing/verification](https://github.com/agentrust-io/trace-spec/blob/v0.10.0/src/agentrust_trace/sign.py). + [TRACE v0.10.0 signing/verification](https://raw.githubusercontent.com/agentrust-io/trace-spec/v0.10.0/src/agentrust_trace/sign.py). This demonstrates application-level accountability under configured local keys and policy. It does **not** establish real merchant identity, buyer consent, diff --git a/examples/ca2a-delegation/README.md b/examples/ca2a-delegation/README.md index c21257f..204b181 100644 --- a/examples/ca2a-delegation/README.md +++ b/examples/ca2a-delegation/README.md @@ -4,7 +4,7 @@ The other examples in this repo govern the **agent-to-tool** boundary: cMCP deci It uses the credit-risk workflow from [`financial-services/`](../financial-services/README.md) as the worked case, and maps the same pattern onto the other examples at the bottom. -> **Scope of what runs here.** cA2A is in alpha. This example exercises the part that is built today: **attenuated delegation credentials and offline chain verification** (`ca2a_runtime.delegation`). The live peer path (attesting an inbound peer, sealing the payload to its measurement) and the per-hop TRACE provenance record are cA2A roadmap. See the [cA2A ROADMAP](https://github.com/agentrust-io/ca2a/blob/main/ROADMAP.md) and [LIMITATIONS](https://github.com/agentrust-io/ca2a/blob/main/LIMITATIONS.md). +> **Scope of what runs here.** cA2A is in alpha. This example exercises the part that is built today: **attenuated delegation credentials and offline chain verification** (`ca2a_runtime.delegation`). The live peer path (attesting an inbound peer, sealing the payload to its measurement) and the per-hop TRACE provenance record are cA2A roadmap. See the [cA2A ROADMAP](https://raw.githubusercontent.com/agentrust-io/ca2a/main/ROADMAP.md) and [LIMITATIONS](https://raw.githubusercontent.com/agentrust-io/ca2a/main/LIMITATIONS.md). --- diff --git a/integrations/docker-sandbox-kit/README.md b/integrations/docker-sandbox-kit/README.md index d531a6e..dd81254 100644 --- a/integrations/docker-sandbox-kit/README.md +++ b/integrations/docker-sandbox-kit/README.md @@ -7,7 +7,7 @@ credentials, volumes, ports. This adapter takes a published Kit's platform manifest and builds a TRACE Level 0 Trust Record whose `policy.bundle_hash` is the digest of that descriptor, byte for byte as the frontend published it. -Written against [SPEC-v3](https://github.com/docker/sandbox-kit-spec/blob/main/docs/spec/SPEC-v3.md) +Written against [SPEC-v3](https://raw.githubusercontent.com/docker/sandbox-kit-spec/main/docs/spec/SPEC-v3.md) at commit `31791ea` (2026-10-01). ## Run it