@bongos/core 1.21.176 → 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 +186 -496
- package/.claude/skills/planning-session/SKILL.md +2 -2
- 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/config/scheduled-routines.json +3 -8
- package/docs/adr/0042-builder-self-deploy-ci-auto-merge.md +3 -2
- package/docs/adr/0091-bounding-the-kernel-and-db-carve.md +1 -1
- package/docs/adr/0093-tranche-2-core-carve-sequence.md +1 -1
- package/docs/api/openapi.json +1 -87
- package/docs/api-reference.md +1 -8
- package/docs/canonical-permissions.md +4 -4
- package/docs/copy-inventory.md +25 -39
- package/docs/copy-registry.json +73 -200
- package/docs/file-map.md +6 -11
- package/docs/handoff-template.md +1 -1
- package/docs/module-api-changelog.md +4 -0
- package/docs/modules-contract.md +2 -2
- package/docs/onboarding/diagrams/README.md +3 -7
- package/docs/onboarding/diagrams/assertions.json +3 -3
- package/docs/page-inventory.json +4 -7
- package/docs/page-readings.json +1520 -1569
- package/docs/recipes/ops-gotchas.md +3 -1
- package/modules/autonomy/db.js +9 -0
- package/modules/autonomy/module.json +2 -2
- package/modules/autonomy/pollers/daily-checks.js +262 -0
- package/modules/autonomy/pollers/routine-timers.js +7 -5
- package/modules/autonomy/rank-roles.js +64 -0
- package/modules/autonomy/server-schedule.js +37 -0
- 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 +36 -0
- package/scripts/bongos/adopt-preflight.js +3 -3
- package/scripts/bongos/api-alias-caller-scan.js +2 -2
- package/scripts/bongos/audit-authorship.sh +1 -8
- package/scripts/bongos/audit-rank-roles.js +15 -56
- package/scripts/bongos/bump-version.js +46 -9
- package/scripts/bongos/codemod-rename-scripts-gds.js +0 -1
- package/scripts/bongos/diagram-facts.js +1 -1
- package/scripts/bongos/do-api.js +1 -1
- package/scripts/bongos/doc-cli-guard.js +2 -0
- package/scripts/bongos/doc-control-claims.js +2 -2
- package/scripts/bongos/fitness-checks-identity.js +4 -15
- package/scripts/bongos/gen-diagrams.js +3 -3
- 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/pulse.js +5 -3
- package/scripts/bongos/routine-schedule.js +38 -3
- package/scripts/bongos/run-unit-tests.js +17 -5
- package/scripts/bongos/ship-regen.js +2 -3
- package/scripts/gds/{goal-names.js → hard-floor.js} +3 -4
- package/scripts/security/dep-audit.js +50 -124
- 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/autonomy_daily_checks.mjs +201 -0
- package/tests/bump_version.mjs +52 -18
- package/tests/claude_materialize.mjs +12 -12
- package/tests/copy_desk_flags_db.mjs +22 -14
- package/tests/effective_visibility_predicate.mjs +0 -18
- 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/routine_capture_kind.mjs +18 -7
- package/tests/routine_schedule.mjs +42 -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/artifact-staleness.js +0 -267
- package/scripts/bongos/ci-grade.js +0 -82
- package/scripts/bongos/classify-discipline.js +0 -153
- package/scripts/bongos/codemod-rename-src-gds.js +0 -145
- package/scripts/bongos/coreB-carve-proof.sh +0 -725
- package/scripts/bongos/gate-review.js +0 -330
- package/scripts/bongos/goal-names.js +0 -102
- package/scripts/bongos/rename-goals.js +0 -109
- package/scripts/bongos/seed-adopt-tasks.js +0 -75
- package/scripts/bongos/seed-api-contract-goal-tasks.js +0 -141
- package/scripts/bongos/seed-board-room-tasks.js +0 -196
- package/scripts/bongos/seed-bongos-app-tasks.js +0 -294
- package/scripts/bongos/seed-bongos-coreB-tranche1-tasks.js +0 -81
- package/scripts/bongos/seed-bongos-coreB-tranche2-tasks.js +0 -100
- package/scripts/bongos/seed-bongos-goal-hierarchy-tasks.js +0 -307
- package/scripts/bongos/seed-bongos-module-system-tasks.js +0 -346
- package/scripts/bongos/seed-bongos-tasks.js +0 -214
- package/scripts/bongos/seed-bongos-v1-seo-tasks.js +0 -104
- package/scripts/bongos/seed-bv1-community-tasks.js +0 -131
- package/scripts/bongos/seed-bv1-privacy-invites-tasks.js +0 -133
- package/scripts/bongos/seed-bv1-profile-page-tasks.js +0 -161
- package/scripts/bongos/seed-bv1-r01-delta-tasks.js +0 -154
- package/scripts/bongos/seed-bv2-tweak-mode-tasks.js +0 -277
- package/scripts/bongos/seed-gds-v4-tasks.js +0 -624
- package/scripts/bongos/seed-governance-tasks.js +0 -340
- package/scripts/bongos/seed-medusa-tasks.js +0 -440
- package/scripts/bongos/seed-module-upstreaming.js +0 -219
- package/scripts/bongos/seed-productization-tasks.js +0 -235
- package/scripts/bongos/seed-project-admin-console-tasks.js +0 -88
- package/scripts/bongos/seed-provisioning-tasks.js +0 -83
- package/scripts/bongos/seed-strict-versioning-tasks.js +0 -275
- package/scripts/bongos/seed-v2-world-tasks.js +0 -283
- package/scripts/gds/artifact-staleness.js +0 -7
- package/scripts/gds/ci-grade.js +0 -7
- package/scripts/gds/classify-discipline.js +0 -7
- package/scripts/gds/codemod-rename-src-gds.js +0 -7
- package/scripts/gds/coreB-carve-proof.sh +0 -8
- package/scripts/gds/gate-review.js +0 -7
- package/scripts/gds/rename-goals.js +0 -7
- package/scripts/gds/seed-adopt-tasks.js +0 -7
- package/scripts/gds/seed-api-contract-goal-tasks.js +0 -7
- package/scripts/gds/seed-board-room-tasks.js +0 -6
- package/scripts/gds/seed-bongos-app-tasks.js +0 -7
- package/scripts/gds/seed-bongos-coreB-tranche1-tasks.js +0 -7
- package/scripts/gds/seed-bongos-coreB-tranche2-tasks.js +0 -7
- package/scripts/gds/seed-bongos-goal-hierarchy-tasks.js +0 -7
- package/scripts/gds/seed-bongos-module-system-tasks.js +0 -7
- package/scripts/gds/seed-bongos-tasks.js +0 -7
- package/scripts/gds/seed-bongos-v1-seo-tasks.js +0 -6
- package/scripts/gds/seed-bv1-community-tasks.js +0 -6
- package/scripts/gds/seed-bv1-privacy-invites-tasks.js +0 -6
- package/scripts/gds/seed-bv1-profile-page-tasks.js +0 -6
- package/scripts/gds/seed-bv1-r01-delta-tasks.js +0 -6
- package/scripts/gds/seed-bv2-tweak-mode-tasks.js +0 -6
- package/scripts/gds/seed-gds-v4-tasks.js +0 -7
- package/scripts/gds/seed-governance-tasks.js +0 -7
- package/scripts/gds/seed-medusa-tasks.js +0 -7
- package/scripts/gds/seed-module-upstreaming.js +0 -7
- package/scripts/gds/seed-productization-tasks.js +0 -7
- package/scripts/gds/seed-project-admin-console-tasks.js +0 -6
- package/scripts/gds/seed-provisioning-tasks.js +0 -7
- package/scripts/gds/seed-strict-versioning-tasks.js +0 -7
- package/scripts/gds/seed-v2-world-tasks.js +0 -7
- package/scripts/migrate_cost_ledger.sh +0 -178
- package/tests/artifact_staleness.mjs +0 -207
- package/tests/gate_approvals.mjs +0 -611
- package/tests/gate_review.mjs +0 -152
- package/tests/goal_names.mjs +0 -120
- package/tests/newcomer_generator.mjs +0 -48
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// tests/autonomy_daily_checks.mjs — the server's daily checks run once a day, file
|
|
2
|
+
// each distinct finding once, and never record "could not check" as clean
|
|
3
|
+
// (task 1004878).
|
|
4
|
+
//
|
|
5
|
+
// DB-free and network-free: the run ledger, the roster, npm and the idea inbox are
|
|
6
|
+
// all injected. The one thing these tests exist to stop is the failure every one of
|
|
7
|
+
// these routines already had — declared daily, run never — so the scheduling half
|
|
8
|
+
// (isDue against the ledger, not the boot) is pinned as hard as the filing half.
|
|
9
|
+
//
|
|
10
|
+
// Run: node --test tests/autonomy_daily_checks.mjs
|
|
11
|
+
|
|
12
|
+
import { test } from 'node:test';
|
|
13
|
+
import assert from 'node:assert/strict';
|
|
14
|
+
import { createRequire } from 'node:module';
|
|
15
|
+
|
|
16
|
+
const require = createRequire(import.meta.url);
|
|
17
|
+
const dc = require('../modules/autonomy/pollers/daily-checks.js');
|
|
18
|
+
const { SERVER_ROUTINES, DAY_MS, WEEK_MS } = require('../modules/autonomy/server-schedule.js');
|
|
19
|
+
const rt = require('../modules/autonomy/pollers/routine-timers.js');
|
|
20
|
+
|
|
21
|
+
const NOW = Date.parse('2026-10-26T12:00:00Z');
|
|
22
|
+
const hoursAgo = (h) => new Date(NOW - h * 3600_000).toISOString();
|
|
23
|
+
const LADDER = ['xenos', 'thetes', 'metic', 'archon'];
|
|
24
|
+
|
|
25
|
+
// A fake ledger: rows newest-first, plus a list of what got recorded.
|
|
26
|
+
function ledger(rows = []) {
|
|
27
|
+
const recorded = [];
|
|
28
|
+
return {
|
|
29
|
+
recorded,
|
|
30
|
+
db: {
|
|
31
|
+
readAutonomyRuns: async () => rows,
|
|
32
|
+
recordAutonomyRun: async (r) => { recorded.push(r); return {}; },
|
|
33
|
+
readRankRoster: async () => [
|
|
34
|
+
{ id: 1, github_login: 'ok', rank: 'archon', status: 'active' },
|
|
35
|
+
{ id: 2, github_login: 'old', rank: 'ephor', status: 'inactive' },
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const quiet = { log: () => {}, error: () => {} };
|
|
41
|
+
const capturing = () => {
|
|
42
|
+
const filed = [];
|
|
43
|
+
return { filed, captureIdea: async (i) => { filed.push(i); return { id: 42 }; } };
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// ------------------------------------------------------------- the schedule table
|
|
47
|
+
|
|
48
|
+
test('the daily poller runs exactly the server-schedule rows that name it', () => {
|
|
49
|
+
assert.deepEqual(dc.ROUTINES, SERVER_ROUTINES.filter((r) => r.poller === 'daily-checks').map((r) => r.routine));
|
|
50
|
+
assert.deepEqual(dc.ROUTINES.sort(), ['dep-audit-nightly', 'rank-roles-audit']);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('peer-vote-tally takes its period from the same table (weekly)', () => {
|
|
54
|
+
assert.equal(rt.PEER_VOTE_INTERVAL_MS, WEEK_MS);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// ------------------------------------------------------------------ isDue
|
|
58
|
+
|
|
59
|
+
test('due when it has never run', () => {
|
|
60
|
+
assert.equal(dc.isDue([], 'rank-roles-audit', NOW), true);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('not due 20 hours after the last run, due 23.5 hours after — daily, not per boot', () => {
|
|
64
|
+
assert.equal(dc.isDue([{ created_at: hoursAgo(20) }], 'rank-roles-audit', NOW), false);
|
|
65
|
+
assert.equal(dc.isDue([{ created_at: hoursAgo(23.5) }], 'rank-roles-audit', NOW), true);
|
|
66
|
+
assert.equal(dc.DUE_SLACK_MS < DAY_MS / 24, true, 'slack must stay under one hourly tick or runs drift');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('an ERROR row counts as a run too, so a broken check is retried daily, not hourly', async () => {
|
|
70
|
+
const L = ledger([{ decision: 'no-go', reason: 'error: x', created_at: hoursAgo(2) }]);
|
|
71
|
+
const out = await dc.runCheck('rank-roles-audit', { db: L.db, now: () => NOW, liveRanks: LADDER, ...quiet });
|
|
72
|
+
assert.equal(out.skipped, 'not_due');
|
|
73
|
+
assert.equal(L.recorded.length, 0);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// --------------------------------------------------------------- filing
|
|
77
|
+
|
|
78
|
+
test('a finding is filed as one idea and the run records its fingerprint', async () => {
|
|
79
|
+
const L = ledger();
|
|
80
|
+
const C = capturing();
|
|
81
|
+
const out = await dc.runCheck('rank-roles-audit', { db: L.db, now: () => NOW, liveRanks: LADDER, captureIdea: C.captureIdea, ...quiet });
|
|
82
|
+
assert.equal(out.outcome, 'filed');
|
|
83
|
+
assert.equal(C.filed.length, 1);
|
|
84
|
+
assert.match(C.filed[0].title, /^\[rank-roles\] 1 builder/);
|
|
85
|
+
assert.match(C.filed[0].bodyMd, /builder 2 \(old\) rank=ephor status=inactive/);
|
|
86
|
+
assert.equal(C.filed[0].kind, 'bug');
|
|
87
|
+
assert.equal(L.recorded[0].decision, 'go');
|
|
88
|
+
assert.match(L.recorded[0].reason, /^findings n=1 fp=[0-9a-f]{12} idea=42$/);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('the SAME finding the next day is recorded unchanged and NOT filed again', async () => {
|
|
92
|
+
const fp = dc.fingerprint(['2:ephor']);
|
|
93
|
+
const L = ledger([{ decision: 'go', reason: `findings n=1 fp=${fp} idea=42`, created_at: hoursAgo(24) }]);
|
|
94
|
+
const C = capturing();
|
|
95
|
+
const out = await dc.runCheck('rank-roles-audit', { db: L.db, now: () => NOW, liveRanks: LADDER, captureIdea: C.captureIdea, ...quiet });
|
|
96
|
+
assert.equal(out.outcome, 'unchanged');
|
|
97
|
+
assert.equal(C.filed.length, 0);
|
|
98
|
+
assert.match(L.recorded[0].reason, /unchanged$/);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('an error between two identical findings does not cause a duplicate filing', async () => {
|
|
102
|
+
const fp = dc.fingerprint(['2:ephor']);
|
|
103
|
+
const L = ledger([
|
|
104
|
+
{ decision: 'no-go', reason: 'error: db blip', created_at: hoursAgo(24) },
|
|
105
|
+
{ decision: 'go', reason: `findings n=1 fp=${fp} idea=42`, created_at: hoursAgo(48) },
|
|
106
|
+
]);
|
|
107
|
+
const C = capturing();
|
|
108
|
+
const out = await dc.runCheck('rank-roles-audit', { db: L.db, now: () => NOW, liveRanks: LADDER, captureIdea: C.captureIdea, ...quiet });
|
|
109
|
+
assert.equal(out.outcome, 'unchanged');
|
|
110
|
+
assert.equal(C.filed.length, 0);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('a finding that comes back after a clean day is filed again', async () => {
|
|
114
|
+
const L = ledger([{ decision: 'go', reason: 'clean', created_at: hoursAgo(24) }]);
|
|
115
|
+
const C = capturing();
|
|
116
|
+
const out = await dc.runCheck('rank-roles-audit', { db: L.db, now: () => NOW, liveRanks: LADDER, captureIdea: C.captureIdea, ...quiet });
|
|
117
|
+
assert.equal(out.outcome, 'filed');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('the fingerprint ignores order and changes when the set changes', () => {
|
|
121
|
+
assert.equal(dc.fingerprint(['a', 'b']), dc.fingerprint(['b', 'a']));
|
|
122
|
+
assert.notEqual(dc.fingerprint(['a']), dc.fingerprint(['a', 'b']));
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('a clean run files nothing and records clean', async () => {
|
|
126
|
+
const L = ledger();
|
|
127
|
+
L.db.readRankRoster = async () => [{ id: 1, rank: 'archon' }];
|
|
128
|
+
const C = capturing();
|
|
129
|
+
const out = await dc.runCheck('rank-roles-audit', { db: L.db, now: () => NOW, liveRanks: LADDER, captureIdea: C.captureIdea, ...quiet });
|
|
130
|
+
assert.equal(out.outcome, 'clean');
|
|
131
|
+
assert.equal(C.filed.length, 0);
|
|
132
|
+
assert.deepEqual(L.recorded[0], { routine: 'rank-roles-audit', decision: 'go', reason: 'clean' });
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('no idea inbox (ideas module off) is recorded as undelivered, and retried next time', async () => {
|
|
136
|
+
const L = ledger();
|
|
137
|
+
const out = await dc.runCheck('rank-roles-audit', { db: L.db, now: () => NOW, liveRanks: LADDER, captureIdea: 'not a function', ...quiet });
|
|
138
|
+
assert.equal(out.outcome, 'undelivered');
|
|
139
|
+
assert.equal(L.recorded[0].decision, 'no-go', 'a no-go row is never read as delivered');
|
|
140
|
+
assert.equal(dc.lastFiledFingerprint(L.recorded.map((r) => ({ ...r }))), null);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test('a capture that throws is undelivered, never a crash', async () => {
|
|
144
|
+
const L = ledger();
|
|
145
|
+
const out = await dc.runCheck('rank-roles-audit', {
|
|
146
|
+
db: L.db, now: () => NOW, liveRanks: LADDER, captureIdea: async () => { throw { code: 'BAD_KIND' }; }, ...quiet,
|
|
147
|
+
});
|
|
148
|
+
assert.equal(out.outcome, 'undelivered');
|
|
149
|
+
assert.match(L.recorded[0].reason, /undelivered: BAD_KIND/);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// ------------------------------------------------------- could not check ≠ clean
|
|
153
|
+
|
|
154
|
+
test('npm missing is an ERROR row, not clean', async () => {
|
|
155
|
+
const L = ledger();
|
|
156
|
+
const out = await dc.runCheck('dep-audit-nightly', {
|
|
157
|
+
db: L.db, now: () => NOW, cwd: '/i', npmAudit: async () => ({ error: 'npm not found' }), ...quiet,
|
|
158
|
+
});
|
|
159
|
+
assert.equal(out.outcome, 'error');
|
|
160
|
+
assert.deepEqual(L.recorded[0], { routine: 'dep-audit-nightly', decision: 'no-go', reason: 'error: npm not found' });
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("npm's own JSON error (no lockfile) is an ERROR row, not clean", async () => {
|
|
164
|
+
const L = ledger();
|
|
165
|
+
const out = await dc.runCheck('dep-audit-nightly', {
|
|
166
|
+
db: L.db, now: () => NOW, cwd: '/i',
|
|
167
|
+
npmAudit: async () => ({ stdout: JSON.stringify({ error: { code: 'ENOLOCK', summary: 'This command requires an existing lockfile.' } }), stderr: '' }),
|
|
168
|
+
...quiet,
|
|
169
|
+
});
|
|
170
|
+
assert.equal(out.outcome, 'error');
|
|
171
|
+
assert.match(L.recorded[0].reason, /lockfile/);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test('dep-audit audits the cwd it is handed and files high/critical findings only', async () => {
|
|
175
|
+
const L = ledger();
|
|
176
|
+
const C = capturing();
|
|
177
|
+
let auditedIn = null;
|
|
178
|
+
const out = await dc.runCheck('dep-audit-nightly', {
|
|
179
|
+
db: L.db, now: () => NOW, cwd: '/srv/instance', captureIdea: C.captureIdea, ...quiet,
|
|
180
|
+
npmAudit: async (cwd) => {
|
|
181
|
+
auditedIn = cwd;
|
|
182
|
+
return { stdout: JSON.stringify({ vulnerabilities: {
|
|
183
|
+
bad: { severity: 'critical', via: [{ severity: 'critical', title: 'RCE', url: 'https://x/1', source: 1 }], fixAvailable: true },
|
|
184
|
+
meh: { severity: 'moderate', via: [], fixAvailable: true },
|
|
185
|
+
} }), stderr: '' };
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
assert.equal(auditedIn, '/srv/instance');
|
|
189
|
+
assert.equal(out.outcome, 'filed');
|
|
190
|
+
assert.match(C.filed[0].title, /^\[dep-audit\] 1 high\/critical vulnerability in/);
|
|
191
|
+
assert.match(C.filed[0].bodyMd, /\*\*bad\*\* \(critical\) — RCE/);
|
|
192
|
+
assert.doesNotMatch(C.filed[0].bodyMd, /meh/);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// --------------------------------------------------------------- the tick
|
|
196
|
+
|
|
197
|
+
test('an unreachable database is a quiet skip for every routine (a builder laptop)', async () => {
|
|
198
|
+
const db = { readAutonomyRuns: async () => { throw new Error('connect ENOENT /var/run/postgresql'); } };
|
|
199
|
+
const out = await dc.tick({ db, ...quiet });
|
|
200
|
+
assert.deepEqual(out.map((r) => r.skipped), dc.ROUTINES.map(() => 'db_unreachable'));
|
|
201
|
+
});
|
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', () => {
|
|
@@ -177,7 +177,7 @@ t('materializeClaude: materializes scheduled-tasks — bodies→bongos, companio
|
|
|
177
177
|
assert.ok(existsSync(rt), '.claude/scheduled-tasks created in the instance');
|
|
178
178
|
// Every materialized routine's core invocation is bongosified — asserted over
|
|
179
179
|
// WHATEVER routines the tree carries (the neutral core carries redteam-weekly;
|
|
180
|
-
// the founder core also carries
|
|
180
|
+
// the founder core also carries rank-roles-audit/peer-vote-tally). No routine
|
|
181
181
|
// body may keep a bare `node scripts/bongos/…` core invocation.
|
|
182
182
|
let sawBongosified = false;
|
|
183
183
|
for (const name of readdirSync(rt)) {
|
|
@@ -191,17 +191,17 @@ t('materializeClaude: materializes scheduled-tasks — bodies→bongos, companio
|
|
|
191
191
|
// Founder-specific fixtures — assert their finer behavior only when present, so
|
|
192
192
|
// this coverage stays live on the founder core without demanding those routines
|
|
193
193
|
// exist in the neutral core (which carries a different routine set).
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const
|
|
201
|
-
assert.ok(
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
assert.ok(!
|
|
194
|
+
// The companion-script case (task 1004878 moved this off the retired
|
|
195
|
+
// diagram-drift routine): a routine whose step runs a .js beside its SKILL.md.
|
|
196
|
+
const pvTally = join(rt, 'peer-vote-tally', 'tally.js');
|
|
197
|
+
if (existsSync(pvTally)) {
|
|
198
|
+
const pv = readFileSync(join(rt, 'peer-vote-tally', 'SKILL.md'), 'utf8');
|
|
199
|
+
assert.ok(pv.includes('node .claude/scheduled-tasks/peer-vote-tally/tally.js'), 'companion-path step command preserved');
|
|
200
|
+
const tally = readFileSync(pvTally, 'utf8');
|
|
201
|
+
assert.ok(tally.includes("require('../../hooks/_core-resolve.js')"), 'companion resolves core via _core-resolve');
|
|
202
|
+
// Code lines only: tally.js's own header QUOTES the bare form to explain why it is gone.
|
|
203
|
+
const code = tally.split('\n').filter((l) => !/^\s*\/\//.test(l)).join('\n');
|
|
204
|
+
assert.ok(!code.includes("require('../../../scripts/bongos/"), 'no bare ../../../scripts/bongos require left in the companion');
|
|
205
205
|
}
|
|
206
206
|
rmSync(dir, { recursive: true, force: true });
|
|
207
207
|
});
|
|
@@ -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`);
|
|
@@ -145,24 +145,6 @@ const SPELLING_EXEMPTIONS = [
|
|
|
145
145
|
obligation: (code) => !/\.query\s*\(/.test(code) && !/require\(\s*'pg'\s*\)/.test(code),
|
|
146
146
|
demands: 'issues no database query and loads no database driver — it answers from fixtures only',
|
|
147
147
|
},
|
|
148
|
-
{
|
|
149
|
-
file: 'scripts/bongos/seed-bv1-privacy-invites-tasks.js',
|
|
150
|
-
allow: [
|
|
151
|
-
{ text: "profile_state='", count: 1, why: 'a one-shot historical seeder: this sits inside a task DESCRIPTION string, quoting the predicate as prose describing the work to be done. Its subject IS this predicate.' },
|
|
152
|
-
{ text: 'NOT hide_stats', count: 1, why: 'the second half of that same quoted sentence in that same task description — prose, not a query.' },
|
|
153
|
-
],
|
|
154
|
-
obligation: (code) => !/\.query\s*\(/.test(code),
|
|
155
|
-
demands: 'issues no database query at all — it posts task rows over HTTP',
|
|
156
|
-
},
|
|
157
|
-
{
|
|
158
|
-
file: 'scripts/bongos/seed-bv1-r01-delta-tasks.js',
|
|
159
|
-
allow: [
|
|
160
|
-
{ text: "profile_state='", count: 1, why: 'the ADR 0171 R01 seeder: the predicate is quoted as prose inside the task briefs this script files, the same shape as the privacy seeder above.' },
|
|
161
|
-
{ text: 'NOT hide_stats', count: 1, why: 'the second half of that same quoted sentence in that same brief — prose describing work, never executed.' },
|
|
162
|
-
],
|
|
163
|
-
obligation: (code) => !/\.query\s*\(/.test(code),
|
|
164
|
-
demands: 'issues no database query at all — it posts task rows over HTTP',
|
|
165
|
-
},
|
|
166
148
|
];
|
|
167
149
|
|
|
168
150
|
// Every SELECT against platform_identity_accounts that is deliberately NOT a
|
|
@@ -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);
|