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
10 changes: 10 additions & 0 deletions .github/governance/policy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"schema_version": 1,
"mode": "SHADOW",
"reviewers": [],
"owners": [],
"codex_app_ids": [],
"max_evidence_age_hours": 24,
"baseline_exceptions": false,
"merge_authorized": false
}
207 changes: 207 additions & 0 deletions .github/workflows/governance-acceptance.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
name: Governance shadow engine

on:
workflow_call:
Comment thread
Pigbibi marked this conversation as resolved.
Resolved
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 = /<!-- codex-review-state:v1 (\{[^\n]*\}) -->/.exec(candidates[0].body);
if (!match) return 'UNKNOWN';
Comment thread
Pigbibi marked this conversation as resolved.
Resolved
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;
17 changes: 17 additions & 0 deletions .github/workflows/governance-shadow-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
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
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ Organization documentation rules are checked by `python scripts/check_style.py`,

- Do not assume this file applies to other repositories; each repository carries
its own `AGENTS.md` and `.github/engineering.md`.
- Do not add organization-wide automation or branch-protection policy here.
- Organization-wide automation or branch-protection policy requires an explicit
owner request and a reviewed change.
- Do not weaken the style guide's rules on credentials, account identifiers or
private infrastructure without a stated reason.

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,5 @@ Organization-wide files for QuantStrategyLab repositories.
## Contributing

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.
2 changes: 2 additions & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,5 @@ QuantStrategyLab 各仓库共用的组织级文件。
## 参与贡献

提交 pull request,说明要解决的问题、改了什么,以及对现有仓库的影响。AI 助手与维护者遵循 [AGENTS.md](AGENTS.md)。

- [Governance shadow engine](docs/governance-shadow.md) — reusable report-only review observations.
Loading
Loading