@bongos/core 1.19.773 → 1.19.775
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 +195 -80
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +2 -0
- package/clients/bongos-client/index.cjs +2 -0
- package/clients/bongos-client/index.d.ts +5 -1
- package/clients/bongos-client/index.mjs +2 -0
- package/config/modules.neutral.json +1 -1
- package/docs/adr/0083-modular-architecture-strangler-build-plan.md +1 -0
- package/docs/adr/0177-goal-membership-kind.md +26 -2
- package/docs/adr/0181-teardown-state-gate-and-address-retention.md +2 -0
- package/docs/adr/0285-a-shared-box-holds-about-twelve-projects-per-gb-and-memory-is-the-wall.md +1 -1
- package/docs/adr/0288-the-goal-wall-has-a-shared-floor-for-paths-no-module-owns.md +33 -0
- package/docs/adr/0289-an-unconsumed-contribution-is-a-ci-failure-not-a-boot-refusal.md +33 -0
- package/docs/adr/0290-disconnect-is-the-owner-initiated-exception-to-address-retention.md +40 -0
- package/docs/adr/README.md +3 -0
- package/docs/api/openapi.json +102 -11
- package/docs/api-reference.md +5 -4
- package/docs/copy-inventory.md +205 -190
- package/docs/copy-registry.json +435 -269
- package/docs/file-map.md +1 -0
- package/docs/module-api-changelog.md +4 -0
- package/docs/modules-contract.md +1 -1
- package/modules/builder-settings/render-prefs.js +281 -33
- package/modules/builder-settings/routes/settings.js +20 -12
- package/modules/dev-box/app/src/vendor/bongos-client.cjs +2 -0
- package/modules/dev-box/module.json +4 -0
- package/modules/hall-ui/public/settings-render.js +138 -0
- package/modules/hall-ui/public/settings.html +16 -9
- package/modules/hall-ui/public/settings.js +3 -70
- package/modules/lifecycle/ship-card.js +49 -13
- package/modules/platform-identity/migrations/platform_identity_021_drop_legacy_role.sql +94 -0
- package/modules/platform-identity/platform-identity.js +8 -19
- package/modules/platform-identity/project-catalog.js +24 -0
- package/modules/platform-identity/routes/sso.js +11 -14
- package/modules/platform-identity/tests/platform-identity.mjs +7 -9
- package/modules/provisioning/capacity-gate.js +54 -0
- package/modules/provisioning/catalog-bridge.js +2 -0
- package/modules/provisioning/disconnect.js +35 -0
- package/modules/provisioning/migrations/provisioning_016_disconnect.sql +21 -0
- package/modules/provisioning/module.json +1 -1
- package/modules/provisioning/provisioning.js +3 -3
- package/modules/provisioning/routes/disconnect.js +63 -0
- package/modules/provisioning/routes/provisioning.js +6 -6
- package/modules/provisioning/starter-bundles.js +32 -30
- package/modules/public-landing/public/projects.html +116 -11
- package/modules/public-landing/public/projects.probes.json +3 -0
- package/modules/ui-design/kit/serve.js +5 -5
- package/modules/ui-design/module.json +1 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/address-park-sweep.sh +78 -0
- package/scripts/gds/cli-lib.js +37 -0
- package/scripts/gds/fitness-migration-contraction.js +50 -0
- package/scripts/gds/fitness.js +18 -0
- package/scripts/gds/land-watch.js +8 -2
- package/scripts/gds/provision-config.js +4 -4
- package/scripts/gds/provision-disconnect.js +90 -0
- package/scripts/gds/provision-teardown.js +108 -0
- package/scripts/gds/provision.js +6 -65
- package/scripts/gds/seam-wiring-guard.js +273 -0
- package/scripts/gds/ship-finish.js +17 -10
- package/scripts/gds/ship-flow.js +5 -5
- package/scripts/gds/start.js +13 -7
- package/src/bongos/auth-admission.js +7 -8
- package/src/bongos/auth-config.js +3 -2
- package/src/bongos/goal-scope-check.js +23 -8
- package/src/bongos/module-scope-map.js +40 -0
- package/src/module-api.js +1 -1
- package/src/module-loader/manifest-schema.js +79 -1
- package/tests/catalog_only_client.mjs +8 -5
- package/tests/fitness.mjs +22 -0
- package/tests/goal_scope_check.mjs +63 -1
- package/tests/hall_settings_world.mjs +57 -1
- package/tests/hub_disconnect_ui.mjs +105 -0
- package/tests/land_watch.mjs +44 -0
- package/tests/membership_kind_lockstep.mjs +86 -27
- package/tests/module-scope-map.mjs +39 -0
- package/tests/module_exports_unique_keys.mjs +70 -0
- package/tests/module_manifest.mjs +85 -0
- package/tests/project_catalog_forget.mjs +83 -0
- package/tests/project_type_editable.mjs +8 -8
- package/tests/projects_hub.mjs +10 -4
- package/tests/projects_hub_pre_uat.mjs +5 -3
- package/tests/provision_disconnect.mjs +231 -0
- package/tests/provision_teardown_address.mjs +230 -0
- package/tests/provisioning_capacity_gate.mjs +192 -0
- package/tests/provisioning_disconnect.mjs +231 -0
- package/tests/provisioning_module_selection.mjs +14 -14
- package/tests/provisioning_recommendations.mjs +17 -25
- package/tests/provisioning_restart.mjs +5 -5
- package/tests/provisioning_starter_bundles.mjs +31 -9
- package/tests/public_landing_projects.mjs +4 -3
- package/tests/render_prefs.mjs +152 -0
- package/tests/seam_wiring_guard.mjs +234 -0
- package/tests/ship_card.mjs +67 -1
- package/tests/sso_checkin_legacy_field_gone.mjs +246 -0
|
@@ -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
|
-
-
|
|
8
|
+
- 374 operations across 58 resource groups
|
|
9
9
|
|
|
10
10
|
## Use it from your project
|
|
11
11
|
|
|
@@ -705,6 +705,8 @@ function createClient(opts = {}) {
|
|
|
705
705
|
getProvisioningInstancesId: (args) => request("GET", "/provisioning/instances/{id}", { hasBody: false }, args),
|
|
706
706
|
// PATCH /provisioning/instances/{id}/detail — rank: any-builder — PATCH /provisioning/instances/:id/detail
|
|
707
707
|
patchProvisioningInstancesIdDetail: (args) => request("PATCH", "/provisioning/instances/{id}/detail", { hasBody: true }, args),
|
|
708
|
+
// POST /provisioning/instances/{id}/disconnect — rank: any-builder — POST /provisioning/instances/:id/disconnect
|
|
709
|
+
postProvisioningInstancesIdDisconnect: (args) => request("POST", "/provisioning/instances/{id}/disconnect", { hasBody: true }, args),
|
|
708
710
|
// POST /provisioning/instances/{id}/domain — rank: any-builder — POST /provisioning/instances/:id/domain
|
|
709
711
|
postProvisioningInstancesIdDomain: (args) => request("POST", "/provisioning/instances/{id}/domain", { hasBody: true }, args),
|
|
710
712
|
// POST /provisioning/instances/{id}/force-teardown — rank: metic+archon — POST /provisioning/instances/:id/force-teardown
|
|
@@ -704,6 +704,8 @@ function createClient(opts = {}) {
|
|
|
704
704
|
getProvisioningInstancesId: (args) => request("GET", "/provisioning/instances/{id}", { hasBody: false }, args),
|
|
705
705
|
// PATCH /provisioning/instances/{id}/detail — rank: any-builder — PATCH /provisioning/instances/:id/detail
|
|
706
706
|
patchProvisioningInstancesIdDetail: (args) => request("PATCH", "/provisioning/instances/{id}/detail", { hasBody: true }, args),
|
|
707
|
+
// POST /provisioning/instances/{id}/disconnect — rank: any-builder — POST /provisioning/instances/:id/disconnect
|
|
708
|
+
postProvisioningInstancesIdDisconnect: (args) => request("POST", "/provisioning/instances/{id}/disconnect", { hasBody: true }, args),
|
|
707
709
|
// POST /provisioning/instances/{id}/domain — rank: any-builder — POST /provisioning/instances/:id/domain
|
|
708
710
|
postProvisioningInstancesIdDomain: (args) => request("POST", "/provisioning/instances/{id}/domain", { hasBody: true }, args),
|
|
709
711
|
// POST /provisioning/instances/{id}/force-teardown — rank: metic+archon — POST /provisioning/instances/:id/force-teardown
|
|
@@ -343,6 +343,8 @@ export interface PostOverrideRequestsIdDecideRequest { decision: string; note?:
|
|
|
343
343
|
export interface PostProjectsInviteRequest { client_id: string; github_login: string }
|
|
344
344
|
export interface PostProjectsRequest { origin: string; name: string; tagline?: string; description?: string; art_url?: string; type?: string; template?: string }
|
|
345
345
|
export interface PostProjectsResponse { project: unknown }
|
|
346
|
+
export interface PostProvisioningInstancesIdDisconnectRequest { confirm_slug: string }
|
|
347
|
+
export interface PostProvisioningInstancesIdDisconnectResponse { ok: boolean; queued: unknown; action: unknown; status: unknown; message: unknown }
|
|
346
348
|
export interface PostProvisioningInstancesIdDomainRequest { domain: string }
|
|
347
349
|
export interface PostProvisioningInstancesIdDomainResponse { ok: boolean; changed: boolean; queued: unknown; domain: unknown; message: unknown; replaced?: unknown }
|
|
348
350
|
export interface PostProvisioningInstancesIdForceTeardownResponse { ok: boolean; queued: unknown; action: unknown; status: unknown; superseded: unknown; message: unknown }
|
|
@@ -375,7 +377,7 @@ export interface PostSsoApplicantProfileRequest { client_id: string; client_secr
|
|
|
375
377
|
export interface PostSsoApplicantProfileResponse { profile: unknown }
|
|
376
378
|
export interface PostSsoDevicePollRequest { device_code: string }
|
|
377
379
|
export interface PostSsoDeviceStartRequest { origin: string }
|
|
378
|
-
export interface PostSsoMembershipCheckInRequest { client_id: string; client_secret: string; github_id: StringifiableId; github_login: string; display_name?: string; avatar_url?: string; membership_kind?: string
|
|
380
|
+
export interface PostSsoMembershipCheckInRequest { client_id: string; client_secret: string; github_id: StringifiableId; github_login: string; display_name?: string; avatar_url?: string; membership_kind?: string }
|
|
379
381
|
export interface PostSsoMembershipCheckInResponse { ok: boolean }
|
|
380
382
|
export interface PostSsoTokenRequest { client_id: string; client_secret: string; code: string; redirect_uri: string }
|
|
381
383
|
export interface PostSsoTokenResponse { github_id: unknown; github_login: unknown; display_name: unknown; avatar_url: unknown }
|
|
@@ -1050,6 +1052,8 @@ export interface BongosClient {
|
|
|
1050
1052
|
getProvisioningInstancesId(args?: RequestArgs): Promise<GetProvisioningInstancesIdResponse>;
|
|
1051
1053
|
/** PATCH /provisioning/instances/{id}/detail — rank: any-builder */
|
|
1052
1054
|
patchProvisioningInstancesIdDetail(args?: RequestArgs & { body?: PatchProvisioningInstancesIdDetailRequest }): Promise<PatchProvisioningInstancesIdDetailResponse>;
|
|
1055
|
+
/** POST /provisioning/instances/{id}/disconnect — rank: any-builder */
|
|
1056
|
+
postProvisioningInstancesIdDisconnect(args: RequestArgs & { body: PostProvisioningInstancesIdDisconnectRequest }): Promise<PostProvisioningInstancesIdDisconnectResponse>;
|
|
1053
1057
|
/** POST /provisioning/instances/{id}/domain — rank: any-builder */
|
|
1054
1058
|
postProvisioningInstancesIdDomain(args: RequestArgs & { body: PostProvisioningInstancesIdDomainRequest }): Promise<PostProvisioningInstancesIdDomainResponse>;
|
|
1055
1059
|
/** POST /provisioning/instances/{id}/force-teardown — rank: metic+archon */
|
|
@@ -701,6 +701,8 @@ export function createClient(opts = {}) {
|
|
|
701
701
|
getProvisioningInstancesId: (args) => request("GET", "/provisioning/instances/{id}", { hasBody: false }, args),
|
|
702
702
|
// PATCH /provisioning/instances/{id}/detail — rank: any-builder — PATCH /provisioning/instances/:id/detail
|
|
703
703
|
patchProvisioningInstancesIdDetail: (args) => request("PATCH", "/provisioning/instances/{id}/detail", { hasBody: true }, args),
|
|
704
|
+
// POST /provisioning/instances/{id}/disconnect — rank: any-builder — POST /provisioning/instances/:id/disconnect
|
|
705
|
+
postProvisioningInstancesIdDisconnect: (args) => request("POST", "/provisioning/instances/{id}/disconnect", { hasBody: true }, args),
|
|
704
706
|
// POST /provisioning/instances/{id}/domain — rank: any-builder — POST /provisioning/instances/:id/domain
|
|
705
707
|
postProvisioningInstancesIdDomain: (args) => request("POST", "/provisioning/instances/{id}/domain", { hasBody: true }, args),
|
|
706
708
|
// POST /provisioning/instances/{id}/force-teardown — rank: metic+archon — POST /provisioning/instances/:id/force-teardown
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_note": "Feature-module selection — the VANILLA (customer-0) default: every optional feature OFF. A fresh Cloud Bongos instance is the methodology core only (builders/tasks/claims/credits/grading/memory/recall) with the engineer discipline. To enable a module on an instance, copy this to config/modules.json and flip flags (or set <ENVPREFIX>_MODULE_<NAME>=1). Sibling of config/branding.neutral.json. Owned by R54 / task 1193 (src/modules.js, ADR 0062 §4). Known modules: game, art-pipeline, discord, dev-box.",
|
|
2
|
+
"_note": "Feature-module selection — the VANILLA (customer-0) default: every optional feature OFF. A fresh Cloud Bongos instance is the methodology core only (builders/tasks/claims/credits/grading/memory/recall) with the engineer discipline. To enable a module on an instance, copy this to config/modules.json and flip flags (or set <ENVPREFIX>_MODULE_<NAME>=1). Sibling of config/branding.neutral.json. Owned by R54 / task 1193 (src/modules.js, ADR 0062 §4). Known modules: game, art-pipeline, discord. RETIRED: dev-box (goal 1000091, task 1003886) — no longer offered in any starter bundle and not in the creation picker's catalog. The flag stays declared and false until modules/dev-box/ is deleted, so an instance carrying an older config/modules.json cannot silently re-enable it.",
|
|
3
3
|
"modules": {
|
|
4
4
|
"game": false,
|
|
5
5
|
"art-pipeline": false,
|
|
@@ -74,6 +74,7 @@ Generalize the working `migrations/game/` precedent ([02 §1 grounded](../design
|
|
|
74
74
|
|
|
75
75
|
- Teach `scripts/migrate.sh` to discover `modules/*/migrations/*.sql` and apply each **only if its module is enabled** (the `MEDUSA_GDS_ONLY` gate generalized to per-module enablement). `schema_migrations` stays **stem-keyed**, so relocating a migration from `migrations/game/` to `modules/game/migrations/` does **not** re-run it on prod — the existing safety guarantee carries the move for free.
|
|
76
76
|
- **Disable preserves data** — the entire mechanism is "don't apply," never "drop." There is no down-migration path and we keep it that way; a disabled module's tables simply are never created (or are left untouched).
|
|
77
|
+
- *Amended 2026-09-16 (task [1003092](https://cloudbongos.com/builders#/task/1003092)):* the additive rule met its first legitimate exception when a module ran an expand-then-contract rename ([ADR 0177](0177-goal-membership-kind.md) §"the fifth use" — `<redacted>` expanded, `<redacted>` contracted). A contract phase must drop the legacy column or the rename never finishes. The fitness check therefore admits **exactly one** destructive shape: `ALTER TABLE <own namespaced table> DROP COLUMN <c>`, one statement, in a file carrying the `-- forward-unsafe: <reason>` pragma core migrations already use for the same acknowledgement. Nothing else changes — `DROP TABLE`, `TRUNCATE` and a bare `DROP CONSTRAINT` are still refused under the pragma, and the pragma is per-statement permission for that shape, not a per-file switch (`scripts/gds/fitness-migration-contraction.js`, negative-tested in `tests/fitness.mjs`).
|
|
77
78
|
- Generalize `scripts/verify-gds-only-schema.sh` into a per-module schema-presence/absence verifier.
|
|
78
79
|
- `scripts/migrate.sh` is a **gate HARD-FLOOR** (`gate-review.js`) and `migrations/` is **NEEDS_TRUST + a protected path** (`permission-path-check.js:78-80`): the runner change needs owner sign-off; new `modules/<key>/migrations/` subtrees inherit the protected-path treatment. The loader/migrate change is owner-gated by construction.
|
|
79
80
|
|
|
@@ -136,11 +136,35 @@ row was ever affected because the pin still served 1.19.218; the invariant is
|
|
|
136
136
|
pinned statically by `tests/membership_kind_lockstep.mjs`, since the trigger only
|
|
137
137
|
misbehaves against a live Postgres and CI has none.
|
|
138
138
|
|
|
139
|
-
The window's removal
|
|
139
|
+
The window's removal was a filed task, and its condition was explicit: every federated
|
|
140
140
|
instance past the window, not merely "one release later". A time-boxed shim still ships
|
|
141
141
|
with the test of its absence (ADR 0174's Lineage lesson) — that test lands with the
|
|
142
142
|
removal, because until then the legacy field is load-bearing, not vestigial.
|
|
143
143
|
|
|
144
|
+
### The window closed — 2026-09-16, task [1003092](https://cloudbongos.com/builders#/task/1003092)
|
|
145
|
+
|
|
146
|
+
`<redacted>` is the contract phase: the lockstep trigger and its function
|
|
147
|
+
are dropped, `<redacted>.role` is dropped, and
|
|
148
|
+
`membership_kind` gains `NOT NULL` + `DEFAULT 'member'` — the two guarantees 010
|
|
149
|
+
deferred. The check-in route's schema no longer names `role` (strict validation
|
|
150
|
+
refuses a legacy-only body with 400 and writes nothing), the sender writes one
|
|
151
|
+
spelling, and the translator functions are deleted.
|
|
152
|
+
|
|
153
|
+
**How the condition was satisfied, since it was a condition and not a date.** The
|
|
154
|
+
hub holds no record of a caller's core version, so the audit probed `/version` on
|
|
155
|
+
every origin in the hub's project catalogue. The release carrying the expand phase is
|
|
156
|
+
core 1.19.241 (2026-08-18). The hub served 1.19.766; every hosted instance served
|
|
157
|
+
1.19.312 or later. Two catalogued instances — the original standalone Example
|
|
158
|
+
(core 1.17.3) and Emersonian Circles (no `coreVersion` at all) — sat *below* the
|
|
159
|
+
window, and that turned out not to matter: federation itself (the hub SSO, the client
|
|
160
|
+
registry, the idp client config and the check-in sender) landed on 2026-07-13, after
|
|
161
|
+
the 1.18.0 re-seed, so a 1.17.x core has no idp config, registers as no sso_client and
|
|
162
|
+
has never sent a check-in of either spelling. They are catalogue-only rows. One hosted
|
|
163
|
+
box (walk-lantern) was unreachable; if it wakes below 1.19.241 it must be upgraded
|
|
164
|
+
before anyone signs in to it. The general lesson for the next cross-version window:
|
|
165
|
+
"behind the release" and "a live legacy sender" are different sets, and the second is
|
|
166
|
+
the gating one.
|
|
167
|
+
|
|
144
168
|
## Consequences
|
|
145
169
|
|
|
146
170
|
- The word `role` no longer names a goal membership in the API, the hall, or the docs, and in the DB it
|
|
@@ -151,5 +175,5 @@ removal, because until then the legacy field is load-bearing, not vestigial.
|
|
|
151
175
|
the only new moving part on the write path, and it is the thing step 2 removes.
|
|
152
176
|
- `docs/canonical-permissions.md` (the goal-management wall) now names `membership_kind`; the
|
|
153
177
|
`authorizeGoalManagement` behaviour is untouched — this ADR changes no authority, floor, or grant.
|
|
154
|
-
- The fifth use (`project_memberships.role`) shipped the same day under the amendment above — same shape, plus a cross-version window because its writer is another instance.
|
|
178
|
+
- The fifth use (`project_memberships.role`) shipped the same day under the amendment above — same shape, plus a cross-version window because its writer is another instance. That window closed under task 1003092 (`<redacted>`); both renames are now complete and one-spelling.
|
|
155
179
|
- **No sixth use is known.** The remaining `role` spellings in the repo are the three legitimate ones ADR 0174 named: the crafts, `builders.system_role` (machine identity), and Discord's own noun — plus HTML's ARIA attribute, which every wall strips before matching.
|
|
@@ -54,6 +54,8 @@ The port and the address are therefore **one coupled decision**, and [task 10028
|
|
|
54
54
|
|
|
55
55
|
The user-facing copy was corrected to match. It previously promised "the server goes away and **the address stops answering**" — untrue. It now says the address **stays reserved for this project and shows an error page until you bring it back**, which is both honest and accurate.
|
|
56
56
|
|
|
57
|
+
**Update (task 1002897).** Teardown now reclaims the address itself. It parks the hostname at the RFC 5737 blackhole through the same release a re-point uses ([ADR 0200](0200-re-point-a-bring-your-own-domain.md) §2: parked, not deleted), and it removes the `<slug>.caddy` block and reloads Caddy (`scripts/gds/provision-teardown.js`). Both steps are best-effort, and a platform-reserved hostname is never parked. `port` and `domain` are still held, so the address is inert without releasing the claim, and a revive's standup UPSERTs the same hostname straight back. The copy now says the address stays reserved and **nothing answers there until you bring it back**. The database and its dumps remain operator-only.
|
|
58
|
+
|
|
57
59
|
## Consequences
|
|
58
60
|
|
|
59
61
|
- An owner can remove a project in any state from the manage page; the 409 `not_teardownable` class is gone except for the one honest case.
|
package/docs/adr/0285-a-shared-box-holds-about-twelve-projects-per-gb-and-memory-is-the-wall.md
CHANGED
|
@@ -61,7 +61,7 @@ Today's box holds **11 of 15**. Resize (or open the next box) when it reaches **
|
|
|
61
61
|
|
|
62
62
|
### 4. The shapes: keep three, and add placement as its own question
|
|
63
63
|
|
|
64
|
-
The measured cost is the same **unit** for `co-tenant` and `standalone`. What separates them is repository layout, not what they cost the box. So no new shape is needed. What the three shapes never answer is **which box**, and the rule is **standalone until the box reaches its ceiling, then the next box**. The box is the unit of spend, and this ceiling governs placement. Filed as [1003927](https://cloudbongos.com/builders#/task/1003927): the runner refuses to place a control-plane-local project past the ceiling.
|
|
64
|
+
The measured cost is the same **unit** for `co-tenant` and `standalone`. What separates them is repository layout, not what they cost the box. So no new shape is needed. What the three shapes never answer is **which box**, and the rule is **standalone until the box reaches its ceiling, then the next box**. The box is the unit of spend, and this ceiling governs placement. Filed as [1003927](https://cloudbongos.com/builders#/task/1003927): the runner refuses to place a control-plane-local project past the ceiling. The create route answers the same ceiling up front, as a 409 `box_full` before any row exists ([1003938](https://cloudbongos.com/builders#/task/1003938), [`capacity-gate.js`](../../modules/provisioning/capacity-gate.js)). The runner stays the wall, because only the runner can measure a remote box and the live memory floor.
|
|
65
65
|
|
|
66
66
|
`dedicated` stays behind `provisioning.fleet.manage` ([`paid-shape-gate.js`](../../modules/provisioning/paid-shape-gate.js)). It has never been provisioned, and it should not be offered to anyone paying nothing: at $6/mo minimum ($24 at the runner's default tier), one dedicated project costs **8–40× a shared slot**.
|
|
67
67
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# 0288 — The goal wall has a shared floor for paths no module owns
|
|
2
|
+
|
|
3
|
+
**Status:** accepted · 2026-09-16 · [task 1003655](https://cloudbongos.com/builders#/task/1003655)
|
|
4
|
+
|
|
5
|
+
## Context
|
|
6
|
+
|
|
7
|
+
[ADR 0086](0086-goal-scoped-work-hierarchy.md) §3 gives a goal member bounded authority to author their own tasks: every element of a candidate task's `touches[]` must be *contained within* the union of the goal's modules' globs, resolved through `MODULE_SCOPE_MAP`. The containment is directional (`matchOne`, not the symmetric `touchesOverlap`) precisely so the wall cannot be widened by declaring a broad parent path.
|
|
8
|
+
|
|
9
|
+
That map answers one question — *who owns this path* — and it answers it for the shipping surfaces. `tests/` has no honest answer. It is 751 flat suites named by feature rather than by module, covering all ~24 keys, and no key claims it. `scopeSubset()` therefore denied every touch under it, under every goal.
|
|
10
|
+
|
|
11
|
+
Since every real task adds a test, the consequence was structural: `POST /goals/:id/tasks` refused any task that honestly declared its own test file, whatever goal it was filed under. The gate only looked passable because callers under-declared their `touches[]`. This was filed as idea 1000816 while checking coverage for unrelated work; it had not yet surfaced as anyone's confusing 403 because no live caller sent `touches` to the capture-time routing path added in R05 ([task 1003066](https://cloudbongos.com/builders#/task/1003066)).
|
|
12
|
+
|
|
13
|
+
The same idea named `.claude/skills/` as a second instance. That half resolved itself: [task 1002989](https://cloudbongos.com/builders#/task/1002989) gave the methodology key `.claude/skills/` as the floor under the ~50 skills no module ships. Only `tests/` remained.
|
|
14
|
+
|
|
15
|
+
## Decision
|
|
16
|
+
|
|
17
|
+
**`MODULE_SCOPE_MAP` gains `SHARED_GLOBS` — a small set of globs inside every goal's wall and outside every module's territory. Its sole entry is `tests/`.**
|
|
18
|
+
|
|
19
|
+
The bar for joining it is deliberately narrow, and both halves must hold: **no module can honestly own the path, and every task legitimately writes it.**
|
|
20
|
+
|
|
21
|
+
`goal-scope-check.scopeSubset()` treats a touch as covered if it is contained in the goal's module globs **or** in the shared floor. The floor is reported as its own `shared_globs` field and is never folded into `goal_globs`, because `check()`'s `unknown_scope` guard tests `goal_globs.length === 0` — mixing an always-present list in would give a goal scoped to nothing but garbage a non-empty territory and slip it past that fail-closed edge.
|
|
22
|
+
|
|
23
|
+
**The exported floor is derived, not trusted.** On the same contract that makes `protected` a derivation rather than a literal (ADR 0086 §7, the SEC #863 second-list failure mode), a raw entry that overlaps `PROTECTED_GLOBS` is filtered out of the export. A protected path listed here would widen every goal's wall into the trust core in a single line.
|
|
24
|
+
|
|
25
|
+
## Consequences
|
|
26
|
+
|
|
27
|
+
- **A task can declare its own test honestly.** The gate stops rewarding under-declaration, which is what made the wall's numbers wrong rather than merely strict.
|
|
28
|
+
- **The other two walls are untouched.** Claim collisions are still caught by `touchesOverlap` on `touches[]`, and the protected-path wall runs independently of the subset wall — a sub-Metic author is still blocked from the trust core whether or not a path is shared. `tests/` is not in `PROTECTED_GLOBS`, so the floor opens nothing that was closed.
|
|
29
|
+
- **A test file is not a shipping surface.** Nothing imports `tests/*.mjs`, so a member authoring one has zero blast radius on another module's code. That is what makes shared territory sound here and would not make it sound for, say, `src/`.
|
|
30
|
+
- **Module-owned suites stay owned.** `modules/<key>/tests/` is already inside its module's glob and is not part of the floor.
|
|
31
|
+
- Rejected — **giving `tests/` to a key** (`devsecops` or `methodology`). It is a false ownership claim: an ideas-module test is the ideas module's work. It would also hand one goal's members the entire suite while denying it to everyone else, which is worse than the gap it closes.
|
|
32
|
+
- Rejected — **enumerating per-module test paths.** The suites are named by feature, not module, and the glob format has no wildcards to carve with, so the list would rot on the next test file.
|
|
33
|
+
- **Adding a second entry is a design decision, not housekeeping.** The floor is the one place where a line of data widens every wall at once, so it carries the derivation guard and a unit test asserting it stays narrow.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# 0289 — An unconsumed contribution is a CI failure, not a boot refusal
|
|
2
|
+
|
|
3
|
+
**Status:** accepted · 2026-09-16 · [task 1003819](https://cloudbongos.com/builders#/task/1003819) (successor to abandoned task 1002464)
|
|
4
|
+
|
|
5
|
+
## Context
|
|
6
|
+
|
|
7
|
+
A module declares what it contributes in `module.json`. Two checks read that block and neither asks the question that matters here:
|
|
8
|
+
|
|
9
|
+
- `src/module-seams.js` `verifyPortsSatisfied` checks the **opposite** direction — every `consumes` port has exactly one provider among the enabled modules. It says nothing about a `contributes` entry with no consumer.
|
|
10
|
+
- `src/module-loader/manifest-schema.js` `validateManifest` checks the **shape** of `contributes` — a closed key set, so a typo'd *key* errors. It does not look at whether the value resolves to anything.
|
|
11
|
+
|
|
12
|
+
So a module can declare `contributes.pollers: ["ghost"]` with no `modules/<key>/pollers/ghost.js` and boot stays silent. That silence is not a bug in the consumers: `startModulePollers` skips a missing poller file because a real poller may run on the control plane rather than in this process, `hallWidgetScripts` skips a `uiSections` entry with no `hall-widget.js` because a section can be declarative, and `moduleWebSurfaces` skips a surface whose directory was never shipped. Each skip is correct for a genuine declarative-only entry, and **indistinguishable from a typo or a rename that left the manifest behind**.
|
|
13
|
+
|
|
14
|
+
Verified on `main` 2026-09-16: 84 declared seam entries across 21 modules resolve to a real artifact, and five do not — `dev-box`'s `box-lifecycle` poller and `boxes` uiSection (both genuinely declarative), and three `ui-design` skills that live in the core `.claude/skills/` rather than the module tree, where `moduleSkillSources` silently skips them. Nothing was broken; nothing could tell you that.
|
|
15
|
+
|
|
16
|
+
## Decision
|
|
17
|
+
|
|
18
|
+
**A declared `contributes.<seam>` whose consumer's artifact is absent fails CI — `scripts/gds/seam-wiring-guard.js`, fitness Check 33 — and does not affect boot.**
|
|
19
|
+
|
|
20
|
+
The guard's `SEAM_CONSUMERS` table pairs each seam with **the function that actually reads it** and the artifact that function resolves, so the check asks the same question the consumer asks rather than inventing a second convention that could drift from it. A seam with no reader anywhere (`rooms`, allowed by the schema since the doorway but wired to nothing) carries `consumer: null`, and every declaration of it is reported.
|
|
21
|
+
|
|
22
|
+
**The exemption is per-entry and lives in the manifest**: `declarativeSeams: { "pollers": ["box-lifecycle"] }`. It is validated in `validateManifest`, against the module's own `contributes`, because that is the one place holding both — so an exemption naming a seam the module does not declare, or an entry it does not list, is an **error**. A typo'd exemption that validated clean would exempt nothing while reading as though it exempted something, which is the failure direction that hides a defect. The guard adds the mirror case: an exemption whose artifact **now exists** is reported as stale, so the list cannot quietly become a lie in either direction.
|
|
23
|
+
|
|
24
|
+
**The wired set is reported on the pass path**, not only the failure path — 84 wired, 5 declarative-only — because a check that is silent when it passes gives no evidence it ran.
|
|
25
|
+
|
|
26
|
+
## Consequences
|
|
27
|
+
|
|
28
|
+
- **The manifest stops being able to describe a feature that isn't there.** A rename that leaves `contributes` behind now reddens the build instead of silently shipping a module one feature short.
|
|
29
|
+
- **CI, not boot, and the asymmetry with `verifyEnabledPorts` is deliberate.** That check throws at boot because an unprovided port otherwise surfaces as a `resolve()` throw on a request path, in a process that already reported healthy — refusing early is strictly safer. For every seam but `routes`, an unconsumed contribution is not that shape: the consumer skips it gracefully, nothing crashes later, and the instance runs correctly minus one feature. Adding a production start-up failure for a manifest-hygiene defect would trade a real outage risk for a cosmetic one, and a red build costs nobody uptime.
|
|
30
|
+
- **`routes` is the exception, and it argues the same way.** `mountModuleRoutes` `require`s the factory with no try/catch, so a missing route file already takes the boot down today — pre-existing, and not changed here. For that seam the guard is not a new gate: it moves an existing crash from production start-up to the build. Strictly earlier, strictly cheaper. CI therefore covers both shapes — the seams that fail silently get a voice, and the one that fails loudly gets to fail before it reaches a server.
|
|
31
|
+
- **A new `contributes` key must be added to `SEAM_CONSUMERS` too**, or it is silently unchecked — the same gap in a new place. A test asserts the guard spans every key `ALLOWED_CONTRIBUTES` permits, so forgetting fails CI rather than going unnoticed.
|
|
32
|
+
- **`rooms` is now visibly dead.** The schema has allowed it since the doorway and nothing ever read it. No module declares it, so nothing fails today; the first one that does gets told.
|
|
33
|
+
- Rejected — **a boot-time refusal beside `verifyPortsSatisfied`**: see the asymmetry above. Rejected — **a blanket skip for the seams that legitimately allow declarative entries** (`pollers`, `uiSections`): that is how the gap arose, and it would hide the real cases the guard exists to catch. Rejected — **rebasing branch `task-1002464`**: 333 lines, 461 main commits behind its base, against a `loader.js` since rewritten. Its two test files were read for the property checklist; PR [#589](https://github.com/example-owner/cloud-bongos/pull/589) should be closed rather than revived.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# 0290 — "Disconnect from Bongos entirely" is the owner-initiated exception to retention
|
|
2
|
+
|
|
3
|
+
**Status:** accepted · 2026-09-16 · [task 1003249](https://cloudbongos.com/builders#/task/1003249)
|
|
4
|
+
|
|
5
|
+
## Context
|
|
6
|
+
|
|
7
|
+
[ADR 0181](0181-teardown-state-gate-and-address-retention.md) made retention deliberate. A teardown keeps the project's database, its nightly dumps, its `port` and its `domain`, so "Take it offline" is always reversible and a stale proxy block can never hand one owner's address to another project. [Task 1002897](https://cloudbongos.com/builders#/task/1002897) then made the kept address inert: parked at 192.0.2.1, with no Caddy block.
|
|
8
|
+
|
|
9
|
+
That left owners with no way to actually be done with a project. The manage page admitted it: "There is no permanent-delete button — clearing a project's database for good, and freeing its web address for another project, are done by hand today." The owner decided on 2026-08-24 (walkthrough decision 6) to rename the teardown control "Take it offline" and to add a separate, true delete.
|
|
10
|
+
|
|
11
|
+
## Decision
|
|
12
|
+
|
|
13
|
+
**1. A separate action, owner-initiated only.** `POST /provisioning/instances/:id/disconnect` is own-scoped with the archon override (404 for anyone else) and carries the typed name as `confirm_slug`, which the route checks server-side, because this erases a database. It is its own card after "Take it offline", never a mode of it, so the reversible control can never read as the destructive one. Every other path still retains everything, as ADR 0181 says.
|
|
14
|
+
|
|
15
|
+
**2. It deletes the provisioning row** (owner decision, this session). The alternative, a tombstone, would keep the name taken forever, since `slug` is UNIQUE. Deleting the row releases all three claims at once: the name, the port (`allocatePort` reads every row) and the address (the next standup UPSERTs it). The row's intents, events and OAuth-manifest attempts cascade with it. The core `audit_log` still records the request, the route writes a `disconnect` event before the delete, and the runner journal records each step.
|
|
16
|
+
|
|
17
|
+
**3. It erases the backups immediately** (owner decision, this session). There is no per-project retention mechanism to hold a "last dump for 30 days", and building one would add a scheduler to operate. The safety net is the typed-name confirm plus a fold that says "cannot be undone". The erase matches the nightly filename exactly (`<db>-YYYY-MM-DDTHHMMSSZ.sql.gz`). The nightly prune's own `<db>-*` glob would let a project named `test` erase `test-nk`'s dumps.
|
|
18
|
+
|
|
19
|
+
**4. The runner does it, in an order that keeps it retryable.** `scripts/gds/provision-disconnect.js`, dispatched from the drain loop's `disconnect` intent (migration `provisioning_016`), runs these steps in order:
|
|
20
|
+
- the teardown legs;
|
|
21
|
+
- dropping the database, then the role that owns it;
|
|
22
|
+
- erasing the dumps;
|
|
23
|
+
- removing the env and state dirs, the per-project checkout (standalone only) and the unix account;
|
|
24
|
+
- removing the catalog, membership and client rows through the new `project.catalog` verb `forget`, under the publish path's two claim rules;
|
|
25
|
+
- **deleting the row last**.
|
|
26
|
+
|
|
27
|
+
A failed erase or catalog step returns a retryable `{ ok:false }`; a teardown that throws ends the run with the intent at `error`, before anything is erased. Either way the row stays, because the row is what lets the runner, or the owner pressing Disconnect again, try again. A dedicated project skips the command erase, since its droplet went whole. The slug is re-validated before any `rm -rf` is built from it.
|
|
28
|
+
|
|
29
|
+
**5. A disconnect supersedes nothing.** Unlike a teardown, which supersedes a stale pending standup, a disconnect is refused while any other run is open, even a pending one. A destructive run must never race a standup that is about to start. A repeat while a disconnect is open is idempotent.
|
|
30
|
+
|
|
31
|
+
**6. The GitHub repository is never touched**, and neither is a GitHub App the owner registered for sign-in. GitHub has no API to delete an App, so the fold tells the owner where to remove it.
|
|
32
|
+
|
|
33
|
+
## Consequences
|
|
34
|
+
|
|
35
|
+
- The manage page's copy changed with the capability. The teardown truth now ends "Taking it offline erases nothing." rather than "Nothing here erases a project.", and the fold points at the new card instead of admitting the capability is missing.
|
|
36
|
+
- The `project.catalog` port has three verbs. `tests/catalog_only_client.mjs` still pins the list.
|
|
37
|
+
- If the runner dies mid-disconnect, the reconcile sweep re-enqueues a *teardown* for a `tearing_down` row, not a disconnect. The project is left offline, with its row, and the owner presses Disconnect again. This is accepted rather than giving the reconcile a memory of intent.
|
|
38
|
+
- The nightly prune's `<db>-*.sql.gz` glob can still reach a longer-named sibling's dumps older than 30 days. It is filed as its own row, not fixed here.
|
|
39
|
+
|
|
40
|
+
Proofs: `tests/provisioning_disconnect.mjs` (queue + route), `tests/provision_disconnect.mjs` (runner order, shapes, failure, guards), `tests/project_catalog_forget.mjs` (claim rules), `tests/hub_disconnect_ui.mjs` + the kit probe (the card).
|
package/docs/adr/README.md
CHANGED
|
@@ -379,3 +379,6 @@ This keeps the decision history honest and traceable.
|
|
|
379
379
|
| 0285 | [**A shared box holds about twelve projects per GB, and memory is the wall** ([task 1003877](https://cloudbongos.com/builders#/task/1003877) · goal 1000106, a spike measured read-only on the live control plane at the owner's call). A project costs ~80 MB warm (70 MB Node + one busy Postgres backend); idle projects look like ~25 MB only because swap holds their cold pages, and swap is not capacity. Fixed base ~750 MB on a control plane. Ceiling per tier `floor((usable − fixed) / 80)`: 15 on today's `s-1vcpu-2gb` (11 live), 39 on `s-2vcpu-4gb`; resize at 12. RAM binds first, Postgres connections (pool default 10 × N vs 97) second; CPU, ports, units, disk and the backup window do not bind. Keep the three shapes, add placement ("standalone until the box is full, then the next box"); a shared slot costs box price ÷ ceiling ($0.62–0.80/mo), `dedicated` 8–40× that.](0285-a-shared-box-holds-about-twelve-projects-per-gb-and-memory-is-the-wall.md) | infra / Cloud Bongos |
|
|
380
380
|
| 0286 | [**A gated project's manifest tells a stranger how to sign in, and nothing else** ([task 1003652](https://cloudbongos.com/builders#/task/1003652) · goal 1000106, from idea 1000845; closes ADR 0192's deferred item). `GET /instance` stays exempt from the member door, but on a gated project a caller with no live session gets `{ schema, name, brand: { identity.productName, project }, auth (incl. idp), admission }` — no modules, ranks, repo coordinates, dev-box flag or branding strings. Members get the full manifest; a failed lookup answers as a stranger; gated responses are `private, no-store` + `Vary: Cookie, Authorization`. Schema id stays `instance-manifest/v1` (a subset; `bongos login` refuses any other). Owner's call: sign-in basics only.](0286-a-gated-project-s-manifest-tells-a-stranger-how-to-sign-in-and-nothing-else.md) | security / Cloud Bongos |
|
|
381
381
|
| 0287 | [**A backlog task that nothing gates promotes itself** ([task 1003932](https://cloudbongos.com/builders#/task/1003932), owner decision 2026-09-15). Every promotion path in migration 163 is an `AFTER UPDATE` trigger at the DEPENDENCY end of an edge, so two states are unreachable by all of them: a task with no gating edge is nobody's dependent and is never evaluated, and an edge added AFTER its dependency shipped missed the only event that would have fired. Measured live: **155 backlog tasks promotable and parked**, 33 of 41 in one goal. Same failure `runCriterionSweep` exists for on the criterion axis (ADR 0183) — a reactive rule cannot drain rows whose event has passed. **Decision: `runPromotionSweep` joins the publish-reconciler tick** and promotes any backlog task with no unmet dependency and no open blocker. The owner chose the widest rule — spikes (excluded by 163 on purpose) and cascade-filed artist reviews included, no carve-outs; promoting only makes a task claimable, it compels nobody. **It ships DISARMED**, reporting what it would promote until `BONGOS_PROMOTION_SWEEP_ARMED=1`, because ~155 rows changing status at once deserves a preview. The **open-blocker guard is re-checked in the sweep's own SQL**: migration 176 folded that clause into `task_is_fully_unblocked` and migration 178 dropped it while claiming to restore "the 163 body" — verified against production, where one task already sits at `ready` with an open blocker (its fix is task 1003934). A bulk promoter must not inherit a single point of failure that has been reverted once. Consequence the owner accepted: **`backlog` stops being a resting state** and `/backlog-review` loses most of its purpose. Rejected: promoting at creation (breaks `cascade.js`'s tested backlog invariant and fixes nothing already parked); scoping the first run to one goal (leaves the defect live in ten others).](0287-a-backlog-task-that-nothing-gates-promotes-itself.md) | lifecycle / work tracking |
|
|
382
|
+
| 0288 | [**The goal wall has a shared floor for paths no module owns** ([task 1003655](https://cloudbongos.com/builders#/task/1003655), from idea 1000816). `MODULE_SCOPE_MAP` answers "who OWNS this path", and `tests/` has no honest answer — 751 flat suites named by feature, covering all ~24 keys, claimed by none. So ADR 0086 §3's directional subset check denied every touch under it, under every goal: `POST /goals/:id/tasks` refused any task that honestly declared its own test file, and the gate only looked passable because callers under-declared their `touches[]`. (The idea's second instance, `.claude/skills/`, was already resolved by task 1002989's methodology key.) **Decision: `SHARED_GLOBS` — globs inside EVERY goal's wall and outside every module's territory, sole entry `tests/`.** The bar is narrow and both halves must hold: no module can honestly own it AND every task legitimately writes it. It is reported as its own `shared_globs` and **never folded into `goal_globs`**, because `check()`'s `unknown_scope` guard tests `goal_globs.length === 0` — folding an always-present list in would give a garbage scope a non-empty territory and slip it past that fail-closed edge. The export is **derived, not trusted**: a raw entry overlapping `PROTECTED_GLOBS` is filtered out, on the same ADR 0086 §7 contract that makes `protected` a derivation (one line here would otherwise widen every wall into the trust core). Sound because a test file is not a shipping surface — nothing imports it, so blast radius on another module is zero — and the claim-collision and protected-path walls are untouched. Rejected: giving `tests/` to `devsecops`/`methodology` (false ownership, and it hands one goal the whole suite while denying everyone else); enumerating per-module test paths (the glob format has no wildcards, so the list rots on the next test file).](0288-the-goal-wall-has-a-shared-floor-for-paths-no-module-owns.md) | lifecycle / work tracking |
|
|
383
|
+
| 0289 | [**An unconsumed contribution is a CI failure, not a boot refusal** ([task 1003819](https://cloudbongos.com/builders#/task/1003819), successor to abandoned task 1002464). A module declares `contributes` and nothing checked that a declared seam is actually WIRED: `verifyPortsSatisfied` asks the OPPOSITE question (every `consumes` has exactly one provider) and `validateManifest` checks only the SHAPE (a closed key set, so a typo'd KEY errors). So `contributes.pollers: ["ghost"]` with no `pollers/ghost.js` boots silent — and correctly so, because `startModulePollers`, `hallWidgetScripts` and `moduleWebSurfaces` each skip a missing artifact the same way they skip a genuine declarative-only entry. Verified on main: 84 declared entries resolve, 5 do not. **Decision: `scripts/gds/seam-wiring-guard.js`, fitness Check 33.** Its `SEAM_CONSUMERS` table pairs each seam with THE FUNCTION THAT ACTUALLY READS IT, so the guard asks the consumer's own question instead of a second convention that could drift; a seam with no reader anywhere (`rooms`) carries `consumer: null` and every declaration of it is reported. The exemption is **per-entry, in the manifest** (`declarativeSeams`), validated in `validateManifest` against the module's own `contributes` — a typo'd exemption is an ERROR, since one that validated clean would exempt nothing while reading as though it exempted something — and a **stale** exemption whose artifact now exists is reported too, so the list cannot lie in either direction. The **wired set prints on the PASS path**: a check that is silent when it passes is no evidence it ran. CI and not boot, deliberately asymmetric with `verifyEnabledPorts`: an unprovided port becomes a runtime throw on a request path in a healthy-looking process, while an unconsumed contribution crashes nothing — a start-up refusal would trade a real outage risk for a cosmetic one. Rejected: a boot refusal; a blanket skip for the seams that allow declarative entries (that is how the gap arose); rebasing branch `task-1002464` (333 lines, 461 commits behind, against a since-rewritten `loader.js` — read its tests, close PR #589).](0289-an-unconsumed-contribution-is-a-ci-failure-not-a-boot-refusal.md) | modules / architecture |
|
|
384
|
+
| 0290 | [**"Disconnect from Bongos entirely" is the owner-initiated exception to retention** ([task 1003249](https://cloudbongos.com/builders#/task/1003249), owner decision 2026-08-24 + this session). ADR 0181 keeps a torn-down project's database, dumps, port and address so "Take it offline" is reversible; this is the separate true delete. Own-scoped + archon override, with the typed name checked SERVER-side (`confirm_slug`); refused while any other run is open (supersedes nothing). The runner tears down, drops the database and its role, erases the dumps by their EXACT nightly filename, removes env/state/checkout/account, forgets the catalog + membership + client rows (new `project.catalog` verb `forget`, both claim rules), and deletes the provisioning row LAST — releasing name, port and address, and keeping every earlier failure retryable. The GitHub repository and any sign-in App are never touched.](0290-disconnect-is-the-owner-initiated-exception-to-address-retention.md) |
|
package/docs/api/openapi.json
CHANGED
|
@@ -9833,7 +9833,7 @@
|
|
|
9833
9833
|
"me"
|
|
9834
9834
|
],
|
|
9835
9835
|
"summary": "GET /me/render",
|
|
9836
|
-
"description": "Per-builder
|
|
9836
|
+
"description": "Per-builder CARD RICHNESS knob (task 1279, a four-rung ladder since task 1002518; shares the behavior_prefs jsonb / migration 119). How much the deterministic session cards spend on the reader: nothing but the guaranteed confirmation line, the plain text card, the in-feed HTML widget, or that widget with its summary already open. The widget is nicer but the agent must relay its full markup verbatim into show_widget (read + re-emit) every session-start / claim / ship stage — a real token cost. No rank cap (pure cost/taste, not a trust gate). The DEFAULT is derived from the builder's interaction profile rather than being a constant, so a builder who never opens this panel still gets a card pitched at the profile they did set. render-prefs.js is the single source of truth for both the ladder and the derivation. rank: any-builder — own profile config; mirrors the skill/sound/wandering routes.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
|
|
9837
9837
|
"x-rank": "any-builder",
|
|
9838
9838
|
"x-source": "modules/builder-settings/routes/settings.js",
|
|
9839
9839
|
"responses": {
|
|
@@ -9862,7 +9862,7 @@
|
|
|
9862
9862
|
"me"
|
|
9863
9863
|
],
|
|
9864
9864
|
"summary": "PATCH /me/render",
|
|
9865
|
-
"description": "PATCH the render knob. Body: { widgets: <bool> | null } where null clears the override (fall back to the
|
|
9865
|
+
"description": "PATCH the render knob. Body: { widgets: <level> | <bool> | null } where null clears the override (fall back to the DERIVED level). The legacy boolean is still accepted and normalised to the rung it always meant, so an older hall build keeps working against this core. Any other key, or a value that is neither a known level nor a boolean nor null → 422. Empty body is a no-op (returns current). Sibling behavior keys (wandering, interaction) are preserved by applyPatch. rank: any-builder — self-service profile edit; same posture as /me/wandering.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
|
|
9866
9866
|
"x-rank": "any-builder",
|
|
9867
9867
|
"x-source": "modules/builder-settings/routes/settings.js",
|
|
9868
9868
|
"requestBody": {
|
|
@@ -12191,7 +12191,7 @@
|
|
|
12191
12191
|
"provisioning"
|
|
12192
12192
|
],
|
|
12193
12193
|
"summary": "POST /provisioning/instances",
|
|
12194
|
-
"description": "POST /provisioning/instances — request a new instance. rank: any authenticated builder (own resource); a `dedicated` (metered) shape
|
|
12194
|
+
"description": "POST /provisioning/instances — request a new instance. rank: any authenticated builder (own resource); a `dedicated` (metered) shape also needs provisioning.fleet.manage (../paid-shape-gate.js), and a full shared box answers 409 box_full up front (../capacity-gate.js). ENQUEUES a 'provision' intent the runner (task P3) stands up. Idempotent on slug for the SAME owner (re-request returns the existing row); a slug owned by ANOTHER builder → 409. Body: { slug (required), target_ref?, hosting_shape? (co-tenant|dedicated), domain?, tier?, onboard_mode? (greenfield|adopt) }. onboard_mode is LOAD-BEARING, not a label (task 1002562): the control-plane runner branches its repo scaffold on it. 'adopt' means target_ref is a repo the owner ALREADY has, so the runner must clone + layer onto its history instead of pushing a fresh tree at `main` (which an existing repo rejects as unrelated history). Absent → 'greenfield', the safe default.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
|
|
12195
12195
|
"x-rank": "any-builder",
|
|
12196
12196
|
"x-source": "modules/provisioning/routes/provisioning.js",
|
|
12197
12197
|
"requestBody": {
|
|
@@ -12341,6 +12341,69 @@
|
|
|
12341
12341
|
]
|
|
12342
12342
|
}
|
|
12343
12343
|
},
|
|
12344
|
+
"/provisioning/instances/{id}/disconnect": {
|
|
12345
|
+
"post": {
|
|
12346
|
+
"operationId": "post_provisioning_instances_id_disconnect",
|
|
12347
|
+
"tags": [
|
|
12348
|
+
"provisioning"
|
|
12349
|
+
],
|
|
12350
|
+
"summary": "POST /provisioning/instances/:id/disconnect",
|
|
12351
|
+
"description": "POST /provisioning/instances/:id/disconnect — rank: any authenticated builder (own resource); an archon may disconnect any project. 404 for anyone else, so another owner's id is never confirmed. The typed name is checked HERE, not only in the page: this erases a database, so a replayed or scripted request must carry the same proof the button does. No status gate — any state may be disconnected; the queue refuses while another run is open (disconnect.enqueueDisconnect).\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
|
|
12352
|
+
"x-rank": "any-builder",
|
|
12353
|
+
"x-source": "modules/provisioning/routes/disconnect.js",
|
|
12354
|
+
"parameters": [
|
|
12355
|
+
{
|
|
12356
|
+
"name": "id",
|
|
12357
|
+
"in": "path",
|
|
12358
|
+
"required": true,
|
|
12359
|
+
"schema": {
|
|
12360
|
+
"type": "string"
|
|
12361
|
+
},
|
|
12362
|
+
"description": "Path parameter `id`."
|
|
12363
|
+
}
|
|
12364
|
+
],
|
|
12365
|
+
"requestBody": {
|
|
12366
|
+
"required": true,
|
|
12367
|
+
"content": {
|
|
12368
|
+
"application/json": {
|
|
12369
|
+
"schema": {
|
|
12370
|
+
"$ref": "#/components/schemas/PostProvisioningInstancesIdDisconnectRequest"
|
|
12371
|
+
}
|
|
12372
|
+
}
|
|
12373
|
+
},
|
|
12374
|
+
"x-validated": true
|
|
12375
|
+
},
|
|
12376
|
+
"responses": {
|
|
12377
|
+
"200": {
|
|
12378
|
+
"description": "Success.",
|
|
12379
|
+
"content": {
|
|
12380
|
+
"application/json": {
|
|
12381
|
+
"schema": {
|
|
12382
|
+
"$ref": "#/components/schemas/PostProvisioningInstancesIdDisconnectResponse"
|
|
12383
|
+
}
|
|
12384
|
+
}
|
|
12385
|
+
}
|
|
12386
|
+
},
|
|
12387
|
+
"400": {
|
|
12388
|
+
"$ref": "#/components/responses/ValidationFailed"
|
|
12389
|
+
},
|
|
12390
|
+
"401": {
|
|
12391
|
+
"$ref": "#/components/responses/Unauthorized"
|
|
12392
|
+
},
|
|
12393
|
+
"403": {
|
|
12394
|
+
"$ref": "#/components/responses/Forbidden"
|
|
12395
|
+
},
|
|
12396
|
+
"404": {
|
|
12397
|
+
"$ref": "#/components/responses/NotFound"
|
|
12398
|
+
}
|
|
12399
|
+
},
|
|
12400
|
+
"security": [
|
|
12401
|
+
{
|
|
12402
|
+
"builderSession": []
|
|
12403
|
+
}
|
|
12404
|
+
]
|
|
12405
|
+
}
|
|
12406
|
+
},
|
|
12344
12407
|
"/provisioning/instances/{id}/domain": {
|
|
12345
12408
|
"post": {
|
|
12346
12409
|
"operationId": "post_provisioning_instances_id_domain",
|
|
@@ -14620,7 +14683,7 @@
|
|
|
14620
14683
|
"sso"
|
|
14621
14684
|
],
|
|
14622
14685
|
"summary": "POST /sso/membership/check-in",
|
|
14623
|
-
"description": "POST /sso/membership/check-in — a federated instance reports, on every sign-in, that a builder builds on its project (ADR 0141 §3). Server-to-server, authed by client_id + client_secret (same constant-time check as /sso/token). Refreshes an EXISTING `platform_identity_project_memberships` row's COARSE membership_kind. The client_id is taken from the AUTHENTICATED client — the caller cannot report membership for a project it doesn't hold the secret for. WHAT THIS CALL MAY NOT DO, AND WHY (ADR 0205, security report 1000027). Client credentials say WHICH PROJECT is calling; they say nothing about WHO the github_id in the body is. So this route may neither create a membership nor rewrite an account's identity: it updates a row the HUB already witnessed a sign-in for (signin_verified_at, stamped only at /sso/token and /sso/device/poll), and it creates the account shell only when absent. Before this, a client holding only its own secret could overwrite anyone's public github_login + avatar_url AND mint the membership that /sso/activity/rollup's membership_required precheck (the ADR 0171 D7 anti-impersonation gate) trusts — so the gate was satisfiable by the very call it was meant to gate. Nothing legitimate is lost: check-in only ever fires after a hub-authenticated sign-in (src/bongos/auth-idp.js), which stamps the witness first. THE FIELD IS `membership_kind
|
|
14686
|
+
"description": "POST /sso/membership/check-in — a federated instance reports, on every sign-in, that a builder builds on its project (ADR 0141 §3). Server-to-server, authed by client_id + client_secret (same constant-time check as /sso/token). Refreshes an EXISTING `platform_identity_project_memberships` row's COARSE membership_kind. The client_id is taken from the AUTHENTICATED client — the caller cannot report membership for a project it doesn't hold the secret for. WHAT THIS CALL MAY NOT DO, AND WHY (ADR 0205, security report 1000027). Client credentials say WHICH PROJECT is calling; they say nothing about WHO the github_id in the body is. So this route may neither create a membership nor rewrite an account's identity: it updates a row the HUB already witnessed a sign-in for (signin_verified_at, stamped only at /sso/token and /sso/device/poll), and it creates the account shell only when absent. Before this, a client holding only its own secret could overwrite anyone's public github_login + avatar_url AND mint the membership that /sso/activity/rollup's membership_required precheck (the ADR 0171 D7 anti-impersonation gate) trusts — so the gate was satisfiable by the very call it was meant to gate. Nothing legitimate is lost: check-in only ever fires after a hub-authenticated sign-in (src/bongos/auth-idp.js), which stamps the witness first. THE FIELD IS `membership_kind` — AND ONLY THAT (task 1003090 renamed it, task 1003092 closed the window; ADR 0177 §\"the fifth use\"). ADR 0174 reserves `role` for the three crafts, and the old value 'builder' was a craft name meaning \"non-owner member\" — so both the field and that value were renamed. Because the callers are OTHER instances on their own upgrade schedule, the hub kept accepting the legacy field for a cross-version window; that window closed once every federated instance that can check in was past core 1.19.241. The schema below is strict, so a legacy-only body is refused with 400 and writes nothing — tests/sso_checkin_legacy_field_gone.mjs is the test of its absence. rank: public — authenticated by client_id + client_secret, not a builder rank.\n\n**Rank:** `public` — No authentication — any caller.",
|
|
14624
14687
|
"x-rank": "public",
|
|
14625
14688
|
"x-source": "modules/platform-identity/routes/sso.js",
|
|
14626
14689
|
"requestBody": {
|
|
@@ -22169,6 +22232,38 @@
|
|
|
22169
22232
|
"project"
|
|
22170
22233
|
]
|
|
22171
22234
|
},
|
|
22235
|
+
"PostProvisioningInstancesIdDisconnectRequest": {
|
|
22236
|
+
"type": "object",
|
|
22237
|
+
"properties": {
|
|
22238
|
+
"confirm_slug": {
|
|
22239
|
+
"type": "string",
|
|
22240
|
+
"maxLength": 40
|
|
22241
|
+
}
|
|
22242
|
+
},
|
|
22243
|
+
"required": [
|
|
22244
|
+
"confirm_slug"
|
|
22245
|
+
],
|
|
22246
|
+
"additionalProperties": false
|
|
22247
|
+
},
|
|
22248
|
+
"PostProvisioningInstancesIdDisconnectResponse": {
|
|
22249
|
+
"type": "object",
|
|
22250
|
+
"properties": {
|
|
22251
|
+
"ok": {
|
|
22252
|
+
"type": "boolean"
|
|
22253
|
+
},
|
|
22254
|
+
"queued": {},
|
|
22255
|
+
"action": {},
|
|
22256
|
+
"status": {},
|
|
22257
|
+
"message": {}
|
|
22258
|
+
},
|
|
22259
|
+
"required": [
|
|
22260
|
+
"ok",
|
|
22261
|
+
"queued",
|
|
22262
|
+
"action",
|
|
22263
|
+
"status",
|
|
22264
|
+
"message"
|
|
22265
|
+
]
|
|
22266
|
+
},
|
|
22172
22267
|
"PostProvisioningInstancesIdDomainRequest": {
|
|
22173
22268
|
"type": "object",
|
|
22174
22269
|
"properties": {
|
|
@@ -22823,10 +22918,6 @@
|
|
|
22823
22918
|
"membership_kind": {
|
|
22824
22919
|
"type": "string",
|
|
22825
22920
|
"maxLength": 16
|
|
22826
|
-
},
|
|
22827
|
-
"role": {
|
|
22828
|
-
"type": "string",
|
|
22829
|
-
"maxLength": 16
|
|
22830
22921
|
}
|
|
22831
22922
|
},
|
|
22832
22923
|
"required": [
|
|
@@ -23688,9 +23779,9 @@
|
|
|
23688
23779
|
"description": "A required dependency/feature is not configured or is temporarily down."
|
|
23689
23780
|
}
|
|
23690
23781
|
},
|
|
23691
|
-
"x-endpoint-count":
|
|
23692
|
-
"x-schema-count":
|
|
23782
|
+
"x-endpoint-count": 374,
|
|
23783
|
+
"x-schema-count": 414,
|
|
23693
23784
|
"x-undocumented-bodies": 9,
|
|
23694
|
-
"x-response-schemas":
|
|
23785
|
+
"x-response-schemas": 281,
|
|
23695
23786
|
"x-generated-by": "scripts/gds/gen-api-docs.js"
|
|
23696
23787
|
}
|
package/docs/api-reference.md
CHANGED
|
@@ -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.
|
|
5
|
+
> **Generated from the live route files** — the route file is authoritative. 374 endpoints across 61 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/0016-trust-boundary-server-enforced-permissions.md)): `public` < `any-builder` < `metic+archon` < `archon`.
|
|
@@ -401,7 +401,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/0016-trust
|
|
|
401
401
|
| GET | `/api/bongos/me/profile-nudge` | `any-builder` | — | The "Complete your profile" nudge dismissal (task 1002802 / P06). |
|
|
402
402
|
| PATCH | `/api/bongos/me/profile-nudge` | `any-builder` | _undocumented_ | PATCH the dismissal. |
|
|
403
403
|
| GET | `/api/bongos/me/recent-ships` | `any-builder` | — | Tasks this builder has shipped, most recent first. |
|
|
404
|
-
| GET | `/api/bongos/me/render` | `any-builder` | — | Per-builder
|
|
404
|
+
| GET | `/api/bongos/me/render` | `any-builder` | — | Per-builder CARD RICHNESS knob (task 1279, a four-rung ladder since task 1002518; shares the behavior_prefs jsonb / migration 119). |
|
|
405
405
|
| PATCH | `/api/bongos/me/render` | `any-builder` | _undocumented_ | PATCH the render knob. |
|
|
406
406
|
| GET | `/api/bongos/me/sessions` | `any-builder` | — | GET /api/bongos/me/sessions — every live session on the CALLER's account. |
|
|
407
407
|
| POST | `/api/bongos/me/sessions/revoke` | `any-builder` | `session_id` | POST /api/bongos/me/sessions/revoke — kill ONE of the caller's own sessions. |
|
|
@@ -488,7 +488,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/0016-trust
|
|
|
488
488
|
| GET | `/api/bongos/projects/featured` | `public` | — | the public map of projects; no auth by design. |
|
|
489
489
|
| POST | `/api/bongos/projects/invite` | `metic+archon` | `client_id`, `github_login` | own-or-metic in effect: invite a builder into a project (ADR 0141 §5, task #1002115), called by a `project.curate` holder OR the project'… |
|
|
490
490
|
|
|
491
|
-
## `provisioning` (
|
|
491
|
+
## `provisioning` (23)
|
|
492
492
|
|
|
493
493
|
| Method | Path | Rank | Body | Description |
|
|
494
494
|
|---|---|---|---|---|
|
|
@@ -501,6 +501,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/0016-trust
|
|
|
501
501
|
| POST | `/api/bongos/provisioning/instances` | `any-builder` | `slug`, `target_ref`, `hosting_shape`, `domain`, `no_address`, `tier`, `onboard_mode`, `type`, `description`, `detail`, `modules` | POST /provisioning/instances — request a new instance. |
|
|
502
502
|
| GET | `/api/bongos/provisioning/instances/:id` | `any-builder` | — | GET /provisioning/instances/:id — read one instance (the manage page's read). |
|
|
503
503
|
| PATCH | `/api/bongos/provisioning/instances/:id/detail` | `any-builder` | `description`, `detail`, `modules`, `type` | ── The optional project detail (task 1002334, goal 1000046) ────────────── PATCH /provisioning/instances/:id/detail — answer (or re-answe… |
|
|
504
|
+
| POST | `/api/bongos/provisioning/instances/:id/disconnect` | `any-builder` | `confirm_slug` | POST /provisioning/instances/:id/disconnect — rank: any authenticated builder (own resource); an archon may disconnect any project. |
|
|
504
505
|
| 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… |
|
|
505
506
|
| POST | `/api/bongos/provisioning/instances/:id/force-teardown` | `metic+archon` | — | POST /provisioning/instances/:id/force-teardown — Archon tears down ANY instance. |
|
|
506
507
|
| 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… |
|
|
@@ -593,7 +594,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/0016-trust
|
|
|
593
594
|
| GET | `/api/bongos/sso/authorize` | `public` | — | GET /sso/authorize?client_id=&redirect_uri=&state= — the browser authorize leg. |
|
|
594
595
|
| POST | `/api/bongos/sso/device/poll` | `public` | `device_code` | POST /sso/device/poll — poll the hub device flow. |
|
|
595
596
|
| POST | `/api/bongos/sso/device/start` | `public` | `origin` | POST /sso/device/start — begin CLI federation. |
|
|
596
|
-
| POST | `/api/bongos/sso/membership/check-in` | `public` | `client_id`, `client_secret`, `github_id`, `github_login`, `display_name`, `avatar_url`, `membership_kind
|
|
597
|
+
| POST | `/api/bongos/sso/membership/check-in` | `public` | `client_id`, `client_secret`, `github_id`, `github_login`, `display_name`, `avatar_url`, `membership_kind` | POST /sso/membership/check-in — a federated instance reports, on every sign-in, that a builder builds on its project (ADR 0141 §3). |
|
|
597
598
|
| GET | `/api/bongos/sso/pubkey` | `public` | — | GET /sso/pubkey — the hub's Ed25519 assertion-verification key (public). |
|
|
598
599
|
| POST | `/api/bongos/sso/token` | `public` | `client_id`, `client_secret`, `code`, `redirect_uri` | POST /sso/token — server-to-server code redemption. |
|
|
599
600
|
|