@bongos/core 1.19.59 → 1.19.61

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.59",
6
- "core_contract": "1.19.59",
7
- "source_commit": "e93244fafbe4236b1b90218f03c70c92bfd0becb",
5
+ "core_version": "1.19.61",
6
+ "core_contract": "1.19.61",
7
+ "source_commit": "073b10995781fbdfae0a9da943fa8755002e51bf",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-08-11T23:37:01.674Z",
9
+ "built_at": "2026-08-12T00:24:52.611Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 291,
13
13
  "agent_docs_stubbed": 21,
14
- "functional_verbatim": 1217,
14
+ "functional_verbatim": 1220,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 1529,
20
- "tree_sha256": "0f793cd7617fce56439932f68de11239e7639d28e50f8d9ee485ba8a602a5f30",
19
+ "file_count": 1532,
20
+ "tree_sha256": "c4d2ff9c96e88bf23e2c2c1acf1593d174589a1b9beeb7b3b12d61c65bb84113",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/blocker-review/SKILL.md",
@@ -347,12 +347,12 @@
347
347
  {
348
348
  "path": "clients/bongos-client/README.md",
349
349
  "mode": "0000644",
350
- "sha256": "8aeb1aaf87db169788d5e17a671846526a1c89f184ee6ff0425730ecea71fb8a"
350
+ "sha256": "36eb537043097dc8e05307adc06d893a2c5ceb8cceb63f24b64ade001a0a2640"
351
351
  },
352
352
  {
353
353
  "path": "clients/bongos-client/bongos-client.global.js",
354
354
  "mode": "0000644",
355
- "sha256": "724bfb1a3647a316226f32b1b62f56e30dba8df28a2ccfadb1dcbaaff65e7197"
355
+ "sha256": "ad598bf38511e3e7bd9f25a82987126cd71cfe76ac8275ddee2bcd90eb8e6a11"
356
356
  },
357
357
  {
358
358
  "path": "clients/bongos-client/examples/hello-world.mjs",
@@ -362,17 +362,17 @@
362
362
  {
363
363
  "path": "clients/bongos-client/index.cjs",
364
364
  "mode": "0000644",
365
- "sha256": "e4efb5ad8e209a5c27c9a85f42e920915a7d7b91491197b03a52f85091319247"
365
+ "sha256": "43823273ec587b104f3e0296614ae8ea6c4dc5c96261dbbb359ae39c21cf4e79"
366
366
  },
367
367
  {
368
368
  "path": "clients/bongos-client/index.d.ts",
369
369
  "mode": "0000644",
370
- "sha256": "5c89524ec76f5f5df365a188541d50aa31167ae7920124baf562fb32ad30632c"
370
+ "sha256": "4deeae55ba58483557310d768a63df7cd75a1219137539b7ccbbf4481f09f5d9"
371
371
  },
372
372
  {
373
373
  "path": "clients/bongos-client/index.mjs",
374
374
  "mode": "0000644",
375
- "sha256": "ff501fe93dd31785d2acb8e55c9b664128065d7acf2862fb91af0800274619de"
375
+ "sha256": "6d441799f948513f840394bd382dba7e601e79a07fb7591bcb0e05aea2edd2a9"
376
376
  },
377
377
  {
378
378
  "path": "clients/bongos-client/package.json",
@@ -1307,12 +1307,12 @@
1307
1307
  {
1308
1308
  "path": "docs/api-reference.md",
1309
1309
  "mode": "0000644",
1310
- "sha256": "27e9c0ab46db5386833b9e03bcff0dae31f4606882e845e7e830f9ef6f2550b5"
1310
+ "sha256": "a30007778606ce90b8e31470d926047a372c72b93022f54023937cfd4c5b5fc0"
1311
1311
  },
1312
1312
  {
1313
1313
  "path": "docs/api/openapi.json",
1314
1314
  "mode": "0000644",
1315
- "sha256": "0076d08133c342fa905fa90682e606eefa02747d5b11aea9f0bdb3563b293f76"
1315
+ "sha256": "0e7646ee148453ef5d6d51b3434253b76f35628ee6c36681dbf6e7b27dd20c6f"
1316
1316
  },
1317
1317
  {
1318
1318
  "path": "docs/architecture.md",
@@ -2552,7 +2552,7 @@
2552
2552
  {
2553
2553
  "path": "modules/dev-box/app/src/vendor/bongos-client.cjs",
2554
2554
  "mode": "0000644",
2555
- "sha256": "e4efb5ad8e209a5c27c9a85f42e920915a7d7b91491197b03a52f85091319247"
2555
+ "sha256": "43823273ec587b104f3e0296614ae8ea6c4dc5c96261dbbb359ae39c21cf4e79"
2556
2556
  },
2557
2557
  {
2558
2558
  "path": "modules/dev-box/app/test/unit.mjs",
@@ -3719,6 +3719,11 @@
3719
3719
  "mode": "0000644",
3720
3720
  "sha256": "c25101daed13fc22ba3f132b2dac59c782fa564fa4bc22a6a5169e6644109b3a"
3721
3721
  },
3722
+ {
3723
+ "path": "modules/provisioning/migrations/provisioning_005_domain_attach.sql",
3724
+ "mode": "0000644",
3725
+ "sha256": "230e8a9df147495ee5113c1400c582e9e39e3286baab7e01ce7684e1b1c7860c"
3726
+ },
3722
3727
  {
3723
3728
  "path": "modules/provisioning/module.json",
3724
3729
  "mode": "0000644",
@@ -3732,7 +3737,7 @@
3732
3737
  {
3733
3738
  "path": "modules/provisioning/provisioning.js",
3734
3739
  "mode": "0000644",
3735
- "sha256": "468557062f75a9a02e3cab53103389d7ecaaede6528b60587e51a71ce9dfecf5"
3740
+ "sha256": "48c047c48579a1cb5cc6b36c2148bcab5c1736a16c4c73c0741a8998bfe922cd"
3736
3741
  },
3737
3742
  {
3738
3743
  "path": "modules/provisioning/rate-limit.js",
@@ -3742,7 +3747,7 @@
3742
3747
  {
3743
3748
  "path": "modules/provisioning/routes/provisioning.js",
3744
3749
  "mode": "0000644",
3745
- "sha256": "f48562d5f17e0ea9934b4ca96c84e1ac0fc448819a8fff54227045e52aa98731"
3750
+ "sha256": "a490a4b4b607a302533bac6fedbc39a0153d57e35a086bcc20079fbefc6815e5"
3746
3751
  },
3747
3752
  {
3748
3753
  "path": "modules/provisioning/tests/provisioning.mjs",
@@ -4032,12 +4037,12 @@
4032
4037
  {
4033
4038
  "path": "package-lock.json",
4034
4039
  "mode": "0000644",
4035
- "sha256": "f2e15bf06d0e8080e47aeea514dfc26dc267c14a96d32fe805cb240491f0baad"
4040
+ "sha256": "9b320dfef27bca09c1ab23bb85c04b499724208dad01a608763f18b9c4e1d1b4"
4036
4041
  },
4037
4042
  {
4038
4043
  "path": "package.json",
4039
4044
  "mode": "0000644",
4040
- "sha256": "c3feb3c561c5bf96e9580673b6995835a0d5674f54f96c8b3109fc304e090556"
4045
+ "sha256": "d1720c6790a6aa61a091335cf8873dac1e75dfbb1250c7d484395c95fd8825c6"
4041
4046
  },
4042
4047
  {
4043
4048
  "path": "public-docs/index.html",
@@ -5007,7 +5012,7 @@
5007
5012
  {
5008
5013
  "path": "scripts/gds/provision.js",
5009
5014
  "mode": "0000644",
5010
- "sha256": "93a5c4a6d6f76cf1fa8ca4c902b19d6ef94f9147488f1c01d3413f692a196942"
5015
+ "sha256": "e4108117dbe8dd6493ba41ed053fe8648424ca127b1c26505aa6f6702b2dd94c"
5011
5016
  },
5012
5017
  {
5013
5018
  "path": "scripts/gds/publish-manifest.js",
@@ -5657,7 +5662,7 @@
5657
5662
  {
5658
5663
  "path": "src/module-api.js",
5659
5664
  "mode": "0000644",
5660
- "sha256": "a02eb4c1cfa272d700a9776652612143de38566cd86208f059ed1d0fed24934b"
5665
+ "sha256": "0126fe1e9af95f3daee985c7a9859e19b5937e023d6a54927b6c5dd7055a83ec"
5661
5666
  },
5662
5667
  {
5663
5668
  "path": "src/module-loader/loader.js",
@@ -7137,7 +7142,12 @@
7137
7142
  {
7138
7143
  "path": "tests/provision.mjs",
7139
7144
  "mode": "0000644",
7140
- "sha256": "6f47c2a9c43fbef9b26090c9ca794a67bbca279f5bc79db625f26fa43775d4b7"
7145
+ "sha256": "bd8e111c2d5614f79086c5f8298cec6ffdcb7ab57b94565457520d85df0c24a8"
7146
+ },
7147
+ {
7148
+ "path": "tests/provisioning_domain_attach.mjs",
7149
+ "mode": "0000644",
7150
+ "sha256": "241e1b208abbd54a625af050d541344186d93806846a1e7f1cbb0f93dfbab802"
7141
7151
  },
7142
7152
  {
7143
7153
  "path": "tests/provisioning_github_app_routes.mjs",
@@ -7149,6 +7159,11 @@
7149
7159
  "mode": "0000644",
7150
7160
  "sha256": "9a41d64fba3a8cc8ffdbccb0aea363ca6ccb63909cfeefd4bd0b05ebab052403"
7151
7161
  },
7162
+ {
7163
+ "path": "tests/provisioning_teardown_intent.mjs",
7164
+ "mode": "0000644",
7165
+ "sha256": "c5ebacf7e29be4e6afecedf5b8593eef750d497e0c502cfa2be92a89b2cff0a1"
7166
+ },
7152
7167
  {
7153
7168
  "path": "tests/pruned_lockfile_integrity.mjs",
7154
7169
  "mode": "0000644",
@@ -5,7 +5,7 @@ A **generated**, zero-dependency typed client for the Bongos API — produced fr
5
5
  by hand; it regenerates when the spec changes, so it can never drift from the routes.
6
6
 
7
7
  - API version: **v1** (served at `/api/bongos/v1`)
8
- - 268 operations across 45 resource groups
8
+ - 269 operations across 45 resource groups
9
9
 
10
10
  ## Install
11
11
 
@@ -523,6 +523,8 @@ function createClient(opts = {}) {
523
523
  postProvisioningInstances: (args) => request("POST", "/provisioning/instances", { hasBody: true }, args),
524
524
  // GET /provisioning/instances/{id} — rank: any-builder — GET /provisioning/instances/:id
525
525
  getProvisioningInstancesId: (args) => request("GET", "/provisioning/instances/{id}", { hasBody: false }, args),
526
+ // POST /provisioning/instances/{id}/domain — rank: any-builder — POST /provisioning/instances/:id/domain
527
+ postProvisioningInstancesIdDomain: (args) => request("POST", "/provisioning/instances/{id}/domain", { hasBody: true }, args),
526
528
  // POST /provisioning/instances/{id}/force-teardown — rank: metic+archon — POST /provisioning/instances/:id/force-teardown
527
529
  postProvisioningInstancesIdForceTeardown: (args) => request("POST", "/provisioning/instances/{id}/force-teardown", { hasBody: true }, args),
528
530
  // POST /provisioning/instances/{id}/github-app — rank: any-builder — POST /provisioning/instances/:id/github-app
@@ -522,6 +522,8 @@ function createClient(opts = {}) {
522
522
  postProvisioningInstances: (args) => request("POST", "/provisioning/instances", { hasBody: true }, args),
523
523
  // GET /provisioning/instances/{id} — rank: any-builder — GET /provisioning/instances/:id
524
524
  getProvisioningInstancesId: (args) => request("GET", "/provisioning/instances/{id}", { hasBody: false }, args),
525
+ // POST /provisioning/instances/{id}/domain — rank: any-builder — POST /provisioning/instances/:id/domain
526
+ postProvisioningInstancesIdDomain: (args) => request("POST", "/provisioning/instances/{id}/domain", { hasBody: true }, args),
525
527
  // POST /provisioning/instances/{id}/force-teardown — rank: metic+archon — POST /provisioning/instances/:id/force-teardown
526
528
  postProvisioningInstancesIdForceTeardown: (args) => request("POST", "/provisioning/instances/{id}/force-teardown", { hasBody: true }, args),
527
529
  // POST /provisioning/instances/{id}/github-app — rank: any-builder — POST /provisioning/instances/:id/github-app
@@ -246,10 +246,12 @@ export interface PostOverrideRequestsIdDecideRequest { decision: string; note?:
246
246
  export interface PostProjectsInviteRequest { client_id: string; github_login: string }
247
247
  export interface PostProjectsRequest { origin: string; name: string; tagline?: string; art_url?: string }
248
248
  export interface PostProjectsResponse { project: unknown }
249
- export interface PostProvisioningInstancesIdForceTeardownResponse { ok: boolean; queued: unknown; action: unknown; status: unknown; message: unknown }
249
+ export interface PostProvisioningInstancesIdDomainRequest { domain: string }
250
+ export interface PostProvisioningInstancesIdDomainResponse { ok: boolean; changed: boolean; queued: unknown; domain: unknown; message: unknown }
251
+ export interface PostProvisioningInstancesIdForceTeardownResponse { ok: boolean; queued: unknown; action: unknown; status: unknown; superseded: unknown; message: unknown }
250
252
  export interface PostProvisioningInstancesIdGithubAppRequest { account?: string }
251
253
  export interface PostProvisioningInstancesIdGithubAppResponse { state: unknown; account: unknown; post_url: unknown; personal_url: unknown; org_url: unknown; org_login: unknown; manifest: unknown; redirect_url: unknown; expires_at: unknown }
252
- export interface PostProvisioningInstancesIdTeardownResponse { ok: boolean; queued: unknown; action: unknown; status: unknown; message: unknown }
254
+ export interface PostProvisioningInstancesIdTeardownResponse { ok: boolean; queued: unknown; action: unknown; status: unknown; superseded: unknown; message: unknown }
253
255
  export interface PostProvisioningInstancesRequest { slug: string; target_ref?: string; hosting_shape?: string; domain?: string; tier?: string; onboard_mode?: string }
254
256
  export interface PostSearchMarkRequest { chunk_id?: StringifiableId; useful?: unknown }
255
257
  export interface PostSearchRequest { expand?: unknown; q?: string; source_kind?: string; limit?: unknown }
@@ -756,6 +758,8 @@ export interface BongosClient {
756
758
  postProvisioningInstances(args: RequestArgs & { body: PostProvisioningInstancesRequest }): Promise<ApiResponse>;
757
759
  /** GET /provisioning/instances/{id} — rank: any-builder */
758
760
  getProvisioningInstancesId(args?: RequestArgs): Promise<GetProvisioningInstancesIdResponse>;
761
+ /** POST /provisioning/instances/{id}/domain — rank: any-builder */
762
+ postProvisioningInstancesIdDomain(args: RequestArgs & { body: PostProvisioningInstancesIdDomainRequest }): Promise<PostProvisioningInstancesIdDomainResponse>;
759
763
  /** POST /provisioning/instances/{id}/force-teardown — rank: metic+archon */
760
764
  postProvisioningInstancesIdForceTeardown(args?: RequestArgs): Promise<PostProvisioningInstancesIdForceTeardownResponse>;
761
765
  /** POST /provisioning/instances/{id}/github-app — rank: any-builder */
@@ -519,6 +519,8 @@ export function createClient(opts = {}) {
519
519
  postProvisioningInstances: (args) => request("POST", "/provisioning/instances", { hasBody: true }, args),
520
520
  // GET /provisioning/instances/{id} — rank: any-builder — GET /provisioning/instances/:id
521
521
  getProvisioningInstancesId: (args) => request("GET", "/provisioning/instances/{id}", { hasBody: false }, args),
522
+ // POST /provisioning/instances/{id}/domain — rank: any-builder — POST /provisioning/instances/:id/domain
523
+ postProvisioningInstancesIdDomain: (args) => request("POST", "/provisioning/instances/{id}/domain", { hasBody: true }, args),
522
524
  // POST /provisioning/instances/{id}/force-teardown — rank: metic+archon — POST /provisioning/instances/:id/force-teardown
523
525
  postProvisioningInstancesIdForceTeardown: (args) => request("POST", "/provisioning/instances/{id}/force-teardown", { hasBody: true }, args),
524
526
  // POST /provisioning/instances/{id}/github-app — rank: any-builder — POST /provisioning/instances/:id/github-app
@@ -8493,6 +8493,69 @@
8493
8493
  ]
8494
8494
  }
8495
8495
  },
8496
+ "/provisioning/instances/{id}/domain": {
8497
+ "post": {
8498
+ "operationId": "post_provisioning_instances_id_domain",
8499
+ "tags": [
8500
+ "provisioning"
8501
+ ],
8502
+ "summary": "POST /provisioning/instances/:id/domain",
8503
+ "description": "POST /provisioning/instances/:id/domain — give the caller's OWN instance its address (task 1002692, gap 5: no path existed to add a domain after creation). Validates + records the domain; for an already-ACTIVE instance it also enqueues a 'domain-attach' intent the control-plane runner drains (web tier holds no cloud tokens — ADR 0111 §2). A requested/error instance only records it: the (re)provision run wires the domain itself. Re-pointing an existing address is out of scope — refuse truthfully. Body: { domain (required) }.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
8504
+ "x-rank": "any-builder",
8505
+ "x-source": "modules/provisioning/routes/provisioning.js",
8506
+ "parameters": [
8507
+ {
8508
+ "name": "id",
8509
+ "in": "path",
8510
+ "required": true,
8511
+ "schema": {
8512
+ "type": "string"
8513
+ },
8514
+ "description": "Path parameter `id`."
8515
+ }
8516
+ ],
8517
+ "requestBody": {
8518
+ "required": true,
8519
+ "content": {
8520
+ "application/json": {
8521
+ "schema": {
8522
+ "$ref": "#/components/schemas/PostProvisioningInstancesIdDomainRequest"
8523
+ }
8524
+ }
8525
+ },
8526
+ "x-validated": true
8527
+ },
8528
+ "responses": {
8529
+ "200": {
8530
+ "description": "Success.",
8531
+ "content": {
8532
+ "application/json": {
8533
+ "schema": {
8534
+ "$ref": "#/components/schemas/PostProvisioningInstancesIdDomainResponse"
8535
+ }
8536
+ }
8537
+ }
8538
+ },
8539
+ "400": {
8540
+ "$ref": "#/components/responses/ValidationFailed"
8541
+ },
8542
+ "401": {
8543
+ "$ref": "#/components/responses/Unauthorized"
8544
+ },
8545
+ "403": {
8546
+ "$ref": "#/components/responses/Forbidden"
8547
+ },
8548
+ "404": {
8549
+ "$ref": "#/components/responses/NotFound"
8550
+ }
8551
+ },
8552
+ "security": [
8553
+ {
8554
+ "builderSession": []
8555
+ }
8556
+ ]
8557
+ }
8558
+ },
8496
8559
  "/provisioning/instances/{id}/force-teardown": {
8497
8560
  "post": {
8498
8561
  "operationId": "post_provisioning_instances_id_force_teardown",
@@ -15628,6 +15691,40 @@
15628
15691
  "project"
15629
15692
  ]
15630
15693
  },
15694
+ "PostProvisioningInstancesIdDomainRequest": {
15695
+ "type": "object",
15696
+ "properties": {
15697
+ "domain": {
15698
+ "type": "string",
15699
+ "maxLength": 253
15700
+ }
15701
+ },
15702
+ "required": [
15703
+ "domain"
15704
+ ],
15705
+ "additionalProperties": false
15706
+ },
15707
+ "PostProvisioningInstancesIdDomainResponse": {
15708
+ "type": "object",
15709
+ "properties": {
15710
+ "ok": {
15711
+ "type": "boolean"
15712
+ },
15713
+ "changed": {
15714
+ "type": "boolean"
15715
+ },
15716
+ "queued": {},
15717
+ "domain": {},
15718
+ "message": {}
15719
+ },
15720
+ "required": [
15721
+ "ok",
15722
+ "changed",
15723
+ "queued",
15724
+ "domain",
15725
+ "message"
15726
+ ]
15727
+ },
15631
15728
  "PostProvisioningInstancesIdForceTeardownResponse": {
15632
15729
  "type": "object",
15633
15730
  "properties": {
@@ -15637,6 +15734,7 @@
15637
15734
  "queued": {},
15638
15735
  "action": {},
15639
15736
  "status": {},
15737
+ "superseded": {},
15640
15738
  "message": {}
15641
15739
  },
15642
15740
  "required": [
@@ -15644,6 +15742,7 @@
15644
15742
  "queued",
15645
15743
  "action",
15646
15744
  "status",
15745
+ "superseded",
15647
15746
  "message"
15648
15747
  ]
15649
15748
  },
@@ -15691,6 +15790,7 @@
15691
15790
  "queued": {},
15692
15791
  "action": {},
15693
15792
  "status": {},
15793
+ "superseded": {},
15694
15794
  "message": {}
15695
15795
  },
15696
15796
  "required": [
@@ -15698,6 +15798,7 @@
15698
15798
  "queued",
15699
15799
  "action",
15700
15800
  "status",
15801
+ "superseded",
15701
15802
  "message"
15702
15803
  ]
15703
15804
  },
@@ -16871,9 +16972,9 @@
16871
16972
  "description": "A required dependency/feature is not configured or is temporarily down."
16872
16973
  }
16873
16974
  },
16874
- "x-endpoint-count": 268,
16875
- "x-schema-count": 300,
16975
+ "x-endpoint-count": 269,
16976
+ "x-schema-count": 302,
16876
16977
  "x-undocumented-bodies": 8,
16877
- "x-response-schemas": 203,
16978
+ "x-response-schemas": 204,
16878
16979
  "x-generated-by": "scripts/gds/gen-api-docs.js"
16879
16980
  }
@@ -2,7 +2,7 @@
2
2
 
3
3
  # Bongos API reference
4
4
 
5
- > **Generated from the live route files** — the route file is authoritative. 268 endpoints across 44 route files.
5
+ > **Generated from the live route files** — the route file is authoritative. 269 endpoints across 44 route files.
6
6
  > Machine-readable spec: [`docs/api/openapi.json`](api/openapi.json) (OpenAPI 3.1). Rendered docs site: **`/docs`** (e.g. `cloudbongos.com/docs`).
7
7
 
8
8
  Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>.md)): `public` < `any-builder` < `metic+archon` < `archon`.
@@ -358,7 +358,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>
358
358
  | GET | `/api/bongos/projects/featured` | `public` | — | a curated public showcase; no auth by design. |
359
359
  | POST | `/api/bongos/projects/invite` | `metic+archon` | `client_id`, `github_login` | invite a builder into a project (ADR 0141 §5, task #1002115). |
360
360
 
361
- ## `provisioning` (13)
361
+ ## `provisioning` (14)
362
362
 
363
363
  | Method | Path | Rank | Body | Description |
364
364
  |---|---|---|---|---|
@@ -370,6 +370,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>
370
370
  | GET | `/api/bongos/provisioning/instances` | `any-builder` | — | GET /provisioning/instances — the caller's own instances + each open intent. |
371
371
  | POST | `/api/bongos/provisioning/instances` | `any-builder` | `slug`, `target_ref`, `hosting_shape`, `domain`, `tier`, `onboard_mode` | POST /provisioning/instances — request a new instance. |
372
372
  | GET | `/api/bongos/provisioning/instances/:id` | `any-builder` | — | GET /provisioning/instances/:id — read one of the caller's own instances. |
373
+ | POST | `/api/bongos/provisioning/instances/:id/domain` | `any-builder` | `domain` | POST /provisioning/instances/:id/domain — give the caller's OWN instance its address (task 1002692, gap 5: no path existed to add a domai… |
373
374
  | POST | `/api/bongos/provisioning/instances/:id/force-teardown` | `metic+archon` | — | POST /provisioning/instances/:id/force-teardown — Archon tears down ANY instance. |
374
375
  | POST | `/api/bongos/provisioning/instances/:id/github-app` | `any-builder` | `account` | POST /provisioning/instances/:id/github-app — mint a GitHub-App-Manifest onboarding link for the caller's OWN instance (task 2080, ADR 01… |
375
376
  | POST | `/api/bongos/provisioning/instances/:id/teardown` | `any-builder` | — | POST /provisioning/instances/:id/teardown — request teardown of the caller's OWN instance. |
@@ -522,6 +522,8 @@ function createClient(opts = {}) {
522
522
  postProvisioningInstances: (args) => request("POST", "/provisioning/instances", { hasBody: true }, args),
523
523
  // GET /provisioning/instances/{id} — rank: any-builder — GET /provisioning/instances/:id
524
524
  getProvisioningInstancesId: (args) => request("GET", "/provisioning/instances/{id}", { hasBody: false }, args),
525
+ // POST /provisioning/instances/{id}/domain — rank: any-builder — POST /provisioning/instances/:id/domain
526
+ postProvisioningInstancesIdDomain: (args) => request("POST", "/provisioning/instances/{id}/domain", { hasBody: true }, args),
525
527
  // POST /provisioning/instances/{id}/force-teardown — rank: metic+archon — POST /provisioning/instances/:id/force-teardown
526
528
  postProvisioningInstancesIdForceTeardown: (args) => request("POST", "/provisioning/instances/{id}/force-teardown", { hasBody: true }, args),
527
529
  // POST /provisioning/instances/{id}/github-app — rank: any-builder — POST /provisioning/instances/:id/github-app
@@ -0,0 +1,20 @@
1
+ -- provisioning_005_domain_attach.sql — the domain-attach intent action
2
+ -- (task 1002692, gap 5 of the creation-flow audit / idea 1000677).
3
+ --
4
+ -- WHY. No path existed to give an EXISTING instance its address: a slug re-request
5
+ -- silently dropped the domain in the body, and a domainless instance stayed
6
+ -- domainless forever — which also blocked GitHub sign-in (the github-app manifest
7
+ -- route requires a domain). The new owner-scoped POST /provisioning/instances/:id/
8
+ -- domain records the domain and, for an already-ACTIVE instance, enqueues a
9
+ -- 'domain-attach' intent; the control-plane runner (the sole token-holder, ADR 0111
10
+ -- §2) re-asserts the domain-dependent standup legs (web.env origins + service
11
+ -- restart, Cloudflare A-record UPSERT, Caddy snippet + reload, healthz). The web
12
+ -- tier still only ENQUEUES — the trust boundary is unchanged.
13
+ --
14
+ -- Widening a CHECK enum is additive (permits MORE values; no row changes) — the
15
+ -- provisioning_003 precedent. Applied only when the provisioning module is enabled.
16
+ -- Safe to re-run. Keep INTENT_ACTIONS in provisioning.js in lockstep.
17
+
18
+ ALTER TABLE provisioning_intents DROP CONSTRAINT IF EXISTS provisioning_intents_action_check;
19
+ ALTER TABLE provisioning_intents ADD CONSTRAINT provisioning_intents_action_check
20
+ CHECK (action IN ('provision', 'teardown', 'oauth-manifest', 'domain-attach'));
@@ -33,11 +33,25 @@ const ONBOARD_MODES = ['greenfield', 'adopt'];
33
33
  // conversion. Today the runner drains it by scanning provisioning_oauth_manifests
34
34
  // (drainOAuthManifests), not the intent queue, but it stays a valid action so the
35
35
  // widened CHECK (migration provisioning_003) and this list never drift.
36
- const INTENT_ACTIONS = ['provision', 'teardown', 'oauth-manifest'];
36
+ // 'domain-attach' (task 1002692, migration provisioning_005): re-assert the
37
+ // domain-dependent standup legs for an already-ACTIVE instance that was just
38
+ // given its address via POST /provisioning/instances/:id/domain.
39
+ const INTENT_ACTIONS = ['provision', 'teardown', 'oauth-manifest', 'domain-attach'];
37
40
  // The states from which a teardown is meaningful (there is something to reclaim).
38
41
  // Mirrors dev-box canTransition('deprovision', …): a requested/torn_down/error
39
42
  // instance has nothing running to tear down via the happy path.
40
43
  const TEARDOWN_FROM = new Set(['provisioning', 'active', 'tearing_down']);
44
+ // The states from which (re)enqueueing a 'provision' intent is meaningful. An
45
+ // 'active' instance needs no standup — blindly re-enqueueing one on a slug
46
+ // re-request left a stale open provision intent that then blocked the owner's
47
+ // teardown on the action-blind ux_provisioning_intents_open (task 1002691);
48
+ // 'tearing_down' is mid-reclaim (any open slot belongs to the teardown).
49
+ const PROVISION_ENQUEUE_FROM = new Set(['requested', 'provisioning', 'error', 'torn_down']);
50
+ // The states from which attaching a domain is meaningful (task 1002692): 'active'
51
+ // needs a 'domain-attach' intent (the runner re-asserts the domain legs);
52
+ // 'requested'/'error' only record it — the (re)provision run wires the domain
53
+ // itself. Mid-flight or torn-down states refuse truthfully.
54
+ const DOMAIN_ATTACH_FROM = new Set(['requested', 'error', 'active']);
41
55
 
42
56
  // Monthly cost estimate (USD) for a dedicated droplet tier — VISIBILITY only
43
57
  // (ADR 0111 §6), never a bill. A small local table so this module needs no
@@ -248,6 +262,29 @@ async function getInstanceBySlug(db, slug) {
248
262
  return rows[0] || null;
249
263
  }
250
264
 
265
+ // The instance currently holding `domain` (case-insensitive), if any — backs the
266
+ // domain-attach route's uniqueness check so one owner's address can never silently
267
+ // shadow another's (task 1002692). Caller normalizes; matching is defensive anyway.
268
+ async function getInstanceByDomain(db, domain) {
269
+ if (!domain) return null;
270
+ const { rows } = await db.query(
271
+ `SELECT * FROM provisioning_instances WHERE lower(domain) = $1 LIMIT 1`,
272
+ [String(domain).toLowerCase()]
273
+ );
274
+ return rows[0] || null;
275
+ }
276
+
277
+ // Record an instance's domain — the ONLY column this touches (task 1002692). Status
278
+ // changes stay setInstanceStatus's job; keeping this narrow means the domain-attach
279
+ // route can never drift an instance's lifecycle as a side effect.
280
+ async function updateInstanceDomain(db, id, domain) {
281
+ const { rows } = await db.query(
282
+ `UPDATE provisioning_instances SET domain = $2, updated_at = now() WHERE id = $1 RETURNING *`,
283
+ [id, domain]
284
+ );
285
+ return rows[0] || null;
286
+ }
287
+
251
288
  // The caller's own instances, newest first.
252
289
  async function listInstancesForOwner(db, ownerBuilderId) {
253
290
  const { rows } = await db.query(
@@ -350,6 +387,31 @@ async function enqueueIntent(db, instanceId, action, requestedBy) {
350
387
  }
351
388
  }
352
389
 
390
+ // Queue a teardown, superseding a stale PENDING provision intent if one holds the
391
+ // open slot (task 1002691: ux_provisioning_intents_open is action-blind, so the old
392
+ // blind enqueue collided with such an intent and the API falsely said "teardown
393
+ // already queued" — the instance leaked). A pending provision is flipped to 'error'
394
+ // (the state CHECK has no 'superseded'; last_error says why) so the runner never
395
+ // stands it up. A RUNNING provision is mid-flight infra work and is never yanked —
396
+ // the caller gets { conflict: 'open_intent' } with the blocking intent so the route
397
+ // can answer truthfully. Each statement is atomic and the index stays the arbiter:
398
+ // any interleaving with the runner or a concurrent enqueue lands on one of these
399
+ // truthful outcomes.
400
+ async function enqueueTeardown(db, instanceId, requestedBy) {
401
+ const { rows: superseded } = await db.query(
402
+ `UPDATE provisioning_intents
403
+ SET state = 'error', last_error = 'superseded: teardown requested',
404
+ updated_at = now(), resolved_at = now()
405
+ WHERE instance_id = $1 AND action = 'provision' AND state = 'pending'
406
+ RETURNING id`,
407
+ [instanceId]
408
+ );
409
+ const { intent, created } = await enqueueIntent(db, instanceId, 'teardown', requestedBy);
410
+ if (created) return { intent, created: true, superseded: superseded.length > 0 };
411
+ if (intent.action === 'teardown') return { intent, created: false, superseded: false };
412
+ return { intent, created: false, superseded: false, conflict: 'open_intent' };
413
+ }
414
+
353
415
  // The instance's currently-open (pending|running) intent, if any.
354
416
  async function getOpenIntent(db, instanceId) {
355
417
  const { rows } = await db.query(
@@ -540,16 +602,16 @@ async function costLedger(db, { limit = 500 } = {}) {
540
602
  module.exports = {
541
603
  // constants
542
604
  INSTANCE_STATES, HOSTING_SHAPES, ONBOARD_MODES, INTENT_ACTIONS, TEARDOWN_FROM,
543
- TIER_MONTHLY_USD, CERT_ELIGIBLE_STATES,
605
+ PROVISION_ENQUEUE_FROM, DOMAIN_ATTACH_FROM, TIER_MONTHLY_USD, CERT_ELIGIBLE_STATES,
544
606
  // pure
545
607
  isValidSlug, costEstimateUsd, publicInstance, normalizeHostname,
546
608
  // pure — GitHub App Manifest flow (task 2080)
547
609
  githubAppName, buildGithubAppManifest, manifestPostUrl,
548
610
  // data access
549
611
  isProvisionedHostname,
550
- getInstanceById, getInstanceBySlug, listInstancesForOwner, listAllInstances,
551
- createInstance, setInstanceStatus,
552
- enqueueIntent, getOpenIntent, claimNextIntent, resolveIntent,
612
+ getInstanceById, getInstanceBySlug, getInstanceByDomain, listInstancesForOwner, listAllInstances,
613
+ createInstance, setInstanceStatus, updateInstanceDomain,
614
+ enqueueIntent, enqueueTeardown, getOpenIntent, claimNextIntent, resolveIntent,
553
615
  recordEvent, instanceStateForSlug, costLedger,
554
616
  // data access — GitHub App Manifest attempts (task 2080)
555
617
  createOAuthManifest, getOAuthManifestByState, attachManifestCode,