@bongos/core 1.19.950 → 1.19.952

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 CHANGED
@@ -2,22 +2,22 @@
2
2
  "artifact": "bongos-core",
3
3
  "manifest_schema": 1,
4
4
  "generator": "scripts/gds/package-core.js",
5
- "core_version": "1.19.950",
6
- "core_contract": "1.19.950",
7
- "source_commit": "b332b91c550367626b416cf9edd7e689d7e5f061",
5
+ "core_version": "1.19.952",
6
+ "core_contract": "1.19.952",
7
+ "source_commit": "76560f7026937d8fcb87e98f1cbb582781a7852a",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-09-22T03:31:42.605Z",
9
+ "built_at": "2026-09-22T04:20:21.435Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 513,
13
13
  "agent_docs_stubbed": 26,
14
- "functional_verbatim": 2347,
14
+ "functional_verbatim": 2350,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 2886,
20
- "tree_sha256": "0faab48c30a2ae5ab6fce64aec38fc54571056bfe7f3fa566f4336818100fd1c",
19
+ "file_count": 2889,
20
+ "tree_sha256": "b5e1b32c4cf3b07a161044a90c080241342270c91f71fcc551b02670ad0dd720",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/ask-for-help/SKILL.md",
@@ -2687,7 +2687,7 @@
2687
2687
  {
2688
2688
  "path": "docs/module-api-changelog.md",
2689
2689
  "mode": "0000644",
2690
- "sha256": "1b1d7162b5d86bf679ed7a8bbf6d0a354eb382f88ef896651211856ab7fda2dc"
2690
+ "sha256": "961a09f17879832bc7fa1e4607a41bddcc1a2d08e0c9ce55c1e5de6ba5af822e"
2691
2691
  },
2692
2692
  {
2693
2693
  "path": "docs/modules-contract.md",
@@ -6517,12 +6517,12 @@
6517
6517
  {
6518
6518
  "path": "modules/provisioning/capacity-gate.js",
6519
6519
  "mode": "0000644",
6520
- "sha256": "28dfe87a6aa6a6a7ef15dfda3a845c5d6cb442ff7489c851308eb474c1043836"
6520
+ "sha256": "5d0d3eb34c68620b785803a40df416872e6a606cf6e144a9d2793f88bc231cb8"
6521
6521
  },
6522
6522
  {
6523
6523
  "path": "modules/provisioning/capacity.js",
6524
6524
  "mode": "0000644",
6525
- "sha256": "1a2b3bd97a2e0c0068c6200a71dabb692e90723ee1ff63a56775f2b91e1cb208"
6525
+ "sha256": "56a2be9188fa0d8785174e6e91547c10266c3b6859fcad00aebd5739de4f366f"
6526
6526
  },
6527
6527
  {
6528
6528
  "path": "modules/provisioning/catalog-bridge.js",
@@ -6649,6 +6649,11 @@
6649
6649
  "mode": "0000644",
6650
6650
  "sha256": "cdc3e8992d1e4273a472b826f208736235c8a3b16a700de33a7705cdae99ef9e"
6651
6651
  },
6652
+ {
6653
+ "path": "modules/provisioning/migrations/provisioning_021_byo_host_shape.sql",
6654
+ "mode": "0000644",
6655
+ "sha256": "cde8a3a4c66c17d17c4e18866f11600a06073377de41d5ed70cd93185cd78d6d"
6656
+ },
6652
6657
  {
6653
6658
  "path": "modules/provisioning/module.json",
6654
6659
  "mode": "0000644",
@@ -6657,12 +6662,12 @@
6657
6662
  {
6658
6663
  "path": "modules/provisioning/onboard-plan.js",
6659
6664
  "mode": "0000644",
6660
- "sha256": "3c36bb1b163a35492ccd3d231e50039bded945fbccac076166c70a67767a687d"
6665
+ "sha256": "c67f592415bb9c81e3eef658a7352c2bc47dfb4056b2cdef52f772e5021d28cf"
6661
6666
  },
6662
6667
  {
6663
6668
  "path": "modules/provisioning/paid-shape-gate.js",
6664
6669
  "mode": "0000644",
6665
- "sha256": "7c08e627e228bec400e9e2dc204418a6995a4668eb6c1817ae59f20f77cbe71e"
6670
+ "sha256": "cceda07080d1175a3629ebb2105e36220a2c2728768f2c6eb691f0269479a002"
6666
6671
  },
6667
6672
  {
6668
6673
  "path": "modules/provisioning/pollers/catalog-backfill.js",
@@ -6682,7 +6687,7 @@
6682
6687
  {
6683
6688
  "path": "modules/provisioning/provisioning.js",
6684
6689
  "mode": "0000644",
6685
- "sha256": "cfd57a109b12ecc629422c16a4db85fe2daf1700dcae60df2885701e5eecd5cc"
6690
+ "sha256": "82bb265d63bb882468bbb609faa60a783ccf7dfeec5cd4595550586c37131bda"
6686
6691
  },
6687
6692
  {
6688
6693
  "path": "modules/provisioning/rate-limit.js",
@@ -6717,7 +6722,7 @@
6717
6722
  {
6718
6723
  "path": "modules/provisioning/routes/provisioning.js",
6719
6724
  "mode": "0000644",
6720
- "sha256": "63919aad35ff1f47c4981e5e5c1f1c08d79eb0de9588e0c3ba2c5ed488167211"
6725
+ "sha256": "a10d9ac50f6be584c33a6bb559ad9d0431c2ac7f4655c23fcdf47baedf540cd2"
6721
6726
  },
6722
6727
  {
6723
6728
  "path": "modules/provisioning/seams.js",
@@ -7242,7 +7247,7 @@
7242
7247
  {
7243
7248
  "path": "modules/public-landing/public/projects.html",
7244
7249
  "mode": "0000644",
7245
- "sha256": "8de1601446961430e081632ad410a22a21665749d9acbc0b6f7f9c7a6b9d92c9"
7250
+ "sha256": "dab97906f2db723c0ec00aaf1a7930b7e0051098e6102704f65a6d0c705543ff"
7246
7251
  },
7247
7252
  {
7248
7253
  "path": "modules/public-landing/public/projects.probes.json",
@@ -8042,12 +8047,12 @@
8042
8047
  {
8043
8048
  "path": "package-lock.json",
8044
8049
  "mode": "0000644",
8045
- "sha256": "ef28356c6251d661ff78b1446e9ddc3e13770144c862634da5a8d91ef7ba6e51"
8050
+ "sha256": "09603574afd51f32c16292e9b19bd8bd8c8c57927721d5187229f414aeae1144"
8046
8051
  },
8047
8052
  {
8048
8053
  "path": "package.json",
8049
8054
  "mode": "0000644",
8050
- "sha256": "fe67ef68895ddb7bc5c2a507a7f57fa4d3b683611e557cba82be40f3641013e3"
8055
+ "sha256": "7a932995f95a2d638f25c48b9b1312f29682ac863cf8b35b4ebe58e515cd42f1"
8051
8056
  },
8052
8057
  {
8053
8058
  "path": "public-docs/index.html",
@@ -8122,7 +8127,7 @@
8122
8127
  {
8123
8128
  "path": "scripts/gds/adopt-preflight.js",
8124
8129
  "mode": "0000644",
8125
- "sha256": "1e2c8a70aec49eaa0d95a9cbf37145fa1c851b46e2ec27f72f773d4198356ab9"
8130
+ "sha256": "9b16bbd8ceb60b801f042ad955b22b67c5703ce6dc8505bf333cefffbd3f37ae"
8126
8131
  },
8127
8132
  {
8128
8133
  "path": "scripts/gds/adr-namespace.js",
@@ -8502,7 +8507,7 @@
8502
8507
  {
8503
8508
  "path": "scripts/gds/device-flow-help.js",
8504
8509
  "mode": "0000644",
8505
- "sha256": "802b8875e2aa7e51b1b95800aae461c5d289d7684ef57e6da95bebc458aa0f5e"
8510
+ "sha256": "843c7ec13103f121b5ff0056ba8d547dd20da837b5a04ef37c74bf4069411c38"
8506
8511
  },
8507
8512
  {
8508
8513
  "path": "scripts/gds/diagnose-achievements.js",
@@ -8767,7 +8772,7 @@
8767
8772
  {
8768
8773
  "path": "scripts/gds/init.js",
8769
8774
  "mode": "0000644",
8770
- "sha256": "f24475957efcba217133ef05c297e5a9f11d78ae884a6e7286084723d9daf912"
8775
+ "sha256": "529a6af81068f10cd8eeb4738f4d2fd17aec41f40c4dd3195909828e4656fe28"
8771
8776
  },
8772
8777
  {
8773
8778
  "path": "scripts/gds/install-git-hooks.js",
@@ -8842,7 +8847,7 @@
8842
8847
  {
8843
8848
  "path": "scripts/gds/login.js",
8844
8849
  "mode": "0000644",
8845
- "sha256": "fcb70ddb8175d4cbbfd3217dcb9352e6280d5597c8caffe88a6399b54041e93b"
8850
+ "sha256": "a5502a11527ac903446e13536e78900c3af82b3382757a547e2561761ef7e396"
8846
8851
  },
8847
8852
  {
8848
8853
  "path": "scripts/gds/main-audit.js",
@@ -8974,6 +8979,11 @@
8974
8979
  "mode": "0000644",
8975
8980
  "sha256": "b0e2c14720e8258eba4d4ef7f69cc2bcbb26c3be312deb891383dce7a1dbf71e"
8976
8981
  },
8982
+ {
8983
+ "path": "scripts/gds/provision-byo-host.js",
8984
+ "mode": "0000644",
8985
+ "sha256": "92ef1fa760b90ece9781abe6d3c75abf84ff39cbb8b42194cca367bab7821269"
8986
+ },
8977
8987
  {
8978
8988
  "path": "scripts/gds/provision-capacity.js",
8979
8989
  "mode": "0000644",
@@ -9027,7 +9037,7 @@
9027
9037
  {
9028
9038
  "path": "scripts/gds/provision.js",
9029
9039
  "mode": "0000644",
9030
- "sha256": "e879686d5fcadfd32eb1dfc4e1640fc229cc4f0afc78615681964484e8457c1f"
9040
+ "sha256": "1d92b6cbbfb544328a8bacd29cb492445508617298c251872d846bb7ab5a180a"
9031
9041
  },
9032
9042
  {
9033
9043
  "path": "scripts/gds/publish-credential-check.js",
@@ -9352,7 +9362,7 @@
9352
9362
  {
9353
9363
  "path": "scripts/gds/setup.js",
9354
9364
  "mode": "0000644",
9355
- "sha256": "79027b353a20b02c363d691ba30eda7d3456b8a081199ddc2c2559162523d66c"
9365
+ "sha256": "edbebc4a0f4557cc8fc9dd0eb7115379a2fd0e34247725180cfb8eccb1cb0a18"
9356
9366
  },
9357
9367
  {
9358
9368
  "path": "scripts/gds/ship-check.js",
@@ -10047,7 +10057,7 @@
10047
10057
  {
10048
10058
  "path": "src/module-api.js",
10049
10059
  "mode": "0000644",
10050
- "sha256": "107e4e25c1f869c19259c01752fecc19c138790c60981256651663823345c049"
10060
+ "sha256": "e780332ee4be93e25030115f0a32c91e121aa0f9080e8f2abb0df1c0f3064eb0"
10051
10061
  },
10052
10062
  {
10053
10063
  "path": "src/module-loader/catalog.js",
@@ -10167,7 +10177,7 @@
10167
10177
  {
10168
10178
  "path": "tests/adopt_preflight.mjs",
10169
10179
  "mode": "0000644",
10170
- "sha256": "2489a19e7c36f53c4046a4a6fab54a55dbdfe8da6daa3452316f9e0abea01742"
10180
+ "sha256": "c31b72a08ca2007497e6c44108f0de35aaea6d3cd1fd1a54e65162a8bf555012"
10171
10181
  },
10172
10182
  {
10173
10183
  "path": "tests/adr_renumber_integrity.mjs",
@@ -10602,7 +10612,7 @@
10602
10612
  {
10603
10613
  "path": "tests/bongos_login.mjs",
10604
10614
  "mode": "0000644",
10605
- "sha256": "149d3b80bb3fce785ffb33bda72ebb30a66c31b374de5977f2283bc713c98441"
10615
+ "sha256": "1e95957f3c780b4054128000e0deefc76e4df41c3076da76d2fbd187b47c95d2"
10606
10616
  },
10607
10617
  {
10608
10618
  "path": "tests/box_access.mjs",
@@ -11137,7 +11147,7 @@
11137
11147
  {
11138
11148
  "path": "tests/discord_approvals.mjs",
11139
11149
  "mode": "0000644",
11140
- "sha256": "748ec9bb00437eea0cb6692a8ca042f3d19c2ff7704cb2f2f79e195d459f1559"
11150
+ "sha256": "6f5e924b108a8738f3c7d4a97057049167a7a88a6f4af997321cd8585df3038b"
11141
11151
  },
11142
11152
  {
11143
11153
  "path": "tests/discord_autojoin.mjs",
@@ -12277,7 +12287,7 @@
12277
12287
  {
12278
12288
  "path": "tests/init.mjs",
12279
12289
  "mode": "0000644",
12280
- "sha256": "26629f6a3882b35f6844b8f0f9af9221e53a0906bb89401e8e23b3e5aee8d1f0"
12290
+ "sha256": "9feeee7dbc2f1ae59a0af7eadbb521b74f114ce83e3f45ffdef59c1f8fdf25ec"
12281
12291
  },
12282
12292
  {
12283
12293
  "path": "tests/instance_config.mjs",
@@ -12617,7 +12627,7 @@
12617
12627
  {
12618
12628
  "path": "tests/module_api.mjs",
12619
12629
  "mode": "0000644",
12620
- "sha256": "53aa3732865dfce17a3216390b057dae825a6a7a8056309f3f31654c06636bee"
12630
+ "sha256": "868942895e19730936594efb9f0fb69e4e511397ed738c05e0b27073c01846f7"
12621
12631
  },
12622
12632
  {
12623
12633
  "path": "tests/module_api_lazy.mjs",
@@ -13062,7 +13072,7 @@
13062
13072
  {
13063
13073
  "path": "tests/projects_hub_pre_uat.mjs",
13064
13074
  "mode": "0000644",
13065
- "sha256": "5095bb2c890d89337c819fc3fc07fe26507f0bd78607b2b49239bccf23419aa3"
13075
+ "sha256": "19e1b68463ddccb1a1155e1adfb3d4c130711745030183fb1d27650f1bfe5444"
13066
13076
  },
13067
13077
  {
13068
13078
  "path": "tests/promote_deps_gate.mjs",
@@ -13104,6 +13114,11 @@
13104
13114
  "mode": "0000644",
13105
13115
  "sha256": "9f62eea1b43a478334a83df52d0e81a3b3b67f4c4eea54680d2d8db3b25ee513"
13106
13116
  },
13117
+ {
13118
+ "path": "tests/provision_byo_host.mjs",
13119
+ "mode": "0000644",
13120
+ "sha256": "79c33863862966ef0d7a0f4d9dc2290596a4f98da2796083b9319c24a6bfc26e"
13121
+ },
13107
13122
  {
13108
13123
  "path": "tests/provision_caddy_wiring.mjs",
13109
13124
  "mode": "0000644",
@@ -13137,12 +13152,12 @@
13137
13152
  {
13138
13153
  "path": "tests/provisioning_capacity.mjs",
13139
13154
  "mode": "0000644",
13140
- "sha256": "bcb976ab17508d1af34ea0c775d25765f336f534d79de9442b2fbc6712fa2a28"
13155
+ "sha256": "7a48fb54dce89efa207d7dd254a8a5ef70c6b1958b6ed2be198a73e76bb74327"
13141
13156
  },
13142
13157
  {
13143
13158
  "path": "tests/provisioning_capacity_gate.mjs",
13144
13159
  "mode": "0000644",
13145
- "sha256": "d95439f89702862e45cf1c0b238aa5a463e81b584d7dca70d91863547bee6642"
13160
+ "sha256": "363405d0f78714e9e3c29ee6ce64a1735a96fdaa95c631930743019daf8e26ff"
13146
13161
  },
13147
13162
  {
13148
13163
  "path": "tests/provisioning_credential_preflight.mjs",
@@ -13182,17 +13197,17 @@
13182
13197
  {
13183
13198
  "path": "tests/provisioning_module_selection.mjs",
13184
13199
  "mode": "0000644",
13185
- "sha256": "540e9b5b158a5623525924403bcef444f87fe0364591cd9c6b9e0a55f4f6295c"
13200
+ "sha256": "8eaa3b40b38cbc294836ac3c4905f6a0a7c4a04e9c68d443e503f479a735c839"
13186
13201
  },
13187
13202
  {
13188
13203
  "path": "tests/provisioning_onboard_mode.mjs",
13189
13204
  "mode": "0000644",
13190
- "sha256": "f7a01aa6ed190c1ad544d91312909b081446a7fc2b6ef339f3ba8fa4d59f0306"
13205
+ "sha256": "96c7ea0d646151393767061f042082f006007390ff652c7c649fd9151ab2ed61"
13191
13206
  },
13192
13207
  {
13193
13208
  "path": "tests/provisioning_paid_shape_gate.mjs",
13194
13209
  "mode": "0000644",
13195
- "sha256": "19961880545fe8f6c9eaf79095aa0d08f7862409109d581cac828f0f20b1be69"
13210
+ "sha256": "859a44a139cfc7a52bc534dac8dc52f1203d60d5bdaac34e64834b6838e128fe"
13196
13211
  },
13197
13212
  {
13198
13213
  "path": "tests/provisioning_recommendations.mjs",
@@ -2374,5 +2374,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
2374
2374
  landed since 1.19.948 with no explicit bump. run 35675336642. (task 1002620)
2375
2375
  1.19.950 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2376
2376
  landed since 1.19.949 with no explicit bump. run 35683452297. (task 1002620)
2377
+ 1.19.951 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2378
+ landed since 1.19.950 with no explicit bump. run 35685346231. (task 1002620)
2379
+ 1.19.952 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2380
+ landed since 1.19.951 with no explicit bump. run 35686469735. (task 1002620)
2377
2381
  ---------------------------------------------------------------------------
2378
2382
  ```
@@ -26,7 +26,13 @@ function requireRoomOnBox(db, log) {
26
26
  return async function capacityGate(req, res, next) {
27
27
  const body = req.body || {};
28
28
  // The handler's own default and validation: a malformed request gets its 400 there.
29
- const shape = body.hosting_shape || 'co-tenant';
29
+ // The absent-shape default MUST match createInstance's coercion and the paid-shape
30
+ // gate's, or this gate prices a request against a shape the row will not take. It
31
+ // was 'co-tenant' until ADR 0323; that shape lands on the control plane, so after the
32
+ // default moved to 'byo-host' — which runs on the OWNER's machine and takes no slot
33
+ // here — a shapeless create on a full box would have been refused box_full for
34
+ // capacity it never consumes. Three places default this; they agree deliberately.
35
+ const shape = body.hosting_shape || 'byo-host';
30
36
  const slug = String(body.slug || '').trim().toLowerCase();
31
37
  if (!provisioning.HOSTING_SHAPES.includes(shape) || !provisioning.isValidSlug(slug)) return next();
32
38
  const target = capacity.placementBox(shape, { remoteHost: process.env.PROVISION_REMOTE_HOST || null });
@@ -68,8 +68,14 @@ function parseMeminfo(text) {
68
68
  // local (task 2074); a co-tenant runs on the remote co-hosting box only when the
69
69
  // runner is in remote-exec mode (ADR 0130), else here. A dedicated droplet is its
70
70
  // own box and has no shared ceiling → null (no preflight).
71
+ //
72
+ // A 'byo-host' (ADR 0323) also answers null, for a stronger reason than dedicated's:
73
+ // the owner supplies the machine, so the platform has no box to place it on and no
74
+ // memory of its own to spend. It can never be refused for capacity, and it must never
75
+ // consume a slot that a cloud-host would otherwise get — which is what returning null
76
+ // here buys, since PLACED_STATES counting keys off the shapes a box declares.
71
77
  function placementBox(hostingShape, { remoteHost = null } = {}) {
72
- if (hostingShape === 'dedicated') return null;
78
+ if (hostingShape === 'dedicated' || hostingShape === 'byo-host') return null;
73
79
  if (hostingShape === 'co-tenant' && remoteHost) {
74
80
  return { box: 'overflow', shapes: ['co-tenant'], fixedMb: FIXED_MB.overflow };
75
81
  }
@@ -0,0 +1,37 @@
1
+ -- provisioning_021 — admit the 'byo-host' hosting shape (ADR 0323, task 1004141).
2
+ --
3
+ -- "Bring your own host": the OWNER runs the instance on their own machine, VPS or
4
+ -- platform account. The platform stands up nothing and reaches into nothing, so the
5
+ -- row is a record of an instance we do not operate — liveness is the ADR 0252 pull
6
+ -- against an owner-supplied origin.
7
+ --
8
+ -- A CHECK-ENUM WIDENING, and nothing else. `DROP CONSTRAINT IF EXISTS` paired with a
9
+ -- re-`ADD CONSTRAINT` of the SAME name in the SAME migration only ever PERMITS more
10
+ -- values: no existing row changes and no data is lost, which is why fitness.js
11
+ -- (isConstraintWidening) treats this exact shape as additive rather than destructive.
12
+ -- The precedent is provisioning_002, which admitted 'standalone' the same way.
13
+ --
14
+ -- 'co-tenant' STAYS IN THE SET even though ADR 0323 retires it. Retirement closes the
15
+ -- DOOR, not the record: one torn-down row still carries that value, and dropping it
16
+ -- from the constraint would make a historical row violate its own table. The door is
17
+ -- closed in code instead — paid-shape-gate.js lists it UNREQUESTABLE, so no new row can
18
+ -- ever take it, while the old one stays readable. A shape that is storable but never
19
+ -- grantable again is exactly what that set is for.
20
+ --
21
+ -- The two managed shapes keep their OLD stored spellings ('standalone', 'dedicated')
22
+ -- on purpose. ADR 0323 renames them owner-facing to "cloud host" and "dedicated cloud
23
+ -- host", but about half the code reading those values is landlord machinery that task
24
+ -- 1004142 deletes when it moves them to a managed platform, and renaming a call site a
25
+ -- task before deleting it is work done twice. That task carries the data rename with
26
+ -- those files already open.
27
+
28
+ ALTER TABLE provisioning_instances DROP CONSTRAINT IF EXISTS provisioning_instances_hosting_shape_check;
29
+ ALTER TABLE provisioning_instances ADD CONSTRAINT provisioning_instances_hosting_shape_check
30
+ CHECK (hosting_shape IN ('byo-host', 'co-tenant', 'dedicated', 'standalone', 'control-plane'));
31
+
32
+ -- The COLUMN default was 'co-tenant' (provisioning_001), which is now a retired shape.
33
+ -- Every insert today goes through createInstance, which always passes an explicit shape,
34
+ -- so nothing currently relies on this default — which is exactly why it is worth moving
35
+ -- now rather than leaving a loaded gun for the first insert that ever omits the column.
36
+ -- Changing a DEFAULT rewrites no existing row.
37
+ ALTER TABLE provisioning_instances ALTER COLUMN hosting_shape SET DEFAULT 'byo-host';
@@ -34,11 +34,18 @@
34
34
  // `shapes` (task 1002696; direction §7 — a surface renders only steps somebody real
35
35
  // will perform on THIS instance's shape). The control-plane shapes (standalone /
36
36
  // co-tenant / dedicated) have the platform's runner performing every 'auto' leg;
37
- // 'self-host' has no runner, so the runner-performed legs are absent from its plan —
38
- // a self-host form must never promise them (QA walk finding, task 1002717). Steps
39
- // with no `shapes` apply everywhere. Filter with stepsForShape().
37
+ // 'byo-host' has no runner, so the runner-performed legs are absent from its plan —
38
+ // a bring-your-own-host form must never promise them (QA walk finding, task 1002717).
39
+ // Steps with no `shapes` apply everywhere. Filter with stepsForShape().
40
+ //
41
+ // 'byo-host' REPLACED the plan-only spelling 'self-host' here (ADR 0323, task 1004141).
42
+ // That value was this file's own invention and had no callers anywhere else, so it was
43
+ // a second vocabulary for a concept the hosting shapes now name directly — and two words
44
+ // for one thing is how a filter starts silently matching nothing. The shape is absent
45
+ // from CONTROL_PLANE_SHAPES, which is what drops the runner-performed legs from its plan
46
+ // BY CONSTRUCTION rather than by a list someone has to remember to update.
40
47
  const CONTROL_PLANE_SHAPES = ['standalone', 'co-tenant', 'dedicated'];
41
- const ALL_SHAPES = [...CONTROL_PLANE_SHAPES, 'self-host'];
48
+ const ALL_SHAPES = [...CONTROL_PLANE_SHAPES, 'byo-host'];
42
49
 
43
50
  function onboardPlan(mode = 'greenfield', opts = {}) {
44
51
  const adopt = mode === 'adopt';
@@ -13,8 +13,15 @@
13
13
  // the shapes it accepts is actually that front door. The others each hand the caller
14
14
  // something the platform pays for or trusts:
15
15
  //
16
- // co-tenant — the front door. Co-tenants on infrastructure the platform already
17
- // runs; `costEstimateUsd` is 0. No authority required, ever.
16
+ // byo-host — the front door since ADR 0323. The OWNER supplies the host, so the
17
+ // platform pays nothing and runs none of the caller's code. No
18
+ // authority required, ever. This is the shape that replaced 'co-tenant'
19
+ // as the free option, and it is a STRONGER front door than co-tenant
20
+ // was: co-tenant was free of charge but still ran on our box, whereas
21
+ // this one never touches it.
22
+ // co-tenant — RETIRED (ADR 0323). Was the free front door; ran on the platform's
23
+ // own box, which is why the name could not be carried over to
24
+ // bring-your-own-host. Now UNREQUESTABLE — see below.
18
25
  // dedicated — allocates a real droplet (`scripts/gds/provision.js`), billed
19
26
  // monthly, defaulting to s-2vcpu-4gb when no tier is named.
20
27
  // standalone — runs the caller's OWN repo on the platform's box. The unit's
@@ -61,11 +68,11 @@
61
68
  const provisioning = require('./provisioning.js');
62
69
 
63
70
  // The shapes any authenticated builder may request with no permission at all. This is
64
- // the whole allow-list, and it is deliberately one entry long: co-tenant is the front
71
+ // the whole allow-list, and it is deliberately one entry long: byo-host is the front
65
72
  // door, and the front door is the only thing that should be free. `tests/
66
73
  // provisioning_paid_shape_gate.mjs` pins HOSTING_SHAPES against this set so a new shape
67
74
  // cannot be added without someone deciding, in writing, which side of the line it is on.
68
- const OPEN_SHAPES = Object.freeze(new Set(['co-tenant']));
75
+ const OPEN_SHAPES = Object.freeze(new Set(['byo-host']));
69
76
 
70
77
  // Shapes that are not requestable through this route AT ALL — no permission admits them,
71
78
  // because there is no rank at which "ask the API for a second control plane" is a
@@ -75,7 +82,13 @@ const OPEN_SHAPES = Object.freeze(new Set(['co-tenant']));
75
82
  // `WHERE hosting_shape = 'control-plane' LIMIT 1`, so a second row makes "which machine
76
83
  // is the control plane" a question with a caller-influenced answer. Refused by name, at
77
84
  // the door, for the same reason control-plane-guard.js refuses by name.
78
- const UNREQUESTABLE_SHAPES = Object.freeze(new Set(['control-plane']));
85
+ // 'co-tenant' joins it for a different reason (ADR 0323): it is RETIRED, not secret.
86
+ // It remains a valid stored value because a torn-down row still carries it, so it cannot
87
+ // simply leave HOSTING_SHAPES without making that row's history unreadable — and a shape
88
+ // that is storable but must never be granted again is exactly what 'unrequestable' means.
89
+ // Refusing it here rather than deleting the value is what makes the retirement complete
90
+ // without rewriting the past.
91
+ const UNREQUESTABLE_SHAPES = Object.freeze(new Set(['control-plane', 'co-tenant']));
79
92
 
80
93
  // shapeAuthority — what does this wire value demand? PURE. Returns one of:
81
94
  // 'open' → admit with no permission
@@ -88,7 +101,7 @@ const UNREQUESTABLE_SHAPES = Object.freeze(new Set(['control-plane']));
88
101
  //
89
102
  // 'defer' is safe despite looking like a hole: the validator runs immediately after this
90
103
  // middleware and refuses anything outside HOSTING_SHAPES, and `createInstance` coerces an
91
- // unrecognised shape to 'co-tenant' rather than storing it. There is no path by which a
104
+ // unrecognised shape to 'byo-host' rather than storing it. There is no path by which a
92
105
  // deferred value becomes a privileged row.
93
106
  //
94
107
  // Matches the exact wire value — a case or whitespace variant of a privileged shape is
@@ -96,7 +109,7 @@ const UNREQUESTABLE_SHAPES = Object.freeze(new Set(['control-plane']));
96
109
  // admitted as a cheap one.
97
110
  function shapeAuthority(rawShape) {
98
111
  const shape = rawShape === undefined || rawShape === null || rawShape === ''
99
- ? 'co-tenant' // an absent shape is the documented default, and it is the open one
112
+ ? 'byo-host' // an absent shape is the documented default, and it is the open one
100
113
  : rawShape;
101
114
  if (typeof shape !== 'string') return 'defer';
102
115
  if (!provisioning.HOSTING_SHAPES.includes(shape)) return 'defer';
@@ -115,9 +128,25 @@ function requireAuthorityForShape(requirePermission) {
115
128
  const verdict = shapeAuthority(req.body && req.body.hosting_shape);
116
129
  if (verdict === 'open' || verdict === 'defer') return next();
117
130
  if (verdict === 'unrequestable') {
131
+ // Two shapes land here for different reasons, so the message names which.
132
+ //
133
+ // It names STORED VALUES and never an owner-facing label, deliberately. The
134
+ // stored value → owner-facing name translation lives in exactly one place —
135
+ // SHAPE_LABEL in modules/public-landing/public/projects.html (ADR 0323, and this
136
+ // module's CLAUDE.md says so) — and an API error string is the worst possible
137
+ // second copy of it: the next person to rename the product renames the label map
138
+ // and a wizard heading, and this sentence keeps selling a name that no longer
139
+ // exists. The right register for a 403 is the wire vocabulary the caller sent,
140
+ // which is also what `details.requestable` already hands them; a UI that wants to
141
+ // say it in product words has SHAPE_LABEL for exactly that. The guard against it
142
+ // creeping back is an assertion, not this comment — see
143
+ // ../../tests/provisioning_paid_shape_gate.mjs.
144
+ const retired = req.body.hosting_shape === 'co-tenant';
118
145
  return res.fail('shape_not_requestable', {
119
146
  status: 403,
120
- message: 'The control plane enrolls its own row; it cannot be requested through this route.',
147
+ message: retired
148
+ ? "That hosting shape has been retired and can no longer be requested. Ask for one of the shapes in `requestable` instead: `byo-host` runs on a host you supply, and the rest are stood up for you."
149
+ : 'The control plane enrolls its own row; it cannot be requested through this route.',
121
150
  details: { hosting_shape: req.body.hosting_shape, requestable: [...provisioning.HOSTING_SHAPES].filter((s) => !UNREQUESTABLE_SHAPES.has(s)) },
122
151
  });
123
152
  }
@@ -24,11 +24,8 @@ const { normalizeModuleSelection, effectiveModules } = require('./starter-bundle
24
24
  // Constants (mirror the CHECK constraints in provisioning_001_tables.sql)
25
25
  // ---------------------------------------------------------------------------
26
26
  const INSTANCE_STATES = ['requested', 'provisioning', 'active', 'tearing_down', 'torn_down', 'error'];
27
- // co-tenant — a process + DB on THIS control-plane checkout (shared box, $0)
28
- // dedicated — its own DigitalOcean droplet (priced by tier)
29
- // standalone — its OWN repo checkout + pinned @cloudbongos/core npm dep, co-located on
30
- // the box (ADR 0108 destination model; owner-hosted, $0). See provision.js.
31
- const HOSTING_SHAPES = ['co-tenant', 'dedicated', 'standalone', 'control-plane'];
27
+ // Shapes, owner-facing names + why the spellings differ: ADR 0323 + this module's CLAUDE.md.
28
+ const HOSTING_SHAPES = ['byo-host', 'co-tenant', 'dedicated', 'standalone', 'control-plane'];
32
29
  // How the instance's repo was onboarded (migration provisioning_004, ADR 0121):
33
30
  // greenfield — a fresh EMPTY repo; the runner scaffolds it and pushes the first commit
34
31
  // adopt — a repo the owner ALREADY has; the runner must LAYER onto it, preserving
@@ -1061,7 +1058,10 @@ async function listAllInstances(db, { status } = {}) {
1061
1058
  // decide (same owner → idempotent re-request; different owner → 409). Never
1062
1059
  // silently reassigns ownership.
1063
1060
  async function createInstance(db, { ownerBuilderId, slug, targetRef, hostingShape, domain, dbName, port, tier, onboardMode, type, description, detail, modules }) {
1064
- const shape = HOSTING_SHAPES.includes(hostingShape) ? hostingShape : 'co-tenant';
1061
+ // Unknown/absent coerces to the shape that costs nothing and trusts nothing. Was
1062
+ // 'co-tenant' (then the free front door); that shape is now retired + unrequestable,
1063
+ // so coercing to it would mint a row no caller is allowed to ask for.
1064
+ const shape = HOSTING_SHAPES.includes(hostingShape) ? hostingShape : 'byo-host';
1065
1065
  // Unknown/absent → 'greenfield', matching the column default. Falling back to the
1066
1066
  // SAFE mode matters: greenfield refuses to push over existing history (no --force
1067
1067
  // anywhere), whereas defaulting an unknown value to 'adopt' would put the layering
@@ -12,10 +12,12 @@
12
12
  //
13
13
  // own-scoped (requireBuilder — the caller's OWN instances):
14
14
  // POST /provisioning/instances — request a new instance
15
- // (only `co-tenant` is free; every other
16
- // shape ALSO needs provisioning.fleet.manage,
17
- // and `control-plane` is not requestable at
18
- // all; ../paid-shape-gate.js.
15
+ // (only `byo-host` is free — ADR 0323 made it
16
+ // the front door; every other shape ALSO
17
+ // needs provisioning.fleet.manage, and
18
+ // `control-plane` and the RETIRED `co-tenant`
19
+ // are not requestable at all;
20
+ // ../paid-shape-gate.js.
19
21
  // An `adopt` ALSO passes the credential
20
22
  // preflight — ../credential-preflight.js,
21
23
  // task 1002909; greenfield is exempt; a full box → 409, ../capacity-gate.js)
@@ -331,7 +333,15 @@ module.exports = function provisioningRoutes() {
331
333
  if (!provisioning.isValidSlug(slug)) {
332
334
  return res.fail('bad_slug', 400, { reason: 'lowercase, 2–40 chars of [a-z0-9-], no leading/trailing/double dash' });
333
335
  }
334
- const shape = body.hosting_shape || 'co-tenant';
336
+ // FOURTH and last place that resolves an absent shape, and they must all agree:
337
+ // this route, paid-shape-gate.js's authority lookup, capacity-gate.js's placement
338
+ // lookup, and createInstance's coercion. Until ADR 0323 they agreed on 'co-tenant'.
339
+ // When that shape was retired this line was the hole: the gate admits an absent
340
+ // shape as 'byo-host' (open), and then a stale default here would hand createInstance
341
+ // a literal 'co-tenant' — which IS in HOSTING_SHAPES, so the coercion never fires and
342
+ // the validator never complains, and a shapeless create quietly stores the one shape
343
+ // no caller is allowed to request. Nothing would have errored.
344
+ const shape = body.hosting_shape || 'byo-host';
335
345
  if (!provisioning.HOSTING_SHAPES.includes(shape)) {
336
346
  return res.fail('bad_hosting_shape', 400, { valid: provisioning.HOSTING_SHAPES });
337
347
  }
@@ -3934,7 +3934,9 @@ summary{min-height:24px;padding:3px 0;}
3934
3934
  ['Address', resolvedDomain()
3935
3935
  ? '<code>' + esc(resolvedDomain()) + '</code>'
3936
3936
  : '<em style="font-weight:600;color:var(--warn)">none — sign-in stays locked until it has one</em>'],
3937
- ['Shape', 'Its own server, running the platform’s current release']
3937
+ /* The wizard creates hosting_shape 'standalone', whose owner-facing name is
3938
+ "cloud host" (ADR 0323) — we stand it up and run it for them. */
3939
+ ['Shape', 'Cloud host — we stand it up and run it for you, on the platform’s current release']
3938
3940
  ];
3939
3941
  $('reviewList').innerHTML = rows.map(function (r) {
3940
3942
  return '<li><span class="k">' + r[0] + '</span><span class="v">' + r[1] + '</span></li>';
@@ -5205,7 +5207,20 @@ summary{min-height:24px;padding:3px 0;}
5205
5207
 
5206
5208
  /* ── rendering ───────────────────────────────────────────────────────── */
5207
5209
 
5208
- var SHAPE_LABEL = { standalone: 'its own server', 'co-tenant': 'shared server', dedicated: 'its own server' };
5210
+ /* The owner-facing name of each hosting shape (ADR 0323). This map IS the translation
5211
+ between the stored value and the word the owner reads: 'standalone' and 'dedicated'
5212
+ keep their old spellings in the database on purpose (task 1004142 renames the data),
5213
+ so this is the ONLY place that difference should ever be visible. Read as a noun
5214
+ phrase — it lands in a meta list and in the "server" ledger row.
5215
+ 'co-tenant' is retired and unrequestable, but one torn-down row still carries it,
5216
+ so it keeps a label rather than falling through to the raw value. */
5217
+ var SHAPE_LABEL = {
5218
+ 'byo-host': 'your own host',
5219
+ standalone: 'cloud host',
5220
+ dedicated: 'dedicated cloud host',
5221
+ 'co-tenant': 'shared server (retired)',
5222
+ 'control-plane': 'the control plane'
5223
+ };
5209
5224
  /* what a teardown ACTUALLY does, per hosting shape (F17 — the DECIDED policy:
5210
5225
  retention is deliberate on shared boxes; a dedicated machine takes its
5211
5226
  database with it). Copy follows the runner, never the other way around. */
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.950",
3
+ "version": "1.19.952",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.950",
9
+ "version": "1.19.952",
10
10
  "license": "AGPL-3.0-or-later",
11
11
  "dependencies": {
12
12
  "express": "^4.21.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.950",
3
+ "version": "1.19.952",
4
4
  "description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "main": "src/platform-server.js",
@@ -12,7 +12,7 @@
12
12
  // • CI collisions — the repo's workflows vs core's expected unit/secret-scan/
13
13
  // gate-review checks.
14
14
  // • IGNORED incoming files — a pre-existing .gitignore rule that would swallow what
15
- // adopt is about to commit (task 1003400). This is the check that was missing when
15
+ // adopt is about to commit (task 1003881). This is the check that was missing when
16
16
  // Cloud Bongos was adopted onto a repo whose .gitignore already carried `.claude/`:
17
17
  // the skills, hooks and settings.json were materialized onto disk and then dropped
18
18
  // by `git add`, so the adopt commit landed 8 files with no methodology surface at
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
  // scripts/gds/device-flow-help.js — the one explanation of a Device-Flow refusal, shared
4
- // by `bongos setup` and `bongos login` (task 1003400).
4
+ // by `bongos setup` and `bongos login` (task 1003881).
5
5
  //
6
6
  // WHY IT EXISTS AT ALL. GitHub creates an App from a manifest with Device Flow OFF and
7
7
  // exposes no API to switch it on — only a checkbox in the App's settings. So on every