@bongos/core 1.24.40 → 1.24.41

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 (69) hide show
  1. package/.bongos-core.json +75 -90
  2. package/docs/canonical-permissions.md +2 -1
  3. package/docs/copy-inventory.md +116 -120
  4. package/docs/copy-registry.json +151 -207
  5. package/docs/module-api-changelog.md +2 -0
  6. package/docs/page-inventory.json +11 -69
  7. package/docs/page-readings.json +382 -763
  8. package/modules/hall-ui/public/ai-studio.html +2 -2
  9. package/modules/hall-ui/public/atlas.html +2 -2
  10. package/modules/hall-ui/public/blockers.html +2 -2
  11. package/modules/hall-ui/public/board-room.html +2 -2
  12. package/modules/hall-ui/public/city.html +2 -2
  13. package/modules/hall-ui/public/collab.html +2 -2
  14. package/modules/hall-ui/public/commands.html +2 -2
  15. package/modules/hall-ui/public/copy-desk.html +2 -2
  16. package/modules/hall-ui/public/craft-mode.js +1 -1
  17. package/modules/hall-ui/public/deploy.html +2 -2
  18. package/modules/hall-ui/public/gate.js +12 -5
  19. package/modules/hall-ui/public/goal-map.html +2 -2
  20. package/modules/hall-ui/public/goals.html +2 -2
  21. package/modules/hall-ui/public/government.html +2 -2
  22. package/modules/hall-ui/public/idea.html +2 -2
  23. package/modules/hall-ui/public/ideas.html +2 -2
  24. package/modules/hall-ui/public/index.html +2 -2
  25. package/modules/hall-ui/public/market-watch-item.html +2 -2
  26. package/modules/hall-ui/public/market-watch-rules.html +2 -2
  27. package/modules/hall-ui/public/market-watch.html +2 -2
  28. package/modules/hall-ui/public/modules.html +2 -2
  29. package/modules/hall-ui/public/money.html +2 -2
  30. package/modules/hall-ui/public/my-agents.html +2 -2
  31. package/modules/hall-ui/public/palette.js +10 -0
  32. package/modules/hall-ui/public/primer.html +2 -2
  33. package/modules/hall-ui/public/profile.html +2 -2
  34. package/modules/hall-ui/public/project-settings.html +2 -2
  35. package/modules/hall-ui/public/roadmap.html +2 -2
  36. package/modules/hall-ui/public/roster.html +2 -2
  37. package/modules/hall-ui/public/sessions.js +21 -10
  38. package/modules/hall-ui/public/settings.html +2 -2
  39. package/modules/hall-ui/public/shell.js +1 -2
  40. package/modules/hall-ui/public/street.html +2 -2
  41. package/modules/hall-ui/public/studio.html +2 -2
  42. package/modules/hall-ui/public/task.html +2 -2
  43. package/modules/hall-ui/public/thinking.html +2 -2
  44. package/modules/hall-ui/public/tweak-editor.html +2 -2
  45. package/modules/hall-ui/public/watch.html +120 -7
  46. package/modules/hall-ui/public/watch.js +11 -0
  47. package/modules/hall-ui/public/watch.states.json +49 -1
  48. package/modules/hall-ui/public/work.html +2 -2
  49. package/package-lock.json +2 -2
  50. package/package.json +1 -1
  51. package/release-notes.json +6 -0
  52. package/scripts/bongos/gen-hall-sitemap.js +2 -0
  53. package/src/bongos/serve-internal.js +15 -22
  54. package/src/module-api.js +1 -1
  55. package/tests/autonomy_builder_scope.mjs +2 -1
  56. package/tests/hall_audit.mjs +1 -1
  57. package/tests/hall_loading.mjs +13 -2
  58. package/tests/hall_nav.mjs +5 -2
  59. package/tests/hall_oversight_tabs.mjs +162 -0
  60. package/tests/hall_oversight_world.mjs +23 -9
  61. package/tests/hall_page_gate_map.mjs +7 -2
  62. package/tests/hall_roster_world.mjs +14 -9
  63. package/tests/hall_sessions_ledger.mjs +2 -1
  64. package/tests/nav_permission_atoms.mjs +3 -2
  65. package/tests/sessions_kit_pager.mjs +8 -4
  66. package/modules/hall-ui/public/gate.html +0 -76
  67. package/modules/hall-ui/public/gate.states.json +0 -60
  68. package/modules/hall-ui/public/sessions.html +0 -128
  69. package/modules/hall-ui/public/sessions.states.json +0 -59
@@ -4,7 +4,7 @@
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
6
  <title>Watch — {{worldName}}</title>
7
- <meta name="description" content="Archon oversight for {{worldName}} — access requests, security, stale claims, reviews, and repo health.">
7
+ <meta name="description" content="Archon oversight for {{worldName}} — access requests, security, stale claims, reviews, repo health, sessions and the Autobongos runner.">
8
8
  <!-- The hub's origin, for the link from a guild's head in the applications queue to
9
9
  its page on the hub (task 1002304). Empty on a vanilla instance: no link. -->
10
10
  <meta name="hub-origin" content="{{hubOrigin}}">
@@ -18,6 +18,9 @@
18
18
  shared .stat-row/.stat-tile + hall-kit rules. -->
19
19
  <link rel="stylesheet" href="/builders/oversight.css">
20
20
  <link rel="stylesheet" href="/builders/watch.css">
21
+ <!-- task 1005049: the Sessions and Autobongos tabs bring their pages' own sheets. -->
22
+ <link rel="stylesheet" href="/builders/sessions.css">
23
+ <link rel="stylesheet" href="/builders/gate.css">
21
24
  </head>
22
25
  <body data-page="watch" data-page-title="Watch">
23
26
 
@@ -49,7 +52,7 @@
49
52
  <a class="stat-tile" href="#people"><span class="stat-tile__label">Access requests</span><span class="stat-tile__num" id="att-access">—</span><span class="stat-tile__sub">pending, on the People tab</span></a>
50
53
  <a class="stat-tile" href="#security"><span class="stat-tile__label">Security reports</span><span class="stat-tile__num" id="att-security">—</span><span class="stat-tile__sub">open, on the Security tab</span></a>
51
54
  <a class="stat-tile" href="/roster?claims=stale"><span class="stat-tile__label">Stale claims</span><span class="stat-tile__num" id="att-stale">—</span><span class="stat-tile__sub">builders, on the Builders page</span></a>
52
- <a class="stat-tile" href="#operations"><span class="stat-tile__label">Sessions</span><span class="stat-tile__num" id="att-sessions">—</span><span class="stat-tile__sub">recorded, on the Operations tab</span></a>
55
+ <a class="stat-tile" href="#sessions"><span class="stat-tile__label">Sessions</span><span class="stat-tile__num" id="att-sessions">—</span><span class="stat-tile__sub">recorded, on the Sessions tab</span></a>
53
56
  </div>
54
57
 
55
58
  <!-- task 2173: the nine surfaces sit behind three OTBKit tabs, hash-synced
@@ -89,10 +92,10 @@
89
92
  always fetched and used to throw away (task 1003469). -->
90
93
  <section class="scroll" id="sessions-scroll" aria-labelledby="sessions-h" hidden>
91
94
  <h2 id="sessions-h" class="scroll__h">Sessions</h2>
92
- <p class="scroll__lede">A tally of recorded work sessions, and the latest five. The full record lives on the Sessions page.</p>
95
+ <p class="scroll__lede">A tally of recorded work sessions, and the latest five. The full record is on the Sessions tab.</p>
93
96
  <div id="sessions-stats" class="ov-figs"><span class="kit-skel"></span></div>
94
97
  <div id="sessions-recent"></div>
95
- <p class="ov-foot"><a href="/sessions">View all sessions</a></p>
98
+ <p class="ov-foot"><a href="#sessions">View all sessions</a></p>
96
99
  </section>
97
100
 
98
101
  <!-- 7. REPO HEALTH -->
@@ -147,6 +150,113 @@
147
150
  <div id="surveyor-body"></div>
148
151
  </section>
149
152
  </div>
153
+ <!-- ============ tab: Sessions (task 1005049: was the /sessions page) ============ -->
154
+ <div id="pane-sessions" role="tabpanel" aria-labelledby="tab-sessions" hidden>
155
+ <!-- Every section below starts hidden, so the tab says it is loading until one shows. -->
156
+ <p class="hall-loading hall-loading--page hall-loading--wait" role="status">Loading…</p>
157
+ <!-- Access notice — shown by sessions.js when the viewer is below the
158
+ floor. The heading and the line are written from the pack's rank
159
+ label (ADR 0157: metic and up), so the static text never renders. -->
160
+ <section class="scroll" id="sess-sealed" aria-labelledby="sess-sealed-h" hidden>
161
+ <h2 id="sess-sealed-h" class="scroll__h">Higher ranks only</h2>
162
+ <p class="scroll__lede" id="sess-sealed-body">The session log opens from a higher rank.</p>
163
+ </section>
164
+
165
+ <!-- Totals — the shell's glass strip at the top of the page (the way
166
+ Watch and Gate carry theirs), not a strip inside a panel. -->
167
+ <section class="ov-summary" id="stats-scroll" aria-labelledby="stats-h" hidden>
168
+ <h2 id="stats-h" class="sr-only">Totals</h2>
169
+ <div class="stat-row" id="stats-body"></div>
170
+ </section>
171
+
172
+ <!-- Every recorded session — the corpus table. No wrapper: the shell's
173
+ card strip scrolls a wide ledger on a phone, and the old wrapper
174
+ turned the sticky header into a scrollport overlay on desktop. -->
175
+ <section class="scroll" id="sess-ledger-scroll" aria-labelledby="sess-ledger-h" hidden>
176
+ <h2 id="sess-ledger-h" class="scroll__h">Recorded sessions</h2>
177
+ <!-- The count, and a failed load, sit here: the page head that used to carry
178
+ them is hidden (task 1004829). -->
179
+ <p class="scroll__lede" id="ledger-status" role="status" aria-live="polite">Loading sessions…</p>
180
+ <!-- task 1003975: the ledger's first filter. It LOOKS like the pager's
181
+ per-page control on purpose (one control grammar per page) but wears
182
+ its own class — the hall_audit guard reads `class="pager…"` as a
183
+ hand-rolled pager, and it is right to: borrowing that family is how a
184
+ second pager implementation starts. Its options are filled from the
185
+ server's vocabulary in sessions.js, never hardcoded here, where they
186
+ would drift from what the route accepts. -->
187
+ <div class="ledger-filters">
188
+ <label class="ledger-filter">
189
+ <span>Kind</span>
190
+ <select id="session-type-filter" aria-label="Filter sessions by kind"></select>
191
+ </label>
192
+ </div>
193
+ <table class="ledger" id="sessions-table">
194
+ <thead>
195
+ <tr>
196
+ <th>Session</th><th>Builder</th>
197
+ <th title="What the session did, derived from its own transcript: whether it wrote files, used tools, and how many prompts a person actually sent.">Kind</th>
198
+ <th>Tasks</th>
199
+ <th class="ledger__num">Earned</th><th class="ledger__num">Turns</th><th class="ledger__num">Tokens</th><th class="ledger__num">Tool errors</th>
200
+ <th class="ledger__num">Duration</th>
201
+ <th class="ledger__num" title="How the session read to this builder, scored against their own interaction profile. Advisory — it gates nothing.">Plain speech</th>
202
+ <th>Recorded</th>
203
+ </tr>
204
+ </thead>
205
+ <tbody id="sessions-body"></tbody>
206
+ </table>
207
+ <!-- The kit stamps the pager bar here (sessions.js, OTBKit.pagerBarHtml).
208
+ makeLedger is deliberately NOT used: it stamps table + pager into one
209
+ mount, and this page keeps them separate so the bar sits below the
210
+ ledger rather than inside whatever scrollport the table is in. -->
211
+ <div id="sessions-pager-mount"></div>
212
+ <p class="ledger__note" id="ledger-foot"></p>
213
+ </section>
214
+
215
+ <!-- task 1002830: the narrative. Opened from a row's Read door; the SAME page
216
+ and the same gate as the ledger above (ADR 0051 — no second page). The
217
+ server decides who may read a transcript and records an administrator's
218
+ cross-builder read BEFORE serving it; this panel only displays. -->
219
+ <section class="scroll" id="transcript-scroll" aria-labelledby="transcript-h" hidden>
220
+ <h2 id="transcript-h" class="scroll__h">Transcript</h2>
221
+ <p class="scroll__lede" id="transcript-sub"></p>
222
+ <p><button type="button" class="btn" id="transcript-close">Close transcript</button></p>
223
+ <div id="transcript-body" class="transcript"></div>
224
+ <p><button type="button" class="btn" id="transcript-more" hidden>Load more turns</button></p>
225
+ </section>
226
+
227
+ </div>
228
+
229
+ <!-- ============ tab: Autobongos (task 1005049: was the /gate page) ============ -->
230
+ <div id="pane-autobongos" role="tabpanel" aria-labelledby="tab-autobongos" hidden>
231
+ <p class="hall-loading hall-loading--page hall-loading--wait" role="status">Loading…</p>
232
+ <!-- task 1003469: the sealed state is a panel with a heading, not a bare
233
+ line on the ground. gate.js writes the real floor into the lede from
234
+ the pack's rank label, so the copy cannot drift from ADR 0157. -->
235
+ <section class="scroll" id="gate-sealed" aria-labelledby="gate-sealed-h" hidden>
236
+ <h2 class="scroll__h" id="gate-sealed-h">Restricted</h2>
237
+ <p class="scroll__lede" id="gate-sealed-body">This page opens at a higher rank.</p>
238
+ </section>
239
+
240
+ <section id="runner-body" hidden>
241
+ <!-- The Autobongos fence (task 1003908, ADR 0307 §6): what is allowed to
242
+ reach main unattended. This page held the held-PR list too until task
243
+ 1004859 removed it; the URL stays /gate so bookmarks keep working.
244
+ Archon-only — a Metic gets the note below instead, and the routes
245
+ behind the panel are system:true/archon server-side regardless. -->
246
+ <p class="scroll__lede" id="gate-archon-only" hidden></p>
247
+ <section class="scroll" id="fence-panel" aria-labelledby="fence-h" hidden>
248
+ <h2 class="scroll__h" id="fence-h">Autobongos runner</h2>
249
+ <!-- One panel (task 1004509): a checklist while anything the runner
250
+ needs is missing, one status line with the switch once it is all
251
+ ticked. Drawn from /autonomy/runners + /autonomy/fence together. -->
252
+ <div id="fence-body"><span class="kit-skel"></span></div>
253
+ <!-- Every builder's runner (task 1004501): what each one picked to
254
+ work, whether you paused it, and whether it is checking in. -->
255
+ <div id="fence-builders"></div>
256
+ </section>
257
+ </section>
258
+
259
+ </div>
150
260
  </main>
151
261
 
152
262
  <footer class="app-foot" id="app-foot"></footer>
@@ -160,14 +270,17 @@
160
270
  chrome exists before the page script runs. -->
161
271
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
162
272
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
163
- <script src="/builders/craft-mode.js?v=2026-11-05-sections"></script>
164
- <script src="/builders/shell.js?v=2026-11-05-sections"></script>
273
+ <script src="/builders/craft-mode.js?v=2026-11-05-oversight"></script>
274
+ <script src="/builders/shell.js?v=2026-11-05-oversight"></script>
165
275
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
166
276
  <script src="/builders/palette.js" defer></script>
167
277
  <!-- shared component kit (window.OTBKit) — tabs, ledger, pager (task 2166). -->
168
278
  <script src="/builders/hall-kit.js"></script>
169
279
  <script src="/builders/task-detail.js?v=2026-07-03-taskdetail"></script>
170
280
  <script src="/builders/brand-apply.js?v=2026-06-19-r56"></script>
171
- <script src="/builders/watch.js?v=2026-11-05-roster"></script>
281
+ <!-- task 1005049: the two tabs' scripts, booted by watch.js when their tab first opens. -->
282
+ <script src="/builders/sessions.js?v=2026-11-05-tab"></script>
283
+ <script src="/builders/gate.js?v=2026-11-05-tab"></script>
284
+ <script src="/builders/watch.js?v=2026-11-05-tabs"></script>
172
285
  </body>
173
286
  </html>
@@ -21,6 +21,9 @@
21
21
  // loads independently in its own try/catch so one failing endpoint cannot
22
22
  // break the others.
23
23
  //
24
+ // TASK 1005049 folded the Sessions page (sessions.js) and the Autobongos runner
25
+ // panel (gate.js) in as two more tabs; /sessions and /gate redirect to them.
26
+ //
24
27
  // TASK 1003469 put the page in the chrome world's composition: every list is
25
28
  // the kit's .ledger through makeLedger (eight of nine were a page-local table
26
29
  // that duplicated it with a hand-rolled th voice), every figure row is the oversight
@@ -1283,7 +1286,15 @@
1283
1286
  { id: 'people', label: 'People', panel: 'pane-people' },
1284
1287
  { id: 'operations', label: 'Operations', panel: 'pane-operations' },
1285
1288
  { id: 'security', label: 'Security', panel: 'pane-security' },
1289
+ // task 1005049 (owner decision 2026-11-03, idea 1001315): the Sessions and
1290
+ // Autobongos pages are tabs here. Each boots the first time it opens.
1291
+ { id: 'sessions', label: 'Sessions', panel: 'pane-sessions' },
1292
+ { id: 'autobongos', label: 'Autobongos', panel: 'pane-autobongos' },
1286
1293
  ],
1294
+ onChange: (id) => {
1295
+ const boot = window.OTBWatchTabs && window.OTBWatchTabs[id];
1296
+ if (boot) boot();
1297
+ },
1287
1298
  });
1288
1299
  }
1289
1300
  }
@@ -15,7 +15,9 @@
15
15
  "/api/(gds|bongos)/versions",
16
16
  "/api/(gds|bongos)/watch",
17
17
  "/api/(gds|bongos)/analytics",
18
- "/api/(gds|bongos)/tasks"
18
+ "/api/(gds|bongos)/tasks",
19
+ "/api/(gds|bongos)/sessions",
20
+ "/api/(gds|bongos)/autonomy/"
19
21
  ],
20
22
  "ignoreConsole": [
21
23
  "/api/(gds|bongos)/",
@@ -92,6 +94,52 @@
92
94
  "#watch-scroll"
93
95
  ]
94
96
  }
97
+ },
98
+ "sessions": {
99
+ "auth": true,
100
+ "actions": [
101
+ [
102
+ "wait",
103
+ 700
104
+ ],
105
+ [
106
+ "click",
107
+ "#tab-sessions"
108
+ ],
109
+ [
110
+ "wait",
111
+ 500
112
+ ]
113
+ ],
114
+ "expect": {
115
+ "visible": [
116
+ "#pane-sessions",
117
+ "#stats-scroll",
118
+ "#sess-ledger-scroll"
119
+ ]
120
+ }
121
+ },
122
+ "autobongos": {
123
+ "auth": true,
124
+ "actions": [
125
+ [
126
+ "wait",
127
+ 700
128
+ ],
129
+ [
130
+ "click",
131
+ "#tab-autobongos"
132
+ ],
133
+ [
134
+ "wait",
135
+ 500
136
+ ]
137
+ ],
138
+ "expect": {
139
+ "visible": [
140
+ "#pane-autobongos"
141
+ ]
142
+ }
95
143
  }
96
144
  },
97
145
  "reducedMotion": {
@@ -173,8 +173,8 @@
173
173
  time so the chrome exists before the deferred page scripts execute. -->
174
174
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
175
175
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
176
- <script src="/builders/craft-mode.js?v=2026-11-05-sections"></script>
177
- <script src="/builders/shell.js?v=2026-11-05-sections"></script>
176
+ <script src="/builders/craft-mode.js?v=2026-11-05-oversight"></script>
177
+ <script src="/builders/shell.js?v=2026-11-05-oversight"></script>
178
178
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
179
179
  <script src="/builders/palette.js" defer></script>
180
180
  <!-- task 1004727: the ONE karma vote control every task/idea list renders -->
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.24.40",
3
+ "version": "1.24.41",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.24.40",
9
+ "version": "1.24.41",
10
10
  "license": "AGPL-3.0-or-later",
11
11
  "dependencies": {
12
12
  "express": "^4.21.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.24.40",
3
+ "version": "1.24.41",
4
4
  "description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "main": "src/platform-server.js",
@@ -11203,5 +11203,11 @@
11203
11203
  "id": "1005048",
11204
11204
  "text": "The Builders page is now the one list of everyone who builds here. If you hold a government seat you also see each builder's active claims, any stale claim, their standing, and the controls to change a rank or offboard someone"
11205
11205
  }
11206
+ ],
11207
+ "1.24.41": [
11208
+ {
11209
+ "id": "1005049",
11210
+ "text": "Watch is now the one oversight page. Sessions and Autobongos moved off the sidebar and became two tabs on Watch, next to People, Operations and Security. Old links to /sessions and /gate open the right tab. Who can see and pre"
11211
+ }
11206
11212
  ]
11207
11213
  }
@@ -62,6 +62,8 @@ const ROUTES = {
62
62
  '/ranks': { to: '/primer', why: 'now a Primer chapter, redirects (task 1005046)' },
63
63
  '/drachmae': { to: '/primer', why: 'now a Primer chapter, redirects (task 1005046)' },
64
64
  '/diagrams': { to: '/primer', why: 'now a Primer chapter, redirects; its images still serve (task 1005046)' },
65
+ '/sessions': { to: '/watch', why: 'now a Watch tab, redirects (task 1005049)' },
66
+ '/gate': { to: '/watch', why: 'Autobongos, now a Watch tab, redirects (task 1005049)' },
65
67
  };
66
68
  // Targets that leave the hall on purpose: sign-in, the hub's /account (platform-server.js;
67
69
  // settings.js points the link at the hub), the API, module assets, and any file with an extension.
@@ -927,23 +927,13 @@ function mountInternalSurfaces(app) {
927
927
  // The apex hands /builders/* (ADR 0036) and /deploy (task 1004736) to the hall's own host.
928
928
  mountApexHallRedirects(app, { skip: (req) => isModuleSurfaceHost(req) || isBuildersHost(req), hostOf: (req) => siteOf(req).host });
929
929
 
930
- // The /watch monitoring page and the /gate page (task 1179) both sit behind
931
- // `page.view.government` — Metic and up since ADR 0157. Same server-side page
932
- // gate, same posture: a builder without the permission never receives the shell
933
- // (requireGovernmentPage — named requireArchonPage before ADR 0157 — redirects
934
- // them to the hall, and an unauthenticated visitor into sign-in). The subdomain
935
- // shim above has already rewritten the bare /watch + /gate onto /builders/*.
936
- // (Defense in depth: every data endpoint these pages call carries its own
937
- // server-side gate.)
930
+ // The /watch monitoring page sits behind `page.view.government` — Metic and up
931
+ // since ADR 0157: a builder without the permission never receives the shell
932
+ // (requireGovernmentPage redirects them to the hall, and an unauthenticated
933
+ // visitor into sign-in). Its Sessions and Autobongos tabs were the /sessions and
934
+ // /gate pages until task 1005049, behind this same gate. (Defense in depth: every
935
+ // data endpoint the page calls carries its own server-side gate.)
938
936
  const WATCH_PAGE_RE = /^\/builders\/watch(?:\.html)?\/?$/;
939
- const GATE_PAGE_RE = /^\/builders\/gate(?:\.html)?\/?$/;
940
- // /sessions (the session-log ledger) belongs to that same Governance group and
941
- // its data route (`session.search`) carries the same metic floor since ADR 0157
942
- // — but it had NO page gate at all: sessions.js only called showSealed() after
943
- // /api/bongos/me, so the shell was served to anyone who asked for the URL. The
944
- // data never leaked (the route gate held), but the page was the one item in the
945
- // group without a wall. Gated here so the group is uniform (task 1002710).
946
- const SESSIONS_PAGE_RE = /^\/builders\/sessions(?:\.html)?\/?$/;
947
937
  // /project-settings — the five fundamentals of the project (task 1003280, goal
948
938
  // 1000072). Government tier, beside /watch and /sessions: it is a STEERING surface,
949
939
  // and ADR 0157 put that floor at metic. Both writes behind it gate independently on
@@ -1049,14 +1039,18 @@ function mountInternalSurfaces(app) {
1049
1039
  return res.redirect(302, `${isBuildersHost(req) ? '' : '/builders'}/market-watch${qs}#settings`);
1050
1040
  });
1051
1041
 
1052
- // task 1005047: Fleet costs is /money's #hosting section now; 302 and no gate, as above.
1053
- const RETIRED_FLEET_RE = /^\/builders\/fleet(?:\.html)?\/?$/;
1042
+ // Pages folded into a section of another page; 302 and no gate, as above. Task 1005047:
1043
+ // Fleet costs is /money#hosting. Task 1005049: Sessions and Autobongos are Watch tabs.
1044
+ const RETIRED_TO_SECTION = Object.freeze({ fleet: 'money#hosting', sessions: 'watch#sessions', gate: 'watch#autobongos' });
1045
+ const RETIRED_SECTION_RE = /^\/builders\/(fleet|sessions|gate)(?:\.html)?\/?$/;
1054
1046
  app.use((req, res, next) => {
1055
1047
  if (isModuleSurfaceHost(req)) return next();
1056
1048
  if (req.method !== 'GET' && req.method !== 'HEAD') return next();
1057
- if (!RETIRED_FLEET_RE.test(path.posix.normalize(req.path))) return next();
1049
+ const m = RETIRED_SECTION_RE.exec(path.posix.normalize(req.path));
1050
+ if (!m) return next();
1058
1051
  const qs = req.url.includes('?') ? req.url.slice(req.url.indexOf('?')) : '';
1059
- return res.redirect(302, `${isBuildersHost(req) ? '' : '/builders'}/money${qs}#hosting`);
1052
+ const [page, section] = RETIRED_TO_SECTION[m[1]].split('#');
1053
+ return res.redirect(302, `${isBuildersHost(req) ? '' : '/builders'}/${page}${qs}#${section}`);
1060
1054
  });
1061
1055
 
1062
1056
  // task 1005046 (owner decision 2026-11-03, idea 1001311): ONE reading room. Ranks,
@@ -1104,8 +1098,7 @@ function mountInternalSurfaces(app) {
1104
1098
  if (isModuleSurfaceHost(req)) return next();
1105
1099
  if (!req.path.startsWith('/builders')) return next();
1106
1100
  const norm = path.posix.normalize(req.path);
1107
- if (WATCH_PAGE_RE.test(norm) || GATE_PAGE_RE.test(norm)
1108
- || SESSIONS_PAGE_RE.test(norm) || PROJECT_SETTINGS_PAGE_RE.test(norm)
1101
+ if (WATCH_PAGE_RE.test(norm) || PROJECT_SETTINGS_PAGE_RE.test(norm)
1109
1102
  || MONEY_PAGE_RE.test(norm)) {
1110
1103
  return gdsAuth.requireGovernmentPage(req, res, next);
1111
1104
  }
package/src/module-api.js CHANGED
@@ -81,7 +81,7 @@ const { sleep } = require('./sleep');
81
81
  // MAJOR (see allowBoxScope below): passes the request through untouched.
82
82
  function deprecatedNoopMiddleware(_req, _res, next) { next(); }
83
83
 
84
- const CORE_VERSION = '1.24.40'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
84
+ const CORE_VERSION = '1.24.41'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
85
85
 
86
86
  // A namespaced logger so a module's log lines are attributable + consistent.
87
87
  // Usage: const log = api.logger('discord'); log.info('mounted');
@@ -460,7 +460,8 @@ test('the owner page draws the PROJECT fence and pauses one builder; the setting
460
460
  assert.match(gate, /const fence = raw\.project \|\| raw;/, 'the switch panel must not draw the owner’s own narrowed picks as the project list');
461
461
  assert.match(gate, /\/autonomy\/runners\/all/);
462
462
  assert.match(gate, /\/autonomy\/builders\/\$\{encodeURIComponent\(id\)\}\/pause/);
463
- const html = fs.readFileSync(path.join(ROOT, 'modules/hall-ui/public/gate.html'), 'utf8');
463
+ // task 1005049: the owner panel is Watch's Autobongos tab.
464
+ const html = fs.readFileSync(path.join(ROOT, 'modules/hall-ui/public/watch.html'), 'utf8');
464
465
  assert.match(html, /id="fence-builders"/);
465
466
 
466
467
  const panel = fs.readFileSync(path.join(ROOT, 'modules/hall-ui/public/settings-autobongos.js'), 'utf8');
@@ -233,7 +233,7 @@ test('the session log pages on the kit — the last hand-roll is gone', () => {
233
233
  assert.match(js, new RegExp(`kit\\.${fn}\\(`), `sessions.js must page through the kit's ${fn}`);
234
234
  }
235
235
  assert.doesNotMatch(js, /const PAGE_SIZE\b/, 'its fixed page size is gone — per-page is the kit\'s now');
236
- assert.match(read('sessions.html'), /hall-kit\.js/, 'and the page has to load the kit');
236
+ assert.match(read('watch.html'), /hall-kit\.js/, 'and Watch, whose Sessions tab it is (task 1005049), has to load the kit');
237
237
  });
238
238
 
239
239
  // ---- 6b. every hall bar series is drawn by the kit (task 1003445) ---------
@@ -83,7 +83,7 @@ test('the page loaders step aside by themselves, and never show without :has()',
83
83
  assert.match(STYLE, /\.hall-loading--wait, \.hall-loading--zones \{ display: none; \}/);
84
84
  assert.match(STYLE, /@supports selector\(:has\(a\)\) \{\s*\.hall-loading--wait:not\(:has\(~ :not\(\[hidden\], script, template, style\)\)\) \{ display: flex; \}\s*\.hall-loading--zones:not\(:has\(~ :not\(\[hidden\]\) > :not\(\[hidden\], script, template, style\)\)\) \{ display: flex; \}\s*\}/);
85
85
  // A page whose sections all start [hidden] carries the --wait loader FIRST in its main.
86
- for (const page of ['board-room', 'deploy', 'gate', 'government', 'money', 'project-settings', 'sessions']) {
86
+ for (const page of ['board-room', 'deploy', 'government', 'money', 'project-settings']) {
87
87
  const html = read(`${page}.html`);
88
88
  const main = html.slice(html.indexOf('<main id="hall-main">'));
89
89
  const first = main.replace(/<main id="hall-main">\s*(<!--[\s\S]*?-->\s*)*/, '');
@@ -95,6 +95,17 @@ test('the page loaders step aside by themselves, and never show without :has()',
95
95
  }
96
96
  assert.match(read('index.html'), /<section class="view" id="view-home"[^>]*>\s*<!--[^>]*-->\s*<p class="hall-loading hall-loading--page hall-loading--zones" role="status">Loading…<\/p>/, 'Home waits on its zones');
97
97
  assert.match(read('watch.html'), /<main id="hall-main">\s*<!--[^>]*-->\s*<p class="hall-loading hall-loading--page hall-loading--zones" role="status">Loading…<\/p>/, 'Watch waits on its panes');
98
+ // task 1005049: the Sessions and Autobongos tabs (once their own pages) open on the
99
+ // --wait loader, so a tab whose sections are all still hidden says it is loading.
100
+ for (const pane of ['pane-sessions', 'pane-autobongos']) {
101
+ const html = read('watch.html');
102
+ const inner = html.slice(html.indexOf(`<div id="${pane}"`));
103
+ const body = inner.slice(inner.indexOf('>') + 1).replace(/^\s*(<!--[\s\S]*?-->\s*)*/, '');
104
+ assert.match(body, /^<p class="hall-loading hall-loading--page hall-loading--wait" role="status">Loading…<\/p>/, `${pane} opens on the page loader`);
105
+ const kids = [...body.slice(body.indexOf('</p>') + 4, body.indexOf('\n </div>')).matchAll(/^ {10}<(section|div|p|nav)\b([^>]*)>/gm)];
106
+ assert.ok(kids.length >= 1, `${pane} has sections after the loader`);
107
+ for (const [, tag, attrs] of kids) assert.match(attrs, /\bhidden\b/, `${pane}: a <${tag}> after the loader starts hidden`);
108
+ }
98
109
  // Settings' layout is never [hidden] while it loads, so its loader is put away by hideLayout().
99
110
  assert.match(read('settings.html'), /id="settings-groups"[^>]*>\s*<!--[^>]*-->\s*<p class="hall-loading hall-loading--page" id="settings-loading" role="status">/);
100
111
  assert.match(read('settings.js'), /function hideLayout\(hide\) \{[\s\S]*?getElementById\('settings-loading'\)[\s\S]*?loading\.hidden = true;/);
@@ -105,7 +116,7 @@ test('no hall page waits on a bare line of text any more', () => {
105
116
  // the class would stay behind on the finished sentence. Each is named with why.
106
117
  const IN_PLACE = new Set([
107
118
  'atlas.html#caption', // the map's caption, rewritten by atlas.js
108
- 'sessions.html#ledger-status', // the ledger's status line, rewritten by sessions.js
119
+ 'watch.html#ledger-status', // the Sessions tab's status line, rewritten by sessions.js
109
120
  'goals.html#goal-deps-card-lede', // the dependency card's lede, rewritten by goals-page.js
110
121
  'goals.html#goal-deps-lede',
111
122
  'profile.html#activity-placeholder', // reused for the signed-out notice by profile.js
@@ -335,7 +335,10 @@ test('sidebar groups read by audience, in order', () => {
335
335
  // 'market-watch-settings' (task 1004938) LEFT the group in task 1005045: the owner saw
336
336
  // "Market Watch" twice and had to guess which was which (owner decision 2026-11-03,
337
337
  // idea 1001310). The settings are an owner-only Settings tab on /market-watch now.
338
- assert.deepEqual(nav[5].items.map((i) => i.id), ['watch', 'gate', 'sessions', 'project-settings', 'money', 'deploy', 'government']);
338
+ // 'gate' (Autobongos) and 'sessions' LEFT the group in task 1005049 (owner decision
339
+ // 2026-11-03, idea 1001315): they are tabs on Watch, and /gate + /sessions redirect there.
340
+ assert.deepEqual(nav[5].items.map((i) => i.id), ['watch', 'project-settings', 'money', 'deploy', 'government']);
341
+ assert.ok(!nav.flatMap((g) => g.items).some((i) => i.href === '/gate' || i.href === '/sessions'), 'no item points at a page that became a Watch tab');
339
342
  // task 1005045: and no label is left twice anywhere in the sidebar, so the next pair
340
343
  // fails here instead of in front of the owner.
341
344
  const labels = [...boot().sidebarHtml.matchAll(/<a class="nav-item" [^>]*>[\s\S]*?<span class="nav-item__label">([^<]*)<\/span>/g)].map((m) => m[1]);
@@ -787,7 +790,7 @@ test('no glyph path data reads as an IP address to the release leak scan', () =>
787
790
  // block (task 1004713). These drive the real shell and the real gov-positions.js.
788
791
 
789
792
  const tick = () => new Promise((resolve) => setImmediate(resolve));
790
- const GOVERNING = ['city', 'board-room', 'watch', 'gate', 'sessions', 'project-settings', 'money', 'deploy', 'government'];
793
+ const GOVERNING = ['city', 'board-room', 'watch', 'project-settings', 'money', 'deploy', 'government'];
791
794
  const lead = (building, title) => ({ kind: 'lead', seat: 'lead:' + building, building, title });
792
795
  const block = (held) => ({ held, title: held.length ? held[0].title : null, has_position: held.length > 0 });
793
796