@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.
Files changed (121) hide show
  1. package/.bongos-core.json +140 -150
  2. package/clients/bongos-client/README.md +1 -1
  3. package/clients/bongos-client/bongos-client.global.js +0 -6
  4. package/clients/bongos-client/index.cjs +0 -6
  5. package/clients/bongos-client/index.d.ts +0 -6
  6. package/clients/bongos-client/index.mjs +0 -6
  7. package/docs/adr/0042-builder-self-deploy-ci-auto-merge.md +3 -2
  8. package/docs/api/openapi.json +1 -87
  9. package/docs/api-reference.md +1 -8
  10. package/docs/canonical-permissions.md +3 -3
  11. package/docs/copy-inventory.md +25 -39
  12. package/docs/copy-registry.json +73 -200
  13. package/docs/file-map.md +3 -3
  14. package/docs/module-api-changelog.md +2 -0
  15. package/docs/modules-contract.md +2 -2
  16. package/docs/page-inventory.json +4 -7
  17. package/docs/page-readings.json +1520 -1569
  18. package/modules/government/catalog.js +10 -7
  19. package/modules/government/decision-board.js +0 -1
  20. package/modules/government/decision-rules.js +0 -1
  21. package/modules/government/docket.js +0 -1
  22. package/modules/government/protected-surfaces.js +1 -1
  23. package/modules/government/protected-surfaces.json +2 -3
  24. package/modules/hall-ui/public/atlas.html +1 -1
  25. package/modules/hall-ui/public/blockers.html +1 -1
  26. package/modules/hall-ui/public/board-room.html +1 -1
  27. package/modules/hall-ui/public/city.html +1 -1
  28. package/modules/hall-ui/public/collab.html +1 -1
  29. package/modules/hall-ui/public/commands.html +1 -1
  30. package/modules/hall-ui/public/copy-desk.html +1 -1
  31. package/modules/hall-ui/public/deploy.html +1 -1
  32. package/modules/hall-ui/public/diagrams.html +1 -1
  33. package/modules/hall-ui/public/drachmae.html +1 -1
  34. package/modules/hall-ui/public/fleet.html +1 -1
  35. package/modules/hall-ui/public/gate.css +4 -26
  36. package/modules/hall-ui/public/gate.html +12 -27
  37. package/modules/hall-ui/public/gate.js +26 -176
  38. package/modules/hall-ui/public/gate.states.json +4 -22
  39. package/modules/hall-ui/public/goal-map.html +1 -1
  40. package/modules/hall-ui/public/goals.html +1 -1
  41. package/modules/hall-ui/public/government.html +1 -1
  42. package/modules/hall-ui/public/idea.html +1 -1
  43. package/modules/hall-ui/public/ideas.html +1 -1
  44. package/modules/hall-ui/public/index.html +1 -1
  45. package/modules/hall-ui/public/modules.html +1 -1
  46. package/modules/hall-ui/public/my-agents.html +1 -1
  47. package/modules/hall-ui/public/primer.html +1 -1
  48. package/modules/hall-ui/public/profile.html +1 -1
  49. package/modules/hall-ui/public/project-settings.html +1 -1
  50. package/modules/hall-ui/public/ranks.html +1 -1
  51. package/modules/hall-ui/public/roadmap.html +1 -1
  52. package/modules/hall-ui/public/roster.html +1 -1
  53. package/modules/hall-ui/public/sessions.html +1 -1
  54. package/modules/hall-ui/public/settings.html +1 -1
  55. package/modules/hall-ui/public/shell.js +1 -1
  56. package/modules/hall-ui/public/street-card.js +5 -9
  57. package/modules/hall-ui/public/street-desk.js +2 -2
  58. package/modules/hall-ui/public/street-inside.js +1 -1
  59. package/modules/hall-ui/public/street.html +1 -1
  60. package/modules/hall-ui/public/studio.html +1 -1
  61. package/modules/hall-ui/public/task.html +1 -1
  62. package/modules/hall-ui/public/thinking.html +1 -1
  63. package/modules/hall-ui/public/tweak-editor.html +1 -1
  64. package/modules/hall-ui/public/watch.html +1 -1
  65. package/modules/hall-ui/public/work.html +1 -1
  66. package/modules/lifecycle/matter-acts.js +0 -6
  67. package/modules/lifecycle/module.json +0 -1
  68. package/package-lock.json +2 -2
  69. package/package.json +1 -1
  70. package/release-notes.json +22 -0
  71. package/scripts/bongos/adopt-preflight.js +3 -3
  72. package/scripts/bongos/audit-authorship.sh +1 -3
  73. package/scripts/bongos/bump-version.js +46 -9
  74. package/scripts/bongos/doc-cli-guard.js +2 -0
  75. package/scripts/bongos/doc-control-claims.js +2 -2
  76. package/scripts/bongos/government-walk.js +3 -9
  77. package/scripts/bongos/hard-floor.js +52 -0
  78. package/scripts/bongos/publish-manifest.js +3 -3
  79. package/scripts/bongos/run-unit-tests.js +17 -5
  80. package/scripts/gds/hard-floor.js +6 -0
  81. package/src/bongos/module-scope-map.js +2 -2
  82. package/src/bongos/permission-path-check.js +1 -1
  83. package/src/bongos/route-rank-check.js +1 -8
  84. package/src/bongos/routes.js +1 -2
  85. package/src/module-api.js +1 -1
  86. package/src/modules.js +1 -1
  87. package/tests/acclaim_feed_evidence_db.mjs +42 -23
  88. package/tests/adr_0089_scope.mjs +7 -5
  89. package/tests/bump_version.mjs +52 -18
  90. package/tests/copy_desk_flags_db.mjs +22 -14
  91. package/tests/gating_review_retired.mjs +1 -1
  92. package/tests/government_act_alone_buildings.mjs +1 -3
  93. package/tests/government_board_membership_db.mjs +35 -22
  94. package/tests/government_protected_surfaces.mjs +29 -13
  95. package/tests/governor_docket.mjs +0 -2
  96. package/tests/governor_matter_acts.mjs +6 -8
  97. package/tests/hall_city.mjs +1 -1
  98. package/tests/hall_matter_card.mjs +2 -2
  99. package/tests/hall_oversight_world.mjs +16 -12
  100. package/tests/hall_street_desk.mjs +4 -3
  101. package/tests/hall_street_inside.mjs +1 -1
  102. package/tests/helpers.mjs +34 -0
  103. package/tests/howto_state.mjs +33 -56
  104. package/tests/howto_state_db.mjs +68 -0
  105. package/tests/idea_credit_streams_db.mjs +11 -3
  106. package/tests/integration_parked_ratchet.mjs +7 -1
  107. package/tests/nav_permission_atoms.mjs +1 -1
  108. package/tests/page_notes_db.mjs +12 -8
  109. package/tests/page_tweak_no_rearm.mjs +50 -0
  110. package/tests/publish_manifest.mjs +64 -0
  111. package/tests/search_isolation.mjs +5 -103
  112. package/tests/search_isolation_db.mjs +140 -0
  113. package/tests/session_earnings_mirror_db.mjs +2 -0
  114. package/tests/workflow_exec_paths.mjs +9 -11
  115. package/modules/lifecycle/routes/gate-approvals.js +0 -299
  116. package/scripts/bongos/ci-grade.js +0 -82
  117. package/scripts/bongos/gate-review.js +0 -330
  118. package/scripts/gds/ci-grade.js +0 -7
  119. package/scripts/gds/gate-review.js +0 -7
  120. package/tests/gate_approvals.mjs +0 -611
  121. package/tests/gate_review.mjs +0 -152
@@ -30,6 +30,8 @@
30
30
  // node scripts/bongos/bump-version.js # dry-run: print the plan
31
31
  // node scripts/bongos/bump-version.js --apply # rewrite both files
32
32
  // node scripts/bongos/bump-version.js --apply --note "run 123456" # extra provenance
33
+ // node scripts/bongos/bump-version.js --apply --taken-file versions.json
34
+ // # skip every version in that JSON array (`npm view @bongos/core versions --json`)
33
35
  // stdout (both modes): one JSON line { from, to, applied } for CI to capture.
34
36
 
35
37
  'use strict';
@@ -51,12 +53,18 @@ const DIVIDER = '---------------------------------------------------------------
51
53
  // entry appended too; omit it to bump the literal alone (the unit tests do).
52
54
  // Throws with a precise message on any shape it does not recognize — a silent
53
55
  // mis-edit of the version literal is the worst possible failure mode here.
54
- function bumpPatch(src, { note = '', changelog = null } = {}) {
56
+ //
57
+ // `taken` (task 1004886): versions that already exist on the registry. The bump
58
+ // lands on the first patch past `from` that is NOT taken, because npm never lets a
59
+ // version be published twice — and a run that "re-publishes" a taken number ships
60
+ // nothing new while main is told it did. That is how 1.21.175 got recorded on main
61
+ // on 2026-10-25 for a tree the registry's 1.21.175 (built 2026-10-23) never held.
62
+ function bumpPatch(src, { note = '', changelog = null, taken = new Set() } = {}) {
55
63
  const m = src.match(CONST_RE);
56
64
  if (!m) throw new Error(`bump-version: could not find the CORE_VERSION literal in ${TARGET} (expected: const CORE_VERSION = 'x.y.z';)`);
57
65
  const [major, minor, patch] = [Number(m[1]), Number(m[2]), Number(m[3])];
58
66
  const from = `${major}.${minor}.${patch}`;
59
- const to = `${major}.${minor}.${patch + 1}`;
67
+ const to = nextFreePatch(major, minor, patch, taken);
60
68
 
61
69
  // 1. The literal — same line shape deploy-divergence greps for.
62
70
  const newConstLine = `const CORE_VERSION = '${to}'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md`;
@@ -79,17 +87,40 @@ function bumpPatch(src, { note = '', changelog = null } = {}) {
79
87
  return { from, to, text, changelogText };
80
88
  }
81
89
 
90
+ // The first patch after `patch` whose version is not in `taken`. Bounded, so a
91
+ // corrupt taken-list can never spin: 10,000 consecutive taken patches is not a
92
+ // registry, it is a bug, and it throws.
93
+ function nextFreePatch(major, minor, patch, taken = new Set()) {
94
+ for (let p = patch + 1; p <= patch + 10000; p += 1) {
95
+ const v = `${major}.${minor}.${p}`;
96
+ if (!taken.has(v)) return v;
97
+ }
98
+ throw new Error(`bump-version: no free patch within 10000 of ${major}.${minor}.${patch} — the taken-list is not a registry listing`);
99
+ }
100
+
101
+ // Parse `npm view <pkg> versions --json` output into a Set of x.y.z strings. npm
102
+ // prints a bare string (not an array) when exactly one version exists. Anything
103
+ // that is not a strict x.y.z is dropped, so a stray line cannot become a version.
104
+ function registryVersions(jsonText) {
105
+ let v;
106
+ try { v = JSON.parse(String(jsonText)); } catch { return new Set(); }
107
+ const list = Array.isArray(v) ? v : (typeof v === 'string' ? [v] : []);
108
+ return new Set(list.filter((x) => typeof x === 'string' && /^\d+\.\d+\.\d+$/.test(x)));
109
+ }
110
+
82
111
  function main() {
83
112
  const apply = process.argv.includes('--apply');
84
113
  const noteIdx = process.argv.indexOf('--note');
85
114
  const note = noteIdx !== -1 ? (process.argv[noteIdx + 1] || '') : '';
115
+ const takenIdx = process.argv.indexOf('--taken-file');
116
+ const taken = takenIdx !== -1 ? registryVersions(fs.readFileSync(process.argv[takenIdx + 1], 'utf8')) : new Set();
86
117
 
87
118
  // Read and transform BOTH files before writing EITHER: a half-applied bump
88
119
  // (literal moved, ledger silent) is precisely the drift this script exists
89
120
  // to prevent, and it would be committed and pushed to main before anyone saw it.
90
121
  const src = fs.readFileSync(TARGET, 'utf8');
91
122
  const changelog = fs.readFileSync(CHANGELOG, 'utf8');
92
- const { from, to, text, changelogText } = bumpPatch(src, { note, changelog });
123
+ const { from, to, text, changelogText } = bumpPatch(src, { note, changelog, taken });
93
124
  if (changelogText === null) throw new Error('bump-version: the ledger was not rewritten — refusing to write a versionless release');
94
125
  if (apply) {
95
126
  fs.writeFileSync(TARGET, text);
@@ -143,10 +174,13 @@ function isReleaseCommit(subject) {
143
174
  // • pack or publish failed → the bump commit was never pushed (it only ever
144
175
  // existed on the discarded runner), so no version looks taken and the next
145
176
  // run recomputes and retries the SAME version. No gap, nothing burned.
146
- // • published, push then failed → version looks free, so the next run bumps to
147
- // it and npm refuses the duplicate. publish.yml treats that refusal as
148
- // success and falls through to the record step, which commits the release
149
- // line main was missing. Self-healing, and nothing wrong ships.
177
+ // • published, push then failed → version looks free to git, but the registry
178
+ // listing publish.yml passes as --taken-file has it, so the next run bumps PAST
179
+ // it (a gap in main's ledger, harmless). Task 1003914 used to treat npm's
180
+ // duplicate refusal as success and record the version instead — but the push
181
+ // fails precisely because main MOVED, so the recorded tree was never the one on
182
+ // the registry. That recorded 1.21.175 for 200+ commits npm never had (task
183
+ // 1004886). A duplicate refusal now fails the run and records nothing.
150
184
  //
151
185
  // Before that reorder the first case was the lossy one: the push landed first,
152
186
  // so a failed publish left main declaring a version the registry never had, the
@@ -196,8 +230,9 @@ function isHumanReleaseCommit(subject) {
196
230
  //
197
231
  // BOTH failure directions stay safe, which is what makes this acceptable here. A
198
232
  // subject wrongly counted → the lane bumps PAST a free version (a gap, harmless). A
199
- // real release missed → npm refuses the duplicate and publish.yml's already-present
200
- // branch records it (task 1003914). Neither direction can double-publish.
233
+ // real release missed → the registry listing marks it taken and the lane bumps past
234
+ // it; if even that read failed, npm refuses the duplicate and the run fails without
235
+ // recording anything (task 1004886). Neither direction can double-publish or mislabel.
201
236
  function laneReleasedVersions(subjects = []) {
202
237
  const out = new Set();
203
238
  for (const subject of subjects) {
@@ -211,6 +246,8 @@ function laneReleasedVersions(subjects = []) {
211
246
 
212
247
  module.exports = {
213
248
  bumpPatch,
249
+ nextFreePatch,
250
+ registryVersions,
214
251
  CONST_RE,
215
252
  RELEASE_COMMIT_RE,
216
253
  HUMAN_RELEASE_COMMIT_RE,
@@ -48,6 +48,8 @@ const DOC_CLI_ALLOW = new Map([
48
48
  'an accepted ADR recording how its evidence was measured; the spike harness was deleted once the verdict stood (task 1004857) and the ADR names the commit to recover it from.'],
49
49
  ['docs/session-logs/2026-08-28-goal-1000072-stale-timer-and-network-map-spike.md::scripts/bongos/spike-task-map.js',
50
50
  'a dated session log — the historical record, never rewritten — of the spike whose harness task 1004857 deleted; it describes what was run then, not what to run now.'],
51
+ ['docs/session-logs/2026-09-16-task-1003752-stranded-detector-never-ran.md::scripts/bongos/gate-review.js',
52
+ 'a dated session log — the historical record, never rewritten — naming the gate-review classifier task 1004859 deleted (its CI check never ran); it describes what was run then, not what to run now.'],
51
53
  ]);
52
54
 
53
55
  // `node scripts/bongos/foo.js` … up to the end of the line/segment, so the flags that
@@ -77,8 +77,8 @@ const SKIP_PREFIXES = ['docs/session-logs/', 'docs/audits/', 'limitations/'];
77
77
  const ALLOW = new Map([
78
78
  ['docs/adr/0025-structured-criterion-task-link.md::scripts/bongos/seed-v3-tasks.js',
79
79
  'describes the grep a session HAD to do before this ADR; a seed script, not a control'],
80
- ['docs/adr/0042-builder-self-deploy-ci-auto-merge.md::.github/workflows/grade-gate.yml',
81
- 'the CI grader gate this ADR designed; it was armed in the grader-off variant its own Status line records, and grading runs server-side (scripts/bongos/ci-grade.js)'],
80
+ ['docs/adr/0089-modular-multi-model-adversarial-grader.md::scripts/bongos/gate-review.js',
81
+ 'ADR 0089 section 8 names the ADR 0058 gatekeeper as a surface the grading engine does not own; task 1004859 deleted it (the CI check it drove never ran) and moved its hard-floor list to scripts/bongos/hard-floor.js'],
82
82
  ['docs/adr/0111-instance-hosting-provisioning-module.md::scripts/bongos/box.js',
83
83
  'the dev-box control-plane runner, retired with dev boxes by ADR 0346 (dev-box-guard.js keeps it out)'],
84
84
  ['docs/adr/0150-box-first-boot-bringup-vendored-instances.md::scripts/bongos/box.js',
@@ -37,8 +37,8 @@
37
37
  // an in-memory fake with the same row shapes and the same guards (one open sitting
38
38
  // per subject, one vote per member, single-winner close, the decision-rules write
39
39
  // only from a passed decision-rules amendment), and the few writes a passed matter
40
- // makes into other modules are recorded in memory instead of written to a database
41
- // (or, for a gate approval, sent to GitHub). The table says which mode ran.
40
+ // makes into other modules are recorded in memory instead of written to a database.
41
+ // The table says which mode ran.
42
42
  //
43
43
  // Usage:
44
44
  // node scripts/bongos/government-walk.js run it and print the table
@@ -102,7 +102,6 @@ const REQUESTS = Object.freeze({
102
102
  'POST /autonomy/builders/:builderId/pause': ['POST', `/autonomy/builders/${BAD}/pause`, { paused: false }],
103
103
  'POST /done-when/:criterionId/satisfy': ['POST', `/done-when/${BAD}/satisfy`, {}],
104
104
  'POST /done-when/:criterionId/unsatisfy': ['POST', `/done-when/${BAD}/unsatisfy`, {}],
105
- 'POST /gate-approvals/:pr/approve': ['POST', `/gate-approvals/${BAD}/approve`, {}],
106
105
  'POST /override-requests/:id/decide': ['POST', `/override-requests/${BAD}/decide`, { decision: 'approve' }],
107
106
  'POST /provisioning/instances/:id/core-upgrade': ['POST', `/provisioning/instances/${BAD}/core-upgrade`, { to: '1.2.3' }],
108
107
  'POST /provisioning/instances/:id/update-channel': ['POST', `/provisioning/instances/${BAD}/update-channel`, { channel: 'nightly' }],
@@ -122,7 +121,6 @@ const MATTER_PLAN = Object.freeze([
122
121
  ['automation', 'Switch unattended building off', { type: 'switch_off' }, 'fence off'],
123
122
  ['automation', 'Allow goal 77 to build unattended', { type: 'allow_goal', goal_id: 77 }, 'goal 77 allowed'],
124
123
  ['release', 'Approve override request 5', { type: 'approve_override', request_id: 5 }, 'request 5 approved'],
125
- ['release', 'Clear the gate on PR 1500', { type: 'approve_gate', pr: 1500 }, 'PR 1500 cleared'],
126
124
  ['review', 'Sign off criterion 9', { type: 'satisfy_criterion', criterion_id: 9 }, 'criterion 9 satisfied'],
127
125
  ['treasury', 'Dee gets $50 a month', { type: 'set_budget', builder_id: 45, monthly_usd: 50 }, 'Dee\'s limit is $50'],
128
126
  ]);
@@ -135,7 +133,6 @@ const ROUTERS = Object.freeze([
135
133
  // no HTTP surface; it contributes the Release and Review matter acts, as at boot
136
134
  'modules/lifecycle/routes/lifecycle.js',
137
135
  'modules/lifecycle/routes/done-when.js',
138
- 'modules/lifecycle/routes/gate-approvals.js',
139
136
  'modules/lifecycle/routes/tasks.js',
140
137
  'modules/lifecycle/routes/goals.js',
141
138
  'modules/lifecycle/routes/versions.js',
@@ -618,7 +615,6 @@ async function runWalk() {
618
615
  const lifeDb = req('modules/lifecycle/db.js');
619
616
  const doneWhen = req('modules/lifecycle/done-when.js');
620
617
  const doneWhenRoutes = req('modules/lifecycle/routes/done-when.js');
621
- const gateRoutes = req('modules/lifecycle/routes/gate-approvals.js');
622
618
  const express = require('express');
623
619
  const http = require('http');
624
620
 
@@ -634,11 +630,10 @@ async function runWalk() {
634
630
  projectSettings.setSetting = async (k, v) => { settings.set(k, String(v)); return { key: k, value: String(v) }; };
635
631
 
636
632
  // What passed matters write into other modules, recorded.
637
- const effects = { fence: null, allowedGoals: [], overrides: [], gates: [], criteria: new Map([['9', false]]), budgets: new Map() };
633
+ const effects = { fence: null, allowedGoals: [], overrides: [], criteria: new Map([['9', false]]), budgets: new Map() };
638
634
  autoDb.setFenceEnabled = async ({ enabled }) => { effects.fence = enabled; return { enabled }; };
639
635
  autoDb.allowGoal = async ({ goalId }) => { effects.allowedGoals.push(String(goalId)); return { goal_id: goalId }; };
640
636
  lifeDb.decideOverrideRequest = async ({ requestId, decision }) => { effects.overrides.push([String(requestId), decision]); return {}; };
641
- gateRoutes.approveGatePr = async ({ number }) => { effects.gates.push(String(number)); return { ok: true, body: {} }; };
642
637
  doneWhen.getCriterion = async (id) => (effects.criteria.has(String(id)) ? { id, satisfied: effects.criteria.get(String(id)) } : null);
643
638
  doneWhenRoutes.satisfyByOverride = async ({ criterionId }) => { effects.criteria.set(String(criterionId), true); return { updated: {}, goalResult: {} }; };
644
639
 
@@ -971,7 +966,6 @@ async function runWalk() {
971
966
  'automation.switch_off': () => effects.fence === false,
972
967
  'automation.allow_goal': () => effects.allowedGoals.includes('77'),
973
968
  'release.approve_override': () => effects.overrides.some(([id, d]) => id === '5' && d === 'approve'),
974
- 'release.approve_gate': () => effects.gates.includes('1500'),
975
969
  'review.satisfy_criterion': () => effects.criteria.get('9') === true,
976
970
  'treasury.set_budget': () => effects.budgets.get('45') === 50,
977
971
  };
@@ -0,0 +1,52 @@
1
+ 'use strict';
2
+
3
+ // scripts/bongos/hard-floor.js — the trust-boundary HARD FLOOR: the repo paths that,
4
+ // if a compromised or injected senior session changed them, would disable the safety
5
+ // system itself or the deploy switch. A subset of PROTECTED_GLOBS (the protected-surface
6
+ // registry, modules/government/protected-surfaces.json); tests/government_protected_surfaces.mjs
7
+ // holds the subset honest.
8
+ //
9
+ // WHERE IT CAME FROM. This list lived in scripts/bongos/gate-review.js, the ADR 0058
10
+ // classifier a planned gate-review CI check was meant to run. That check never ran on
11
+ // main, and task 1004859 removed the classifier with the hall Gate page that waited on
12
+ // it. Two live readers still need the list, so it moved here unchanged (minus the two
13
+ // deleted drivers that named themselves):
14
+ // - scripts/bongos/publish-manifest.js — what the public mirror must never carry;
15
+ // - scripts/bongos/adopt-preflight.js — whether an adopted repo lands on the floor.
16
+ //
17
+ // Imported, never re-typed: a consumer that needs the floor requires this file.
18
+ // Kept as globs so the matcher is the one permission-path-check builds (trailing '/' =
19
+ // subtree, case- and NFC-folded — task 1002874: a byte-exact matcher let an uppercased
20
+ // spelling of a floor surface walk past).
21
+
22
+ const { compileGlobMatcher } = require('../../src/bongos/permission-path-check');
23
+
24
+ const HARD_FLOOR_GLOBS = [
25
+ 'scripts/bongos/hard-floor.js', // self
26
+ 'src/bongos/permission-path-check.js', // defines the protected set
27
+ 'src/bongos/route-rank-check.js', // the route-gate auditor
28
+ 'src/bongos/auth.js', // requireRank / requireBuilder — the enforcement point
29
+ 'modules/grading/grader.js', // BV1.R73: grading carved to modules/grading/
30
+ 'modules/grading/grader-workers/',
31
+ 'modules/grading/grader-rubric.json',
32
+ 'scripts/bongos/grade-smoke.js', // grade-smoke driver
33
+ 'scripts/bongos/conductor-dispatch.js', // mid-flight grader dispatch
34
+ 'scripts/bongos/main-audit.js', // the authoritative deploy-time audit (the backstop)
35
+ 'scripts/bongos/push-main.js', // the sanctioned main-push carrying the override
36
+ 'scripts/bongos/run-unit-tests.js', // the unit-gate test selector
37
+ 'scripts/bongos/install-git-hooks.js',
38
+ 'scripts/bongos/hooks/',
39
+ 'scripts/migrate.sh',
40
+ 'scripts/deploy/',
41
+ 'config/deploy.json', // the laptop/ci/bypass deploy switch
42
+ 'infra/', // branch-protection.json + deploy substrate
43
+ '.github/', // CI workflows: the unit gate, secret scan, publish
44
+ 'CODEOWNERS',
45
+ '.husky/', // pre-push/pre-commit gate delivery
46
+ '.claude/hooks/',
47
+ '.claude/settings.json',
48
+ ];
49
+
50
+ const matchesHardFloor = compileGlobMatcher(HARD_FLOOR_GLOBS);
51
+
52
+ module.exports = { HARD_FLOOR_GLOBS, matchesHardFloor };
@@ -16,7 +16,7 @@
16
16
  // that falls inside an allowlisted subtree is dropped if the denylist forbids it.
17
17
  // isPublishable(p) == matchesDeny(p) ? false : matchesAllow(p)
18
18
  //
19
- // REUSE, NEVER RE-TYPE (the task's explicit instruction; the gate-review.js doctrine):
19
+ // REUSE, NEVER RE-TYPE (the task's explicit instruction; the hard-floor.js doctrine):
20
20
  // The three canonical sensitive-surface inventories are IMPORTED, never copied —
21
21
  // PROTECTED_GLOBS (write-protection over trust/pipeline/infra code),
22
22
  // HARD_FLOOR_GLOBS (the rank-independent escalate subset), and
@@ -28,10 +28,10 @@
28
28
  // cross-checks the partition against the live inventory so it cannot silently drift.
29
29
 
30
30
  // The three canonical sensitive-surface inventories — imported from their homes,
31
- // NEVER re-typed (the gate-review.js "imported, never hand-retyped" doctrine). All
31
+ // NEVER re-typed (the "imported, never hand-retyped" doctrine). All
32
32
  // three are pure to require (no DB/network at load; their CLIs are require.main-guarded).
33
33
  const { PROTECTED_GLOBS, globToRegExp } = require('../../src/bongos/permission-path-check');
34
- const { HARD_FLOOR_GLOBS } = require('./gate-review');
34
+ const { HARD_FLOOR_GLOBS } = require('./hard-floor');
35
35
  const { SENSITIVE_DOC_GLOBS } = require('./search-ingest-md');
36
36
 
37
37
  // ---------------------------------------------------------------------------
@@ -258,6 +258,14 @@ const INTEGRATION = new Set([
258
258
  // branch over a fake table; only a real engine runs the one-shot DO block and
259
259
  // shows a legacy account published again and marked apart from a sign-in.
260
260
  'terms_grandfather_db',
261
+ // task 1004863: three database halves that no lane ran. page_notes_db (the page
262
+ // feedback doc schema rules; modules/page-notes is live and on by default) self-skipped
263
+ // in the unit lane and was in no set. howto_state_db and search_isolation_db are the
264
+ // database halves split out of howto_state and search_isolation, whose DB-free halves
265
+ // stay in the unit lane: the first runs the how-to read contract on the real schema,
266
+ // the second proves the search index never widens a caller past their rank or into
267
+ // another builder memory. Each skipped in CI before, so none had ever been gated.
268
+ 'page_notes_db', 'howto_state_db', 'search_isolation_db',
261
269
  ]);
262
270
 
263
271
  // Excluded so this gate is trustworthy in a CLEAN checkout (the CI condition):
@@ -448,6 +456,15 @@ function discoverModuleTests({ modulesDir = MODULES_DIR, isEnabled = isModuleEna
448
456
  // needed terms_accepted_at stamped: the public read requires both consent columns since
449
457
  // ADR 0213. If one of them reds again, check the module flag is still set first.
450
458
  //
459
+ // FIVE LEFT THIS LIST under task 1004863, each repaired against a freshly migrated
460
+ // database: copy_desk_flags_db, government_board_membership_db (builders.github_id is
461
+ // NOT NULL now; their rows go through insertTestBuilders in tests/helpers.mjs, and the
462
+ // stand-in tables that hid the drift are gone), idea_credit_streams_db and
463
+ // session_earnings_mirror_db (fixture rows named a version status and a task discipline
464
+ // the CHECK constraints no longer admit), and acclaim_feed_evidence_db, which now calls
465
+ // the real feed route handler instead of a sliced copy of its SQL and owns a building
466
+ // version rather than colliding with the seeded planning one (the oscillation above).
467
+ //
451
468
  // consumer_layout_boot LEFT THIS LIST under task 1002863. Its "missing core tarball" was
452
469
  // package-core's fail-closed leak gate reading the lane's throwaway database password (the
453
470
  // word postgres, present in hundreds of core files) as a leaked credential and refusing to
@@ -464,11 +481,6 @@ function discoverModuleTests({ modulesDir = MODULES_DIR, isEnabled = isModuleEna
464
481
  // with no blocker_ref, and db.shipTask called without the land assurance task 1002483
465
482
  // made mandatory. Each passes alone on a freshly migrated database.
466
483
  const INTEGRATION_PARKED = new Set([
467
- 'acclaim_feed_evidence_db',
468
- 'copy_desk_flags_db',
469
- 'government_board_membership_db',
470
- 'idea_credit_streams_db',
471
- 'session_earnings_mirror_db',
472
484
  ]);
473
485
 
474
486
  function main() {
@@ -0,0 +1,6 @@
1
+ // scripts/gds/hard-floor.js — @bongos-forwarder (task 1004848). Generated by
2
+ // scripts/bongos/codemod-rename-scripts-gds.js; do not edit.
3
+ // The real script is scripts/bongos/hard-floor.js. This forwarder keeps callers that still name
4
+ // the old path working, and warns once so they get repointed. Task 1003706 removes it.
5
+ 'use strict';
6
+ require('../bongos/forwarder.js').forward(module, 'hard-floor.js');
@@ -93,7 +93,7 @@ const MODULE_GLOBS = {
93
93
  ],
94
94
  // --- the build state machine + ship/grade/deploy orchestration + schema ---
95
95
  // Carved into modules/lifecycle/ (BV1.R86 / ADR 0093 §1,§3): the task/claim/
96
- // version/goal/done-when/dependency/gate-approvals routes + the db slice, the
96
+ // version/goal/done-when/dependency routes + the db slice, the
97
97
  // task-classifier + hierarchy + done-when libs, github-push + ship-card +
98
98
  // ship-preflight + merge-lock + conflict-resolve + publish-reconciler +
99
99
  // version-close, and the `lifecycle` kernel PORT — all under the
@@ -115,7 +115,7 @@ const MODULE_GLOBS = {
115
115
  'scripts/bongos/ship-regen.js', 'scripts/bongos/ship-resume.js', 'scripts/bongos/ship-session-upload.js',
116
116
  'scripts/bongos/ship-state.js', 'scripts/bongos/ship-surprise.js', 'scripts/bongos/ship-visual.js',
117
117
  'scripts/bongos/merge-mode.js', 'scripts/bongos/confirm.js', 'scripts/bongos/main-audit.js',
118
- 'scripts/bongos/gate-review.js', 'scripts/bongos/push-main.js', 'scripts/bongos/ci-grade.js',
118
+ 'scripts/bongos/hard-floor.js', 'scripts/bongos/push-main.js',
119
119
  'scripts/bongos/merge-lock.js', 'scripts/bongos/version-close.js',
120
120
  'migrations/',
121
121
 
@@ -43,7 +43,7 @@ const RANK_TIER = { xenos: 1, thetes: 2, metic: 3, archon: 4 };
43
43
  // modules/government/protected-surfaces.json — no longer a constant baked into
44
44
  // this file. Re-mapping a floor there is a POLICY edit. It cannot move
45
45
  // enforcement into the app: the three layers above (git pre-push hook, grader
46
- // pre-pass, post-push main audit) plus gate-review.js and the goal-scope check
46
+ // pre-pass, post-push main audit) plus the goal-scope check
47
47
  // read the registry out of band — no DB, no server, no HTTP — and remain the
48
48
  // authority (ADR 0043). Making the request path the enforcer is the exact
49
49
  // regression this task exists to prevent.
@@ -220,7 +220,7 @@ const EXPECTED_RANKS = {
220
220
  'GET /builders/directory': 'any-builder',
221
221
  // (the lifecycle fleet pins — task authoring/promotion/abandonment, override
222
222
  // adjudication, polymorphic dependency edges, goal tier + ask-to-widen,
223
- // criterion confirmation, and gate-approvals — moved to MODULE_EXPECTED_RANKS
223
+ // and criterion confirmation — moved to MODULE_EXPECTED_RANKS
224
224
  // ['lifecycle'] below in BV1.R86. They live in the lifecycle module now
225
225
  // (modules/lifecycle/routes/…), so they're verified only when that module is in
226
226
  // scope, not unconditionally from this core list — the pins follow the routes.)
@@ -500,13 +500,6 @@ const MODULE_EXPECTED_RANKS = {
500
500
  'POST /done-when/:criterionId/uat/recording': 'perm:criterion.review',
501
501
  'POST /done-when/:criterionId/uat': 'perm:criterion.review',
502
502
  'PUT /done-when/:criterionId/backend-only': 'perm:criterion.create',
503
- // gate-surface PR approval from the hall (task 1179, ADR 0058 amendment). ADR 0157
504
- // SPLIT these two: the list route is `gate_approval.read` (metic — the Gate page
505
- // opens to the working rank), while APPROVING still clears the gate-review hard
506
- // floor and stays `gate_approval.decide` (archon, system). The split is the reason
507
- // the page is usable at all — see the atom's note in catalog.js.
508
- 'GET /gate-approvals': 'perm:gate_approval.read',
509
- 'POST /gate-approvals/:pr/approve': 'perm:gate_approval.decide',
510
503
  },
511
504
  // Ratifying a criterion proposal WRITES scope-truth (it creates a done-when
512
505
  // criterion). Metic+ may file the proposal; ADR 0157 also lets Metic ratify it
@@ -31,7 +31,7 @@ const buildMySessionsRouter = require('./routes/my-sessions');
31
31
  const buildMyPasskeysRouter = require('./routes/my-passkeys');
32
32
  const buildBuildersRouter = require('./routes/builders');
33
33
  // The LIFECYCLE routes (tasks · claims · versions · done-when · goals ·
34
- // dependencies · gate-approvals · analytics) carved into modules/lifecycle/
34
+ // dependencies · analytics) carved into modules/lifecycle/
35
35
  // (BV1.R86 / ADR 0093 §1,§3); moduleLoader.mountModuleRoutes (below) mounts them
36
36
  // via the lifecycle module's route factories — no core require. The `/tasks/:id/
37
37
  // publish-branch` body-parser bypass below STAYS (the route carries its own larger
@@ -187,7 +187,6 @@ function buildGdsRouter() {
187
187
  // and discord (with its bug-attachments route) have both graduated
188
188
  // into real modules (modules/<key>/) — they are discovered + mounted by
189
189
  // moduleLoader.mountModuleRoutes below, not here (BV1.R43 / R48 / R47 / R72).
190
- // gate-approvals likewise moved to the lifecycle module (BV1.R86).
191
190
  router.use(buildLlmCacheRouter());
192
191
 
193
192
  // Loader-mounted module routers (ADR 0083 / BV1.R41). Discovers + mounts every
package/src/module-api.js CHANGED
@@ -75,7 +75,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
75
75
  // MAJOR (see allowBoxScope below): passes the request through untouched.
76
76
  function deprecatedNoopMiddleware(_req, _res, next) { next(); }
77
77
 
78
- const CORE_VERSION = '1.21.177'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
78
+ const CORE_VERSION = '1.21.178'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
79
79
 
80
80
  // A namespaced logger so a module's log lines are attributable + consistent.
81
81
  // Usage: const log = api.logger('discord'); log.info('mounted');
package/src/modules.js CHANGED
@@ -44,7 +44,7 @@ const INSTANCE_PATH = path.join(resolveInstanceRoot(), 'config', 'modules.json')
44
44
  //
45
45
  // CORE (always on, never a module): auth, builders, tasks, claims, versions,
46
46
  // done-when, credits/cost, inbox, blockers, leaderboard, analytics,
47
- // audit-log, security, access-requests, sessions, gate-approvals, healthz,
47
+ // audit-log, security, access-requests, sessions, healthz,
48
48
  // public. These mount unconditionally. The optional feature modules AND the
49
49
  // first TWO carved CORE-domain modules have graduated to modules/<key>/ (discord
50
50
  // BV1.R47, art-pipeline BV1.R48, game BV1.R49, memory BV1.R72 —
@@ -16,21 +16,22 @@
16
16
  // - a ship with several cost rows must sum them, not pick one;
17
17
  // - the two LATERALs and the grades join must not multiply the row out.
18
18
  //
19
- // Runs the route's own query text — sliced out of the shipped source, never
20
- // retyped — so a future edit to the route is tested rather than a copy of it.
19
+ // Calls the REAL route handler (GET /tasks/peer-votes/feed from the lifecycle
20
+ // router) against the database, so a future edit to the route is tested rather
21
+ // than a copy of it. It used to slice the query text out of the route's source and
22
+ // re-interpolate it by hand; the route grew a second interpolation (the helper
23
+ // join), the copy stopped resolving, and the test failed on its own scaffolding
24
+ // rather than on the route (task 1004863). Driving the handler cannot drift that way.
21
25
  // Self-skips without a reachable Postgres; in the INTEGRATION set, so the
22
26
  // DB-free unit gate never runs it and .github/workflows/integration.yml does.
23
27
  //
24
28
  // Run: DATABASE_URL=... node tests/acclaim_feed_evidence_db.mjs
25
29
  import { createRequire } from 'node:module';
26
30
  import { strict as assert } from 'node:assert';
27
- import fs from 'node:fs';
28
- import path from 'node:path';
29
- import { fileURLToPath } from 'node:url';
31
+ import { insertTestBuilders } from './helpers.mjs';
30
32
 
31
33
  const require = createRequire(import.meta.url);
32
34
  const { Pool } = require('pg');
33
- const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
34
35
 
35
36
  const pool = new Pool({ connectionString: process.env.DATABASE_URL || undefined });
36
37
  try {
@@ -41,18 +42,30 @@ try {
41
42
  process.exit(0);
42
43
  }
43
44
 
44
- // The query under test, taken from the shipped route. gradeQualityWhere is
45
- // interpolated by the route at call time, so interpolate it the same way.
46
- const { gradeQualityWhere } = require('../modules/lifecycle/db-shared.js');
47
- const routeSrc = fs.readFileSync(path.join(ROOT, 'modules/lifecycle/routes/tasks.js'), 'utf8');
48
- const start = routeSrc.indexOf('`SELECT t.id, t.title, t.value_summary, t.version_id, t.status,');
49
- assert.ok(start > 0, 'the peer-votes feed query is still where this test slices it from');
50
- const end = routeSrc.indexOf('LIMIT $2`', start);
51
- assert.ok(end > start, 'the feed query still ends at its LIMIT');
52
- const SQL = routeSrc
53
- .slice(start + 1, end + 'LIMIT $2'.length)
54
- .replace(/\$\{gradeQualityWhere\('g'\)\}/g, gradeQualityWhere('g'));
55
- assert.ok(!SQL.includes('${'), 'every interpolation in the sliced query was resolved');
45
+ // The handler under test: the last stack entry of the shipped route (the first is
46
+ // auth.requireBuilder, which needs a session; the handler reads req.builder, which
47
+ // we supply). The router's pool is the core pool, which reads DATABASE_URL — the
48
+ // same database this file writes its fixtures to.
49
+ const buildTasksRouter = require('../modules/lifecycle/routes/tasks.js');
50
+ const feedLayer = buildTasksRouter().stack
51
+ .find((l) => l.route && l.route.path === '/tasks/peer-votes/feed' && l.route.methods.get);
52
+ assert.ok(feedLayer, 'the lifecycle router still serves GET /tasks/peer-votes/feed');
53
+ const feedHandler = feedLayer.route.stack[feedLayer.route.stack.length - 1].handle;
54
+
55
+ // Call the route as `builder` and return its JSON body; a failure surfaces as a throw.
56
+ async function feed(builder, limit = 100) {
57
+ let body = null;
58
+ let failure = null;
59
+ const res = {
60
+ json(b) { body = b; return res; },
61
+ fail(code, opts) { failure = { code, opts }; return res; },
62
+ status() { return res; },
63
+ };
64
+ await feedHandler({ builder, query: { limit: String(limit) }, params: {}, headers: {}, get: () => undefined }, res);
65
+ if (failure) throw new Error(`the feed route failed: ${JSON.stringify(failure)}`);
66
+ assert.ok(body && Array.isArray(body.tasks), 'the feed answered with a task list');
67
+ return body;
68
+ }
56
69
 
57
70
  const VOTER = 90000801;
58
71
  const SHIPPER = 90000802;
@@ -82,10 +95,13 @@ const check = (name, fn) => {
82
95
  try {
83
96
  await cleanup();
84
97
 
85
- await q("INSERT INTO versions (id, name, status) VALUES ($1, 'acclaim evidence fixture', 'planning')", [VER]);
86
- for (const [id, login] of [[VOTER, 'zz-acc-voter'], [SHIPPER, 'zz-acc-shipper']]) {
87
- await q("INSERT INTO builders (id, github_login, display_name, rank) VALUES ($1, $2, $2, 'metic')", [id, login]);
88
- }
98
+ // 'building', not 'planning': versions_one_planning_idx allows ONE planning row and
99
+ // the seeds own it — the collision that parked this test (task 1004042).
100
+ await q("INSERT INTO versions (id, name, status) VALUES ($1, 'acclaim evidence fixture', 'building')", [VER]);
101
+ await insertTestBuilders(pool, [
102
+ { id: VOTER, login: 'zz-acc-voter', rank: 'metic' },
103
+ { id: SHIPPER, login: 'zz-acc-shipper', rank: 'metic' },
104
+ ], { onConflict: '' });
89
105
  for (const [id, title] of [
90
106
  [T.GOOD, '__acc__ graded and costed'],
91
107
  [T.OUTAGE, '__acc__ grader outage'],
@@ -123,7 +139,10 @@ try {
123
139
  await cost(T.FREE, '0');
124
140
  // T.OUTAGE and T.UNGRADED get no cost row at all.
125
141
 
126
- const rows = await q(SQL, [VOTER, 100]);
142
+ // A metic caller: cost reaches only the ranks that can vote, so this is the view
143
+ // that carries every figure under test.
144
+ const body = await feed({ id: VOTER, rank: 'metic' });
145
+ const rows = body.tasks;
127
146
  const byId = new Map(rows.map((r) => [String(r.id), r]));
128
147
  const got = (id) => {
129
148
  const row = byId.get(String(id));
@@ -28,9 +28,11 @@ const adr = read('docs/adr/0089-modular-multi-model-adversarial-grader.md');
28
28
  const section = adr.slice(adr.indexOf('### 8. Scope: what the engine does NOT own'), adr.indexOf('## Consequences'))
29
29
  .replaceAll('scripts/gds/', 'scripts/bongos/'); // rename-keep
30
30
 
31
+ // §8 also names scripts/bongos/gate-review.js. Task 1004859 deleted it (the CI check it
32
+ // drove never ran), so there is nothing left to hold to the boundary; the ADR keeps the
33
+ // name as history.
31
34
  const SURFACES = [
32
35
  'scripts/bongos/session-eval.js',
33
- 'scripts/bongos/gate-review.js',
34
36
  'scripts/bongos/main-audit.js',
35
37
  'scripts/bongos/architect-audit.js',
36
38
  ];
@@ -40,7 +42,7 @@ test('ADR 0089 carries the §8 scope section, before Consequences', () => {
40
42
  assert.ok(section.length > 200, 'the section sits between §7 and Consequences');
41
43
  });
42
44
 
43
- test('§8 names each of the four surfaces by path, and each path exists', () => {
45
+ test('§8 names each of the remaining surfaces by path, and each path exists', () => {
44
46
  for (const p of SURFACES) {
45
47
  assert.ok(section.includes(p), `§8 names ${p}`);
46
48
  assert.ok(fs.existsSync(new URL(`../${p}`, import.meta.url)), `${p} exists`);
@@ -56,14 +58,14 @@ test('the Architect uses exactly the three grader helpers §8 names — nothing
56
58
  for (const name of used) assert.ok(section.includes(name), `§8 names ${name}`);
57
59
  });
58
60
 
59
- test('session-eval, gate-review and main-audit import no grading module', () => {
61
+ test('session-eval and main-audit import no grading module', () => {
60
62
  for (const p of SURFACES.filter((s) => !s.endsWith('architect-audit.js'))) {
61
63
  assert.doesNotMatch(read(p), /require\([^)]*modules\/grading/, `${p} must not require grading code`);
62
64
  }
63
65
  });
64
66
 
65
- test('main-audit and gate-review share only the rank-permission checker §8 names', () => {
66
- for (const p of ['scripts/bongos/main-audit.js', 'scripts/bongos/gate-review.js']) {
67
+ test('main-audit reads the rank-permission checker §8 names', () => {
68
+ for (const p of ['scripts/bongos/main-audit.js']) {
67
69
  assert.match(read(p), /permission-path-check/, `${p} reads the checker`);
68
70
  }
69
71
  assert.ok(section.includes('src/bongos/permission-path-check.js'), '§8 names the shared checker');