@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.
Files changed (214) hide show
  1. package/.bongos-core.json +186 -496
  2. package/.claude/skills/planning-session/SKILL.md +2 -2
  3. package/clients/bongos-client/README.md +1 -1
  4. package/clients/bongos-client/bongos-client.global.js +0 -6
  5. package/clients/bongos-client/index.cjs +0 -6
  6. package/clients/bongos-client/index.d.ts +0 -6
  7. package/clients/bongos-client/index.mjs +0 -6
  8. package/config/scheduled-routines.json +3 -8
  9. package/docs/adr/0042-builder-self-deploy-ci-auto-merge.md +3 -2
  10. package/docs/adr/0091-bounding-the-kernel-and-db-carve.md +1 -1
  11. package/docs/adr/0093-tranche-2-core-carve-sequence.md +1 -1
  12. package/docs/api/openapi.json +1 -87
  13. package/docs/api-reference.md +1 -8
  14. package/docs/canonical-permissions.md +4 -4
  15. package/docs/copy-inventory.md +25 -39
  16. package/docs/copy-registry.json +73 -200
  17. package/docs/file-map.md +6 -11
  18. package/docs/handoff-template.md +1 -1
  19. package/docs/module-api-changelog.md +4 -0
  20. package/docs/modules-contract.md +2 -2
  21. package/docs/onboarding/diagrams/README.md +3 -7
  22. package/docs/onboarding/diagrams/assertions.json +3 -3
  23. package/docs/page-inventory.json +4 -7
  24. package/docs/page-readings.json +1520 -1569
  25. package/docs/recipes/ops-gotchas.md +3 -1
  26. package/modules/autonomy/db.js +9 -0
  27. package/modules/autonomy/module.json +2 -2
  28. package/modules/autonomy/pollers/daily-checks.js +262 -0
  29. package/modules/autonomy/pollers/routine-timers.js +7 -5
  30. package/modules/autonomy/rank-roles.js +64 -0
  31. package/modules/autonomy/server-schedule.js +37 -0
  32. package/modules/government/catalog.js +10 -7
  33. package/modules/government/decision-board.js +0 -1
  34. package/modules/government/decision-rules.js +0 -1
  35. package/modules/government/docket.js +0 -1
  36. package/modules/government/protected-surfaces.js +1 -1
  37. package/modules/government/protected-surfaces.json +2 -3
  38. package/modules/hall-ui/public/atlas.html +1 -1
  39. package/modules/hall-ui/public/blockers.html +1 -1
  40. package/modules/hall-ui/public/board-room.html +1 -1
  41. package/modules/hall-ui/public/city.html +1 -1
  42. package/modules/hall-ui/public/collab.html +1 -1
  43. package/modules/hall-ui/public/commands.html +1 -1
  44. package/modules/hall-ui/public/copy-desk.html +1 -1
  45. package/modules/hall-ui/public/deploy.html +1 -1
  46. package/modules/hall-ui/public/diagrams.html +1 -1
  47. package/modules/hall-ui/public/drachmae.html +1 -1
  48. package/modules/hall-ui/public/fleet.html +1 -1
  49. package/modules/hall-ui/public/gate.css +4 -26
  50. package/modules/hall-ui/public/gate.html +12 -27
  51. package/modules/hall-ui/public/gate.js +26 -176
  52. package/modules/hall-ui/public/gate.states.json +4 -22
  53. package/modules/hall-ui/public/goal-map.html +1 -1
  54. package/modules/hall-ui/public/goals.html +1 -1
  55. package/modules/hall-ui/public/government.html +1 -1
  56. package/modules/hall-ui/public/idea.html +1 -1
  57. package/modules/hall-ui/public/ideas.html +1 -1
  58. package/modules/hall-ui/public/index.html +1 -1
  59. package/modules/hall-ui/public/modules.html +1 -1
  60. package/modules/hall-ui/public/my-agents.html +1 -1
  61. package/modules/hall-ui/public/primer.html +1 -1
  62. package/modules/hall-ui/public/profile.html +1 -1
  63. package/modules/hall-ui/public/project-settings.html +1 -1
  64. package/modules/hall-ui/public/ranks.html +1 -1
  65. package/modules/hall-ui/public/roadmap.html +1 -1
  66. package/modules/hall-ui/public/roster.html +1 -1
  67. package/modules/hall-ui/public/sessions.html +1 -1
  68. package/modules/hall-ui/public/settings.html +1 -1
  69. package/modules/hall-ui/public/shell.js +1 -1
  70. package/modules/hall-ui/public/street-card.js +5 -9
  71. package/modules/hall-ui/public/street-desk.js +2 -2
  72. package/modules/hall-ui/public/street-inside.js +1 -1
  73. package/modules/hall-ui/public/street.html +1 -1
  74. package/modules/hall-ui/public/studio.html +1 -1
  75. package/modules/hall-ui/public/task.html +1 -1
  76. package/modules/hall-ui/public/thinking.html +1 -1
  77. package/modules/hall-ui/public/tweak-editor.html +1 -1
  78. package/modules/hall-ui/public/watch.html +1 -1
  79. package/modules/hall-ui/public/work.html +1 -1
  80. package/modules/lifecycle/matter-acts.js +0 -6
  81. package/modules/lifecycle/module.json +0 -1
  82. package/package-lock.json +2 -2
  83. package/package.json +1 -1
  84. package/release-notes.json +36 -0
  85. package/scripts/bongos/adopt-preflight.js +3 -3
  86. package/scripts/bongos/api-alias-caller-scan.js +2 -2
  87. package/scripts/bongos/audit-authorship.sh +1 -8
  88. package/scripts/bongos/audit-rank-roles.js +15 -56
  89. package/scripts/bongos/bump-version.js +46 -9
  90. package/scripts/bongos/codemod-rename-scripts-gds.js +0 -1
  91. package/scripts/bongos/diagram-facts.js +1 -1
  92. package/scripts/bongos/do-api.js +1 -1
  93. package/scripts/bongos/doc-cli-guard.js +2 -0
  94. package/scripts/bongos/doc-control-claims.js +2 -2
  95. package/scripts/bongos/fitness-checks-identity.js +4 -15
  96. package/scripts/bongos/gen-diagrams.js +3 -3
  97. package/scripts/bongos/government-walk.js +3 -9
  98. package/scripts/bongos/hard-floor.js +52 -0
  99. package/scripts/bongos/publish-manifest.js +3 -3
  100. package/scripts/bongos/pulse.js +5 -3
  101. package/scripts/bongos/routine-schedule.js +38 -3
  102. package/scripts/bongos/run-unit-tests.js +17 -5
  103. package/scripts/bongos/ship-regen.js +2 -3
  104. package/scripts/gds/{goal-names.js → hard-floor.js} +3 -4
  105. package/scripts/security/dep-audit.js +50 -124
  106. package/src/bongos/module-scope-map.js +2 -2
  107. package/src/bongos/permission-path-check.js +1 -1
  108. package/src/bongos/route-rank-check.js +1 -8
  109. package/src/bongos/routes.js +1 -2
  110. package/src/module-api.js +1 -1
  111. package/src/modules.js +1 -1
  112. package/tests/acclaim_feed_evidence_db.mjs +42 -23
  113. package/tests/adr_0089_scope.mjs +7 -5
  114. package/tests/autonomy_daily_checks.mjs +201 -0
  115. package/tests/bump_version.mjs +52 -18
  116. package/tests/claude_materialize.mjs +12 -12
  117. package/tests/copy_desk_flags_db.mjs +22 -14
  118. package/tests/effective_visibility_predicate.mjs +0 -18
  119. package/tests/gating_review_retired.mjs +1 -1
  120. package/tests/government_act_alone_buildings.mjs +1 -3
  121. package/tests/government_board_membership_db.mjs +35 -22
  122. package/tests/government_protected_surfaces.mjs +29 -13
  123. package/tests/governor_docket.mjs +0 -2
  124. package/tests/governor_matter_acts.mjs +6 -8
  125. package/tests/hall_city.mjs +1 -1
  126. package/tests/hall_matter_card.mjs +2 -2
  127. package/tests/hall_oversight_world.mjs +16 -12
  128. package/tests/hall_street_desk.mjs +4 -3
  129. package/tests/hall_street_inside.mjs +1 -1
  130. package/tests/helpers.mjs +34 -0
  131. package/tests/howto_state.mjs +33 -56
  132. package/tests/howto_state_db.mjs +68 -0
  133. package/tests/idea_credit_streams_db.mjs +11 -3
  134. package/tests/integration_parked_ratchet.mjs +7 -1
  135. package/tests/nav_permission_atoms.mjs +1 -1
  136. package/tests/page_notes_db.mjs +12 -8
  137. package/tests/page_tweak_no_rearm.mjs +50 -0
  138. package/tests/publish_manifest.mjs +64 -0
  139. package/tests/routine_capture_kind.mjs +18 -7
  140. package/tests/routine_schedule.mjs +42 -0
  141. package/tests/search_isolation.mjs +5 -103
  142. package/tests/search_isolation_db.mjs +140 -0
  143. package/tests/session_earnings_mirror_db.mjs +2 -0
  144. package/tests/workflow_exec_paths.mjs +9 -11
  145. package/modules/lifecycle/routes/gate-approvals.js +0 -299
  146. package/scripts/bongos/artifact-staleness.js +0 -267
  147. package/scripts/bongos/ci-grade.js +0 -82
  148. package/scripts/bongos/classify-discipline.js +0 -153
  149. package/scripts/bongos/codemod-rename-src-gds.js +0 -145
  150. package/scripts/bongos/coreB-carve-proof.sh +0 -725
  151. package/scripts/bongos/gate-review.js +0 -330
  152. package/scripts/bongos/goal-names.js +0 -102
  153. package/scripts/bongos/rename-goals.js +0 -109
  154. package/scripts/bongos/seed-adopt-tasks.js +0 -75
  155. package/scripts/bongos/seed-api-contract-goal-tasks.js +0 -141
  156. package/scripts/bongos/seed-board-room-tasks.js +0 -196
  157. package/scripts/bongos/seed-bongos-app-tasks.js +0 -294
  158. package/scripts/bongos/seed-bongos-coreB-tranche1-tasks.js +0 -81
  159. package/scripts/bongos/seed-bongos-coreB-tranche2-tasks.js +0 -100
  160. package/scripts/bongos/seed-bongos-goal-hierarchy-tasks.js +0 -307
  161. package/scripts/bongos/seed-bongos-module-system-tasks.js +0 -346
  162. package/scripts/bongos/seed-bongos-tasks.js +0 -214
  163. package/scripts/bongos/seed-bongos-v1-seo-tasks.js +0 -104
  164. package/scripts/bongos/seed-bv1-community-tasks.js +0 -131
  165. package/scripts/bongos/seed-bv1-privacy-invites-tasks.js +0 -133
  166. package/scripts/bongos/seed-bv1-profile-page-tasks.js +0 -161
  167. package/scripts/bongos/seed-bv1-r01-delta-tasks.js +0 -154
  168. package/scripts/bongos/seed-bv2-tweak-mode-tasks.js +0 -277
  169. package/scripts/bongos/seed-gds-v4-tasks.js +0 -624
  170. package/scripts/bongos/seed-governance-tasks.js +0 -340
  171. package/scripts/bongos/seed-medusa-tasks.js +0 -440
  172. package/scripts/bongos/seed-module-upstreaming.js +0 -219
  173. package/scripts/bongos/seed-productization-tasks.js +0 -235
  174. package/scripts/bongos/seed-project-admin-console-tasks.js +0 -88
  175. package/scripts/bongos/seed-provisioning-tasks.js +0 -83
  176. package/scripts/bongos/seed-strict-versioning-tasks.js +0 -275
  177. package/scripts/bongos/seed-v2-world-tasks.js +0 -283
  178. package/scripts/gds/artifact-staleness.js +0 -7
  179. package/scripts/gds/ci-grade.js +0 -7
  180. package/scripts/gds/classify-discipline.js +0 -7
  181. package/scripts/gds/codemod-rename-src-gds.js +0 -7
  182. package/scripts/gds/coreB-carve-proof.sh +0 -8
  183. package/scripts/gds/gate-review.js +0 -7
  184. package/scripts/gds/rename-goals.js +0 -7
  185. package/scripts/gds/seed-adopt-tasks.js +0 -7
  186. package/scripts/gds/seed-api-contract-goal-tasks.js +0 -7
  187. package/scripts/gds/seed-board-room-tasks.js +0 -6
  188. package/scripts/gds/seed-bongos-app-tasks.js +0 -7
  189. package/scripts/gds/seed-bongos-coreB-tranche1-tasks.js +0 -7
  190. package/scripts/gds/seed-bongos-coreB-tranche2-tasks.js +0 -7
  191. package/scripts/gds/seed-bongos-goal-hierarchy-tasks.js +0 -7
  192. package/scripts/gds/seed-bongos-module-system-tasks.js +0 -7
  193. package/scripts/gds/seed-bongos-tasks.js +0 -7
  194. package/scripts/gds/seed-bongos-v1-seo-tasks.js +0 -6
  195. package/scripts/gds/seed-bv1-community-tasks.js +0 -6
  196. package/scripts/gds/seed-bv1-privacy-invites-tasks.js +0 -6
  197. package/scripts/gds/seed-bv1-profile-page-tasks.js +0 -6
  198. package/scripts/gds/seed-bv1-r01-delta-tasks.js +0 -6
  199. package/scripts/gds/seed-bv2-tweak-mode-tasks.js +0 -6
  200. package/scripts/gds/seed-gds-v4-tasks.js +0 -7
  201. package/scripts/gds/seed-governance-tasks.js +0 -7
  202. package/scripts/gds/seed-medusa-tasks.js +0 -7
  203. package/scripts/gds/seed-module-upstreaming.js +0 -7
  204. package/scripts/gds/seed-productization-tasks.js +0 -7
  205. package/scripts/gds/seed-project-admin-console-tasks.js +0 -6
  206. package/scripts/gds/seed-provisioning-tasks.js +0 -7
  207. package/scripts/gds/seed-strict-versioning-tasks.js +0 -7
  208. package/scripts/gds/seed-v2-world-tasks.js +0 -7
  209. package/scripts/migrate_cost_ledger.sh +0 -178
  210. package/tests/artifact_staleness.mjs +0 -207
  211. package/tests/gate_approvals.mjs +0 -611
  212. package/tests/gate_review.mjs +0 -152
  213. package/tests/goal_names.mjs +0 -120
  214. package/tests/newcomer_generator.mjs +0 -48
@@ -10,6 +10,7 @@ import { strict as assert } from 'node:assert';
10
10
  import fs from 'node:fs';
11
11
  import path from 'node:path';
12
12
  import url from 'node:url';
13
+ import { insertTestBuilders } from './helpers.mjs';
13
14
 
14
15
  const require = createRequire(import.meta.url);
15
16
  const { Pool } = require('pg');
@@ -42,15 +43,15 @@ async function refuses(sql, params, constraint) {
42
43
  throw new Error(`expected ${constraint || 'a refusal'}, but the write landed`);
43
44
  }
44
45
 
45
- await pool.query(`
46
- CREATE TABLE IF NOT EXISTS schema_migrations (version text PRIMARY KEY);
47
- CREATE TABLE IF NOT EXISTS builders (id bigserial PRIMARY KEY, github_login text, display_name text);
48
- CREATE TABLE IF NOT EXISTS idea_inbox (id bigserial PRIMARY KEY, title text, status text);
49
- `);
46
+ // Needs a MIGRATED database (the integration lane's). The stand-in builders /
47
+ // idea_inbox tables this once created drifted from the schema (builders.github_id
48
+ // became NOT NULL) and are gone (task 1004863).
50
49
  const A = 910000001;
51
50
  const B = 910000002;
52
- await pool.query('INSERT INTO builders (id, github_login, display_name) VALUES ($1,$2,$3),($4,$5,$6) ON CONFLICT (id) DO NOTHING',
53
- [A, 'pntest-a', 'Page Notes Test A', B, 'pntest-b', '']);
51
+ await insertTestBuilders(pool, [
52
+ { id: A, login: 'pntest-a', display_name: 'Page Notes Test A' },
53
+ { id: B, login: 'pntest-b', display_name: '' },
54
+ ]);
54
55
 
55
56
  const MIGRATION = fs.readFileSync(path.join(ROOT, 'modules/page-notes/migrations/page_notes_001_notes.sql'), 'utf8');
56
57
  await pool.query(MIGRATION);
@@ -137,7 +138,6 @@ await test('deleting a doc takes its images with it', async () => {
137
138
  });
138
139
 
139
140
  await clean();
140
- await pool.query('DELETE FROM builders WHERE id = ANY($1::bigint[])', [[A, B]]).catch(() => {});
141
141
  await test('THE SITE LIST (task 1004499): one surface, submitted only, newest first, read through its own index', async () => {
142
142
  await pool.query(MIGRATION_2);
143
143
  const mk = async (author, page, at) => {
@@ -162,6 +162,10 @@ await test('THE SITE LIST (task 1004499): one surface, submitted only, newest fi
162
162
  await clean();
163
163
  });
164
164
 
165
+ // The builders go LAST (task 1004863): the site-list case above writes notes authored
166
+ // by A and B, and page_notes_docs.author_id is a foreign key into builders.
167
+ await clean();
168
+ await pool.query('DELETE FROM builders WHERE id = ANY($1::bigint[])', [[A, B]]).catch(() => {});
165
169
  await pool.end();
166
170
  console.log(`page_notes_db: ${passed} passed, ${failed} failed`);
167
171
  process.exit(failed ? 1 : 0);
@@ -0,0 +1,50 @@
1
+ // tests/page_tweak_no_rearm.mjs — a page tweak held for its artist is never
2
+ // re-armed for auto-merge from outside github-push (task 1004859).
3
+ //
4
+ // WHY. ADR 0341 D7 holds a page tweak until its artist signs it off. The one
5
+ // caller that could re-arm auto-merge on an open PR was the hall's gate-approval
6
+ // route (POST /gate-approvals/:pr/approve → recheckPullRequest), and
7
+ // tests/gate_approvals.mjs "layer 3" pinned that it passed { arm: false } for a
8
+ // held tweak. Task 1004859 deleted that route with the dead held-PR list. The
9
+ // guarantee now rests on there being NO other caller, so this pins that: a
10
+ // new caller of recheckPullRequest must come with its own held-tweak check
11
+ // and its own test, and this file is where it gets noticed.
12
+ //
13
+ // DB-free, no network. Run: node --test tests/page_tweak_no_rearm.mjs
14
+
15
+ import { strict as assert } from 'node:assert';
16
+ import { test } from 'node:test';
17
+ import { execFileSync } from 'node:child_process';
18
+ import fs from 'node:fs';
19
+ import path from 'node:path';
20
+ import { fileURLToPath } from 'node:url';
21
+
22
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
23
+ const OWNER = 'modules/lifecycle/github-push.js';
24
+
25
+ // Code only: a comment naming the function is not a call.
26
+ const stripComments = (src) => src.replace(/\/\*[\s\S]*?\*\//g, '').replace(/\/\/[^\n]*/g, '');
27
+
28
+ function callers(files, read) {
29
+ return files.filter((f) => f !== OWNER && /recheckPullRequest\s*\(/.test(stripComments(read(f))));
30
+ }
31
+
32
+ test('nothing outside github-push calls recheckPullRequest (the only re-arm path was the removed gate route)', () => {
33
+ const files = execFileSync('git', ['ls-files', 'src', 'modules', 'scripts'], { cwd: ROOT, encoding: 'utf8' })
34
+ .split('\n').filter((f) => /\.(c|m)?js$/.test(f) && !/\/tests?\//.test(f));
35
+ assert.ok(files.length > 500, `only ${files.length} files scanned — the sweep is not reading the tree`);
36
+ const read = (f) => fs.readFileSync(path.join(ROOT, f), 'utf8');
37
+ assert.deepEqual(callers(files, read), [],
38
+ 'a new caller re-arms auto-merge on an open PR: it must check heldForArtistFailClosed for the PR\'s task '
39
+ + 'and pass { arm: false } when held (ADR 0341 D7), with a test, then be listed here');
40
+ assert.ok(!fs.existsSync(path.join(ROOT, 'modules/lifecycle/routes/gate-approvals.js')), 'the gate route stays removed');
41
+ });
42
+
43
+ test('the sweep would catch a caller (counterfactual), and ignores one in a comment', () => {
44
+ const fake = {
45
+ 'modules/x/a.js': 'await githubPush.recheckPullRequest({ number: 1 });',
46
+ 'modules/x/b.js': '// recheckPullRequest(...) is described here only',
47
+ [OWNER]: 'async function recheckPullRequest() {}',
48
+ };
49
+ assert.deepEqual(callers(Object.keys(fake), (f) => fake[f]), ['modules/x/a.js']);
50
+ });
@@ -22,6 +22,7 @@ import { createRequire } from 'node:module';
22
22
  import fs from 'node:fs';
23
23
  import path from 'node:path';
24
24
  import url from 'node:url';
25
+ import { execFileSync } from 'node:child_process';
25
26
 
26
27
  const ROOT = path.resolve(path.dirname(url.fileURLToPath(import.meta.url)), '..');
27
28
  const require = createRequire(import.meta.url);
@@ -324,5 +325,68 @@ t('no publishable test statically imports an excluded module (R89 boundary)', ()
324
325
  assert.equal(offenders.length, 0, '\n ' + offenders.join('\n '));
325
326
  });
326
327
 
328
+ // ── hall-preview fixtures stay out of the core; the harness stays in (task 1004888) ──
329
+ // scripts/hall-preview/fixtures/ is ~2.4 MB of captured hall data from THIS instance
330
+ // (owner login, world name, prior instance name). INSTANCE_EXCLUDE keeps it out of the
331
+ // published core (task 1002219, audit H5); this pins that, so an edit to the exclude
332
+ // list cannot silently start shipping the fixtures to every hosted project again.
333
+ // The selection is built the way package-core builds it: enumerate the git tree
334
+ // (`git ls-tree -r` on a ref) and keep what isPublishable() admits.
335
+ const HALL_PREVIEW = 'scripts/hall-preview/';
336
+ const HALL_FIXTURES = `${HALL_PREVIEW}fixtures/`;
337
+ // The harness files the preview cannot boot without: the server and its two local
338
+ // requires (./live-identity, ./pref-writes). A named floor, so the check below can
339
+ // never pass by finding no harness at all.
340
+ const HALL_HARNESS_FLOOR = ['server.js', 'live-identity.js', 'pref-writes.js'].map((f) => HALL_PREVIEW + f);
341
+
342
+ function treeFiles() {
343
+ // package-core's ref path: every blob on HEAD. A core checkout with no git history
344
+ // (a consumer's vendored core) falls back to walking the disk, as --from-worktree does.
345
+ try {
346
+ const out = execFileSync('git', ['ls-tree', '-r', '--name-only', 'HEAD'], { cwd: ROOT, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], maxBuffer: 64 * 1024 * 1024 });
347
+ return out.split('\n').filter(Boolean);
348
+ } catch (_) {
349
+ const files = [];
350
+ const walk = (rel) => {
351
+ for (const ent of fs.readdirSync(path.join(ROOT, rel), { withFileTypes: true })) {
352
+ const r = rel ? `${rel}/${ent.name}` : ent.name;
353
+ if (ent.isDirectory()) walk(r); else files.push(r);
354
+ }
355
+ };
356
+ if (fs.existsSync(path.join(ROOT, HALL_PREVIEW))) walk(HALL_PREVIEW.slice(0, -1));
357
+ return files;
358
+ }
359
+ }
360
+
361
+ t('hall-preview fixtures are excluded from the core (path rule, task 1004888)', () => {
362
+ // Holds even where the fixtures are not on disk (a consumer core never has them).
363
+ for (const p of [`${HALL_FIXTURES}me.json`, `${HALL_FIXTURES}nested/deep.json`, `${HALL_FIXTURES}tasks.json`]) {
364
+ assert.equal(isPublishable(p), false, `${p} would ship in the published core — keep '${HALL_FIXTURES}' in INSTANCE_EXCLUDE`);
365
+ }
366
+ for (const p of HALL_HARNESS_FLOOR) {
367
+ assert.equal(isPublishable(p), true, `${p} is the preview harness and must still publish`);
368
+ }
369
+ });
370
+
371
+ t('the package-core selection carries no hall-preview fixture, but keeps the harness (task 1004888)', () => {
372
+ const hall = treeFiles().filter((f) => f.startsWith(HALL_PREVIEW));
373
+ const fixtures = hall.filter((f) => f.startsWith(HALL_FIXTURES));
374
+ const harness = hall.filter((f) => !f.startsWith(HALL_FIXTURES));
375
+ const { publish } = partitionPaths(hall);
376
+ const leaked = publish.filter((f) => f.startsWith(HALL_FIXTURES));
377
+ assert.equal(leaked.length, 0, `${leaked.length} hall-preview fixture(s) would ship in the core, e.g. ${leaked.slice(0, 3).join(', ')} — keep '${HALL_FIXTURES}' in INSTANCE_EXCLUDE`);
378
+ // Every harness file on the tree publishes, and the named floor is among them
379
+ // whenever the harness exists here at all (the source repo always has it).
380
+ const dropped = harness.filter((f) => !publish.includes(f));
381
+ assert.equal(dropped.length, 0, `hall-preview harness file(s) dropped from the core: ${dropped.join(', ')}`);
382
+ if (harness.length) {
383
+ for (const p of HALL_HARNESS_FLOOR) assert.ok(publish.includes(p), `${p} missing from the core selection`);
384
+ }
385
+ // Non-vacuous in the source repo: the fixtures are there to be excluded.
386
+ if (fs.existsSync(path.join(ROOT, HALL_FIXTURES))) {
387
+ assert.ok(fixtures.length > 0, `found ${HALL_FIXTURES} on disk but no fixture in the enumerated tree — the enumeration is broken, so this check proves nothing`);
388
+ }
389
+ });
390
+
327
391
  console.log(`\n${passed} passed, ${failed} failed`);
328
392
  process.exit(failed ? 1 : 0);
@@ -2,6 +2,7 @@
2
2
  // kind the server accepts (task 1003694).
3
3
  //
4
4
  // THE FAILURE THIS PINS. .claude/scheduled-tasks/dep-audit-nightly/SKILL.md filed
5
+ // (before task 1004878 moved its filing into the server's daily-checks poller)
5
6
  // with `--kind security`. That is not one of the kinds — captureIdea throws BAD_KIND
6
7
  // and the route 400s — so the capture step of that nightly routine had NEVER filed
7
8
  // anything.
@@ -137,11 +138,21 @@ test('a capture command wrapped over lines is still read', () => {
137
138
  'dep-audit-nightly writes exactly this shape — a line-at-a-time matcher misses it');
138
139
  });
139
140
 
140
- test('dep-audit-nightly specifically now files with an accepted kind (task 1003694)', () => {
141
- const p = path.join(REPO_ROOT, '.claude', 'scheduled-tasks', 'dep-audit-nightly', 'SKILL.md');
142
- const src = readFileSync(p, 'utf8');
143
- assert.ok(!/--kind\s+security\b/.test(src), 'security is not a kind and never was');
144
- const kinds = literalKinds(src);
145
- assert.ok(kinds.length > 0, 'the routine must still file something');
146
- for (const k of kinds) assert.ok(VALID_KINDS.includes(k), `${k} is not a valid kind`);
141
+ test('dep-audit-nightly specifically now files with an accepted kind (task 1003694)', async () => {
142
+ // Since task 1004878 the routine files from the server's daily-checks poller, not
143
+ // from a capture command in its SKILL.md — so the kind to check is the one the
144
+ // poller hands ideas.capture. Driven, not grepped: a stub npm reports one finding.
145
+ const { runCheck } = require('../modules/autonomy/pollers/daily-checks.js');
146
+ const filed = [];
147
+ const out = await runCheck('dep-audit-nightly', {
148
+ db: { readAutonomyRuns: async () => [], recordAutonomyRun: async () => ({}) },
149
+ cwd: '/instance',
150
+ npmAudit: async () => ({ stdout: JSON.stringify({ vulnerabilities: { x: { severity: 'high', via: [], fixAvailable: true } } }), stderr: '' }),
151
+ captureIdea: async (idea) => { filed.push(idea); return { id: 1 }; },
152
+ log: () => {}, error: () => {},
153
+ });
154
+ assert.equal(out.outcome, 'filed');
155
+ assert.equal(filed.length, 1);
156
+ assert.notEqual(filed[0].kind, 'security', 'security is not a kind and never was');
157
+ assert.ok(VALID_KINDS.includes(filed[0].kind), `${filed[0].kind} is not a valid kind`);
147
158
  });
@@ -234,3 +234,45 @@ test('LIVE: the patrol workflow installs gitleaks, which the history scan cannot
234
234
  assert.match(wf, /sha256sum -c -/, 'the binary must be checksum-verified (the tj-actions lesson)');
235
235
  assert.match(wf, /fetch-depth: 0/, 'a shallow clone makes the history scan a confident no-op');
236
236
  });
237
+
238
+ // ------------------------------------------------- the server scheduler (task 1004878)
239
+
240
+ const SRV = [{ routine: 'nightly-thing', cadence: 'daily' }];
241
+
242
+ test('a server row fires its routine: declared daily + server daily is green with NO workflow', () => {
243
+ const r = rs.evaluate(routines([{ name: 'nightly-thing', cadence: 'daily', steps: [] }]), [], SRV);
244
+ assert.deepEqual(r.violations, []);
245
+ });
246
+
247
+ test('without the server row the same routine is dormant — the row is what makes it green', () => {
248
+ const v = rs.evaluate(routines([{ name: 'nightly-thing', cadence: 'daily', steps: [] }]), [], []).violations;
249
+ assert.match(v[0], /NOTHING fires it/);
250
+ });
251
+
252
+ test('a server row at a different period than the routine declares is red', () => {
253
+ const v = rs.evaluate(routines([{ name: 'nightly-thing', cadence: 'weekly', steps: [] }]), [], SRV).violations;
254
+ assert.match(v[0], /declares cadence: weekly but the server fires it daily/);
255
+ });
256
+
257
+ test('the server firing a routine whose SKILL.md declares no cadence is red, not a quiet warning', () => {
258
+ const r = rs.evaluate(routines([], [{ name: 'nightly-thing' }]), [], SRV);
259
+ assert.match(r.violations[0], /fired daily by the server but its SKILL\.md declares no cadence/);
260
+ assert.equal(r.warnings.length, 0, 'the same routine must not ALSO read as manual-only');
261
+ });
262
+
263
+ test('a server row naming a routine that is gone is red (rename rot)', () => {
264
+ const v = rs.evaluate(routines(WEEKLY), [WEEKLY_WF], SRV).violations;
265
+ assert.ok(v.some((x) => /fires routine 'nightly-thing', which has no/.test(x)), v.join(' | '));
266
+ });
267
+
268
+ test('with no routine tree at all (an instance before materialize) server rows are not dangling', () => {
269
+ assert.deepEqual(rs.evaluate(routines([]), null, SRV).violations, []);
270
+ });
271
+
272
+ test('LIVE: every server-fired routine declares the cadence the server realizes', () => {
273
+ const { SERVER_ROUTINES } = require(path.join(ROOT, 'modules', 'autonomy', 'server-schedule.js'));
274
+ const declared = new Map(rs.readRoutines().withCadence.map((r) => [r.name, r.cadence]));
275
+ for (const s of SERVER_ROUTINES) {
276
+ assert.equal(declared.get(s.routine), s.cadence, `${s.routine}: SKILL.md cadence vs server-schedule.js`);
277
+ }
278
+ });
@@ -14,15 +14,13 @@
14
14
  // to searchChunks and IGNORES any attempt to widen scope via the body
15
15
  // (builder_id / rank / min_rank / owner_builder_id). This is the
16
16
  // "can't be tricked" proof.
17
- // C. DB-backed (auto-SKIPS without a reachable Postgres): seeds rank-gated +
18
- // owner-scoped chunks and runs the REAL searchChunks in a ROLLED-BACK
19
- // transaction for the done-when cases (xenos-vs-protected, cross-builder
20
- // memory, owner-sees-own). The same cases were also proven against the prod
21
- // DB in a rolled-back transaction at ship time (the authoritative evidence).
17
+ // C. DB-backed: lives in tests/search_isolation_db.mjs (task 1004863). It was
18
+ // the third layer of this file, whose comment said it ran in the integration
19
+ // gate — but this file was never in that set, so in CI Layer C always skipped
20
+ // and no lane executed it. Split out, it is in the INTEGRATION set, and A + B
21
+ // stay here in the DB-free unit lane.
22
22
  //
23
23
  // Run: node tests/search_isolation.mjs
24
- // (Layer C runs only where a Postgres is reachable — the integration gate / any
25
- // machine with one; it skips cleanly otherwise so the DB-free unit gate stays green.)
26
24
 
27
25
  import { strict as assert } from 'node:assert';
28
26
  import { createRequire } from 'node:module';
@@ -136,102 +134,6 @@ try {
136
134
  search.searchChunks = orig.searchChunks;
137
135
  }
138
136
 
139
- // ---------------------------------------------------------------------------
140
- // Layer C — DB-backed real-query scoping (auto-skips without Postgres)
141
- // ---------------------------------------------------------------------------
142
- console.log('\nLayer C — real searchChunks against a live DB (skips if unreachable):');
143
-
144
- let pool = null;
145
- try {
146
- // task 1003207: this said `../src/db`, which has not existed since the kernel
147
- // carve — so the require ALWAYS threw, pool stayed null, and Layer C skipped
148
- // even against a live Postgres. It was a dead layer reporting as a skip.
149
- ({ pool } = require('../src/bongos/pool'));
150
- } catch { pool = null; }
151
-
152
- async function dbReachable() {
153
- if (!pool) return null;
154
- try {
155
- const c = await pool.connect();
156
- try { await c.query('SELECT 1'); return c; } catch { c.release(); return null; }
157
- } catch { return null; }
158
- }
159
-
160
- const client = await dbReachable();
161
- if (!client) {
162
- console.log(' SKIP no reachable Postgres — Layer C runs in the integration gate / wherever a Postgres is reachable.');
163
- console.log(' (The same scoping cases were proven on the prod DB in a rolled-back transaction at ship time.)');
164
- // END THE POOL ON THE SKIP PATH TOO (task 1003848). Only the else-branch below
165
- // ended it, so when Postgres was unreachable the Pool survived with libuv handles
166
- // still closing from its failed connect — and the process.exit() at the bottom of
167
- // this file then tore down the loop underneath them. On Windows that surfaces as
168
- // `Assertion failed: !(handle->flags & UV_HANDLE_CLOSING), src\win\async.c`, exit
169
- // 0xC0000409: a process-teardown crash AFTER '5 passed, 0 failed', which reads as
170
- // a failing suite in the unit gate while every assertion actually passed.
171
- if (pool) await pool.end().catch(() => {});
172
- } else {
173
- try {
174
- await client.query('BEGIN');
175
- await client.query(`SET LOCAL pg_trgm.word_similarity_threshold = ${search.TRGM_THRESHOLD}`);
176
- // builder 1 exists (example-owner); seed a protected (metic) chunk, a public one,
177
- // and a chunk owned by builder 1 — all matching q='enforcement'.
178
- await client.query(`INSERT INTO search_chunks (source_kind, source_ref, chunk_index, heading_path, title, body, min_rank, owner_builder_id, content_hash) VALUES
179
- ('doc','__iso__/protected.md',0,'p','PROT','trust boundary permission core enforcement','metic',NULL,'i1'),
180
- ('doc','__iso__/public.md',0,'pub','PUB','trust boundary public enforcement overview','xenos',NULL,'i2'),
181
- ('db','__iso__/memory:1',0,'m','MEM1','builder one private enforcement memo','xenos',1,'i3')`);
182
-
183
- const titles = (rows) => rows.map((r) => r.title).sort().join(',');
184
-
185
- await t('xenos caller sees the public chunk but NOT the metic-protected one (xenos-vs-protected)', async () => {
186
- const rows = await search.searchChunks({ q: 'enforcement', builderId: 2, builderRank: 'xenos', limit: 50 }, { client });
187
- const ts = titles(rows);
188
- assert.ok(ts.includes('PUB'), `expected PUB, got [${ts}]`);
189
- assert.ok(!ts.includes('PROT'), `xenos must NOT see the metic chunk, got [${ts}]`);
190
- });
191
-
192
- await t('metic caller sees the protected chunk too', async () => {
193
- const rows = await search.searchChunks({ q: 'enforcement', builderId: 2, builderRank: 'metic', limit: 50 }, { client });
194
- const ts = titles(rows);
195
- assert.ok(ts.includes('PROT') && ts.includes('PUB'), `metic should see PROT+PUB, got [${ts}]`);
196
- });
197
-
198
- await t('builder 2 does NOT see builder 1\'s owned chunk (cross-builder memory)', async () => {
199
- const rows = await search.searchChunks({ q: 'enforcement', builderId: 2, builderRank: 'archon', limit: 50 }, { client });
200
- assert.ok(!rows.some((r) => r.title === 'MEM1'), 'an Archon STILL must not see another builder\'s private chunk');
201
- });
202
-
203
- await t('builder 1 DOES see their own owned chunk (owner-sees-own)', async () => {
204
- const rows = await search.searchChunks({ q: 'enforcement', builderId: 1, builderRank: 'xenos', limit: 50 }, { client });
205
- assert.ok(rows.some((r) => r.title === 'MEM1'), 'a builder must see their own private chunk');
206
- });
207
-
208
- // The public port (task 1002534, ADR 0367): the Discord #support answerer
209
- // has no session, so its scope is fixed — shared xenos docs on the
210
- // allowlist, nothing else, whatever matches the query.
211
- await client.query(`INSERT INTO search_chunks (source_kind, source_ref, chunk_index, heading_path, title, body, min_rank, owner_builder_id, content_hash) VALUES
212
- ('doc','__pub__/allowed/guide.md',0,'g','PUBOK','support enforcement guide','xenos',NULL,'p1'),
213
- ('doc','__pub__/allowed/locked.md',0,'l','PUBMETIC','support enforcement locked','metic',NULL,'p2'),
214
- ('doc','__pub__/allowed/mine.md',0,'m','PUBOWNED','support enforcement owned','xenos',1,'p3'),
215
- ('db','__pub__/allowed/task:1',0,'d','PUBDB','support enforcement task prose','xenos',NULL,'p4'),
216
- ('doc','__pub__/elsewhere.md',0,'e','PUBOFFLIST','support enforcement off the list','xenos',NULL,'p5')`);
217
-
218
- await t('searchPublicDocs returns ONLY a shared xenos doc on the allowlist (no metic, owned, db, or off-list row)', async () => {
219
- const rows = await search.searchPublicDocs({ q: 'support enforcement', refs: ['__pub__/allowed/'], limit: 10 }, { client });
220
- const ts = titles(rows);
221
- assert.equal(ts, 'PUBOK', `expected only PUBOK, got [${ts}]`);
222
- assert.ok(rows[0].body.includes('support enforcement guide'), 'the body comes back with the hit');
223
- });
224
-
225
- await t('searchPublicDocs with no allowlist returns nothing at all', async () => {
226
- assert.deepEqual(await search.searchPublicDocs({ q: 'support enforcement', refs: [] }, { client }), []);
227
- });
228
- } finally {
229
- await client.query('ROLLBACK').catch(() => {});
230
- client.release();
231
- await pool.end().catch(() => {});
232
- }
233
- }
234
-
235
137
  console.log(`\n${passed} passed, ${failed} failed`);
236
138
  // EXIT CODE, NOT process.exit() (task 1003848). A hard exit tears the event loop down
237
139
  // underneath any handle that is still closing — and pg leaves one behind after a
@@ -0,0 +1,140 @@
1
+ // tests/search_isolation_db.mjs
2
+ //
3
+ // Layer C of the GDS search isolation suite (task 950, V4.R12 / ADR 0060 §6 / ADR 0016),
4
+ // against a REAL Postgres. Layers A (pure + SQL structure) and B (the HTTP route derives
5
+ // scope from the session only) are DB-free and stay in tests/search_isolation.mjs.
6
+ //
7
+ // Seeds rank-gated + owner-scoped chunks and runs the REAL searchChunks and
8
+ // searchPublicDocs in a ROLLED-BACK transaction for the done-when cases
9
+ // (xenos-vs-protected, cross-builder memory, owner-sees-own, the public port's fixed
10
+ // scope). The search index must never be a read-side bypass of the trust boundary.
11
+ //
12
+ // SPLIT OUT by task 1004863. As the third layer of search_isolation.mjs it said it ran in
13
+ // the integration gate, but that file was never in the INTEGRATION set — so in CI it
14
+ // always skipped and no lane executed it. This file is in the set; it needs a MIGRATED
15
+ // database (builders 1 and 2 are the seeded system rows the owned chunk points at).
16
+ //
17
+ // Run: DATABASE_URL=postgres://... node tests/search_isolation_db.mjs
18
+
19
+ import { strict as assert } from 'node:assert';
20
+ import { createRequire } from 'node:module';
21
+
22
+ const require = createRequire(import.meta.url);
23
+ const search = require('../modules/memory/search.js');
24
+
25
+ let passed = 0;
26
+ let failed = 0;
27
+ async function t(name, fn) {
28
+ try { await fn(); passed++; console.log(` ok ${name}`); }
29
+ catch (err) { failed++; console.log(` FAIL ${name}`); console.log(` ${err.message}`); }
30
+ }
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // Layer C — DB-backed real-query scoping (auto-skips without Postgres)
34
+ // ---------------------------------------------------------------------------
35
+ console.log('Layer C — real searchChunks against a live DB (skips if unreachable):');
36
+
37
+ let pool = null;
38
+ try {
39
+ // task 1003207: this said `../src/db`, which has not existed since the kernel
40
+ // carve — so the require ALWAYS threw, pool stayed null, and Layer C skipped
41
+ // even against a live Postgres. It was a dead layer reporting as a skip.
42
+ ({ pool } = require('../src/bongos/pool'));
43
+ } catch { pool = null; }
44
+
45
+ async function dbReachable() {
46
+ if (!pool) return null;
47
+ try {
48
+ const c = await pool.connect();
49
+ try { await c.query('SELECT 1'); return c; } catch { c.release(); return null; }
50
+ } catch { return null; }
51
+ }
52
+
53
+ const client = await dbReachable();
54
+ if (!client) {
55
+ console.log(' SKIP no reachable Postgres — this runs in the integration lane (run-unit-tests.js INTEGRATION) / wherever a Postgres is reachable.');
56
+ console.log(' (The same scoping cases were proven on the prod DB in a rolled-back transaction at ship time.)');
57
+ // END THE POOL ON THE SKIP PATH TOO (task 1003848). Only the else-branch below
58
+ // ended it, so when Postgres was unreachable the Pool survived with libuv handles
59
+ // still closing from its failed connect — and the process.exit() at the bottom of
60
+ // the old combined file then tore down the loop underneath them. On Windows that surfaces as
61
+ // `Assertion failed: !(handle->flags & UV_HANDLE_CLOSING), src\win\async.c`, exit
62
+ // 0xC0000409: a process-teardown crash AFTER '5 passed, 0 failed', which reads as
63
+ // a failing suite in the unit gate while every assertion actually passed.
64
+ if (pool) await pool.end().catch(() => {});
65
+ } else {
66
+ try {
67
+ await client.query('BEGIN');
68
+ await client.query(`SET LOCAL pg_trgm.word_similarity_threshold = ${search.TRGM_THRESHOLD}`);
69
+ // builder 1 exists (example-owner); seed a protected (metic) chunk, a public one,
70
+ // and a chunk owned by builder 1 — all matching q=ISO_TERM.
71
+ //
72
+ // ISO_TERM is a word no real document contains (task 1004889). It used to be
73
+ // 'enforcement', and in the integration lane the booted server's search-index
74
+ // poller fills search_chunks with the repo's docs first: 149 real chunks matched,
75
+ // the three seeded rows fell out of the top 50, and every "sees" case failed.
76
+ // Worse, the "does NOT see" cases were then passing VACUOUSLY — a hidden row and a
77
+ // crowded-out row look the same. With a unique term the seeded rows are the only
78
+ // candidates, so a row missing from the results can only mean the scope removed it.
79
+ const ISO_TERM = 'zqxisoprobe';
80
+ await client.query(`INSERT INTO search_chunks (source_kind, source_ref, chunk_index, heading_path, title, body, min_rank, owner_builder_id, content_hash) VALUES
81
+ ('doc','__iso__/protected.md',0,'p','PROT','trust boundary permission core ${ISO_TERM}','metic',NULL,'i1'),
82
+ ('doc','__iso__/public.md',0,'pub','PUB','trust boundary public ${ISO_TERM} overview','xenos',NULL,'i2'),
83
+ ('db','__iso__/memory:1',0,'m','MEM1','builder one private ${ISO_TERM} memo','xenos',1,'i3')`);
84
+
85
+ const titles = (rows) => rows.map((r) => r.title).sort().join(',');
86
+
87
+ await t('xenos caller sees the public chunk but NOT the metic-protected one (xenos-vs-protected)', async () => {
88
+ const rows = await search.searchChunks({ q: ISO_TERM, builderId: 2, builderRank: 'xenos', limit: 50 }, { client });
89
+ const ts = titles(rows);
90
+ assert.ok(ts.includes('PUB'), `expected PUB, got [${ts}]`);
91
+ assert.ok(!ts.includes('PROT'), `xenos must NOT see the metic chunk, got [${ts}]`);
92
+ });
93
+
94
+ await t('metic caller sees the protected chunk too', async () => {
95
+ const rows = await search.searchChunks({ q: ISO_TERM, builderId: 2, builderRank: 'metic', limit: 50 }, { client });
96
+ const ts = titles(rows);
97
+ assert.ok(ts.includes('PROT') && ts.includes('PUB'), `metic should see PROT+PUB, got [${ts}]`);
98
+ });
99
+
100
+ await t('builder 2 does NOT see builder 1\'s owned chunk (cross-builder memory)', async () => {
101
+ const rows = await search.searchChunks({ q: ISO_TERM, builderId: 2, builderRank: 'archon', limit: 50 }, { client });
102
+ assert.ok(!rows.some((r) => r.title === 'MEM1'), 'an Archon STILL must not see another builder\'s private chunk');
103
+ });
104
+
105
+ await t('builder 1 DOES see their own owned chunk (owner-sees-own)', async () => {
106
+ const rows = await search.searchChunks({ q: ISO_TERM, builderId: 1, builderRank: 'xenos', limit: 50 }, { client });
107
+ assert.ok(rows.some((r) => r.title === 'MEM1'), 'a builder must see their own private chunk');
108
+ });
109
+
110
+ // The public port (task 1002534, ADR 0367): the Discord #support answerer
111
+ // has no session, so its scope is fixed — shared xenos docs on the
112
+ // allowlist, nothing else, whatever matches the query.
113
+ await client.query(`INSERT INTO search_chunks (source_kind, source_ref, chunk_index, heading_path, title, body, min_rank, owner_builder_id, content_hash) VALUES
114
+ ('doc','__pub__/allowed/guide.md',0,'g','PUBOK','support enforcement guide','xenos',NULL,'p1'),
115
+ ('doc','__pub__/allowed/locked.md',0,'l','PUBMETIC','support enforcement locked','metic',NULL,'p2'),
116
+ ('doc','__pub__/allowed/mine.md',0,'m','PUBOWNED','support enforcement owned','xenos',1,'p3'),
117
+ ('db','__pub__/allowed/task:1',0,'d','PUBDB','support enforcement task prose','xenos',NULL,'p4'),
118
+ ('doc','__pub__/elsewhere.md',0,'e','PUBOFFLIST','support enforcement off the list','xenos',NULL,'p5')`);
119
+
120
+ await t('searchPublicDocs returns ONLY a shared xenos doc on the allowlist (no metic, owned, db, or off-list row)', async () => {
121
+ const rows = await search.searchPublicDocs({ q: 'support enforcement', refs: ['__pub__/allowed/'], limit: 10 }, { client });
122
+ const ts = titles(rows);
123
+ assert.equal(ts, 'PUBOK', `expected only PUBOK, got [${ts}]`);
124
+ assert.ok(rows[0].body.includes('support enforcement guide'), 'the body comes back with the hit');
125
+ });
126
+
127
+ await t('searchPublicDocs with no allowlist returns nothing at all', async () => {
128
+ assert.deepEqual(await search.searchPublicDocs({ q: 'support enforcement', refs: [] }, { client }), []);
129
+ });
130
+ } finally {
131
+ await client.query('ROLLBACK').catch(() => {});
132
+ client.release();
133
+ await pool.end().catch(() => {});
134
+ }
135
+ }
136
+
137
+ console.log(`\n${passed} passed, ${failed} failed`);
138
+ // exitCode, not process.exit() (task 1003848): a hard exit can tear the loop down under a
139
+ // pg handle still closing after a failed connect — the skip path.
140
+ process.exitCode = failed === 0 ? 0 : 1;
@@ -125,6 +125,8 @@ async function fixture(client) {
125
125
  title: `${tag} task`,
126
126
  status: 'shipped',
127
127
  version_id: version.id,
128
+ // Named, not placeholder-filled: tasks_discipline_check admits only the live crafts.
129
+ discipline: 'engineer',
128
130
  });
129
131
  const sessionId = `${tag}-session`;
130
132
  await insertRow(client, 'session_records', {
@@ -23,11 +23,12 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
23
23
  const REPO = path.resolve(__dirname, '..');
24
24
  const CORE_PATH_SH = path.join(REPO, 'infra', 'core-path.sh');
25
25
 
26
- // infra/ + the gate workflows under .github/ are HOST topology (publish-manifest.js
27
- // PROTECTED_TOPOLOGY_EXCLUDE) — absent from a neutral core checkout (task 1002470).
28
- // The real-tree checks only mean something where that tree exists; the synthetic
29
- // counterfactual below always runs.
30
- const HAS_GATE_WORKFLOWS = fs.existsSync(path.join(REPO, '.github', 'workflows', 'gate-review.yml'));
26
+ // infra/ is HOST topology (publish-manifest.js PROTECTED_TOPOLOGY_EXCLUDE) — absent
27
+ // from a neutral core checkout (task 1002470). The real-tree checks only mean
28
+ // something where that tree exists; the synthetic counterfactual below always runs.
29
+ // Task 1004859: this used to key on .github/workflows/gate-review.yml, a workflow that
30
+ // was only ever planned; it now keys on the resolver every routed invocation needs.
31
+ const HAS_HOST_TOPOLOGY = fs.existsSync(path.join(REPO, 'infra', 'core-node.sh'));
31
32
  const HAS_CORE_PATH = fs.existsSync(CORE_PATH_SH);
32
33
 
33
34
  let passed = 0, failed = 0;
@@ -42,14 +43,11 @@ function hostTest(name, present, fn) {
42
43
  }
43
44
 
44
45
  // ---- A. the workflow lint is green on the real tree ---------------------------------------------
45
- hostTest('lint-workflow-exec: every core invocation is routed + publishable (no bare repo-relative form)', HAS_GATE_WORKFLOWS, () => {
46
+ hostTest('lint-workflow-exec: every core invocation is routed + publishable (no bare repo-relative form)', HAS_HOST_TOPOLOGY, () => {
46
47
  const r = lintWorkflowExec({ instanceRoot: REPO, coreRoot: REPO });
47
48
  assert.deepEqual(r.violations, [], `expected no violations, got: ${JSON.stringify(r.violations, null, 2)}`);
48
49
  assert.ok(r.ok, 'lint should pass');
49
- // The security-critical merge gates + the functional gates must all be covered.
50
50
  const rels = r.checked.map((c) => `${c.file}:${c.rel}`);
51
- assert.ok(rels.some((s) => s.startsWith('gate-review.yml:') && s.endsWith('scripts/bongos/gate-review.js')), 'gate-review routed');
52
- assert.ok(rels.some((s) => s.startsWith('grade-gate.yml:') && s.endsWith('scripts/bongos/ci-grade.js')), 'grade-gate routed');
53
51
  assert.ok(rels.some((s) => s.endsWith('scripts/migrate.sh')), 'selfhost migrate routed via core-path.sh');
54
52
  });
55
53
 
@@ -58,9 +56,9 @@ test('lint-workflow-exec: a bare core invocation in a workflow is flagged (count
58
56
  try {
59
57
  fs.mkdirSync(path.join(tmp, '.github', 'workflows'), { recursive: true });
60
58
  fs.mkdirSync(path.join(tmp, 'scripts', 'bongos'), { recursive: true });
61
- fs.writeFileSync(path.join(tmp, 'scripts', 'bongos', 'gate-review.js'), '// probe\n');
59
+ fs.writeFileSync(path.join(tmp, 'scripts', 'bongos', 'main-audit.js'), '// probe\n');
62
60
  fs.writeFileSync(path.join(tmp, '.github', 'workflows', 'legacy.yml'),
63
- 'jobs:\n x:\n steps:\n - run: node scripts/bongos/gate-review.js\n');
61
+ 'jobs:\n x:\n steps:\n - run: node scripts/bongos/main-audit.js\n');
64
62
  const r = lintWorkflowExec({ instanceRoot: tmp, coreRoot: tmp });
65
63
  assert.ok(!r.ok, 'a bare core invocation must fail the lint');
66
64
  assert.equal(r.violations.length, 1);