@bongos/core 1.19.260 → 1.19.262

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.260",
6
- "core_contract": "1.19.260",
7
- "source_commit": "ad8f5abf4fd783bbabedd69168e2beef68f5a4cd",
5
+ "core_version": "1.19.262",
6
+ "core_contract": "1.19.262",
7
+ "source_commit": "5c66bd4246a027582b292909499f6d31009157c6",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-08-19T18:45:10.437Z",
9
+ "built_at": "2026-08-19T20:21:59.469Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
- "docs_redacted": 317,
12
+ "docs_redacted": 318,
13
13
  "agent_docs_stubbed": 22,
14
- "functional_verbatim": 1360,
14
+ "functional_verbatim": 1362,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 1699,
20
- "tree_sha256": "9ddca0b6deb2305538528d69700ca5be24d62e9531ecb14df0313e11c5564ebd",
19
+ "file_count": 1702,
20
+ "tree_sha256": "6c446d9f84793d24dace5316025f4a09517fd85c5260884cb2885e445dcfb2d1",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/blocker-review/SKILL.md",
@@ -382,7 +382,7 @@
382
382
  {
383
383
  "path": "config/branding.neutral.json",
384
384
  "mode": "0000644",
385
- "sha256": "c3d785e319252965f8b5f338908d1dd66bd56985dafacb5d2ab8e5095d2515b3"
385
+ "sha256": "d1d2622e002edaed8a98deb8b69ff5a77c6eeab7993e3b0e7f1513f32ff027c6"
386
386
  },
387
387
  {
388
388
  "path": "config/design-tokens.neutral.json",
@@ -1384,10 +1384,15 @@
1384
1384
  "mode": "0000644",
1385
1385
  "sha256": "62de8180208c3945c7a040b2b2799dceafd59b1c5a117aab7db56a57eb538e44"
1386
1386
  },
1387
+ {
1388
+ "path": "docs/adr/0181-teardown-state-gate-and-address-retention.md",
1389
+ "mode": "0000644",
1390
+ "sha256": "26f39ae01ea1768dfbd61789752356ae30364b465117b1e7ddee94fabc7d6bbf"
1391
+ },
1387
1392
  {
1388
1393
  "path": "docs/adr/README.md",
1389
1394
  "mode": "0000644",
1390
- "sha256": "798471e95b27664e775d12dbf008fc1aa12edff0b115942a3632ecb0f2da24b5"
1395
+ "sha256": "01396708c202d9c266bc2a3cf3ab4052fd2f3823e6957e7669370fdad2fdb7a9"
1391
1396
  },
1392
1397
  {
1393
1398
  "path": "docs/api-reference.md",
@@ -1397,7 +1402,7 @@
1397
1402
  {
1398
1403
  "path": "docs/api/openapi.json",
1399
1404
  "mode": "0000644",
1400
- "sha256": "2fc5bd12b302ff6ef0bc4e0012282bcef8616942c23e9c5f0fbe7c9e6a71d6a9"
1405
+ "sha256": "979d2d19c20a8d4eb4fb7f4715ef307c40d64b99038d0d816702a50bd211fcc9"
1401
1406
  },
1402
1407
  {
1403
1408
  "path": "docs/architecture.md",
@@ -1422,12 +1427,12 @@
1422
1427
  {
1423
1428
  "path": "docs/copy-inventory.md",
1424
1429
  "mode": "0000644",
1425
- "sha256": "6119beb46619399f1739efd9b5da3dbff136f7ac5eada4119c25c8429e751da1"
1430
+ "sha256": "8eb317c49266b41d6b64f73331b58eea0ec3b647eb5fb5bff202c2458ee555e9"
1426
1431
  },
1427
1432
  {
1428
1433
  "path": "docs/copy-registry.json",
1429
1434
  "mode": "0000644",
1430
- "sha256": "5ee4809afc99ee9e384a45c0a63187695e8db0ad87ee1ddf94fc125b30e60352"
1435
+ "sha256": "9d656a81eda850d1c7c5f7f358395c7319d4ac3c42afa995cd74e8dcc4bb7401"
1431
1436
  },
1432
1437
  {
1433
1438
  "path": "docs/design-contract.md",
@@ -4147,7 +4152,7 @@
4147
4152
  {
4148
4153
  "path": "modules/provisioning/provisioning.js",
4149
4154
  "mode": "0000644",
4150
- "sha256": "b9d4ee116eb4b213b084408868f727a4587876faa5fae78f4d2f64cb2fe12231"
4155
+ "sha256": "862fa78c7272a28cdc644debfd8ec364b80b635e8b9a6f41afb76b93312f27dd"
4151
4156
  },
4152
4157
  {
4153
4158
  "path": "modules/provisioning/rate-limit.js",
@@ -4157,7 +4162,7 @@
4157
4162
  {
4158
4163
  "path": "modules/provisioning/routes/provisioning.js",
4159
4164
  "mode": "0000644",
4160
- "sha256": "a4419bdd0693d0e1168051bd640721b615bf400b25f26478d786b9aac908ab5e"
4165
+ "sha256": "8bd43d1101599f5ff838641a9cbaa9bfd208eea4a3c7876f45ac034a8bf3b5d0"
4161
4166
  },
4162
4167
  {
4163
4168
  "path": "modules/provisioning/tests/liveness.mjs",
@@ -4457,12 +4462,12 @@
4457
4462
  {
4458
4463
  "path": "package-lock.json",
4459
4464
  "mode": "0000644",
4460
- "sha256": "8042f25b6c9f89872fd36b1cb4083940c6c716ee808d5aab2c707e11b3758ffc"
4465
+ "sha256": "687fe7e65dc80ea4688cbb7e530324b70b97e512f3b786d0264c810e47629388"
4461
4466
  },
4462
4467
  {
4463
4468
  "path": "package.json",
4464
4469
  "mode": "0000644",
4465
- "sha256": "f6c76f48f6069150d06693aaca3383c690c0186fe5eaa7817f304b07d4c5239e"
4470
+ "sha256": "bdb5fabfd35c8e1402b7a68bfd9033ab7bb8072e83f3cd181a835aed180eab8a"
4466
4471
  },
4467
4472
  {
4468
4473
  "path": "public-docs/index.html",
@@ -4702,7 +4707,7 @@
4702
4707
  {
4703
4708
  "path": "public-landing/assets/cosmos.css",
4704
4709
  "mode": "0000644",
4705
- "sha256": "bb8a74eaadebf0adf8e59dd03cab87f9c3cbd7faf21d1d6a5d47b460c032c28b"
4710
+ "sha256": "0c9b05cbe6f92338533ff4a303212919e98fe0f409f3836b8b31395fe16e82fc"
4706
4711
  },
4707
4712
  {
4708
4713
  "path": "public-landing/assets/verro-medallion.png",
@@ -4722,7 +4727,7 @@
4722
4727
  {
4723
4728
  "path": "public-landing/projects.html",
4724
4729
  "mode": "0000644",
4725
- "sha256": "88f416526efee1a722fdc082a1f92f26de1d4f8850db4054be79f46d50a1582a"
4730
+ "sha256": "5f2ea9faa0bc5a37e0250a536bcfc5c1586b26d171f9b59bd12b572ac37b7270"
4726
4731
  },
4727
4732
  {
4728
4733
  "path": "public-landing/world/assets/clouds/cloud-island-large.png",
@@ -4992,7 +4997,7 @@
4992
4997
  {
4993
4998
  "path": "scripts/gds/copy-inventory.js",
4994
4999
  "mode": "0000644",
4995
- "sha256": "8eeeea6fb29101dbd4dc6ce874a8bc87dd1945434f5b6c13a04e7312cc449a4b"
5000
+ "sha256": "24e875eaace524598d18b5bf4a931700296f580404b79166da1e7c1d2d906de3"
4996
5001
  },
4997
5002
  {
4998
5003
  "path": "scripts/gds/coreB-carve-proof.sh",
@@ -5397,7 +5402,7 @@
5397
5402
  {
5398
5403
  "path": "scripts/gds/provision.js",
5399
5404
  "mode": "0000644",
5400
- "sha256": "0a1cdb35e9357e07307d58de8099b2fc3c46377cedafb4d0f5784e2a9a51a639"
5405
+ "sha256": "1b1e4ca721a9052206265736712ea17e96b2b724c2926faba49839def36a1818"
5401
5406
  },
5402
5407
  {
5403
5408
  "path": "scripts/gds/publish-credential-check.js",
@@ -6087,7 +6092,7 @@
6087
6092
  {
6088
6093
  "path": "src/branding.js",
6089
6094
  "mode": "0000644",
6090
- "sha256": "a374d2625e09050948c7f619af1937e9c0b315fe7c25691d6b1667147386a04e"
6095
+ "sha256": "04c7bd3146abc16cb51d81fa6550520cdd169cd527fbe2de13dffd2c6fcc8ef5"
6091
6096
  },
6092
6097
  {
6093
6098
  "path": "src/build-info.js",
@@ -6107,7 +6112,7 @@
6107
6112
  {
6108
6113
  "path": "src/module-api.js",
6109
6114
  "mode": "0000644",
6110
- "sha256": "d45b74aa005129b58c508dd6d568766a225a2b1c8b75295e07d74e5405d62215"
6115
+ "sha256": "1c68d692ed6e52924b04af09f9d816514b8b1ccd795984960f1c9cddefb6eba5"
6111
6116
  },
6112
6117
  {
6113
6118
  "path": "src/module-loader/catalog.js",
@@ -7852,7 +7857,7 @@
7852
7857
  {
7853
7858
  "path": "tests/projects_hub.mjs",
7854
7859
  "mode": "0000644",
7855
- "sha256": "a5ddb284f4b5cb46e19b90d07dbb06ee42de9e4911d76b882db23a6dab63f8b5"
7860
+ "sha256": "211aa868b86a2b663f5525c3d2ddfa406498d608363d28c0786219d3c0b6e249"
7856
7861
  },
7857
7862
  {
7858
7863
  "path": "tests/provision.mjs",
@@ -7884,6 +7889,11 @@
7884
7889
  "mode": "0000644",
7885
7890
  "sha256": "a525fdcfa2fde8edd2be3198ca7213b9b6bc43a12f748a718a570b7f993972b6"
7886
7891
  },
7892
+ {
7893
+ "path": "tests/provisioning_settings_env.mjs",
7894
+ "mode": "0000644",
7895
+ "sha256": "ee73d6af28f68bc6c197aae847663e9db2afb45e5073e2886a28c2a15925ad53"
7896
+ },
7887
7897
  {
7888
7898
  "path": "tests/provisioning_slug_available.mjs",
7889
7899
  "mode": "0000644",
@@ -7894,6 +7904,11 @@
7894
7904
  "mode": "0000644",
7895
7905
  "sha256": "68d328b69cd8b981e8454d679c00a757abeeafb4e083249489eec744cdd4898d"
7896
7906
  },
7907
+ {
7908
+ "path": "tests/provisioning_teardown_states.mjs",
7909
+ "mode": "0000644",
7910
+ "sha256": "1b5decd026e6487c9477b87f533852dd93821b6688c3c76e694041b3918ee420"
7911
+ },
7897
7912
  {
7898
7913
  "path": "tests/pruned_lockfile_integrity.mjs",
7899
7914
  "mode": "0000644",
@@ -7902,7 +7917,7 @@
7902
7917
  {
7903
7918
  "path": "tests/public_landing_projects.mjs",
7904
7919
  "mode": "0000644",
7905
- "sha256": "eaadefd338bd50a8310c699cf0acc4bceb34ef601e8ed901cb301bfcdf6bd4c3"
7920
+ "sha256": "9dab9ff400b0bcdfbf97e9fd365e3682457b76abfd9a8a70029cb944265b81f8"
7906
7921
  },
7907
7922
  {
7908
7923
  "path": "tests/public_mirror_export.mjs",
@@ -3,7 +3,10 @@
3
3
  "identity": {
4
4
  "productName": "Cloud Bongos",
5
5
  "worldName": "Cloud Bongos",
6
- "company": { "name": "", "site": "" }
6
+ "company": {
7
+ "name": "",
8
+ "site": ""
9
+ }
7
10
  },
8
11
  "llmProjectName": "this project",
9
12
  "domains": {
@@ -23,33 +26,70 @@
23
26
  "_note": "Cross-site identity (ADR 0141). idp=false → this instance owns its auth outright (its own GitHub app; the default + the self-hosted path). A HOSTED instance sets idp=true and gets its hub origin + client credentials from env (<PREFIX>_HUB_ORIGIN / _HUB_CLIENT_ID / _HUB_CLIENT_SECRET, written by provisioning) so web + CLI sign-in federate through the Cloud Bongos hub with no GitHub app of its own. Identity is federated; authority stays local.",
24
27
  "idp": false
25
28
  },
26
- "repo": { "owner": "", "name": "" },
27
- "db": { "database": null },
29
+ "project": {
30
+ "_note": "Per-project policy the PLATFORM decides and this instance OBEYS (task 1003139, goal 1000058). Stored on the platform's provisioning_instances.settings; written here as <PREFIX>_PLATFORM_VISIBILITY / <PREFIX>_JOINABILITY by the provisioning runner, which re-writes web.env and restarts the service when the owner changes one. The defaults are TODAY's behavior, so an instance that never receives a push — a self-hosted one, or one provisioned before this channel — keeps behaving exactly as it does now. platformVisibility: public | gated. joinability: open | apply | invite_only.",
31
+ "platformVisibility": "public",
32
+ "joinability": "apply"
33
+ },
34
+ "repo": {
35
+ "owner": "",
36
+ "name": ""
37
+ },
38
+ "db": {
39
+ "database": null
40
+ },
28
41
  "configDir": "cloudbongos",
29
42
  "firstAdmin": "",
30
- "currency": { "label": "credits", "symbol": "" },
31
- "reward": { "marginPct": 20, "usdPeg": 1, "mode": "cost-plus-and-estimate" },
32
- "models": { "main": "opus", "subagentDefault": "opus", "subagentRoutine": "haiku" },
43
+ "currency": {
44
+ "label": "credits",
45
+ "symbol": ""
46
+ },
47
+ "reward": {
48
+ "marginPct": 20,
49
+ "usdPeg": 1,
50
+ "mode": "cost-plus-and-estimate"
51
+ },
52
+ "models": {
53
+ "main": "opus",
54
+ "subagentDefault": "opus",
55
+ "subagentRoutine": "haiku"
56
+ },
33
57
  "theme": {
34
58
  "palette": "",
35
59
  "mode": "cloud-bongos",
36
60
  "ui": {
37
61
  "colors": {
38
- "bg": "#f9d9a8", "bgCard": "#fffaf2", "bgDeep": "#f6d8ae", "bgFrame": "#f0c489",
39
- "ink": "#2b170e", "inkSoft": "#6d4a33", "inkFaint": "#785a41",
40
- "rule": "#e8a463", "ruleSoft": "#f6d8ae",
41
- "accent": "#ce4806", "accentDeep": "#9e3406", "sea": "#0f7a96", "terracotta": "#855b3d"
62
+ "bg": "#f9d9a8",
63
+ "bgCard": "#fffaf2",
64
+ "bgDeep": "#f6d8ae",
65
+ "bgFrame": "#f0c489",
66
+ "ink": "#2b170e",
67
+ "inkSoft": "#6d4a33",
68
+ "inkFaint": "#785a41",
69
+ "rule": "#e8a463",
70
+ "ruleSoft": "#f6d8ae",
71
+ "accent": "#ce4806",
72
+ "accentDeep": "#9e3406",
73
+ "sea": "#0f7a96",
74
+ "terracotta": "#855b3d"
42
75
  },
43
76
  "fonts": {
44
77
  "display": "\"Baloo 2\", \"Quicksand\", system-ui, sans-serif",
45
78
  "body": "\"Quicksand\", system-ui, -apple-system, \"Segoe UI\", sans-serif",
46
- "links": ["https://fonts.googleapis.com/css2?family=Baloo+2:wght@500;600;700&family=Quicksand:wght@400;500;600;700&family=Fira+Code:wght@400;500&display=swap"]
79
+ "links": [
80
+ "https://fonts.googleapis.com/css2?family=Baloo+2:wght@500;600;700&family=Quicksand:wght@400;500;600;700&family=Fira+Code:wght@400;500&display=swap"
81
+ ]
47
82
  },
48
83
  "favicon": "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><circle cx='12' cy='17' r='8' fill='%23ce4806'/><circle cx='22' cy='17' r='6' fill='%23855b3d'/><circle cx='12' cy='17' r='3.5' fill='%23fffaf2'/><circle cx='22' cy='17' r='2.5' fill='%23fffaf2'/></svg>",
49
84
  "backdrop": "#f9d9a8"
50
85
  }
51
86
  },
52
- "rankLabels": { "xenos": "Newcomer", "thetes": "Contributor", "metic": "Trusted", "archon": "Owner" },
87
+ "rankLabels": {
88
+ "xenos": "Newcomer",
89
+ "thetes": "Contributor",
90
+ "metic": "Trusted",
91
+ "archon": "Owner"
92
+ },
53
93
  "copy": {
54
94
  "tagline": "Building Digitally, as simple as Bongos.",
55
95
  "hallName": "Builders' Hall",
@@ -0,0 +1,63 @@
1
+ # ADR 0181 — Teardown accepts every state but `torn_down`, and deliberately keeps the address
2
+
3
+ - **Status:** Accepted
4
+ - **Date:** 2026-08-19
5
+ - **Task:** [task 1003042](https://cloudbongos.com/builders#/task/1003042) (BONGOS-V1, goal 1000058 — *Non-cloudbongos project lifecycle*)
6
+ - **Deciders:** jaxri (Archon), Claude
7
+ - **Supersedes nothing. Bounds:** [task 1002897](https://cloudbongos.com/builders#/task/1002897) (address reclaim) owns the half this ADR deliberately does not do.
8
+ - **Related:** [ADR 0111](<redacted>.md) (the provisioning module + intent/runner split), [ADR 0157](<redacted>.md) (permission floors)
9
+
10
+ ---
11
+
12
+ ## Context
13
+
14
+ Walk finding **N5** (`docs/audits/project-creation-gap-list.md`): an instance at `status='error'` could never be removed. The owner's `POST /provisioning/instances/:id/teardown` answered 409 `not_teardownable`, and the archon `force-teardown` — which exists precisely as the escape hatch — answered the **same** refusal, because both read one shared constant:
15
+
16
+ ```js
17
+ const TEARDOWN_FROM = new Set(['provisioning', 'active', 'tearing_down']);
18
+ ```
19
+
20
+ `requested` and `error` were absent. There was therefore **no supported path, for any role**, to remove such a row; the only remedy was direct DB surgery. Errored rows accumulated permanently — instance 15 (`swag`) sat in its owner's list from 2026-08-07.
21
+
22
+ The whitelist's comment justified itself as "a requested/torn_down/error instance has nothing running to tear down via the happy path." That premise is **false for `error`**. A provision that fails mid-run lands at `error` *after* the runner may already have created the database, allocated the port, written the systemd unit, written the Caddy site block, and UPSERTed the DNS A record. An errored row is frequently a *partial standup*, which is exactly the thing most in need of reclaiming.
23
+
24
+ ## Decision 1 — the gate is derived, not hand-kept
25
+
26
+ `TEARDOWN_FROM` is now every state except the one where there is provably nothing left:
27
+
28
+ ```js
29
+ const TEARDOWN_FROM = new Set(INSTANCE_STATES.filter((s) => s !== 'torn_down'));
30
+ ```
31
+
32
+ Deriving it is the load-bearing part. The bug was not a typo — it was a **hand-maintained allow-list that silently omitted states**, with no test asserting completeness. Derivation inverts the default: a seventh lifecycle state added tomorrow is teardownable unless someone deliberately excludes it. For a failure mode whose signature is "a row with no exit", the safe default is *an exit exists*.
33
+
34
+ The runner needed no change. Every step in `teardownInstance` (`scripts/gds/provision.js`) is individually `allowFail`, so it already reclaimed whatever a partial standup happened to hold and settled the row at `torn_down` either way. **Only the web-tier guard refused.**
35
+
36
+ `torn_down` still refuses, and that refusal is now one shared helper used by both routes. They had drifted: the owner route carried a plain sentence, while force-teardown used the positional `res.fail(code, status, details)` form with no message — and `envelope()`'s `message || code` fallback then put the bare string `not_teardownable` on the wire as the human-facing text, violating the plain-sentence rule (§5.1). One helper, one wording, no second copy to drift.
37
+
38
+ **"Force" means across ownership, not past the state machine.** `force-teardown` skips the owner check; it deliberately shares the state gate. (Its permission `provisioning.fleet.manage` has a **metic** floor per ADR 0157, despite older route comments saying archon.)
39
+
40
+ ## Decision 2 — the port and the domain are deliberately NOT released
41
+
42
+ Task 1003042's description listed "releases the DB row, port, DNS record". We release the row's *state* and stop there. **The `port` and `domain` columns are kept.**
43
+
44
+ This is not an omission — releasing the port alone would be **actively worse than the status quo**:
45
+
46
+ - Teardown has no DNS leg and no Caddy leg. The per-instance `<slug>.caddy` snippet is written at standup (`provision.js`, step 6) and **survives teardown**; the Cloudflare hook is UPSERT-only and is never called on the teardown path. Verified live on 2026-08-19: `walk-beacon.cloudbongos.com` and `qa-private-repo.cloudbongos.com` were both `torn_down` since 2026-08-13 and both still resolved.
47
+ - That surviving Caddy block reverse-proxies the hostname to a **port number**, not to an identity.
48
+ - `allocatePort` selects `WHERE port IS NOT NULL` with **no status filter**. Null the port and it returns to the pool.
49
+ - So a later, unrelated instance handed the same port would begin **answering at the torn-down project's still-live address** — one owner's project served under another owner's hostname.
50
+
51
+ Today the same stale block yields a harmless 502, because the port stays held. Holding it is what keeps the address inert.
52
+
53
+ The port and the address are therefore **one coupled decision**, and [task 1002897](https://cloudbongos.com/builders#/task/1002897) owns it: give the runner a DNS DELETE counterpart, remove the stale Caddy block, and decide with the owner whether a torn-down address 404s or serves an honest "this project ended" page. Retention also happens to be *correct* for the revive path — `PROVISION_ENQUEUE_FROM` includes `torn_down`, and a revive re-uses exactly this hostname, so clearing `domain` would break bringing a project back at its own address.
54
+
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
+
57
+ ## Consequences
58
+
59
+ - 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.
60
+ - Instances 15 (`swag`) and 20 (`walk-lantern`) become removable once the live pin carries this change.
61
+ - `tests/provisioning_teardown_states.mjs` pins both decisions and mounts the **real** error envelope, so the bare-code regression is caught on the wire rather than in the call shape.
62
+ - **A torn-down project still holds its address and its port.** That is the known, deliberate remaining gap; it is not fixed here, and the copy no longer claims otherwise. Task 1002897 closes it.
63
+ - Teardown still leaves the database, the backup dumps, and the repo checkout in place — unchanged policy, data is precious.
@@ -254,5 +254,6 @@ This keeps the decision history honest and traceable.
254
254
  | 0178 | [**The copy desk: a flag carries a judgement, and no column here could ever hold copy** ([task 1003113](https://cloudbongos.com/builders#/task/1003113), R02 of goal 1000074). [ADR 0081](<redacted>.md) made the repo the source of truth for design TOKENS; user-facing TEXT had no equivalent. R01 built the inventory (`copy-inventory.js` → `docs/copy-registry.json`), which answers *what does the product say* but cannot answer *what is wrong with it* — a judgement a person must record. This is that record plus the artist's queue. A new default-on module keyed **`copy-desk`, not `copy`**, for a concrete reason: the branding contract already owns a `copy` block, so the bare word reds the `core carries no module code` fitness check on the English word in `src/branding.js` — and the editorial "copy desk" is the more accurate name anyway. **The reason is required in the route AND in a CHECK constraint**: the route gives a person a usable error, the constraint means no future write path can create the reasonless flag the queue exists to not contain (a pointer with no judgement is *worse* than no flag — it still costs the artist a look). Closing requires a note by the same symmetry, since an unexplained close is indistinguishable from clearing a queue to move a number. The load-bearing decision is **NO LIVE CMS enforced by the schema's SHAPE**: `copy_desk_flags` has no column that could hold replacement text and must never gain one, because that non-goal does not erode via an "add a CMS" commit but via one plausible column, then one plausible read of it — `copy_no_cms.mjs` pins the write surface at exactly two verbs so R03's proposal path changes it in a reviewable diff. Rank is asymmetric: **filing is open to every rank** (the engineer who wrote the toast and the newcomer who could not read the button are the likeliest noticers, and neither is the artist — the reason floor is the quality gate, rank is not), closing is Metic+ **or the author** (withdrawing your own flag is a correction, not triage); nothing gates on craft, since an artist is a craft and not a rank. Duplicates are the only automatic signal (the one of stale/duplicated/off-voice needing no judgement layer) and the count **subtracts settled work**, or it never moves and teaches that the queue is decorative. A vanished target is **reported, never auto-closed** — the id is a hash over the text, so an edit routinely orphans a flag, and closing a human judgement because a hash moved is the family of error ADR 0162 rules out. Deliberately reports **no `generated_at`**: R01's contract is determinism, so a clock is the one field it cannot carry.](<redacted>.md) | design / artist loop |
255
255
  | 0179 | [**`rank:metic+` means metic and above; a bare `rank:metic` still means exactly metic** ([task 1003094](https://cloudbongos.com/builders#/task/1003094), goal 1000069). [ADR 0175](<redacted>.md) gave the constitution ONE membership form, `rank:<key>`, resolved by an EXACT SQL match — so `rank:metic` seated metics ONLY (excluding Archons), `rank:xenos` seated NEWCOMERS ONLY rather than "everyone", and "Metic and above" was inexpressible. Everywhere else a rank is a LADDER and authority is cumulative (ADR 0034 inheritance, requireRank('metic','archon'), the floor-derived seed); board membership was the one place it meant "exactly", and nothing said so. Rehearsing R25 on a real Postgres, an Archon ratified `rank:metic` under monarchy — the apply worked perfectly — and the next vote came back 403 not_board_member: **the owner flipped the switch and fell off their own board**, with the board deadlocked at one member because an Archon can still PROPOSE but no longer VOTE. Owner decision: EXPLICIT GRAMMAR. `rank:metic+` (that rank and above), `rank:metic,archon` (union), and **a bare key still means EXACTLY that key** — rejecting the smaller diff (silently redefining the bare form) precisely because it would have changed the meaning of every already-ratified constitution, the monarchy default included, on upgrade: nothing in a government should change meaning without a vote. `+` is defined only on the STANDARD ladder — "and above" about a custom rank with no ladder position is a parse REFUSAL, not a guess, and a refusal seats nobody. The grammar had been written TWICE (a shape regex in the config sanitizer, a parse regex in the resolver, pinned by a property test) — sound for one literal form, unsound the moment a rule needs a LOOKUP, which is the same class of drift that produced the bug; both now call one pure `membership-predicate.js`. Adds the DISENFRANCHISEMENT GUARD: a proposal that would unseat its own proposer is refused before any write, naming the one-character fix, overridable with an explicit `acknowledge_self_removal` (a separate parameter, never a proposal field) because stepping back is a legitimate act. The two DB reads take a set via `= ANY($1)` + DISTINCT (a double-counted member silently raises the consent bar), the structural filters are re-proven against real Postgres (a deactivated builder and a machine principal still never govern), and the hall panel now describes every form in plain language — rendering `rank:metic+` as "the metic+ rank" would be the same bug one layer up. Unblocks task 1003095, which can now write each charter's TRUE predicate rather than wording around a limitation.](<redacted>.md) | government / the board |
256
256
  | 0180 | [**Dependency intake & drift policy: baseline-ratchet audit gate, install scripts off, cooldown via patrol, OIDC publish** ([task 1003127](https://cloudbongos.com/builders#/task/1003127), goal 1000076). The red-team baseline audit found 18 advisories (2 high) sitting unwatched in 9 production deps, the `undici ^6.27.0` override pinned INSIDE a vulnerable range while also breaking Node 26 CLIs, and nothing gated what an agent may install — against a 2025 npm threat wave (chalk/debug, Shai-Hulud, Nx) that ran on postinstall scripts and long-lived publish tokens. Four decisions. (1) `scripts/gds/audit-gate.js` runs in the required `unit` check: a high/critical advisory NOT in the committed `config/audit-baseline.json` fails the merge gate; the baseline is **shrink-only pre-gate debt** (10 findings at adoption) — adding an entry to silence a NEW advisory is out of policy. Fail-OPEN on registry/infra errors, fail-CLOSED on findings: the gate catches new advisories, it does not prove registry uptime. Notably a blanket `npm audit fix` cannot even run here (`workspace:` protocol, EUNSUPPORTEDPROTOCOL) — fixes are explicit tasks. (2) `.npmrc ignore-scripts=true`: the tree needs no install-lifecycle scripts — sharp installs prebuilds via optionalDependencies, proven by a script-less `npm ci` + full 430-test suite; a future dep needing one is an ADR-level exception. (3) Update cooldown is PATROL-enforced (task 1003128 flags versions younger than 7 days + stale overrides + action-pin drift) rather than `.npmrc`-enforced, because CI's npm 10 warns on the npm-11 `minimum-release-age` key for every builder; move it into `.npmrc` when the toolchain floor reaches npm ≥ 11.10. (4) Publish auth moves to npm **trusted publishing (OIDC)** — needs a one-time owner registration on npmjs.com (blocker filed); until then the granular 90-day `NPM_TOKEN` is a documented exception.](<redacted>.md) | devsecops / supply chain |
257
+ | 0181 | [**Teardown accepts every state but `torn_down`, and deliberately keeps the address** ([task 1003042](https://cloudbongos.com/builders#/task/1003042), goal 1000058). Walk finding N5: an instance at `status='error'` could never be removed — the owner's teardown answered 409 `not_teardownable` and the archon `force-teardown`, which exists as the escape hatch, answered the SAME refusal, because both read one hand-kept whitelist `['provisioning','active','tearing_down']` that omitted `requested` and `error`. No role had a path out, so errored rows accumulated permanently (instance 15 `swag` from 2026-08-07). The whitelist justified itself as "nothing running to tear down", which is FALSE for `error`: a provision failing mid-run lands there AFTER it may have created the database, taken a port, and written the systemd unit, Caddy block and DNS record — a partial standup is the thing most in need of reclaiming. `TEARDOWN_FROM` is now DERIVED from `INSTANCE_STATES` minus `torn_down`, so a future state is teardownable by DEFAULT — the bug was an allow-list that silently omitted states, and for a failure mode whose signature is "a row with no exit", the safe default is that an exit exists. The runner needed no change (its steps are each `allowFail`); only the web guard refused. The one surviving refusal is now ONE shared helper: the copies had drifted, and force-teardown's positional `res.fail` form put the bare code `not_teardownable` on the wire as the human text. **The `port` and `domain` are deliberately NOT released** — releasing the port alone would be WORSE than the status quo, because the `<slug>.caddy` block survives teardown and proxies the hostname to a PORT NUMBER, so a later instance given that port would answer at the torn-down project's still-live address (verified live: two projects torn down 2026-08-13 still resolved on 2026-08-19). Holding the port is what keeps the address inert at a harmless 502; releasing both is [task 1002897](https://cloudbongos.com/builders#/task/1002897). Retention is also correct for revive, which re-uses the same hostname. Copy was corrected to match: it had promised "the address stops answering", which was never true.](0181-teardown-state-gate-and-address-retention.md) | provisioning / lifecycle |
257
258
 
258
259
  > ⚠️ **Numbering collision (known):** two ADRs share `0008`. The numbers were assigned in parallel sessions before the lock could be enforced. The files are kept under their existing filenames to preserve git history, but the table disambiguates them as `0008-a` / `0008-b`. New ADRs should resume at `0040` to avoid further collisions. (`0024` is also a real on-disk duplicate: the cloneable-repo/local-first-memory ADR and the multi-agent-system-architecture ADR were both numbered `0024` in parallel; both keep their filenames, disambiguated as `0024-a` / `0024-b` like `0008`. Likewise `0025` ended up with two on-disk files — the offsite-backup ADR and the structured-criterion↔task-link ADR — both kept under their existing filenames and disambiguated as `0025-a` / `0025-b`; separately, the BFG ADR had also claimed `0025` on 2026-05-30 and yielded to `0026`. A third happened 2026-06-06: `0036` was claimed in parallel by the builders-subdomain ADR ([#726](https://example.com/builders#/task/726)) and this Discord-channels ADR ([#727](https://example.com/builders#/task/727)); the Discord-channels ADR yielded to `0037`. A fourth landed 2026-06-10: `0044` was claimed in parallel by the palette-replacement ADR and the per-box game-preview ADR ([#897](https://example.com/builders#/task/897)); both keep their filenames, disambiguated as `0044-a` / `0044-b` like `0008`. A fifth was caught pre-land 2026-06-13: `0050` was claimed in parallel by the device-flow-admission ADR ([#1000](https://example.com/builders#/task/1000)) and the sandbox-for-everyone ADR ([#1001](https://example.com/builders#/task/1001)); the latter yielded to `0052` before merging, so there is no `0050` duplicate. A sixth was caught pre-land 2026-06-21: the tool-agnostic-design-layer ADR ([#1375](https://example.com/builders#/task/1375)) first collided at `0079` with the value-gated-autonomous-engagement ADR, then at `0080` with the llm-cache-api-transport ADR (both part of the 0075–0080 LLM-cost-reduction series, landed on `main` in parallel); it yielded to `0081` before merging, so there is no `0079`/`0080` design-layer duplicate. A seventh landed 2026-06-23: `0084` was claimed in parallel by the auto-derived-per-task-rank-floor ADR and the remove-default-spend-cap ADR (task 1499); the rank-floor ADR landed on `main` first, so the spend-cap ADR yielded to `0085`, leaving no `0084` duplicate. An eighth landed 2026-06-27: `0095` was claimed in parallel by the borrowed-memory-and-retrieval-concepts ADR (PR #459, landed first) and the cross-agent-context-management ADR ([#1651](https://example.com/builders#/task/1651), PR #460); both keep their filenames, disambiguated as `0095-a` / `0095-b` like `0008`. These two are companions — `0095-a` adopts the memory/retrieval concepts and explicitly defers the grep-vs-semantic question to `0095-b`.)
@@ -9338,7 +9338,7 @@
9338
9338
  "provisioning"
9339
9339
  ],
9340
9340
  "summary": "POST /provisioning/instances/:id/force-teardown",
9341
- "description": "POST /provisioning/instances/:id/force-teardown — Archon tears down ANY instance. Trust-safe by the same mechanism as the owner's own teardown: it ENQUEUES a 'teardown' intent for the control-plane runner; the web tier never holds cloud tokens. rank: archon (acts on another owner's infrastructure). Declared among the archon routes (archon-first).\n\n**Rank:** `metic+archon` — Metic or Archon rank (review/triage powers).\n\n**Permissions:** `provisioning.fleet.manage` (all required).",
9341
+ "description": "POST /provisioning/instances/:id/force-teardown — Archon tears down ANY instance. Trust-safe by the same mechanism as the owner's own teardown: it ENQUEUES a 'teardown' intent for the control-plane runner; the web tier never holds cloud tokens. Gated by 'provisioning.fleet.manage', whose floor is metic (ADR 0157 lowered it from archon) — \"force\" means ACROSS OWNERSHIP, not past the state machine: the state gate below is the same one the owner route uses. Declared among the archon routes (archon-first).\n\n**Rank:** `metic+archon` — Metic or Archon rank (review/triage powers).\n\n**Permissions:** `provisioning.fleet.manage` (all required).",
9342
9342
  "x-rank": "metic+archon",
9343
9343
  "x-source": "modules/provisioning/routes/provisioning.js",
9344
9344
  "x-permissions": [