@bongos/core 1.24.25 → 1.24.27

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 (114) hide show
  1. package/.bongos-core.json +173 -108
  2. package/docs/adr/0307-autobongos-a-deterministic-supervisor-and-agentic-workers.md +12 -0
  3. package/docs/copy-inventory.md +280 -256
  4. package/docs/copy-registry.json +572 -306
  5. package/docs/module-api-changelog.md +4 -0
  6. package/docs/page-inventory.json +10 -4
  7. package/docs/page-readings.json +4337 -4313
  8. package/docs/recipes/ui-look-before-you-ship.md +1 -0
  9. package/modules/autonomy/fence.js +16 -1
  10. package/modules/government/protected-surfaces.json +2 -0
  11. package/modules/hall-ui/public/ai-studio.html +2 -2
  12. package/modules/hall-ui/public/atlas.html +1 -1
  13. package/modules/hall-ui/public/blockers.html +2 -2
  14. package/modules/hall-ui/public/blockers.js +3 -1
  15. package/modules/hall-ui/public/board-room.html +3 -1
  16. package/modules/hall-ui/public/city.html +1 -1
  17. package/modules/hall-ui/public/collab.html +4 -4
  18. package/modules/hall-ui/public/collab.js +4 -0
  19. package/modules/hall-ui/public/commands.html +3 -3
  20. package/modules/hall-ui/public/copy-desk.html +3 -3
  21. package/modules/hall-ui/public/core-move-notice.js +9 -6
  22. package/modules/hall-ui/public/criterion-uat-ui.js +1 -1
  23. package/modules/hall-ui/public/deploy-handoff.js +203 -0
  24. package/modules/hall-ui/public/deploy.html +6 -2
  25. package/modules/hall-ui/public/deploy.js +7 -1
  26. package/modules/hall-ui/public/diagrams.html +1 -1
  27. package/modules/hall-ui/public/drachmae.html +2 -2
  28. package/modules/hall-ui/public/fleet.html +3 -1
  29. package/modules/hall-ui/public/gate.html +3 -1
  30. package/modules/hall-ui/public/goal-map.html +1 -1
  31. package/modules/hall-ui/public/goals-page.js +1 -1
  32. package/modules/hall-ui/public/goals.css +3 -2
  33. package/modules/hall-ui/public/goals.html +3 -3
  34. package/modules/hall-ui/public/goals.js +1 -1
  35. package/modules/hall-ui/public/government.html +3 -1
  36. package/modules/hall-ui/public/hall-widgets.js +9 -9
  37. package/modules/hall-ui/public/idea.html +1 -1
  38. package/modules/hall-ui/public/ideas.html +3 -3
  39. package/modules/hall-ui/public/index.html +3 -1
  40. package/modules/hall-ui/public/lightbulb.js +1 -1
  41. package/modules/hall-ui/public/market-watch-item.html +2 -2
  42. package/modules/hall-ui/public/market-watch-rules.html +2 -2
  43. package/modules/hall-ui/public/market-watch-settings.html +3 -1
  44. package/modules/hall-ui/public/market-watch.html +2 -2
  45. package/modules/hall-ui/public/modules.html +2 -2
  46. package/modules/hall-ui/public/my-agents.html +3 -3
  47. package/modules/hall-ui/public/primer.html +2 -2
  48. package/modules/hall-ui/public/profile.html +6 -6
  49. package/modules/hall-ui/public/project-settings.html +3 -1
  50. package/modules/hall-ui/public/ranks.html +4 -4
  51. package/modules/hall-ui/public/roadmap.html +2 -2
  52. package/modules/hall-ui/public/roster.html +2 -2
  53. package/modules/hall-ui/public/sessions.html +3 -1
  54. package/modules/hall-ui/public/settings-software-update.js +23 -9
  55. package/modules/hall-ui/public/settings-specialities.js +1 -1
  56. package/modules/hall-ui/public/settings.html +10 -6
  57. package/modules/hall-ui/public/settings.js +3 -0
  58. package/modules/hall-ui/public/shell.js +25 -73
  59. package/modules/hall-ui/public/street.html +1 -1
  60. package/modules/hall-ui/public/studio.html +1 -1
  61. package/modules/hall-ui/public/studio.js +3 -3
  62. package/modules/hall-ui/public/style.css +209 -40
  63. package/modules/hall-ui/public/task-detail.js +2 -2
  64. package/modules/hall-ui/public/task.html +1 -1
  65. package/modules/hall-ui/public/thinking.html +1 -1
  66. package/modules/hall-ui/public/tweak-editor.html +2 -2
  67. package/modules/hall-ui/public/update-pill.js +623 -0
  68. package/modules/hall-ui/public/watch.html +3 -1
  69. package/modules/hall-ui/public/watch.js +1 -1
  70. package/modules/hall-ui/public/work.html +4 -4
  71. package/modules/hall-ui/records/loader.md +35 -0
  72. package/modules/hall-ui/records/update-pill.md +12 -0
  73. package/modules/lifecycle/github-push.js +1 -0
  74. package/modules/lifecycle/routes/tasks.js +2 -2
  75. package/modules/lifecycle/runner-self-guard.js +122 -0
  76. package/modules/lifecycle/runner-self.js +154 -0
  77. package/modules/public-landing/public/account.html +3 -3
  78. package/modules/public-landing/public/assets/cosmos.css +45 -0
  79. package/modules/public-landing/public/community.html +4 -4
  80. package/modules/public-landing/public/connections.html +1 -1
  81. package/modules/public-landing/public/guild.html +1 -1
  82. package/modules/public-landing/public/invitations.html +4 -4
  83. package/modules/public-landing/public/projects.html +26 -26
  84. package/modules/status-ui/public/index.html +2 -2
  85. package/modules/status-ui/public/index.states.json +12 -0
  86. package/modules/status-ui/public/status.js +10 -2
  87. package/modules/status-ui/public/style.css +57 -0
  88. package/modules/ui-design/kit/README.md +1 -1
  89. package/modules/ui-design/kit/serve.js +8 -1
  90. package/package-lock.json +2 -2
  91. package/package.json +1 -1
  92. package/release-notes.json +24 -0
  93. package/scripts/bongos/autobongos-loop.js +4 -0
  94. package/scripts/bongos/autobongos-run.js +21 -4
  95. package/scripts/bongos/autobongos-tweak-lane.js +5 -3
  96. package/scripts/bongos/ship-flow.js +20 -0
  97. package/scripts/hall-preview/README.md +1 -0
  98. package/scripts/hall-preview/server.js +9 -0
  99. package/src/bongos/apex-hall-redirects.js +53 -0
  100. package/src/bongos/serve-internal.js +3 -18
  101. package/src/module-api.js +1 -1
  102. package/tests/autobongos_host_access.mjs +5 -3
  103. package/tests/autobongos_self_modification.mjs +420 -0
  104. package/tests/core_update_banner.mjs +6 -92
  105. package/tests/core_upgrade_owner_door.mjs +4 -56
  106. package/tests/deploy_handoff.mjs +206 -0
  107. package/tests/government_protected_surfaces.mjs +15 -0
  108. package/tests/hall_loading.mjs +153 -0
  109. package/tests/hub_connections_page.mjs +1 -1
  110. package/tests/hub_invitations_page.mjs +1 -1
  111. package/tests/loading_mark.mjs +203 -0
  112. package/tests/nav_permission_atoms.mjs +3 -2
  113. package/tests/software_update.mjs +39 -2
  114. package/tests/update_pill.mjs +431 -0
@@ -21,6 +21,7 @@ SURFACE=hall-ui PREFIX=/builders/ node modules/ui-design/kit/serve.js
21
21
  CHILD=1 node modules/ui-design/kit/serve.js # the child-instance shape (apex-only regions stripped)
22
22
  LIVE=1 node modules/ui-design/kit/serve.js # public feeds proxied to the pack's public origin
23
23
  FAIL=featured,progress node modules/ui-design/kit/serve.js # those feeds answer 500
24
+ LATENCY=4000 node modules/ui-design/kit/serve.js # every /api/ read held 4s, to look at a page's waiting state (the loader; task 1005053)
24
25
  STYLE=grove node modules/ui-design/kit/serve.js # under a look from the style library (modules/ui-design/styles/; task 1003323)
25
26
  ```
26
27
 
@@ -48,6 +48,7 @@ const REFUSALS = {
48
48
  no_allowlist: 'no goal is allowlisted, so there is nothing this runner may work on',
49
49
  goal_not_allowlisted: 'that goal is not on the allowlist',
50
50
  protected_path: 'the task declares work on a permission, migration, infrastructure or deploy-pipeline surface',
51
+ self_modification: "the task declares work on the runner's own supervisor, safety checks or kill switch",
51
52
  needs_host_access: 'the work needs shell access to a live host (SSH, root, systemd) that the runner machine does not have',
52
53
  grader_unavailable: 'the grader is bypassed, and it is the only review between an unattended worker and main',
53
54
  not_permitted: 'your rank does not hold autonomy.run, so your runner may not work on this project',
@@ -332,7 +333,13 @@ function needsHostAccess(task) {
332
333
  // in core and a module reaches core only through the doorway. Passing the hits in
333
334
  // also means the caller decides WHAT to match, which is what lets a future caller
334
335
  // run the same decision over a real diff instead of a declaration.
335
- function decideTask({ task, goals = [], protectedHits = [] } = {}) {
336
+ //
337
+ // `selfHits` is the same idea for the runner's OWN files (task 1004546): the output
338
+ // of modules/lifecycle/runner-self.js matchRunnerSelf over the same touches[]. The
339
+ // owner's rule is that the runner never edits its own supervisor, safety checks or
340
+ // kill switch, and none of those are on the ADR 0043 registry. Declared touches are
341
+ // a claim, so the ship-time check and the server's publish check read the real diff.
342
+ function decideTask({ task, goals = [], protectedHits = [], selfHits = [] } = {}) {
336
343
  if (!task || task.id === undefined || task.id === null) {
337
344
  return { go: false, code: 'fence_unreadable', reason: 'no task to decide about' };
338
345
  }
@@ -351,6 +358,14 @@ function decideTask({ task, goals = [], protectedHits = [] } = {}) {
351
358
  globs,
352
359
  };
353
360
  }
361
+ if (Array.isArray(selfHits) && selfHits.length) {
362
+ const files = [...new Set(selfHits.map((h) => h && h.file).filter(Boolean))];
363
+ return {
364
+ go: false, code: 'self_modification',
365
+ reason: `${REFUSALS.self_modification}${files.length ? ` (${files.join(', ')})` : ''}`,
366
+ files,
367
+ };
368
+ }
354
369
  const host = needsHostAccess(task);
355
370
  if (host.needs) {
356
371
  return {
@@ -109,6 +109,8 @@
109
109
  { "glob": "modules/lifecycle/github-push.js", "group": "pipeline", "why": "BV1.R86: the server-mediated branch publish (ADR 0055)" },
110
110
  { "glob": "modules/lifecycle/conflict-resolve.js", "group": "pipeline", "why": "BV1.R86: self-heals generated-file conflicts at land time" },
111
111
  { "glob": "modules/lifecycle/publish-reconciler.js", "group": "pipeline", "why": "BV1.R86: lands confirmed merges" },
112
+ { "glob": "modules/lifecycle/runner-self.js", "group": "pipeline", "why": "task 1004546: the one list of files an Autobongos runner may never change, and the shape of a runner's claim" },
113
+ { "glob": "modules/lifecycle/runner-self-guard.js", "group": "pipeline", "why": "task 1004546: the server refuses to publish a runner's change to its own supervisor, safety checks or kill switch" },
112
114
  { "glob": "migrations/", "group": "schema", "why": "schema migrations are never newcomer work" },
113
115
  { "glob": "infra/", "group": "substrate", "why": "the deploy substrate" },
114
116
  { "glob": ".github/", "group": "substrate", "why": "CI workflows (deploy-prod.yml, grade gate, secrets-scan) are the pipeline" },
@@ -73,7 +73,7 @@
73
73
  <!-- The view, painted by ai-studio.js. The page's one <h1> is each
74
74
  view's own question or figure. -->
75
75
  <div class="ais-page" id="ais-page">
76
- <p class="ais-quiet">Opening…</p>
76
+ <p class="hall-loading hall-loading--page" role="status">Opening…</p>
77
77
  </div>
78
78
  <input type="file" id="ais-file" class="ais-file" multiple accept="image/png,image/jpeg,image/gif,image/webp,application/pdf" tabindex="-1" aria-hidden="true">
79
79
  <input type="file" id="ais-camera-file" class="ais-file" accept="image/*" capture="environment" tabindex="-1" aria-hidden="true">
@@ -89,7 +89,7 @@
89
89
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
90
90
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
91
91
  <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
92
- <script src="/builders/shell.js?v=2026-10-30-github-chip"></script>
92
+ <script src="/builders/shell.js?v=2026-11-03-update-pill"></script>
93
93
  <script src="/builders/palette.js" defer></script>
94
94
  <script src="/builders/tweak-editor-lib.js?v=2026-09-28-tw23"></script>
95
95
  <script src="/builders/studio-lib.js?v=2026-10-09-day"></script>
@@ -92,7 +92,7 @@
92
92
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
93
93
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
94
94
  <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
95
- <script src="/builders/shell.js?v=2026-10-30-github-chip"></script>
95
+ <script src="/builders/shell.js?v=2026-11-03-update-pill"></script>
96
96
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
97
97
  <script src="/builders/palette.js" defer></script>
98
98
  <script src="/builders/atlas.js?v=2026-09-02-index" defer></script>
@@ -44,7 +44,7 @@
44
44
  <h2 id="blockers-h" class="scroll__h">Requires project owner fix<span class="count-pill" id="blockers-count" hidden></span></h2>
45
45
  <div id="blockers-file-area"></div>
46
46
  <div id="blockers-body">
47
- <p class="profile__placeholder">Loading…</p>
47
+ <p class="hall-loading hall-loading--page" role="status">Loading…</p>
48
48
  </div>
49
49
  </section>
50
50
  </main>
@@ -61,7 +61,7 @@
61
61
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
62
62
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
63
63
  <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
64
- <script src="/builders/shell.js?v=2026-10-30-github-chip"></script>
64
+ <script src="/builders/shell.js?v=2026-11-03-update-pill"></script>
65
65
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
66
66
  <script src="/builders/palette.js" defer></script>
67
67
  <!-- the branding pack's currency label, loaded BEFORE blockers.js. -->
@@ -297,7 +297,9 @@
297
297
  }
298
298
 
299
299
  function mountShell() {
300
- body.innerHTML = '<ul class="ov-rows" id="blockers-list"></ul>' + kit.pagerBarHtml(pager.prefix, { label: 'blockers' });
300
+ // The loader holds the list until renderList's first paint replaces it (task 1005043).
301
+ body.innerHTML = '<ul class="ov-rows" id="blockers-list"><li class="hall-loading hall-loading--page" role="status">Loading…</li></ul>'
302
+ + kit.pagerBarHtml(pager.prefix, { label: 'blockers' });
301
303
  kit.wirePager(pager, function () { fetchPage(); });
302
304
  }
303
305
 
@@ -32,6 +32,8 @@
32
32
  </div>
33
33
 
34
34
  <main id="hall-main">
35
+ <!-- task 1005043: the page loader, shown until any section below is revealed. -->
36
+ <p class="hall-loading hall-loading--page hall-loading--wait" role="status">Loading…</p>
35
37
  <!-- task 1003469: the sealed state is a panel with a heading, not a bare
36
38
  line on the ground. board-room.js writes the floor into the lede from
37
39
  the pack's rank label, so the copy cannot drift. Unlike its
@@ -61,7 +63,7 @@
61
63
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
62
64
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
63
65
  <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
64
- <script src="/builders/shell.js?v=2026-10-30-github-chip"></script>
66
+ <script src="/builders/shell.js?v=2026-11-03-update-pill"></script>
65
67
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
66
68
  <script src="/builders/palette.js" defer></script>
67
69
  <script src="/builders/hall-kit.js"></script>
@@ -108,7 +108,7 @@
108
108
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
109
109
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab" defer></script>
110
110
  <script src="/builders/craft-mode.js?v=2026-10-30-market-watch" defer></script>
111
- <script src="/builders/shell.js?v=2026-10-30-github-chip" defer></script>
111
+ <script src="/builders/shell.js?v=2026-11-03-update-pill" defer></script>
112
112
  <script src="/builders/palette.js" defer></script>
113
113
  <script src="/builders/gov-living.js?v=2026-10-09-positions1" defer></script>
114
114
  <script src="/builders/gov-feeds.js?v=2026-10-09-living3" defer></script>
@@ -49,7 +49,7 @@
49
49
  <span class="cb-block__age" id="cb-for-me-age" hidden></span>
50
50
  <span class="cb-faces" id="cb-for-me-faces" aria-hidden="true"></span>
51
51
  </div>
52
- <ul class="ov-rows" id="cb-for-me"></ul>
52
+ <ul class="ov-rows" id="cb-for-me"><li class="hall-loading" role="status">Loading…</li></ul>
53
53
  <div class="empty-state cb-empty" id="cb-for-me-empty" hidden>
54
54
  <p>Nobody is waiting on you. Requests reach you by role —
55
55
  <a href="/builders/settings">set yours in Settings</a>.</p>
@@ -79,7 +79,7 @@
79
79
  <h2 class="scroll__h" id="cb-mine-h">Your asks <span class="cb-block__n" id="cb-mine-n"></span></h2>
80
80
  <span class="cb-block__age" id="cb-mine-age" hidden></span>
81
81
  </div>
82
- <ul class="ov-rows" id="cb-mine"></ul>
82
+ <ul class="ov-rows" id="cb-mine"><li class="hall-loading" role="status">Loading…</li></ul>
83
83
  <div class="empty-state cb-empty" id="cb-mine-empty" hidden><p>You have no open asks.</p></div>
84
84
  <button class="btn-ghost cb-more" id="cb-mine-more" type="button" aria-expanded="false" aria-controls="cb-mine" hidden></button>
85
85
  </section>
@@ -91,7 +91,7 @@
91
91
  <span class="cb-block__age" id="cb-all-age" hidden></span>
92
92
  <span class="cb-faces" id="cb-all-faces" aria-hidden="true"></span>
93
93
  </div>
94
- <ul class="ov-rows" id="cb-all"></ul>
94
+ <ul class="ov-rows" id="cb-all"><li class="hall-loading" role="status">Loading…</li></ul>
95
95
  <div class="empty-state cb-empty" id="cb-all-empty" hidden><p>Nobody has an open ask right now.</p></div>
96
96
  <button class="btn-ghost cb-more" id="cb-all-more" type="button" aria-expanded="false" aria-controls="cb-all" hidden></button>
97
97
  </section>
@@ -162,7 +162,7 @@
162
162
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
163
163
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
164
164
  <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
165
- <script src="/builders/shell.js?v=2026-10-30-github-chip"></script>
165
+ <script src="/builders/shell.js?v=2026-11-03-update-pill"></script>
166
166
  <script src="/builders/palette.js" defer></script>
167
167
  <script src="/builders/collab-lib.js?v=2026-09-24-archive"></script>
168
168
  <script src="/builders/collab.js?v=2026-09-24-archive"></script>
@@ -843,5 +843,9 @@
843
843
  const sub = $('#cb-sub');
844
844
  // V6: a message that stops someone points at the next move in the same breath.
845
845
  if (sub) sub.textContent = 'Could not load requests — reload to try again.';
846
+ // task 1005043: the loaders stop, and the same words stand where the asks would have.
847
+ ['for-me', 'mine', 'all'].forEach((k) => { const host = $(`#cb-${k}`); if (host) host.innerHTML = ''; });
848
+ const first = $('#cb-for-me');
849
+ if (first) first.innerHTML = '<li class="empty-state"><p>Could not load requests — reload to try again.</p></li>';
846
850
  });
847
851
  })();
@@ -33,14 +33,14 @@
33
33
  <span><span class="fact-pill fact-pill--info">Shown to Claude</span> Claude can start it when you ask in plain words.</span>
34
34
  <span><span class="fact-pill cmd-how--typed">Type it yourself</span> Kept out of Claude's menu to save room; type <code>/name</code> to run it.</span>
35
35
  </div>
36
- <div id="skills-body" aria-live="polite"><p class="cmd-error">Loading the slash commands…</p></div>
36
+ <div id="skills-body" aria-live="polite"><p class="hall-loading" role="status">Loading the slash commands…</p></div>
37
37
  <p class="cmd-note">Some commands refuse below a certain rank.</p>
38
38
  </section>
39
39
 
40
40
  <section class="scroll" id="cli-scroll" aria-labelledby="cli-h">
41
41
  <h2 id="cli-h" class="scroll__h">Terminal commands (bongos)</h2>
42
42
  <p class="scroll__lede">The same work from any terminal. <code>bongos help</code> lists them there.</p>
43
- <div id="cli-body" aria-live="polite"><p class="cmd-error">Loading the terminal commands…</p></div>
43
+ <div id="cli-body" aria-live="polite"><p class="hall-loading" role="status">Loading the terminal commands…</p></div>
44
44
  </section>
45
45
  </main>
46
46
 
@@ -54,7 +54,7 @@
54
54
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
55
55
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
56
56
  <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
57
- <script src="/builders/shell.js?v=2026-10-30-github-chip"></script>
57
+ <script src="/builders/shell.js?v=2026-11-03-update-pill"></script>
58
58
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
59
59
  <script src="/builders/palette.js" defer></script>
60
60
  <script src="/builders/hall-kit.js"></script>
@@ -46,7 +46,7 @@
46
46
  <h2 id="cd-flags-h" class="scroll__h">Flagged</h2>
47
47
  <p class="scroll__lede">Copy someone has judged as needing attention, with their reason. Anyone can file one; closing it needs the <span id="cd-close-rank">Metic</span> rank or above, or that it is your own.</p>
48
48
  <div id="cd-filters"></div>
49
- <div id="cd-flags"><div class="empty-state"><p>Loading…</p></div></div>
49
+ <div id="cd-flags"><div class="empty-state"><p class="hall-loading" role="status">Loading…</p></div></div>
50
50
  </section>
51
51
 
52
52
  <!-- The one signal a machine can compute with no judgement layer at all
@@ -55,7 +55,7 @@
55
55
  <section class="scroll" id="cd-dupes-scroll" aria-labelledby="cd-dupes-h">
56
56
  <h2 id="cd-dupes-h" class="scroll__h">Said more than once</h2>
57
57
  <p class="scroll__lede">The same sentence, in several places. Sometimes that is right and the answer is to dismiss it; sometimes it is seven copies of an error message that should have been one. Nobody has looked at the ones marked untouched.</p>
58
- <div id="cd-dupes"><div class="empty-state"><p>Loading…</p></div></div>
58
+ <div id="cd-dupes"><div class="empty-state"><p class="hall-loading" role="status">Loading…</p></div></div>
59
59
  </section>
60
60
 
61
61
  <!-- Find any string in the product and flag it. Without this the queue
@@ -180,7 +180,7 @@
180
180
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
181
181
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
182
182
  <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
183
- <script src="/builders/shell.js?v=2026-10-30-github-chip"></script>
183
+ <script src="/builders/shell.js?v=2026-11-03-update-pill"></script>
184
184
  <script src="/builders/palette.js" defer></script>
185
185
  <script src="/builders/hall-kit.js"></script>
186
186
  <script src="/builders/copy-desk.js?v=2026-09-01-redesign"></script>
@@ -32,19 +32,22 @@
32
32
  }
33
33
  var topbar = document.getElementById('app-topbar');
34
34
  if (!topbar) return;
35
+ // A small card in the top-right corner, the Update pill's own (task 1005041): the owner
36
+ // called the full-width strips "way too obstructing". It still sits after the top bar in
37
+ // the document, so a screen reader meets it where the strip was.
35
38
  var el = document.createElement('div');
36
- el.className = 'core-update-notice core-move-notice';
39
+ el.className = 'upd-card upd-card--note core-move-notice';
37
40
  el.id = 'core-move-notice';
38
41
  el.setAttribute('role', 'status');
39
42
  var entries = Array.isArray(moved.entries) ? moved.entries : [];
40
43
  var list = entries.length
41
- ? '<ul class="core-move-notice__list">' + entries.map(function (e) { return '<li>' + esc(e && e.text) + '</li>'; }).join('') +
44
+ ? '<ul class="upd-card__list core-move-notice__list">' + entries.map(function (e) { return '<li>' + esc(e && e.text) + '</li>'; }).join('') +
42
45
  (moved.more > 0 ? '<li>…and ' + esc(String(moved.more)) + ' more.</li>' : '') + '</ul>'
43
46
  : '';
44
- el.innerHTML = '<div class="core-move-notice__body"><span>' + esc(moved.message) +
45
- (entries.length ? ' What changed:' : '') + '</span>' + list + '</div>' +
46
- '<a class="core-update-notice__action core-move-notice__more" href="' + esc((opts && opts.href) || '/settings#software-update') + '">Update history</a>' +
47
- '<button type="button" class="core-update-notice__close core-move-notice__close" aria-label="Dismiss">×</button>';
47
+ el.innerHTML = '<button type="button" class="upd-card__close core-move-notice__close" aria-label="Dismiss">×</button>' +
48
+ '<h2 class="upd-card__h">Bongos is updated</h2>' +
49
+ '<p>' + esc(moved.message) + (entries.length ? ' What changed:' : '') + '</p>' + list +
50
+ '<div class="upd-card__actions"><a class="upd-btn core-move-notice__more" href="' + esc((opts && opts.href) || '/settings#software-update') + '">Update history</a></div>';
48
51
  function markSeen() {
49
52
  try {
50
53
  if (window.localStorage) window.localStorage.setItem(SEEN_KEY, String(moved.id));
@@ -195,7 +195,7 @@
195
195
  if (existing) { existing.remove(); btn.setAttribute('aria-expanded', 'false'); return; }
196
196
  const panel = document.createElement('div');
197
197
  panel.className = 'uat-panel';
198
- panel.textContent = 'Loading…';
198
+ panel.innerHTML = '<p class="hall-loading hall-loading--inline" role="status">Loading…</p>';
199
199
  row.appendChild(panel);
200
200
  btn.setAttribute('aria-expanded', 'true');
201
201
  try {
@@ -0,0 +1,203 @@
1
+ // deploy-handoff.js — the hub's half of the in-place update (task 1005041).
2
+ //
3
+ // A hosted project's owner presses Update now in their own hall (update-pill.js). The act
4
+ // has to happen HERE, on the hub, with the owner's hub session: the project's own server
5
+ // may never move its own pin (ADR 0293 D1, ADR 0339 D3). So the hall opens this page in a
6
+ // small window as /deploy?project=<slug>&handoff=1, and the two windows say four things:
7
+ // hub → hall { tag, step: 'ready' } "I am loaded; which version?"
8
+ // hall → hub { tag, step: 'go', to } the version the owner was shown
9
+ // hub → hall { tag, step: 'started', to } the move is queued; the window closes
10
+ // hub → hall { tag, step: 'refused', message } the server's own sentence
11
+ //
12
+ // WHAT IS CHECKED BEFORE ANYTHING MOVES (judgeGo). The message must come from the window
13
+ // that opened this one, from that project's own registered address (provisioning's
14
+ // `domain`, which is where its hall is served), and name exactly the version this hub's
15
+ // own preview would move it to. Anything else moves nothing: a page that opens this
16
+ // window from elsewhere is talking to a window that will not listen, and a version this
17
+ // hub would not offer is shown to the owner instead of sent. The move itself is the same
18
+ // POST the deploy door makes, gated server-side exactly as before (owner or core.pin.move).
19
+ //
20
+ // ONE PRESS, NEVER ZERO, WHEN IT CANNOT HEAR. If no word comes from the hall (a browser
21
+ // that cuts the tie between the two windows, or this page opened by hand), it shows one
22
+ // button naming the version, the door's own confirm. It never moves on load by itself.
23
+ //
24
+ // Loaded by deploy.html; deploy.js hands over to run() when ?handoff=1 is present.
25
+ // Node-require-safe, so the tests run judgeGo and projectOrigin without a page.
26
+ (function (root) {
27
+ 'use strict';
28
+
29
+ var MSG = 'bongos-update'; // update-pill.js's tag
30
+ var VERSION_RE = /^\d+\.\d+\.\d+$/;
31
+ var HOST_RE = /^(?=.{1,253}$)[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)+(?::\d{1,5})?$/;
32
+ var API = '/api/bongos';
33
+ // How long the window waits for the hall to say which version, before it offers the press.
34
+ var HEAR_MS = typeof root !== 'undefined' && root && typeof root.__handoffHearMs === 'number' ? root.__handoffHearMs : 5000;
35
+
36
+ /** Is this page the handoff window? Read defensively, as deploy.js reads its scope. */
37
+ function wanted(loc) {
38
+ try { return /[?&]handoff=1(?:&|$)/.test(String((loc && loc.search) || '')); } catch (e) { return false; }
39
+ }
40
+
41
+ /** The one origin allowed to ask for this project's move: its registered address, on the
42
+ * scheme this hub is served on (https in production; http only on a local harness). */
43
+ function projectOrigin(inst, protocol) {
44
+ var host = inst && typeof inst.domain === 'string' ? inst.domain.trim().toLowerCase() : '';
45
+ if (!host || !HOST_RE.test(host)) return null;
46
+ return (protocol === 'http:' ? 'http://' : 'https://') + host;
47
+ }
48
+
49
+ /** Why a message may NOT start the move, or null when it may. Each refusal names itself. */
50
+ function judgeGo(o) {
51
+ var data = o && o.data;
52
+ if (!data || data.tag !== MSG || data.step !== 'go') return 'not_go';
53
+ if (!o.opener || o.source !== o.opener) return 'not_opener';
54
+ if (!o.allowed || o.origin !== o.allowed) return 'wrong_origin';
55
+ var p = o.preview;
56
+ if (!p || p.reading !== 'ok' || !p.behind || !VERSION_RE.test(String(p.recommended_version || ''))) return 'nothing_to_move';
57
+ if (p.viewer_may_move === false) return 'not_allowed';
58
+ if (typeof data.to !== 'string' || data.to !== p.recommended_version) return 'wrong_version';
59
+ if (p.open_intent) return 'busy';
60
+ return null;
61
+ }
62
+
63
+ // ---------- the page ----------
64
+
65
+ function esc(s) { return root.OTB.escapeHtml(String(s == null ? '' : s)); }
66
+
67
+ function run(opts) {
68
+ var d = root.document;
69
+ var api = opts.api;
70
+ var main = d.getElementById('hall-main');
71
+ if (!main) return;
72
+ d.documentElement.setAttribute('data-handoff', 'on');
73
+ main.innerHTML = '<section class="upd-card upd-card--handoff" aria-labelledby="ho-h"><div id="ho-body"><p class="upd-card__note" role="status">Opening this project\'s update…</p></div></section>';
74
+ var body = d.getElementById('ho-body');
75
+ var slug = scopeSlug(root.location);
76
+ var opener = null;
77
+ try { opener = root.opener || null; } catch (e) { opener = null; }
78
+ var inst = null;
79
+ var preview = null;
80
+ var allowed = null;
81
+ var heard = false;
82
+ var moving = false;
83
+
84
+ function draw(html) { body.innerHTML = html; }
85
+ function title(t) { return '<h1 class="upd-card__h" id="ho-h">' + esc(t) + '</h1>'; }
86
+ function tell(msg) {
87
+ if (!opener || !allowed) return;
88
+ try { opener.postMessage(Object.assign({ tag: MSG }, msg), allowed); } catch (e) { /* the hall watches its own version anyway */ }
89
+ }
90
+ function refuse(message) {
91
+ tell({ step: 'refused', message: message });
92
+ draw(title('The update did not start') + '<p class="upd-card__err" role="alert">' + esc(message) + '</p>' +
93
+ '<div class="upd-card__actions"><button type="button" class="upd-btn" id="ho-close">Close</button></div>');
94
+ wireClose();
95
+ }
96
+ function wireClose() {
97
+ var c = d.getElementById('ho-close');
98
+ if (c) c.addEventListener('click', function () { root.close(); });
99
+ }
100
+
101
+ // The one press, naming the version THIS hub would move to: the door's own confirm.
102
+ function offerPress(note) {
103
+ if (moving) return;
104
+ var to = preview.recommended_version;
105
+ draw(title('Update ' + (slug || 'this project')) +
106
+ (note ? '<p class="upd-card__note">' + esc(note) + '</p>' : '') +
107
+ '<p>Moves it to version <span class="upd-ver">' + esc(to) + '</span>. It restarts once, and goes back to the version it runs now on its own if the new one is not healthy.</p>' +
108
+ '<div class="upd-card__actions"><button type="button" class="upd-btn upd-btn--go" id="ho-go">Update to ' + esc(to) + '</button>' +
109
+ '<button type="button" class="upd-btn" id="ho-close">Cancel</button></div>');
110
+ d.getElementById('ho-go').addEventListener('click', function () { move(to); });
111
+ wireClose();
112
+ }
113
+
114
+ function move(to) {
115
+ if (moving) return;
116
+ moving = true;
117
+ draw(title('Starting the update') + '<p class="upd-card__note" role="status">Asking the server to move ' + esc(slug) + ' to ' + esc(to) + '…</p>');
118
+ api.request('POST', API + '/provisioning/instances/' + encodeURIComponent(inst.id) + '/core-upgrade', { body: { to: to } }).then(function (res) {
119
+ if (res && res.status === 401) { signIn(); return; }
120
+ if (!res || !res.ok) {
121
+ moving = false;
122
+ var err = (res && res.data && res.data.error) || {};
123
+ refuse(err.message && err.message !== err.code ? err.message : 'The server refused the update (' + (res ? res.status : 'no answer') + ').');
124
+ return;
125
+ }
126
+ tell({ step: 'started', to: to });
127
+ draw(title('The update has started') +
128
+ '<p role="status">You can close this window. ' + (opener ? 'Your project\'s hall shows how it is going.' : 'Open your project\'s hall to see how it is going.') + '</p>' +
129
+ '<div class="upd-card__actions"><button type="button" class="upd-btn" id="ho-close">Close</button></div>');
130
+ wireClose();
131
+ if (opener) root.setTimeout(function () { root.close(); }, 1200);
132
+ }, function () { moving = false; refuse('The server did not answer. Nothing was changed.'); });
133
+ }
134
+
135
+ function onMessage(e) {
136
+ if (!e || !e.data || e.data.tag !== MSG) return;
137
+ var why = judgeGo({ data: e.data, source: e.source, opener: opener, origin: e.origin, allowed: allowed, preview: preview });
138
+ if (why === 'not_go' || why === 'not_opener' || why === 'wrong_origin') return; // not ours to answer
139
+ heard = true;
140
+ if (!why) { move(e.data.to); return; }
141
+ if (why === 'wrong_version') { offerPress('A different version is ready than the one your hall showed. Check it before updating.'); return; }
142
+ if (why === 'busy') { refuse('Another change is already running for this project. Try again once it settles.'); return; }
143
+ if (why === 'not_allowed') { refuse('Only the project\'s owner can update it.'); return; }
144
+ refuse('This project already runs the newest version it can take.');
145
+ }
146
+
147
+ function signIn() {
148
+ var back = String(root.location.pathname || '/deploy') + String(root.location.search || '');
149
+ root.location.assign(API + '/auth/web/start?return=' + encodeURIComponent(back));
150
+ }
151
+
152
+ function read(path) {
153
+ return api.request('GET', API + path).then(function (res) {
154
+ if (res && res.status === 401) { signIn(); throw { kind: '401' }; }
155
+ if (!res || !res.ok) {
156
+ var err = (res && res.data && res.data.error) || {};
157
+ var e = new Error(err.message && err.message !== err.code ? err.message : (err.code || path + ' → ' + (res && res.status)));
158
+ e.status = res && res.status;
159
+ throw e;
160
+ }
161
+ return res.data;
162
+ });
163
+ }
164
+
165
+ // The project by name: the whole roster where the viewer may read it, else their own
166
+ // projects (deploy.js findBySlug's order). A lookup, never a listing.
167
+ read('/provisioning/fleet').then(function (d) { return d.instances || []; }, function (e) {
168
+ if (e && e.status === 403) return read('/provisioning/instances').then(function (d) { return d.instances || []; });
169
+ throw e;
170
+ }).then(function (rows) {
171
+ for (var i = 0; i < rows.length; i++) if (rows[i] && String(rows[i].slug) === slug) inst = rows[i];
172
+ if (!inst) throw new Error('No project called ' + slug + ' is hosted here under your account.');
173
+ allowed = projectOrigin(inst, root.location.protocol);
174
+ return read('/provisioning/instances/' + encodeURIComponent(inst.id) + '/core-upgrade');
175
+ }).then(function (d) {
176
+ preview = d.preview || null;
177
+ if (!preview || preview.reading !== 'ok' || !preview.behind) {
178
+ refuse(preview && preview.reading === 'ok' ? 'This project already runs the newest version it can take.' : 'This project\'s version could not be read just now. Nothing was changed.');
179
+ return;
180
+ }
181
+ if (preview.open_intent) { refuse('Another change is already running for this project. Try again once it settles.'); return; }
182
+ if (!opener || !allowed) { offerPress(); return; }
183
+ root.addEventListener('message', onMessage);
184
+ tell({ step: 'ready' });
185
+ draw(title('Updating ' + slug) + '<p class="upd-card__note" role="status">Waiting for your hall…</p>');
186
+ root.setTimeout(function () { if (!heard && !moving) offerPress(); }, HEAR_MS);
187
+ }).then(null, function (e) {
188
+ if (e && e.kind === '401') return;
189
+ refuse(String((e && e.message) || 'This project\'s update could not be opened.'));
190
+ });
191
+ }
192
+
193
+ function scopeSlug(loc) {
194
+ try {
195
+ var m = /[?&]project=([^&#]+)/.exec(String((loc && loc.search) || ''));
196
+ return m ? decodeURIComponent(m[1]) : null;
197
+ } catch (e) { return null; }
198
+ }
199
+
200
+ var api = { wanted: wanted, run: run, judgeGo: judgeGo, projectOrigin: projectOrigin, MSG: MSG };
201
+ if (typeof module !== 'undefined' && module.exports) module.exports = api;
202
+ if (root) root.OTBDeployHandoff = api;
203
+ })(typeof window !== 'undefined' ? window : null);
@@ -28,6 +28,8 @@
28
28
  </div>
29
29
 
30
30
  <main id="hall-main">
31
+ <!-- task 1005043: the page loader, shown until any section below is revealed. -->
32
+ <p class="hall-loading hall-loading--page hall-loading--wait" role="status">Loading…</p>
31
33
  <section class="scroll" id="dp-sealed" aria-labelledby="dp-sealed-h" hidden>
32
34
  <h2 class="scroll__h" id="dp-sealed-h">Restricted</h2>
33
35
  <p class="scroll__lede" id="dp-sealed-body">This page opens at a higher rank.</p>
@@ -79,11 +81,13 @@
79
81
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
80
82
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
81
83
  <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
82
- <script src="/builders/shell.js?v=2026-10-30-github-chip"></script>
84
+ <script src="/builders/shell.js?v=2026-11-03-update-pill"></script>
83
85
  <script src="/builders/palette.js" defer></script>
84
86
  <script src="/builders/hall-kit.js"></script>
85
87
  <!-- the shared ask card the move is drawn with (task 1004829) -->
86
88
  <script src="/builders/hall-ask.js"></script>
87
- <script src="/builders/deploy.js?v=2026-09-23-changelist1"></script>
89
+ <!-- the in-place update window a hosted project's hall opens (task 1005041) -->
90
+ <script src="/builders/deploy-handoff.js?v=2026-11-03-handoff"></script>
91
+ <script src="/builders/deploy.js?v=2026-11-03-handoff"></script>
88
92
  </body>
89
93
  </html>
@@ -906,5 +906,11 @@
906
906
  }
907
907
  }
908
908
 
909
- document.addEventListener('DOMContentLoaded', () => { load(); loadModuleSections(); });
909
+ document.addEventListener('DOMContentLoaded', () => {
910
+ // task 1005041: the small window a hosted project's hall opens to update in place. It is
911
+ // this door, asked from the project's own site, and deploy-handoff.js draws it instead.
912
+ const handoff = window.OTBDeployHandoff;
913
+ if (handoff && handoff.wanted(window.location)) { handoff.run({ api }); return; }
914
+ load(); loadModuleSections();
915
+ });
910
916
  })();
@@ -97,7 +97,7 @@
97
97
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
98
98
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
99
99
  <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
100
- <script src="/builders/shell.js?v=2026-10-30-github-chip"></script>
100
+ <script src="/builders/shell.js?v=2026-11-03-update-pill"></script>
101
101
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
102
102
  <script src="/builders/palette.js" defer></script>
103
103
  <script src="/builders/diagram-viewer.js?v=2026-06-14-diagviewer-1" defer></script>
@@ -51,7 +51,7 @@
51
51
  <nav class="primer-toc__inner">
52
52
  <p class="primer-toc__heading" id="primer-toc-heading">On this page</p>
53
53
  <ol class="primer-toc__list" id="primer-toc-list" aria-labelledby="primer-toc-heading">
54
- <li class="primer-toc__placeholder">Loading…</li>
54
+ <li class="hall-loading hall-loading--inline" role="status">Loading…</li>
55
55
  </ol>
56
56
  </nav>
57
57
  </aside>
@@ -97,7 +97,7 @@
97
97
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
98
98
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
99
99
  <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
100
- <script src="/builders/shell.js?v=2026-10-30-github-chip"></script>
100
+ <script src="/builders/shell.js?v=2026-11-03-update-pill"></script>
101
101
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
102
102
  <script src="/builders/palette.js" defer></script>
103
103
  <script src="/builders/md-reader.js?v=2026-09-02-mdreader-2"></script>
@@ -27,6 +27,8 @@
27
27
  </div>
28
28
 
29
29
  <main id="hall-main">
30
+ <!-- task 1005043: the page loader, shown until any section below is revealed. -->
31
+ <p class="hall-loading hall-loading--page hall-loading--wait" role="status">Loading…</p>
30
32
  <section class="scroll" id="fl-sealed" aria-labelledby="fl-sealed-h" hidden>
31
33
  <h2 class="scroll__h" id="fl-sealed-h">Restricted</h2>
32
34
  <p class="scroll__lede">This page opens at a higher rank.</p>
@@ -58,7 +60,7 @@
58
60
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
59
61
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
60
62
  <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
61
- <script src="/builders/shell.js?v=2026-10-30-github-chip"></script>
63
+ <script src="/builders/shell.js?v=2026-11-03-update-pill"></script>
62
64
  <script src="/builders/palette.js" defer></script>
63
65
  <script src="/builders/hall-kit.js"></script>
64
66
  <script src="/builders/fleet-costs.js"></script>
@@ -27,6 +27,8 @@
27
27
  </div>
28
28
 
29
29
  <main id="hall-main">
30
+ <!-- task 1005043: the page loader, shown until any section below is revealed. -->
31
+ <p class="hall-loading hall-loading--page hall-loading--wait" role="status">Loading…</p>
30
32
  <!-- task 1003469: the sealed state is a panel with a heading, not a bare
31
33
  line on the ground. gate.js writes the real floor into the lede from
32
34
  the pack's rank label, so the copy cannot drift from ADR 0157. -->
@@ -65,7 +67,7 @@
65
67
  <script src="/builders/dom-utils.js?v=2026-10-25-escape"></script>
66
68
  <script src="/builders/shell-trail.js?v=2026-10-08-city-tab"></script>
67
69
  <script src="/builders/craft-mode.js?v=2026-10-30-market-watch"></script>
68
- <script src="/builders/shell.js?v=2026-10-30-github-chip"></script>
70
+ <script src="/builders/shell.js?v=2026-11-03-update-pill"></script>
69
71
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
70
72
  <script src="/builders/palette.js" defer></script>
71
73
  <script src="/builders/hall-kit.js"></script>