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
98 changes: 90 additions & 8 deletions .github/workflows/baseline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,33 +9,115 @@ on:
permissions:
contents: read

concurrency:
group: baseline-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
verify:
workflows:
name: Workflow validation
runs-on: ubuntu-latest
timeout-minutes: 20
timeout-minutes: 5
steps:
- name: Check out repository
uses: actions/checkout@v7

- name: Validate GitHub Actions workflows
uses: raven-actions/actionlint@3d39aea434753780c3b3d4a1a31c854b4dbf49d7
with:
version: 1.7.12

backend:
name: Backend build and tests
runs-on: ubuntu-latest
timeout-minutes: 12
steps:
- name: Check out repository
uses: actions/checkout@v7
- name: Set up pinned Node.js and npm
uses: ./.github/actions/setup-ross-node

- name: Install backend dependencies
run: npm ci --prefix backend
- name: Run backend tests
run: npm run test:chat --prefix backend && npm run test:legal-sources --prefix backend && npm run test:security --prefix backend
- name: Build backend
run: npm run build:backend

frontend:
name: Frontend build
runs-on: ubuntu-latest
timeout-minutes: 12
steps:
- name: Check out repository
uses: actions/checkout@v7
- name: Set up pinned Node.js and npm
uses: ./.github/actions/setup-ross-node
- name: Install frontend dependencies
run: npm ci --prefix frontend
- name: Build frontend
run: npm run build:frontend

website:
name: Website build, lint, and routes
runs-on: ubuntu-latest
timeout-minutes: 12
steps:
- name: Check out repository
uses: actions/checkout@v7
- name: Set up pinned Node.js and npm
uses: ./.github/actions/setup-ross-node
- name: Install website dependencies
run: npm ci --prefix website
- name: Build website
run: npm run build:website
- name: Lint website
run: npm run lint:website
- name: Test website routes
run: npm run test:website

- name: Regenerate release manifest for verification
run: npm run build:release-manifest
governance:
name: Governance and release checks
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Check out repository
uses: actions/checkout@v7
- name: Set up pinned Node.js and npm
uses: ./.github/actions/setup-ross-node
- name: Install locked dependencies
run: npm run install:all
- name: Regenerate governed artifacts for verification
run: |
npm run build:public-content
npm run build:ross-workflows
npm run build:completion-dossier
npm run build:release-manifest
- name: Verify repository and release controls
run: |
npm run toolchain:check
npm run test:workflow-sources
npm run test:public-content
npm run test:release-manifest
npm run test:operations
npm run test:evaluation
npm run test:final
npm run test:baseline
npm run audit:high
npm run lint:baseline

- name: Verify inherited product baseline
run: npm run check
verify:
name: verify
if: always()
needs: [workflows, backend, frontend, website, governance]
runs-on: ubuntu-latest
steps:
- name: Require every Baseline partition to pass
env:
WORKFLOWS_RESULT: ${{ needs.workflows.result }}
BACKEND_RESULT: ${{ needs.backend.result }}
FRONTEND_RESULT: ${{ needs.frontend.result }}
WEBSITE_RESULT: ${{ needs.website.result }}
GOVERNANCE_RESULT: ${{ needs.governance.result }}
run: |
for result in "$WORKFLOWS_RESULT" "$BACKEND_RESULT" "$FRONTEND_RESULT" "$WEBSITE_RESULT" "$GOVERNANCE_RESULT"; do
test "$result" = success
done
100 changes: 100 additions & 0 deletions .github/workflows/merge-verified-agent-pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
name: Merge verified agent pull requests

on:
workflow_run:
workflows: ["Baseline verification"]
types: [completed]

permissions:
contents: write
pull-requests: write

concurrency:
group: merge-verified-${{ github.event.workflow_run.id }}
cancel-in-progress: false

jobs:
merge:
if: >-
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.pull_requests[0] != null
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Verify exact PR head and merge
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3
with:
script: |
const run = context.payload.workflow_run;
const linked = run.pull_requests?.[0];
if (!linked) {
core.info("No pull request is linked to this Baseline run.");
return;
}

const { owner, repo } = context.repo;
const { data: pr } = await github.rest.pulls.get({
owner,
repo,
pull_number: linked.number,
});

const trustedAssociations = new Set(["OWNER", "MEMBER", "COLLABORATOR"]);
const eligible =
pr.state === "open" &&
!pr.draft &&
pr.head.repo?.full_name === `${owner}/${repo}` &&
pr.head.ref.startsWith("agent/") &&
trustedAssociations.has(pr.author_association) &&
pr.head.sha === linked.head.sha;

if (!eligible) {
core.info("PR is not an eligible trusted same-repository agent PR at the verified head.");
return;
}

const query = `
query($owner: String!, $repo: String!, $number: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $number) {
state
isDraft
headRefOid
reviewDecision
mergeable
reviewThreads(first: 100) {
nodes { isResolved }
}
}
}
}
`;
const result = await github.graphql(query, {
owner,
repo,
number: pr.number,
});
const node = result.repository.pullRequest;
const unresolved = node.reviewThreads.nodes.some((thread) => !thread.isResolved);
const blocked =
node.state !== "OPEN" ||
node.isDraft ||
node.headRefOid !== pr.head.sha ||
node.reviewDecision === "CHANGES_REQUESTED" ||
node.mergeable !== "MERGEABLE" ||
unresolved;

if (blocked) {
core.info("PR has changed or has a review/merge blocker; it will not be merged.");
return;
}

await github.rest.pulls.merge({
owner,
repo,
pull_number: pr.number,
merge_method: "squash",
sha: pr.head.sha,
});
core.notice(`Merged PR #${pr.number} immediately after successful final-head Baseline verification.`);
46 changes: 13 additions & 33 deletions .github/workflows/refresh-release-manifest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,48 +2,35 @@ name: Refresh release manifest

on:
workflow_dispatch:
pull_request_target:
types: [opened, synchronize, reopened, ready_for_review]
push:
branches: [main]
paths-ignore:
- reports/release-manifest-v1.json

permissions:
actions: write
contents: write
pull-requests: read

concurrency:
group: release-manifest-${{ github.event.pull_request.number || github.ref }}
group: release-manifest-${{ github.ref }}
cancel-in-progress: true

jobs:
refresh:
if: >-
(github.event_name == 'workflow_dispatch' && github.ref_name != github.event.repository.default_branch) ||
(github.event_name == 'pull_request_target' &&
github.event.pull_request.head.repo.full_name == github.repository &&
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.pull_request.author_association))
runs-on: ubuntu-latest
timeout-minutes: 25
env:
TARGET_REF: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.ref || github.ref_name }}
timeout-minutes: 10
steps:
- name: Check out the target branch
- name: Check out main
uses: actions/checkout@v7
with:
ref: ${{ env.TARGET_REF }}
ref: main
fetch-depth: 0

- name: Set up pinned Node.js and npm
uses: ./.github/actions/setup-ross-node

- name: Install locked dependencies
run: npm run install:all

- name: Regenerate the exact-byte release manifest
run: npm run build:release-manifest

- name: Run the complete verification gate
run: npm run check

- name: Confirm only the generated report changed
shell: bash
run: |
Expand Down Expand Up @@ -71,23 +58,16 @@ jobs:
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add reports/release-manifest-v1.json
git commit -m "Refresh release manifest from exact branch bytes"
git push origin "HEAD:${TARGET_REF}"
git commit -m "Refresh release manifest after merge"
git push origin HEAD:main
echo "changed=true" >> "$GITHUB_OUTPUT"

- name: Dispatch Baseline Verification on the updated branch
if: steps.commit.outputs.changed != 'true'
env:
GH_TOKEN: ${{ github.token }}
run: gh workflow run baseline.yml --ref "${TARGET_REF}"

- name: Summarize
run: |
{
echo "## Release manifest refreshed"
echo "## Release manifest refresh"
echo
echo "- Branch: ${TARGET_REF}"
echo "- Branch: main"
echo "- Report changed: ${{ steps.commit.outputs.changed }}"
echo "- Full check: passed"
echo "- Baseline Verification: ${{ steps.commit.outputs.changed == 'true' && 'will run from the pushed commit' || 'dispatched' }}"
echo "- PR branches are never mutated by this workflow."
} >> "$GITHUB_STEP_SUMMARY"
16 changes: 14 additions & 2 deletions tests/baseline/ross-ci-toolchain.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,25 @@ test("the release train uses current artifact actions and pins Fly setup", () =>
assert.doesNotMatch(deployment, /setup-flyctl@master/);
});

test("the release train runs the same full gate as baseline CI", () => {
test("baseline CI partitions the complete gate while release deploys run it serially", () => {
const baseline = read(".github/workflows/baseline.yml");
const deployment = read(
".github/workflows/verify-and-deploy-public-beta.yml",
);

assert.match(baseline, /run: npm run check/);
for (const job of ["workflows", "backend", "frontend", "website", "governance"]) {
assert.match(baseline, new RegExp(`^ ${job}:`, "m"));
}
assert.match(baseline, /^ verify:/m);
assert.match(
baseline,
/needs: \[workflows, backend, frontend, website, governance\]/,
);
assert.match(baseline, /npm run test:baseline/);
assert.match(baseline, /npm run audit:high/);
assert.match(baseline, /npm run build:frontend/);
assert.match(baseline, /npm run build:website/);

assert.match(deployment, /name: Run complete engineering gate/);
assert.match(deployment, /run: npm run check/);
assert.doesNotMatch(
Expand Down