@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.
@@ -0,0 +1,68 @@
1
+ 'use strict';
2
+
3
+ // scripts/gds/provision-byo-host.js — the runner's answer for a 'byo-host' instance:
4
+ // stand up NOTHING (ADR 0323, task 1004141).
5
+ //
6
+ // "Bring your own host" means the OWNER runs the instance — their machine, their VPS,
7
+ // their own platform account. The platform's only job is to hold the record, so the
8
+ // runner's job here is defined ENTIRELY BY WHAT IT MUST NOT DO: no port allocation, no
9
+ // database or role, no `web.env` under /etc, no systemd unit, no Caddy snippet, no DNS
10
+ // record, no cloud API call, no shell on anybody's box.
11
+ //
12
+ // WHY IT IS A GUARD AND NOT A CASE IN THE SWITCH. `provisionInstance` runs four
13
+ // preflights before it mutates anything — box capacity, ingress, unix accounts, Caddy
14
+ // wiring — and every one of them asks a question about a machine the platform owns. On
15
+ // a byo-host row the honest answer to each is not "pass" or "fail" but "not ours to
16
+ // ask", so the shape has to return ABOVE them, not be handled after them. That is why
17
+ // the caller's line sits immediately under the already-active idempotency check.
18
+ //
19
+ // WHAT IT PREVENTED. Before this existed the shape fell through to the shared
20
+ // co-tenant/standalone standup. `standalone` is false for it, so boxExec was the
21
+ // CONTROL PLANE's own shell: the runner would have allocated a port, run createdb,
22
+ // written env under /etc, installed a unit and a Caddy snippet and reloaded the proxy —
23
+ // standing up, on our box, the one shape whose entire promise is that we never do.
24
+ // Nothing would have errored. A silent success is the failure mode, which is why the
25
+ // proof (`tests/provision_byo_host.mjs`) asserts ABSENCE — no exec, no write, no port,
26
+ // no database — instead of asserting that the right steps ran.
27
+ //
28
+ // WHY THE ROW STILL GOES 'active'. `active` records that the platform has nothing left
29
+ // to do, not that the platform is running it. Liveness for this shape is the ADR 0252
30
+ // pull against the owner-supplied origin: the ~5-minute liveness sweep probes it by
31
+ // domain and only ever writes the overlay columns, never `status`, so the card earns its
32
+ // verdict from an observation rather than from our runner's say-so.
33
+ //
34
+ // Its own file because scripts/gds/provision.js sits at the 1500-line oversized-file
35
+ // ratchet — the same reason provision-net.js, provision-units.js and
36
+ // provision-core-upgrade.js were carved out.
37
+
38
+ // The stored value. A literal, not a regex or a prefix match: a near-miss must reach the
39
+ // standup's own validation rather than be quietly treated as the shape we skip.
40
+ const BYO_HOST_SHAPE = 'byo-host';
41
+
42
+ function isByoHost(hostingShape) {
43
+ return hostingShape === BYO_HOST_SHAPE;
44
+ }
45
+
46
+ // Record a byo-host instance as provisioned without touching any box. Reaches the
47
+ // database ONLY through the injected `provisioning` port (the status flip + the audit
48
+ // event) — it takes no executor and no writeFile, so there is nothing here that could
49
+ // mutate a machine even by mistake.
50
+ async function recordByoHost(inst, { db, provisioning, log, apply }) {
51
+ log(' [byo-host] the owner runs this one — no port, no database, no unit, no Caddy snippet, no DNS');
52
+ if (!apply) {
53
+ log(' [byo-host] would mark it active without touching any box');
54
+ return { ok: true, byoHost: true, stoodUp: false };
55
+ }
56
+ // error_note: null — a stale note from an earlier failed run must not ride into the
57
+ // active state (the same clearing the co-tenant/standalone tail does).
58
+ await provisioning.setInstanceStatus(db, inst.id, 'active', { error_note: null, provisioned_at: new Date().toISOString() });
59
+ await provisioning.recordEvent(db, {
60
+ instanceId: inst.id, ownerBuilderId: inst.owner_builder_id, event: 'active',
61
+ detail: 'byo-host — the owner supplies the host; the platform stood up nothing',
62
+ actor: 'runner',
63
+ });
64
+ log(` ✓ ${inst.slug} recorded (byo-host) — nothing was stood up`);
65
+ return { ok: true, byoHost: true, stoodUp: false };
66
+ }
67
+
68
+ module.exports = { BYO_HOST_SHAPE, isByoHost, recordByoHost };
@@ -59,6 +59,7 @@ const { installedCoreVersion, runnerBanner } = require('./runner-drift.js');
59
59
  const { teardownInstance } = require('./provision-teardown.js');
60
60
  const { disconnectInstance } = require('./provision-disconnect.js');
61
61
  const { coreUpgradeInstance, preflightCoreUpgrade, snapshotAllCoreVersions } = require('./provision-core-upgrade.js'); // the owner's deploy door, ADR 0293
62
+ const { isByoHost, recordByoHost } = require('./provision-byo-host.js'); // stand up NOTHING for a byo-host row, ADR 0323
62
63
 
63
64
  // WHAT supervises an instance's service (ADR 0306). systemd on every box we run — the
64
65
  // seam exists so a plane without systemd is not a dead end. Injectable for tests.
@@ -250,6 +251,7 @@ async function provisionInstance(inst, deps) {
250
251
  // Idempotency: an already-active instance is a no-op success (re-drained intent).
251
252
  if (inst.status === 'active') { log(' already active — nothing to do'); return { ok: true, already: true }; }
252
253
 
254
+ if (isByoHost(inst.hosting_shape)) return recordByoHost(inst, deps); // stand up NOTHING, ABOVE every preflight — reasoning in provision-byo-host.js's header (ADR 0323)
253
255
  // ── preflight: DNS token scope (task 1980) ─────────────────────────────────
254
256
  // BEFORE committing to the address (allocating a port / creating a droplet / the
255
257
  // step-5 DNS UPSERT), verify the control-plane Cloudflare token can actually reach
@@ -261,6 +261,13 @@ const MODULE_GLOBS = {
261
261
  // the front door is this module's first customer, not its owner. ---
262
262
  'ui-design': [
263
263
  'modules/ui-design/',
264
+ // The `impeccable` design method's working tree — mocks/, review/, surfaces/.
265
+ // DESIGN.md:226 names .impeccable/mocks/world-assets/ as the SOURCE of cutouts
266
+ // that shipped to modules/public-landing/public/assets/world/, so this is
267
+ // ui-design's own output (task 1004146). Deliberately here and not in a
268
+ // protected module: ui-design is unprotected, so a ui builder keeps reach over
269
+ // their own working directory.
270
+ '.impeccable/',
264
271
  'scripts/gds/design-sync.js', 'scripts/gds/figma-design-sync.js', 'scripts/gds/validate-design.js',
265
272
  'scripts/gds/ui-kit-transform.js', // the kit's stub reaches the real serving transforms through this command (task 1003320)
266
273
  '.claude/skills/design/', '.claude/skills/design-sync/', '.claude/skills/figma-design-sync/',
@@ -377,6 +384,19 @@ const MODULE_GLOBS = {
377
384
  '.claude/settings.json', '.claude/hooks/',
378
385
  '.husky/',
379
386
  '.npmrc', '.gitleaks.toml',
387
+ // The SELF-HOST IMAGE (V4.R63, task 1202, ADR 0062): `docker compose up` brings
388
+ // up Postgres + a vanilla, neutral-branded instance with every feature module
389
+ // off. Assigned here by task 1004146 — same class as the .github/ and .husky/
390
+ // entries above and the .devcontainer/ one below: repo-root build config that
391
+ // defines WHAT RUNS. Not
392
+ // `provisioning`, which owns the control plane that stands up instances, not an
393
+ // image a stranger runs. They are not dev-box artifacts either, despite most
394
+ // inbound references coming from .devcontainer/ and the devcontainer workflow.
395
+ 'Dockerfile', 'docker-compose.yml', 'docker-entrypoint.sh', '.dockerignore',
396
+ // Repo-level dotfiles, the class .gitattributes and .npmrc already sit in.
397
+ // .env.local.example is the local-secrets template, next to the .gitleaks.toml
398
+ // scanner config above (task 1004146).
399
+ '.gitignore', '.env.local.example',
380
400
  // .devcontainer/ is the same class as the entries above it — repo-level
381
401
  // developer-environment config, not a feature's territory (task 1004029).
382
402
  // It belonged to NO module and was not in SHARED_GLOBS, so isCovered() said
@@ -557,17 +577,6 @@ const UNOWNED_ROOTS = Object.freeze({
557
577
  // Generated packaging manifests — written by package-core.js, never hand-edited.
558
578
  '.bongos-core.json': 'generated package manifest (scripts/gds/package-core.js)',
559
579
  '.cloudbongos-core.json': 'generated package manifest (scripts/gds/package-core.js)',
560
- // Container build + repo-level dotfiles. review: devsecops already owns the
561
- // neighbouring .npmrc / .gitattributes / package.json, so these plausibly belong
562
- // to it — but that is a scope-wall change and wants its own decision.
563
- '.dockerignore': 'review: container build, plausibly devsecops',
564
- 'Dockerfile': 'review: container build, plausibly devsecops',
565
- 'docker-compose.yml': 'review: container build, plausibly devsecops',
566
- 'docker-entrypoint.sh': 'review: container build, plausibly devsecops',
567
- '.gitignore': 'review: repo-level dotfile, plausibly devsecops',
568
- '.env.local.example': 'review: repo-level dotfile, plausibly devsecops',
569
- // Design tooling checked into the tree. review: plausibly ui-design.
570
- '.impeccable/': 'review: design tooling, plausibly ui-design',
571
580
  // Governance + legal. Deliberately not methodology: methodology owns docs/, and
572
581
  // these are the project's constitutional documents rather than its handbook.
573
582
  'LICENSE': 'project legal text',
package/src/module-api.js CHANGED
@@ -71,7 +71,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
71
71
  // there. scripts/gds/bump-version.js still rewrites the literal below; it appends
72
72
  // the entry to that file. Look for a version's history there, not here.
73
73
  // ---------------------------------------------------------------------------
74
- const CORE_VERSION = '1.19.949'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
74
+ const CORE_VERSION = '1.19.951'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
75
75
 
76
76
  // A namespaced logger so a module's log lines are attributable + consistent.
77
77
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
package/tests/init.mjs CHANGED
@@ -239,14 +239,16 @@ test('provisioningSlug: derives from productName, clamps to 40, strips trailing
239
239
  test('provisioningRequest: null unless opted in; builds the POST body when opted in', () => {
240
240
  assert.equal(init.provisioningRequest(fullSpec()), null); // no block → not requested
241
241
  const co = init.provisioningRequest(provSpec({})); // empty block = opted in, defaults
242
- assert.deepEqual(co, { slug: 'cloud-bongos', hosting_shape: 'co-tenant' });
242
+ assert.deepEqual(co, { slug: 'cloud-bongos', hosting_shape: 'byo-host' });
243
243
  const full = init.provisioningRequest(provSpec({ shape: 'dedicated', domain: 'example.com', targetRef: 'do:main', tier: 's-1vcpu-2gb' }));
244
244
  assert.deepEqual(full, { slug: 'cloud-bongos', hosting_shape: 'dedicated', domain: 'example.com', target_ref: 'do:main', tier: 's-1vcpu-2gb' });
245
245
  });
246
246
 
247
- test('provisioningRequest: unknown shape falls back to the cheapest co-tenant', () => {
248
- // normalizeSpec coerces a bad shape → co-tenant (the budget default, ADR 0111 §6).
249
- assert.equal(init.provisioningRequest(provSpec({ shape: 'kubernetes' })).hosting_shape, 'co-tenant');
247
+ test('provisioningRequest: unknown shape falls back to byo-host, the free front door', () => {
248
+ // normalizeSpec coerces a bad shape → byo-host (ADR 0323; was co-tenant, which that
249
+ // decision retired — the server now refuses it, so falling back to it would write a
250
+ // spec the create route rejects a step later).
251
+ assert.equal(init.provisioningRequest(provSpec({ shape: 'kubernetes' })).hosting_shape, 'byo-host');
250
252
  });
251
253
 
252
254
  test('kickoffTasks: provisioning requested → production-topology is AUTOMATED (dropped), manual steps stay', () => {
@@ -264,7 +266,7 @@ test('kickoffTasks: provisioning requested → production-topology is AUTOMATED
264
266
  test('normalizeSpec: coerces the provisioning block; drops it when absent', () => {
265
267
  assert.equal(init.normalizeSpec({}).provisioning, undefined);
266
268
  const n = init.normalizeSpec({ provisioning: { shape: 'DEDICATED', domain: ' Example.com ', junk: 1 } });
267
- assert.equal(n.provisioning.shape, 'co-tenant'); // unknown shape (case) → default
269
+ assert.equal(n.provisioning.shape, 'byo-host'); // unknown shape (case) → default
268
270
  assert.equal(n.provisioning.domain, 'Example.com'); // trimmed
269
271
  assert.equal(n.provisioning.junk, undefined); // stray field dropped
270
272
  });
@@ -651,14 +653,14 @@ async function runSeedTests() {
651
653
 
652
654
  await at('seedViaApi: provisioning 404 (module off) → falls back to the manual production-topology task', async () => {
653
655
  const api = fakeApi([['/provisioning/instances', { ok: false, status: 404, data: { error: 'not_found' } }]]);
654
- const out = await init.seedViaApi(provSpec({ shape: 'co-tenant' }), { log: () => {}, api });
656
+ const out = await init.seedViaApi(provSpec({ shape: 'byo-host' }), { log: () => {}, api });
655
657
  assert.equal(out.provisioned, false);
656
658
  assert.ok(findTaskPost(api, /Production topology/), 'filed the manual fallback task');
657
659
  });
658
660
 
659
661
  await at('seedViaApi: provisioning error → also files the fallback task', async () => {
660
662
  const api = fakeApi([['/provisioning/instances', { ok: false, status: 500, data: {} }]]);
661
- const out = await init.seedViaApi(provSpec({ shape: 'co-tenant' }), { log: () => {}, api });
663
+ const out = await init.seedViaApi(provSpec({ shape: 'byo-host' }), { log: () => {}, api });
662
664
  assert.equal(out.provisioned, false);
663
665
  assert.ok(findTaskPost(api, /Production topology/), 'filed the manual fallback task on error');
664
666
  });
@@ -237,7 +237,11 @@ test('no wall-clock promises, no operator vocabulary on the stranger’s path',
237
237
  assert.doesNotMatch(HUB, /about five minutes/, 'the empty state');
238
238
  assert.doesNotMatch(HUB, /a runner does the real work/, 'the mine fine print');
239
239
  assert.doesNotMatch(SCRIPT, /pinned platform core/, 'the review Shape row');
240
- assert.match(SCRIPT, /\['Shape', 'Its own server, running the platform’s current release'\]/);
240
+ /* The wizard creates hosting_shape 'standalone', whose owner-facing name became
241
+ "cloud host" at ADR 0323 — "its own server" was ambiguous between that shape and
242
+ a dedicated one, and said nothing about WHO runs it. The assertion still guards the
243
+ same property this test is about: owner vocabulary, and no wall-clock promise. */
244
+ assert.match(SCRIPT, /\['Shape', 'Cloud host — we stand it up and run it for you, on the platform’s current release'\]/);
241
245
  assert.doesNotMatch(HUB, /\/new-project<\/code> assistant/, 'the done panel’s closing line');
242
246
  assert.match(HUB, /Sign-in switches on after the GitHub steps|switch sign-in on/, 'the address step does not promise sign-in "just works"');
243
247
  assert.doesNotMatch(HUB, /Sign-in, sharing, everything just works/);
@@ -0,0 +1,122 @@
1
+ // tests/provision_byo_host.mjs — the runner stands up NOTHING for a 'byo-host'
2
+ // instance (ADR 0323, task 1004141).
3
+ //
4
+ // PROVES ABSENCE, not that the right steps ran, and that is the whole point. The
5
+ // shape's promise is negative — the owner supplies the host, so the platform must
6
+ // allocate no port, create no database, write no file, install no unit, touch no
7
+ // proxy and publish no DNS record. Asserting "the byo-host path ran" would pass
8
+ // just as happily if the co-tenant standup ran underneath it, which is exactly the
9
+ // bug this file exists to catch: before the branch, 'byo-host' fell through to the
10
+ // shared standup on the CONTROL PLANE's own shell and nothing errored.
11
+ //
12
+ // Hermetic: every executor is a spy that RECORDS instead of acting, so a single
13
+ // stray command shows up as a failed assertion rather than as a mutated box.
14
+ import assert from 'node:assert/strict';
15
+ import { test } from 'node:test';
16
+ import { createRequire } from 'node:module';
17
+
18
+ const require = createRequire(import.meta.url);
19
+ const P = require('../scripts/gds/provision.js');
20
+
21
+ const byoHost = {
22
+ id: 42, slug: 'mercury', hosting_shape: 'byo-host', status: 'requested',
23
+ owner_builder_id: 7, port: null, db_name: null, domain: 'mercury.example.com',
24
+ };
25
+
26
+ // Spies for every way provisionInstance can reach a machine or a record. `exec`
27
+ // and `writeFile` THROW rather than return, so a leaked step fails loudly with the
28
+ // command in the message instead of being silently collected and forgotten.
29
+ function harness({ apply = true } = {}) {
30
+ const queries = [], statuses = [], events = [], logs = [];
31
+ const boom = (what) => (arg) => { throw new Error(`byo-host must not ${what}: ${String(arg).slice(0, 200)}`); };
32
+ const deps = {
33
+ apply, log: (m) => logs.push(String(m)), sleep: async () => {},
34
+ db: { query: async (sql, params) => { queries.push({ sql, params }); return { rows: [] }; } },
35
+ provisioning: {
36
+ setInstanceStatus: async (_db, id, status, patch = {}) => { statuses.push({ id, status, patch }); },
37
+ recordEvent: async (_db, e) => { events.push(e); },
38
+ recordLivenessProbe: async () => { throw new Error('byo-host must not record a loopback liveness probe'); },
39
+ recordDnsNote: async () => { throw new Error('byo-host must not write a DNS note'); },
40
+ clearReleasedDomains: async () => { throw new Error('byo-host must not release domains'); },
41
+ allocatePort: async () => { throw new Error('byo-host must not allocate a port'); },
42
+ },
43
+ exec: boom('run a command'),
44
+ controlExec: boom('run a control-plane command'),
45
+ writeFile: boom('write a file'),
46
+ controlWriteFile: boom('write a control-plane file'),
47
+ doApi: () => { throw new Error('byo-host must not call a cloud API'); },
48
+ checkDnsTokenScope: async () => { throw new Error('byo-host must not preflight a DNS zone'); },
49
+ checkCaddyWiring: () => { throw new Error('byo-host must not inspect Caddy'); },
50
+ ingressPlaneGap: () => { throw new Error('byo-host must not probe this plane for ingress'); },
51
+ accountPlaneGap: () => { throw new Error('byo-host must not probe this plane for unix accounts'); },
52
+ getGithubToken: async () => { throw new Error('byo-host must not need a repo credential'); },
53
+ };
54
+ return { deps, queries, statuses, events, logs };
55
+ }
56
+
57
+ test('a byo-host provision touches no box: no exec, no file, no port, no database, no DNS', async () => {
58
+ const h = harness();
59
+ const r = await P.provisionInstance(byoHost, h.deps);
60
+ assert.equal(r.ok, true);
61
+ assert.equal(r.stoodUp, false, 'the result says plainly that nothing was stood up');
62
+ assert.equal(r.byoHost, true);
63
+ // Absence: the only DB work is the status flip + the event, both through the
64
+ // provisioning port. No raw query — a port allocation, a createdb or a
65
+ // capacity count would each have to go through db.query to happen at all.
66
+ assert.deepEqual(h.queries, [], 'no direct SQL — capacity counting, port allocation and the DB steps all query');
67
+ assert.equal(r.port, undefined, 'no port is reported because none is allocated');
68
+ });
69
+
70
+ test('the row reaches active with provisioned_at set, and nothing else is patched', async () => {
71
+ const h = harness();
72
+ await P.provisionInstance(byoHost, h.deps);
73
+ assert.equal(h.statuses.length, 1);
74
+ const [{ id, status, patch }] = h.statuses;
75
+ assert.equal(id, 42);
76
+ assert.equal(status, 'active', "'active' means the platform has nothing left to do, not that it runs it");
77
+ assert.equal(patch.error_note, null, 'a stale note from an earlier failed run must not ride into active');
78
+ assert.ok(patch.provisioned_at, 'the record carries when the platform finished with it');
79
+ assert.equal(patch.port, undefined, 'no port is stored');
80
+ assert.equal(patch.db_name, undefined, 'no database is named');
81
+ assert.equal(h.events.length, 1);
82
+ assert.equal(h.events[0].event, 'active');
83
+ assert.match(h.events[0].detail, /byo-host/, 'the audit trail says which shape, so the absence is readable later');
84
+ assert.equal(h.events[0].actor, 'runner');
85
+ });
86
+
87
+ test('a dry run writes nothing at all — not even the status', async () => {
88
+ const h = harness({ apply: false });
89
+ const r = await P.provisionInstance(byoHost, h.deps);
90
+ assert.equal(r.ok, true);
91
+ assert.equal(r.stoodUp, false);
92
+ assert.deepEqual(h.statuses, []);
93
+ assert.deepEqual(h.events, []);
94
+ assert.ok(h.logs.some((l) => /would mark it active/.test(l)), 'a dry run still says what it would do');
95
+ });
96
+
97
+ test('the branch is keyed on the shape, not on a missing port — a domainless row behaves the same', async () => {
98
+ const h = harness();
99
+ const r = await P.provisionInstance({ ...byoHost, domain: null }, h.deps);
100
+ assert.equal(r.ok, true);
101
+ assert.equal(r.stoodUp, false);
102
+ assert.equal(h.statuses[0].status, 'active');
103
+ });
104
+
105
+ test('an already-active byo-host row stays a plain idempotent no-op', async () => {
106
+ const h = harness();
107
+ const r = await P.provisionInstance({ ...byoHost, status: 'active' }, h.deps);
108
+ assert.deepEqual(r, { ok: true, already: true });
109
+ assert.deepEqual(h.statuses, [], 're-draining an intent must not re-stamp provisioned_at');
110
+ });
111
+
112
+ test('the shape is absent from the runner shapes that DO get stood up', () => {
113
+ // The guard is above every preflight, so the proof that it cannot be reordered
114
+ // by accident is that the co-tenant/standalone standup is unreachable for it.
115
+ // Any other shape still reaches the first executor — that is the control.
116
+ const h = harness();
117
+ return assert.rejects(
118
+ P.provisionInstance({ ...byoHost, hosting_shape: 'standalone', domain: null }, h.deps),
119
+ /byo-host must not/,
120
+ 'a non-byo-host shape still reaches the box steps — the branch is shape-scoped, not a global off switch',
121
+ );
122
+ });
@@ -84,6 +84,11 @@ t('placementBox: standalone → control plane always; co-tenant → the remote b
84
84
  assert.deepEqual(placementBox('co-tenant', {}), { box: 'control-plane', shapes: ['standalone', 'co-tenant'], fixedMb: 750 });
85
85
  assert.deepEqual(placementBox('co-tenant', { remoteHost: 'cohost' }), { box: 'overflow', shapes: ['co-tenant'], fixedMb: 630 });
86
86
  assert.equal(placementBox('dedicated', {}), null);
87
+ // ADR 0323: a byo-host runs on the owner's own machine, so it has no box of ours and
88
+ // no shared ceiling — null for a STRONGER reason than dedicated's (dedicated is our
89
+ // droplet, just not a shared one). remoteHost must not change that answer.
90
+ assert.equal(placementBox('byo-host', {}), null);
91
+ assert.equal(placementBox('byo-host', { remoteHost: 'cohost' }), null);
87
92
  });
88
93
 
89
94
  t('placementVerdict: the ceiling refuses at N of N, the floor refuses under 256 MB free, an unmeasured floor cannot refuse', () => {
@@ -122,11 +122,21 @@ await ta('a box with one slot left admits the create', async () => {
122
122
  assert.equal(lastCreateArgs && lastCreateArgs.slug, 'lantern');
123
123
  });
124
124
 
125
- await ta('an absent shape (the co-tenant default) on a local box is counted too', async () => {
125
+ await ta('an absent shape takes the byo-host default, so a full box does NOT refuse it', async () => {
126
+ // This assertion INVERTED at task 1004141 (ADR 0323), and the inversion is the point.
127
+ // The default used to be 'co-tenant', which lands on the control plane, so a shapeless
128
+ // create was counted against the ceiling and could be refused box_full. The default is
129
+ // now 'byo-host', which runs on the owner's own machine and consumes nothing here — so
130
+ // refusing it for OUR capacity would be refusing a project that costs us no memory.
131
+ //
132
+ // The real trap this pins: THREE places resolve an absent shape — createInstance's
133
+ // coercion, paid-shape-gate's authority lookup, and this gate's placement lookup. If
134
+ // they disagree, the gate prices a request against a shape the row will not take, and
135
+ // nothing errors; you just get a 409 on a create that should have succeeded.
126
136
  reset({ count: 15 });
127
137
  const res = await post({ slug: 'lantern', no_address: true });
128
- assert.equal(res.status, 409, res.raw);
129
- assert.equal(res.body.error.code, 'box_full');
138
+ assert.equal(res.status, 201, res.raw);
139
+ assert.equal(capacityReads, 0, 'a shape with no box of ours must not even read the count');
130
140
  });
131
141
 
132
142
  // ── boxes the web tier cannot count are the runner's ──────────────────────────────
@@ -138,15 +148,25 @@ await ta('dedicated is its own box: never refused here, and the count is never r
138
148
  assert.equal(capacityReads, 0);
139
149
  });
140
150
 
141
- await ta('a co-tenant under remote-exec lands on a box this host cannot measure: admitted', async () => {
151
+ await ta('byo-host runs on the OWNER\'s machine: never refused here, and the count is never read', async () => {
152
+ // ADR 0323's new shape, and the strongest form of "not our box": there is no machine
153
+ // of ours to place it on at all, so a full control plane must not refuse it and the
154
+ // count must not even be read. A full box turning away the one shape that costs us
155
+ // nothing would be the worst possible refusal.
142
156
  reset({ count: 15 });
143
- process.env.PROVISION_REMOTE_HOST = 'overflow.example';
144
- const res = await post({ slug: 'lantern', hosting_shape: 'co-tenant', no_address: true });
157
+ const res = await post({ slug: 'lantern', hosting_shape: 'byo-host', no_address: true });
145
158
  assert.equal(res.status, 201, res.raw);
146
159
  assert.equal(capacityReads, 0);
147
- delete process.env.PROVISION_REMOTE_HOST;
148
160
  });
149
161
 
162
+ // The co-tenant-under-remote-exec case this file used to assert HERE is now unreachable
163
+ // through the route: ADR 0323 retires that shape and paid-shape-gate.js refuses it
164
+ // before the capacity gate runs, so a route-level assertion would only ever be proving
165
+ // the 403. The placement rule itself is still live — the runner reads it for the rows
166
+ // that already carry the shape — and stays pinned at unit level in
167
+ // tests/provisioning_capacity.mjs ('placementBox: … co-tenant → the remote box only
168
+ // under remote-exec'). Moved rather than deleted, per the live-code rule.
169
+
150
170
  await ta('the live-memory floor is left to the runner: slots free + memory tight still admits', async () => {
151
171
  reset({ count: 3, mem: MEMINFO_TIGHT });
152
172
  const res = await post(WIZARD);
@@ -235,7 +235,7 @@ await ta('POST /instances: the picker\'s ids ride the create request through to
235
235
 
236
236
  // Act — the body the wizard files
237
237
  const res = await post('/provisioning/instances', {
238
- slug: 'mercury', hosting_shape: 'co-tenant', type: 'game', modules: ['agents', 'discord'],
238
+ slug: 'mercury', hosting_shape: 'byo-host', type: 'game', modules: ['agents', 'discord'],
239
239
  });
240
240
 
241
241
  // Assert
@@ -245,7 +245,7 @@ await ta('POST /instances: the picker\'s ids ride the create request through to
245
245
 
246
246
  await ta('POST /instances: an omitted modules key is still a valid create — skipping is supported, not an error', async () => {
247
247
  lastCreateArgs = null; existingRow = null;
248
- const res = await post('/provisioning/instances', { slug: 'mercury', hosting_shape: 'co-tenant', type: 'game' });
248
+ const res = await post('/provisioning/instances', { slug: 'mercury', hosting_shape: 'byo-host', type: 'game' });
249
249
  assert.equal(res.status, 201, `expected 201, got ${res.status} ${res.raw}`);
250
250
  assert.equal(lastCreateArgs.modules, undefined, 'nothing is invented on the owner\'s behalf');
251
251
  // …and the receipt already carries the set they will actually get.
@@ -255,7 +255,7 @@ await ta('POST /instances: an omitted modules key is still a valid create — sk
255
255
 
256
256
  await ta('POST /instances: an explicit empty selection is accepted as an answer', async () => {
257
257
  lastCreateArgs = null; existingRow = null;
258
- const res = await post('/provisioning/instances', { slug: 'mercury', hosting_shape: 'co-tenant', type: 'game', modules: [] });
258
+ const res = await post('/provisioning/instances', { slug: 'mercury', hosting_shape: 'byo-host', type: 'game', modules: [] });
259
259
  assert.equal(res.status, 201, `expected 201, got ${res.status} ${res.raw}`);
260
260
  assert.deepEqual(lastCreateArgs.modules, []);
261
261
  });
@@ -266,7 +266,7 @@ await ta('POST /instances: an unknown module is REFUSED and named, never silentl
266
266
  // prevent, and the reason badProjectDetail was written beside this.
267
267
  existingRow = null;
268
268
  const res = await post('/provisioning/instances', {
269
- slug: 'mercury', hosting_shape: 'co-tenant', modules: ['discrod'],
269
+ slug: 'mercury', hosting_shape: 'byo-host', modules: ['discrod'],
270
270
  });
271
271
  assert.equal(res.status, 400, res.raw);
272
272
  assert.equal(res.body.error.code, 'bad_modules');
@@ -277,7 +277,7 @@ await ta('POST /instances: an unknown module is REFUSED and named, never silentl
277
277
  await ta('POST /instances: naming an always-on module is refused with the reason, not the generic miss', async () => {
278
278
  existingRow = null;
279
279
  const res = await post('/provisioning/instances', {
280
- slug: 'mercury', hosting_shape: 'co-tenant', modules: ['memory'],
280
+ slug: 'mercury', hosting_shape: 'byo-host', modules: ['memory'],
281
281
  });
282
282
  assert.equal(res.status, 400, res.raw);
283
283
  assert.equal(res.body.error.code, 'bad_modules');
@@ -286,11 +286,11 @@ await ta('POST /instances: naming an always-on module is refused with the reason
286
286
 
287
287
  await ta('POST /instances: modules must be an array of strings', async () => {
288
288
  existingRow = null;
289
- const notArray = await post('/provisioning/instances', { slug: 'mercury', hosting_shape: 'co-tenant', modules: 'discord' });
289
+ const notArray = await post('/provisioning/instances', { slug: 'mercury', hosting_shape: 'byo-host', modules: 'discord' });
290
290
  assert.equal(notArray.status, 400, notArray.raw);
291
291
  assert.equal(notArray.body.error.code, 'validation_failed');
292
292
 
293
- const notStrings = await post('/provisioning/instances', { slug: 'mercury', hosting_shape: 'co-tenant', modules: [7] });
293
+ const notStrings = await post('/provisioning/instances', { slug: 'mercury', hosting_shape: 'byo-host', modules: [7] });
294
294
  assert.equal(notStrings.status, 400, notStrings.raw);
295
295
  assert.equal(notStrings.body.error.code, 'validation_failed');
296
296
  });
@@ -301,7 +301,7 @@ await ta('POST /instances: a re-request never changes an existing project\'s mod
301
301
  // to hunt for one.
302
302
  existingRow = { id: 41, owner_builder_id: 7, slug: 'mercury', status: 'active', type: 'game', modules: ['agents'], detail: {} };
303
303
  const res = await post('/provisioning/instances', {
304
- slug: 'mercury', hosting_shape: 'co-tenant', modules: ['discord'],
304
+ slug: 'mercury', hosting_shape: 'byo-host', modules: ['discord'],
305
305
  });
306
306
  assert.equal(res.status, 200, res.raw);
307
307
  assert.ok(res.body.modules_ignored, 'the dropped answer is reported, not swallowed');
@@ -589,7 +589,7 @@ await ta('POST /instances: a null answer at CREATION is accepted and stores noth
589
589
  lastCreateArgs = null; existingRow = null;
590
590
 
591
591
  const res = await post('/provisioning/instances', {
592
- slug: 'mercury', hosting_shape: 'co-tenant', type: 'game', detail: { team_shape: null },
592
+ slug: 'mercury', hosting_shape: 'byo-host', type: 'game', detail: { team_shape: null },
593
593
  });
594
594
 
595
595
  assert.equal(res.status, 201, `expected 201, got ${res.status} ${res.raw}`);
@@ -150,7 +150,7 @@ function post(path, body) {
150
150
  await ta('POST /instances: onboard_mode=adopt is threaded through to createInstance', async () => {
151
151
  lastCreateArgs = null;
152
152
  const res = await post('/provisioning/instances', {
153
- slug: 'demo', hosting_shape: 'co-tenant', target_ref: 'example-owner/demo', onboard_mode: 'adopt',
153
+ slug: 'demo', hosting_shape: 'byo-host', target_ref: 'example-owner/demo', onboard_mode: 'adopt',
154
154
  });
155
155
  assert.equal(res.status, 201, `expected 201, got ${res.status} ${res.raw}`);
156
156
  assert.equal(lastCreateArgs && lastCreateArgs.onboardMode, 'adopt');
@@ -158,7 +158,7 @@ await ta('POST /instances: onboard_mode=adopt is threaded through to createInsta
158
158
 
159
159
  await ta('POST /instances: onboard_mode absent → greenfield (unchanged behavior for every existing caller)', async () => {
160
160
  lastCreateArgs = null;
161
- const res = await post('/provisioning/instances', { slug: 'demo', hosting_shape: 'co-tenant' });
161
+ const res = await post('/provisioning/instances', { slug: 'demo', hosting_shape: 'byo-host' });
162
162
  assert.equal(res.status, 201, `expected 201, got ${res.status} ${res.raw}`);
163
163
  assert.equal(lastCreateArgs && lastCreateArgs.onboardMode, 'greenfield');
164
164
  });
@@ -167,7 +167,7 @@ await ta('POST /instances: an unknown onboard_mode is REFUSED (never silently co
167
167
  // Coercing here would be worse than refusing: the caller believes it asked for one thing
168
168
  // and the runner does another. The route names the valid set so the client can fix itself.
169
169
  const res = await post('/provisioning/instances', {
170
- slug: 'demo', hosting_shape: 'co-tenant', target_ref: 'a/b', onboard_mode: 'brownfield',
170
+ slug: 'demo', hosting_shape: 'byo-host', target_ref: 'a/b', onboard_mode: 'brownfield',
171
171
  });
172
172
  assert.equal(res.status, 400);
173
173
  assert.equal(res.body.error.code, 'bad_onboard_mode');
@@ -204,8 +204,8 @@ await ta('POST /instances: adopt WITHOUT target_ref is refused — there is no r
204
204
  // Without target_ref the runner would fall back to greenfield behavior on a row labelled
205
205
  // adopt. Refuse at the door rather than store a lie.
206
206
  for (const body of [
207
- { slug: 'demo', hosting_shape: 'co-tenant', onboard_mode: 'adopt' },
208
- { slug: 'demo', hosting_shape: 'co-tenant', onboard_mode: 'adopt', target_ref: ' ' },
207
+ { slug: 'demo', hosting_shape: 'byo-host', onboard_mode: 'adopt' },
208
+ { slug: 'demo', hosting_shape: 'byo-host', onboard_mode: 'adopt', target_ref: ' ' },
209
209
  ]) {
210
210
  const res = await post('/provisioning/instances', body);
211
211
  assert.equal(res.status, 400, `expected 400 for ${JSON.stringify(body)}`);