@bongos/core 1.19.788 → 1.19.790

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 (48) hide show
  1. package/.bongos-core.json +111 -41
  2. package/clients/bongos-client/README.md +1 -1
  3. package/clients/bongos-client/bongos-client.global.js +6 -0
  4. package/clients/bongos-client/index.cjs +6 -0
  5. package/clients/bongos-client/index.d.ts +6 -0
  6. package/clients/bongos-client/index.mjs +6 -0
  7. package/docs/adr/0293-the-owners-deploy-door-is-an-intent-the-control-plane-drains.md +124 -0
  8. package/docs/adr/README.md +1 -0
  9. package/docs/api/openapi.json +129 -1
  10. package/docs/api-reference.md +5 -2
  11. package/docs/copy-inventory.md +31 -14
  12. package/docs/copy-registry.json +205 -31
  13. package/docs/module-api-changelog.md +4 -0
  14. package/docs/recipes/core-release-pipeline.md +12 -0
  15. package/docs/recipes/upgrading-the-core.md +7 -1
  16. package/migrations/core_241_core_upgrades_actor.sql +65 -0
  17. package/migrations/core_242_core_version_snapshot.sql +54 -0
  18. package/modules/dev-box/app/src/vendor/bongos-client.cjs +6 -0
  19. package/modules/government/catalog.js +8 -0
  20. package/modules/government/migrations/government_014_core_pin_move.sql +46 -0
  21. package/modules/hall-ui/public/deploy.html +64 -0
  22. package/modules/hall-ui/public/deploy.js +188 -0
  23. package/modules/hall-ui/public/shell.js +4 -0
  24. package/modules/provisioning/control-plane-guard.js +102 -0
  25. package/modules/provisioning/core-upgrade.js +289 -0
  26. package/modules/provisioning/migrations/provisioning_018_core_upgrade_intent.sql +98 -0
  27. package/modules/provisioning/module.json +1 -1
  28. package/modules/provisioning/provisioning.js +8 -8
  29. package/modules/provisioning/routes/core-upgrade.js +95 -0
  30. package/package-lock.json +2 -2
  31. package/package.json +1 -1
  32. package/scripts/gds/enroll-control-plane.js +101 -0
  33. package/scripts/gds/provision-core-upgrade.js +311 -0
  34. package/scripts/gds/provision.js +4 -2
  35. package/scripts/gds/upgrade.js +53 -14
  36. package/src/bongos/auth.js +19 -0
  37. package/src/bongos/serve-internal.js +21 -0
  38. package/src/module-api.js +1 -1
  39. package/tests/auth_resolve_failure.mjs +4 -2
  40. package/tests/core_upgrade_door.mjs +408 -0
  41. package/tests/core_upgrade_runner.mjs +377 -0
  42. package/tests/hall_nav.mjs +4 -1
  43. package/tests/hall_page_gate_map.mjs +7 -0
  44. package/tests/provisioning_disconnect.mjs +30 -5
  45. package/tests/provisioning_paid_shape_gate.mjs +14 -4
  46. package/tests/provisioning_restart.mjs +25 -9
  47. package/tests/provisioning_settings_apply.mjs +11 -2
  48. package/tests/provisioning_teardown_intent.mjs +7 -0
@@ -3,17 +3,17 @@
3
3
  "generator": "scripts/gds/copy-inventory.js",
4
4
  "goal": 1000074,
5
5
  "counts": {
6
- "strings": 1776,
7
- "unique_ids": 1535,
8
- "certain": 1776,
6
+ "strings": 1793,
7
+ "unique_ids": 1549,
8
+ "certain": 1793,
9
9
  "heuristic": 0,
10
- "duplicate_ids": 167
10
+ "duplicate_ids": 169
11
11
  },
12
12
  "surfaces": {
13
13
  "builders-hall": {
14
14
  "at": "modules/hall-ui/public",
15
- "files": 82,
16
- "strings": 1286
15
+ "files": 84,
16
+ "strings": 1303
17
17
  },
18
18
  "status-dashboard": {
19
19
  "at": "modules/status-ui/public",
@@ -86,6 +86,20 @@
86
86
  "modules/public-landing/public/projects.html:1162"
87
87
  ]
88
88
  },
89
+ {
90
+ "id": "9a685b01e2c6",
91
+ "surface": "builders-hall",
92
+ "text": "This page opens at a higher rank.",
93
+ "occurrences": 6,
94
+ "at": [
95
+ "modules/hall-ui/public/deploy.html:31",
96
+ "modules/hall-ui/public/gate.html:37",
97
+ "modules/hall-ui/public/government.html:37",
98
+ "modules/hall-ui/public/harbor.html:37",
99
+ "modules/hall-ui/public/project-settings.html:36",
100
+ "modules/hall-ui/public/watch.html:40"
101
+ ]
102
+ },
89
103
  {
90
104
  "id": "5df2baabf82e",
91
105
  "surface": "landing",
@@ -152,19 +166,6 @@
152
166
  "modules/hall-ui/public/watch.js:1306"
153
167
  ]
154
168
  },
155
- {
156
- "id": "9a685b01e2c6",
157
- "surface": "builders-hall",
158
- "text": "This page opens at a higher rank.",
159
- "occurrences": 5,
160
- "at": [
161
- "modules/hall-ui/public/gate.html:37",
162
- "modules/hall-ui/public/government.html:37",
163
- "modules/hall-ui/public/harbor.html:37",
164
- "modules/hall-ui/public/project-settings.html:36",
165
- "modules/hall-ui/public/watch.html:40"
166
- ]
167
- },
168
169
  {
169
170
  "id": "4d8ee7d68974",
170
171
  "surface": "builders-hall",
@@ -247,7 +248,7 @@
247
248
  "modules/hall-ui/public/hall-render.js:275",
248
249
  "modules/hall-ui/public/hall-render.js:346",
249
250
  "modules/hall-ui/public/hall-render.js:415",
250
- "modules/hall-ui/public/shell.js:418"
251
+ "modules/hall-ui/public/shell.js:422"
251
252
  ]
252
253
  },
253
254
  {
@@ -742,6 +743,16 @@
742
743
  "modules/hall-ui/public/settings.html:95"
743
744
  ]
744
745
  },
746
+ {
747
+ "id": "7ec7dc1adaa6",
748
+ "surface": "builders-hall",
749
+ "text": "Deploy — {{worldName}}",
750
+ "occurrences": 2,
751
+ "at": [
752
+ "modules/hall-ui/public/deploy.html:6",
753
+ "modules/hall-ui/public/deploy.html:6"
754
+ ]
755
+ },
745
756
  {
746
757
  "id": "457f4967541b",
747
758
  "surface": "builders-hall",
@@ -1002,6 +1013,16 @@
1002
1013
  "modules/hall-ui/public/modules.html:6"
1003
1014
  ]
1004
1015
  },
1016
+ {
1017
+ "id": "e77d5f3beaef",
1018
+ "surface": "builders-hall",
1019
+ "text": "Move to {…}",
1020
+ "occurrences": 2,
1021
+ "at": [
1022
+ "modules/hall-ui/public/deploy.js:103",
1023
+ "modules/hall-ui/public/deploy.js:161"
1024
+ ]
1025
+ },
1005
1026
  {
1006
1027
  "id": "4a167ef6e515",
1007
1028
  "surface": "landing",
@@ -1348,8 +1369,8 @@
1348
1369
  "text": "Sign out",
1349
1370
  "occurrences": 2,
1350
1371
  "at": [
1351
- "modules/hall-ui/public/shell.js:436",
1352
- "modules/hall-ui/public/shell.js:593"
1372
+ "modules/hall-ui/public/shell.js:440",
1373
+ "modules/hall-ui/public/shell.js:597"
1353
1374
  ]
1354
1375
  },
1355
1376
  {
@@ -4358,6 +4379,24 @@
4358
4379
  "origin": "js-markup",
4359
4380
  "confidence": "certain"
4360
4381
  },
4382
+ {
4383
+ "id": "7ec7dc1adaa6",
4384
+ "surface": "builders-hall",
4385
+ "text": "Deploy — {{worldName}}",
4386
+ "file": "modules/hall-ui/public/deploy.html",
4387
+ "line": 6,
4388
+ "origin": "html-text",
4389
+ "confidence": "certain"
4390
+ },
4391
+ {
4392
+ "id": "7ec7dc1adaa6",
4393
+ "surface": "builders-hall",
4394
+ "text": "Deploy — {{worldName}}",
4395
+ "file": "modules/hall-ui/public/deploy.html",
4396
+ "line": 6,
4397
+ "origin": "html-title",
4398
+ "confidence": "certain"
4399
+ },
4361
4400
  {
4362
4401
  "id": "8cac6be8afdf",
4363
4402
  "surface": "builders-hall",
@@ -5893,7 +5932,7 @@
5893
5932
  "surface": "builders-hall",
5894
5933
  "text": "Jump to a page or record",
5895
5934
  "file": "modules/hall-ui/public/shell.js",
5896
- "line": 457,
5935
+ "line": 461,
5897
5936
  "origin": "js-markup",
5898
5937
  "confidence": "certain"
5899
5938
  },
@@ -5902,7 +5941,7 @@
5902
5941
  "surface": "builders-hall",
5903
5942
  "text": "Jump to…",
5904
5943
  "file": "modules/hall-ui/public/shell.js",
5905
- "line": 459,
5944
+ "line": 463,
5906
5945
  "origin": "js-markup",
5907
5946
  "confidence": "certain"
5908
5947
  },
@@ -6347,6 +6386,15 @@
6347
6386
  "origin": "html-text",
6348
6387
  "confidence": "certain"
6349
6388
  },
6389
+ {
6390
+ "id": "c4b39de1e478",
6391
+ "surface": "builders-hall",
6392
+ "text": "Make it live",
6393
+ "file": "modules/hall-ui/public/deploy.js",
6394
+ "line": 128,
6395
+ "origin": "js-markup",
6396
+ "confidence": "certain"
6397
+ },
6350
6398
  {
6351
6399
  "id": "6b3d660e0e31",
6352
6400
  "surface": "builders-hall",
@@ -6383,6 +6431,15 @@
6383
6431
  "origin": "js-markup",
6384
6432
  "confidence": "certain"
6385
6433
  },
6434
+ {
6435
+ "id": "6bd456635167",
6436
+ "surface": "builders-hall",
6437
+ "text": "Merging work does not put it in front of anyone. This is the step that does: it moves this instance onto a newer core, waits for the box to answer, and puts itself back if it does not.",
6438
+ "file": "modules/hall-ui/public/deploy.html",
6439
+ "line": 39,
6440
+ "origin": "html-text",
6441
+ "confidence": "certain"
6442
+ },
6386
6443
  {
6387
6444
  "id": "7637613c231b",
6388
6445
  "surface": "builders-hall",
@@ -6500,6 +6557,24 @@
6500
6557
  "origin": "js-markup",
6501
6558
  "confidence": "certain"
6502
6559
  },
6560
+ {
6561
+ "id": "e77d5f3beaef",
6562
+ "surface": "builders-hall",
6563
+ "text": "Move to {…}",
6564
+ "file": "modules/hall-ui/public/deploy.js",
6565
+ "line": 103,
6566
+ "origin": "js-markup",
6567
+ "confidence": "certain"
6568
+ },
6569
+ {
6570
+ "id": "e77d5f3beaef",
6571
+ "surface": "builders-hall",
6572
+ "text": "Move to {…}",
6573
+ "file": "modules/hall-ui/public/deploy.js",
6574
+ "line": 161,
6575
+ "origin": "js-text-assign",
6576
+ "confidence": "certain"
6577
+ },
6503
6578
  {
6504
6579
  "id": "ae84ebe1d735",
6505
6580
  "surface": "builders-hall",
@@ -7049,6 +7124,15 @@
7049
7124
  "origin": "js-markup",
7050
7125
  "confidence": "certain"
7051
7126
  },
7127
+ {
7128
+ "id": "9ac63d97239a",
7129
+ "surface": "builders-hall",
7130
+ "text": "Nothing newer is published that this box can take. There is nothing to deploy.",
7131
+ "file": "modules/hall-ui/public/deploy.js",
7132
+ "line": 126,
7133
+ "origin": "js-markup",
7134
+ "confidence": "certain"
7135
+ },
7052
7136
  {
7053
7137
  "id": "ad9a17a8a5eb",
7054
7138
  "surface": "builders-hall",
@@ -7225,7 +7309,7 @@
7225
7309
  "surface": "builders-hall",
7226
7310
  "text": "Open navigation",
7227
7311
  "file": "modules/hall-ui/public/shell.js",
7228
- "line": 448,
7312
+ "line": 452,
7229
7313
  "origin": "js-markup",
7230
7314
  "confidence": "certain"
7231
7315
  },
@@ -8543,6 +8627,15 @@
8543
8627
  "origin": "js-markup",
8544
8628
  "confidence": "certain"
8545
8629
  },
8630
+ {
8631
+ "id": "55cde0774831",
8632
+ "surface": "builders-hall",
8633
+ "text": "See what would change",
8634
+ "file": "modules/hall-ui/public/deploy.js",
8635
+ "line": 91,
8636
+ "origin": "js-markup",
8637
+ "confidence": "certain"
8638
+ },
8546
8639
  {
8547
8640
  "id": "75dc5463facc",
8548
8641
  "surface": "builders-hall",
@@ -8597,6 +8690,15 @@
8597
8690
  "origin": "js-markup",
8598
8691
  "confidence": "certain"
8599
8692
  },
8693
+ {
8694
+ "id": "fcd68c6a21fd",
8695
+ "surface": "builders-hall",
8696
+ "text": "Serving now",
8697
+ "file": "modules/hall-ui/public/deploy.js",
8698
+ "line": 115,
8699
+ "origin": "js-markup",
8700
+ "confidence": "certain"
8701
+ },
8600
8702
  {
8601
8703
  "id": "64a1f055129e",
8602
8704
  "surface": "builders-hall",
@@ -8872,7 +8974,7 @@
8872
8974
  "surface": "builders-hall",
8873
8975
  "text": "Sign in with GitHub",
8874
8976
  "file": "modules/hall-ui/public/shell.js",
8875
- "line": 418,
8977
+ "line": 422,
8876
8978
  "origin": "js-markup",
8877
8979
  "confidence": "certain"
8878
8980
  },
@@ -8881,7 +8983,7 @@
8881
8983
  "surface": "builders-hall",
8882
8984
  "text": "Sign out",
8883
8985
  "file": "modules/hall-ui/public/shell.js",
8884
- "line": 436,
8986
+ "line": 440,
8885
8987
  "origin": "js-markup",
8886
8988
  "confidence": "certain"
8887
8989
  },
@@ -8890,7 +8992,7 @@
8890
8992
  "surface": "builders-hall",
8891
8993
  "text": "Sign out",
8892
8994
  "file": "modules/hall-ui/public/shell.js",
8893
- "line": 593,
8995
+ "line": 597,
8894
8996
  "origin": "js-markup",
8895
8997
  "confidence": "certain"
8896
8998
  },
@@ -8917,7 +9019,7 @@
8917
9019
  "surface": "builders-hall",
8918
9020
  "text": "Skip to content",
8919
9021
  "file": "modules/hall-ui/public/shell.js",
8920
- "line": 584,
9022
+ "line": 588,
8921
9023
  "origin": "js-text-assign",
8922
9024
  "confidence": "certain"
8923
9025
  },
@@ -9097,7 +9199,7 @@
9097
9199
  "surface": "builders-hall",
9098
9200
  "text": "Switch theme",
9099
9201
  "file": "modules/hall-ui/public/shell.js",
9100
- "line": 463,
9202
+ "line": 467,
9101
9203
  "origin": "js-markup",
9102
9204
  "confidence": "certain"
9103
9205
  },
@@ -9259,7 +9361,7 @@
9259
9361
  "surface": "builders-hall",
9260
9362
  "text": "Text size",
9261
9363
  "file": "modules/hall-ui/public/shell.js",
9262
- "line": 462,
9364
+ "line": 466,
9263
9365
  "origin": "js-markup",
9264
9366
  "confidence": "certain"
9265
9367
  },
@@ -10001,6 +10103,15 @@
10001
10103
  "origin": "html-text",
10002
10104
  "confidence": "certain"
10003
10105
  },
10106
+ {
10107
+ "id": "9a685b01e2c6",
10108
+ "surface": "builders-hall",
10109
+ "text": "This page opens at a higher rank.",
10110
+ "file": "modules/hall-ui/public/deploy.html",
10111
+ "line": 31,
10112
+ "origin": "html-text",
10113
+ "confidence": "certain"
10114
+ },
10004
10115
  {
10005
10116
  "id": "9a685b01e2c6",
10006
10117
  "surface": "builders-hall",
@@ -10046,6 +10157,15 @@
10046
10157
  "origin": "html-text",
10047
10158
  "confidence": "certain"
10048
10159
  },
10160
+ {
10161
+ "id": "77e3b8f5318b",
10162
+ "surface": "builders-hall",
10163
+ "text": "This restarts the platform onto core",
10164
+ "file": "modules/hall-ui/public/deploy.js",
10165
+ "line": 103,
10166
+ "origin": "js-markup",
10167
+ "confidence": "certain"
10168
+ },
10049
10169
  {
10050
10170
  "id": "69e8beced2ff",
10051
10171
  "surface": "builders-hall",
@@ -10433,6 +10553,15 @@
10433
10553
  "origin": "html-attr:placeholder",
10434
10554
  "confidence": "certain"
10435
10555
  },
10556
+ {
10557
+ "id": "04b3ff4c4f4c",
10558
+ "surface": "builders-hall",
10559
+ "text": "What users can see",
10560
+ "file": "modules/hall-ui/public/deploy.html",
10561
+ "line": 35,
10562
+ "origin": "html-text",
10563
+ "confidence": "certain"
10564
+ },
10436
10565
  {
10437
10566
  "id": "1d5bfd09068c",
10438
10567
  "surface": "builders-hall",
@@ -11711,6 +11840,15 @@
11711
11840
  "origin": "html-text",
11712
11841
  "confidence": "certain"
11713
11842
  },
11843
+ {
11844
+ "id": "d0e722302840",
11845
+ "surface": "builders-hall",
11846
+ "text": "is published and this box is not on it yet.",
11847
+ "file": "modules/hall-ui/public/deploy.js",
11848
+ "line": 91,
11849
+ "origin": "js-markup",
11850
+ "confidence": "certain"
11851
+ },
11714
11852
  {
11715
11853
  "id": "aa530bd15088",
11716
11854
  "surface": "builders-hall",
@@ -11846,6 +11984,15 @@
11846
11984
  "origin": "js-markup",
11847
11985
  "confidence": "certain"
11848
11986
  },
11987
+ {
11988
+ "id": "9e194a57d054",
11989
+ "surface": "builders-hall",
11990
+ "text": "no reading",
11991
+ "file": "modules/hall-ui/public/deploy.js",
11992
+ "line": 74,
11993
+ "origin": "js-markup",
11994
+ "confidence": "certain"
11995
+ },
11849
11996
  {
11850
11997
  "id": "33ca71bef112",
11851
11998
  "surface": "builders-hall",
@@ -12089,6 +12236,15 @@
12089
12236
  "origin": "html-attr:placeholder",
12090
12237
  "confidence": "certain"
12091
12238
  },
12239
+ {
12240
+ "id": "41a4b66c21e5",
12241
+ "surface": "builders-hall",
12242
+ "text": "reads the control plane's own observation · ADR 0293 · every move is recorded with who asked",
12243
+ "file": "modules/hall-ui/public/deploy.js",
12244
+ "line": 132,
12245
+ "origin": "js-markup",
12246
+ "confidence": "certain"
12247
+ },
12092
12248
  {
12093
12249
  "id": "f83f0a9d6b6a",
12094
12250
  "surface": "builders-hall",
@@ -12953,6 +13109,15 @@
12953
13109
  "origin": "js-markup",
12954
13110
  "confidence": "certain"
12955
13111
  },
13112
+ {
13113
+ "id": "d9b4ae2db0f4",
13114
+ "surface": "builders-hall",
13115
+ "text": "{…} — read from the box itself, {…}.",
13116
+ "file": "modules/hall-ui/public/deploy.js",
13117
+ "line": 115,
13118
+ "origin": "js-markup",
13119
+ "confidence": "certain"
13120
+ },
12956
13121
  {
12957
13122
  "id": "085f93f3f775",
12958
13123
  "surface": "builders-hall",
@@ -12962,6 +13127,15 @@
12962
13127
  "origin": "js-markup",
12963
13128
  "confidence": "certain"
12964
13129
  },
13130
+ {
13131
+ "id": "b814cb35ce2f",
13132
+ "surface": "builders-hall",
13133
+ "text": "{…} — the box this platform runs on",
13134
+ "file": "modules/hall-ui/public/deploy.js",
13135
+ "line": 168,
13136
+ "origin": "js-text-assign",
13137
+ "confidence": "certain"
13138
+ },
12965
13139
  {
12966
13140
  "id": "f75ee96a7511",
12967
13141
  "surface": "builders-hall",
@@ -2050,5 +2050,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
2050
2050
  landed since 1.19.786 with no explicit bump. run 35158163962. (task 1002620)
2051
2051
  1.19.788 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2052
2052
  landed since 1.19.787 with no explicit bump. run 35159266737. (task 1002620)
2053
+ 1.19.789 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2054
+ landed since 1.19.788 with no explicit bump. run 35159544354. (task 1002620)
2055
+ 1.19.790 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2056
+ landed since 1.19.789 with no explicit bump. run 35163350575. (task 1002620)
2053
2057
  ---------------------------------------------------------------------------
2054
2058
  ```
@@ -112,6 +112,18 @@ node scripts/gds/package-core.js --publish --live # permanent
112
112
 
113
113
  ## Gate 3 — move the instance's pin
114
114
 
115
+ > **There is now a control for this, and it is the owner's** (task 1003159, [ADR 0293](../adr/0293-the-owners-deploy-door-is-an-intent-the-control-plane-drains.md)). Everything below still describes what actually happens on the box, and is what you need when something goes wrong — but the ordinary path no longer requires a shell or an ssh key.
116
+ >
117
+ > **Hall → Deploy** (`/deploy`) shows the version the box is actually serving, what it could move to, and moves it on an explicit second click. It is gated on the `core.pin.move` atom — archon-floor and `system: true` — and every move lands in the `core_upgrades` ledger with the builder who asked. The web tier never gains a shell: it queues a `core-upgrade` intent and the control-plane runner drains it, driving exactly the `bongos upgrade` invocation documented below, with its health gate and auto-rollback intact.
118
+ >
119
+ > **One install-time step enables it.** The door needs a `provisioning_instances` row to point at, and the control plane has never had one:
120
+ >
121
+ > ```bash
122
+ > node scripts/gds/enroll-control-plane.js --owner <builderId> --apply
123
+ > ```
124
+ >
125
+ > Until that is run, `/deploy` says so and names this command. The self row carries `hosting_shape='control-plane'`, which every destructive intent action refuses by name — a teardown aimed at it would destroy the platform from inside its own UI, so the guard is an allow-list (`core-upgrade`, `restart`) enforced at `enqueueIntent`, the one place every intent passes.
126
+
115
127
  Each instance decides, independently, when to take a release. **Which mechanism it uses is a per-instance fact you must establish before prescribing anything** — read its `package.json`:
116
128
 
117
129
  | Pin in `package.json` | Path | How it updates |
@@ -110,6 +110,8 @@ node .claude/scheduled-tasks/core-update-subscription/subscribe.js --apply #
110
110
 
111
111
  Each subscribed instance: read its installed core → ask the registry (`npm view`, no credential needed — the package is public) → resolve the newest version its channel permits → if newer, `bongos upgrade --to <v> --registry` with **auto-rollback ON**. It prints one line per instance plus a tally; a non-zero exit means an upgrade failed (already auto-reverted).
112
112
 
113
+ > **Enrolling a `file:`-pinned instance resolves NOTHING — convert the pin first** (task 1003915). A scaffolded co-tenant declares its core as `"@bongos/core": "file:vendor/bongos-core-<v>.tgz"`, a local path with no registry channel to follow, so the sweep has nothing to compare against and the instance sits frozen while the roster reports it enrolled. The conversion is not a separate chore: one manual `bongos upgrade --to <v> --registry` rewrites the pin to a bare registry semver on its way through, and every later sweep then works. Measured on the cloudbongos droplet 2026-09-13, **20 of 22 co-tenants** were `file:`-pinned, which is why enrolment alone had never moved any of them.
114
+
113
115
  **Triple-gated, so nothing moves by accident:** the `core-update-subscription` routine is `requiresAutonomy: true` + **default OFF** in [`config/scheduled-routines.json`](../../config/scheduled-routines.json), the roster ships empty, and every upgrade is health-gated. Arm it by enabling the routine's timer on the control plane once autonomy is on.
114
116
 
115
117
  **Not covered:** multi-host / SSH orchestration across a real fleet (the deferred fleet-control-plane epic, task [#1948](https://example.com/builders#/task/1948)); and *publishing* new patches, which stays a gated owner step (ADR 0134) — a follow-up task decides whether merges auto-publish.
@@ -119,7 +121,11 @@ Each subscribed instance: read its installed core → ask the registry (`npm vie
119
121
  - **`--registry` needs no credential at all** (task 1003878). `@bongos/core` is **public** on npm (task 1003872), so `bongos upgrade --registry` resolves it from the default registry like any other dependency — no token, no login, and no instance `.npmrc`. An install failure on this path is therefore a *real* failure (network, registry outage, or a version that was never published), never a missing credential.
120
122
  *History, so the symptom is recognisable on an old instance:* the core used to publish private, and the instance `.npmrc` read `_authToken=<redacted> npm gives that per-project key precedence, so with the env var unset it expanded EMPTY and **masked a perfectly valid literal token in `~/.npmrc`** — npm went anonymous, the private core 404'd, and the error blamed a credential the box actually held. Task 1002754 worked around it by resolving the token in-process; task 1003878 deleted the whole mechanism along with the `.npmrc` that caused it. An instance still carrying a committed `.npmrc` from a pre-1003878 scaffold can safely delete it.
121
123
  - **A green `publish` run is not evidence of a publish.** The publish-on-merge lane succeeds on its no-op paths too (cascade terminator, armed-gate). Read the run's **job summary**, which now states the verdict explicitly: `PUBLISHED @bongos/core@<v>` vs `NO-OP — nothing was published this run`.
122
- - **`PGDATABASE` is load-bearing on a co-tenant droplet.** `migrate.sh` defaults `PGDATABASE` to `example` — **production**. Always set `PGDATABASE=<instance-db>` explicitly, or you migrate the wrong database.
124
+ - **`PGDATABASE` is load-bearing on a co-tenant droplet.** `migrate.sh` defaults `PGDATABASE` to `example` — **production**. Always set `PGDATABASE=<instance-db>` explicitly, or you migrate the wrong database. The dry run checks this *before* the pin moves, so `--dry-run` first costs seconds and saves a full rollback.
125
+ - **The pin push to a co-tenant's `origin` always fails, and that is not the bump failing.** A co-tenant's remote is `git@github-instances:example-owner/<slug>.git` and the alias carries no usable key, so `--commit-pin` commits locally and the push is refused — reported as a failure even though the core installed, restarted, passed its health check and is serving. Read the **served version** before believing the tally.
126
+ - **⚠ An enrolled co-tenant upgrades ONCE, then wedges itself out of the sweep** (proven on `charter` 2026-09-16, task 1003915). The upgrade's own *materialize* and *regen doc assets* steps rewrite tracked files (`.claude/skills/*`, `clients/*` — 14 of them), and the pin-commit step commits only `package.json`/`package-lock.json`. So a bump that genuinely succeeded — installed, restarted, health 200, **serving** the new core — exits `failed=1`, and the **next** sweep refuses the instance: `worktree not clean and the changes are NOT the pin … skipped, a human needs to look`. Every later run skips it, and a skip is not a failure in the tally, so nothing surfaces it. Until this is fixed, `git add -A && git commit` inside the instance after each sweep; that un-wedges it immediately.
127
+ - **Survey co-tenant git state as `lars`, never as root.** `ssh cloudbongos` lands you as root, where `git config --get remote.origin.url` on a `lars`-owned repo returns **nothing** — which reads exactly like "this instance has no remote" and is wrong. They do: `git@github-instances:example-owner/<slug>.git`. Prefix every probe with `sudo -u lars`.
128
+ - **A scaffold's `migrate` script is not guaranteed.** 21 of the 22 co-tenants have `scripts.migrate`; `mercury` has **no `scripts` block at all** and is started directly by its systemd `ExecStart`, so the normal lane cannot complete on it. `--skip-migrate` gets the install through but silently skips every schema change in the window — check what that window carries before reaching for it, rather than after.
123
129
  - **The health check polls the restart** (fixed in task [#1984](https://example.com/builders#/task/1984)). The service binds its port ~1s after `systemctl restart`, so `--health-url` now polls up to ~30s for a 2xx before reporting a warning — an immediate `fetch failed` no longer means a failed upgrade. (Pre-1984 cores fetched once and spuriously reported `fetch failed`; there, confirm with `is-active` + a manual `curl`.)
124
130
  - **The ledger auto-inserts via `PGDATABASE` too** (fixed in task [#1984](https://example.com/builders#/task/1984)). A consumer on `PGDATABASE` + peer auth now gets its `core_upgrades` row written automatically — the channel delegates the insert to `psql -d "$PGDATABASE"`, exactly like `migrate.sh`, so no manual `psql INSERT` is needed. (`DATABASE_URL` still works, via the `pg` driver.) Pre-1984 cores only auto-wrote with `DATABASE_URL`; on those, record the row manually.
125
131
  - **No deploy key on the droplet** → deploys/upgrades ride a `git bundle`, not `git pull` (idea 509).
@@ -0,0 +1,65 @@
1
+ -- core_241_core_upgrades_actor.sql — who asked for this upgrade (task 1003159,
2
+ -- ADR 0293 D5).
3
+ --
4
+ -- core_189 built the per-instance core-upgrade ledger with from_version, to_version,
5
+ -- source_commit and a free-text `note`. That was right for the world it was written
6
+ -- in: every upgrade came from a human at a shell, and the note carried "who ran it"
7
+ -- as prose when someone remembered.
8
+ --
9
+ -- Task 1003159 makes an upgrade something an HTTP request can START. That is the
10
+ -- most privileged act in the system — it restarts a service and changes what code
11
+ -- the platform runs — and the task's own non-negotiable is that **every invocation
12
+ -- lands in this ledger with an actor**. Prose in a nullable note is not an actor:
13
+ -- it cannot be joined, cannot be audited, and is absent exactly when someone was in
14
+ -- a hurry.
15
+ --
16
+ -- NULLABLE, DELIBERATELY. An upgrade run the old way — a human at a shell on the
17
+ -- box — has no builder to attribute and must keep working unchanged; this column is
18
+ -- not a gate on the CLI path. NULL therefore reads as "not requested through the
19
+ -- door", which is a true and useful statement rather than missing data. The route
20
+ -- is what makes it non-null in practice: it has an authenticated builder by
21
+ -- construction, and a request it cannot attribute is refused before it reaches here.
22
+ --
23
+ -- ON DELETE SET NULL matches every other actor reference in the schema: deleting a
24
+ -- person must not erase the record of what the platform did.
25
+ --
26
+ -- Additive, idempotent, no down-migration.
27
+
28
+ BEGIN;
29
+
30
+ ALTER TABLE core_upgrades ADD COLUMN IF NOT EXISTS requested_by_builder_id bigint
31
+ REFERENCES builders(id) ON DELETE SET NULL;
32
+
33
+ -- How the upgrade was started. 'shell' is every pre-existing row and every future
34
+ -- CLI run; 'owner-control' is the door this task builds. Text + CHECK rather than
35
+ -- an enum type, the fail-closed shape the rest of the schema uses: an unwired
36
+ -- source is refused at insert, and widening later is a one-line constraint change.
37
+ ALTER TABLE core_upgrades ADD COLUMN IF NOT EXISTS source text;
38
+
39
+ DO $$
40
+ BEGIN
41
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'core_upgrades_source_chk') THEN
42
+ ALTER TABLE core_upgrades ADD CONSTRAINT core_upgrades_source_chk
43
+ CHECK (source IS NULL OR source IN ('shell', 'owner-control'));
44
+ END IF;
45
+
46
+ -- An upgrade that came through the DOOR must name its actor. A shell run need
47
+ -- not, and pre-existing rows carry neither. NOT VALID so the constraint binds
48
+ -- every future write without scanning history that predates the concept — the
49
+ -- same trade agents_002_payer.sql made for the same reason.
50
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'core_upgrades_door_needs_actor_chk') THEN
51
+ ALTER TABLE core_upgrades ADD CONSTRAINT core_upgrades_door_needs_actor_chk
52
+ CHECK (source IS DISTINCT FROM 'owner-control' OR requested_by_builder_id IS NOT NULL) NOT VALID;
53
+ END IF;
54
+ END $$;
55
+
56
+ -- "What has this builder deployed?" — the audit read. Partial: a shell run has no
57
+ -- actor and would only dilute the scan.
58
+ CREATE INDEX IF NOT EXISTS core_upgrades_actor_idx
59
+ ON core_upgrades (requested_by_builder_id, applied_at DESC)
60
+ WHERE requested_by_builder_id IS NOT NULL;
61
+
62
+ INSERT INTO schema_migrations (version) VALUES ('core_241_core_upgrades_actor')
63
+ ON CONFLICT (version) DO NOTHING;
64
+
65
+ COMMIT;
@@ -0,0 +1,54 @@
1
+ -- core_242_core_version_snapshot.sql — what the box serves and what it could take
2
+ -- (task 1003159, ADR 0293 D3 + D6).
3
+ --
4
+ -- THE PROBLEM THIS SOLVES IS A LAYERING ONE, not a storage one. The deploy control has
5
+ -- to show the owner two facts before they press anything: the version the box is
6
+ -- ACTUALLY serving, and the versions it could be moved to. Neither is reachable from the
7
+ -- web tier:
8
+ --
9
+ -- • the served version is `curl 127.0.0.1:<port>/version` ON the box (ADR 0293 D6) —
10
+ -- the apex sits behind pre-launch basic_auth and its HTML rides a CDN, so a public
11
+ -- read can report a stale answer with total confidence;
12
+ -- • the available versions are `npm view`, which update-channel.js runs via spawnSync.
13
+ -- On a single-process web server that blocks every other request for the seconds npm
14
+ -- takes — and npm took 8–13 minutes to publish on 2026-09-16 (learnings 1000263,
15
+ -- 1000266), so this is not a theoretical stall.
16
+ --
17
+ -- Both ARE cheap for the control-plane runner, which is a CLI process on the box and
18
+ -- already wakes on a timer. So the runner writes what it sees; the web tier reads a row.
19
+ -- That is the same direction of travel as the intent queue itself (ADR 0293 D1): the
20
+ -- privileged side acts and reports, the web side asks and displays.
21
+ --
22
+ -- ONE ROW PER INSTANCE, REPLACED IN PLACE. This is a cache of an observation, not a
23
+ -- ledger — core_upgrades is the ledger and keeps every move forever. Keeping history
24
+ -- here would grow a row per instance per tick to answer a question only ever asked about
25
+ -- the present.
26
+ --
27
+ -- observed_at IS PART OF THE ANSWER, NOT METADATA. A cache that renders as though it
28
+ -- were live is how a control tells someone the box is on a version it left an hour ago.
29
+ -- The UI is expected to say how old this is, which it can only do if the age is stored.
30
+ --
31
+ -- Additive, idempotent, no down-migration.
32
+
33
+ BEGIN;
34
+
35
+ CREATE TABLE IF NOT EXISTS core_version_snapshot (
36
+ instance_id bigint PRIMARY KEY REFERENCES provisioning_instances(id) ON DELETE CASCADE,
37
+ -- What 127.0.0.1/version answered. NULL means the box did not answer — a real and
38
+ -- different state from "answered with an old version", and the control must not
39
+ -- collapse the two into a confident number.
40
+ served_version text,
41
+ -- Versions the registry reports, newest last, as update-channel.js parsed them.
42
+ -- jsonb rather than text[]: it is written whole and read whole, never queried inside.
43
+ available jsonb NOT NULL DEFAULT '[]'::jsonb,
44
+ -- What the channel policy would actually take from `available` right now. Stored
45
+ -- rather than recomputed in the browser so the control and the runner cannot disagree
46
+ -- about what pressing the button would do.
47
+ recommended text,
48
+ observed_at timestamptz NOT NULL DEFAULT now()
49
+ );
50
+
51
+ INSERT INTO schema_migrations (version) VALUES ('core_242_core_version_snapshot')
52
+ ON CONFLICT (version) DO NOTHING;
53
+
54
+ COMMIT;
@@ -688,6 +688,8 @@ function createClient(opts = {}) {
688
688
  postProjectsInvite: (args) => request("POST", "/projects/invite", { hasBody: true }, args),
689
689
  },
690
690
  "provisioning": {
691
+ // GET /provisioning/control-plane — rank: archon — GET /provisioning/control-plane
692
+ getProvisioningControlPlane: (args) => request("GET", "/provisioning/control-plane", { hasBody: false }, args),
691
693
  // GET /provisioning/fleet — rank: metic+archon — GET /provisioning/fleet
692
694
  getProvisioningFleet: (args) => request("GET", "/provisioning/fleet", { hasBody: false }, args),
693
695
  // GET /provisioning/fleet/cost-ledger — rank: metic+archon — GET /provisioning/fleet/cost-ledger
@@ -704,6 +706,10 @@ function createClient(opts = {}) {
704
706
  postProvisioningInstances: (args) => request("POST", "/provisioning/instances", { hasBody: true }, args),
705
707
  // GET /provisioning/instances/{id} — rank: any-builder — GET /provisioning/instances/:id
706
708
  getProvisioningInstancesId: (args) => request("GET", "/provisioning/instances/{id}", { hasBody: false }, args),
709
+ // GET /provisioning/instances/{id}/core-upgrade — rank: archon — GET /provisioning/instances/:id/core-upgrade
710
+ getProvisioningInstancesIdCoreUpgrade: (args) => request("GET", "/provisioning/instances/{id}/core-upgrade", { hasBody: false }, args),
711
+ // POST /provisioning/instances/{id}/core-upgrade — rank: archon — POST /provisioning/instances/:id/core-upgrade
712
+ postProvisioningInstancesIdCoreUpgrade: (args) => request("POST", "/provisioning/instances/{id}/core-upgrade", { hasBody: true }, args),
707
713
  // PATCH /provisioning/instances/{id}/detail — rank: any-builder — PATCH /provisioning/instances/:id/detail
708
714
  patchProvisioningInstancesIdDetail: (args) => request("PATCH", "/provisioning/instances/{id}/detail", { hasBody: true }, args),
709
715
  // POST /provisioning/instances/{id}/disconnect — rank: any-builder — POST /provisioning/instances/:id/disconnect
@@ -101,6 +101,14 @@ const PERMISSIONS = [
101
101
  // enforced out of band and can never be delegated by a row in this database.
102
102
  { key: 'builder.rank.set', system: true, floor: 'archon', guards: 'PATCH /builders/:id/rank — the single rank writer' },
103
103
  { key: 'version.close', system: true, floor: 'archon', guards: 'version close disposition (never delegated below Archon)' },
104
+ // core.pin.move (task 1003159, ADR 0293) — MOVE AN INSTANCE'S CORE PIN: the act
105
+ // that makes shipped work visible to users, and the most privileged thing an HTTP
106
+ // request can start here (it restarts a service and changes what code runs).
107
+ // system:true + archon, alongside version.close, because it is a HARD GATE the
108
+ // owner holds: the catalog's own invariant forbids granting a system key to metic,
109
+ // so this cannot be delegated downward by a custom rank. The web tier still gains
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)' },
104
112
  // BV1.R21 (task 1003608, goal 1000086). ADR 0250 §6 amends ADR 0157 for exactly
105
113
  // this key: `version.create` returns to the Archon floor.
106
114
  //