@bongos/core 1.21.177 → 1.21.178
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.bongos-core.json +140 -150
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +0 -6
- package/clients/bongos-client/index.cjs +0 -6
- package/clients/bongos-client/index.d.ts +0 -6
- package/clients/bongos-client/index.mjs +0 -6
- package/docs/adr/0042-builder-self-deploy-ci-auto-merge.md +3 -2
- package/docs/api/openapi.json +1 -87
- package/docs/api-reference.md +1 -8
- package/docs/canonical-permissions.md +3 -3
- package/docs/copy-inventory.md +25 -39
- package/docs/copy-registry.json +73 -200
- package/docs/file-map.md +3 -3
- package/docs/module-api-changelog.md +2 -0
- package/docs/modules-contract.md +2 -2
- package/docs/page-inventory.json +4 -7
- package/docs/page-readings.json +1520 -1569
- package/modules/government/catalog.js +10 -7
- package/modules/government/decision-board.js +0 -1
- package/modules/government/decision-rules.js +0 -1
- package/modules/government/docket.js +0 -1
- package/modules/government/protected-surfaces.js +1 -1
- package/modules/government/protected-surfaces.json +2 -3
- package/modules/hall-ui/public/atlas.html +1 -1
- package/modules/hall-ui/public/blockers.html +1 -1
- package/modules/hall-ui/public/board-room.html +1 -1
- package/modules/hall-ui/public/city.html +1 -1
- package/modules/hall-ui/public/collab.html +1 -1
- package/modules/hall-ui/public/commands.html +1 -1
- package/modules/hall-ui/public/copy-desk.html +1 -1
- package/modules/hall-ui/public/deploy.html +1 -1
- package/modules/hall-ui/public/diagrams.html +1 -1
- package/modules/hall-ui/public/drachmae.html +1 -1
- package/modules/hall-ui/public/fleet.html +1 -1
- package/modules/hall-ui/public/gate.css +4 -26
- package/modules/hall-ui/public/gate.html +12 -27
- package/modules/hall-ui/public/gate.js +26 -176
- package/modules/hall-ui/public/gate.states.json +4 -22
- package/modules/hall-ui/public/goal-map.html +1 -1
- package/modules/hall-ui/public/goals.html +1 -1
- package/modules/hall-ui/public/government.html +1 -1
- package/modules/hall-ui/public/idea.html +1 -1
- package/modules/hall-ui/public/ideas.html +1 -1
- package/modules/hall-ui/public/index.html +1 -1
- package/modules/hall-ui/public/modules.html +1 -1
- package/modules/hall-ui/public/my-agents.html +1 -1
- package/modules/hall-ui/public/primer.html +1 -1
- package/modules/hall-ui/public/profile.html +1 -1
- package/modules/hall-ui/public/project-settings.html +1 -1
- package/modules/hall-ui/public/ranks.html +1 -1
- package/modules/hall-ui/public/roadmap.html +1 -1
- package/modules/hall-ui/public/roster.html +1 -1
- package/modules/hall-ui/public/sessions.html +1 -1
- package/modules/hall-ui/public/settings.html +1 -1
- package/modules/hall-ui/public/shell.js +1 -1
- package/modules/hall-ui/public/street-card.js +5 -9
- package/modules/hall-ui/public/street-desk.js +2 -2
- package/modules/hall-ui/public/street-inside.js +1 -1
- package/modules/hall-ui/public/street.html +1 -1
- package/modules/hall-ui/public/studio.html +1 -1
- package/modules/hall-ui/public/task.html +1 -1
- package/modules/hall-ui/public/thinking.html +1 -1
- package/modules/hall-ui/public/tweak-editor.html +1 -1
- package/modules/hall-ui/public/watch.html +1 -1
- package/modules/hall-ui/public/work.html +1 -1
- package/modules/lifecycle/matter-acts.js +0 -6
- package/modules/lifecycle/module.json +0 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +22 -0
- package/scripts/bongos/adopt-preflight.js +3 -3
- package/scripts/bongos/audit-authorship.sh +1 -3
- package/scripts/bongos/bump-version.js +46 -9
- package/scripts/bongos/doc-cli-guard.js +2 -0
- package/scripts/bongos/doc-control-claims.js +2 -2
- package/scripts/bongos/government-walk.js +3 -9
- package/scripts/bongos/hard-floor.js +52 -0
- package/scripts/bongos/publish-manifest.js +3 -3
- package/scripts/bongos/run-unit-tests.js +17 -5
- package/scripts/gds/hard-floor.js +6 -0
- package/src/bongos/module-scope-map.js +2 -2
- package/src/bongos/permission-path-check.js +1 -1
- package/src/bongos/route-rank-check.js +1 -8
- package/src/bongos/routes.js +1 -2
- package/src/module-api.js +1 -1
- package/src/modules.js +1 -1
- package/tests/acclaim_feed_evidence_db.mjs +42 -23
- package/tests/adr_0089_scope.mjs +7 -5
- package/tests/bump_version.mjs +52 -18
- package/tests/copy_desk_flags_db.mjs +22 -14
- package/tests/gating_review_retired.mjs +1 -1
- package/tests/government_act_alone_buildings.mjs +1 -3
- package/tests/government_board_membership_db.mjs +35 -22
- package/tests/government_protected_surfaces.mjs +29 -13
- package/tests/governor_docket.mjs +0 -2
- package/tests/governor_matter_acts.mjs +6 -8
- package/tests/hall_city.mjs +1 -1
- package/tests/hall_matter_card.mjs +2 -2
- package/tests/hall_oversight_world.mjs +16 -12
- package/tests/hall_street_desk.mjs +4 -3
- package/tests/hall_street_inside.mjs +1 -1
- package/tests/helpers.mjs +34 -0
- package/tests/howto_state.mjs +33 -56
- package/tests/howto_state_db.mjs +68 -0
- package/tests/idea_credit_streams_db.mjs +11 -3
- package/tests/integration_parked_ratchet.mjs +7 -1
- package/tests/nav_permission_atoms.mjs +1 -1
- package/tests/page_notes_db.mjs +12 -8
- package/tests/page_tweak_no_rearm.mjs +50 -0
- package/tests/publish_manifest.mjs +64 -0
- package/tests/search_isolation.mjs +5 -103
- package/tests/search_isolation_db.mjs +140 -0
- package/tests/session_earnings_mirror_db.mjs +2 -0
- package/tests/workflow_exec_paths.mjs +9 -11
- package/modules/lifecycle/routes/gate-approvals.js +0 -299
- package/scripts/bongos/ci-grade.js +0 -82
- package/scripts/bongos/gate-review.js +0 -330
- package/scripts/gds/ci-grade.js +0 -7
- package/scripts/gds/gate-review.js +0 -7
- package/tests/gate_approvals.mjs +0 -611
- package/tests/gate_review.mjs +0 -152
|
@@ -1,330 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
// scripts/bongos/gate-review.js — the automatic gate-surface gatekeeper (ADR 0058).
|
|
5
|
-
//
|
|
6
|
-
// WHY. In deploy mode 'ci' (config/deploy.json) PRs auto-merge on green checks and
|
|
7
|
-
// main auto-deploys to prod. CODEOWNERS (owner review on gate surfaces) was the ONLY
|
|
8
|
-
// human in that loop — and the bottleneck. The owner chose a RANK-BASED trust model
|
|
9
|
-
// (2026-06-15): trust senior builders to auto-merge routine sensitive work, but keep a
|
|
10
|
-
// HARD, rank-independent floor for the gate's own machinery + deploy switch.
|
|
11
|
-
//
|
|
12
|
-
// WHAT. This is a PURE, deterministic classifier over the PR diff-as-DATA. It runs in
|
|
13
|
-
// CI (.github/workflows/gate-review.yml) from the TRUSTED BASE copy (the workflow
|
|
14
|
-
// overlays scripts/+src/ from origin/main before running, so a PR cannot edit the
|
|
15
|
-
// analyzer to pass itself). It emits one of three verdicts:
|
|
16
|
-
//
|
|
17
|
-
// PASS — no gate surface touched, OR only AUTO pure-removal surfaces
|
|
18
|
-
// (un-mute a test, narrow the secret-scanner allowlist). Auto-merge.
|
|
19
|
-
// NEEDS_TRUST — a sensitive-but-routine gate surface (migrations, deps, routes,
|
|
20
|
-
// server, ship plumbing, boxes, auth-adjacent, doctrine docs). The
|
|
21
|
-
// workflow auto-merges this IFF the author is Metic+ (rank trust) or
|
|
22
|
-
// the owner approved; otherwise it escalates.
|
|
23
|
-
// ESCALATE — the HARD FLOOR: the gate's own machinery, the deploy switch, branch
|
|
24
|
-
// protection, CI workflows, the rank/permission enforcement, the
|
|
25
|
-
// grader, the deploy-time audit, or the hooks the gate ships through.
|
|
26
|
-
// Rank-trust does NOT apply — this ALWAYS pings the owner. Also any
|
|
27
|
-
// pre-flight rejection (symlink / non-ASCII path / oversize / analyzer
|
|
28
|
-
// error) → ESCALATE, fail-closed.
|
|
29
|
-
//
|
|
30
|
-
// The gate-surface SET is the authoritative src/bongos/permission-path-check.PROTECTED_GLOBS
|
|
31
|
-
// (imported, NEVER hand-retyped — the red-team's #1 finding). The classifier only
|
|
32
|
-
// decides, per touched surface, whether it is HARD-FLOOR, NEEDS_TRUST, or AUTO. Anything
|
|
33
|
-
// matched-protected-but-unclassified defaults to ESCALATE (no allow-by-omission).
|
|
34
|
-
//
|
|
35
|
-
// The pure core (classifyChanges) takes diff data as arguments and is exhaustively
|
|
36
|
-
// unit-tested (tests/gate_review.mjs) with the red-team evasion matrix. The CLI wrapper
|
|
37
|
-
// (main) gathers the real diff via git and applies GATE_REPORT_ONLY / exit codes.
|
|
38
|
-
|
|
39
|
-
const { execFileSync } = require('node:child_process');
|
|
40
|
-
const fs = require('node:fs');
|
|
41
|
-
// Destructured AT the require, not off a held module object: knip's static pass
|
|
42
|
-
// cannot follow `const x = require(...); const { y } = x;` and reports `y` as an
|
|
43
|
-
// unused export, which reds the CI knip step (this file's own CLAUDE.md records
|
|
44
|
-
// the same trap for src/instance-config.js).
|
|
45
|
-
const { PROTECTED_GLOBS, compileGlobMatcher } = require('../../src/bongos/permission-path-check');
|
|
46
|
-
|
|
47
|
-
// --- HARD FLOOR (rank-independent ESCALATE) -------------------------------------------
|
|
48
|
-
// A subset of PROTECTED_GLOBS: the surfaces that, if a compromised/injected senior
|
|
49
|
-
// session auto-merged them, would DISABLE the safety system itself or the deploy gate.
|
|
50
|
-
// Kept as globs so the matcher is identical to permission-path-check (trailing '/' =
|
|
51
|
-
// subtree). The owner can promote a NEEDS_TRUST surface here with a one-line edit (which
|
|
52
|
-
// is itself a hard-floor change to this file → escalates — correct).
|
|
53
|
-
const HARD_FLOOR_GLOBS = [
|
|
54
|
-
'scripts/bongos/gate-review.js', // self
|
|
55
|
-
'src/bongos/permission-path-check.js', // defines the protected set this gate reads
|
|
56
|
-
'src/bongos/route-rank-check.js', // the route-gate auditor
|
|
57
|
-
'src/bongos/auth.js', // requireRank / requireBuilder — the enforcement point
|
|
58
|
-
'modules/grading/grader.js', // BV1.R73: grading carved to modules/grading/
|
|
59
|
-
'modules/grading/grader-workers/',
|
|
60
|
-
'modules/grading/grader-rubric.json',
|
|
61
|
-
'scripts/bongos/ci-grade.js', // CI grade-gate driver (task 1143 — red-team #3)
|
|
62
|
-
'scripts/bongos/grade-smoke.js', // grade-smoke driver
|
|
63
|
-
'scripts/bongos/conductor-dispatch.js', // mid-flight grader dispatch
|
|
64
|
-
'scripts/bongos/main-audit.js', // the authoritative deploy-time audit (the backstop)
|
|
65
|
-
'scripts/bongos/push-main.js', // the sanctioned main-push carrying the override
|
|
66
|
-
'scripts/bongos/run-unit-tests.js', // the test selector (AUTO pure-removal handled first)
|
|
67
|
-
'scripts/bongos/install-git-hooks.js',
|
|
68
|
-
'scripts/bongos/hooks/',
|
|
69
|
-
'scripts/migrate.sh',
|
|
70
|
-
'scripts/deploy/',
|
|
71
|
-
'config/deploy.json', // the laptop/ci/bypass deploy switch
|
|
72
|
-
'infra/', // branch-protection.json + deploy substrate
|
|
73
|
-
'.github/', // CI gates: gate-review.yml, deploy-prod.yml, secret scan, grade gate
|
|
74
|
-
'CODEOWNERS',
|
|
75
|
-
'.husky/', // pre-push/pre-commit gate delivery (secrets-allowlist.txt is AUTO, checked first)
|
|
76
|
-
'.claude/hooks/',
|
|
77
|
-
'.claude/settings.json',
|
|
78
|
-
];
|
|
79
|
-
|
|
80
|
-
// db.js is HARD-FLOOR only for the rank-tier brain (REQUIRES_RANK_TIER); every other
|
|
81
|
-
// db.js change is NEEDS_TRUST (it is a huge, routinely-edited module — file-level
|
|
82
|
-
// hard-flooring it would ping the owner for ordinary query work). This is the one
|
|
83
|
-
// content-level rule; everything else is path-level.
|
|
84
|
-
const DB_JS = 'src/bongos/db.js';
|
|
85
|
-
const RANK_TIER_RE = /REQUIRES_RANK_TIER\s*=\s*\{[\s\S]*?\}/;
|
|
86
|
-
|
|
87
|
-
// AUTO pure-removal surfaces — a NARROWING-only edit can only STRENGTHEN the gate, so
|
|
88
|
-
// it may auto-pass for ANY author. Any non-narrowing edit falls through to its glob
|
|
89
|
-
// classification (run-unit-tests.js → HARD FLOOR; secrets-allowlist.txt → .husky/ HARD FLOOR).
|
|
90
|
-
const AUTO_NARROWING = {
|
|
91
|
-
'scripts/bongos/run-unit-tests.js': certifyRunUnitTestsNarrowing,
|
|
92
|
-
'.husky/secrets-allowlist.txt': certifyAllowlistNarrowing,
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
// task 1002874: compile through the authoritative module's FOLDING matcher, not
|
|
96
|
-
// globToRegExp directly. Both of these used to build their own byte-exact regexes
|
|
97
|
-
// from the raw globs and test raw candidates, so after protected-path matching
|
|
98
|
-
// gained NFC + case-folding, an uppercased spelling of a gate surface still walked
|
|
99
|
-
// past THIS classifier — the CI auto-merge gate — which is the bypass the fix
|
|
100
|
-
// exists to close. isCanonicalPath below rejects non-NFC and non-ASCII, but it
|
|
101
|
-
// does not case-fold, so it never covered this.
|
|
102
|
-
const matchesHardFloor = compileGlobMatcher(HARD_FLOOR_GLOBS);
|
|
103
|
-
|
|
104
|
-
// A path is "protected" per the authoritative module.
|
|
105
|
-
const matchesProtected = compileGlobMatcher(PROTECTED_GLOBS);
|
|
106
|
-
|
|
107
|
-
// Only [\x20-\x7e] path chars, NFC-stable, no leading/trailing whitespace. Defeats the
|
|
108
|
-
// unicode-homoglyph + whitespace-alias evasions (red-team Holes 6/8).
|
|
109
|
-
function isCanonicalPath(p) {
|
|
110
|
-
return typeof p === 'string' && p.length > 0 && /^[\x20-\x7e]+$/.test(p)
|
|
111
|
-
&& p === p.normalize('NFC') && p === p.trim();
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// AUTO certifier: run-unit-tests.js may auto-pass ONLY if its sole semantic change is
|
|
115
|
-
// REMOVING entries from INTEGRATION/KNOWN_FAILING (un-muting tests) — never adding, and
|
|
116
|
-
// the rest of the file byte-identical modulo those two set literals.
|
|
117
|
-
function certifyRunUnitTestsNarrowing(base, head) {
|
|
118
|
-
if (base == null || head == null) return { pass: false, reason: 'added/removed wholesale — not a narrowing edit' };
|
|
119
|
-
const setOf = (t, name) => {
|
|
120
|
-
const m = new RegExp(`const ${name} = new Set\\(\\[([\\s\\S]*?)\\]\\)`).exec(t);
|
|
121
|
-
if (!m) return null;
|
|
122
|
-
return new Set([...m[1].matchAll(/['"]([^'"]+)['"]/g)].map((x) => x[1]));
|
|
123
|
-
};
|
|
124
|
-
const strip = (t) => t.replace(/const (INTEGRATION|KNOWN_FAILING) = new Set\(\[[\s\S]*?\]\)/g, 'SET');
|
|
125
|
-
if (strip(base) !== strip(head)) return { pass: false, reason: 'non-set code changed in run-unit-tests.js' };
|
|
126
|
-
for (const name of ['INTEGRATION', 'KNOWN_FAILING']) {
|
|
127
|
-
const b = setOf(base, name), h = setOf(head, name);
|
|
128
|
-
if (!b || !h) return { pass: false, reason: `cannot parse ${name} set — fail-closed` };
|
|
129
|
-
for (const x of h) if (!b.has(x)) return { pass: false, reason: `${name} ADDED '${x}' (muting a test)` };
|
|
130
|
-
}
|
|
131
|
-
return { pass: true };
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// AUTO certifier: secrets-allowlist.txt may auto-pass ONLY if its head line set is a
|
|
135
|
-
// subset of the base (removing/narrowing allowlist entries) — never adding a new exclude.
|
|
136
|
-
function certifyAllowlistNarrowing(base, head) {
|
|
137
|
-
if (base == null || head == null) return { pass: false, reason: 'added/removed wholesale — not a narrowing edit' };
|
|
138
|
-
const norm = (t) => new Set(t.split('\n').map((l) => l.trim()).filter((l) => l && !l.startsWith('#')));
|
|
139
|
-
const b = norm(base), h = norm(head);
|
|
140
|
-
for (const line of h) if (!b.has(line)) return { pass: false, reason: `allowlist ADDED/BROADENED: ${line}` };
|
|
141
|
-
return { pass: true };
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// ----------------------------------------------------------------------------------------
|
|
145
|
-
// PURE CORE. Inputs are diff DATA (no git, no fs) so the whole policy is unit-tested:
|
|
146
|
-
// files: [{ path, mode, deleted }] — changed paths + git new-mode + delete flag
|
|
147
|
-
// blobAt: (which:'base'|'head', path) => string|null — file content at base/head
|
|
148
|
-
// diffBytes: number — total textual diff size
|
|
149
|
-
// Returns { verdict:'pass'|'needs_trust'|'escalate', escalations:[{path,reason}],
|
|
150
|
-
// needsTrust:[{path,reason}], autoPassed:[path], surfaces:[path] }.
|
|
151
|
-
// ----------------------------------------------------------------------------------------
|
|
152
|
-
const MAX_DIFF_BYTES = Number(process.env.GATE_MAX_DIFF_BYTES || 2 * 1024 * 1024);
|
|
153
|
-
|
|
154
|
-
function classifyChanges({ files, blobAt, diffBytes = 0 } = {}) {
|
|
155
|
-
const escalations = [];
|
|
156
|
-
const needsTrust = [];
|
|
157
|
-
const autoPassed = [];
|
|
158
|
-
const surfaces = [];
|
|
159
|
-
|
|
160
|
-
// --- Pre-flight, fail-closed (whole changed set) ---
|
|
161
|
-
for (const f of files || []) {
|
|
162
|
-
if (f.mode === '120000') escalations.push({ path: f.path, reason: 'symlink in diff — refused (alias evasion)' });
|
|
163
|
-
if (!isCanonicalPath(f.path)) escalations.push({ path: String(f.path), reason: 'non-ASCII/whitespace path — refused' });
|
|
164
|
-
}
|
|
165
|
-
if (diffBytes > MAX_DIFF_BYTES) {
|
|
166
|
-
escalations.push({ path: '<diff>', reason: `diff ${diffBytes}B exceeds ${MAX_DIFF_BYTES}B — too large to certify` });
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
for (const f of files || []) {
|
|
170
|
-
const p = f.path;
|
|
171
|
-
if (!isCanonicalPath(p)) continue; // already escalated above
|
|
172
|
-
// A gate surface = the authoritative PROTECTED_GLOBS, OR a hard-floor glob (some
|
|
173
|
-
// floor paths — gate-review.js itself, config/deploy.json, CODEOWNERS — are also
|
|
174
|
-
// added to PROTECTED_GLOBS, but OR-ing keeps the gatekeeper correct even if a
|
|
175
|
-
// future hard-floor entry isn't yet in the shared list), OR a tests/*.mjs file.
|
|
176
|
-
const isProtected = matchesProtected(p) || matchesHardFloor(p);
|
|
177
|
-
const isTest = /^tests\/.+\.mjs$/.test(p);
|
|
178
|
-
if (!isProtected && !isTest) continue; // not a gate surface
|
|
179
|
-
|
|
180
|
-
surfaces.push(p);
|
|
181
|
-
|
|
182
|
-
// tests/*.mjs: deleting or shrinking a test silently weakens the suite → NEEDS_TRUST
|
|
183
|
-
// (rank-trustable per the owner's model); adding/expanding is fine → PASS.
|
|
184
|
-
if (isTest && !isProtected) {
|
|
185
|
-
if (f.deleted) { needsTrust.push({ path: p, reason: 'test file DELETED — suite shrink' }); continue; }
|
|
186
|
-
const base = blobAt('base', p), head = blobAt('head', p);
|
|
187
|
-
if (base != null && head != null && head.length < base.length) {
|
|
188
|
-
needsTrust.push({ path: p, reason: 'test file SHRANK — possible muting' });
|
|
189
|
-
}
|
|
190
|
-
continue;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// AUTO pure-removal surfaces: certify a narrowing-only edit (any author).
|
|
194
|
-
if (Object.prototype.hasOwnProperty.call(AUTO_NARROWING, p)) {
|
|
195
|
-
const cert = AUTO_NARROWING[p](blobAt('base', p), blobAt('head', p));
|
|
196
|
-
if (cert.pass) { autoPassed.push(p); continue; }
|
|
197
|
-
// not a narrowing edit → fall through to HARD FLOOR (both AUTO files are floor globs)
|
|
198
|
-
escalations.push({ path: p, reason: `AUTO surface failed narrowing cert (${cert.reason}) — root-of-trust` });
|
|
199
|
-
continue;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// db.js: HARD FLOOR only when the rank-tier brain changed; else NEEDS_TRUST.
|
|
203
|
-
if (p === DB_JS) {
|
|
204
|
-
const base = blobAt('base', p), head = blobAt('head', p);
|
|
205
|
-
const region = (t) => { const m = t == null ? null : RANK_TIER_RE.exec(t); return m ? m[0] : (t == null ? '<absent>' : '<no-region>'); };
|
|
206
|
-
if (base == null || head == null) { needsTrust.push({ path: p, reason: 'db.js added/removed' }); continue; }
|
|
207
|
-
if (region(base) !== region(head)) {
|
|
208
|
-
escalations.push({ path: p, reason: 'db.js REQUIRES_RANK_TIER (rank brain) changed — root-of-trust' });
|
|
209
|
-
} else {
|
|
210
|
-
needsTrust.push({ path: p, reason: 'db.js change outside the rank-tier region' });
|
|
211
|
-
}
|
|
212
|
-
continue;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// HARD FLOOR globs → ESCALATE regardless of rank.
|
|
216
|
-
if (matchesHardFloor(p)) { escalations.push({ path: p, reason: 'gate machinery / deploy switch (hard floor) — owner review required' }); continue; }
|
|
217
|
-
|
|
218
|
-
// Any other protected surface → NEEDS_TRUST (rank-trustable).
|
|
219
|
-
needsTrust.push({ path: p, reason: 'sensitive gate surface — auto-merges for Metic+ (rank trust)' });
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
const verdict = escalations.length ? 'escalate' : (needsTrust.length ? 'needs_trust' : 'pass');
|
|
223
|
-
return { verdict, escalations, needsTrust, autoPassed, surfaces };
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Resolve the merge action from the verdict + the two trust signals (both injected by
|
|
227
|
-
// the workflow, never read by the pure classifier). RANK trust (server-attested Metic+)
|
|
228
|
-
// clears NEEDS_TRUST only; the HARD FLOOR (escalate) is cleared ONLY by an explicit
|
|
229
|
-
// owner review — rank never bypasses it. An owner approval clears anything.
|
|
230
|
-
// pass → 'pass'
|
|
231
|
-
// needs_trust → 'pass' iff (rank-trusted OR owner-approved)
|
|
232
|
-
// escalate → 'pass' iff owner-approved
|
|
233
|
-
function resolveAction({ verdict, trusted, ownerApproved }) {
|
|
234
|
-
if (verdict === 'pass') return 'pass';
|
|
235
|
-
if (verdict === 'escalate') return ownerApproved ? 'pass' : 'block';
|
|
236
|
-
return trusted || ownerApproved ? 'pass' : 'block';
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// ----------------------------------------------------------------------------------------
|
|
240
|
-
// CLI wrapper — gather the real diff and apply report-only / exit codes. Never returns 0
|
|
241
|
-
// on an analyzer error (fail-closed). The rank/owner-approval resolution for NEEDS_TRUST
|
|
242
|
-
// is provided by the workflow via GATE_AUTHOR_TRUSTED (set from the server-resolved rank /
|
|
243
|
-
// owner approval); the pure core never reads rank.
|
|
244
|
-
// ----------------------------------------------------------------------------------------
|
|
245
|
-
function git(args) { return execFileSync('git', args, { encoding: 'utf8', maxBuffer: 128 * 1024 * 1024 }); }
|
|
246
|
-
|
|
247
|
-
function gatherDiff(baseRef) {
|
|
248
|
-
const mb = git(['merge-base', baseRef, 'HEAD']).trim();
|
|
249
|
-
const raw = git(['diff', '--raw', `${mb}...HEAD`]).split('\n').filter(Boolean);
|
|
250
|
-
const files = [];
|
|
251
|
-
for (const line of raw) {
|
|
252
|
-
// ":100644 100644 sha sha M\tpath" (status D = delete; mode 120000 = symlink)
|
|
253
|
-
const m = /^:(\d{6}) (\d{6}) \S+ \S+ (\S+)\t(.+)$/.exec(line);
|
|
254
|
-
if (!m) { files.push({ path: '<unparseable>', mode: '000000', deleted: false, parseError: true }); continue; }
|
|
255
|
-
const [, , newMode, status, rawPath] = m;
|
|
256
|
-
files.push({ path: rawPath, mode: newMode, deleted: status.startsWith('D') });
|
|
257
|
-
}
|
|
258
|
-
const diffBytes = Buffer.byteLength(git(['diff', `${mb}...HEAD`]), 'utf8');
|
|
259
|
-
const blobAt = (which, p) => {
|
|
260
|
-
const ref = which === 'base' ? mb : 'HEAD';
|
|
261
|
-
try { return git(['show', `${ref}:${p}`]); } catch { return null; }
|
|
262
|
-
};
|
|
263
|
-
return { files, blobAt, diffBytes };
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
async function main() {
|
|
267
|
-
const baseRef = process.env.GATE_BASE_REF || 'origin/main';
|
|
268
|
-
const reportOnly = process.env.GATE_REPORT_ONLY === '1';
|
|
269
|
-
const trusted = process.env.GATE_AUTHOR_TRUSTED === '1'; // set by the workflow from server-resolved rank / owner approval
|
|
270
|
-
|
|
271
|
-
let result;
|
|
272
|
-
try {
|
|
273
|
-
if ((process.env.GATE_FILES_JSON || '').trim()) {
|
|
274
|
-
// test/dry-run injection: {files, blobs:{ 'base:path':..., 'head:path':... }, diffBytes}
|
|
275
|
-
const inj = JSON.parse(process.env.GATE_FILES_JSON);
|
|
276
|
-
const blobAt = (which, p) => (inj.blobs && Object.prototype.hasOwnProperty.call(inj.blobs, `${which}:${p}`)) ? inj.blobs[`${which}:${p}`] : null;
|
|
277
|
-
result = classifyChanges({ files: inj.files, blobAt, diffBytes: inj.diffBytes || 0 });
|
|
278
|
-
} else {
|
|
279
|
-
const { files, blobAt, diffBytes } = gatherDiff(baseRef);
|
|
280
|
-
if (files.some((f) => f.parseError)) throw new Error('unparseable --raw diff line (fail-closed)');
|
|
281
|
-
result = classifyChanges({ files, blobAt, diffBytes });
|
|
282
|
-
}
|
|
283
|
-
} catch (e) {
|
|
284
|
-
console.error(`gate-review: analyzer error (fail-closed): ${e && e.stack ? e.stack : e}`);
|
|
285
|
-
process.exit(reportOnly ? 0 : 1);
|
|
286
|
-
return;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// Resolve the final action.
|
|
290
|
-
const ownerApproved = process.env.GATE_OWNER_APPROVED === '1';
|
|
291
|
-
const action = resolveAction({ verdict: result.verdict, trusted, ownerApproved });
|
|
292
|
-
|
|
293
|
-
const lines = [`## gate-review: ${result.verdict.toUpperCase()}${reportOnly ? ' (report-only)' : ''} → would ${action === 'pass' ? 'PASS' : 'BLOCK + ping owner'}`, ''];
|
|
294
|
-
if (result.surfaces.length) lines.push(`Gate surfaces touched: ${result.surfaces.length}`);
|
|
295
|
-
for (const e of result.escalations) lines.push(`- ESCALATE \`${e.path}\` — ${e.reason}`);
|
|
296
|
-
for (const n of result.needsTrust) lines.push(`- NEEDS-TRUST \`${n.path}\` — ${n.reason} → ${action === 'pass' ? 'PASS' : 'BLOCK (needs Metic+ attestation or owner approval)'}`);
|
|
297
|
-
for (const a of result.autoPassed) lines.push(`- AUTO-PASS \`${a}\` — pure narrowing (strengthens the gate)`);
|
|
298
|
-
if (!result.surfaces.length) lines.push('No gate surface touched — auto-merge.');
|
|
299
|
-
|
|
300
|
-
const out = lines.join('\n');
|
|
301
|
-
console.log(out);
|
|
302
|
-
if (process.env.GITHUB_STEP_SUMMARY) { try { fs.appendFileSync(process.env.GITHUB_STEP_SUMMARY, out + '\n'); } catch (_) { /* best-effort */ } }
|
|
303
|
-
|
|
304
|
-
if (action === 'block' && !reportOnly) {
|
|
305
|
-
await pingOwner(result).catch(() => {});
|
|
306
|
-
process.exit(1);
|
|
307
|
-
}
|
|
308
|
-
process.exit(0);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
async function pingOwner(result) {
|
|
312
|
-
const url = process.env.DISCORD_GATE_APPROVALS_WEBHOOK;
|
|
313
|
-
const pr = process.env.GATE_PR_NUMBER;
|
|
314
|
-
if (!url || !pr) return;
|
|
315
|
-
const surfaces = [...new Set([...result.escalations, ...result.needsTrust].map((x) => x.path))].slice(0, 10).join(', ');
|
|
316
|
-
const body = { content: `gate-pr #${pr} — gate-surface change needs your review: ${surfaces}\nReview + approve the PR to land it.`, allowed_mentions: { parse: [] } };
|
|
317
|
-
await fetch(url, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) });
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
module.exports = {
|
|
321
|
-
classifyChanges,
|
|
322
|
-
resolveAction,
|
|
323
|
-
certifyRunUnitTestsNarrowing,
|
|
324
|
-
certifyAllowlistNarrowing,
|
|
325
|
-
isCanonicalPath,
|
|
326
|
-
matchesHardFloor,
|
|
327
|
-
HARD_FLOOR_GLOBS,
|
|
328
|
-
};
|
|
329
|
-
|
|
330
|
-
if (require.main === module) main();
|
package/scripts/gds/ci-grade.js
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// scripts/gds/ci-grade.js — @bongos-forwarder (task 1004848). Generated by
|
|
3
|
-
// scripts/bongos/codemod-rename-scripts-gds.js; do not edit.
|
|
4
|
-
// The real script is scripts/bongos/ci-grade.js. This forwarder keeps callers that still name
|
|
5
|
-
// the old path working, and warns once so they get repointed. Task 1003706 removes it.
|
|
6
|
-
'use strict';
|
|
7
|
-
require('../bongos/forwarder.js').forward(module, 'ci-grade.js');
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// scripts/gds/gate-review.js — @bongos-forwarder (task 1004848). Generated by
|
|
3
|
-
// scripts/bongos/codemod-rename-scripts-gds.js; do not edit.
|
|
4
|
-
// The real script is scripts/bongos/gate-review.js. This forwarder keeps callers that still name
|
|
5
|
-
// the old path working, and warns once so they get repointed. Task 1003706 removes it.
|
|
6
|
-
'use strict';
|
|
7
|
-
require('../bongos/forwarder.js').forward(module, 'gate-review.js');
|