@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
package/tests/bump_version.mjs
CHANGED
|
@@ -22,7 +22,7 @@ const require = createRequire(import.meta.url);
|
|
|
22
22
|
const ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
23
23
|
|
|
24
24
|
const bump = require(path.join(ROOT, 'scripts', 'bongos', 'bump-version.js'));
|
|
25
|
-
const { bumpPatch, CONST_RE } = bump;
|
|
25
|
+
const { bumpPatch, nextFreePatch, registryVersions, CONST_RE } = bump;
|
|
26
26
|
// The LIVE test below reads this repo's real release history (task 1003202).
|
|
27
27
|
const { execFileSync } = require('node:child_process');
|
|
28
28
|
|
|
@@ -217,25 +217,59 @@ test('the push to main happens exactly once, and not inside the bump step', () =
|
|
|
217
217
|
assert.ok(bumpStep.includes('git commit -m'), 'the bump step still commits locally — package-core stamps the version found AT the ref');
|
|
218
218
|
});
|
|
219
219
|
|
|
220
|
-
test('an already-published version
|
|
221
|
-
//
|
|
222
|
-
//
|
|
223
|
-
//
|
|
224
|
-
//
|
|
220
|
+
test('an already-published version FAILS the publish and records nothing (task 1004886)', () => {
|
|
221
|
+
// Task 1003914 made a duplicate-version refusal count as success and fell through to
|
|
222
|
+
// the record step. But a version is left unrecorded only when its record push lost a
|
|
223
|
+
// race to a MOVED main, so the registry's copy is never this run's tree: on
|
|
224
|
+
// 2026-10-25 that recorded 1.21.175 on main for a tree npm's 1.21.175 (2026-10-23)
|
|
225
|
+
// never held. The refusal must now fail the step, which skips the record step.
|
|
225
226
|
const pubAt = PUBLISH_YML.indexOf('id: pub');
|
|
226
227
|
assert.ok(pubAt !== -1, 'the publish step must carry id: pub');
|
|
227
|
-
const pubStep = PUBLISH_YML.slice(pubAt, PUBLISH_YML.indexOf('\n #
|
|
228
|
-
assert.match(
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
);
|
|
228
|
+
const pubStep = PUBLISH_YML.slice(pubAt, PUBLISH_YML.indexOf('\n # THE ONLY IRREVERSIBLE STEP'));
|
|
229
|
+
assert.match(pubStep, /cannot publish over\|previously published\|EPUBLISHCONFLICT/,
|
|
230
|
+
'the publish step still recognises npm refusing a duplicate — to say so plainly');
|
|
231
|
+
assert.doesNotMatch(pubStep, /already=true/, 'no already-present success branch');
|
|
232
|
+
// After a non-zero npm publish, the only way out is `exit "$RC"`: the duplicate
|
|
233
|
+
// branch must not be able to reach the "published" line.
|
|
234
|
+
const failAt = pubStep.indexOf('if [ "$RC" -ne 0 ]; then');
|
|
235
|
+
const failBlock = pubStep.slice(failAt, pubStep.indexOf('\n fi\n echo "published', failAt));
|
|
236
|
+
assert.ok(failAt !== -1 && failBlock.trimEnd().endsWith('exit "$RC"'), 'every publish failure, the duplicate included, exits non-zero');
|
|
237
|
+
const recAt = PUBLISH_YML.indexOf('- name: Record the release on main');
|
|
238
|
+
const recIf = PUBLISH_YML.slice(recAt, PUBLISH_YML.indexOf('\n run:', recAt));
|
|
239
|
+
assert.doesNotMatch(recIf, /always\(\)|failure\(\)/, 'the record step must not run after a failed publish');
|
|
240
|
+
const verdict = PUBLISH_YML.slice(PUBLISH_YML.indexOf('# PUBLISH VERDICT'));
|
|
241
|
+
assert.doesNotMatch(verdict.slice(0, 1500), /ALREADY PRESENT/, 'the verdict no longer reports a recorded duplicate');
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test('the registry listing is read and every listed version is skipped by the bump (task 1004886)', () => {
|
|
245
|
+
const verAt = PUBLISH_YML.indexOf('id: ver');
|
|
246
|
+
const verStep = PUBLISH_YML.slice(verAt, PUBLISH_YML.indexOf('\n - name:', verAt));
|
|
247
|
+
assert.match(verStep, /npm view @bongos\/core versions --json > "\$TAKEN_FILE"/, 'the resolve step reads the registry listing');
|
|
248
|
+
assert.match(verStep, /registryVersions\(.*\)\.has\(process\.argv\[2\]\) \? 0 : 1\)" "\$TAKEN_FILE" "\$HEAD_V"; then\s+TAKEN=true/,
|
|
249
|
+
'a HEAD version already on the registry counts as taken, so it is bumped past');
|
|
250
|
+
assert.match(verStep, /echo '\[\]' > "\$TAKEN_FILE"/, 'an unreadable listing degrades to git-only, it does not stop the lane');
|
|
251
|
+
assert.match(verStep, /taken_file=/, 'the listing is handed on');
|
|
252
|
+
const bumpAt = PUBLISH_YML.indexOf('id: bump');
|
|
253
|
+
const bumpStep = PUBLISH_YML.slice(bumpAt, PUBLISH_YML.indexOf('\n - name:', bumpAt));
|
|
254
|
+
assert.match(bumpStep, /bump-version\.js --apply .*--taken-file "\$\{\{ steps\.ver\.outputs\.taken_file \}\}"/, 'the bump skips the listed versions');
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test('bumpPatch skips every taken version (task 1004886)', () => {
|
|
258
|
+
const src = "const CORE_VERSION = '1.21.174'; // x\n";
|
|
259
|
+
assert.equal(bumpPatch(src).to, '1.21.175', 'nothing taken → the next patch, as before');
|
|
260
|
+
assert.equal(bumpPatch(src, { taken: new Set(['1.21.175']) }).to, '1.21.176', 'the 2026-10-25 shape: 1.21.175 is on npm with an older tree');
|
|
261
|
+
assert.equal(bumpPatch(src, { taken: new Set(['1.21.175', '1.21.176', '1.21.178']) }).to, '1.21.177', 'the first free patch, not the highest');
|
|
262
|
+
assert.equal(bumpPatch(src, { taken: new Set(['1.22.0', '1.21.174']) }).to, '1.21.175', 'other minors and the current version do not matter');
|
|
263
|
+
assert.match(bumpPatch(src, { taken: new Set(['1.21.175']) }).text, /^const CORE_VERSION = '1\.21\.176';/m, 'the literal carries the skipped-to version');
|
|
264
|
+
assert.throws(() => nextFreePatch(1, 0, 0, { has: () => true }), /no free patch/, 'a taken-list that covers everything throws, never spins');
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
test('registryVersions parses npm view output and drops anything that is not x.y.z', () => {
|
|
268
|
+
assert.deepEqual([...registryVersions('["1.21.174","1.21.175"]')], ['1.21.174', '1.21.175']);
|
|
269
|
+
assert.deepEqual([...registryVersions('"1.0.0"')], ['1.0.0'], 'npm prints a bare string when one version exists');
|
|
270
|
+
assert.deepEqual([...registryVersions('["1.2.3","1.2.4-rc.1","x; rm -rf",7]')], ['1.2.3']);
|
|
271
|
+
assert.equal(registryVersions('not json').size, 0);
|
|
272
|
+
assert.equal(registryVersions('').size, 0);
|
|
239
273
|
});
|
|
240
274
|
|
|
241
275
|
test('the record step gates on a bump actually having been made', () => {
|
|
@@ -26,15 +26,17 @@
|
|
|
26
26
|
//
|
|
27
27
|
// Run: DATABASE_URL=postgres://... node tests/copy_desk_flags_db.mjs
|
|
28
28
|
//
|
|
29
|
-
// It
|
|
30
|
-
//
|
|
31
|
-
//
|
|
29
|
+
// It needs a MIGRATED database (scripts/migrate.sh), the one the integration lane
|
|
30
|
+
// builds. It once created its own minimal `builders`/`tasks` stand-ins; those drifted
|
|
31
|
+
// from the real schema (builders.github_id became NOT NULL) and hid it, so they are
|
|
32
|
+
// gone (task 1004863). It cleans up every row it writes.
|
|
32
33
|
|
|
33
34
|
import { createRequire } from 'node:module';
|
|
34
35
|
import { strict as assert } from 'node:assert';
|
|
35
36
|
import fs from 'node:fs';
|
|
36
37
|
import path from 'node:path';
|
|
37
38
|
import url from 'node:url';
|
|
39
|
+
import { insertTestBuilders } from './helpers.mjs';
|
|
38
40
|
|
|
39
41
|
const require = createRequire(import.meta.url);
|
|
40
42
|
const { Pool } = require('pg');
|
|
@@ -58,21 +60,19 @@ async function test(name, fn) {
|
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
// --- fixtures -------------------------------------------------------------
|
|
61
|
-
// Minimal stand-ins ONLY if the real tables are absent (throwaway container).
|
|
62
|
-
// Against a fully migrated DB these are no-ops and the real tables are used.
|
|
63
|
-
await pool.query(`
|
|
64
|
-
CREATE TABLE IF NOT EXISTS schema_migrations (version text PRIMARY KEY);
|
|
65
|
-
CREATE TABLE IF NOT EXISTS builders (id bigserial PRIMARY KEY, github_login text, display_name text);
|
|
66
|
-
CREATE TABLE IF NOT EXISTS tasks (id bigserial PRIMARY KEY);
|
|
67
|
-
`);
|
|
68
|
-
|
|
69
63
|
// Two throwaway builders + one task, at ids far above any real row.
|
|
70
64
|
const A = 900000001;
|
|
71
65
|
const B = 900000002;
|
|
72
66
|
const T = 900000003;
|
|
73
|
-
await pool
|
|
74
|
-
|
|
75
|
-
|
|
67
|
+
await insertTestBuilders(pool, [
|
|
68
|
+
{ id: A, login: 'cdtest-a', display_name: 'Copy Desk Test A' },
|
|
69
|
+
{ id: B, login: 'cdtest-b', display_name: 'Copy Desk Test B' },
|
|
70
|
+
]);
|
|
71
|
+
// The task needs a version (tasks.version_id is NOT NULL). Its own row, at status
|
|
72
|
+
// 'building': the seeds own the one 'planning' version the partial index allows.
|
|
73
|
+
const VER = 'ZZZ-CDF';
|
|
74
|
+
await pool.query("INSERT INTO versions (id, name, status) VALUES ($1, 'copy desk fixture', 'building') ON CONFLICT (id) DO NOTHING", [VER]);
|
|
75
|
+
await pool.query("INSERT INTO tasks (id, version_id, title, discipline) VALUES ($1, $2, 'copy desk fixture', 'engineer') ON CONFLICT (id) DO NOTHING", [T, VER]);
|
|
76
76
|
|
|
77
77
|
// Apply the migration verbatim — this also proves it is idempotent, since a
|
|
78
78
|
// migrated DB has already run it.
|
|
@@ -248,6 +248,12 @@ await test('closeFlag returns null on a second call — the route answers 409 fr
|
|
|
248
248
|
// --- page asks (task 1004317 / TW06, copy_desk_002) -----------------------
|
|
249
249
|
// A page ask's surface IS its page id, so these use SURF as the page id and the
|
|
250
250
|
// cleanup above still catches every row.
|
|
251
|
+
//
|
|
252
|
+
// The section starts from no rows (task 1004863): the string-flag cases above leave
|
|
253
|
+
// open flags on SURF (some with a null string_id), and the one-open-per-target index
|
|
254
|
+
// keys on (flagged_by, surface, string_id) — so a page ask by the same builder on the
|
|
255
|
+
// same surface collided with them rather than testing anything about page asks.
|
|
256
|
+
await clean();
|
|
251
257
|
console.log('copy_desk_flags — page asks (copy_desk_002)');
|
|
252
258
|
|
|
253
259
|
const pageIns = (o = {}) => pool.query(
|
|
@@ -299,6 +305,8 @@ await test('pageAskSummary groups open asks by page: distinct askers, the oldest
|
|
|
299
305
|
|
|
300
306
|
// --- cleanup --------------------------------------------------------------
|
|
301
307
|
await clean();
|
|
308
|
+
await pool.query('DELETE FROM tasks WHERE id = $1', [T]).catch(() => {});
|
|
309
|
+
await pool.query('DELETE FROM versions WHERE id = $1', [VER]).catch(() => {});
|
|
302
310
|
await pool.query('DELETE FROM builders WHERE id = ANY($1)', [[A, B]]).catch(() => {});
|
|
303
311
|
|
|
304
312
|
console.log(`\n${passed} passed, ${failed} failed`);
|
|
@@ -43,7 +43,7 @@ test('cmdOn normalizes BOTH restore paths — a pre-retirement snapshot cannot r
|
|
|
43
43
|
|
|
44
44
|
test('the CODEOWNERS file is gone; its GLOB guards remain where they enforce', () => {
|
|
45
45
|
assert.ok(!fs.existsSync(path.join(ROOT, 'CODEOWNERS')), 'CODEOWNERS must not exist (ADR 0162)');
|
|
46
|
-
const gateReview = fs.readFileSync(path.join(ROOT, 'scripts', 'bongos', '
|
|
46
|
+
const gateReview = fs.readFileSync(path.join(ROOT, 'scripts', 'bongos', 'hard-floor.js'), 'utf8');
|
|
47
47
|
assert.match(gateReview, /'CODEOWNERS'/, 'the HARD_FLOOR glob stays — reintroducing the file is itself gate-flagged');
|
|
48
48
|
});
|
|
49
49
|
|
|
@@ -68,7 +68,7 @@ async function withPort(port, fn) {
|
|
|
68
68
|
// The port the routers below are driven through (section 3); restored after
|
|
69
69
|
// each gate test swaps in its own.
|
|
70
70
|
const ATOMS = [
|
|
71
|
-
'autonomy.fence.manage', 'criterion.satisfy', 'criterion.unsatisfy', '
|
|
71
|
+
'autonomy.fence.manage', 'criterion.satisfy', 'criterion.unsatisfy', 'override_request.decide',
|
|
72
72
|
'core.pin.move', 'builder.budget.set', 'goal.create', 'goal.reorder', 'version.focus.set', 'project.settings.manage',
|
|
73
73
|
'security.report.adjudicate', 'access_request.review', 'government.manage',
|
|
74
74
|
];
|
|
@@ -211,7 +211,6 @@ app.use(attachFail);
|
|
|
211
211
|
for (const rel of [
|
|
212
212
|
'modules/autonomy/routes/autonomy.js',
|
|
213
213
|
'modules/lifecycle/routes/done-when.js',
|
|
214
|
-
'modules/lifecycle/routes/gate-approvals.js',
|
|
215
214
|
'modules/lifecycle/routes/tasks.js',
|
|
216
215
|
'modules/lifecycle/routes/goals.js',
|
|
217
216
|
'modules/lifecycle/routes/versions.js',
|
|
@@ -264,7 +263,6 @@ const REQUESTS = {
|
|
|
264
263
|
'POST /autonomy/builders/:builderId/pause': ['POST', `/autonomy/builders/${BAD}/pause`, { paused: false }],
|
|
265
264
|
'POST /done-when/:criterionId/satisfy': ['POST', `/done-when/${BAD}/satisfy`, {}],
|
|
266
265
|
'POST /done-when/:criterionId/unsatisfy': ['POST', `/done-when/${BAD}/unsatisfy`, {}],
|
|
267
|
-
'POST /gate-approvals/:pr/approve': ['POST', `/gate-approvals/${BAD}/approve`, {}],
|
|
268
266
|
'POST /override-requests/:id/decide': ['POST', `/override-requests/${BAD}/decide`, { decision: 'approve' }],
|
|
269
267
|
'POST /provisioning/instances/:id/core-upgrade': ['POST', `/provisioning/instances/${BAD}/core-upgrade`, { to: '1.2.3' }],
|
|
270
268
|
'POST /provisioning/instances/:id/update-channel': ['POST', `/provisioning/instances/${BAD}/update-channel`, { channel: 'nightly' }],
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
// ids far above any real row, and deletes them at the end.
|
|
27
27
|
|
|
28
28
|
import pg from 'pg';
|
|
29
|
+
import { insertTestBuilders } from './helpers.mjs';
|
|
29
30
|
import { createRequire } from 'node:module';
|
|
30
31
|
const require = createRequire(import.meta.url);
|
|
31
32
|
|
|
@@ -45,38 +46,48 @@ async function t(name, fn) {
|
|
|
45
46
|
}
|
|
46
47
|
const assert = (await import('node:assert')).strict;
|
|
47
48
|
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
49
|
+
// Needs a MIGRATED database (the integration lane's). The stand-in tables this once
|
|
50
|
+
// created when the real ones were absent drifted from the schema (builders.github_id
|
|
51
|
+
// became NOT NULL) and are gone (task 1004863); the builder rows go through the shared
|
|
52
|
+
// helper so the next required column is added in one place.
|
|
53
|
+
|
|
54
|
+
// ONE TRANSACTION, ALWAYS ROLLED BACK (task 1004863). The machine principal below needs a
|
|
55
|
+
// real system_role, and system_role is UNIQUE with only two legal values — both held by
|
|
56
|
+
// the seeded system builders on any migrated database. So the fixture releases the seeded
|
|
57
|
+
// 'bfg' role inside this transaction, gives it to its own throwaway machine, and the
|
|
58
|
+
// rollback restores everything. The library under test is pointed at the same client, so
|
|
59
|
+
// it reads exactly the rows the fixture wrote.
|
|
60
|
+
const db = await pool.connect();
|
|
61
|
+
await db.query('BEGIN');
|
|
62
|
+
await db.query("UPDATE builders SET system_role = NULL WHERE system_role = 'bfg'");
|
|
57
63
|
|
|
58
64
|
// Ids far above any real row, and every login prefixed, so cleanup is exact and
|
|
59
65
|
// this can never touch a real builder or a real rank assignment.
|
|
60
66
|
const B = 990000000;
|
|
61
67
|
const ID = { xenos: B + 1, thetes: B + 2, metic: B + 3, archon: B + 4, gone: B + 5, bot: B + 6, ops: B + 7 };
|
|
62
68
|
const cleanup = async () => {
|
|
63
|
-
await
|
|
64
|
-
await
|
|
69
|
+
await db.query('DELETE FROM government_builder_ranks WHERE builder_id >= $1', [B]);
|
|
70
|
+
await db.query('DELETE FROM builders WHERE id >= $1', [B]);
|
|
65
71
|
};
|
|
66
72
|
await cleanup();
|
|
67
|
-
await
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
await insertTestBuilders(db, [
|
|
74
|
+
{ id: ID.xenos, login: 'gbmdb-x', display_name: 'Xenos', status: 'active', system_role: null },
|
|
75
|
+
{ id: ID.thetes, login: 'gbmdb-t', display_name: 'Thetes', status: 'active', system_role: null },
|
|
76
|
+
{ id: ID.metic, login: 'gbmdb-m', display_name: 'Metic', status: 'active', system_role: null },
|
|
77
|
+
{ id: ID.archon, login: 'gbmdb-a', display_name: 'Archon', status: 'active', system_role: null },
|
|
78
|
+
{ id: ID.gone, login: 'gbmdb-gone', display_name: 'Deactivated Archon', status: 'inactive', system_role: null },
|
|
79
|
+
{ id: ID.bot, login: 'gbmdb-bot', display_name: 'A Machine', status: 'active', system_role: 'bfg' },
|
|
80
|
+
{ id: ID.ops, login: 'gbmdb-ops', display_name: 'Custom Ops', status: 'active', system_role: null },
|
|
81
|
+
], { onConflict: '' });
|
|
82
|
+
// The custom rank is a real government_ranks row: government_builder_ranks.rank_key is a
|
|
83
|
+
// foreign key into it on a migrated database.
|
|
84
|
+
await db.query("INSERT INTO government_ranks (rank_key, label) VALUES ('gbmdb-ops-rank', 'Custom Ops (test)')");
|
|
85
|
+
await db.query(`INSERT INTO government_builder_ranks (builder_id, rank_key) VALUES
|
|
75
86
|
($1,'xenos'),($2,'thetes'),($3,'metic'),($4,'archon'),($5,'archon'),($6,'archon'),($7,'gbmdb-ops-rank')`,
|
|
76
87
|
[ID.xenos, ID.thetes, ID.metic, ID.archon, ID.gone, ID.bot, ID.ops]);
|
|
77
88
|
|
|
78
89
|
const api = require(process.cwd() + '/src/module-api.js');
|
|
79
|
-
api.pool.query = (...a) =>
|
|
90
|
+
api.pool.query = (...a) => db.query(...a); // the library reads through the same transaction
|
|
80
91
|
const { resolveBoardMembers, isBoardMember } = require(process.cwd() + '/modules/government/board-membership.js');
|
|
81
92
|
|
|
82
93
|
const board = (membership) => ({ membership, pass_rule: 'consent', window_minutes: null, close_early_on_full_turnout: true });
|
|
@@ -120,13 +131,13 @@ await t('the deactivated and the machine are STILL excluded under the widened fo
|
|
|
120
131
|
});
|
|
121
132
|
|
|
122
133
|
await t('no duplicate rows when a builder matches through two keys', async () => {
|
|
123
|
-
await
|
|
134
|
+
await db.query('INSERT INTO government_builder_ranks (builder_id, rank_key) VALUES ($1, $2)', [ID.archon, 'gbmdb-ops-rank']);
|
|
124
135
|
try {
|
|
125
136
|
const l = await logins('rank:archon,gbmdb-ops-rank');
|
|
126
137
|
assert.deepEqual(l.filter((x) => x === 'a').length, 1,
|
|
127
138
|
'DISTINCT holds — a double-counted member silently raises the consent bar');
|
|
128
139
|
} finally {
|
|
129
|
-
await
|
|
140
|
+
await db.query("DELETE FROM government_builder_ranks WHERE builder_id=$1 AND rank_key='gbmdb-ops-rank'", [ID.archon]);
|
|
130
141
|
}
|
|
131
142
|
});
|
|
132
143
|
|
|
@@ -145,6 +156,8 @@ await t('an unparseable predicate still seats nobody against a real db', async (
|
|
|
145
156
|
});
|
|
146
157
|
|
|
147
158
|
await cleanup();
|
|
159
|
+
await db.query('ROLLBACK');
|
|
160
|
+
db.release();
|
|
148
161
|
console.log(`\n${passed} passed, ${failed} failed`);
|
|
149
162
|
await pool.end();
|
|
150
163
|
process.exit(failed ? 1 : 0);
|
|
@@ -126,6 +126,16 @@ function carve(parent, names, why) {
|
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
const ADDED_SINCE_R101 = [
|
|
129
|
+
// task 1004859: the hard-floor list moved out of gate-review.js (retired below) into
|
|
130
|
+
// its own file, which keeps the self-protection gate-review.js had. Anchored on the
|
|
131
|
+
// retired entry so it lands where the list used to live.
|
|
132
|
+
{
|
|
133
|
+
after: 'scripts/bongos/gate-review.js',
|
|
134
|
+
glob: 'scripts/bongos/hard-floor.js',
|
|
135
|
+
why: 'the trust-boundary hard-floor list, moved out of the removed gate-review.js; '
|
|
136
|
+
+ 'publish-manifest.js and adopt-preflight.js read it, so an edit here changes what '
|
|
137
|
+
+ 'the public mirror withholds and what an adopted repo is warned about.',
|
|
138
|
+
},
|
|
129
139
|
// task 1003366, audit ref MIT1 (2026-08-29 security audit, goal 1000084). The EIGHT
|
|
130
140
|
// execution roots the merge resolver runs out of a cloned branch. The audit named four
|
|
131
141
|
// of them ("the three gen-* scripts and .gitattributes"); modules/lifecycle/executed-
|
|
@@ -330,6 +340,12 @@ const RENAMED_SINCE_R101 = [
|
|
|
330
340
|
// protection from code that still runs. Applied LAST, after renames and additions,
|
|
331
341
|
// so an addition anchored on a retired entry still resolves.
|
|
332
342
|
const RETIRED_SINCE_R101 = [
|
|
343
|
+
...['gate-review.js', 'ci-grade.js'].map((f) => ({
|
|
344
|
+
glob: `scripts/bongos/${f}`,
|
|
345
|
+
why: 'task 1004859 deleted the two drivers of the planned gate-review and grade-gate '
|
|
346
|
+
+ 'CI checks (ADR 0058). No workflow ever ran them and branch protection requires '
|
|
347
|
+
+ 'only the unit check; the hard-floor list gate-review.js held moved to hard-floor.js.',
|
|
348
|
+
})),
|
|
333
349
|
...['boxes.js', 'box-access.js', 'box-credential.js'].map((f) => ({
|
|
334
350
|
glob: `modules/dev-box/${f}`,
|
|
335
351
|
why: 'task 1003892 (goal 1000120) deleted the dev-box module outright — the rank floor '
|
|
@@ -446,7 +462,7 @@ test('every glob added since R101 carries a documented reason', () => {
|
|
|
446
462
|
// floor-gating ordinary source at Metic — src/bongos/logger.js, src/branding.js and
|
|
447
463
|
// src/build-info.js are all in the closure — and locking newcomers out of routine work.
|
|
448
464
|
// The entrypoints are different in kind: they exist ONLY to be executed by the pipeline,
|
|
449
|
-
// which is exactly what group 'pipeline' already means for ship.js and
|
|
465
|
+
// which is exactly what group 'pipeline' already means for ship.js and hard-floor.js.
|
|
450
466
|
test('every execution root in the closure is a protected surface (MIT1 drift tripwire)', () => {
|
|
451
467
|
const ec = require(path.join(ROOT, 'modules', 'lifecycle', 'executed-closure.js'));
|
|
452
468
|
// ENTRYPOINTS lists both script-tree spellings across the C-series rename window; only
|
|
@@ -657,10 +673,10 @@ test('every credential reader in the tree is a protected surface (1002891 tripwi
|
|
|
657
673
|
}
|
|
658
674
|
});
|
|
659
675
|
|
|
660
|
-
// ---------- the hard-floor subset:
|
|
676
|
+
// ---------- the hard-floor subset: the second list may narrow, never diverge ----------
|
|
661
677
|
//
|
|
662
678
|
// task 1002875, from idea 1000620 (raised as a major by the BV1.R101 ship grader).
|
|
663
|
-
// scripts/bongos/gate-review.js
|
|
679
|
+
// scripts/bongos/hard-floor.js (gate-review.js until task 1004859) keeps HARD_FLOOR_GLOBS — the rank-INDEPENDENT
|
|
664
680
|
// escalate-to-owner set — and the comment above it declares the array to be "A subset of
|
|
665
681
|
// PROTECTED_GLOBS". That was a checkable invariant stated in prose and enforced by nothing.
|
|
666
682
|
//
|
|
@@ -673,13 +689,13 @@ test('every credential reader in the tree is a protected surface (1002891 tripwi
|
|
|
673
689
|
//
|
|
674
690
|
// Checked at BOTH altitudes on purpose. The source literal is what a reviewer reads and
|
|
675
691
|
// what the decision asked be parsed; the exported array is what compileGlobMatcher actually
|
|
676
|
-
// compiles
|
|
692
|
+
// compiles in hard-floor.js. Asserting they agree means a future refactor that
|
|
677
693
|
// computes or filters the list cannot leave the visible source saying one thing while the
|
|
678
694
|
// gate enforces another.
|
|
679
695
|
function hardFloorGlobsFromSource() {
|
|
680
|
-
const src = fs.readFileSync(path.join(ROOT, 'scripts', 'bongos', '
|
|
696
|
+
const src = fs.readFileSync(path.join(ROOT, 'scripts', 'bongos', 'hard-floor.js'), 'utf8');
|
|
681
697
|
const block = src.match(/const HARD_FLOOR_GLOBS = \[([\s\S]*?)\n\];/);
|
|
682
|
-
assert.ok(block, 'could not find the HARD_FLOOR_GLOBS array literal in
|
|
698
|
+
assert.ok(block, 'could not find the HARD_FLOOR_GLOBS array literal in hard-floor.js — '
|
|
683
699
|
+ 'if it was renamed or restructured, this test must be taught the new shape rather '
|
|
684
700
|
+ 'than deleted: it is the only thing holding the declared subset honest.');
|
|
685
701
|
return [...block[1].matchAll(/'([^']+)'/g)].map((m) => m[1]);
|
|
@@ -688,21 +704,21 @@ function hardFloorGlobsFromSource() {
|
|
|
688
704
|
test('every hard-floor glob is a protected surface (1002875 subset tripwire)', () => {
|
|
689
705
|
const fromSource = hardFloorGlobsFromSource();
|
|
690
706
|
assert.ok(fromSource.length >= 20,
|
|
691
|
-
`fixture sanity: parsed ${fromSource.length} hard-floor globs out of
|
|
707
|
+
`fixture sanity: parsed ${fromSource.length} hard-floor globs out of hard-floor.js, `
|
|
692
708
|
+ 'and the array has held about two dozen since it was written. A parse that returns '
|
|
693
709
|
+ 'few or none passes the subset check vacuously.');
|
|
694
710
|
|
|
695
|
-
const gr = require(path.join(ROOT, 'scripts', 'bongos', '
|
|
711
|
+
const gr = require(path.join(ROOT, 'scripts', 'bongos', 'hard-floor.js'));
|
|
696
712
|
assert.deepEqual(fromSource, gr.HARD_FLOOR_GLOBS,
|
|
697
|
-
'the HARD_FLOOR_GLOBS literal in
|
|
713
|
+
'the HARD_FLOOR_GLOBS literal in hard-floor.js no longer matches the array the module '
|
|
698
714
|
+ 'exports and compiles into its matcher. The source and the enforced list must be the '
|
|
699
715
|
+ 'same thing, or a reviewer reading the file is reading a list the gate does not use.');
|
|
700
716
|
|
|
701
717
|
const registry = new Set(ppc.PROTECTED_GLOBS);
|
|
702
718
|
for (const glob of gr.HARD_FLOOR_GLOBS) {
|
|
703
719
|
assert.ok(registry.has(glob),
|
|
704
|
-
`'${glob}' is a HARD FLOOR surface in scripts/bongos/
|
|
705
|
-
+ '
|
|
720
|
+
`'${glob}' is a HARD FLOOR surface in scripts/bongos/hard-floor.js — a trust-boundary path `
|
|
721
|
+
+ 'whatever the author\'s rank — but it is not in '
|
|
706
722
|
+ 'modules/government/protected-surfaces.json, so no rank floor covers it at all. '
|
|
707
723
|
+ 'The hard floor is declared to be a SUBSET of the protected set: it may be '
|
|
708
724
|
+ 'narrower (most protected paths are ordinary Metic work and should not ping the '
|
|
@@ -1290,8 +1306,8 @@ test('compileGlobMatcher folds BOTH sides', () => {
|
|
|
1290
1306
|
assert.ok(!m(null), 'nor is a missing path');
|
|
1291
1307
|
});
|
|
1292
1308
|
|
|
1293
|
-
test('
|
|
1294
|
-
const gr = require(path.join(ROOT, 'scripts', 'bongos', '
|
|
1309
|
+
test('the hard-floor matcher folds a CASE-VARIANT gate surface', () => {
|
|
1310
|
+
const gr = require(path.join(ROOT, 'scripts', 'bongos', 'hard-floor.js'));
|
|
1295
1311
|
const literal = gr.HARD_FLOOR_GLOBS.find((g) => !g.includes('*') && !g.endsWith('/'));
|
|
1296
1312
|
assert.ok(literal, 'no literal hard-floor glob — this test is reading the wrong shape');
|
|
1297
1313
|
assert.ok(gr.matchesHardFloor(literal), 'the exact spelling must escalate (sanity)');
|
|
@@ -510,7 +510,6 @@ test('each contributed source gates on EXACTLY the atom its own list route requi
|
|
|
510
510
|
const factories = [
|
|
511
511
|
'modules/ideas/routes/blockers.js',
|
|
512
512
|
'modules/lifecycle/routes/lifecycle.js',
|
|
513
|
-
'modules/lifecycle/routes/gate-approvals.js',
|
|
514
513
|
'modules/onboarding/routes/access-requests.js',
|
|
515
514
|
'modules/security/routes/reports.js',
|
|
516
515
|
'modules/autonomy/routes/autonomy.js',
|
|
@@ -528,7 +527,6 @@ test('each contributed source gates on EXACTLY the atom its own list route requi
|
|
|
528
527
|
override_requests: ['modules/lifecycle/routes/tasks.js', 'GET /override-requests'],
|
|
529
528
|
removal_proposals: ['modules/lifecycle/routes/removal-proposals.js', 'GET /removal-proposals'],
|
|
530
529
|
criteria_uat: ['modules/lifecycle/routes/done-when.js', 'GET /done-when/pending-review'],
|
|
531
|
-
gate_approvals: ['modules/lifecycle/routes/gate-approvals.js', 'GET /gate-approvals'],
|
|
532
530
|
access_requests: ['modules/onboarding/routes/access-requests.js', 'GET /access-requests'],
|
|
533
531
|
redteam_reports: ['modules/security/routes/reports.js', 'GET /security/reports'],
|
|
534
532
|
core_update: ['src/bongos/routes/core-update.js', 'GET /core-update'],
|
|
@@ -24,7 +24,6 @@ const lifeActs = require(path.join(ROOT, 'modules', 'lifecycle', 'matter-acts.js
|
|
|
24
24
|
const lifeDb = require(path.join(ROOT, 'modules', 'lifecycle', 'db.js'));
|
|
25
25
|
const doneWhen = require(path.join(ROOT, 'modules', 'lifecycle', 'done-when.js'));
|
|
26
26
|
const doneWhenRoutes = require(path.join(ROOT, 'modules', 'lifecycle', 'routes', 'done-when.js'));
|
|
27
|
-
const gateRoutes = require(path.join(ROOT, 'modules', 'lifecycle', 'routes', 'gate-approvals.js'));
|
|
28
27
|
const lifeDocket = require(path.join(ROOT, 'modules', 'lifecycle', 'governor-docket.js'));
|
|
29
28
|
const buildersRoute = require(path.join(ROOT, 'src', 'bongos', 'routes', 'builders.js'));
|
|
30
29
|
const rrc = require(path.join(ROOT, 'src', 'bongos', 'route-rank-check.js'));
|
|
@@ -47,7 +46,6 @@ test('each building\'s act is checked strictly when the matter is opened: shape,
|
|
|
47
46
|
const v = decisionBoard.validateMatterAction;
|
|
48
47
|
assert.deepEqual(v('automation', { type: 'switch_on' }), { ok: true, action: { type: 'switch_on' } });
|
|
49
48
|
assert.deepEqual(v('automation', { type: 'allow_goal', goal_id: '12' }).action, { type: 'allow_goal', goal_id: 12 });
|
|
50
|
-
assert.deepEqual(v('release', { type: 'approve_gate', pr: 1500 }).action, { type: 'approve_gate', pr: 1500 });
|
|
51
49
|
assert.deepEqual(v('release', { type: 'approve_override', request_id: 4 }).action, { type: 'approve_override', request_id: 4 });
|
|
52
50
|
assert.deepEqual(v('review', { type: 'satisfy_criterion', criterion_id: 9 }).action, { type: 'satisfy_criterion', criterion_id: 9 });
|
|
53
51
|
assert.deepEqual(v('treasury', { type: 'set_budget', builder_id: 7, monthly_usd: 50 }).action, { type: 'set_budget', builder_id: 7, monthly_usd: 50 });
|
|
@@ -58,6 +56,8 @@ test('each building\'s act is checked strictly when the matter is opened: shape,
|
|
|
58
56
|
assert.equal(v('automation', { type: 'satisfy_criterion', criterion_id: 9 }).reason, 'bad_action', 'an act from another building');
|
|
59
57
|
assert.equal(v('automation', { type: 'switch_on', goal_id: 3 }).reason, 'unknown_field');
|
|
60
58
|
assert.equal(v('automation', { type: 'allow_goal' }).field, 'action.goal_id');
|
|
59
|
+
// task 1004859 removed the gate-approval route, so Release no longer carries an act for it
|
|
60
|
+
assert.equal(v('release', { type: 'approve_gate', pr: 1500 }).reason, 'bad_action', 'the retired gate act is refused');
|
|
61
61
|
assert.equal(v('automation', { type: 'allow_goal', goal_id: 0 }).reason, 'bad_action');
|
|
62
62
|
assert.equal(v('automation', { type: 'allow_goal', goal_id: true }).reason, 'bad_action');
|
|
63
63
|
assert.equal(v('treasury', { type: 'set_budget', builder_id: 7, monthly_usd: -1 }).reason, 'bad_action');
|
|
@@ -161,7 +161,7 @@ test('a passed Review matter signs the criterion off by override, once', async (
|
|
|
161
161
|
assert.match(signed[0].reason, /passed Review matter/);
|
|
162
162
|
});
|
|
163
163
|
|
|
164
|
-
test('a passed Release matter approves an override
|
|
164
|
+
test('a passed Release matter approves an override; a request already decided is said, not thrown', async () => {
|
|
165
165
|
const calls = [];
|
|
166
166
|
await stub(lifeDb, {
|
|
167
167
|
decideOverrideRequest: async (a) => {
|
|
@@ -169,14 +169,12 @@ test('a passed Release matter approves an override or clears a gate; a request a
|
|
|
169
169
|
if (a.requestId === 5) throw Object.assign(new Error('closed'), { code: 'REQUEST_NOT_OPEN' });
|
|
170
170
|
return {};
|
|
171
171
|
},
|
|
172
|
-
},
|
|
172
|
+
}, async () => {
|
|
173
173
|
assert.equal((await closeMatter({ area: 'release', action: { type: 'approve_override', request_id: 4 } })).out.acted.done, true);
|
|
174
174
|
assert.equal((await closeMatter({ area: 'release', action: { type: 'approve_override', request_id: 5 } })).out.acted.reason, 'request_not_open');
|
|
175
|
-
|
|
176
|
-
assert.equal((await closeMatter({ area: 'release', action: { type: 'approve_gate', pr: 2 } })).out.acted.reason, 'pr_not_open');
|
|
177
|
-
}));
|
|
175
|
+
});
|
|
178
176
|
assert.deepEqual(calls[0], ['override', { requestId: 4, archonId: '42', decision: 'approve', note: 'A passed Release matter (matter 8) decided it.' }]);
|
|
179
|
-
assert.equal(calls
|
|
177
|
+
assert.equal(calls.length, 2);
|
|
180
178
|
});
|
|
181
179
|
|
|
182
180
|
test('a passed Treasury matter sets the limit through the same write as PATCH /builders/:id/budget', async () => {
|
package/tests/hall_city.mjs
CHANGED
|
@@ -384,7 +384,7 @@ const FEEDS = (ms, goals = [goal(1)], rollups = [rollup(1, { done: 2, active: 1
|
|
|
384
384
|
|
|
385
385
|
test('the page reads the three feeds and draws the docket\'s pins and line', async () => {
|
|
386
386
|
const soon = new Date(Date.now() + 2 * 3600000).toISOString();
|
|
387
|
-
const ms = [matter({ id: 'board:1', building: 'charter', state: 'needs', deadline: soon }), matter({ id: 'blockers:2', state: 'needs' }), matter({ id: 'blockers:4', state: 'moving' }), matter({ id: '
|
|
387
|
+
const ms = [matter({ id: 'board:1', building: 'charter', state: 'needs', deadline: soon }), matter({ id: 'blockers:2', state: 'needs' }), matter({ id: 'blockers:4', state: 'moving' }), matter({ id: 'override_requests:3', building: 'release', state: 'moving' }), matter({ id: 'removal_proposals:5', building: 'disputes', state: 'settled' })];
|
|
388
388
|
const p = await bootPage({ feeds: FEEDS(ms) });
|
|
389
389
|
assert.deepEqual([...p.calls].sort(), ['/api/bongos/goals', '/api/bongos/goals/rollups', '/api/bongos/government/docket']);
|
|
390
390
|
const pins = p.byId.get('cityPins').children;
|
|
@@ -182,8 +182,9 @@ test('every act maps to the route that does it, with the body that route accepts
|
|
|
182
182
|
const ov = card([{ id: 'override_requests:9', source: 'override_requests', building: 'release', state: 'needs', title: 'Release', ref: { type: 'override_request', id: '9' } }], 'override_requests:9');
|
|
183
183
|
assert.deepEqual(C.request(ov, 'release'), { method: 'POST', url: '/api/bongos/override-requests/9/decide', body: { decision: 'approve' } });
|
|
184
184
|
assert.equal(C.request(ov, 'hold'), null, 'holding records nothing');
|
|
185
|
+
// task 1004859 removed POST /gate-approvals/:pr/approve: a stale gate matter must send nothing
|
|
185
186
|
const gate = card([{ id: 'gate_approvals:812', source: 'gate_approvals', building: 'release', state: 'needs', title: 'A PR', ref: { type: 'pull_request', id: 812 } }], 'gate_approvals:812');
|
|
186
|
-
assert.
|
|
187
|
+
assert.equal(C.request(gate, 'release'), null, 'the retired gate route is never called');
|
|
187
188
|
});
|
|
188
189
|
|
|
189
190
|
// Task 1004559 moved the core update onto the Release desk, check first: the ONLY
|
|
@@ -406,7 +407,6 @@ const SOURCE_FIXTURES = () => {
|
|
|
406
407
|
{ id: 'redteam_reports:4', source: 'redteam_reports', building: 'disputes', rule: 'decide', state: 'needs', title: 'Security report: sign-in limit skipped', href: '/watch', ref: { type: 'vulnerability_report', id: '4' }, acts: [{ key: 'confirm' }, { key: 'dispute' }] },
|
|
407
408
|
{ id: 'criteria_uat:3', source: 'criteria_uat', building: 'review', rule: 'signoff', state: 'needs', title: 'The export runs nightly', href: '/goals#/goal/7', ref: { type: 'criterion', id: '3' }, acts: [{ key: 'signoff', options: [{ key: 'live', label: 'tested it live' }] }, { key: 'force', options: [{ key: 'tested_by_hand', label: 'tested by hand' }] }] },
|
|
408
409
|
{ id: 'core_update:1.21.20', source: 'core_update', building: 'release', rule: 'decide', state: 'needs', title: 'Take core 1.21.20 (running 1.21.19)', href: '/settings#software-update', ref: { type: 'core_version', id: '1.21.20' } },
|
|
409
|
-
{ id: 'gate_approvals:88', source: 'gate_approvals', building: 'release', rule: 'decide', state: 'needs', title: 'PR 88 waits at the gate', href: '/gate', ref: { type: 'pull_request', id: '88' } },
|
|
410
410
|
{ id: 'autobongos_fence:project', source: 'autobongos_fence', building: 'automation', rule: 'decide', state: 'needs', title: 'Unattended building is off', href: '/autobongos', ref: { type: 'autonomy_fence', id: 'project' }, acts: [{ key: 'switch_on' }] },
|
|
411
411
|
{ id: 'module_rechecks:7', source: 'module_rechecks', building: 'release', rule: 'rule', state: 'needs', title: 'image-tools 1.2.0 failed its security check', href: '/modules', ref: { type: 'module_version', id: 'image-tools@1.2.0' }, acts: [{ key: 'recheck', options: [{ key: 'fixed', label: 'fixed since' }] }] },
|
|
412
412
|
// task 1004712: a dry pot's borrow from the reserve
|
|
@@ -91,9 +91,9 @@ test('the shared confirm modal sits on --z-modal, closed by one hairline and no
|
|
|
91
91
|
const modal = css.slice(css.indexOf('.ov-modal {'), css.indexOf('.ov-modal .scroll__h'));
|
|
92
92
|
assert.ok(!/box-shadow/.test(modal), 'no shadow under the dialog');
|
|
93
93
|
assert.match(modal, /border: var\(--line\) solid var\(--rule\)/, 'one rule hairline');
|
|
94
|
-
// task 1004829
|
|
95
|
-
//
|
|
96
|
-
assert.ok(!/ov-modal-bg/.test(read('gate.js')), 'gate.js opens no modal
|
|
94
|
+
// task 1004829 made the Gate's approve an ask card with no modal; task 1004859
|
|
95
|
+
// removed the approve altogether. The page still opens no modal — and styles none.
|
|
96
|
+
assert.ok(!/ov-modal-bg/.test(read('gate.js')), 'gate.js opens no modal');
|
|
97
97
|
assert.ok(!/-modal-bg/.test(read('gate.css')), 'gate.css styles no modal of its own');
|
|
98
98
|
});
|
|
99
99
|
|
|
@@ -303,15 +303,19 @@ test('a failed load on Watch and Sessions is said in the page body', () => {
|
|
|
303
303
|
assert.match(read('sessions.html'), /id="ledger-status" role="status"/, 'the count and a failed page load sit on the ledger itself');
|
|
304
304
|
});
|
|
305
305
|
|
|
306
|
-
test('the
|
|
306
|
+
test('the page at /gate is the Autobongos panel: no held-PR list, and a Metic is told the controls are the Archon\'s', () => {
|
|
307
|
+
// task 1004859 (owner, 2026-10-25: "Keep page, drop PR list"). The held-PR list
|
|
308
|
+
// waited on a gate-review CI check that never ran; its route is gone, so the page
|
|
309
|
+
// must not call it, and the URL stays /gate so bookmarks hold.
|
|
307
310
|
const js = read('gate.js');
|
|
308
|
-
|
|
309
|
-
assert.
|
|
310
|
-
assert.match(js, /if \(
|
|
311
|
-
assert.match(js, /
|
|
312
|
-
assert.match(js, /'data-act': 'approve'/);
|
|
313
|
-
assert.match(js, /Waiting for approval/, 'everyone else reads the facts as a plain row');
|
|
311
|
+
assert.ok(!/['\`]\/gate-approvals/.test(js), 'gate.js no longer calls the removed /gate-approvals routes');
|
|
312
|
+
assert.ok(!/renderRow\(pr|OTBAsk/.test(js), 'no held-PR row or ask card is drawn');
|
|
313
|
+
assert.match(js, /if \(rank !== 'archon'\) \{ showArchonOnly\(\); return; \}/, 'a Metic gets the note, not an empty page');
|
|
314
|
+
assert.match(js, /The Autobongos controls are for the \$\{rankLabel\('archon'\)\}/, 'the rank is named from the pack');
|
|
314
315
|
const html = read('gate.html');
|
|
315
|
-
assert.
|
|
316
|
-
assert.match(html, /
|
|
316
|
+
assert.match(html, /<title>Autobongos — /, 'the page says what it is');
|
|
317
|
+
assert.match(html, /data-page-title="Autobongos"/);
|
|
318
|
+
assert.match(html, /id="gate-archon-only"/);
|
|
319
|
+
assert.ok(!/gate-list|hall-ask\.js/.test(html), 'the list mount and the ask-card script are gone');
|
|
320
|
+
assert.match(read('shell.js'), /\{ id: 'gate', label: 'Autobongos', icon: 'gate', href: '\/gate'/, 'the nav names it, at the same URL');
|
|
317
321
|
});
|
|
@@ -51,7 +51,7 @@ function board() {
|
|
|
51
51
|
|
|
52
52
|
// ── a long-winded docket: every source, long words everywhere ─────────────────
|
|
53
53
|
const LONG = 'The extraordinarily long-winded proposal to reorganise every single floor of the hall before winter';
|
|
54
|
-
const SOURCES = [['board', 'charter'], ['blockers', 'planning'], ['access_requests', 'people'], ['goal_join_requests', 'people'], ['override_requests', 'release'], ['removal_proposals', 'disputes'], ['redteam_reports', 'disputes'], ['criteria_uat', 'review'], ['core_update', 'release'], ['
|
|
54
|
+
const SOURCES = [['board', 'charter'], ['blockers', 'planning'], ['access_requests', 'people'], ['goal_join_requests', 'people'], ['override_requests', 'release'], ['removal_proposals', 'disputes'], ['redteam_reports', 'disputes'], ['criteria_uat', 'review'], ['core_update', 'release'], ['autobongos_fence', 'automation'], ['module_rechecks', 'release'], ['grade_appeals', 'disputes'], ['standing_rules', 'rules'], ['treasury_spending', 'treasury']];
|
|
55
55
|
function matter(over = {}) {
|
|
56
56
|
return { id: 'blockers:1', source: 'blockers', building: 'planning', rule: 'majority', state: 'needs', title: 'A matter', short: 'a matter', where: 'Planning', opened_at: '2026-09-29T10:00:00.000Z', deadline: null, proposer: null, tally: null, href: null, ref: null, ...over };
|
|
57
57
|
}
|
|
@@ -78,6 +78,9 @@ function longMatters() {
|
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
out.push(matter({ id: 'board:900', source: 'board', building: 'planning', short: 'idea to ratify', title: LONG, state: 'needs' }));
|
|
81
|
+
// A second override waiting at Release: task 1004859 removed the gate-approval source that
|
|
82
|
+
// used to fill a Release floor, and the stress docket must still reach its 30 rows.
|
|
83
|
+
out.push(matter({ id: 'override_requests:901', source: 'override_requests', building: 'release', title: `${LONG} 901`, short: 'a matter with a very long short description', state: 'needs', ref: { id: 901 } }));
|
|
81
84
|
return out;
|
|
82
85
|
}
|
|
83
86
|
const ROOM = { open: [{ id: '1', subject_type: 'constitutional_amendment', subject: { title: LONG, author_id: '7' }, votes: [] }, { id: '900', subject_type: 'full_idea', subject: { title: LONG }, votes: [] }], closed: [] };
|
|
@@ -176,7 +179,6 @@ test('a row\'s buttons are the card\'s acts under the board\'s words, and send t
|
|
|
176
179
|
matter({ id: 'access_requests:11', source: 'access_requests', building: 'people', state: 'needs', proposer: { name: 'Ana Ruiz' }, ref: { id: 11 } }),
|
|
177
180
|
matter({ id: 'goal_join_requests:12', source: 'goal_join_requests', building: 'people', state: 'needs', proposer: { name: 'Kai Mo' }, href: '/builders/goal/42', ref: { id: 12 } }),
|
|
178
181
|
matter({ id: 'override_requests:13', source: 'override_requests', building: 'release', state: 'needs', ref: { id: 13 } }),
|
|
179
|
-
matter({ id: 'gate_approvals:14', source: 'gate_approvals', building: 'release', state: 'needs', ref: { id: 14 } }),
|
|
180
182
|
matter({ id: 'board:1', source: 'board', building: 'charter', state: 'needs', ref: { type: 'board_item', id: '1' } }),
|
|
181
183
|
];
|
|
182
184
|
const dk = docket(ms);
|
|
@@ -189,7 +191,6 @@ test('a row\'s buttons are the card\'s acts under the board\'s words, and send t
|
|
|
189
191
|
const rel = D.rowsFor('release', 0, dk, { now: NOW });
|
|
190
192
|
assert.deepEqual(rel[0].actions.map((a) => a.label), ['release', 'hold']);
|
|
191
193
|
assert.equal(C.request(rel[0].card, 'release').url, '/api/bongos/override-requests/13/decide');
|
|
192
|
-
assert.equal(C.request(D.rowsFor('release', 5, dk, { now: NOW })[0].card, 'release').url, '/api/bongos/gate-approvals/14/approve');
|
|
193
194
|
// a vote: yes and no, and the no's reason is a chip, never free text
|
|
194
195
|
const vote = row('charter', 0);
|
|
195
196
|
assert.deepEqual(vote.actions.map((a) => [a.key, a.label]), [['for', 'yes'], ['object', 'no']]);
|