openpencil/tools/ci/policy/src/policy.ts
Danila Poyarkov 5b9533a230
ci: build and check packages in parallel and reuse verified trees in the merge queue (#944)
* ci: build and check packages in parallel

The Vue SDK's declarations used the tsc resolver, which took 21 of the 32 seconds a local package build takes; tsdown's default oxc resolver writes byte-identical output in 4 seconds. Packages now build level by level, each level's packages together, with their output printed whole. Package checks run npm and Bun packing side by side and ATTW on every core instead of two.

* ci: skip the merge queue's suites for a tree its PR already passed

The merge queue reran every check even when master had not moved, so the queued commit had exactly the tree the pull request's CI had just passed. A passing PR run now records that tree as a commit status on the PR head, and the queue's classification compares its own tree with it: a match runs only the always-on checks, anything else the full suites. Fork PRs cannot write the status and keep the full run.

* ci: accept a verified tree only from its pull request's passing CI run

Any writer can post a commit status, and another pull request's CI could post one on this head, so a status alone could skip the queue's suites. The record now links the run that wrote it, and the queue accepts it only when GitHub shows Actions created it and the run is this repository's CI workflow on pull_request, passed, and ran on this exact head. Recording no longer fails the gate when the status cannot be written. Parallel packs and builds now all settle before a failure is reported, so none writes into a directory that is being removed or rebuilt.

* refactor(ci): group the verified-tree lookup and recorder in one folder
2026-10-07 11:04:39 +00:00

64 lines
2.7 KiB
TypeScript

const ROOT_DOCS = new Set(['README.md', 'CONTRIBUTING.md', 'AGENTS.md', 'CHANGELOG.md', 'LICENSE'])
const DOC_ASSET = /\.(?:md|png|jpe?g|gif|webp|svg|ico|pdf|woff2?|ttf)$/i
/** `verified`: a merge queue commit whose exact tree already passed its pull request's CI. */
export type ChangeScope = 'docs' | 'code' | 'verified'
/** Root docs, package READMEs, and every AGENTS.md guide are docs-only; unknown paths, executable docs, and runtime prompt Markdown require the code checks. */
export function classifyPaths(paths: readonly string[]): Exclude<ChangeScope, 'verified'> {
if (paths.length === 0) return 'code'
return paths.every((path) => {
if (ROOT_DOCS.has(path) || /^packages\/[^/]+\/README\.md$/.test(path)) return true
if (/(?:^|\/)AGENTS\.md$/.test(path)) return true
if (path.startsWith('packages/docs/')) return DOC_ASSET.test(path)
if (path.startsWith('openspec/')) return path.endsWith('.md')
if (path.startsWith('skills/')) return path.endsWith('.md') || path.endsWith('/LICENSE.txt')
return false
})
? 'docs'
: 'code'
}
export const CODE_JOBS = [
'source-quality',
'package-quality',
'repository-quality',
'storybook',
'native-test-contracts',
'unit-tests'
] as const
export const DOCS_JOB = 'documentation'
export const ALWAYS_JOBS = ['commit-messages'] as const
/** The jobs each scope runs besides `ALWAYS_JOBS`; a verified tree runs none of them. */
const SELECTED_JOBS = new Map<string, readonly string[]>([
['docs', [DOCS_JOB]],
['code', CODE_JOBS],
['verified', []]
] satisfies [ChangeScope, readonly string[]][])
type JobResult = 'success' | 'failure' | 'cancelled' | 'skipped'
export interface JobStatus {
result: JobResult
outputs?: Record<string, string>
}
/** The sole required gate accepts only the successful checks selected by successful detection. */
export function gateErrors(needs: Partial<Record<string, JobStatus>>): string[] {
const detection = needs.changes
if (detection?.result !== 'success') return ['Change detection did not succeed']
const scope = detection.outputs?.scope
const selected = scope ? SELECTED_JOBS.get(scope) : undefined
if (!selected) return ['Invalid or missing change scope']
const required: readonly string[] = [...ALWAYS_JOBS, ...selected]
const excluded = [...CODE_JOBS, DOCS_JOB].filter((job) => !required.includes(job))
const errors = required
.filter((job) => needs[job]?.result !== 'success')
.map((job) => `${job} did not succeed`)
// Unexpected execution is also a policy failure: docs must not run the full suites, and a
// verified tree runs nothing it already passed.
for (const job of excluded) {
if (needs[job]?.result !== 'skipped') errors.push(`${job} was not skipped for ${scope} routing`)
}
return errors
}