@bongos/core 1.19.467 → 1.19.469

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 (33) hide show
  1. package/.bongos-core.json +86 -36
  2. package/docs/adr/0225-privacy-suppresses-cross-project-surfaces-only.md +84 -0
  3. package/docs/adr/0227-a-task-network-map-is-refused-the-goal-graph-is-the-map.md +169 -0
  4. package/docs/adr/README.md +2 -0
  5. package/docs/copy-inventory.md +5 -10
  6. package/docs/copy-registry.json +15 -92
  7. package/docs/file-map.md +5 -1
  8. package/docs/module-api-changelog.md +4 -0
  9. package/docs/research/task-network-map/a-every-task-p2.png +0 -0
  10. package/docs/research/task-network-map/a-every-task-p40.png +0 -0
  11. package/docs/research/task-network-map/b-dependency-forest-p2.png +0 -0
  12. package/docs/research/task-network-map/b-dependency-forest-p40.png +0 -0
  13. package/docs/research/task-network-map/c-goals-p2.png +0 -0
  14. package/docs/research/task-network-map/c-goals-p40.png +0 -0
  15. package/docs/research/task-network-map/d-joined-by-goal.png +0 -0
  16. package/docs/research/task-network-map-readability.md +191 -0
  17. package/modules/hall-ui/public/people.css +3 -2
  18. package/modules/hall-ui/public/people.html +2 -2
  19. package/modules/hall-ui/public/people.js +5 -4
  20. package/modules/hall-ui/public/people.states.json +1 -1
  21. package/modules/hall-ui/public/settings.js +1 -1
  22. package/modules/hall-ui/public/style.css +4 -3
  23. package/package-lock.json +2 -2
  24. package/package.json +1 -1
  25. package/scripts/gds/spike-task-map.js +730 -0
  26. package/src/bongos/serve-internal.js +20 -8
  27. package/src/module-api.js +1 -1
  28. package/tests/hall_page_gate_map.mjs +4 -2
  29. package/tests/hall_reading_world.mjs +2 -2
  30. package/tests/hall_roster_world.mjs +40 -12
  31. package/tests/spike_task_map.mjs +0 -0
  32. package/modules/hall-ui/public/scouting.html +0 -69
  33. package/modules/hall-ui/public/scouting.states.json +0 -59
@@ -837,13 +837,25 @@ function mountInternalSurfaces(app) {
837
837
  // exact permission its data route (GET /scouting) checks — same posture as the
838
838
  // /government tab above (the page can never drift wider than the API it fronts).
839
839
  const PEOPLE_PAGE_RE = /^\/builders\/people(?:\.html)?\/?$/;
840
- // /scouting is the LEGACY twin of /people — the same /api/gds/scouting directory,
841
- // left behind when task 1002577 graduated it into the hall. It has no nav entry
842
- // and its own header claims "Archon-only", but it was never page-gated: the page
843
- // rendered for anyone and leaned entirely on the data route's 403. Gated on the
844
- // same atom as /people so the two can't drift apart (task 1002710). Deleting it
845
- // in favour of /people is a separate call — it is walled, not removed, here.
846
- const SCOUTING_PAGE_RE = /^\/builders\/scouting(?:\.html)?\/?$/;
840
+ // /scouting WAS the legacy twin of /people — the same /api/gds/scouting
841
+ // directory, left behind when task 1002577 graduated it into the hall. Task
842
+ // 1003470 had already made the two one page (scouting.html rendered people's
843
+ // body byte-for-byte and scouting.js was deleted); task 1003440 removes the
844
+ // door itself, because a second URL onto one directory is a surface to keep in
845
+ // sync for no reader. The URL survives as a 301 so an old bookmark lands on the
846
+ // directory instead of a 404. It carries no page gate: the redirect discloses
847
+ // nothing, and /people's wall applies the instant the browser follows it.
848
+ const SCOUTING_LEGACY_RE = /^\/builders\/scouting(?:\.html)?\/?$/;
849
+ app.use((req, res, next) => {
850
+ if (isModuleSurfaceHost(req)) return next();
851
+ if (req.method !== 'GET' && req.method !== 'HEAD') return next();
852
+ if (!SCOUTING_LEGACY_RE.test(path.posix.normalize(req.path))) return next();
853
+ const qs = req.url.includes('?') ? req.url.slice(req.url.indexOf('?')) : '';
854
+ // On the builders subdomain the /builders prefix is redundant (the canonical
855
+ // redirect above strips it), so emit the clean path and save a second hop.
856
+ return res.redirect(301, `${isBuildersHost(req) ? '' : '/builders'}/people${qs}`);
857
+ });
858
+
847
859
  app.use((req, res, next) => {
848
860
  if (isModuleSurfaceHost(req)) return next();
849
861
  if (!req.path.startsWith('/builders')) return next();
@@ -862,7 +874,7 @@ function mountInternalSurfaces(app) {
862
874
  if (GOVERNMENT_PAGE_RE.test(norm)) {
863
875
  return gdsAuth.requireGovernmentPage(req, res, next);
864
876
  }
865
- if (PEOPLE_PAGE_RE.test(norm) || SCOUTING_PAGE_RE.test(norm)) {
877
+ if (PEOPLE_PAGE_RE.test(norm)) {
866
878
  return gdsAuth.requireProjectCuratePage(req, res, next);
867
879
  }
868
880
  next();
package/src/module-api.js CHANGED
@@ -33,7 +33,7 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
33
33
  // there. scripts/gds/bump-version.js still rewrites the literal below; it appends
34
34
  // the entry to that file. Look for a version's history there, not here.
35
35
  // ---------------------------------------------------------------------------
36
- const CORE_VERSION = '1.19.467'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
36
+ const CORE_VERSION = '1.19.469'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
37
37
 
38
38
  // A namespaced logger so a module's log lines are attributable + consistent.
39
39
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
@@ -63,9 +63,11 @@ const ORACLE = {
63
63
  '/builders/sessions': 'requireGovernmentPage',
64
64
 
65
65
  // Metic+ via the `project.curate` atom — the exact permission GET /scouting
66
- // checks, so neither page can drift wider than the API it fronts.
66
+ // checks, so the page can never drift wider than the API it fronts. Its
67
+ // legacy twin /builders/scouting is gone (task 1003440): the URL now 301s to
68
+ // /people before this dispatch runs, so it holds no gate and has no row —
69
+ // tests/hall_roster_world.mjs owns the proof that the redirect is there.
67
70
  '/builders/people': 'requireProjectCuratePage',
68
- '/builders/scouting': 'requireProjectCuratePage',
69
71
 
70
72
  // Metic+ since R16 (goal 1000069): the Government page is three rooms —
71
73
  // Permissions · Board Room · Constitution (reordered, task 1003269) — and only
@@ -364,8 +364,8 @@ test('no reading room carries an inline <style>, and none needs an audit excepti
364
364
  assert.ok(budgets, 'hall_audit.mjs still declares the ratchet');
365
365
  assert.doesNotMatch(budgets[1].replace(/\/\/[^\n]*/g, ''), /['"][a-z-]+\.html['"]\s*:/,
366
366
  'no page carries an inline-style exception any more');
367
- // (scouting.html keeps a one-line block, comfortably inside the DEFAULT
368
- // budget — an exception is what this asserts is gone, not every <style>.)
367
+ // (an exception is what this asserts is gone, not every <style>. scouting.html
368
+ // was the last page carrying a one-line block; task 1003440 deleted the page.)
369
369
  });
370
370
 
371
371
  test('roadmap\'s two sub-24px controls clear the floor, on both axes', () => {
@@ -5,8 +5,8 @@
5
5
  // in the open and the rest behind the kit's disclosure, the readiness is the
6
6
  // family's figure band in the same dot language as the roll line, the view
7
7
  // toggle is a view and not a filter, the accent lands on the one live door; the
8
- // two directory pages are ONE page (the legacy door renders the same body from
9
- // the same script) and the directory shows the avatar, the name and the login
8
+ // legacy /scouting door is deleted and its URL redirects onto the one directory
9
+ // page (task 1003440), which shows the avatar, the name and the login
10
10
  // the API returns; the session ledger drops the nowrap + wrapper pair that broke
11
11
  // its sticky header, makes every id a door, and names its floor from the pack.
12
12
  import test from 'node:test';
@@ -165,17 +165,47 @@ test('goals.css and people.css spend only the fifteen, and every token they name
165
165
  });
166
166
 
167
167
  // =========================================================================
168
- // PEOPLE + SCOUTING — one directory, two doors
168
+ // PEOPLE — one directory, one door
169
169
  // =========================================================================
170
170
 
171
- test('scouting is the same page as people: one body, one script, no script of its own', () => {
171
+ // Task 1003470 made /scouting render people.html byte-for-byte; task 1003440
172
+ // removes the door itself. Two URLs onto one directory is a surface to keep in
173
+ // sync that no reader ever asked for — but the URL still has to answer, or a
174
+ // bookmark on the old door 404s.
175
+ test('the legacy /scouting door is deleted, and nothing scouting-shaped is left in the hall', () => {
176
+ for (const f of ['scouting.html', 'scouting.js', 'scouting.states.json', 'scouting.css']) {
177
+ assert.ok(!fs.existsSync(path.join(PUB, f)), f + ' is gone — one directory, one door');
178
+ }
179
+ });
180
+
181
+ test('the /scouting URL redirects to /people, and no dead gate dispatches to it', () => {
182
+ const serve = fs.readFileSync(path.join(ROOT, 'src', 'bongos', 'serve-internal.js'), 'utf8');
183
+ const i = serve.indexOf('const SCOUTING_LEGACY_RE');
184
+ assert.ok(i > 0, 'the retired URL keeps a matcher, so the bookmark still lands somewhere');
185
+ const block = serve.slice(i, i + 1200);
186
+ assert.ok(block.includes('res.redirect(301,'), 'and answers with a permanent redirect');
187
+ assert.ok(block.includes("/people"), 'pointing at the directory that replaced it');
188
+ // RUN the shipped matcher, rather than trusting that it reads right. Anchoring
189
+ // is the half that goes wrong: an unanchored twin would swallow the API route.
190
+ const decl = 'const SCOUTING_LEGACY_RE = ';
191
+ const j = serve.indexOf(decl);
192
+ const rx = eval(serve.slice(j + decl.length, serve.indexOf(';', j))); // eslint-disable-line no-eval
193
+ for (const p of ['/builders/scouting', '/builders/scouting/', '/builders/scouting.html']) {
194
+ assert.ok(rx.test(p), p + ' must reach the redirect');
195
+ }
196
+ for (const p of ['/api/gds/scouting', '/api/bongos/scouting', '/builders/people',
197
+ '/builders/scoutingfoo', '/builders/scouting/extra']) {
198
+ assert.ok(!rx.test(p), p + ' must NOT be redirected — the data route and the directory are not the door');
199
+ }
200
+ // The gate is the half that would rot silently: a page-gate branch still
201
+ // naming a deleted page is a wall guarding nothing.
202
+ assert.ok(!decomment(serve).includes('SCOUTING_PAGE_RE'),
203
+ 'no page gate is left dispatching to a page that no longer exists');
204
+ });
205
+
206
+ test('the directory page loads its own script, sheet and toolkit', () => {
172
207
  const people = read('people.html');
173
- const scouting = read('scouting.html');
174
- const bodyOf = (html) => html.slice(html.indexOf('<body'));
175
- assert.equal(bodyOf(scouting), bodyOf(people), 'the two bodies are byte-identical');
176
- assert.ok(!fs.existsSync(path.join(PUB, 'scouting.js')), 'scouting.js is gone — the third rendering of one endpoint');
177
- assert.doesNotMatch(scouting, /<style[^>]*>/, 'the legacy door carries no inline style');
178
- for (const html of [people, scouting]) {
208
+ for (const html of [people]) {
179
209
  assert.match(html, /\/builders\/people\.js/);
180
210
  assert.match(html, /\/builders\/people\.css/);
181
211
  assert.match(html, /\/builders\/dom-utils\.js/, 'the toolkit the rank label and the number formats come from');
@@ -280,8 +310,6 @@ test('the states files exercise the redesign\'s new states', () => {
280
310
  assert.deepEqual(goals.states.cards.expect.value, [['#f-goal-view', 'cards']]);
281
311
  const people = JSON.parse(read('people.states.json'));
282
312
  assert.ok(people.states.search, 'people: a search is measured');
283
- const scouting = JSON.parse(read('scouting.states.json'));
284
- assert.deepEqual(scouting.states.board.expect.visible, ['#people-scroll'], 'scouting expects the People panel');
285
313
  const sessions = JSON.parse(read('sessions.states.json'));
286
314
  assert.deepEqual(sessions.states.ledger.expect.visible, ['#stats-scroll', '#sessions-scroll']);
287
315
  });
Binary file
@@ -1,69 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <title>People — {{worldName}}</title>
7
- <meta name="description" content="The builder directory: everyone building across {{worldName}} projects, searchable and ranked by track record.">
8
- <link rel="icon" href="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><text x='16' y='24' font-size='24' text-anchor='middle' font-family='sans-serif'>⌂</text></svg>">
9
- <link rel="preconnect" href="https://fonts.googleapis.com">
10
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
- <link rel="stylesheet" href="/builders/style.css?v=2026-07-02-shell1">
12
- <!-- /scouting is the LEGACY DOOR to the People directory (task 1002577
13
- graduated it into the hall; task 1002710 walled it on the same
14
- project.curate gate). Task 1003470 made it the SAME PAGE: the body below
15
- is byte-identical to people.html and runs people.js — the one
16
- /api/gds/scouting rollup used to be drawn three different ways, and a
17
- door that renders a different page from the same data is the drift.
18
- tests/hall_roster_world.mjs holds the two bodies equal. -->
19
- <link rel="stylesheet" href="/builders/people.css">
20
- </head>
21
- <body data-page="people" data-page-title="People">
22
-
23
- <div class="app">
24
- <aside class="app-sidebar" id="app-sidebar" aria-label="Primary"></aside>
25
- <div class="app-scrim" id="app-scrim" hidden></div>
26
- <div class="app-body">
27
- <header class="app-topbar" id="app-topbar"></header>
28
- <div class="app-content">
29
- <!-- The board page head (task 1003307): the label voice, because on a
30
- working surface the page's own name is the least interesting thing on
31
- it. The sub is rewritten by people.js once the directory has loaded. -->
32
- <div class="page-head page-head--board">
33
- <h1 class="page-head__h">People</h1>
34
- <p class="page-head__sub" id="people-sub">Everyone building across hosted projects, ranked by track record. Descriptive only — cross-project numbers never carry authority here.</p>
35
- </div>
36
-
37
- <main id="hall-main">
38
- <!-- task 1003470: ONE directory. /people and its legacy door /scouting
39
- render this same page from the same script — the same
40
- /api/gds/scouting rollup used to be drawn three different ways. -->
41
- <section class="scroll" id="people-scroll" aria-labelledby="people-h">
42
- <h2 id="people-h" class="scroll__h">Builder directory<span class="count-pill" id="people-count" hidden></span></h2>
43
- <div id="people-filters"></div>
44
- <div id="people-body">
45
- <div class="profile__placeholder">Loading the directory…</div>
46
- </div>
47
- </section>
48
- </main>
49
-
50
- <footer class="app-foot" id="app-foot"></footer>
51
- </div>
52
- </div>
53
- </div>
54
-
55
- <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
56
-
57
- <!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
58
- chrome exists before the page script runs. -->
59
- <script src="/builders/shell.js?v=2026-07-02-shell1"></script>
60
- <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
61
- <script src="/builders/palette.js" defer></script>
62
- <!-- shared frontend toolkit (window.OTB — rank labels, number formats) + the
63
- branding pack's currency label, loaded BEFORE people.js. -->
64
- <script src="/builders/dom-utils.js?v=2026-06-14-domutils"></script>
65
- <script src="/builders/brand-apply.js"></script>
66
- <script src="/builders/hall-kit.js"></script>
67
- <script src="/builders/people.js"></script>
68
- </body>
69
- </html>
@@ -1,59 +0,0 @@
1
- {
2
- "_": "Scouting — the LEGACY DOOR to the People directory, on the same project.curate gate. Since task 1003470 it renders the People page itself (the same body, the same people.js), so this file measures the door, not a second design: the same floors at the same widths, with the People panel expected. The three-line inline <style> it used to carry went with the page it belonged to.",
3
- "page": "/builders/scouting",
4
- "surface": "hall-ui",
5
- "stub": {
6
- "prefix": "/builders/"
7
- },
8
- "modeQuery": false,
9
- "_feeds": "The kit's own stub carries no feed for this page, so against it the page renders its EMPTY and SKELETON states — which is the point: those are states the floors must hold in. Against the hall-preview harness the same states render populated. Every declared 404 below is a fixture the harness does not carry, named rather than hidden.",
10
- "ignoreRequests": [
11
- "/api/(gds|bongos)/me$",
12
- "/api/(gds|bongos)/me/",
13
- "/api/(gds|bongos)/goals",
14
- "/api/(gds|bongos)/inbox/",
15
- "/api/(gds|bongos)/versions",
16
- "/api/(gds|bongos)/scouting",
17
- "/api/(gds|bongos)/builders"
18
- ],
19
- "ignoreConsole": [
20
- "/api/(gds|bongos)/",
21
- "\\b401\\b",
22
- "\\b403\\b",
23
- "\\b404\\b",
24
- "\\b501\\b",
25
- "Unauthorized",
26
- "Forbidden",
27
- "Not Found",
28
- "Not Implemented",
29
- "load failed"
30
- ],
31
- "states": {
32
- "board": {
33
- "auth": true,
34
- "actions": [
35
- [
36
- "wait",
37
- 700
38
- ]
39
- ],
40
- "expect": {
41
- "visible": [
42
- "#people-scroll"
43
- ]
44
- }
45
- },
46
- "out": {
47
- "auth": false
48
- }
49
- },
50
- "reducedMotion": {
51
- "auth": true,
52
- "actions": [
53
- [
54
- "wait",
55
- 600
56
- ]
57
- ]
58
- }
59
- }