@bongos/core 1.19.917 → 1.19.919

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.
@@ -213,6 +213,10 @@
213
213
  "name": "public",
214
214
  "description": "Endpoints under `/api/bongos/public`."
215
215
  },
216
+ {
217
+ "name": "removal-proposals",
218
+ "description": "Endpoints under `/api/bongos/removal-proposals`."
219
+ },
216
220
  {
217
221
  "name": "scouting",
218
222
  "description": "Endpoints under `/api/bongos/scouting`."
@@ -14126,6 +14130,106 @@
14126
14130
  "security": []
14127
14131
  }
14128
14132
  },
14133
+ "/removal-proposals": {
14134
+ "get": {
14135
+ "operationId": "get_removal_proposals",
14136
+ "tags": [
14137
+ "removal-proposals"
14138
+ ],
14139
+ "summary": "GET /removal-proposals",
14140
+ "description": "GET /removal-proposals — the removal questions nobody has answered yet. rank: metic+archon (task.promote). No client-side rank gate is needed or wanted in the hall: a non-triager's fetch 403s and the slice renders nothing. One gate, on the server, beats a client check that can disagree with it.\n\n**Rank:** `metic+archon` — Metic or Archon rank (review/triage powers).\n\n**Permissions:** `task.promote` (all required).",
14141
+ "x-rank": "metic+archon",
14142
+ "x-source": "modules/lifecycle/routes/removal-proposals.js",
14143
+ "x-permissions": [
14144
+ "task.promote"
14145
+ ],
14146
+ "responses": {
14147
+ "200": {
14148
+ "description": "Success.",
14149
+ "content": {
14150
+ "application/json": {
14151
+ "schema": {
14152
+ "$ref": "#/components/schemas/GetRemovalProposalsResponse"
14153
+ }
14154
+ }
14155
+ }
14156
+ },
14157
+ "400": {
14158
+ "$ref": "#/components/responses/BadRequest"
14159
+ },
14160
+ "401": {
14161
+ "$ref": "#/components/responses/Unauthorized"
14162
+ },
14163
+ "403": {
14164
+ "$ref": "#/components/responses/Forbidden"
14165
+ }
14166
+ },
14167
+ "security": [
14168
+ {
14169
+ "builderSession": []
14170
+ }
14171
+ ]
14172
+ }
14173
+ },
14174
+ "/removal-proposals/{id}/decide": {
14175
+ "post": {
14176
+ "operationId": "post_removal_proposals_id_decide",
14177
+ "tags": [
14178
+ "removal-proposals"
14179
+ ],
14180
+ "summary": "POST /removal-proposals/:id/decide",
14181
+ "description": "POST /removal-proposals/:id/decide — confirm or dismiss ONE proposal. ONE ROUTE WITH AN ENUM rather than /confirm and /dismiss, because the two answers are the same act with opposite content and splitting them would invite the two halves to drift in what they close, what they record and what they refuse. The enum is validated against an allow-list in the domain layer and by the CHECK in lifecycle_009 — neither admits a value the other does not. rank: metic+archon (task.promote), the same atom as the read above.\n\n**Rank:** `metic+archon` — Metic or Archon rank (review/triage powers).\n\n**Permissions:** `task.promote` (all required).",
14182
+ "x-rank": "metic+archon",
14183
+ "x-source": "modules/lifecycle/routes/removal-proposals.js",
14184
+ "x-permissions": [
14185
+ "task.promote"
14186
+ ],
14187
+ "parameters": [
14188
+ {
14189
+ "name": "id",
14190
+ "in": "path",
14191
+ "required": true,
14192
+ "schema": {
14193
+ "type": "string"
14194
+ },
14195
+ "description": "Path parameter `id`."
14196
+ }
14197
+ ],
14198
+ "requestBody": {
14199
+ "required": true,
14200
+ "content": {
14201
+ "application/json": {
14202
+ "schema": {
14203
+ "$ref": "#/components/schemas/PostRemovalProposalsIdDecideRequest"
14204
+ }
14205
+ }
14206
+ },
14207
+ "x-validated": true
14208
+ },
14209
+ "responses": {
14210
+ "200": {
14211
+ "description": "Success."
14212
+ },
14213
+ "400": {
14214
+ "$ref": "#/components/responses/ValidationFailed"
14215
+ },
14216
+ "401": {
14217
+ "$ref": "#/components/responses/Unauthorized"
14218
+ },
14219
+ "403": {
14220
+ "$ref": "#/components/responses/Forbidden"
14221
+ },
14222
+ "404": {
14223
+ "$ref": "#/components/responses/NotFound"
14224
+ }
14225
+ },
14226
+ "security": [
14227
+ {
14228
+ "builderSession": []
14229
+ }
14230
+ ]
14231
+ }
14232
+ },
14129
14233
  "/scouting": {
14130
14234
  "get": {
14131
14235
  "operationId": "get_scouting",
@@ -20092,6 +20196,17 @@
20092
20196
  "page"
20093
20197
  ]
20094
20198
  },
20199
+ "GetRemovalProposalsResponse": {
20200
+ "type": "object",
20201
+ "properties": {
20202
+ "pending": {},
20203
+ "count": {}
20204
+ },
20205
+ "required": [
20206
+ "pending",
20207
+ "count"
20208
+ ]
20209
+ },
20095
20210
  "GetScoutingHandleResponse": {
20096
20211
  "type": "object",
20097
20212
  "properties": {
@@ -23988,6 +24103,22 @@
23988
24103
  ],
23989
24104
  "additionalProperties": false
23990
24105
  },
24106
+ "PostRemovalProposalsIdDecideRequest": {
24107
+ "type": "object",
24108
+ "properties": {
24109
+ "verdict": {
24110
+ "type": "string"
24111
+ },
24112
+ "note": {
24113
+ "type": "string",
24114
+ "maxLength": 2000
24115
+ }
24116
+ },
24117
+ "required": [
24118
+ "verdict"
24119
+ ],
24120
+ "additionalProperties": false
24121
+ },
23991
24122
  "PostSearchMarkRequest": {
23992
24123
  "type": "object",
23993
24124
  "properties": {
@@ -25421,9 +25552,9 @@
25421
25552
  "description": "A required dependency/feature is not configured or is temporarily down."
25422
25553
  }
25423
25554
  },
25424
- "x-endpoint-count": 401,
25425
- "x-schema-count": 440,
25555
+ "x-endpoint-count": 403,
25556
+ "x-schema-count": 442,
25426
25557
  "x-undocumented-bodies": 9,
25427
- "x-response-schemas": 300,
25558
+ "x-response-schemas": 301,
25428
25559
  "x-generated-by": "scripts/gds/gen-api-docs.js"
25429
25560
  }
@@ -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. 401 endpoints across 64 route files.
5
+ > **Generated from the live route files** — the route file is authoritative. 403 endpoints across 65 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`.
@@ -558,6 +558,13 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/0016-trust
558
558
  | GET | `/api/bongos/public/uptime` | `public` | — | Uptime: current status + 90-day history bucketed by hour. |
559
559
  | GET | `/api/bongos/public/versions` | `public` | — | read-only surface for the status dashboard (status.<apex>). |
560
560
 
561
+ ## `removal-proposals` (2)
562
+
563
+ | Method | Path | Rank | Body | Description |
564
+ |---|---|---|---|---|
565
+ | GET | `/api/bongos/removal-proposals` | `metic+archon` | — | GET /removal-proposals — the removal questions nobody has answered yet. |
566
+ | POST | `/api/bongos/removal-proposals/:id/decide` | `metic+archon` | `verdict`, `note` | POST /removal-proposals/:id/decide — confirm or dismiss ONE proposal. |
567
+
561
568
  ## `scouting` (2)
562
569
 
563
570
  | Method | Path | Rank | Body | Description |
@@ -4,13 +4,13 @@
4
4
  > Deterministic on a given tree — regenerate and commit rather than hand-editing.
5
5
  > Machine-readable twin: [`copy-registry.json`](copy-registry.json).
6
6
 
7
- **1828 strings** across 3 surfaces · 1585 distinct pieces of copy · 1828 certain, 0 heuristic · 166 shown in more than one place.
7
+ **1832 strings** across 3 surfaces · 1589 distinct pieces of copy · 1832 certain, 0 heuristic · 166 shown in more than one place.
8
8
 
9
9
  ## Surfaces
10
10
 
11
11
  | surface | lives at | files | strings |
12
12
  |---|---|---|---|
13
- | `builders-hall` | `modules/hall-ui/public` | 84 | 1338 |
13
+ | `builders-hall` | `modules/hall-ui/public` | 84 | 1342 |
14
14
  | `status-dashboard` | `modules/status-ui/public` | 4 | 100 |
15
15
  | `landing` | `modules/public-landing/public` | 5 | 390 |
16
16
 
@@ -78,7 +78,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
78
78
  | `996edffc1966` | 2x | Access requests | modules/hall-ui/public/watch.html:46, modules/hall-ui/public/watch.html:61 |
79
79
  | … | | _126 more in the JSON registry_ | |
80
80
 
81
- ## `builders-hall` — 1338 strings
81
+ ## `builders-hall` — 1342 strings
82
82
 
83
83
  | id | conf | text | where |
84
84
  |---|---|---|---|
@@ -125,6 +125,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
125
125
  | `5723baf26b2d` | certain | A quick idea is a thought filed fast. It is not an unfinished Full Idea — only its author can offer it up for | `modules/hall-ui/public/idea.js:260` |
126
126
  | `23dc5028183a` | certain | A quick report — title, what's going on, and where it belongs. Pick a goal and it lands there as a task the mo | `modules/hall-ui/public/ideas.html:60` |
127
127
  | `a1f06e5c11ae` | certain | A quiet place to tend the words and art the project shows people. | `modules/hall-ui/public/studio.html:38` |
128
+ | `08c1f66fd572` | certain | A refactor, a move, or work already superseded | `modules/hall-ui/public/goal-inbox.js:336` |
128
129
  | `244ea2a3564e` | certain | A role says how you work. A speciality says what you work on — and how you, as someone who knows the subject, | `modules/hall-ui/public/settings.html:253` |
129
130
  | `eff6b977b1d2` | certain | A self-drawing map of the codebase: what each module is, and what depends on it. Redrawn from the code on ever | `modules/hall-ui/public/atlas.html:31` |
130
131
  | `f3d6ed7c0d2c` | certain | A shipped task cannot be retired — it is immutable history now. | `modules/hall-ui/public/task.js:751` |
@@ -462,7 +463,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
462
463
  | `4794a991de3d` | certain | Getting started | `modules/hall-ui/public/hall-widgets.js:59` |
463
464
  | `a28b1a567f1a` | certain | GitHub username | `modules/hall-ui/public/hall-render.js:294` |
464
465
  | `8e52f0649662` | certain | Give the category a label. | `modules/hall-ui/public/project-settings.js:189` |
465
- | `79934b879a12` | certain | Give the nod | `modules/hall-ui/public/goal-inbox.js:244` |
466
+ | `79934b879a12` | certain | Give the nod | `modules/hall-ui/public/goal-inbox.js:258` |
466
467
  | `988e2c60df3d` | certain | Goal archived. | `modules/hall-ui/public/goals-render.js:1048` |
467
468
  | `9e70797b7e97` | certain | Goal id | `modules/hall-ui/public/goals-render.js:830` |
468
469
  | `529460e82058` | certain | Goal invitations to you | `modules/hall-ui/public/goal-inbox.js:113` |
@@ -621,7 +622,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
621
622
  | `31e91ee6ce03` | certain | Name must be {…} characters or fewer. | `modules/hall-ui/public/settings.js:766` |
622
623
  | `5e8199266a8a` | certain | Name unvouched | `modules/hall-ui/public/watch.js:374` |
623
624
  | `3970d299c95a` | certain | Needs work | `modules/hall-ui/public/studio.js:162` |
624
- | `7def635421ea` | certain | Needs your attention | `modules/hall-ui/public/goal-inbox.js:463` |
625
+ | `7def635421ea` | certain | Needs your attention | `modules/hall-ui/public/goal-inbox.js:557` |
625
626
  | `87f09d983f6c` | certain | New category label | `modules/hall-ui/public/project-settings.js:138` |
626
627
  | `d427d9dab01c` | certain | New claims are paused: {…} confirmed {…} can't land yet, so {…} reward is pending. Rebase and re-ship, or rele | `modules/hall-ui/public/hall-render.js:674` |
627
628
  | `7fcc2583c372` | certain | New custom rank | `modules/hall-ui/public/government.html:79` |
@@ -952,6 +953,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
952
953
  | `282c3fa2d456` | certain | That is the wording it already has. Change something, or file a flag instead if you only want to mark it. | `modules/hall-ui/public/copy-desk.js:438` |
953
954
  | `c0e28f30524d` | certain | That project's own standing, as reported by that project. | `modules/hall-ui/public/profile.js:314` |
954
955
  | `e99d4a811e92` | certain | That request is no longer available. | `modules/hall-ui/public/profile-connections.js:270` |
956
+ | `34017119ce22` | certain | That work is genuinely gone | `modules/hall-ui/public/goal-inbox.js:336` |
955
957
  | `6e5a324b2972` | certain | That's you | `modules/hall-ui/public/watch.js:803` |
956
958
  | `50198c465225` | certain | The 16-value rank enum in three bands, which ranks are live, and the permissions each rank holds. | `modules/hall-ui/public/diagrams.html:59` |
957
959
  | `c5e6bdd32fe3` | certain | The Board Room is open to the builders this project’s constitution seats on its board. | `modules/hall-ui/public/board-room.js:436` |
@@ -1266,7 +1268,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
1266
1268
  | `99f88f2af2ee` | certain | of work — a tangent, an investigation, an unprompted suggestion — instead of staying on your claimed task. It | `modules/hall-ui/public/settings.html:316` |
1267
1269
  | `5e7a2220c6a9` | certain | of {…} | `modules/hall-ui/public/roadmap.js:288` |
1268
1270
  | `759c12b5f983` | certain | oldest first | `modules/hall-ui/public/hall-widgets.js:134` |
1269
- | `cbaf58cdbe35` | certain | open the queue | `modules/hall-ui/public/goal-inbox.js:427` |
1271
+ | `cbaf58cdbe35` | certain | open the queue | `modules/hall-ui/public/goal-inbox.js:521` |
1270
1272
  | `af3a8e0fe6a8` | certain | open your goals | `modules/hall-ui/public/goal-inbox.js:121` |
1271
1273
  | `4f279d68175f` | certain | open, on the Security tab | `modules/hall-ui/public/watch.html:47` |
1272
1274
  | `7b85e6494596` | certain | opens a new tab | `modules/hall-ui/public/palette.js:190` |
@@ -1375,7 +1377,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
1375
1377
  | `b3757ba3bb46` | certain | {…} · Stored encrypted; never shown again after saving. | `modules/hall-ui/public/settings.js:990` |
1376
1378
  | `a6e4ed98fbea` | certain | {…} · revoke it any time under Active sessions. | `modules/hall-ui/public/settings.js:922` |
1377
1379
  | `d73256fbc395` | certain | {…} · who votes, and how it {…} | `modules/hall-ui/public/board-room.js:357` |
1378
- | `cab2a4296aec` | certain | {…} · {…} · {…}{…} | `modules/hall-ui/public/goal-inbox.js:326` |
1380
+ | `cab2a4296aec` | certain | {…} · {…} · {…}{…} | `modules/hall-ui/public/goal-inbox.js:420` |
1379
1381
  | `085f93f3f775` | certain | {…} — see all standings | `modules/hall-ui/public/hall-render.js:80` |
1380
1382
  | `f75ee96a7511` | certain | {…} — total payout from shipped work. | `modules/hall-ui/public/profile.js:94` |
1381
1383
  | `2a2728a4ec4f` | certain | {…}% of tasks complete | `modules/hall-ui/public/roadmap.js:63` |
@@ -1387,6 +1389,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
1387
1389
  | `499916045b19` | certain | {…}{…} asks to join | `modules/hall-ui/public/goal-inbox.js:69` |
1388
1390
  | `97e27ab169ae` | certain | {…}{…} session{…} across {…} builder{…}. | `modules/hall-ui/public/sessions.js:338` |
1389
1391
  | `3d74668f69e5` | certain | {…}{…} · {…} | `modules/hall-ui/public/task.js:653` |
1392
+ | `7f79bedecf60` | certain | {…}{…} — open it to see what went | `modules/hall-ui/public/goal-inbox.js:336` |
1390
1393
  | `9588652a7aff` | certain | · follows your profile setting | `modules/hall-ui/public/settings.js:566` |
1391
1394
  | `4a5ff4f662df` | certain | — ${…} of ${…} this month ({…}%). {…} | `modules/hall-ui/public/hall-render.js:517` |
1392
1395
  | `a6ae9cbdee30` | certain | — all builders — | `modules/hall-ui/public/builders.js:713` |
@@ -1396,14 +1399,15 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
1396
1399
  | `da9962dc1729` | certain | — or open any diagram fullscreen. | `modules/hall-ui/public/diagrams.html:33` |
1397
1400
  | `e5c5e94152b3` | certain | — that's all the tooling you need up front. | `modules/hall-ui/public/hall-render.js:890` |
1398
1401
  | `392afc533191` | certain | — type | `modules/hall-ui/public/hall-render.js:825` |
1399
- | `ff7514f48a7b` | certain | — untouched {…}+ days | `modules/hall-ui/public/goal-inbox.js:363` |
1402
+ | `ff7514f48a7b` | certain | — untouched {…}+ days | `modules/hall-ui/public/goal-inbox.js:457` |
1400
1403
  | `d3afa6d6dd9e` | certain | — your words, in your voice. Needed for a note or a follow-up. | `modules/hall-ui/public/studio.js:162` |
1401
1404
  | `68b547166d48` | certain | — {…} confirmed {…} can't land yet, so {…} reward is pending (it lands on ship). Rebase + re-ship or release { | `modules/hall-ui/public/hall-render.js:558` |
1402
1405
  | `5b9fa7cb4fb0` | certain | …and {…} more waiting — | `modules/hall-ui/public/goal-inbox.js:121` |
1403
- | `b2a04abfead0` | certain | …{…} more awaiting a nod. | `modules/hall-ui/public/goal-inbox.js:264` |
1404
- | `616fb9adf3cf` | certain | …{…} more going quiet. | `modules/hall-ui/public/goal-inbox.js:353` |
1406
+ | `b2a04abfead0` | certain | …{…} more awaiting a nod. | `modules/hall-ui/public/goal-inbox.js:278` |
1407
+ | `f16717b3e122` | certain | …{…} more awaiting an answer. | `modules/hall-ui/public/goal-inbox.js:358` |
1408
+ | `616fb9adf3cf` | certain | …{…} more going quiet. | `modules/hall-ui/public/goal-inbox.js:447` |
1405
1409
  | `e35f11bdc5d7` | certain | …{…} more open ideas — triage clears them one at a time. | `modules/hall-ui/public/builders.js:895` |
1406
- | `5f1d914ad45e` | certain | …{…} more — | `modules/hall-ui/public/goal-inbox.js:427` |
1410
+ | `5f1d914ad45e` | certain | …{…} more — | `modules/hall-ui/public/goal-inbox.js:521` |
1407
1411
  | `de8fa5223aee` | certain | ‹ Prev | `modules/hall-ui/public/hall-kit.js:74` |
1408
1412
  | `d4b84b7fbdeb` | certain | ← All goals | `modules/hall-ui/public/goals.html:80` |
1409
1413
  | `7926e30955d2` | certain | ← Back to goal | `modules/hall-ui/public/goals.html:184` |
@@ -3,9 +3,9 @@
3
3
  "generator": "scripts/gds/copy-inventory.js",
4
4
  "goal": 1000074,
5
5
  "counts": {
6
- "strings": 1828,
7
- "unique_ids": 1585,
8
- "certain": 1828,
6
+ "strings": 1832,
7
+ "unique_ids": 1589,
8
+ "certain": 1832,
9
9
  "heuristic": 0,
10
10
  "duplicate_ids": 166
11
11
  },
@@ -13,7 +13,7 @@
13
13
  "builders-hall": {
14
14
  "at": "modules/hall-ui/public",
15
15
  "files": 84,
16
- "strings": 1338
16
+ "strings": 1342
17
17
  },
18
18
  "status-dashboard": {
19
19
  "at": "modules/status-ui/public",
@@ -2164,6 +2164,15 @@
2164
2164
  "origin": "html-text",
2165
2165
  "confidence": "certain"
2166
2166
  },
2167
+ {
2168
+ "id": "08c1f66fd572",
2169
+ "surface": "builders-hall",
2170
+ "text": "A refactor, a move, or work already superseded",
2171
+ "file": "modules/hall-ui/public/goal-inbox.js",
2172
+ "line": 336,
2173
+ "origin": "js-markup",
2174
+ "confidence": "certain"
2175
+ },
2167
2176
  {
2168
2177
  "id": "244ea2a3564e",
2169
2178
  "surface": "builders-hall",
@@ -5202,7 +5211,7 @@
5202
5211
  "surface": "builders-hall",
5203
5212
  "text": "Give the nod",
5204
5213
  "file": "modules/hall-ui/public/goal-inbox.js",
5205
- "line": 244,
5214
+ "line": 258,
5206
5215
  "origin": "js-markup",
5207
5216
  "confidence": "certain"
5208
5217
  },
@@ -6633,7 +6642,7 @@
6633
6642
  "surface": "builders-hall",
6634
6643
  "text": "Needs your attention",
6635
6644
  "file": "modules/hall-ui/public/goal-inbox.js",
6636
- "line": 463,
6645
+ "line": 557,
6637
6646
  "origin": "js-markup",
6638
6647
  "confidence": "certain"
6639
6648
  },
@@ -9607,6 +9616,15 @@
9607
9616
  "origin": "js-notice",
9608
9617
  "confidence": "certain"
9609
9618
  },
9619
+ {
9620
+ "id": "34017119ce22",
9621
+ "surface": "builders-hall",
9622
+ "text": "That work is genuinely gone",
9623
+ "file": "modules/hall-ui/public/goal-inbox.js",
9624
+ "line": 336,
9625
+ "origin": "js-markup",
9626
+ "confidence": "certain"
9627
+ },
9610
9628
  {
9611
9629
  "id": "6e5a324b2972",
9612
9630
  "surface": "builders-hall",
@@ -12438,7 +12456,7 @@
12438
12456
  "surface": "builders-hall",
12439
12457
  "text": "open the queue",
12440
12458
  "file": "modules/hall-ui/public/goal-inbox.js",
12441
- "line": 427,
12459
+ "line": 521,
12442
12460
  "origin": "js-markup",
12443
12461
  "confidence": "certain"
12444
12462
  },
@@ -13419,7 +13437,7 @@
13419
13437
  "surface": "builders-hall",
13420
13438
  "text": "{…} · {…} · {…}{…}",
13421
13439
  "file": "modules/hall-ui/public/goal-inbox.js",
13422
- "line": 326,
13440
+ "line": 420,
13423
13441
  "origin": "js-markup",
13424
13442
  "confidence": "certain"
13425
13443
  },
@@ -13522,6 +13540,15 @@
13522
13540
  "origin": "js-markup",
13523
13541
  "confidence": "certain"
13524
13542
  },
13543
+ {
13544
+ "id": "7f79bedecf60",
13545
+ "surface": "builders-hall",
13546
+ "text": "{…}{…} — open it to see what went",
13547
+ "file": "modules/hall-ui/public/goal-inbox.js",
13548
+ "line": 336,
13549
+ "origin": "js-markup",
13550
+ "confidence": "certain"
13551
+ },
13525
13552
  {
13526
13553
  "id": "9588652a7aff",
13527
13554
  "surface": "builders-hall",
@@ -13608,7 +13635,7 @@
13608
13635
  "surface": "builders-hall",
13609
13636
  "text": "— untouched {…}+ days",
13610
13637
  "file": "modules/hall-ui/public/goal-inbox.js",
13611
- "line": 363,
13638
+ "line": 457,
13612
13639
  "origin": "js-markup",
13613
13640
  "confidence": "certain"
13614
13641
  },
@@ -13644,7 +13671,16 @@
13644
13671
  "surface": "builders-hall",
13645
13672
  "text": "…{…} more awaiting a nod.",
13646
13673
  "file": "modules/hall-ui/public/goal-inbox.js",
13647
- "line": 264,
13674
+ "line": 278,
13675
+ "origin": "js-markup",
13676
+ "confidence": "certain"
13677
+ },
13678
+ {
13679
+ "id": "f16717b3e122",
13680
+ "surface": "builders-hall",
13681
+ "text": "…{…} more awaiting an answer.",
13682
+ "file": "modules/hall-ui/public/goal-inbox.js",
13683
+ "line": 358,
13648
13684
  "origin": "js-markup",
13649
13685
  "confidence": "certain"
13650
13686
  },
@@ -13653,7 +13689,7 @@
13653
13689
  "surface": "builders-hall",
13654
13690
  "text": "…{…} more going quiet.",
13655
13691
  "file": "modules/hall-ui/public/goal-inbox.js",
13656
- "line": 353,
13692
+ "line": 447,
13657
13693
  "origin": "js-markup",
13658
13694
  "confidence": "certain"
13659
13695
  },
@@ -13671,7 +13707,7 @@
13671
13707
  "surface": "builders-hall",
13672
13708
  "text": "…{…} more —",
13673
13709
  "file": "modules/hall-ui/public/goal-inbox.js",
13674
- "line": 427,
13710
+ "line": 521,
13675
13711
  "origin": "js-markup",
13676
13712
  "confidence": "certain"
13677
13713
  },
@@ -2308,5 +2308,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
2308
2308
  landed since 1.19.915 with no explicit bump. run 35525694524. (task 1002620)
2309
2309
  1.19.917 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2310
2310
  landed since 1.19.916 with no explicit bump. run 35526196428. (task 1002620)
2311
+ 1.19.918 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2312
+ landed since 1.19.917 with no explicit bump. run 35529342372. (task 1002620)
2313
+ 1.19.919 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2314
+ landed since 1.19.918 with no explicit bump. run 35531198454. (task 1002620)
2311
2315
  ---------------------------------------------------------------------------
2312
2316
  ```
@@ -191,6 +191,20 @@
191
191
  return;
192
192
  }
193
193
 
194
+ // task 1004095 (BV2.R16): confirming or dismissing a removal proposal. Like
195
+ // the two branches above it refreshes ITS OWN slice only — repainting the
196
+ // goals list would leave the answered row sitting there still asking (the
197
+ // task-1003066 lesson). The verdict is server-declared on the button rather
198
+ // than inferred from which one was pressed, so this branch needs to know
199
+ // nothing about the vocabulary beyond what to say afterwards.
200
+ const removal = e.target.closest('[data-removal-task]');
201
+ if (removal) {
202
+ const verdict = removal.dataset.removalVerdict;
203
+ act(() => postJSON(`${API}/removal-proposals/${encodeURIComponent(removal.dataset.removalTask)}/decide`, { verdict }),
204
+ REMOVAL_TOASTS[verdict] || 'Answered.', loadRemovals);
205
+ return;
206
+ }
207
+
194
208
  const item = e.target.closest('.ginbox__item');
195
209
  const goalId = item && item.dataset.goal;
196
210
  if (!goalId) return;
@@ -275,6 +289,86 @@
275
289
  .catch(() => { /* a non-triager's 403, or a hiccup: never break the hall */ });
276
290
  }
277
291
 
292
+ // ---- removal proposals (task 1004095, BV2.R16) ----------------------------
293
+ //
294
+ // A ship that deleted files an earlier SHIPPED task put there files a QUESTION,
295
+ // never a verdict (task 1004090, ADR 0313): refactors, renames and file moves
296
+ // all look like deletions to a diff, so nothing is marked removed until a person
297
+ // says so. This slice is where they say so.
298
+ //
299
+ // WHY IT IS NOT A ROW IN THE NOD QUEUE, which is the obvious place. That queue is
300
+ // scoped to ROUTED FILINGS — `origin='route'` in ideas_task_lineage — and ADR 0299
301
+ // decided, on live numbers, that it stays that way and is honestly named for that
302
+ // population. A removal proposal has no idea lineage at all. Same CARD (the
303
+ // anti-accretion rule at the top of this file), same floor, separate slice.
304
+ //
305
+ // THE ANSWER IS NOT IN THIS ROW, and that is deliberate. Which files went and
306
+ // whose work they were is prose in the proposal's body, because the cascade that
307
+ // files it has no field for a target (ADR 0242). So the title LINKS to the task
308
+ // and the buttons sit beside it: a person who has not read the evidence should
309
+ // have to take one more step, not one fewer.
310
+ //
311
+ // No client-side rank gate, for the same reason the nod slice has none: GET
312
+ // /removal-proposals is Metic+ server-side, so a non-triager's fetch 403s and the
313
+ // slice renders nothing. One gate, on the server.
314
+ // What each answer says when it lands. Keyed by the SERVER's verdict name, the
315
+ // ROT_TOASTS convention, so a verdict added later degrades to a neutral
316
+ // confirmation instead of a blank toast. The wording states the CONSEQUENCE,
317
+ // because that is the half a person cannot see from the row they just cleared.
318
+ const REMOVAL_TOASTS = {
319
+ confirmed: 'Confirmed — the ideas behind that work will dim.',
320
+ dismissed: 'Dismissed — nothing dims.',
321
+ };
322
+
323
+ // THE LABELS ARE ONE WORD EACH BECAUSE THE ROW IS A FLEX LINE AT EVERY WIDTH.
324
+ // `.ginbox__actions` is `flex: 0 0 auto` and nothing in the card wraps, so two
325
+ // buttons take their width straight off the body’s. Measured at 375px:
326
+ // "Confirm removal" + "Dismiss" left the title 46px wide and 440px tall — a
327
+ // column of single letters — where every other row on this card keeps 138–218px.
328
+ // One word each brings it back into that band; the sentence each answer means
329
+ // rides the `title`, and the head already says what the question is.
330
+ function removalRowHtml(r) {
331
+ const title = escapeHtml(r.title || `task #${r.proposal_task_id}`);
332
+ const from = r.ship_task_id
333
+ ? `deleted by task #${escapeHtml(String(r.ship_task_id))}`
334
+ : 'from a ship whose id could not be read';
335
+ const goal = r.goal_title ? ` · ${escapeHtml(r.goal_title)}` : '';
336
+ return `<li class="ginbox__item ginbox__item--stacked">
337
+ <div class="ginbox__body">
338
+ <a class="ginbox__goal" href="/task/${encodeURIComponent(r.proposal_task_id)}">${title}</a>
339
+ <span class="ginbox__sub">${from}${goal} — open it to see what went</span>
340
+ </div>
341
+ <div class="ginbox__actions">
342
+ <button class="btn-ghost" type="button" data-removal-task="${escapeHtml(String(r.proposal_task_id))}"
343
+ data-removal-verdict="confirmed" title="That work is genuinely gone">Confirm</button>
344
+ <button class="btn-ghost" type="button" data-removal-task="${escapeHtml(String(r.proposal_task_id))}"
345
+ data-removal-verdict="dismissed" title="A refactor, a move, or work already superseded">Dismiss</button>
346
+ </div>
347
+ </li>`;
348
+ }
349
+
350
+ function renderRemovals(data) {
351
+ const box = host && host.querySelector('#attention-removals');
352
+ if (!box) return;
353
+ const rows = (data && Array.isArray(data.pending) ? data.pending : []);
354
+ const count = data && data.count != null ? data.count : rows.length;
355
+ if (!rows.length) {
356
+ box.innerHTML = '';
357
+ } else {
358
+ const more = count > 5 ? `<p class="ginbox__pending">…${count - 5} more awaiting an answer.</p>` : '';
359
+ box.innerHTML = '<h3 class="ginbox__sec-h">Was this removed? '
360
+ + `<span class="count-pill">${count}</span></h3>`
361
+ + `<ul class="ginbox__list">${rows.slice(0, 5).map(removalRowHtml).join('')}</ul>${more}`;
362
+ }
363
+ window.OTBAttention?.refresh();
364
+ }
365
+
366
+ function loadRemovals() {
367
+ return getJSON(`${API}/removal-proposals`)
368
+ .then((data) => renderRemovals(data))
369
+ .catch(() => { /* a non-triager's 403, or an older server: never break the hall */ });
370
+ }
371
+
278
372
  // ---- the rot queue (task 1003279, goal 1000072) ---------------------------
279
373
  //
280
374
  // "Untouched-for-X triggers a configured action rather than a silent decay."
@@ -484,6 +578,13 @@
484
578
  // triage. Its rows carry the shared .ginbox__item class, so refresh()
485
579
  // counts them with no change to the selector below.
486
580
  '<div id="attention-nod"></div>' +
581
+ // task 1004095 (BV2.R16): removal proposals — a ship deleted files an
582
+ // earlier shipped task put there, and somebody has to say whether that work
583
+ // is genuinely gone or it was a refactor. Straight after nod because it is
584
+ // the same act at the same floor: a Metic+ answering a question the system
585
+ // filed, inside a goal. Before blockers because a blocker is waiting on the
586
+ // owner specifically, while this is waiting on whoever looks first.
587
+ '<div id="attention-removals"></div>' +
487
588
  // task 1003455 (goal 1000089): open blockers — things stuck on the owner
488
589
  // (modules/ideas/blockers.js). AFTER nod, BEFORE rot: like every slice
489
590
  // above it, a blocker is genuinely waiting on a human, but unlike them it
@@ -499,6 +600,7 @@
499
600
  host.addEventListener('click', onClick);
500
601
  load();
501
602
  loadNodQueue();
603
+ loadRemovals();
502
604
  loadBlockers();
503
605
  loadRot();
504
606
  // task 1002802 (P06): the profile slice lives in its own file (builders.js is
@@ -3032,6 +3032,18 @@ main [id] { scroll-margin-top: calc(var(--topbar-h) + 16px); }
3032
3032
  .ginbox__note { margin: 3px 0 0; font-size: 12.5px; color: var(--ink-soft); font-style: italic; }
3033
3033
  .ginbox__actions { flex: 0 0 auto; display: flex; gap: 6px; }
3034
3034
  .ginbox__pending { font-size: 12.5px; margin: 10px 0 0; color: var(--ink-soft); }
3035
+ /* task 1004095: a row whose actions are TWO verbs, not one. `.ginbox__actions` is
3036
+ `flex: 0 0 auto` and the card never wraps, so a second button takes its width
3037
+ straight off the title’s — measured at 375px, the removal row’s title fell to
3038
+ 96px and 252px tall where every other row on this card keeps 138–218px. The
3039
+ modifier lets the actions drop under the words at phone width instead, which is
3040
+ the card’s own "a phone gets its own treatment, the document never widens"
3041
+ rule. Opt-in: an existing single-verb row is untouched. */
3042
+ @media (max-width: 640px) {
3043
+ .ginbox__item--stacked { flex-wrap: wrap; }
3044
+ .ginbox__item--stacked .ginbox__body { flex: 1 1 100%; }
3045
+ .ginbox__item--stacked .ginbox__actions { margin-top: 6px; }
3046
+ }
3035
3047
  /* task 1003464: the system's notices are inbox LINES on Home (rebase owed, an
3036
3048
  input the system needs, the monthly budget) — the placement rule, not a
3037
3049
  banner inside the Standing card. A row is a kind in the label voice over one
@@ -113,6 +113,13 @@
113
113
  // person. Kept as an explicit set rather than inferred, because "is this
114
114
  // copy/visual work" is a judgement the rule should state out loud and a test
115
115
  // should be able to pin — an inferred predicate is the kind that quietly widens.
116
+ // The removal rule's key, lifted out of its declaration so the SURFACE that
117
+ // answers a removal proposal (task 1004095 / BV2.R16) can find those proposals
118
+ // without hand-typing the string. The key is half of every generated row's
119
+ // `source_ref` (see cascadeSourceRef below), so a reader that re-types it is a
120
+ // reader that silently returns nothing the day the key changes.
121
+ const REMOVAL_RULE_KEY = 'removal-proposed-on-ship';
122
+
116
123
  const COPY_VISUAL_MODULES = Object.freeze([
117
124
  'copy-desk', // the copy registry + proposals (goal 1000074's own spine)
118
125
  'hall-ui', // the builders' hall — the most-seen surface
@@ -234,7 +241,7 @@ const CASCADES = Object.freeze([
234
241
  body: artistReviewBody,
235
242
  }),
236
243
  Object.freeze({
237
- key: 'removal-proposed-on-ship',
244
+ key: REMOVAL_RULE_KEY,
238
245
  // ENGINEER, not artist: confirming asks whether deleted code is genuinely
239
246
  // gone or was moved, which is a question about the diff.
240
247
  discipline: 'engineer',
@@ -567,6 +574,7 @@ async function runCascadesForShip(shipped, deps = {}) {
567
574
 
568
575
  module.exports = {
569
576
  CASCADES,
577
+ REMOVAL_RULE_KEY,
570
578
  removalProposalBody,
571
579
  IDEA_CASCADES,
572
580
  TRIGGERS,