@bongos/core 1.19.949 → 1.19.951
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 +43 -28
- package/docs/module-api-changelog.md +4 -0
- package/modules/provisioning/capacity-gate.js +7 -1
- package/modules/provisioning/capacity.js +7 -1
- package/modules/provisioning/migrations/provisioning_021_byo_host_shape.sql +37 -0
- package/modules/provisioning/onboard-plan.js +11 -4
- package/modules/provisioning/paid-shape-gate.js +37 -8
- package/modules/provisioning/provisioning.js +6 -6
- package/modules/provisioning/routes/provisioning.js +15 -5
- package/modules/public-landing/public/projects.html +17 -2
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/init.js +20 -7
- package/scripts/gds/provision-byo-host.js +68 -0
- package/scripts/gds/provision.js +2 -0
- package/src/bongos/module-scope-map.js +20 -11
- package/src/module-api.js +1 -1
- package/tests/init.mjs +9 -7
- package/tests/projects_hub_pre_uat.mjs +5 -1
- package/tests/provision_byo_host.mjs +122 -0
- package/tests/provisioning_capacity.mjs +5 -0
- package/tests/provisioning_capacity_gate.mjs +27 -7
- package/tests/provisioning_module_selection.mjs +9 -9
- package/tests/provisioning_onboard_mode.mjs +5 -5
- package/tests/provisioning_paid_shape_gate.mjs +76 -6
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.
|
|
6
|
-
"core_contract": "1.19.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.19.951",
|
|
6
|
+
"core_contract": "1.19.951",
|
|
7
|
+
"source_commit": "52686c08170a959adbd2fff6681162b3b3e248ae",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-
|
|
9
|
+
"built_at": "2026-09-22T04:02:59.605Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 513,
|
|
13
13
|
"agent_docs_stubbed": 26,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 2350,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 2889,
|
|
20
|
+
"tree_sha256": "4c573e93b9c8147a0c7657ef1f9c43b7e3a0e37d6254e65502e205f91a77fdb0",
|
|
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": "
|
|
2690
|
+
"sha256": "707238093af0e4b56e36139173b6d46d7ffd33a86c348181a40dc74083821738"
|
|
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": "
|
|
6520
|
+
"sha256": "5d0d3eb34c68620b785803a40df416872e6a606cf6e144a9d2793f88bc231cb8"
|
|
6521
6521
|
},
|
|
6522
6522
|
{
|
|
6523
6523
|
"path": "modules/provisioning/capacity.js",
|
|
6524
6524
|
"mode": "0000644",
|
|
6525
|
-
"sha256": "
|
|
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": "
|
|
6665
|
+
"sha256": "c67f592415bb9c81e3eef658a7352c2bc47dfb4056b2cdef52f772e5021d28cf"
|
|
6661
6666
|
},
|
|
6662
6667
|
{
|
|
6663
6668
|
"path": "modules/provisioning/paid-shape-gate.js",
|
|
6664
6669
|
"mode": "0000644",
|
|
6665
|
-
"sha256": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
8050
|
+
"sha256": "14465332b60fa42b702654bd0dd552e8b332b3c17cc969bfe08c3cd8a720320a"
|
|
8046
8051
|
},
|
|
8047
8052
|
{
|
|
8048
8053
|
"path": "package.json",
|
|
8049
8054
|
"mode": "0000644",
|
|
8050
|
-
"sha256": "
|
|
8055
|
+
"sha256": "f7f40ce54ba7eddac00919f6407ee95888c15a87e3fc526450b64a00365cd913"
|
|
8051
8056
|
},
|
|
8052
8057
|
{
|
|
8053
8058
|
"path": "public-docs/index.html",
|
|
@@ -8767,7 +8772,7 @@
|
|
|
8767
8772
|
{
|
|
8768
8773
|
"path": "scripts/gds/init.js",
|
|
8769
8774
|
"mode": "0000644",
|
|
8770
|
-
"sha256": "
|
|
8775
|
+
"sha256": "d18b93dc0ac9136cfae79d98cd3506cb12d94fce30829a39dfb15f880770b93e"
|
|
8771
8776
|
},
|
|
8772
8777
|
{
|
|
8773
8778
|
"path": "scripts/gds/install-git-hooks.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": "
|
|
9040
|
+
"sha256": "1d92b6cbbfb544328a8bacd29cb492445508617298c251872d846bb7ab5a180a"
|
|
9031
9041
|
},
|
|
9032
9042
|
{
|
|
9033
9043
|
"path": "scripts/gds/publish-credential-check.js",
|
|
@@ -9852,7 +9862,7 @@
|
|
|
9852
9862
|
{
|
|
9853
9863
|
"path": "src/bongos/module-scope-map.js",
|
|
9854
9864
|
"mode": "0000644",
|
|
9855
|
-
"sha256": "
|
|
9865
|
+
"sha256": "8cb783238eaf296a3d057537b829ec14c7d3d65f380874af18861d6262cffb10"
|
|
9856
9866
|
},
|
|
9857
9867
|
{
|
|
9858
9868
|
"path": "src/bongos/module-submissions.js",
|
|
@@ -10047,7 +10057,7 @@
|
|
|
10047
10057
|
{
|
|
10048
10058
|
"path": "src/module-api.js",
|
|
10049
10059
|
"mode": "0000644",
|
|
10050
|
-
"sha256": "
|
|
10060
|
+
"sha256": "156e1f0be121fb3fa396c32374f1ab8d0540f1d6c42a8aee28d05b5429a14115"
|
|
10051
10061
|
},
|
|
10052
10062
|
{
|
|
10053
10063
|
"path": "src/module-loader/catalog.js",
|
|
@@ -12277,7 +12287,7 @@
|
|
|
12277
12287
|
{
|
|
12278
12288
|
"path": "tests/init.mjs",
|
|
12279
12289
|
"mode": "0000644",
|
|
12280
|
-
"sha256": "
|
|
12290
|
+
"sha256": "9feeee7dbc2f1ae59a0af7eadbb521b74f114ce83e3f45ffdef59c1f8fdf25ec"
|
|
12281
12291
|
},
|
|
12282
12292
|
{
|
|
12283
12293
|
"path": "tests/instance_config.mjs",
|
|
@@ -13062,7 +13072,7 @@
|
|
|
13062
13072
|
{
|
|
13063
13073
|
"path": "tests/projects_hub_pre_uat.mjs",
|
|
13064
13074
|
"mode": "0000644",
|
|
13065
|
-
"sha256": "
|
|
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": "
|
|
13155
|
+
"sha256": "7a48fb54dce89efa207d7dd254a8a5ef70c6b1958b6ed2be198a73e76bb74327"
|
|
13141
13156
|
},
|
|
13142
13157
|
{
|
|
13143
13158
|
"path": "tests/provisioning_capacity_gate.mjs",
|
|
13144
13159
|
"mode": "0000644",
|
|
13145
|
-
"sha256": "
|
|
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": "
|
|
13200
|
+
"sha256": "8eaa3b40b38cbc294836ac3c4905f6a0a7c4a04e9c68d443e503f479a735c839"
|
|
13186
13201
|
},
|
|
13187
13202
|
{
|
|
13188
13203
|
"path": "tests/provisioning_onboard_mode.mjs",
|
|
13189
13204
|
"mode": "0000644",
|
|
13190
|
-
"sha256": "
|
|
13205
|
+
"sha256": "96c7ea0d646151393767061f042082f006007390ff652c7c649fd9151ab2ed61"
|
|
13191
13206
|
},
|
|
13192
13207
|
{
|
|
13193
13208
|
"path": "tests/provisioning_paid_shape_gate.mjs",
|
|
13194
13209
|
"mode": "0000644",
|
|
13195
|
-
"sha256": "
|
|
13210
|
+
"sha256": "859a44a139cfc7a52bc534dac8dc52f1203d60d5bdaac34e64834b6838e128fe"
|
|
13196
13211
|
},
|
|
13197
13212
|
{
|
|
13198
13213
|
"path": "tests/provisioning_recommendations.mjs",
|
|
@@ -2372,5 +2372,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
2372
2372
|
landed since 1.19.947 with no explicit bump. run 35671807404. (task 1002620)
|
|
2373
2373
|
1.19.949 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2374
2374
|
landed since 1.19.948 with no explicit bump. run 35675336642. (task 1002620)
|
|
2375
|
+
1.19.950 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
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)
|
|
2375
2379
|
---------------------------------------------------------------------------
|
|
2376
2380
|
```
|
|
@@ -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
|
-
|
|
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
|
-
// '
|
|
38
|
-
// a
|
|
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, '
|
|
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
|
-
//
|
|
17
|
-
//
|
|
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:
|
|
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(['
|
|
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
|
-
|
|
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 '
|
|
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
|
-
? '
|
|
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:
|
|
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
|
-
//
|
|
28
|
-
|
|
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
|
-
|
|
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 `
|
|
16
|
-
// shape ALSO
|
|
17
|
-
//
|
|
18
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
3
|
+
"version": "1.19.951",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.951",
|
|
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.
|
|
3
|
+
"version": "1.19.951",
|
|
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",
|
package/scripts/gds/init.js
CHANGED
|
@@ -87,7 +87,13 @@ const KNOWN_MODULES = ['game', 'art-pipeline', 'discord', 'dev-box'];
|
|
|
87
87
|
// request (POST /provisioning/instances validates slug/shape, and returns 404 when
|
|
88
88
|
// the module isn't enabled on the target instance — init then falls back to filing
|
|
89
89
|
// the manual production-topology task). ADR 0111 §3.
|
|
90
|
-
|
|
90
|
+
// ADR 0323: 'byo-host' is the free front door and the default; 'co-tenant' is RETIRED
|
|
91
|
+
// and the server refuses it outright, so it is absent here DELIBERATELY. Leaving it in
|
|
92
|
+
// would let init write a spec the create route then 403s — a local list that accepts
|
|
93
|
+
// more than the server does is worse than no list, because the refusal arrives a step
|
|
94
|
+
// later with no useful message. Dropping it means `--shape co-tenant` fails here, naming
|
|
95
|
+
// the shapes that work.
|
|
96
|
+
const HOSTING_SHAPES = ['byo-host', 'dedicated', 'standalone'];
|
|
91
97
|
|
|
92
98
|
// The Cloud Bongos core npm package an instance pins as a dependency (ADR 0108).
|
|
93
99
|
// `bongos init --adopt` LAYERS this onto an existing repo's package.json; `bongos
|
|
@@ -205,11 +211,11 @@ function provisioningSlug(spec) {
|
|
|
205
211
|
// block). THE ADR 0111 §3 ARM: infra answers become a real provisioning REQUEST (a
|
|
206
212
|
// provisioning_intents row the control-plane runner drains) instead of only a manual
|
|
207
213
|
// runbook task. Pure — no I/O; the caller POSTs it. Shape defaults to the cheapest
|
|
208
|
-
//
|
|
214
|
+
// byo-host ($0 to us — the owner supplies the host) per the budget DNA (ADR 0323).
|
|
209
215
|
function provisioningRequest(spec) {
|
|
210
216
|
const p = spec && spec.provisioning;
|
|
211
217
|
if (!isObj(p)) return null;
|
|
212
|
-
const shape = HOSTING_SHAPES.includes(p.shape) ? p.shape : '
|
|
218
|
+
const shape = HOSTING_SHAPES.includes(p.shape) ? p.shape : 'byo-host';
|
|
213
219
|
const body = { slug: provisioningSlug(spec), hosting_shape: shape };
|
|
214
220
|
if (p.domain) body.domain = String(p.domain).trim();
|
|
215
221
|
if (p.targetRef) body.target_ref = String(p.targetRef).trim();
|
|
@@ -334,12 +340,12 @@ function normalizeSpec(raw = {}) {
|
|
|
334
340
|
spec.modules = Array.isArray(spec.modules) ? spec.modules.filter((m) => KNOWN_MODULES.includes(m)) : [];
|
|
335
341
|
if (!spec.version.status) spec.version.status = 'building';
|
|
336
342
|
// Provisioning (ADR 0111 §3) — a block ⇒ the owner opted in. Coerce the shape to a
|
|
337
|
-
// known value (default the
|
|
343
|
+
// known value (default byo-host, the free front door) and trim the free-text refs; drop the
|
|
338
344
|
// block entirely when absent so provisioningRequest() reads it as "not requested".
|
|
339
345
|
if (isObj(spec.provisioning)) {
|
|
340
346
|
const p = spec.provisioning;
|
|
341
347
|
spec.provisioning = {
|
|
342
|
-
shape: HOSTING_SHAPES.includes(p.shape) ? p.shape : '
|
|
348
|
+
shape: HOSTING_SHAPES.includes(p.shape) ? p.shape : 'byo-host',
|
|
343
349
|
...(p.slug ? { slug: String(p.slug).trim() } : {}),
|
|
344
350
|
...(p.domain ? { domain: String(p.domain).trim() } : {}),
|
|
345
351
|
...(p.targetRef ? { targetRef: String(p.targetRef).trim() } : {}),
|
|
@@ -446,13 +452,20 @@ async function interview({ adopt = false } = {}) {
|
|
|
446
452
|
let provisioning;
|
|
447
453
|
const wantProv = (await ask(rl, ' Provision production hosting now? (y/N)', 'N')).toLowerCase();
|
|
448
454
|
if (wantProv === 'y' || wantProv === 'yes') {
|
|
449
|
-
|
|
455
|
+
// Describes each shape by WHAT IT DOES and never by its owner-facing product
|
|
456
|
+
// name. The stored value → owner-facing label translation lives in exactly one
|
|
457
|
+
// place — SHAPE_LABEL in modules/public-landing/public/projects.html (ADR 0323,
|
|
458
|
+
// and this module's CLAUDE.md says so) — so glossing '"cloud host"' here would
|
|
459
|
+
// be a second copy of it, in a file no designer renaming the product would
|
|
460
|
+
// think to open. A CLI prompt naming the stored value it is about to write is
|
|
461
|
+
// the right register anyway: this asks for a column value, not for a purchase.
|
|
462
|
+
const shape = (await ask(rl, ' Hosting shape? (byo-host = you run it on your own host / standalone = its own repo + pinned @bongos/core on the platform\'s box / dedicated = the same on its own droplet, priced by tier)', 'byo-host')).toLowerCase();
|
|
450
463
|
const domain = await ask(rl, ' Domain you own — bring-your-own (blank = the platform assigns <slug>.<its apex> when configured)?');
|
|
451
464
|
const targetRef = await ask(rl, ' Target credential ref? (which control-plane creds the runner should use; optional)');
|
|
452
465
|
let tier = '';
|
|
453
466
|
if (shape === 'dedicated') tier = await ask(rl, ' Droplet tier? (e.g. s-1vcpu-2gb; optional)');
|
|
454
467
|
provisioning = {
|
|
455
|
-
shape: HOSTING_SHAPES.includes(shape) ? shape : '
|
|
468
|
+
shape: HOSTING_SHAPES.includes(shape) ? shape : 'byo-host',
|
|
456
469
|
domain: domain || undefined,
|
|
457
470
|
targetRef: targetRef || undefined,
|
|
458
471
|
tier: tier || undefined,
|