@bongos/core 1.19.1034 → 1.19.1036
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.
- package/.bongos-core.json +88 -68
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +6 -0
- package/clients/bongos-client/index.cjs +6 -0
- package/clients/bongos-client/index.d.ts +9 -1
- package/clients/bongos-client/index.mjs +6 -0
- package/docs/adr/0339-a-projects-owner-upgrades-it-and-a-hosted-project-hands-off-to-the-hub-door.md +84 -0
- package/docs/adr/README.md +1 -0
- package/docs/api/openapi.json +126 -8
- package/docs/api-reference.md +9 -2
- package/docs/copy-inventory.md +10 -10
- package/docs/copy-registry.json +13 -13
- package/docs/module-api-changelog.md +4 -0
- package/modules/autonomy/db.js +19 -0
- package/modules/autonomy/routes/autonomy.js +28 -0
- package/modules/government/catalog.js +2 -2
- package/modules/hall-ui/public/atlas.html +1 -1
- package/modules/hall-ui/public/blockers.html +1 -1
- package/modules/hall-ui/public/board-room.html +1 -1
- package/modules/hall-ui/public/collab.html +1 -1
- package/modules/hall-ui/public/copy-desk.html +1 -1
- package/modules/hall-ui/public/deploy.html +1 -1
- package/modules/hall-ui/public/diagrams.html +1 -1
- package/modules/hall-ui/public/drachmae.html +1 -1
- package/modules/hall-ui/public/fleet.html +1 -1
- package/modules/hall-ui/public/gate.html +1 -1
- package/modules/hall-ui/public/goals.html +1 -1
- package/modules/hall-ui/public/government.html +1 -1
- package/modules/hall-ui/public/idea.html +1 -1
- package/modules/hall-ui/public/ideas.html +1 -1
- package/modules/hall-ui/public/index.html +1 -1
- package/modules/hall-ui/public/modules.html +1 -1
- package/modules/hall-ui/public/people.html +1 -1
- package/modules/hall-ui/public/primer.html +1 -1
- package/modules/hall-ui/public/profile.html +1 -1
- package/modules/hall-ui/public/project-settings.html +1 -1
- package/modules/hall-ui/public/ranks.html +1 -1
- package/modules/hall-ui/public/roadmap.html +1 -1
- package/modules/hall-ui/public/roster.html +1 -1
- package/modules/hall-ui/public/sessions.html +1 -1
- package/modules/hall-ui/public/settings.html +1 -1
- package/modules/hall-ui/public/shell.js +36 -5
- package/modules/hall-ui/public/studio.html +1 -1
- package/modules/hall-ui/public/task.html +1 -1
- package/modules/hall-ui/public/watch.html +1 -1
- package/modules/hall-ui/public/work.html +1 -1
- package/modules/lifecycle/ship-assurance.js +17 -1
- package/modules/provisioning/core-upgrade-authz.js +85 -0
- package/modules/provisioning/core-upgrade.js +2 -1
- package/modules/provisioning/provisioning.js +4 -4
- package/modules/provisioning/routes/core-upgrade.js +12 -6
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +20 -0
- package/scripts/gds/provision-core-upgrade.js +12 -4
- package/src/bongos/auth.js +8 -2
- package/src/bongos/core-update.js +23 -1
- package/src/bongos/routes/core-update.js +51 -4
- package/src/module-api.js +1 -1
- package/tests/core_upgrade_owner_door.mjs +444 -0
- package/tests/nav_permission_atoms.mjs +1 -1
- package/tests/provisioning_teardown_intent.mjs +28 -2
- package/tests/routine_observability_api.mjs +195 -0
- package/tests/scouting_page_gate.mjs +11 -3
- package/tests/ship_assurance.mjs +86 -3
|
@@ -108,7 +108,7 @@ const PERMISSIONS = [
|
|
|
108
108
|
// owner holds: the catalog's own invariant forbids granting a system key to metic,
|
|
109
109
|
// so this cannot be delegated downward by a custom rank. The web tier still gains
|
|
110
110
|
// no shell - the route only QUEUES an intent the control-plane runner drains.
|
|
111
|
-
{ key: 'core.pin.move', system: true, floor: 'archon', guards: 'POST /provisioning/instances/:id/core-upgrade — gate 3 of the release pipeline (ADR 0293)' },
|
|
111
|
+
{ key: 'core.pin.move', system: true, floor: 'archon', guards: 'POST /provisioning/instances/:id/core-upgrade — gate 3 of the release pipeline (ADR 0293); also the reads GET /core-update (the upgrade banner) and GET /core-upgrades (the upgrade ledger, task 1002669)' },
|
|
112
112
|
// autonomy.fence.manage (task 1003908, ADR 0307 §6) — THE AUTOBONGOS KILL
|
|
113
113
|
// SWITCH and the allowlist of goals an unattended runner may work. system:true
|
|
114
114
|
// + archon for core.pin.move's reason, in a slightly sharper form: this decides
|
|
@@ -253,7 +253,7 @@ const PERMISSIONS = [
|
|
|
253
253
|
{ key: 'security.report.adjudicate', system: false, floor: 'metic', guards: 'GET /security/reports, POST /security/reports/:id/{confirm,dispute,fix,link-fix}, /security/docs CRUD (ADR 0157, was archon)' },
|
|
254
254
|
{ key: 'provisioning.fleet.manage', system: false, floor: 'metic', guards: 'GET /provisioning/fleet[/cost-ledger], POST .../force-teardown (ADR 0157, was archon)' },
|
|
255
255
|
{ key: 'box.fleet.manage', system: false, floor: 'metic', scope: 'any', resource: 'box', verb: 'manage', guards: 'GET /boxes[/cost-ledger], POST /boxes/:b/close, PATCH .../block (ADR 0157, was archon)' },
|
|
256
|
-
{ key: 'autonomy.precheck', system: false, floor: 'metic', guards: 'GET /autonomy/precheck — unsupervised-runner gate (ADR 0157, was archon)' },
|
|
256
|
+
{ key: 'autonomy.precheck', system: false, floor: 'metic', guards: 'GET /autonomy/precheck — unsupervised-runner gate (ADR 0157, was archon); also GET /autonomy/runs, the gate\'s past decisions (task 1002669)' },
|
|
257
257
|
{ key: 'discord.channels.manage', system: false, floor: 'metic', guards: 'GET /discord/channels/plan, POST .../reconcile (ADR 0157, was archon)' },
|
|
258
258
|
// Metic by the discord.channels.manage analogue — configuring a project-wide
|
|
259
259
|
// social surface — rather than archon. The blast radius is bounded by the OTHER
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
|
|
92
92
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
93
93
|
|
|
94
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
94
|
+
<script src="/builders/shell.js?v=2026-09-26-own1"></script>
|
|
95
95
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
96
96
|
<script src="/builders/palette.js" defer></script>
|
|
97
97
|
<script src="/builders/atlas.js?v=2026-09-02-index" defer></script>
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
|
|
51
51
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
52
52
|
chrome exists before the page script runs. -->
|
|
53
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
53
|
+
<script src="/builders/shell.js?v=2026-09-26-own1"></script>
|
|
54
54
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
55
55
|
<script src="/builders/palette.js" defer></script>
|
|
56
56
|
<!-- shared frontend toolkit (window.OTB — rank labels, number formats) + the
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
|
|
61
61
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
62
62
|
|
|
63
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
63
|
+
<script src="/builders/shell.js?v=2026-09-26-own1"></script>
|
|
64
64
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
65
65
|
<script src="/builders/palette.js" defer></script>
|
|
66
66
|
<script src="/builders/dom-utils.js?v=2026-06-16-board"></script>
|
|
@@ -148,7 +148,7 @@
|
|
|
148
148
|
|
|
149
149
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
150
150
|
|
|
151
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
151
|
+
<script src="/builders/shell.js?v=2026-09-26-own1"></script>
|
|
152
152
|
<script src="/builders/palette.js" defer></script>
|
|
153
153
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
154
154
|
<script src="/builders/collab-lib.js?v=2026-09-24-archive"></script>
|
|
@@ -177,7 +177,7 @@
|
|
|
177
177
|
|
|
178
178
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
179
179
|
|
|
180
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
180
|
+
<script src="/builders/shell.js?v=2026-09-26-own1"></script>
|
|
181
181
|
<script src="/builders/palette.js" defer></script>
|
|
182
182
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
183
183
|
<script src="/builders/hall-kit.js"></script>
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
|
|
61
61
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
62
62
|
|
|
63
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
63
|
+
<script src="/builders/shell.js?v=2026-09-26-own1"></script>
|
|
64
64
|
<script src="/builders/palette.js" defer></script>
|
|
65
65
|
<script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
|
|
66
66
|
<script src="/builders/hall-kit.js"></script>
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
|
|
98
98
|
<!-- The app shell (sidebar + top bar + footer). Dependency-free; runs at parse
|
|
99
99
|
time so the chrome exists before the deferred page scripts execute. -->
|
|
100
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
100
|
+
<script src="/builders/shell.js?v=2026-09-26-own1"></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>
|
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
|
|
99
99
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
100
100
|
|
|
101
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
101
|
+
<script src="/builders/shell.js?v=2026-09-26-own1"></script>
|
|
102
102
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
103
103
|
<script src="/builders/palette.js" defer></script>
|
|
104
104
|
<script src="/builders/md-reader.js?v=2026-09-02-mdreader-2"></script>
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
|
|
55
55
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
56
56
|
|
|
57
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
57
|
+
<script src="/builders/shell.js?v=2026-09-26-own1"></script>
|
|
58
58
|
<script src="/builders/palette.js" defer></script>
|
|
59
59
|
<script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
|
|
60
60
|
<script src="/builders/hall-kit.js"></script>
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
<div id="gate-modal-root"></div>
|
|
77
77
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
78
78
|
|
|
79
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
79
|
+
<script src="/builders/shell.js?v=2026-09-26-own1"></script>
|
|
80
80
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
81
81
|
<script src="/builders/palette.js" defer></script>
|
|
82
82
|
<script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
|
|
@@ -235,7 +235,7 @@
|
|
|
235
235
|
|
|
236
236
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
237
237
|
|
|
238
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
238
|
+
<script src="/builders/shell.js?v=2026-09-26-own1"></script>
|
|
239
239
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
240
240
|
<script src="/builders/palette.js" defer></script>
|
|
241
241
|
<script src="/builders/dom-utils.js?v=2026-06-14-domutils"></script>
|
|
@@ -134,7 +134,7 @@
|
|
|
134
134
|
|
|
135
135
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
136
136
|
|
|
137
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
137
|
+
<script src="/builders/shell.js?v=2026-09-26-own1"></script>
|
|
138
138
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
139
139
|
<script src="/builders/palette.js" defer></script>
|
|
140
140
|
<script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
|
|
53
53
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
54
54
|
|
|
55
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
55
|
+
<script src="/builders/shell.js?v=2026-09-26-own1"></script>
|
|
56
56
|
<script src="/builders/palette.js" defer></script>
|
|
57
57
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib" defer></script>
|
|
58
58
|
<!-- task-detail.js still owns the #/idea/N and #/blocker/N overlay, so a ref
|
|
@@ -225,7 +225,7 @@
|
|
|
225
225
|
|
|
226
226
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
227
227
|
|
|
228
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
228
|
+
<script src="/builders/shell.js?v=2026-09-26-own1"></script>
|
|
229
229
|
<script src="/builders/palette.js" defer></script>
|
|
230
230
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
231
231
|
<!-- task 1003069 (R08) — the quick-filing form's routed result links a task
|
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
<script src="/builders/brand-apply.js?v=2026-06-27-r75" defer></script>
|
|
103
103
|
<!-- The app shell (sidebar + top bar + view router). Dependency-free; loaded
|
|
104
104
|
BEFORE builders.js so the chrome ids exist when events are wired. -->
|
|
105
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
105
|
+
<script src="/builders/shell.js?v=2026-09-26-own1" defer></script>
|
|
106
106
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
107
107
|
<script src="/builders/palette.js" defer></script>
|
|
108
108
|
<!-- task 2166: the shared component kit (window.OTBKit) — pager/ledger/rail.
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
|
|
81
81
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
82
82
|
|
|
83
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
83
|
+
<script src="/builders/shell.js?v=2026-09-26-own1"></script>
|
|
84
84
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
85
85
|
<script src="/builders/palette.js" defer></script>
|
|
86
86
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
|
|
67
67
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
68
68
|
chrome exists before the page script runs. -->
|
|
69
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
69
|
+
<script src="/builders/shell.js?v=2026-09-26-own1"></script>
|
|
70
70
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
71
71
|
<script src="/builders/palette.js" defer></script>
|
|
72
72
|
<!-- shared frontend toolkit (window.OTB — rank labels, number formats) + the
|
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
|
|
116
116
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
117
117
|
|
|
118
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
118
|
+
<script src="/builders/shell.js?v=2026-09-26-own1"></script>
|
|
119
119
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
120
120
|
<script src="/builders/palette.js" defer></script>
|
|
121
121
|
<!-- the shared markdown reader (window.OTBMdReader) — primer.js renders through
|
|
@@ -187,7 +187,7 @@
|
|
|
187
187
|
|
|
188
188
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
189
189
|
chrome exists before the page script runs. -->
|
|
190
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
190
|
+
<script src="/builders/shell.js?v=2026-09-26-own1"></script>
|
|
191
191
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
192
192
|
<script src="/builders/palette.js" defer></script>
|
|
193
193
|
<!-- shared frontend toolkit (window.OTB) — loaded before profile.js, which pulls from it. -->
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
|
|
59
59
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
60
60
|
|
|
61
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
61
|
+
<script src="/builders/shell.js?v=2026-09-26-own1"></script>
|
|
62
62
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
63
63
|
<script src="/builders/palette.js" defer></script>
|
|
64
64
|
<script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
|
|
69
69
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
70
70
|
chrome exists before the page script runs. -->
|
|
71
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
71
|
+
<script src="/builders/shell.js?v=2026-09-26-own1"></script>
|
|
72
72
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
73
73
|
<script src="/builders/palette.js" defer></script>
|
|
74
74
|
<!-- shared frontend toolkit (window.OTB) — loaded before ranks.js, which pulls from it. -->
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
|
|
42
42
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
43
43
|
|
|
44
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
44
|
+
<script src="/builders/shell.js?v=2026-09-26-own1"></script>
|
|
45
45
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
46
46
|
<script src="/builders/palette.js" defer></script>
|
|
47
47
|
<script src="/builders/dom-utils.js?v=2026-06-14-domutils"></script>
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
|
|
51
51
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
52
52
|
chrome exists before the page script runs. -->
|
|
53
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
53
|
+
<script src="/builders/shell.js?v=2026-09-26-own1"></script>
|
|
54
54
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
55
55
|
<script src="/builders/palette.js" defer></script>
|
|
56
56
|
<!-- shared frontend toolkit (window.OTB — rank labels, number formats) + the
|
|
@@ -99,7 +99,7 @@
|
|
|
99
99
|
|
|
100
100
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
101
101
|
chrome exists before the page script runs. -->
|
|
102
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
102
|
+
<script src="/builders/shell.js?v=2026-09-26-own1"></script>
|
|
103
103
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
104
104
|
<script src="/builders/palette.js" defer></script>
|
|
105
105
|
<!-- shared frontend toolkit (window.OTB) — loaded BEFORE sessions.js so it can pull from it. -->
|
|
@@ -416,7 +416,7 @@
|
|
|
416
416
|
|
|
417
417
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
418
418
|
chrome exists before the page scripts run. -->
|
|
419
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
419
|
+
<script src="/builders/shell.js?v=2026-09-26-own1"></script>
|
|
420
420
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
421
421
|
<script src="/builders/palette.js" defer></script>
|
|
422
422
|
<!-- shared frontend toolkit (window.OTB) — loaded before consuming IIFEs. -->
|
|
@@ -344,7 +344,12 @@
|
|
|
344
344
|
// The deploy door (task 1003159, ADR 0293). Gated 'archon' here to mirror the
|
|
345
345
|
// `core.pin.move` atom its page and both its routes check — cosmetic only, as
|
|
346
346
|
// every item in this list is; the wall is server-side.
|
|
347
|
-
|
|
347
|
+
//
|
|
348
|
+
// THE DOOR IS THE PROVISIONING MODULE'S, and a hosted project does not run it, so
|
|
349
|
+
// there this link opened a page with nothing behind it (task 1004174). It is
|
|
350
|
+
// dropped where the module is off, and re-pointed at the hub's own door for THIS
|
|
351
|
+
// project once /core-update names one (pointDeployAtHub, ADR 0339).
|
|
352
|
+
{ id: 'deploy', label: 'Deploy', icon: 'admin', href: '/deploy', gate: 'archon', module: 'provisioning' },
|
|
348
353
|
// task 1001949: what each hosted project costs. 'government' mirrors the metic floor
|
|
349
354
|
// of `provisioning.fleet.manage`, the atom its page and its one read check (the
|
|
350
355
|
// Deploy precedent above); dropped where the provisioning module is not enabled.
|
|
@@ -622,16 +627,40 @@
|
|
|
622
627
|
if (!perms || perms['core.pin.move'] !== true) return;
|
|
623
628
|
coreUpdateAsked = true;
|
|
624
629
|
api.request('GET', '/api/bongos/core-update').then(function (res) {
|
|
625
|
-
if (!res || res.status !== 200 || !res.data
|
|
626
|
-
|
|
630
|
+
if (!res || res.status !== 200 || !res.data) return;
|
|
631
|
+
var handoff = hubHandoff(res.data.deploy_url);
|
|
632
|
+
if (handoff) pointDeployAtHub(handoff);
|
|
633
|
+
if (res.data.update) renderCoreUpdateBanner(res.data.update, handoff);
|
|
627
634
|
}, function () { /* fail-soft: no banner is the correct answer to no answer */ });
|
|
628
635
|
}
|
|
629
636
|
|
|
637
|
+
// task 1004174 (ADR 0339) — where a HOSTED project's owner takes the upgrade: the
|
|
638
|
+
// hub's own deploy door scoped to this project, which the server names as
|
|
639
|
+
// `deploy_url` when this project is federated with a hub. Only where this hall has no
|
|
640
|
+
// door of its own: on the platform the local /deploy is the door, and a link away
|
|
641
|
+
// from it would be a second road to the same button. An address that is not a plain
|
|
642
|
+
// http(s) URL is ignored rather than trusted into an href.
|
|
643
|
+
function hubHandoff(url) {
|
|
644
|
+
if (moduleOn('provisioning')) return null;
|
|
645
|
+
if (typeof url !== 'string' || !/^https?:\/\/[^\s"'<>]+$/i.test(url)) return null;
|
|
646
|
+
return url;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// The rail's Deploy item is hidden on a hosted project (its module gate); this gives
|
|
650
|
+
// it back, aimed at the hub. Its rank gate still applies, exactly as on the platform.
|
|
651
|
+
function pointDeployAtHub(url) {
|
|
652
|
+
var item = document.getElementById('nav-deploy');
|
|
653
|
+
if (!item) return;
|
|
654
|
+
item.setAttribute('href', url);
|
|
655
|
+
if (item.dataset) delete item.dataset.module;
|
|
656
|
+
item.hidden = !gateAllows(item.dataset && item.dataset.gate);
|
|
657
|
+
}
|
|
658
|
+
|
|
630
659
|
// SAY NOTHING WHEN THERE IS NOTHING TO SAY. `behind` is false both when the box
|
|
631
660
|
// is current AND when the registry could not be read; `reading` separates them,
|
|
632
661
|
// and neither renders. A banner is a claim about being out of date, and a wrong
|
|
633
662
|
// one on a project that is actually current is worse than silence.
|
|
634
|
-
function renderCoreUpdateBanner(update) {
|
|
663
|
+
function renderCoreUpdateBanner(update, handoff) {
|
|
635
664
|
if (!update || update.reading !== 'ok' || !update.behind || !update.latest) return;
|
|
636
665
|
// A dismissal is remembered PER VERSION, not forever: the next release is new
|
|
637
666
|
// news and must re-announce itself, or the surface is dead after one click.
|
|
@@ -659,8 +688,10 @@
|
|
|
659
688
|
// happen": nothing had, and nothing had been asked for. The control is on that page
|
|
660
689
|
// already, so on it the banner informs and points, and does not pretend to act.
|
|
661
690
|
var onDeploy = window.location && String(window.location.pathname || '').replace(/\/+$/, '') === '/deploy';
|
|
691
|
+
// A hosted project has no door of its own; it hands its owner to the hub's door for
|
|
692
|
+
// this project when the server named one (task 1004174), and otherwise only informs.
|
|
662
693
|
var action = !moduleOn('provisioning')
|
|
663
|
-
? ''
|
|
694
|
+
? (handoff ? '<a class="core-update-notice__action" href="' + esc(handoff) + '">Upgrade</a>' : '')
|
|
664
695
|
: (onDeploy
|
|
665
696
|
? '<span class="core-update-notice__hint">Use the control below to take it.</span>'
|
|
666
697
|
: '<a class="core-update-notice__action" href="/deploy">Upgrade</a>');
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
|
|
67
67
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
68
68
|
|
|
69
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
69
|
+
<script src="/builders/shell.js?v=2026-09-26-own1"></script>
|
|
70
70
|
<script src="/builders/palette.js" defer></script>
|
|
71
71
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
72
72
|
<!-- The kit, for its ONE renderer: the full-size ship-time visual figure
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
|
|
58
58
|
<!-- The app shell (sidebar + top bar + footer). Dependency-free; runs at parse
|
|
59
59
|
time so the chrome exists before the deferred page scripts execute. -->
|
|
60
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
60
|
+
<script src="/builders/shell.js?v=2026-09-26-own1"></script>
|
|
61
61
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
62
62
|
<script src="/builders/palette.js" defer></script>
|
|
63
63
|
<!-- shared frontend toolkit (window.OTB) — loaded FIRST so task.js can pull from it. -->
|
|
@@ -162,7 +162,7 @@
|
|
|
162
162
|
|
|
163
163
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
164
164
|
chrome exists before the page script runs. -->
|
|
165
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
165
|
+
<script src="/builders/shell.js?v=2026-09-26-own1"></script>
|
|
166
166
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
167
167
|
<script src="/builders/palette.js" defer></script>
|
|
168
168
|
<!-- shared frontend toolkit (window.OTB) — loaded before watch.js, which pulls from it. -->
|
|
@@ -171,7 +171,7 @@
|
|
|
171
171
|
|
|
172
172
|
<!-- The app shell (sidebar + top bar + footer). Dependency-free; runs at parse
|
|
173
173
|
time so the chrome exists before the deferred page scripts execute. -->
|
|
174
|
-
<script src="/builders/shell.js?v=2026-09-
|
|
174
|
+
<script src="/builders/shell.js?v=2026-09-26-own1"></script>
|
|
175
175
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
176
176
|
<script src="/builders/palette.js" defer></script>
|
|
177
177
|
<!-- shared frontend toolkit (window.OTB) — loaded FIRST so work.js can pull from it. -->
|
|
@@ -74,10 +74,26 @@ async function deriveShipAssurance({ taskId, body = {}, db, githubPush, onProbeE
|
|
|
74
74
|
// before landProvenForTask ever saw a recorded tip, so a commit provably on main
|
|
75
75
|
// was refused as "not proven" (strands 1003285/1003286, task 1003434). Each step
|
|
76
76
|
// now fails on its own: a status miss degrades to status=null, never to no-proof.
|
|
77
|
+
//
|
|
78
|
+
// The status probe needs a BRANCH (task 1004275). It used to be called with
|
|
79
|
+
// { taskId, publishedSha }, which getPublishStatus does not read, so it threw
|
|
80
|
+
// BAD_BRANCH every time and both PR-witness proofs in landProvenForTask (the
|
|
81
|
+
// squash/out-of-band land, and the no-tip fallback) could never fire on this path,
|
|
82
|
+
// while the reconciler proved the same land ~5 min later. So find the task's own PR
|
|
83
|
+
// first, by the "task <id>: …" title both publish paths give it (the same lookup
|
|
84
|
+
// GET /publish-status does since task 1003764), and ask about that branch. No PR,
|
|
85
|
+
// or a failed search, leaves status=null: the tip proof still runs.
|
|
77
86
|
let publishedSha = null;
|
|
78
87
|
try { publishedSha = await db.getClaimPublishedSha(taskId); } catch (e) { report(e); }
|
|
88
|
+
let branch = null;
|
|
89
|
+
try {
|
|
90
|
+
const found = await githubPush.findTaskPullRequest({ taskId });
|
|
91
|
+
branch = (found && found.branch) || null;
|
|
92
|
+
} catch (e) { report(e); }
|
|
79
93
|
let status = null;
|
|
80
|
-
|
|
94
|
+
if (branch) {
|
|
95
|
+
try { status = await githubPush.getPublishStatus({ branch }); } catch (e) { report(e); }
|
|
96
|
+
}
|
|
81
97
|
try {
|
|
82
98
|
const proof = await githubPush.landProvenForTask({ taskId, publishedSha, status });
|
|
83
99
|
if (proof && proof.proven) return { assurance: { kind: 'proof', reason: proof.reason } };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// modules/provisioning/core-upgrade-authz.js — who may move ONE hosted project's core
|
|
4
|
+
// pin: its owner, or a holder of `core.pin.move` (task 1004174, ADR 0339).
|
|
5
|
+
//
|
|
6
|
+
// THE OWNER'S RULING, 2026-09-26: only a project's owner upgrades it — "owners and
|
|
7
|
+
// archons are the same" for the platform operator, because the operator owns the
|
|
8
|
+
// platform. Until this file the deploy door was `core.pin.move` alone: archon-floor
|
|
9
|
+
// and system:true, so the owner of a hosted project below Archon could see that they
|
|
10
|
+
// were behind and could never act on it. The door stays on the control plane (ADR
|
|
11
|
+
// 0293 D1): a hosted project hands its owner off to this hub's /deploy?project=<slug>
|
|
12
|
+
// and gains no capability of its own.
|
|
13
|
+
//
|
|
14
|
+
// THE SHAPE IS THE ONE THE MODULE ALREADY USES FOR "OWNER OR OPERATOR" (ADR 0328
|
|
15
|
+
// Decision 2, and platform-identity's requireScoutingReader, ADR 0210). Ownership is
|
|
16
|
+
// asked FIRST; on any "no" — not the owner, no such row, an unreadable id, a database
|
|
17
|
+
// blip — the request falls through to the UNMODIFIED permission gate. So a caller who
|
|
18
|
+
// is neither gets byte-for-byte the refusal the door has always given, whether or not
|
|
19
|
+
// the id exists, and the widening can only ever admit. The atom is still spelled in
|
|
20
|
+
// the route's middleware list, where route-rank-check.js and gen-api-docs.js read it.
|
|
21
|
+
//
|
|
22
|
+
// THE PLATFORM'S OWN ROW IS NOT A PROJECT AN OWNER MAY MOVE HERE. Moving the control
|
|
23
|
+
// plane changes the code every hosted project is driven by, so it stays on the atom:
|
|
24
|
+
// OWNER_SHAPES admits the two tenant shapes and nothing else, and a shape added later
|
|
25
|
+
// is excluded by omission.
|
|
26
|
+
|
|
27
|
+
// The tenant shapes the runner can actually reach (UPGRADE_SHAPES minus the platform).
|
|
28
|
+
const OWNER_SHAPES = Object.freeze(new Set(['co-tenant', 'cloud-host']));
|
|
29
|
+
|
|
30
|
+
// An id as the route names it: a positive integer and nothing else. Anything else is
|
|
31
|
+
// not looked up at all — it falls to the permission gate, and the handler's own
|
|
32
|
+
// parseId gives the 400 an admitted caller would get.
|
|
33
|
+
const ID_RE = /^[1-9][0-9]{0,15}$/;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Does this builder own this instance in a way that lets them move its core? PURE.
|
|
37
|
+
* Fail-closed on every missing piece: no row, no builder, no recorded owner, or a
|
|
38
|
+
* shape that is not a tenant.
|
|
39
|
+
*/
|
|
40
|
+
function ownsUpgradableProject(inst, builder) {
|
|
41
|
+
return !!inst && !!builder && builder.id != null
|
|
42
|
+
&& OWNER_SHAPES.has(inst.hosting_shape)
|
|
43
|
+
&& inst.owner_builder_id != null
|
|
44
|
+
&& String(inst.owner_builder_id) === String(builder.id);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The OR gate, as one middleware mounted after requireBuilder. `pinGate` is the
|
|
49
|
+
* ALREADY-BUILT `requirePermission('core.pin.move')` middleware.
|
|
50
|
+
*
|
|
51
|
+
* It records WHICH door admitted the caller on `req.coreUpgradeAuthority`, because
|
|
52
|
+
* the intent's actor has to say so truthfully (coreUpgradeActor below).
|
|
53
|
+
*/
|
|
54
|
+
function requireOwnProjectOrPin({ pinGate, provisioning, pool, log }) {
|
|
55
|
+
return async function ownProjectOrPinGate(req, res, next) {
|
|
56
|
+
const raw = req.params ? req.params.id : undefined;
|
|
57
|
+
if (typeof raw === 'string' && ID_RE.test(raw)) {
|
|
58
|
+
try {
|
|
59
|
+
const inst = await provisioning.getInstanceById(pool, Number(raw));
|
|
60
|
+
if (ownsUpgradableProject(inst, req.builder)) {
|
|
61
|
+
req.coreUpgradeAuthority = 'owner';
|
|
62
|
+
return next();
|
|
63
|
+
}
|
|
64
|
+
} catch (err) {
|
|
65
|
+
// Message only: a query error can carry row data. The permission gate below
|
|
66
|
+
// then decides, so a blip here can deny an owner but never admit anyone.
|
|
67
|
+
if (log && log.error) log.error(`core-upgrade owner check failed, falling back to core.pin.move: ${err && err.message}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
req.coreUpgradeAuthority = 'pin';
|
|
71
|
+
return pinGate(req, res, next);
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The actor recorded on the intent and the event. Both forms carry the builder id,
|
|
77
|
+
* because the runner hands it to the core_upgrades ledger as `--requested-by` and
|
|
78
|
+
* ADR 0293 D5 refuses an unattributed row for a move an HTTP request started.
|
|
79
|
+
*/
|
|
80
|
+
function coreUpgradeActor(req) {
|
|
81
|
+
const door = req.coreUpgradeAuthority === 'owner' ? 'owner' : 'archon';
|
|
82
|
+
return `api:${door}:${req.builder.id}`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = { OWNER_SHAPES, ownsUpgradableProject, requireOwnProjectOrPin, coreUpgradeActor };
|
|
@@ -177,7 +177,8 @@ function makeCoreUpgradeHandler({ provisioning, pool, parseId, validateOrRespond
|
|
|
177
177
|
// The actor rides the intent, and the runner carries it through to the
|
|
178
178
|
// core_upgrades ledger (ADR 0293 D5): a privileged action with an
|
|
179
179
|
// unattributed row is not acceptable here.
|
|
180
|
-
|
|
180
|
+
// `api:owner:<id>` when the project's owner pressed it (task 1004174), else `api:archon:<id>`.
|
|
181
|
+
const actor = require('./core-upgrade-authz').coreUpgradeActor(req);
|
|
181
182
|
// `dry_run: true` asks what the move WOULD do and moves nothing (task 1004121).
|
|
182
183
|
// Queued the same way as the move, through the same single-open-intent slot, so a
|
|
183
184
|
// preview cannot be requested on top of a move that is already running — and, just
|
|
@@ -1158,9 +1158,9 @@ async function enqueueIntent(db, instanceId, action, requestedBy, targetVersion
|
|
|
1158
1158
|
// delete hostage would repeat 1002691. A pending 'restart' or 'repo-private' (task 1004193) joins them for the same
|
|
1159
1159
|
// reason (task 1003238) — and more sharply, since the owner reaches for Restart
|
|
1160
1160
|
// precisely when a project looks broken, which is also when they give up and delete
|
|
1161
|
-
// it. A RUNNING intent is mid-flight and never yanked —
|
|
1162
|
-
//
|
|
1163
|
-
//
|
|
1161
|
+
// it. A RUNNING intent is mid-flight and never yanked — the caller gets { conflict: 'open_intent' } with the blocking intent so the route
|
|
1162
|
+
// can answer truthfully. The control-plane row is never superseded, in the same statement (task 1004286): enqueueIntent's guard refuses its
|
|
1163
|
+
// teardown, and a refused teardown must cancel nothing (it used to cancel a pending restart). Each statement is atomic, the index the arbiter:
|
|
1164
1164
|
// any interleaving with the runner or a concurrent enqueue lands on one of these
|
|
1165
1165
|
// truthful outcomes.
|
|
1166
1166
|
async function enqueueTeardown(db, instanceId, requestedBy) {
|
|
@@ -1169,7 +1169,7 @@ async function enqueueTeardown(db, instanceId, requestedBy) {
|
|
|
1169
1169
|
SET state = 'error', last_error = 'superseded: teardown requested',
|
|
1170
1170
|
updated_at = now(), resolved_at = now()
|
|
1171
1171
|
WHERE instance_id = $1 AND action IN ('provision', 'settings-apply', 'restart', 'repo-private') AND state = 'pending'
|
|
1172
|
-
|
|
1172
|
+
AND NOT EXISTS (SELECT 1 FROM provisioning_instances WHERE id = $1 AND hosting_shape = 'control-plane') RETURNING id`,
|
|
1173
1173
|
[instanceId]
|
|
1174
1174
|
);
|
|
1175
1175
|
const { intent, created } = await enqueueIntent(db, instanceId, 'teardown', requestedBy);
|
|
@@ -26,6 +26,7 @@ const express = require('express');
|
|
|
26
26
|
const api = require('../../../src/module-api');
|
|
27
27
|
const provisioning = require('../provisioning');
|
|
28
28
|
const { makeCoreUpgradeHandler, makeCoreUpgradePreviewHandler, makeControlPlaneHandler } = require('../core-upgrade');
|
|
29
|
+
const { requireOwnProjectOrPin } = require('../core-upgrade-authz');
|
|
29
30
|
|
|
30
31
|
const log = api.logger('provisioning');
|
|
31
32
|
|
|
@@ -40,6 +41,11 @@ module.exports = function buildCoreUpgradeRouter() {
|
|
|
40
41
|
// and not `requireRank`: tests/government_require_permission.mjs fails the build
|
|
41
42
|
// on a new requireRank in a modules/*/routes gate.
|
|
42
43
|
//
|
|
44
|
+
// OR THE PROJECT'S OWN OWNER (task 1004174, ADR 0339 — the owner's ruling that only
|
|
45
|
+
// a project's owner upgrades it). Ownership of a TENANT row is asked first; any other
|
|
46
|
+
// caller meets the unmodified `core.pin.move` gate and its unchanged refusal. The
|
|
47
|
+
// platform's own row is not an owner's to move here — it stays on the atom.
|
|
48
|
+
//
|
|
43
49
|
// Body: { to: "1.19.757" } — and requiring it IS the second step of the two-step
|
|
44
50
|
// the owner asked for (ADR 0293 D4). A request that cannot fire without naming
|
|
45
51
|
// its target cannot fire from a stray click, and "move to whatever is newest",
|
|
@@ -55,7 +61,7 @@ module.exports = function buildCoreUpgradeRouter() {
|
|
|
55
61
|
router.post(
|
|
56
62
|
'/provisioning/instances/:id/core-upgrade',
|
|
57
63
|
api.requireBuilder,
|
|
58
|
-
api.requirePermission('core.pin.move'),
|
|
64
|
+
requireOwnProjectOrPin({ pinGate: api.requirePermission('core.pin.move'), provisioning, pool: api.pool, log }),
|
|
59
65
|
makeCoreUpgradeHandler({
|
|
60
66
|
provisioning,
|
|
61
67
|
pool: api.pool,
|
|
@@ -71,19 +77,19 @@ module.exports = function buildCoreUpgradeRouter() {
|
|
|
71
77
|
// core version and how far behind it is, which is a vulnerability-window disclosure —
|
|
72
78
|
// precisely the sort of thing ADR 0043 keeps off unauthenticated surfaces. A preview
|
|
73
79
|
// anyone could read would also let an attacker time a request against a known-stale
|
|
74
|
-
// window. The read being no cheaper than the write costs the owner nothing:
|
|
75
|
-
// the
|
|
80
|
+
// window. The read being no cheaper than the write costs the owner nothing: whoever
|
|
81
|
+
// may press the button may read what it would do — the same two doors as the POST.
|
|
76
82
|
router.get(
|
|
77
83
|
'/provisioning/instances/:id/core-upgrade',
|
|
78
84
|
api.requireBuilder,
|
|
79
|
-
api.requirePermission('core.pin.move'),
|
|
85
|
+
requireOwnProjectOrPin({ pinGate: api.requirePermission('core.pin.move'), provisioning, pool: api.pool, log }),
|
|
80
86
|
makeCoreUpgradePreviewHandler({ provisioning, pool: api.pool, parseId: api.parseId, log }),
|
|
81
87
|
);
|
|
82
88
|
|
|
83
89
|
// GET /provisioning/control-plane — "which row is this platform, and where does it
|
|
84
90
|
// stand?" The deploy page's first call: the self row is created once by hand at
|
|
85
|
-
// install time, so its id is per-install and nobody's to memorise.
|
|
86
|
-
//
|
|
91
|
+
// install time, so its id is per-install and nobody's to memorise. The atom ALONE
|
|
92
|
+
// here, with no owner door: this is the platform's own row (ADR 0339).
|
|
87
93
|
router.get(
|
|
88
94
|
'/provisioning/control-plane',
|
|
89
95
|
api.requireBuilder,
|