diff --git a/.github/governance/policy.json b/.github/governance/policy.json deleted file mode 100644 index d346891..0000000 --- a/.github/governance/policy.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "schema_version": 1, - "mode": "SHADOW", - "reviewers": [], - "owners": [], - "codex_app_ids": [1144995], - "max_evidence_age_hours": 24, - "baseline_exceptions": false, - "merge_authorized": false -} diff --git a/.github/workflows/governance-acceptance.yml b/.github/workflows/governance-acceptance.yml deleted file mode 100644 index 5b84d16..0000000 --- a/.github/workflows/governance-acceptance.yml +++ /dev/null @@ -1,207 +0,0 @@ -name: Governance shadow engine - -on: - workflow_call: - inputs: - pr-number: - required: true - type: number - policy-revision: - description: Same immutable central commit as the reusable workflow reference - required: true - type: string - mode: - default: SHADOW - type: string - -permissions: - contents: read - pull-requests: read - issues: read - -jobs: - observe: - name: Governance shadow observation - runs-on: ubuntu-latest - timeout-minutes: 5 - steps: - - name: Observe native candidate evidence - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - env: - PR_NUMBER: ${{ inputs.pr-number }} - POLICY_REVISION: ${{ inputs.policy-revision }} - MODE: ${{ inputs.mode }} - with: - script: | - // Trusted reusable workflow code only; never load candidate source. - const crypto = require('node:crypto'); - const digest = value => crypto.createHash('sha256').update(JSON.stringify(value)).digest('hex'); - const revision = process.env.POLICY_REVISION; - const number = Number(process.env.PR_NUMBER); - const fail = code => { throw new Error(code); }; - const hash = value => typeof value === 'string' && /^[0-9a-f]{40}$/.test(value); - let report = {schema_version: 1, mode: 'SHADOW', merge_authorized: false, - observation: 'UNKNOWN', reason: 'READ_OR_INTEGRITY_FAILURE'}; - try { - if (process.env.MODE !== 'SHADOW' || !hash(revision) - || !Number.isSafeInteger(number) || number < 1) fail('INVALID_INPUT'); - const {data: content} = await github.rest.repos.getContent({owner: 'QuantStrategyLab', - repo: '.github', path: '.github/governance/policy.json', ref: revision}); - if (content.encoding !== 'base64' || typeof content.content !== 'string') fail('INVALID_POLICY'); - const policy = JSON.parse(Buffer.from(content.content, 'base64').toString('utf8')); - if (policy.schema_version !== 1 || policy.mode !== 'SHADOW' || policy.merge_authorized !== false - || policy.baseline_exceptions !== false || !Array.isArray(policy.reviewers) - || !Array.isArray(policy.owners) || ![...policy.reviewers, ...policy.owners].every(x => typeof x === 'string') - || !Array.isArray(policy.codex_app_ids) - || !policy.codex_app_ids.every(x => Number.isSafeInteger(x) && x > 0) - || !Number.isInteger(policy.max_evidence_age_hours) - || policy.max_evidence_age_hours < 1 || policy.max_evidence_age_hours > 24) fail('INVALID_POLICY'); - const policyDigest = digest(policy); - const args = {...context.repo, pull_number: number}; - const readPR = async () => (await github.rest.pulls.get(args)).data; - const readEvidence = async () => ({ - reviews: await github.paginate(github.rest.pulls.listReviews, {...args, per_page: 100}), - comments: await github.paginate(github.rest.issues.listComments, - {...context.repo, issue_number: number, per_page: 100}) - }); - const binding = pr => [pr.id, pr.state, pr.head?.sha, pr.base?.sha, pr.base?.ref, - pr.base?.repo?.id, pr.head?.repo?.id, pr.user?.login, pr.changed_files, pr.additions, pr.deletions]; - const pr = await readPR(); - if (pr.state !== 'open' || !hash(pr.head?.sha) || !hash(pr.base?.sha) - || !Number.isSafeInteger(pr.changed_files) || pr.changed_files < 1 || pr.changed_files >= 3000 - || !pr.user?.login || pr.base?.repo?.full_name?.toLowerCase() - !== `${context.repo.owner}/${context.repo.repo}`.toLowerCase()) fail('INVALID_CANDIDATE'); - const files = await github.paginate(github.rest.pulls.listFiles, {...args, per_page: 100}); - if (files.length !== pr.changed_files || new Set(files.map(f => f.filename)).size !== files.length) - fail('INCOMPLETE_FILES'); - function complete(file) { - if (!['added', 'modified', 'removed', 'renamed'].includes(file.status) - || typeof file.filename !== 'string' || !file.filename || file.filename.includes('\0') - || (file.status === 'renamed' && !file.previous_filename) - || !Number.isInteger(file.additions) || file.additions < 0 - || !Number.isInteger(file.deletions) || file.deletions < 0 - || typeof file.patch !== 'string' || !file.patch) return false; - let adds = 0, dels = 0, oldLeft = 0, newLeft = 0, hunk = false; - for (const line of file.patch.split('\n')) { - const m = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/.exec(line); - if (m) { - if (oldLeft || newLeft) return false; - oldLeft = m[2] === undefined ? 1 : Number(m[2]); - newLeft = m[4] === undefined ? 1 : Number(m[4]); hunk = true; - } else if (line.startsWith('\\ No newline')) { - if (!hunk) return false; - } else { - if (!hunk) return false; - if (line[0] === '+') { adds++; newLeft--; } - else if (line[0] === '-') { dels++; oldLeft--; } - else if (line[0] === ' ') { oldLeft--; newLeft--; } - else return false; - if (oldLeft < 0 || newLeft < 0) return false; - } - } - return hunk && oldLeft === 0 && newLeft === 0 && adds === file.additions && dels === file.deletions; - } - if (!files.every(complete) - || files.reduce((n,f) => n + f.additions, 0) !== pr.additions - || files.reduce((n,f) => n + f.deletions, 0) !== pr.deletions) fail('INCOMPLETE_PATCH'); - const scope = digest(files.map(f => [f.filename, f.previous_filename || null, f.status, - f.additions, f.deletions, digest(f.patch)]).sort((a,b) => a[0].localeCompare(b[0]))); - const authority = /authoriz|approv|governance|acceptance|permission|secret|credential|token|account|iam|risk|trad(?:e|ing)|kelly|stop.?loss|deploy|publish|baseline|safety|mask|redact|门禁|审批|授权|风控|密钥|止损/i; - const sensitive = /(^|\/)(\.github|tests?|fixtures?|config|deploy|runtime)(\/|$)|(^|\/)(AGENTS\.md|CODEOWNERS|SECURITY\.md)$|lock$|requirements|package\.json|pyproject\.toml/i; - const isR2 = files.some(f => [f.filename, f.previous_filename || ''].some(p => sensitive.test(p) || authority.test(p)) - || authority.test(f.patch) || (f.filename.endsWith('.md') && (f.deletions > 0 || f.status !== 'added'))); - const proseOnly = files.every(f => /^docs\/[a-z0-9/-]+\.md$/.test(f.filename) - && f.status === 'added' && f.deletions === 0 && !/[<`\[\]{}]|https?:|\bmust\b|\bshall\b|\brequir|必须|应当/i.test(f.patch)); - const tier = isR2 ? 'R2' : proseOnly ? 'R0' : 'R1'; - const evidence = await readEvidence(); - function codexObservation(comments) { - if (policy.codex_app_ids.length === 0) return 'NOT_CONFIGURED'; - const candidates = comments.filter(entry => entry.user?.type === 'Bot' - && policy.codex_app_ids.includes(entry.performed_via_github_app?.id) - && typeof entry.body === 'string' && entry.body.includes('codex-review-state:v1')); - if (candidates.some(entry => !Number.isSafeInteger(entry.id) - || !Number.isFinite(Date.parse(entry.updated_at)))) return 'UNKNOWN'; - candidates.sort((a, b) => Date.parse(b.updated_at || '') - Date.parse(a.updated_at || '') || b.id - a.id); - if (!candidates.length) return 'NOT_FOUND'; - if ((candidates[0].body.match(/codex-review-state:v1/g) || []).length !== 1) return 'UNKNOWN'; - const match = //.exec(candidates[0].body); - if (!match) return 'UNKNOWN'; - let review; - try { review = JSON.parse(match[1]).code_review; } catch { return 'UNKNOWN'; } - if (!review || review.commit_sha !== pr.head.sha) return 'STALE_OR_UNKNOWN'; - if (review.status === 'running') return 'IN_PROGRESS'; - if (review.status !== 'completed' || typeof review.has_any_findings !== 'boolean' - || typeof review.has_unresolved_findings !== 'boolean') return 'UNKNOWN'; - return review.has_unresolved_findings ? 'UNRESOLVED_FINDINGS' - : review.has_any_findings ? 'FINDINGS_REPORTED' : 'NO_REPORTED_FINDINGS'; - } - const now = Date.now(); - const findingsClear = findings => Array.isArray(findings) && findings.every(finding => - finding && (finding.kind === 'advisory' || (finding.kind === 'defect' - && ['resolved', 'false_positive', 'obsolete'].includes(finding.disposition)))); - function decision(kind, list, allowed) { - const candidates = []; - for (const entry of list) { - if (entry.user?.type !== 'User' || !allowed.includes(entry.user?.login) - || entry.user.login === pr.user.login) continue; - // No labels, provider completion, executor receipt or shared identity inference. - let record; - try { record = JSON.parse(entry.body); } catch { continue; } - if (record.schema_version !== 1 || record.kind !== kind || record.head !== pr.head.sha - || record.base !== pr.base.sha || record.scope !== scope || record.policy !== policyDigest - || record.repository !== `${context.repo.owner}/${context.repo.repo}` || record.pr !== number) continue; - const created = Date.parse(entry.submitted_at || entry.created_at); - const expires = Date.parse(record.expires_at); - const valid = Number.isFinite(created) && Number.isFinite(expires) && created <= now && expires > now - && expires > created && expires - created <= policy.max_evidence_age_hours * 3600000 - && now - created <= policy.max_evidence_age_hours * 3600000 - && (!entry.updated_at || entry.updated_at === entry.created_at) - && (kind !== 'review' || (entry.state === 'APPROVED' && entry.commit_id === pr.head.sha)) - && findingsClear(record.findings); - // Newer revoked/invalid records must dominate an older approval. - candidates.push({id: entry.id, valid, decision: record.decision}); - } - candidates.sort((a,b) => b.id - a.id); - return candidates[0]?.valid === true && candidates[0]?.decision === 'approve'; - } - const unresolvedByReviewer = new Map(); - for (const review of [...evidence.reviews].sort((a,b) => a.id - b.id)) { - if (review.user?.type !== 'User' || !policy.reviewers.includes(review.user.login) - || review.user.login === pr.user.login || review.commit_id !== pr.head.sha - || review.state === 'DISMISSED') continue; - const reviewer = review.user.login; - if (review.state === 'CHANGES_REQUESTED') { - unresolvedByReviewer.set(reviewer, true); - } else if (review.state === 'APPROVED' && decision('review', [review], policy.reviewers)) { - unresolvedByReviewer.set(reviewer, false); - } else { - try { - const record = JSON.parse(review.body); - if (record.head === pr.head.sha && record.base === pr.base.sha && record.scope === scope - && record.policy === policyDigest && !findingsClear(record.findings)) - unresolvedByReviewer.set(reviewer, true); - } catch { /* A comment without a record cannot clear a change request. */ } - } - } - const unresolved = [...unresolvedByReviewer.values()].some(Boolean); - const reviewed = !unresolved && decision('review', evidence.reviews, policy.reviewers); - const owned = decision('owner', evidence.comments, policy.owners); - const currentEvidence = await readEvidence(); - const current = await readPR(); - if (digest(binding(pr)) !== digest(binding(current)) || digest(evidence) !== digest(currentEvidence)) fail('STALE_SNAPSHOT'); - report = {...report, tier, file_count: files.length, head: pr.head.sha, base: pr.base.sha, - scope, policy: policyDigest, policy_revision: revision, - observation: tier === 'R0' ? 'PROSE_CANDIDATE' : !reviewed ? 'PENDING_REVIEW' - : tier === 'R2' && !owned ? 'PENDING_OWNER' : 'EVIDENCE_PRESENT', - reason: 'SHADOW_ONLY', independent_review: reviewed, owner_approval: owned, - semantic_classification: 'HEURISTIC_NOT_ACCEPTANCE', required_checks: 'NOT_EVALUATED', - event_freshness: 'SNAPSHOT_ONLY', baseline_exception: 'UNSUPPORTED', - codex_observation: codexObservation(evidence.comments)}; - } catch { - // Deliberately omit exception text, paths, patches, user bodies and API response data. - report = {...report, observation: 'UNKNOWN', reason: 'READ_OR_INTEGRITY_FAILURE'}; - core.setFailed('Shadow observation unavailable; existing gates remain authoritative'); - } - await core.summary.addHeading('Governance shadow observation').addCodeBlock(JSON.stringify(report, null, 2), 'json').write(); - core.setOutput('observation', report.observation); - return report; diff --git a/.github/workflows/governance-shadow-tests.yml b/.github/workflows/governance-shadow-tests.yml deleted file mode 100644 index 2057b1a..0000000 --- a/.github/workflows/governance-shadow-tests.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: Governance shadow tests -on: - pull_request: - push: - branches: [main] -permissions: - contents: read -jobs: - test-shadow: - name: Governance shadow tests - runs-on: ubuntu-latest - timeout-minutes: 5 - steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - with: - persist-credentials: false - - run: node --test scripts/test-governance-shadow.cjs diff --git a/PROPOSAL.md b/PROPOSAL.md index ae99280..bfe95e9 100644 --- a/PROPOSAL.md +++ b/PROPOSAL.md @@ -8,7 +8,10 @@ QuantAIService and could not converge. Its own check, status and workflow-run triggers change the check evidence it rereads, so every evaluation ended in `error` and closed both repositories to merges. Both returned to the canonical `.github/workflows/risk-gate.yml` that the other 23 repositories run. The text -below is the record of the proposed design, not the deployed gate. +below is the record of the proposed design, not the deployed gate. The shadow +observer and its tests were removed on 2026-11-06, so the references below to +`scripts/test-governance-shadow.cjs` and `.github/governance/policy.json` are +historical. ## Decision rule diff --git a/README.md b/README.md index d35f368..fcbee84 100644 --- a/README.md +++ b/README.md @@ -23,4 +23,3 @@ Organization-wide files for QuantStrategyLab repositories. Open a pull request. Describe the problem, the change, and the effect on existing repositories. AI assistants and maintainers follow [AGENTS.md](AGENTS.md). -- [Governance shadow engine](docs/governance-shadow.md) — reusable report-only review observations. diff --git a/README.zh-CN.md b/README.zh-CN.md index 805f71f..0c32ae7 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -23,4 +23,3 @@ QuantStrategyLab 各仓库共用的组织级文件。 提交 pull request,说明要解决的问题、改了什么,以及对现有仓库的影响。AI 助手与维护者遵循 [AGENTS.md](AGENTS.md)。 -- [Governance shadow engine](docs/governance-shadow.md) — reusable report-only review observations. diff --git a/docs/governance-shadow.md b/docs/governance-shadow.md deleted file mode 100644 index 30db82e..0000000 --- a/docs/governance-shadow.md +++ /dev/null @@ -1,83 +0,0 @@ -# Governance shadow engine - -The reusable `governance-acceptance.yml` workflow observes pull requests without -changing merge authorization. It is public and can be called from repositories -under any owner. Both the workflow reference and `policy-revision` must use the -same full, reviewed central commit. There are no inherited secrets, candidate -checkouts, comment writes, status writes or merge operations. - -## Caller contract - -Use `pull_request` with opened, synchronize, reopened, edited and ready-for-review -events. Pass the event pull request number, `mode: SHADOW` and the central policy -revision. Use the dedicated `Governance shadow observation` job name; do not add -it to required checks or replace an existing check. Read-only contents, pull -requests and issues permissions suffice. Existing required checks remain separate. -The public workflow requires no organization access-setting change. Hosting -restrictions or an unavailable pin must be reported rather than bypassed. - -## Observations and evidence - -The engine reads all changed files and verifies file counts, aggregate additions -and deletions, and hunk lengths. Missing or binary patches are unknown. It hashes -the complete scope and central policy. Authority-related paths and prose, test -changes and modified Markdown are conservatively R2; ordinary source changes -are R1. Only narrowly constrained added explanatory prose is an R0 candidate. -These are heuristic observations, never semantic review acceptance. - -Native reviews and owner comments may carry a JSON record containing -`schema_version: 1`, `kind: review` or `kind: owner`, `repository`, numeric `pr`, -full `head` and `base`, `scope`, `policy`, `decision: approve` or `decision: revoke`, -`expires_at` and `findings: []`. Findings may also contain optional -`kind: advisory` advice, or `kind: defect` records with a reviewed disposition of -`resolved`, `false_positive` or `obsolete`; unresolved or malformed defects block -review evidence. Digest values come from the candidate summary. -An independent review also needs an APPROVED native review at that head. -Only human identities listed in the pinned policy qualify; the author cannot -approve their own work. Owner comments edited after creation do not qualify. -Newer invalid or revoked bound records dominate older approvals. No label or -provider availability supplies authorization. Default identity lists are empty, -so R1/R2 observations remain pending until a separately reviewed source policy -establishes identities. No identities or settings are provisioned by this pilot. -An unresolved change request survives comment-only reviews and can be cleared -only by a valid later approval from that reviewer or dismissal of the request. - -An optional `codex_app_ids` list enables a separate observation of the native -Codex summary comment. The source must be a bot posted through a configured -GitHub App and its review state must name the current head. The observation -distinguishes running, stale, malformed and unresolved findings. An empty list -leaves this observation unconfigured. This signal does not establish full -coverage or base binding, does not accept a review, and never authorizes merge. -Multiple receipt blocks in one comment are ambiguous and reported as unknown. - -The final read compares the candidate and evidence with the initial reads. -Failures produce UNKNOWN and a failed, non-required observation job. Reports use -fixed fields, candidate bindings and counts; paths, titles, patches, identities, -comment text and raw API errors are never exported. A green workflow means the -observer ran; it never means the pull request is approved. - -## Limits and promotion - -`merge_authorized` is always false. Required-check acceptance, baseline-red -exceptions, durable writer admission, atomic freshness, event completeness, -merge-queue support and identity independence from execution are not established. -Review/comment and base-only changes do not currently schedule a new observation; -a later pull request event refreshes the snapshot. This is suitable only for -report-only comparison, not enforcement. There is no persistent decision cache: -parallel or restarted observations independently re-read native state and cannot -mutate repository state. No deployment, publication or live operation is added. - -Keep the central PR and pilot callers under review. Callers pin the central -candidate commit; merge the reviewed central source before integrating callers. -The PR check run can exercise the pinned public candidate before that merge, -subject to repository Actions policy. Promote only after native runtime evidence -and separately approved identity, required-check and event-freshness changes. -Rollback consists of reverting only the new caller through a normal reviewed PR; -existing gates stay in place. - -## Local verification - -Run `node --test scripts/test-governance-shadow.cjs`, the existing Python checker -and secret-scan tests, and `python scripts/check_style.py --phase 2`. The new -`Governance shadow tests` workflow exercises the exact inline reusable code with -fake API responses. Offline tests do not prove hosted execution or adoption. diff --git a/scripts/test-governance-shadow.cjs b/scripts/test-governance-shadow.cjs deleted file mode 100644 index dbce45d..0000000 --- a/scripts/test-governance-shadow.cjs +++ /dev/null @@ -1,232 +0,0 @@ -const {test} = require('node:test'); -const assert = require('node:assert/strict'); -const fs = require('node:fs'); -const path = require('node:path'); -const workflowPath = process.env.SHADOW_WORKFLOW || path.join(__dirname, '../.github/workflows/governance-acceptance.yml'); -const workflow = fs.readFileSync(workflowPath, 'utf8'); -const script = workflow.split(' script: |\n')[1]?.split('\n').map(x => x.slice(12)).join('\n'); -assert.ok(script, 'Reusable engine must contain executable observer'); -const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor; -const execute = new AsyncFunction('github', 'context', 'core', 'require', 'process', script); -const H = 'a'.repeat(40), B = 'b'.repeat(40), R = 'c'.repeat(40); -const policy = JSON.parse(fs.readFileSync(path.join(__dirname, '../.github/governance/policy.json'))); -function fixture() { - return {pr: {id: 1, state: 'open', head: {sha: H, repo: {id: 2}}, - base: {sha: B, ref: 'main', repo: {id: 2, full_name: '/sample'}}, - user: {login: '-author'}, changed_files: 1, additions: 1, deletions: 0}, - files: [{filename: 'docs/example.md', status: 'added', additions: 1, deletions: 0, patch: '@@ -0,0 +1 @@\n+Plain explanatory prose.'}], - evidence: {reviews: [], comments: []}, policy: structuredClone(policy)}; -} -async function observe(f = fixture()) { - const state = {failed: false, summary: '', calls: 0}; - const clone = x => structuredClone(x); - const github = {rest: {repos: {getContent: async () => { - if (f.apiError) throw new Error('private token '); - return {data: {encoding: 'base64', content: Buffer.from(JSON.stringify(f.policy)).toString('base64')}}; - }}, pulls: {get: async () => ({data: clone(++state.calls > 1 && f.current || f.pr)}), - listFiles: 'files', listReviews: 'reviews'}, issues: {listComments: 'comments'}}, - paginate: async method => clone(method === 'files' ? f.files : f.evidence[method])}; - const core = {setFailed: () => {state.failed = true;}, setOutput: () => {}, summary: { - addHeading() {return this;}, addCodeBlock(s) {state.summary = s; return this;}, async write() {}}}; - const report = await execute(github, {repo: {owner: '', repo: 'sample'}}, core, require, - {env: {MODE: f.mode || 'SHADOW', POLICY_REVISION: R, PR_NUMBER: '1'}}); - assert.equal(report.merge_authorized, false); - assert.ok(!state.summary.includes('private token')); - return {report, state}; -} -test('Narrow added prose produces an observation, never merge authorization', async () => { - const {report} = await observe(); assert.equal(report.tier, 'R0'); assert.equal(report.observation, 'PROSE_CANDIDATE'); -}); -test('Ordinary code needs independent review', async () => { - const f = fixture(); f.files[0].filename = 'src/example.js'; - assert.equal((await observe(f)).report.observation, 'PENDING_REVIEW'); -}); -function codexFixture() { - const f = fixture(); f.files[0].filename = 'src/example.js'; f.policy.codex_app_ids = [12345]; - const record = {code_review: {commit_sha: H, status: 'completed', - has_any_findings: false, has_unresolved_findings: false}}; - const entry = {id: 11, user: {type: 'Bot'}, performed_via_github_app: {id: 12345}, - updated_at: new Date().toISOString(), body: ``}; - f.evidence.comments = [entry]; - return {f, entry, record}; -} -test('Codex current-head receipt is observation only, never approval', async () => { - const {f} = codexFixture(); const {report} = await observe(f); - assert.equal(report.codex_observation, 'NO_REPORTED_FINDINGS'); - assert.equal(report.observation, 'PENDING_REVIEW'); - assert.equal(report.independent_review, false); -}); -for (const [mutation, expected] of [['stale', 'STALE_OR_UNKNOWN'], ['running', 'IN_PROGRESS'], - ['unresolved', 'UNRESOLVED_FINDINGS'], ['resolved', 'FINDINGS_REPORTED'], - ['malformed', 'UNKNOWN'], ['untrusted', 'NOT_FOUND'], ['user', 'NOT_FOUND'], ['timestamp', 'UNKNOWN']]) { - test(`Codex receipt preserves untrusted or incomplete state: ${mutation}`, async () => { - const {f, entry, record} = codexFixture(); - if (mutation === 'stale') record.code_review.commit_sha = 'd'.repeat(40); - if (mutation === 'running') record.code_review.status = 'running'; - if (mutation === 'unresolved') record.code_review.has_unresolved_findings = true; - if (mutation === 'resolved') record.code_review.has_any_findings = true; - if (mutation === 'untrusted') entry.performed_via_github_app.id = 54321; - if (mutation === 'user') entry.user.type = 'User'; - if (mutation === 'timestamp') entry.updated_at = 'invalid'; - entry.body = mutation === 'malformed' ? '' - : ``; - const {report} = await observe(f); - assert.equal(report.codex_observation, expected); - assert.equal(report.observation, 'PENDING_REVIEW'); - }); -} -test('Unconfigured Codex observer never trusts arbitrary receipts', async () => { - const {f} = codexFixture(); f.policy.codex_app_ids = []; - assert.equal((await observe(f)).report.codex_observation, 'NOT_CONFIGURED'); -}); -test('A malformed newer native receipt cannot revive an older clean receipt', async () => { - const {f, entry} = codexFixture(); - f.evidence.comments.push({...entry, id: 12, updated_at: 'invalid'}); - assert.equal((await observe(f)).report.codex_observation, 'UNKNOWN'); -}); -for (const duplicate of ['unresolved', 'malformed']) { - test(`Duplicate Codex receipt blocks an ambiguous observation: ${duplicate}`, async () => { - const {f, entry, record} = codexFixture(); - record.code_review.has_unresolved_findings = true; - entry.body += duplicate === 'unresolved' - ? `\n` - : '\n'; - assert.equal((await observe(f)).report.codex_observation, 'UNKNOWN'); - }); -} -for (const filename of ['.github/workflows/example.yml', 'tests/example.js', 'docs/acceptance-policy.md', 'AGENTS.md']) { - test(`Sensitive scope is R2: ${filename}`, async () => { - const f = fixture(); f.files[0].filename = filename; - assert.equal((await observe(f)).report.tier, 'R2'); - }); -} -test('Authority-changing Markdown is R2', async () => { - const f = fixture(); f.files[0].patch = '@@ -0,0 +1 @@\n+Approval is optional.'; - assert.equal((await observe(f)).report.tier, 'R2'); -}); -for (const mutation of ['missing', 'truncated', 'count', 'binary', 'duplicate', 'rename', 'api', 'mode', 'policy']) { - test(`Unknown integrity never becomes valid: ${mutation}`, async () => { - const f = fixture(); - if (mutation === 'missing') f.files = []; - if (mutation === 'truncated') f.files[0].patch = '@@ -0,0 +1,2 @@\n+Only one line'; - if (mutation === 'count') f.pr.additions = 2; - if (mutation === 'binary') delete f.files[0].patch; - if (mutation === 'duplicate') {f.files.push(f.files[0]); f.pr.changed_files = 2;} - if (mutation === 'rename') f.files[0].status = 'renamed'; - if (mutation === 'api') f.apiError = true; - if (mutation === 'mode') f.mode = 'ENFORCE'; - if (mutation === 'policy') f.policy.merge_authorized = true; - const {report, state} = await observe(f); - assert.equal(report.observation, 'UNKNOWN'); assert.equal(state.failed, true); - }); -} -for (const drift of ['head', 'base', 'ref', 'files', 'author']) { - test(`Re-read rejects snapshot drift: ${drift}`, async () => { - const f = fixture(); f.current = structuredClone(f.pr); - if (drift === 'head') f.current.head.sha = 'd'.repeat(40); - if (drift === 'base') f.current.base.sha = 'e'.repeat(40); - if (drift === 'ref') f.current.base.ref = 'other'; - if (drift === 'files') f.current.changed_files = 2; - if (drift === 'author') f.current.user.login = '-other'; - assert.equal((await observe(f)).report.observation, 'UNKNOWN'); - }); -} -async function recordFixture() { - const f = fixture(); f.files[0].filename = '.github/example.yml'; - f.policy.reviewers = ['-reviewer', '-other']; f.policy.owners = ['-owner']; - const {report} = await observe(f); - const record = {schema_version: 1, kind: 'review', repository: '/sample', pr: 1, - head: H, base: B, scope: report.scope, policy: report.policy, decision: 'approve', findings: [], - expires_at: new Date(Date.now() + 3600000).toISOString()}; - const created_at = new Date(Date.now() - 60000).toISOString(); - f.evidence.reviews = [{id: 1, user: {login: '-reviewer', type: 'User'}, state: 'APPROVED', commit_id: H, - submitted_at: created_at, body: JSON.stringify(record)}]; - return {f, record, created_at}; -} -test('Configured independent review still requires distinct owner evidence for R2', async () => { - const {f} = await recordFixture(); assert.equal((await observe(f)).report.observation, 'PENDING_OWNER'); -}); -for (const invalid of ['expired', 'base', 'scope', 'policy', 'author', 'bot', 'dismissed', 'defect', 'revoked']) { - test(`Reject invalid independent review: ${invalid}`, async () => { - const {f, record} = await recordFixture(); - if (invalid === 'expired') record.expires_at = new Date(Date.now() - 1000).toISOString(); - if (['base', 'scope', 'policy'].includes(invalid)) record[invalid] = 'f'.repeat(40); - if (invalid === 'author') {f.policy.reviewers.push(f.pr.user.login); f.evidence.reviews[0].user.login = f.pr.user.login;} - if (invalid === 'bot') f.evidence.reviews[0].user.type = 'Bot'; - if (invalid === 'dismissed') f.evidence.reviews[0].state = 'DISMISSED'; - if (invalid === 'defect') record.findings = [{severity: 'defect'}]; - if (invalid === 'revoked') record.decision = 'revoke'; - f.evidence.reviews[0].body = JSON.stringify(record); - assert.equal((await observe(f)).report.observation, 'PENDING_REVIEW'); - }); -} -test('Edited owner comment cannot authorize; subsequent revoke dominates approval', async () => { - const {f, record, created_at} = await recordFixture(); record.kind = 'owner'; - f.evidence.comments = [{id: 2, user: {login: '-owner', type: 'User'}, created_at, updated_at: created_at, - body: JSON.stringify(record)}]; - assert.equal((await observe(f)).report.observation, 'EVIDENCE_PRESENT'); - f.evidence.comments[0].updated_at = new Date().toISOString(); - assert.equal((await observe(f)).report.observation, 'PENDING_OWNER'); - f.evidence.comments[0].updated_at = created_at; - f.evidence.comments.push({...f.evidence.comments[0], id: 3, body: JSON.stringify({...record, decision: 'revoke'})}); - assert.equal((await observe(f)).report.observation, 'PENDING_OWNER'); -}); -test('Workflow has no checkout, secret inheritance, write permission or legacy status publisher', () => { - assert.doesNotMatch(workflow, /checkout|secrets:|: write|createCommitStatus|checks\.create|pull_request_target/); - assert.match(workflow, /timeout-minutes: 5/); -}); - -test('An unresolved concrete defect cannot be erased by another reviewer approval', async () => { - const {f, record} = await recordFixture(); - f.evidence.reviews.push({...f.evidence.reviews[0], id: 2, state: 'CHANGES_REQUESTED', - body: JSON.stringify({...record, findings: [{severity: 'defect'}]})}); - f.evidence.reviews.push({...f.evidence.reviews[0], id: 3, user: {login: '-other', type: 'User'}}); - assert.equal((await observe(f)).report.observation, 'PENDING_REVIEW'); -}); -test('A comment-only review does not clear a prior change request', async () => { - const {f, record} = await recordFixture(); - f.evidence.reviews.push({...f.evidence.reviews[0], id: 2, state: 'CHANGES_REQUESTED'}); - f.evidence.reviews.push({...f.evidence.reviews[0], id: 3, state: 'COMMENTED', body: 'Follow-up comment'}); - f.evidence.reviews.push({...f.evidence.reviews[0], id: 4, user: {login: '-other', type: 'User'}, - body: JSON.stringify(record)}); - const {report} = await observe(f); - assert.equal(report.independent_review, false); - assert.equal(report.observation, 'PENDING_REVIEW'); -}); -test('A valid later approval by the requesting reviewer clears the request', async () => { - const {f} = await recordFixture(); - f.evidence.reviews.push({...f.evidence.reviews[0], id: 2, state: 'CHANGES_REQUESTED'}); - f.evidence.reviews.push({...f.evidence.reviews[0], id: 3, state: 'COMMENTED', body: 'Follow-up'}); - f.evidence.reviews.push({...f.evidence.reviews[0], id: 4}); - const {report} = await observe(f); - assert.equal(report.independent_review, true); - assert.equal(report.observation, 'PENDING_OWNER'); -}); -test('A dismissed change request no longer blocks an independent approval', async () => { - const {f} = await recordFixture(); - f.evidence.reviews.push({...f.evidence.reviews[0], id: 2, state: 'DISMISSED'}); - f.evidence.reviews.push({...f.evidence.reviews[0], id: 3, - user: {login: '-other', type: 'User'}}); - assert.equal((await observe(f)).report.observation, 'PENDING_OWNER'); -}); -test('An expired approval cannot clear a prior change request', async () => { - const {f, record} = await recordFixture(); - f.evidence.reviews.push({...f.evidence.reviews[0], id: 2, state: 'CHANGES_REQUESTED'}); - record.expires_at = new Date(Date.now() - 1000).toISOString(); - f.evidence.reviews.push({...f.evidence.reviews[0], id: 3, body: JSON.stringify(record)}); - f.evidence.reviews.push({...f.evidence.reviews[0], id: 4, - user: {login: '-other', type: 'User'}}); - assert.equal((await observe(f)).report.observation, 'PENDING_REVIEW'); -}); -test('Parallel observations do not share mutable decisions', async () => { - const f = fixture(); f.apiError = true; - const [bad, good] = await Promise.all([observe(f), observe()]); - assert.equal(bad.report.observation, 'UNKNOWN'); - assert.equal(good.report.observation, 'PROSE_CANDIDATE'); -}); - -test('Optional style advice does not require repair', async () => { - const {f, record} = await recordFixture(); - record.findings = [{kind: 'advisory'}]; f.evidence.reviews[0].body = JSON.stringify(record); - assert.equal((await observe(f)).report.observation, 'PENDING_OWNER'); -});