@bongos/core 1.19.877 → 1.19.879

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 (45) hide show
  1. package/.bongos-core.json +54 -84
  2. package/docs/copy-inventory.md +195 -283
  3. package/docs/copy-registry.json +254 -1141
  4. package/docs/file-map.md +2 -2
  5. package/docs/module-api-changelog.md +5 -1
  6. package/docs/recipes/autobongos-windows-host.md +26 -1
  7. package/modules/autonomy/digest.js +344 -0
  8. package/modules/hall-ui/module.json +1 -1
  9. package/modules/hall-ui/public/builders.js +1 -13
  10. package/modules/hall-ui/public/dom-utils.js +4 -4
  11. package/modules/hall-ui/public/hall-render.js +2 -18
  12. package/modules/hall-ui/public/index.html +0 -3
  13. package/modules/hall-ui/public/panel.css +8 -9
  14. package/modules/hall-ui/public/settings.css +0 -5
  15. package/modules/hall-ui/public/settings.html +0 -29
  16. package/modules/hall-ui/public/settings.js +1 -26
  17. package/modules/hall-ui/public/shell.js +0 -1
  18. package/modules/hall-ui/public/style.css +0 -44
  19. package/modules/hall-ui/public/watch.html +0 -7
  20. package/modules/hall-ui/public/watch.js +1 -37
  21. package/package-lock.json +2 -2
  22. package/package.json +1 -1
  23. package/scripts/gds/autobongos-digest.js +123 -0
  24. package/scripts/gds/autobongos-run.js +7 -0
  25. package/scripts/gds/autobongos-service.ps1 +7 -1
  26. package/scripts/gds/provision-core-upgrade.js +23 -3
  27. package/src/bongos/serve-internal.js +11 -11
  28. package/src/module-api.js +1 -1
  29. package/tests/autonomy_digest.mjs +302 -0
  30. package/tests/core_upgrade_runner.mjs +43 -1
  31. package/tests/government_page_gates.mjs +1 -1
  32. package/tests/hall_landing_boot.mjs +1 -1
  33. package/tests/hall_nav.mjs +1 -1
  34. package/tests/hall_oversight_world.mjs +5 -5
  35. package/tests/hall_page_gate_map.mjs +11 -2
  36. package/tests/hall_settings_world.mjs +21 -18
  37. package/modules/hall-ui/public/box-panel.js +0 -456
  38. package/modules/hall-ui/public/harbor.css +0 -9
  39. package/modules/hall-ui/public/harbor.html +0 -69
  40. package/modules/hall-ui/public/harbor.js +0 -367
  41. package/modules/hall-ui/public/harbor.states.json +0 -77
  42. package/modules/hall-ui/public/pair.css +0 -23
  43. package/modules/hall-ui/public/pair.html +0 -58
  44. package/modules/hall-ui/public/pair.js +0 -132
  45. package/modules/hall-ui/public/pair.states.json +0 -14
@@ -157,11 +157,6 @@ select.voices__select { min-width: 140px; }
157
157
  .settings-btn--discord { background: #5865F2; border-color: #4853c4; color: #ffffff; }
158
158
  .settings-btn--discord:hover:not(:disabled) { background: #4853c4; border-color: #4853c4; color: #ffffff; }
159
159
 
160
- /* ---- dev box ------------------------------------------------------------- */
161
- /* the box panel is the shell's (.setup__box, box-panel.js); inside a settings
162
- panel it sits on the glass rather than a second card */
163
- .settings-groups .setup__box { margin: var(--sp-sm) 0 0; background: var(--glass); border-color: var(--glass-edge); border-radius: var(--radius-sm); }
164
-
165
160
  /* Specialities panel (task 1003855, ADR 0296). A speciality is a named bundle of
166
161
  knowledge and terms under a craft; this styles the list, the train form and the
167
162
  create form. Tokens only — no literal colours, so light/dark follow the page. */
@@ -195,20 +195,6 @@
195
195
 
196
196
  <!-- ============ group: devbox ============ -->
197
197
 
198
- <!-- DEV BOX — full box management (#807, ADR 0040). Request/wake, open
199
- the online terminal, see SSH details, and close the box. Settings
200
- shows BOTH the online terminal AND the SSH IP + command. The
201
- OTBBoxPanel module (box-panel.js) renders + polls inside #box-mount. -->
202
- <section class="scroll" id="box-scroll" aria-labelledby="box-h" data-group="devbox" hidden>
203
- <h2 id="box-h" class="scroll__h">Your dev box</h2>
204
- <p class="scroll__lede">
205
- The hosted machine your work runs on. Open it in your browser (the
206
- online terminal) or connect over SSH — whichever you prefer.
207
- </p>
208
- <div class="setup__box" id="box-mount"></div>
209
- <p class="section-note">Actions apply at once. Closing stops and deletes the box; you can start a fresh one anytime.</p>
210
- </section>
211
-
212
198
  <!-- DEV BOX APP — the desktop switch (#904, ADR 0044). Static download
213
199
  links; the /downloads/devbox/* redirects resolve to the current
214
200
  release (config/devbox-app.json). -->
@@ -236,19 +222,6 @@
236
222
  </p>
237
223
  </section>
238
224
 
239
- <!-- PAIRING — link out to the dedicated pair page (task 2172): keeps
240
- the whole dev-box story reachable from this group. -->
241
- <section class="scroll" id="pairing-scroll" aria-labelledby="pairing-h" data-group="devbox" hidden>
242
- <h2 id="pairing-h" class="scroll__h">Pair Claude Desktop</h2>
243
- <p class="scroll__lede">
244
- Connect your Claude Desktop to your dev box with a one-time pairing
245
- code — the guided flow lives on its own page.
246
- </p>
247
- <div class="settings-card">
248
- <a class="settings-btn" href="/pair">Open the pairing page</a>
249
- </div>
250
- </section>
251
-
252
225
  <!-- ============ group: preferences ============ -->
253
226
 
254
227
  <!-- INTERACTION PROFILE — how replies are pitched for you (task 2010,
@@ -453,8 +426,6 @@
453
426
  <!-- shared record overlay (#/idea|blocker/N) — every hall page carries it; this
454
427
  page was the one gap (task 2172). -->
455
428
  <script src="/builders/task-detail.js?v=2026-07-03-taskdetail"></script>
456
- <!-- #807: shared dev-box panel — loaded before settings.js so OTBBoxPanel exists. -->
457
- <script src="/builders/box-panel.js?v=2026-06-14-domutils"></script>
458
429
  <script src="/builders/settings.js?v=2026-09-02-panel"></script>
459
430
  <!-- task 1003341: the Active sessions panel. Its own file rather than more of
460
431
  settings.js, which sits close to the 1500-line size ratchet. -->
@@ -6,8 +6,6 @@
6
6
  //
7
7
  // 1. VOICES — per-skill model picker (GET/PATCH /api/bongos/me/skill-prefs).
8
8
  // 2. CRAFT — preferred disciplines (GET /api/bongos/me, PATCH /api/bongos/me/disciplines).
9
- // 3. DEV BOX — full box management via the shared OTBBoxPanel (#807, ADR 0040):
10
- // request/open the online terminal, SSH details, and close.
11
9
  // 5. SOUNDS — per-sound on/off toggles (GET/PATCH /api/bongos/me/sound-prefs, #786).
12
10
  //
13
11
  // On a 401 it redirects into the web sign-in flow, like the other hall pages.
@@ -813,24 +811,6 @@
813
811
  $('#display-name-scroll').hidden = false;
814
812
  }
815
813
 
816
- // ---- section 1b: DEV BOX (full management, #807 ADR 0040) ----------------
817
- //
818
- // Mounts the shared OTBBoxPanel (box-panel.js) with showSsh:true so a builder
819
- // sees BOTH the online terminal and the SSH IP + command, and can request,
820
- // open, or close their box. The module owns its own polling + state painting;
821
- // we just hand it the mount element. Hidden if the module failed to load.
822
- function setupBox() {
823
- // R60 (task 1199): dev boxes are a feature module. On a vanilla instance the
824
- // module is off — there is no box to manage, so the whole section stays
825
- // hidden (its #box-scroll defaults to hidden in the markup).
826
- if (!window.OTB.moduleOn('dev-box')) return;
827
- const mount = $('#box-mount');
828
- if (!mount || !window.OTBBoxPanel) return;
829
- const panel = window.OTBBoxPanel.create({ api: API, showSsh: true, toast });
830
- $('#box-scroll').hidden = false;
831
- panel.mount(mount);
832
- }
833
-
834
814
  // ---- section 3: CONNECTIONS (Discord link/unlink, F7 ADR 0033) -----------
835
815
  //
836
816
  // The /me payload carries `discord: { configured, linked }`. The section is
@@ -1243,9 +1223,7 @@
1243
1223
  'disciplines': 'craft-scroll',
1244
1224
  'cli': 'cli-scroll',
1245
1225
  'art-key': 'art-key',
1246
- 'dev-box': 'box-scroll',
1247
1226
  'dev-box-app': 'boxapp-scroll',
1248
- 'pairing': 'pairing-scroll',
1249
1227
  'interaction': 'interaction-scroll',
1250
1228
  'wandering': 'wandering-scroll',
1251
1229
  'rendering': 'render-scroll',
@@ -1358,14 +1336,11 @@
1358
1336
  // DISPLAY NAME — the label beside your work (task 1669); seeded from /me.
1359
1337
  setupDisplayName(builder);
1360
1338
 
1361
- // DEV BOX — full box management (online terminal + SSH + close), #807.
1362
- setupBox();
1363
- // DEV BOX APP + PAIRING — revealed once we know the viewer is a real builder,
1339
+ // DEV BOX APP — revealed once we know the viewer is a real builder,
1364
1340
  // and only where the dev-box module runs (the group's rail entry is gated
1365
1341
  // the same way above — task 2172).
1366
1342
  if (window.OTB.moduleOn('dev-box')) {
1367
1343
  $('#boxapp-scroll').hidden = false;
1368
- $('#pairing-scroll').hidden = false;
1369
1344
  loadDevboxLatestVersion(); // #924: best-effort "latest version: X" courtesy line
1370
1345
  }
1371
1346
  // CONNECTIONS — Discord (hidden unless the server has Discord configured).
@@ -314,7 +314,6 @@
314
314
  // Metic+ oversight could not honestly hold it. Everything below IS a rank
315
315
  // surface, so this group's gate and audience are unchanged.
316
316
  { id: 'watch', label: 'Watch', icon: 'admin', href: '/watch', gate: 'government' },
317
- { id: 'harbor', label: 'Harbor', icon: 'admin', href: '/harbor', gate: 'government', module: 'dev-box' },
318
317
  { id: 'gate', label: 'Gate', icon: 'admin', href: '/gate', gate: 'government' },
319
318
  { id: 'sessions', label: 'Sessions', icon: 'admin', href: '/sessions', gate: 'government' },
320
319
  // The project's own fundamentals (task 1003280, goal 1000072). Mirrors the
@@ -2421,50 +2421,6 @@ main [id] { scroll-margin-top: calc(var(--topbar-h) + 16px); }
2421
2421
  }
2422
2422
  .setup__btn:hover:not(:disabled) { background: var(--accent-deep); border-color: var(--accent-deep); color: #fff; text-decoration: none; }
2423
2423
  .setup__btn:disabled, .setup__btn--open[aria-disabled="true"] { opacity: 0.5; cursor: not-allowed; }
2424
- .setup__btn--box { background: var(--surface); border-color: var(--border-strong); color: var(--ink); }
2425
- .setup__btn--box:hover:not(:disabled) { background: var(--surface-2); color: var(--ink); }
2426
- .setup__btn--danger { background: var(--surface); border-color: color-mix(in srgb, var(--danger) 45%, transparent); color: var(--danger); }
2427
- .setup__btn--danger:hover:not(:disabled) { background: var(--danger-weak); }
2428
-
2429
- .setup__box { border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 12px 14px; margin: 10px 0; background: var(--surface-2); }
2430
- .setup__box-btns { display: flex; flex-wrap: wrap; gap: 8px; margin: 8px 0; }
2431
- .setup__box-msg { font-size: 13px; margin: 6px 0; }
2432
- .setup__box-msg--ok { color: var(--ok); }
2433
- .setup__box-msg--warn { color: var(--warn); }
2434
- .setup__box-msg--err { color: var(--danger); }
2435
- .setup__box-out {
2436
- font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
2437
- font-size: 12px;
2438
- background: var(--surface);
2439
- border: 1px solid var(--border);
2440
- border-radius: var(--radius-sm);
2441
- padding: 8px 10px;
2442
- max-height: 180px;
2443
- overflow: auto;
2444
- }
2445
- .setup__box-close { margin-left: auto; }
2446
- .setup__box-close-hint { font-size: 12px; color: var(--ink-faint); }
2447
- .setup__cred-row { display: flex; align-items: center; gap: 8px; margin: 4px 0; font-size: 13px; flex-wrap: wrap; }
2448
- .setup__cred-k { color: var(--ink-faint); min-width: 90px; }
2449
- .setup__cred-v {
2450
- font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
2451
- font-size: 12px;
2452
- background: var(--surface-2);
2453
- border: 1px solid var(--border);
2454
- border-radius: 4px;
2455
- padding: 2px 8px;
2456
- }
2457
- .setup__copy {
2458
- border: 1px solid var(--border-strong);
2459
- background: var(--surface);
2460
- color: var(--ink-soft);
2461
- border-radius: var(--radius-sm);
2462
- font-size: 12px;
2463
- padding: 3px 10px;
2464
- cursor: pointer;
2465
- }
2466
- .setup__copy:hover { background: var(--surface-2); }
2467
- .setup__copy--copied { background: var(--ok-weak); color: var(--ok); border-color: color-mix(in srgb, var(--ok) 35%, transparent); }
2468
2424
  .setup__getcode { margin: 10px 0; }
2469
2425
  /* A <summary> is a control, so it owes the 24px floor like any other; it
2470
2426
  measured 20px tall (task 1003304). The height is bought with padding rather
@@ -89,13 +89,6 @@
89
89
  <!-- ============ tab: Operations ============ -->
90
90
  <div id="pane-operations" role="tabpanel" aria-labelledby="tab-operations" hidden>
91
91
 
92
- <!-- 5. BOX COSTS (#602) — per-builder dev-box cost ledger + totals. -->
93
- <section class="scroll" id="box-costs-scroll" aria-labelledby="box-costs-h" hidden>
94
- <h2 id="box-costs-h" class="scroll__h">Dev-box costs</h2>
95
- <p class="scroll__lede">What each builder's dev box has cost — spin-up, compute, and storage. Tracked for oversight; not charged in the early stage.</p>
96
- <div id="box-costs-body"><span class="kit-skel"></span></div>
97
- </section>
98
-
99
92
  <!-- 6. SESSIONS — the tally, and the five most recent sessions the page
100
93
  always fetched and used to throw away (task 1003469). -->
101
94
  <section class="scroll" id="sessions-scroll" aria-labelledby="sessions-h" hidden>
@@ -897,41 +897,6 @@
897
897
  });
898
898
  }
899
899
 
900
- // ---- Box costs (#602): per-builder container-cost totals + recent ledger ----
901
- function renderBoxCosts(data, target) {
902
- const totals = Array.isArray(data.totals_by_builder) ? data.totals_by_builder : [];
903
- const recent = Array.isArray(data.ledger) ? data.ledger : [];
904
- const usd = (n) => `$${(Number(n) || 0).toFixed(2)}`;
905
- target.innerHTML = '';
906
- if (!totals.length) {
907
- target.innerHTML = emptyStateHtml('No dev-box costs accrued yet.');
908
- return;
909
- }
910
- ledger(group(target, 'Totals by builder', totals.length), {
911
- columns: ['Builder', { label: 'Total', class: 'num' }],
912
- rowHtml: (tb) => `<tr><td>${escapeHtml(tb.builder_login || ('#' + (tb.builder_id ?? '?')))}</td><td class="num">${usd(tb.total_usd)}</td></tr>`,
913
- }).render(totals);
914
- ledger(group(target, 'Recent ledger', recent.length), {
915
- columns: ['When', 'Builder', 'Kind', { label: 'Amount', class: 'num' }, 'Detail'],
916
- pager: makePager('watch-boxcosts', 'otb.watch.boxCostsPerPage'),
917
- emptyMsg: 'No ledger rows yet.',
918
- rowHtml: (r) => `<tr><td>${escapeHtml(String(r.recorded_at || '').slice(0, 16).replace('T', ' '))}</td>` +
919
- `<td>${escapeHtml(r.builder_login || ('#' + (r.builder_id ?? '?')))}</td>` +
920
- `<td><span class="ov-mono">${escapeHtml((r.source || '').replace('box-', ''))}</span></td>` +
921
- `<td class="num">${usd(r.amount_usd)}</td>` +
922
- `<td>${escapeHtml(r.description || '')}</td></tr>`,
923
- }).render(recent);
924
- target.insertAdjacentHTML('beforeend', foot('Container cost is tracked for oversight — not charged in the early stage.'));
925
- }
926
-
927
- function loadBoxCosts() {
928
- return section($('#box-costs-body'), () => fetchJson('/boxes/cost-ledger?limit=100'), renderBoxCosts, {
929
- sealedMsg: 'The cost ledger is not visible at your rank.',
930
- errMsg: 'Could not load the dev-box cost ledger.',
931
- name: 'box costs',
932
- });
933
- }
934
-
935
900
  // =========================================================================
936
901
  // 4. MARKS — reviews by builder (per-builder grade breakdown)
937
902
  // NOTE: built in parallel — code defensively (404 / [] / missing fields).
@@ -1316,7 +1281,7 @@
1316
1281
 
1317
1282
  function revealSections() {
1318
1283
  [
1319
- 'watch-attention', 'watch-tabs', 'gate-scroll', 'watch-scroll', 'roster-scroll', 'box-costs-scroll',
1284
+ 'watch-attention', 'watch-tabs', 'gate-scroll', 'watch-scroll', 'roster-scroll',
1320
1285
  'marks-scroll', 'mason-marks-scroll', 'surveyor-scroll', 'ledger-scroll', 'sessions-scroll',
1321
1286
  ].forEach((id) => {
1322
1287
  const el = document.getElementById(id);
@@ -1376,7 +1341,6 @@
1376
1341
  loadOverrides();
1377
1342
  loadAudit();
1378
1343
  loadRoster();
1379
- loadBoxCosts();
1380
1344
  loadMarks();
1381
1345
  loadMasonMarks();
1382
1346
  loadSurveyor();
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.877",
3
+ "version": "1.19.879",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.877",
9
+ "version": "1.19.879",
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.19.877",
3
+ "version": "1.19.879",
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",
@@ -0,0 +1,123 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // scripts/gds/autobongos-digest.js — read the night without opening a log
5
+ // (task 1003906, ADR 0307).
6
+ //
7
+ // node scripts/gds/autobongos-digest.js # since midnight, local
8
+ // node scripts/gds/autobongos-digest.js --since 24h # a rolling window
9
+ // node scripts/gds/autobongos-digest.js --json # the structured shape
10
+ // node scripts/gds/autobongos-digest.js --all # the whole log
11
+ //
12
+ // The decision logic is modules/autonomy/digest.js and has no I/O; this half
13
+ // finds the two files it reads and prints the result. Exit 0 always, including on
14
+ // an empty night: a digest is a report, and a non-zero exit would make a cron
15
+ // wrapper treat "nothing happened" as a failure.
16
+
17
+ const fs = require('node:fs');
18
+ const path = require('node:path');
19
+
20
+ const digest = require('../../modules/autonomy/digest.js');
21
+
22
+ const REPO_ROOT = path.resolve(__dirname, '..', '..');
23
+
24
+ function arg(flag, argv) {
25
+ const i = argv.indexOf(flag);
26
+ return i >= 0 ? argv[i + 1] : null;
27
+ }
28
+
29
+ function logPath() {
30
+ if (process.env.AUTOBONGOS_LOG_FILE) return process.env.AUTOBONGOS_LOG_FILE;
31
+ try { return require('../../src/instance-config.js').configPath('autobongos-runs.jsonl'); }
32
+ catch (_) { return path.join(REPO_ROOT, 'autobongos-runs.jsonl'); }
33
+ }
34
+
35
+ function gaugePath() {
36
+ if (process.env.AUTOBONGOS_GAUGE_FILE) return process.env.AUTOBONGOS_GAUGE_FILE;
37
+ try { return require('../../src/instance-config.js').configPath('autobongos-gauge.json'); }
38
+ catch (_) { return path.join(REPO_ROOT, 'autobongos-gauge.json'); }
39
+ }
40
+
41
+ // readRows — an append-only JSONL, and a single unparseable line must not cost
42
+ // the whole night. A JSONL written by a process that can be killed mid-write has
43
+ // a torn last line more often than never.
44
+ function readRows(file) {
45
+ let text;
46
+ try { text = fs.readFileSync(file, 'utf8'); }
47
+ catch (_) { return { rows: [], missing: true, skipped: 0 }; }
48
+ const rows = [];
49
+ let skipped = 0;
50
+ for (const line of text.split('\n')) {
51
+ const t = line.trim();
52
+ if (!t) continue;
53
+ try { rows.push(JSON.parse(t)); } catch (_) { skipped += 1; }
54
+ }
55
+ return { rows, missing: false, skipped };
56
+ }
57
+
58
+ function readGaugeFile(file) {
59
+ try { return JSON.parse(fs.readFileSync(file, 'utf8')); }
60
+ catch (_) { return null; }
61
+ }
62
+
63
+ // sinceMs — `--since 24h|90m|3d`, or midnight LOCAL by default.
64
+ //
65
+ // Local rather than UTC because the question is "what happened last night" and
66
+ // night is a local idea; a UTC midnight cut would split an owner's evening in
67
+ // half depending on where they live.
68
+ function sinceMs(spec, now = new Date()) {
69
+ if (!spec) {
70
+ const d = new Date(now);
71
+ d.setHours(0, 0, 0, 0);
72
+ return d.getTime();
73
+ }
74
+ const m = /^(\d+(?:\.\d+)?)\s*([hmd])$/i.exec(String(spec).trim());
75
+ if (!m) return null;
76
+ const mult = { h: 3600e3, m: 60e3, d: 86400e3 }[m[2].toLowerCase()];
77
+ return now.getTime() - Number(m[1]) * mult;
78
+ }
79
+
80
+ function main(argv) {
81
+ const all = argv.includes('--all');
82
+ const asJson = argv.includes('--json');
83
+ const now = new Date();
84
+ const from = all ? null : sinceMs(arg('--since', argv), now);
85
+ if (!all && from === null) {
86
+ console.error('autobongos-digest: --since wants a span like 24h, 90m or 3d.');
87
+ return 2;
88
+ }
89
+
90
+ const lf = logPath();
91
+ const { rows, missing, skipped } = readRows(lf);
92
+ const inWindow = all ? rows : rows.filter((r) => {
93
+ const t = Date.parse(r && r.at);
94
+ return Number.isFinite(t) ? t >= from : true; // an undated row is kept, not silently dropped
95
+ });
96
+
97
+ const d = digest.buildDigest(inWindow, {
98
+ gauge: readGaugeFile(gaugePath()),
99
+ nowEpochS: Math.floor(now.getTime() / 1000),
100
+ sinceMs: all ? null : from,
101
+ untilMs: now.getTime(),
102
+ });
103
+
104
+ if (asJson) { console.log(JSON.stringify(d, null, 2)); return 0; }
105
+
106
+ console.log('');
107
+ console.log(digest.renderDigest(d));
108
+ // Both of these are about the DIGEST's own reliability, so they sit outside the
109
+ // five questions rather than being folded into them.
110
+ if (missing) {
111
+ console.log('');
112
+ console.log(` No run log at ${lf} — the runner has not written one here yet.`);
113
+ }
114
+ if (skipped) console.log(` (${skipped} unreadable line(s) in the log were skipped.)`);
115
+ console.log('');
116
+ return 0;
117
+ }
118
+
119
+ if (require.main === module) {
120
+ process.exit(main(process.argv.slice(2)));
121
+ }
122
+
123
+ module.exports = { main, sinceMs, readRows, logPath, gaugePath };
@@ -381,6 +381,13 @@ async function iteration(opts, deps = {}) {
381
381
  // What the LEDGER says, kept separate from what the worker said, so the run
382
382
  // log can be read afterwards without having to trust either one alone.
383
383
  ledger_status: ledger.status, ledger_shape: ledger.shape, verified: !!ledger.verified,
384
+ // The LEDGER's own explanation, kept beside the worker's. `reason` above is
385
+ // verdict.reason — what the WORKER said happened — and it was the only reason
386
+ // in the row, so anything reading the log afterwards was quoting the thing
387
+ // being checked rather than the check. The morning digest did exactly that
388
+ // until the grader caught it (task 1003906).
389
+ ledger_reason: ledger.reason || null,
390
+ ledger_remedy: ledger.remedy || null,
384
391
  artifact_on_main: ledger.artifact && ledger.artifact.checked ? !!ledger.artifact.onMain : null,
385
392
  claim_released: !!ledger.released,
386
393
  blocker_id: ledger.blocker && ledger.blocker.blockerId ? ledger.blocker.blockerId : null,
@@ -3,6 +3,7 @@
3
3
  #
4
4
  # powershell -ExecutionPolicy Bypass -File scripts\gds\autobongos-service.ps1 install
5
5
  # powershell -ExecutionPolicy Bypass -File scripts\gds\autobongos-service.ps1 status
6
+ # powershell -ExecutionPolicy Bypass -File scripts\gds\autobongos-service.ps1 digest
6
7
  # powershell -ExecutionPolicy Bypass -File scripts\gds\autobongos-service.ps1 start|stop|uninstall
7
8
  #
8
9
  # WHY TASK SCHEDULER AND NOT A REAL SERVICE. Windows services speak a protocol
@@ -29,7 +30,7 @@
29
30
  [CmdletBinding()]
30
31
  param(
31
32
  [Parameter(Position = 0)]
32
- [ValidateSet('install', 'uninstall', 'start', 'stop', 'status')]
33
+ [ValidateSet('install', 'uninstall', 'start', 'stop', 'status', 'digest')]
33
34
  [string]$Action = 'status',
34
35
 
35
36
  # The checkout to run from. Defaults to the repo this script lives in, so the
@@ -118,6 +119,11 @@ switch ($Action) {
118
119
  Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false -ErrorAction SilentlyContinue
119
120
  Write-Host "removed '$TaskName'. The log at $LogFile is left in place."
120
121
  }
122
+ 'digest' {
123
+ # The night in one page (task 1003906). On the box for convenience; the same
124
+ # five answers are what the Gate page and a mailed digest would carry.
125
+ & node (Join-Path $RepoRoot 'scripts\gds\autobongos-digest.js') @args
126
+ }
121
127
  'start' { Start-ScheduledTask -TaskName $TaskName; Write-Host "started '$TaskName'" }
122
128
  'stop' {
123
129
  Stop-ScheduledTask -TaskName $TaskName
@@ -187,6 +187,25 @@ async function coreUpgradeInstance(inst, deps, intent) {
187
187
  const healthUrl = `http://127.0.0.1:${inst.port}/healthz`;
188
188
  const versionUrl = `http://127.0.0.1:${inst.port}/version`;
189
189
  const sudoP = privileged ? 'sudo ' : '';
190
+ // WHERE THE CLI LIVES AND WHERE THE TARGET LIVES ARE DIFFERENT PLACES (task 1004053).
191
+ //
192
+ // `scripts/gds/upgrade.js` ships in the CORE checkout. A provisioned instance root holds
193
+ // no scripts/ directory at all — its core sits under node_modules/@bongos/core — so
194
+ // running the CLI with cwd = the instance root resolved nothing, and EVERY standalone
195
+ // upgrade died before it began:
196
+ //
197
+ // Error: Cannot find module '/srv/<base>/<slug>/scripts/gds/upgrade.js'
198
+ //
199
+ // It worked only where instanceDir HAPPENED to be this checkout — the control-plane self
200
+ // row and a co-tenant — which is why it survived to production: the shapes that are
201
+ // upgraded by hand are the shapes the relative path fits. Every ACTIVE tenant is
202
+ // standalone, so the deploy door could not move a single one of them.
203
+ //
204
+ // upgrade.js's own header states the contract this restores: "Runs from the consumer
205
+ // instance root (default) or `--instance <dir>`. This script ships IN the core … against
206
+ // `--instance <consumer>`." So: run it where it exists, and NAME the target.
207
+ const runDir = REPO_ROOT;
208
+ const targetArg = instanceDir === REPO_ROOT ? '' : `--instance ${instanceDir}`;
190
209
  const cli = path.posix.join('scripts', 'gds', 'upgrade.js');
191
210
  const requestedBy = intent.requested_by && /^api:archon:(\d+)$/.test(intent.requested_by)
192
211
  ? intent.requested_by.replace(/^api:archon:/, '')
@@ -195,6 +214,7 @@ async function coreUpgradeInstance(inst, deps, intent) {
195
214
  `${sudoP}node ${cli}`,
196
215
  '--registry',
197
216
  `--to ${to}`,
217
+ targetArg,
198
218
  `--service ${inst.slug}`,
199
219
  `--health-url ${healthUrl}`,
200
220
  `--version-url ${versionUrl}`,
@@ -204,13 +224,13 @@ async function coreUpgradeInstance(inst, deps, intent) {
204
224
  ].filter(Boolean).join(' ');
205
225
 
206
226
  if (!apply) {
207
- log(` ✓ would run: ${cmd} (dry-run — nothing ran)`);
227
+ log(` ✓ would run (in ${runDir}): ${cmd} (dry-run — nothing ran)`);
208
228
  log(` ✓ would then read ${versionUrl} on the box and require it to say ${to}`);
209
229
  return { ok: true, served: null };
210
230
  }
211
231
  try {
212
- log(` [upgrade] ${cmd}`);
213
- boxExec(cmd, { cwd: instanceDir });
232
+ log(` [upgrade] (in ${runDir}) ${cmd}`);
233
+ boxExec(cmd, { cwd: runDir });
214
234
  } catch (e) {
215
235
  // upgrade.js rolls itself back on a failed health check or an unchanged served
216
236
  // version, so by the time this throws the instance is back on the core it had.
@@ -927,16 +927,16 @@ function mountInternalSurfaces(app) {
927
927
  return res.redirect(301, `https://builders.${apex}/${rest}${qs}`);
928
928
  });
929
929
 
930
- // The /watch monitoring page and the /harbor dev-box management page (idea 257)
931
- // and the /gate page (task 1179) all sit behind `page.view.government` — Metic
932
- // and up since ADR 0157. Same server-side page gate, same posture: a builder
933
- // without the permission never receives the shell (requireGovernmentPage — named
934
- // requireArchonPage before ADR 0157 — redirects them to the hall, and an
935
- // unauthenticated visitor into sign-in). The subdomain shim above has already
936
- // rewritten the bare /watch + /harbor + /gate onto /builders/*. (Defense in
937
- // depth: every data endpoint these pages call carries its own server-side gate.)
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.) /harbor sat here too until task 1003890 removed the dev
938
+ // box: its page is gone, and a gate in front of nothing is a dead guard.
938
939
  const WATCH_PAGE_RE = /^\/builders\/watch(?:\.html)?\/?$/;
939
- const HARBOR_PAGE_RE = /^\/builders\/harbor(?:\.html)?\/?$/;
940
940
  const GATE_PAGE_RE = /^\/builders\/gate(?:\.html)?\/?$/;
941
941
  // /sessions (the session-log ledger) belongs to that same Governance group and
942
942
  // its data route (`session.search`) carries the same metic floor since ADR 0157
@@ -1020,7 +1020,7 @@ function mountInternalSurfaces(app) {
1020
1020
  if (isModuleSurfaceHost(req)) return next();
1021
1021
  if (!req.path.startsWith('/builders')) return next();
1022
1022
  const norm = path.posix.normalize(req.path);
1023
- if (WATCH_PAGE_RE.test(norm) || HARBOR_PAGE_RE.test(norm) || GATE_PAGE_RE.test(norm)
1023
+ if (WATCH_PAGE_RE.test(norm) || GATE_PAGE_RE.test(norm)
1024
1024
  || SESSIONS_PAGE_RE.test(norm) || PROJECT_SETTINGS_PAGE_RE.test(norm)) {
1025
1025
  return gdsAuth.requireGovernmentPage(req, res, next);
1026
1026
  }
@@ -1201,7 +1201,7 @@ function mountInternalSurfaces(app) {
1201
1201
  // read behind it is gated server-side, so this closed an inconsistency rather
1202
1202
  // than an exposure; the point is that one URL cannot be two tiers at once.
1203
1203
  const SIGNEDIN_BUILDERS_PAGE_RE =
1204
- /^\/builders\/(?:work|pair|settings|atlas|modules|goals|roadmap|copy-desk|studio|ideas|collab|task|idea|primer(?:\.md)?|diagrams)(?:\.html)?\/?$/;
1204
+ /^\/builders\/(?:work|settings|atlas|modules|goals|roadmap|copy-desk|studio|ideas|collab|task|idea|primer(?:\.md)?|diagrams)(?:\.html)?\/?$/;
1205
1205
  // The diagram image assets under /builders/diagrams/ ride the same tier as the
1206
1206
  // page that embeds them.
1207
1207
  const SIGNEDIN_BUILDERS_PREFIX_RE = /^\/builders\/diagrams\//;
package/src/module-api.js CHANGED
@@ -71,7 +71,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
71
71
  // there. scripts/gds/bump-version.js still rewrites the literal below; it appends
72
72
  // the entry to that file. Look for a version's history there, not here.
73
73
  // ---------------------------------------------------------------------------
74
- const CORE_VERSION = '1.19.877'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
74
+ const CORE_VERSION = '1.19.879'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
75
75
 
76
76
  // A namespaced logger so a module's log lines are attributable + consistent.
77
77
  // Usage: const log = api.logger('dev-box'); log.info('mounted');