@bongos/core 1.19.324 → 1.19.326

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.
@@ -33,34 +33,9 @@ const { execFileSync } = require('node:child_process');
33
33
 
34
34
  const REPO_ROOT = path.resolve(__dirname, '..', '..');
35
35
 
36
- // ---------------------------------------------------------------------------
37
- // Check 22 — a doc that NAMES a CLI invocation must match the real CLI
38
- // (task 1003165 / G01, goal 1000077).
39
- //
40
- // WHY. Task 1002931 shipped a skill instructing `capture.js --full --fields-file`
41
- // while those flags lived on an unlanded branch. Its test passed, because the test
42
- // pinned the skill's PROSE — it asserted the file said the words, never that the
43
- // words worked. So the skill confidently told agents to run a command that
44
- // silently ignored both flags and filed the wrong thing. That is the whole class:
45
- // AN UNRECOGNISED FLAG IS NOT AN ERROR. `arg('--x')`/`hasFlag('--x')` just return
46
- // null and the command proceeds, so a doc can name a flag that does nothing and
47
- // every test stays green (learning 1000198).
48
- //
49
- // It happened TWICE in one session: the second time, /workspace ran an older
50
- // capture.js and the same documented flags were dropped on the floor again.
51
- //
52
- // WHAT IT CHECKS, deliberately narrow: for every `node scripts/gds/<x>.js` in a
53
- // tracked .md, (a) the script exists, and (b) each `--flag` written next to it is
54
- // parsed by that script. Nothing else — this is not a linter for prose.
55
- //
56
- // STATIC ONLY, NEVER EXECUTED. The task says so and the reason matters: running
57
- // every documented CLI from a fitness check would be slow, would need a session,
58
- // and a command that blocks on a prompt would hang CI forever — the exact failure
59
- // R151's TTY gate exists to prevent.
60
- // Key is `<doc path>::<token>`, where token is either the script path (the whole
61
- // invocation is tolerated) or `--flag` (just that flag). Every entry carries its
62
- // reason on the same line: an unexplained entry is a silent hole, and the point of
63
- // this check is that a doc's claims are checkable.
36
+ // Check 22 in fitness.js's CHECKS. The why, the narrowness and the static-only
37
+ // rule are all in the file header above — not restated here.
38
+
64
39
  const DOC_CLI_ALLOW = new Map([
65
40
  ['.claude/skills/paint/SKILL.md::scripts/gds/art-stage.js',
66
41
  'art-stage.js ships with the art-pipeline HOST module, which publish-manifest.js excludes from core — absent from a core-only checkout by design; the skill says so inline.'],
@@ -120,6 +95,14 @@ function scanDocs(docs, { readDoc, readScript, allow = DOC_CLI_ALLOW } = {}) {
120
95
  if (!srcCache.has(rel)) srcCache.set(rel, readScript(rel));
121
96
  return srcCache.get(rel);
122
97
  };
98
+ // Memoized alongside srcCache: stripLineComments is two regex passes over a
99
+ // whole script, and it was being recomputed for EVERY flag on every invocation
100
+ // of that script. Invariant per file, so compute it once (grader, task 1003165).
101
+ const codeCache = new Map();
102
+ const codeOnly = (rel, src) => {
103
+ if (!codeCache.has(rel)) codeCache.set(rel, stripLineComments(src));
104
+ return codeCache.get(rel);
105
+ };
123
106
 
124
107
  for (const doc of docs) {
125
108
  const text = readDoc(doc);
@@ -156,7 +139,7 @@ function scanDocs(docs, { readDoc, readScript, allow = DOC_CLI_ALLOW } = {}) {
156
139
  // rather than failing is the deliberate choice: a usage banner is the
157
140
  // ONLY place some scripts name a flag they genuinely handle via a helper,
158
141
  // so failing here would resurrect the false positives that sank draft 1.
159
- if (!scriptMentionsFlag(stripLineComments(src), flag)) {
142
+ if (!scriptMentionsFlag(codeOnly(scriptRel, src), flag)) {
160
143
  warnings.push(
161
144
  `${doc} → \`node ${scriptRel} --${flag}\`: ${scriptRel} names \`--${flag}\` only in comments/usage text, `
162
145
  + 'never in code — it may be a flag whose implementation was deleted. Verify it is still handled.'
@@ -83,7 +83,7 @@ module.exports = function buildMeRouter() {
83
83
  const [
84
84
  builder, activeClaims, estVsActual, unlocked, lockedWithProgress, streakInfo,
85
85
  recentGrades, builderAvg, onboardingRow, shipCount, recentKarma, karma30d, discordLink, boxConnected, cliAuthed,
86
- behaviorPrefs, geminiKeyRow, needsRebaseTasks, foundingBuilder,
86
+ behaviorPrefs, geminiKeyRow, needsRebaseTasks, foundingBuilder, helpRequestCount,
87
87
  ] = await Promise.all([
88
88
  db.getBuilderById(req.builder.id),
89
89
  lc().getActiveClaimsForBuilder(req.builder.id),
@@ -148,6 +148,18 @@ module.exports = function buildMeRouter() {
148
148
  // builder-needs founding-grace window (a newborn instance's founder gets a
149
149
  // welcome, not a nag). Best-effort: null just means no grace.
150
150
  db.getFoundingBuilder().catch(() => null),
151
+ // task 1003171 (C01): open help requests addressed to this builder, via the
152
+ // `lifecycle` port — core imports no module (ADR 0093). IN the fan-out, not
153
+ // before it: /me is the session-open hot path and every other per-builder
154
+ // read here is batched to keep latency at one round-trip depth (grader).
155
+ // Fail-open — a collab read must never be able to break a profile.
156
+ (async () => {
157
+ try {
158
+ const lifecycle = lc();
159
+ if (!lifecycle || !lifecycle.openHelpRequestsFor) return 0;
160
+ return await lifecycle.openHelpRequestsFor(req.builder.id);
161
+ } catch (_) { return 0; }
162
+ })(),
151
163
  ]);
152
164
  // V3.R84 (#302), criterion C3: the builder's month-to-date spend vs their
153
165
  // monthly budget. Surfaced here so the CLI (session-start ping) and the hall
@@ -284,6 +296,11 @@ module.exports = function buildMeRouter() {
284
296
  isFoundingOwner: !!(foundingBuilder && String(foundingBuilder.id) === String(req.builder.id)),
285
297
  founderCreatedAt: foundingBuilder ? foundingBuilder.created_at : null,
286
298
  projectName: (() => { try { return branding().identity?.productName || null; } catch { return null; } })(),
299
+ // task 1003171 (C01, goal 1000075): open help requests addressed to this
300
+ // builder. Read through the `lifecycle` PORT, so core never imports the
301
+ // module (ADR 0093); undefined when lifecycle is off, which the need reads
302
+ // as "nothing asked".
303
+ openHelpRequests: helpRequestCount,
287
304
  }) : [],
288
305
  };
289
306
  // Module→core contribution seam (BV1.R42, ADR 0083). Let each enabled module
package/src/module-api.js CHANGED
@@ -1075,8 +1075,12 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
1075
1075
  // landed since 1.19.322 with no explicit bump. run 32680138776. (task 1002620)
1076
1076
  // 1.19.324 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1077
1077
  // landed since 1.19.323 with no explicit bump. run 32737868129. (task 1002620)
1078
+ // 1.19.325 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1079
+ // landed since 1.19.324 with no explicit bump. run 32738897943. (task 1002620)
1080
+ // 1.19.326 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1081
+ // landed since 1.19.325 with no explicit bump. run 32777788032. (task 1002620)
1078
1082
  // ---------------------------------------------------------------------------
1079
- const CORE_VERSION = '1.19.324'; // CI auto-patch carrier (ADR 0161); see changelog above
1083
+ const CORE_VERSION = '1.19.326'; // CI auto-patch carrier (ADR 0161); see changelog above
1080
1084
 
1081
1085
  // A namespaced logger so a module's log lines are attributable + consistent.
1082
1086
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
@@ -58,3 +58,63 @@ test('all-present + all-wired yields zero violations', () => {
58
58
  const v = cm.evaluate(controls, { exists: () => true, read: () => 'has NEEDLE inside' });
59
59
  assert.deepEqual(v, []);
60
60
  });
61
+
62
+ // ── goal 1000077's four guards (task 1003169 / G05) ────────────────────────
63
+ // The goal exists because prose documented four failure modes and nothing
64
+ // refused them. These assert the guards themselves cannot quietly vanish —
65
+ // three are ONE LINE in fitness.js's CHECKS array, so deleting that line makes
66
+ // the check never run with nothing going red.
67
+
68
+ const GUARD_LABELS = ['G01', 'G02', 'G03', 'G04'];
69
+
70
+ test('all four goal-1000077 guards are in the manifest', () => {
71
+ for (const g of GUARD_LABELS) {
72
+ const row = cm.CONTROLS.find((c) => c.label.includes(`(${g})`));
73
+ assert.ok(row, `${g} has a manifest entry`);
74
+ assert.ok(row.file, `${g} names its file`);
75
+ assert.ok(row.wiredIn && row.wiredIn.file && row.wiredIn.needle, `${g} names where it is wired`);
76
+ assert.ok(Array.isArray(row.alsoExists) && row.alsoExists.some((f) => f.startsWith('tests/')),
77
+ `${g} names its TEST — a check whose test is gone still runs, but nothing proves it can fail`);
78
+ }
79
+ });
80
+
81
+ test('each guard file really exists and is really wired', () => {
82
+ // Not a source-text assertion: run the manifest's own evaluator over the
83
+ // real tree for just these rows.
84
+ const rows = cm.CONTROLS.filter((c) => GUARD_LABELS.some((g) => c.label.includes(`(${g})`)));
85
+ assert.equal(rows.length, 4);
86
+ assert.deepEqual(cm.evaluate(rows), [], 'no violations on the live tree');
87
+ });
88
+
89
+ test('a DELETED guard file is caught', () => {
90
+ const row = cm.CONTROLS.find((c) => c.label.includes('(G01)'));
91
+ const out = cm.evaluate([row], { exists: () => false, read: () => '' });
92
+ assert.equal(out.length, 1);
93
+ assert.match(out[0], /missing/i);
94
+ });
95
+
96
+ test('a guard UNWIRED from fitness.js is caught — the one-line deletion', () => {
97
+ // The realistic rot: someone removes the CHECKS entry and the guard sits on
98
+ // disk, never running, with a green build.
99
+ for (const g of ['G01', 'G02', 'G03']) {
100
+ const row = cm.CONTROLS.find((c) => c.label.includes(`(${g})`));
101
+ const out = cm.evaluate([row], { exists: () => true, read: () => 'const CHECKS = [];' });
102
+ assert.equal(out.length, 1, `${g} must be caught when unwired`);
103
+ }
104
+ });
105
+
106
+ test("G04 is wired via ship-flow, not fitness — it is a module, not a check", () => {
107
+ const row = cm.CONTROLS.find((c) => c.label.includes('(G04)'));
108
+ assert.equal(row.wiredIn.file, 'scripts/gds/ship-flow.js');
109
+ const out = cm.evaluate([row], { exists: () => true, read: () => 'nothing requires it' });
110
+ assert.equal(out.length, 1, 'an unrequired ship-honesty is caught');
111
+ });
112
+
113
+ test('a guard whose TEST vanished is caught', () => {
114
+ const row = cm.CONTROLS.find((c) => c.label.includes('(G02)'));
115
+ const out = cm.evaluate([row], {
116
+ exists: (f) => !String(f).startsWith('tests/'),
117
+ read: () => "require('./version-literal-guard.js')",
118
+ });
119
+ assert.ok(out.length >= 1, 'a missing test is a violation, not a shrug');
120
+ });
@@ -0,0 +1,263 @@
1
+ // tests/help_requests.mjs — "I am stuck": a help request names who it needs
2
+ // (task 1003171 / C01, goal 1000075, criterion collab-ask-for-help).
3
+ //
4
+ // The premise the whole goal rests on: the person you need is often NOT another
5
+ // one of you. So a request addresses a CRAFT or one specific person — and the
6
+ // interesting cases are the refusals, because a request addressed to everyone is
7
+ // a broadcast and one addressed to nobody cannot be routed at all.
8
+ import { strict as assert } from 'node:assert';
9
+ import { test } from 'node:test';
10
+ import { createRequire } from 'node:module';
11
+ import fs from 'node:fs';
12
+ import path from 'node:path';
13
+ import { fileURLToPath } from 'node:url';
14
+
15
+ const require = createRequire(import.meta.url);
16
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
17
+ const help = require(path.join(ROOT, 'modules', 'lifecycle', 'help-requests.js'));
18
+ const read = (...p) => fs.readFileSync(path.join(ROOT, ...p), 'utf8');
19
+ const reasons = (input) => help.validateHelpRequest(input).errors.map((e) => e.reason);
20
+
21
+ // ---- the addressee rules --------------------------------------------------
22
+ test('a craft request is valid', () => {
23
+ assert.deepEqual(reasons({ needs_craft: 'artist', what_is_stuck: 'is this readable?' }), []);
24
+ });
25
+
26
+ test('a person request is valid', () => {
27
+ assert.deepEqual(reasons({ needs_builder_id: 42, what_is_stuck: 'stuck on the migration' }), []);
28
+ });
29
+
30
+ test('addressed to NOBODY is refused', () => {
31
+ // Unroutable: there is no one to notify.
32
+ assert.ok(reasons({ what_is_stuck: 'help' }).includes('no_addressee'));
33
+ });
34
+
35
+ test('addressed to BOTH is refused, not resolved', () => {
36
+ // Picking one would send the request somewhere the asker did not choose.
37
+ assert.ok(reasons({ needs_craft: 'artist', needs_builder_id: 7, what_is_stuck: 'help' })
38
+ .includes('two_addressees'));
39
+ });
40
+
41
+ test('an unknown craft is refused', () => {
42
+ assert.ok(reasons({ needs_craft: 'wizard', what_is_stuck: 'help' }).includes('unknown_craft'));
43
+ });
44
+
45
+ test('the crafts are ADR 0174\'s three', () => {
46
+ assert.deepEqual(help.helpCrafts().sort(), ['artist', 'builder', 'ideator']);
47
+ });
48
+
49
+ test('THE VOCABULARY SEAM: builder maps to the discipline spelled engineer', () => {
50
+ // builders.preferred_disciplines spells this craft 'engineer'. If C02 re-derives
51
+ // the mapping instead of consuming disciplineForCraft, the two spellings drift
52
+ // and a builder silently matches nothing.
53
+ assert.equal(help.disciplineForCraft('builder'), 'engineer');
54
+ assert.equal(help.disciplineForCraft('artist'), 'artist');
55
+ assert.equal(help.disciplineForCraft('ideator'), 'ideator');
56
+ assert.equal(help.disciplineForCraft('wizard'), null);
57
+ });
58
+
59
+ // ---- what is stuck --------------------------------------------------------
60
+ test('what_is_stuck is required and must carry content', () => {
61
+ for (const v of [undefined, '', ' ', '\n\t ']) {
62
+ assert.ok(reasons({ needs_craft: 'artist', what_is_stuck: v }).includes('required'),
63
+ `${JSON.stringify(v)} is not a description of being stuck`);
64
+ }
65
+ });
66
+
67
+ test('an over-long description is refused rather than truncated', () => {
68
+ const long = 'x'.repeat(help.MAX_STUCK + 1);
69
+ assert.ok(reasons({ needs_craft: 'artist', what_is_stuck: long }).includes('too_long'));
70
+ });
71
+
72
+ // ---- context --------------------------------------------------------------
73
+ test('a request may name a task OR a goal, never both', () => {
74
+ assert.deepEqual(reasons({ needs_craft: 'artist', what_is_stuck: 'x', task_id: 1 }), []);
75
+ assert.deepEqual(reasons({ needs_craft: 'artist', what_is_stuck: 'x', goal_id: 1 }), []);
76
+ assert.ok(reasons({ needs_craft: 'artist', what_is_stuck: 'x', task_id: 1, goal_id: 2 })
77
+ .includes('two_contexts'), 'two contexts means two answers to "what is this about"');
78
+ });
79
+
80
+ test('context is optional', () => {
81
+ assert.deepEqual(reasons({ needs_craft: 'ideator', what_is_stuck: 'a shape question' }), []);
82
+ });
83
+
84
+ // ---- the schema keeps the same promises -----------------------------------
85
+ test('the CHECK constraints mirror the validator, so neither is the only guard', () => {
86
+ // The validator exists so a refusal names the field; the constraints are the
87
+ // authority. Both must exist or one of the two promises is unkept.
88
+ const sql = read('modules', 'lifecycle', 'migrations', 'lifecycle_004_help_requests.sql');
89
+ assert.match(sql, /needs_craft IN \('artist', 'builder', 'ideator'\)/);
90
+ assert.match(sql, /\(needs_craft IS NOT NULL\) <> \(needs_builder_id IS NOT NULL\)/, 'exactly-one-addressee');
91
+ assert.match(sql, /NOT \(task_id IS NOT NULL AND goal_id IS NOT NULL\)/, 'at most one context');
92
+ assert.match(sql, /length\(btrim\(what_is_stuck\)\) > 0/, 'non-empty description');
93
+ });
94
+
95
+ test('the table is module-owned and lifecycle-namespaced', () => {
96
+ // A NEW table the module owns, unlike goals/tasks which are core tables it only
97
+ // queries — so fitness check 11 permits it (ADR 0083 §Decision #5).
98
+ const sql = read('modules', 'lifecycle', 'migrations', 'lifecycle_004_help_requests.sql');
99
+ assert.match(sql, /CREATE TABLE IF NOT EXISTS lifecycle_help_requests/);
100
+ assert.ok(!/ALTER TABLE (?!lifecycle_)/.test(sql), 'it alters no core table');
101
+ });
102
+
103
+ test('sub-speciality is deliberately NOT modelled yet', () => {
104
+ // The criterion says "and later on sub-speciality". Modelling it now would
105
+ // invent a taxonomy before anyone has asked a real question through this.
106
+ const sql = read('modules', 'lifecycle', 'migrations', 'lifecycle_004_help_requests.sql');
107
+ // Assert on the COLUMN LIST, not the file: the first version of this matched
108
+ // "speciality" anywhere outside a `--` comment and flagged the migration's own
109
+ // COMMENT ON TABLE, which says it is deliberately not modelled. Same
110
+ // over-broad-source-assertion reflex as learning 1000196.
111
+ const body = sql.slice(sql.indexOf('CREATE TABLE'), sql.indexOf(');'));
112
+ const columns = body.split('\n')
113
+ .map((l) => l.replace(/--.*$/, '').trim())
114
+ .filter((l) => l && !/^(CONSTRAINT|CHECK|\))/.test(l));
115
+ assert.ok(!columns.some((l) => /special/i.test(l)), `no speciality column: ${columns.filter((l) => /special/i.test(l))}`);
116
+ });
117
+
118
+ // ---- the read that makes a request answerable -----------------------------
119
+ test('the addressed read excludes your own requests', () => {
120
+ // Answering your own request is not help.
121
+ const src = read('modules', 'lifecycle', 'help-requests.js');
122
+ const fn = src.slice(src.indexOf('async function listOpenForBuilder'));
123
+ assert.match(fn.slice(0, 700), /requested_by <> \$1/);
124
+ assert.match(fn.slice(0, 700), /status = 'open'/, 'and settled requests are history');
125
+ });
126
+
127
+ test('only the ASKER can settle their own request', () => {
128
+ const src = read('modules', 'lifecycle', 'help-requests.js');
129
+ const fn = src.slice(src.indexOf('async function setHelpRequestStatus'));
130
+ assert.match(fn.slice(0, 600), /requested_by = \$2/, 'ownership is in the WHERE, not in rank');
131
+ assert.match(fn.slice(0, 600), /status = 'open'/, 'and a settled request cannot be re-settled');
132
+ });
133
+
134
+ // ---- it stands alone ------------------------------------------------------
135
+ test('C01 depends on nothing social — it must stay independently shippable', () => {
136
+ const src = read('modules', 'lifecycle', 'help-requests.js');
137
+ const routes = read('modules', 'lifecycle', 'routes', 'help-requests.js');
138
+ for (const forbidden of ['closeness', 'mingle', 'agora', 'Agora']) {
139
+ assert.ok(!new RegExp(`require\\([^)]*${forbidden}`, 'i').test(src + routes),
140
+ `must not depend on ${forbidden}`);
141
+ }
142
+ });
143
+
144
+ test('the route is registered in the module manifest', () => {
145
+ // A route file nothing mounts is a file, not a surface.
146
+ const manifest = JSON.parse(read('modules', 'lifecycle', 'module.json'));
147
+ assert.ok(manifest.contributes.routes.includes('help-requests'));
148
+ });
149
+
150
+ // ---- the four grader findings from the first pass -------------------------
151
+ const needs = require(path.join(ROOT, 'modules', 'builder-settings', 'builder-needs.js'));
152
+
153
+ test('THE BLOCKER: a request REACHES people, it is not merely stored', () => {
154
+ // The criterion is "...and it reaches the right craft". Storing is not reaching.
155
+ // The reach lands in the needs roster the hall card and the CLI nudge already
156
+ // render, rather than in a second notification system invented beside it.
157
+ const n = needs.helpRequestsNeed({ openHelpRequests: 2 });
158
+ assert.ok(n, 'a pending request produces a need');
159
+ assert.equal(n.state, 'action_needed', 'a person is waiting on a human answer');
160
+ assert.match(n.message, /2 people/);
161
+ assert.ok(n.action && n.action.href, 'and it says where to go');
162
+ });
163
+
164
+ test('no requests produces NO need, not a cheerful empty state', () => {
165
+ // An empty state is not an input the system needs from you.
166
+ for (const ctx of [{ openHelpRequests: 0 }, {}, { openHelpRequests: undefined }]) {
167
+ assert.equal(needs.helpRequestsNeed(ctx), null);
168
+ }
169
+ });
170
+
171
+ test('the need is registered, so every renderer picks it up', () => {
172
+ const roster = needs.computeNeeds({ openHelpRequests: 1, ownKeySet: true, rank: 'metic' });
173
+ assert.ok(roster.items.some((i) => i.id === 'help_requests'), 'it is in the roster');
174
+ assert.ok(roster.action_needed_count >= 1);
175
+ });
176
+
177
+ test('THE PRIVACY BUG: craft comes from the caller, never from the query', () => {
178
+ // The first version took ?craft= and validated it only against the enum, so any
179
+ // builder could read every request addressed to a craft they do not belong to
180
+ // by trying each value. The param may now only NARROW what the caller has.
181
+ const routes = read('modules', 'lifecycle', 'routes', 'help-requests.js');
182
+ assert.match(routes, /craftsForBuilder\(req\.builder\.id\)/, "the caller's own crafts are read");
183
+ const forMe = routes.slice(routes.indexOf("'GET /help-requests/for-me'"));
184
+ const body = forMe.slice(0, forMe.indexOf('errorCode'));
185
+ assert.match(body, /mine\.filter\(/, 'the query param only intersects');
186
+ assert.ok(!/listOpenForBuilder\(\{[^}]*craft: (want|req\.query)/.test(body),
187
+ 'the raw query value never reaches the read');
188
+ });
189
+
190
+ test('a builder with no declared craft sees only requests addressed BY NAME', () => {
191
+ // Fail-closed: belonging to a craft is a claim about yourself the system must
192
+ // not assume on your behalf.
193
+ const src = read('modules', 'lifecycle', 'help-requests.js');
194
+ const fn = src.slice(src.indexOf('async function craftsForBuilder'));
195
+ assert.match(fn.slice(0, 500), /preferred_disciplines/);
196
+ assert.match(fn.slice(0, 500), /filter\(Boolean\)/, 'unmapped disciplines drop out');
197
+ });
198
+
199
+ test('the settle refusal no longer lets you enumerate ids', () => {
200
+ // The comment claimed one refusal for three cases; the code looked the row up
201
+ // and said which. Comment and code now agree.
202
+ const routes = read('modules', 'lifecycle', 'routes', 'help-requests.js');
203
+ const patch = routes.slice(routes.indexOf("'PATCH /help-requests/:id'"));
204
+ assert.ok(!/getHelpRequest/.test(patch), 'no existence lookup in the refusal path');
205
+ const reasons = patch.match(/reason: '[^']+'/g) || [];
206
+ assert.equal(reasons.length, 1, `exactly one refusal reason, got ${reasons}`);
207
+ });
208
+
209
+ test('both reads are capped', () => {
210
+ assert.equal(help.capRows(5), 5);
211
+ assert.equal(help.capRows(999999), help.MAX_ROWS, 'an absurd limit is capped');
212
+ for (const bad of [0, -1, null, 'x', 1.5]) {
213
+ assert.equal(help.capRows(bad), help.MAX_ROWS, `${bad} falls back to the cap`);
214
+ }
215
+ });
216
+
217
+ test('core reaches the count through the PORT, never by importing lifecycle', () => {
218
+ const me = read('src', 'bongos', 'routes', 'me.js');
219
+ assert.match(me, /openHelpRequestsFor/);
220
+ assert.ok(!/require\([^)]*modules\/lifecycle/.test(me), 'no module import in the kernel (ADR 0093)');
221
+ // The fail-open lives in the fan-out's IIFE now, not a standalone try/catch —
222
+ // the property is what matters, so assert the property.
223
+ const fan = me.slice(me.indexOf('] = await Promise.all(['));
224
+ const iife = fan.slice(fan.indexOf('openHelpRequestsFor') - 400, fan.indexOf('openHelpRequestsFor') + 300);
225
+ assert.match(iife, /catch \(_\) \{ return 0; \}/, 'fail-open: /me must not break on a collab read');
226
+ });
227
+
228
+ // ---- the second-pass grader findings --------------------------------------
229
+ test('the /me read is IN the fan-out, not a serialized await before it', () => {
230
+ // /me is the session-open hot path; every other per-builder read is batched
231
+ // into one Promise.all to keep latency at one round-trip depth. The first
232
+ // version added ~2 serialized round trips in front of it (grader).
233
+ const me = read('src', 'bongos', 'routes', 'me.js');
234
+ const fanStart = me.indexOf('] = await Promise.all([');
235
+ const fanEnd = me.indexOf('\n ]);', fanStart);
236
+ assert.ok(fanStart > 0 && fanEnd > fanStart);
237
+ const fan = me.slice(fanStart, fanEnd);
238
+ assert.match(fan, /openHelpRequestsFor/, 'the read is inside the fan-out');
239
+ assert.match(me.slice(0, fanStart).slice(-2000), /helpRequestCount,/, 'and destructured from it');
240
+ assert.ok(!/helpRequestCount = await /.test(me), 'no standalone await remains');
241
+ });
242
+
243
+ test('the count is ONE query, and its craft map is generated from the JS map', () => {
244
+ // Two sequential queries on a hot path is a cost every builder pays on every
245
+ // open. The SQL CASE is generated from DISCIPLINE_TO_CRAFT so the two spellings
246
+ // of 'engineer' cannot drift apart.
247
+ const sql = help.craftMapSql('d');
248
+ assert.match(sql, /WHEN 'engineer' THEN 'builder'/);
249
+ for (const craft of help.helpCrafts()) {
250
+ const disc = help.disciplineForCraft(craft);
251
+ assert.ok(sql.includes(`WHEN '${disc}' THEN '${craft}'`), `${disc}->${craft} is in the SQL map`);
252
+ }
253
+ const src = read('modules', 'lifecycle', 'help-requests.js');
254
+ const fn = src.slice(src.indexOf('async function countOpenForBuilder'));
255
+ const body = fn.slice(0, fn.indexOf('\n}'));
256
+ assert.equal((body.match(/activePool\.query/g) || []).length, 1, 'exactly one round trip');
257
+ });
258
+
259
+ test('a help request addressed to a non-existent builder is a named 400', () => {
260
+ const routes = read('modules', 'lifecycle', 'routes', 'help-requests.js');
261
+ assert.match(routes, /builderExists/);
262
+ assert.match(routes, /no_such_builder/, 'the refusal names the field, not an FK 500');
263
+ });