@bongos/core 1.19.485 → 1.19.486

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.
@@ -6298,6 +6298,51 @@
6298
6298
  ]
6299
6299
  }
6300
6300
  },
6301
+ "/goals/{id}/water": {
6302
+ "post": {
6303
+ "operationId": "post_goals_id_water",
6304
+ "tags": [
6305
+ "goals"
6306
+ ],
6307
+ "summary": "POST /goals/:id/water",
6308
+ "description": "POST /goals/:id/water — reset a rotting goal's clock.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
6309
+ "x-rank": "any-builder",
6310
+ "x-source": "modules/lifecycle/routes/rot.js",
6311
+ "parameters": [
6312
+ {
6313
+ "name": "id",
6314
+ "in": "path",
6315
+ "required": true,
6316
+ "schema": {
6317
+ "type": "string"
6318
+ },
6319
+ "description": "Path parameter `id`."
6320
+ }
6321
+ ],
6322
+ "responses": {
6323
+ "200": {
6324
+ "description": "Success."
6325
+ },
6326
+ "400": {
6327
+ "$ref": "#/components/responses/BadRequest"
6328
+ },
6329
+ "401": {
6330
+ "$ref": "#/components/responses/Unauthorized"
6331
+ },
6332
+ "403": {
6333
+ "$ref": "#/components/responses/Forbidden"
6334
+ },
6335
+ "404": {
6336
+ "$ref": "#/components/responses/NotFound"
6337
+ }
6338
+ },
6339
+ "security": [
6340
+ {
6341
+ "builderSession": []
6342
+ }
6343
+ ]
6344
+ }
6345
+ },
6301
6346
  "/government/assignments": {
6302
6347
  "get": {
6303
6348
  "operationId": "get_government_assignments",
@@ -7364,6 +7409,44 @@
7364
7409
  ]
7365
7410
  }
7366
7411
  },
7412
+ "/inbox/rotting": {
7413
+ "get": {
7414
+ "operationId": "get_inbox_rotting",
7415
+ "tags": [
7416
+ "inbox"
7417
+ ],
7418
+ "summary": "GET /inbox/rotting",
7419
+ "description": "GET /inbox/rotting — what is quietly rotting, for this caller. rank: any-builder — own-scoped by default. The builder id comes from the authenticated session and is never read off the request, so a caller can only ever widen the result by HOLDING an atom, never by naming a different builder. A Xenos sees exactly the things they created and nothing else. ?rot_days= is a LENS, not a setting: it answers \"what would 7 days show me?\" without reconfiguring the project, and is never written back. The response reports the timer and where the number came from, so a reader can always tell a lens apart from the project's real answer (the src/stale-timer.js receipt convention — a number with no provenance is what let 6h and 24h coexist).\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
7420
+ "x-rank": "any-builder",
7421
+ "x-source": "modules/lifecycle/routes/rot.js",
7422
+ "responses": {
7423
+ "200": {
7424
+ "description": "Success.",
7425
+ "content": {
7426
+ "application/json": {
7427
+ "schema": {
7428
+ "$ref": "#/components/schemas/GetInboxRottingResponse"
7429
+ }
7430
+ }
7431
+ }
7432
+ },
7433
+ "400": {
7434
+ "$ref": "#/components/responses/BadRequest"
7435
+ },
7436
+ "401": {
7437
+ "$ref": "#/components/responses/Unauthorized"
7438
+ },
7439
+ "403": {
7440
+ "$ref": "#/components/responses/Forbidden"
7441
+ }
7442
+ },
7443
+ "security": [
7444
+ {
7445
+ "builderSession": []
7446
+ }
7447
+ ]
7448
+ }
7449
+ },
7367
7450
  "/inbox/score": {
7368
7451
  "post": {
7369
7452
  "operationId": "post_inbox_score",
@@ -15191,6 +15274,51 @@
15191
15274
  ]
15192
15275
  }
15193
15276
  },
15277
+ "/tasks/{id}/water": {
15278
+ "post": {
15279
+ "operationId": "post_tasks_id_water",
15280
+ "tags": [
15281
+ "tasks"
15282
+ ],
15283
+ "summary": "POST /tasks/:id/water",
15284
+ "description": "POST /tasks/:id/water — reset a rotting task's clock.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
15285
+ "x-rank": "any-builder",
15286
+ "x-source": "modules/lifecycle/routes/rot.js",
15287
+ "parameters": [
15288
+ {
15289
+ "name": "id",
15290
+ "in": "path",
15291
+ "required": true,
15292
+ "schema": {
15293
+ "type": "string"
15294
+ },
15295
+ "description": "Path parameter `id`."
15296
+ }
15297
+ ],
15298
+ "responses": {
15299
+ "200": {
15300
+ "description": "Success."
15301
+ },
15302
+ "400": {
15303
+ "$ref": "#/components/responses/BadRequest"
15304
+ },
15305
+ "401": {
15306
+ "$ref": "#/components/responses/Unauthorized"
15307
+ },
15308
+ "403": {
15309
+ "$ref": "#/components/responses/Forbidden"
15310
+ },
15311
+ "404": {
15312
+ "$ref": "#/components/responses/NotFound"
15313
+ }
15314
+ },
15315
+ "security": [
15316
+ {
15317
+ "builderSession": []
15318
+ }
15319
+ ]
15320
+ }
15321
+ },
15194
15322
  "/versions": {
15195
15323
  "get": {
15196
15324
  "operationId": "get_versions",
@@ -16307,6 +16435,22 @@
16307
16435
  "open_count"
16308
16436
  ]
16309
16437
  },
16438
+ "GetInboxRottingResponse": {
16439
+ "type": "object",
16440
+ "properties": {
16441
+ "rows": {},
16442
+ "count": {},
16443
+ "scope": {},
16444
+ "timer": {},
16445
+ "cutoff": {},
16446
+ "ok": {
16447
+ "type": "boolean"
16448
+ },
16449
+ "tier": {},
16450
+ "id": {},
16451
+ "watered_at": {}
16452
+ }
16453
+ },
16310
16454
  "GetInboxSparksResponse": {
16311
16455
  "type": "object",
16312
16456
  "properties": {
@@ -21505,9 +21649,9 @@
21505
21649
  "description": "A required dependency/feature is not configured or is temporarily down."
21506
21650
  }
21507
21651
  },
21508
- "x-endpoint-count": 341,
21509
- "x-schema-count": 374,
21652
+ "x-endpoint-count": 344,
21653
+ "x-schema-count": 375,
21510
21654
  "x-undocumented-bodies": 9,
21511
- "x-response-schemas": 255,
21655
+ "x-response-schemas": 256,
21512
21656
  "x-generated-by": "scripts/gds/gen-api-docs.js"
21513
21657
  }
@@ -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. 341 endpoints across 55 route files.
5
+ > **Generated from the live route files** — the route file is authoritative. 344 endpoints across 56 route files.
6
6
  > Machine-readable spec: [`docs/api/openapi.json`](api/openapi.json) (OpenAPI 3.1). Rendered docs site: **`/docs`** (e.g. `cloudbongos.com/docs`).
7
7
 
8
8
  Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>.md)): `public` < `any-builder` < `metic+archon` < `archon`.
@@ -220,7 +220,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>
220
220
  | GET | `/api/bongos/github/repos` | `any-builder` | — | acts only on the caller's OWN GitHub identity (the token they granted). |
221
221
  | POST | `/api/bongos/github/repos` | `any-builder` | `name` | creates a repo in the caller's OWN account. |
222
222
 
223
- ## `goals` (30)
223
+ ## `goals` (31)
224
224
 
225
225
  | Method | Path | Rank | Body | Description |
226
226
  |---|---|---|---|---|
@@ -250,6 +250,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>
250
250
  | POST | `/api/bongos/goals/:id/tasks` | `any-builder` | `title`, `description`, `touches`, `est_minutes`, `priority`, `kind`, `discipline`, `value_summary`, `parent_task_id` | POST /goals/:id/tasks — bounded task authoring within a goal's scope (BV1.R60 / ADR 0086 §3). |
251
251
  | POST | `/api/bongos/goals/:id/transfer` | `any-builder` | `builder_id` | POST /goals/:id/transfer — hand the goal to a new owner (task 1735). |
252
252
  | PATCH | `/api/bongos/goals/:id/visibility` | `any-builder` | — | PATCH /goals/:id/visibility — make a goal public\|private (task 1952 / ADR 0112). |
253
+ | POST | `/api/bongos/goals/:id/water` | `any-builder` | — | POST /goals/:id/water — reset a rotting goal's clock. |
253
254
  | GET | `/api/bongos/goals/inbox` | `any-builder` | — | GET /goals/inbox — the caller's private-goals inbox (task 1952 / ADR 0112): { invitations: invites addressed to me (accept/decline); join… |
254
255
  | POST | `/api/bongos/goals/reorder` | `metic+archon` | `version_id`, `ordered_ids` | POST /goals/reorder — Archon drag-to-reorder: set the priority order of every goal WITHIN one version (task 1760). |
255
256
  | GET | `/api/bongos/goals/rollups` | `any-builder` | — | GET /goals/rollups?version= — one small row per goal: readiness counters, the last-activity timestamp, the craft set, and the next claima… |
@@ -294,7 +295,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>
294
295
  | GET | `/api/bongos/help-requests/for-me` | `any-builder` | — | The addressed read — C01's answer to "and fires a notification". |
295
296
  | GET | `/api/bongos/help-requests/mine` | `any-builder` | — | your own asks, so you can settle them. |
296
297
 
297
- ## `inbox` (15)
298
+ ## `inbox` (16)
298
299
 
299
300
  | Method | Path | Rank | Body | Description |
300
301
  |---|---|---|---|---|
@@ -310,6 +311,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>
310
311
  | DELETE | `/api/bongos/inbox/:id/spark` | `any-builder` | — | Withdraw the Big Idea Spark mark — the reversibility half of R144. |
311
312
  | GET | `/api/bongos/inbox/awaiting-nod` | `metic+archon` | — | GET /inbox/awaiting-nod — the NOD QUEUE (task 1003066, BV1.R05). |
312
313
  | GET | `/api/bongos/inbox/by-builder/:id` | `any-builder` | — | THE IDEA TRAIL: every idea one builder filed, newest first, with what became of each (task 1003281, goal 1000072, criterion legibility-ac… |
314
+ | GET | `/api/bongos/inbox/rotting` | `any-builder` | — | GET /inbox/rotting — what is quietly rotting, for this caller. |
313
315
  | POST | `/api/bongos/inbox/score` | `any-builder` | `field_values`, `template_key` | |
314
316
  | GET | `/api/bongos/inbox/sparks` | `any-builder` | — | GET /inbox/sparks — THE SPARK QUEUE (task 1002921 / BV1.R145, goal 1000062). |
315
317
  | GET | `/api/bongos/inbox/template` | `any-builder` | — | ----------------------------------------------------------------------- The two reads the hall's Full Idea composer is built from (task 1… |
@@ -559,7 +561,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>
559
561
  |---|---|---|---|---|
560
562
  | GET | `/api/bongos/task-visuals/:name` | `public` | — | the WIDEST path this route can take, declared honestly. |
561
563
 
562
- ## `tasks` (30)
564
+ ## `tasks` (31)
563
565
 
564
566
  | Method | Path | Rank | Body | Description |
565
567
  |---|---|---|---|---|
@@ -588,6 +590,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>
588
590
  | POST | `/api/bongos/tasks/:id/visual` | `any-builder` | `image_b64`, `content_type`, `alt` | POST /tasks/:id/visual — attach (or replace) this task's ship-time visual. |
589
591
  | DELETE | `/api/bongos/tasks/:id/visual` | `any-builder` | — | DELETE /tasks/:id/visual — remove it again. |
590
592
  | POST | `/api/bongos/tasks/:id/vote` | `metic+archon` | `vote` | POST /tasks/:id/vote { vote: 1 \| -1 \| 0 } — cast/change/clear a peer vote on a confirmed/shipped task. |
593
+ | POST | `/api/bongos/tasks/:id/water` | `any-builder` | — | POST /tasks/:id/water — reset a rotting task's clock. |
591
594
  | GET | `/api/bongos/tasks/claimable` | `any-builder` | — | NOTE: /tasks/claimable must come BEFORE /tasks/:id or Express will route 'claimable' to the :id param. |
592
595
  | GET | `/api/bongos/tasks/newcomer-floor` | `any-builder` | — | GET /tasks/newcomer-floor — supply gauge for the newcomer queue (task 614). |
593
596
  | POST | `/api/bongos/tasks/newcomer-restock` | `metic+archon` | — | POST /tasks/newcomer-restock (task 1168) — bring every evergreen newcomer chore's standing pool up to its configured floor. |
@@ -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
- **1576 strings** across 3 surfaces · 1369 distinct pieces of copy · 1576 certain, 0 heuristic · 148 shown in more than one place.
7
+ **1579 strings** across 3 surfaces · 1372 distinct pieces of copy · 1579 certain, 0 heuristic · 148 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` | 72 | 1133 |
13
+ | `builders-hall` | `modules/hall-ui/public` | 72 | 1136 |
14
14
  | `status-dashboard` | `modules/status-ui/public` | 4 | 98 |
15
15
  | `landing` | `modules/public-landing/public` | 5 | 345 |
16
16
 
@@ -78,7 +78,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
78
78
  | `563d29ed86d8` | 2x | Bulk actions | modules/hall-ui/public/goals.html:172, modules/hall-ui/public/work.html:129 |
79
79
  | … | | _108 more in the JSON registry_ | |
80
80
 
81
- ## `builders-hall` — 1133 strings
81
+ ## `builders-hall` — 1136 strings
82
82
 
83
83
  | id | conf | text | where |
84
84
  |---|---|---|---|
@@ -421,7 +421,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
421
421
  | `2ae2114b61db` | certain | Get a free key ↗ | `modules/hall-ui/public/settings.js:1020` |
422
422
  | `4794a991de3d` | certain | Getting started | `modules/hall-ui/public/hall-widgets.js:59` |
423
423
  | `a28b1a567f1a` | certain | GitHub username | `modules/hall-ui/public/hall-render.js:278` |
424
- | `79934b879a12` | certain | Give the nod | `modules/hall-ui/public/goal-inbox.js:231` |
424
+ | `79934b879a12` | certain | Give the nod | `modules/hall-ui/public/goal-inbox.js:244` |
425
425
  | `9e70797b7e97` | certain | Goal id | `modules/hall-ui/public/goals-render.js:768` |
426
426
  | `529460e82058` | certain | Goal invitations to you | `modules/hall-ui/public/goal-inbox.js:113` |
427
427
  | `e2a9e33abe60` | certain | Goal tasks | `modules/hall-ui/public/goals.html:136` |
@@ -551,7 +551,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
551
551
  | `f14a91b3f625` | certain | Most recent first. Each row is one recorded work session; every id on it opens its record. | `modules/hall-ui/public/sessions.html:56` |
552
552
  | `ae84ebe1d735` | certain | Name cannot be blank. | `modules/hall-ui/public/settings.js:830` |
553
553
  | `31e91ee6ce03` | certain | Name must be {…} characters or fewer. | `modules/hall-ui/public/settings.js:835` |
554
- | `7def635421ea` | certain | Needs your attention | `modules/hall-ui/public/goal-inbox.js:274` |
554
+ | `7def635421ea` | certain | Needs your attention | `modules/hall-ui/public/goal-inbox.js:385` |
555
555
  | `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:604` |
556
556
  | `7fcc2583c372` | certain | New custom rank | `modules/hall-ui/public/government.html:81` |
557
557
  | `9e06c14d155a` | certain | New here — request access | `modules/hall-ui/public/hall-render.js:278` |
@@ -1176,6 +1176,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
1176
1176
  | `b3757ba3bb46` | certain | {…} · Stored encrypted; never shown again after saving. | `modules/hall-ui/public/settings.js:1054` |
1177
1177
  | `94445e6ed5e8` | certain | {…} · code | `modules/hall-ui/public/pair.js:53` |
1178
1178
  | `d73256fbc395` | certain | {…} · who votes, and how it {…} | `modules/hall-ui/public/government.js:1043` |
1179
+ | `cab2a4296aec` | certain | {…} · {…} · {…}{…} | `modules/hall-ui/public/goal-inbox.js:326` |
1179
1180
  | `085f93f3f775` | certain | {…} — see all standings | `modules/hall-ui/public/hall-render.js:80` |
1180
1181
  | `f75ee96a7511` | certain | {…} — total payout from shipped work. | `modules/hall-ui/public/profile.js:58` |
1181
1182
  | `2a2728a4ec4f` | certain | {…}% of tasks complete | `modules/hall-ui/public/roadmap.js:70` |
@@ -1194,9 +1195,11 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
1194
1195
  | `e5c5e94152b3` | certain | — that's all the tooling you need up front. | `modules/hall-ui/public/hall-render.js:811` |
1195
1196
  | `a63451a6db06` | certain | — to start and stop your dev box on your behalf. | `modules/hall-ui/public/pair.js:53` |
1196
1197
  | `392afc533191` | certain | — type | `modules/hall-ui/public/hall-render.js:744` |
1198
+ | `ff7514f48a7b` | certain | — untouched {…}+ days | `modules/hall-ui/public/goal-inbox.js:363` |
1197
1199
  | `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:488` |
1198
1200
  | `5b9fa7cb4fb0` | certain | …and {…} more waiting — | `modules/hall-ui/public/goal-inbox.js:121` |
1199
- | `b2a04abfead0` | certain | …{…} more awaiting a nod. | `modules/hall-ui/public/goal-inbox.js:251` |
1201
+ | `b2a04abfead0` | certain | …{…} more awaiting a nod. | `modules/hall-ui/public/goal-inbox.js:264` |
1202
+ | `616fb9adf3cf` | certain | …{…} more going quiet. | `modules/hall-ui/public/goal-inbox.js:353` |
1200
1203
  | `e35f11bdc5d7` | certain | …{…} more open ideas — triage clears them one at a time. | `modules/hall-ui/public/builders.js:853` |
1201
1204
  | `de8fa5223aee` | certain | ‹ Prev | `modules/hall-ui/public/hall-kit.js:71` |
1202
1205
  | `d4b84b7fbdeb` | certain | ← All goals | `modules/hall-ui/public/goals.html:80` |
@@ -3,9 +3,9 @@
3
3
  "generator": "scripts/gds/copy-inventory.js",
4
4
  "goal": 1000074,
5
5
  "counts": {
6
- "strings": 1576,
7
- "unique_ids": 1369,
8
- "certain": 1576,
6
+ "strings": 1579,
7
+ "unique_ids": 1372,
8
+ "certain": 1579,
9
9
  "heuristic": 0,
10
10
  "duplicate_ids": 148
11
11
  },
@@ -13,7 +13,7 @@
13
13
  "builders-hall": {
14
14
  "at": "modules/hall-ui/public",
15
15
  "files": 72,
16
- "strings": 1133
16
+ "strings": 1136
17
17
  },
18
18
  "status-dashboard": {
19
19
  "at": "modules/status-ui/public",
@@ -4635,7 +4635,7 @@
4635
4635
  "surface": "builders-hall",
4636
4636
  "text": "Give the nod",
4637
4637
  "file": "modules/hall-ui/public/goal-inbox.js",
4638
- "line": 231,
4638
+ "line": 244,
4639
4639
  "origin": "js-markup",
4640
4640
  "confidence": "certain"
4641
4641
  },
@@ -5805,7 +5805,7 @@
5805
5805
  "surface": "builders-hall",
5806
5806
  "text": "Needs your attention",
5807
5807
  "file": "modules/hall-ui/public/goal-inbox.js",
5808
- "line": 274,
5808
+ "line": 385,
5809
5809
  "origin": "js-markup",
5810
5810
  "confidence": "certain"
5811
5811
  },
@@ -11425,6 +11425,15 @@
11425
11425
  "origin": "js-markup",
11426
11426
  "confidence": "certain"
11427
11427
  },
11428
+ {
11429
+ "id": "cab2a4296aec",
11430
+ "surface": "builders-hall",
11431
+ "text": "{…} · {…} · {…}{…}",
11432
+ "file": "modules/hall-ui/public/goal-inbox.js",
11433
+ "line": 326,
11434
+ "origin": "js-markup",
11435
+ "confidence": "certain"
11436
+ },
11428
11437
  {
11429
11438
  "id": "085f93f3f775",
11430
11439
  "surface": "builders-hall",
@@ -11587,6 +11596,15 @@
11587
11596
  "origin": "js-markup",
11588
11597
  "confidence": "certain"
11589
11598
  },
11599
+ {
11600
+ "id": "ff7514f48a7b",
11601
+ "surface": "builders-hall",
11602
+ "text": "— untouched {…}+ days",
11603
+ "file": "modules/hall-ui/public/goal-inbox.js",
11604
+ "line": 363,
11605
+ "origin": "js-markup",
11606
+ "confidence": "certain"
11607
+ },
11590
11608
  {
11591
11609
  "id": "68b547166d48",
11592
11610
  "surface": "builders-hall",
@@ -11610,7 +11628,16 @@
11610
11628
  "surface": "builders-hall",
11611
11629
  "text": "…{…} more awaiting a nod.",
11612
11630
  "file": "modules/hall-ui/public/goal-inbox.js",
11613
- "line": 251,
11631
+ "line": 264,
11632
+ "origin": "js-markup",
11633
+ "confidence": "certain"
11634
+ },
11635
+ {
11636
+ "id": "616fb9adf3cf",
11637
+ "surface": "builders-hall",
11638
+ "text": "…{…} more going quiet.",
11639
+ "file": "modules/hall-ui/public/goal-inbox.js",
11640
+ "line": 353,
11614
11641
  "origin": "js-markup",
11615
11642
  "confidence": "certain"
11616
11643
  },
@@ -1407,5 +1407,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
1407
1407
  landed since 1.19.483 with no explicit bump. run 33675143968. (task 1002620)
1408
1408
  1.19.485 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1409
1409
  landed since 1.19.484 with no explicit bump. run 33681828446. (task 1002620)
1410
+ 1.19.486 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1411
+ landed since 1.19.485 with no explicit bump. run 33682949187. (task 1002620)
1410
1412
  ---------------------------------------------------------------------------
1411
1413
  ```
@@ -0,0 +1,163 @@
1
+ -- core_231_project_settings_and_attention_water.sql — the rot clock and the knob
2
+ -- that sets it (task 1003279, goal 1000072, criterion legibility-sort-and-staleness).
3
+ --
4
+ -- THE GAP. Goal 1000072's rotting half asks that "untouched-for-X triggers a
5
+ -- configured action rather than a silent decay". Two things were missing to make
6
+ -- that sentence true, and this migration adds exactly those two:
7
+ --
8
+ -- 1. X had nowhere to live. src/stale-timer.js (task 1003476, ADR 0226) owns the
9
+ -- CLAIM-silence timer — hours, env-configured, and the sweep that ACTS on it
10
+ -- releases someone's claim. The rot timer is a different measurement with a
11
+ -- different unit and a different consequence (days; it asks a human a
12
+ -- question), and task 1003280 requires it be "live, DB-backed" and warns
13
+ -- "do not fork a second store". So: one generic project_settings table, with
14
+ -- the rot timer as its first key.
15
+ --
16
+ -- 2. "Water" had nothing to write. The owner's four verbs are prune, kill,
17
+ -- prioritise and WATER — leave the thing alone and reset its clock. Without
18
+ -- somewhere to record that, watering would have to fake a real edit, and the
19
+ -- same line would come back on the next read; the inbox teaches people to
20
+ -- ignore it within a week. attention_watered_at IS the reset.
21
+ --
22
+ -- WHY A COLUMN AND NOT AN ITEMS TABLE. There is deliberately no `attention_items`
23
+ -- row anywhere in this design. The inbox line is DERIVED on read from the rot clock
24
+ -- (below) vs the timer, which makes "exactly one open item per stale thing, not one
25
+ -- per sweep" true by construction rather than by a de-duplicating sweep that has to
26
+ -- be got right.
27
+ -- Acting on the line (prune/kill/prioritise) changes status and the row stops
28
+ -- matching; watering pushes the clock forward and it stops matching. Nothing to
29
+ -- reconcile, nothing to garbage-collect, and no way to accrete duplicates.
30
+ --
31
+ -- Additive + forward-safe: every column is nullable with no backfill, nothing is
32
+ -- renamed, and no existing reader changes meaning (tests/migrations_forward_safe.mjs).
33
+
34
+ BEGIN;
35
+
36
+ -- ---------------------------------------------------------------------------
37
+ -- 1. project_settings — the instance's live, DB-backed knobs.
38
+ -- ---------------------------------------------------------------------------
39
+ --
40
+ -- Deliberately a generic key/value table rather than one column per knob: task
41
+ -- 1003280 puts five project fundamentals on one page, and a table that grows a
42
+ -- migration per knob would make that page a schema change every time. Values are
43
+ -- stored as TEXT and parsed by a typed accessor at the read edge
44
+ -- (src/bongos/project-settings.js), the same shape src/stale-timer.js already uses
45
+ -- for env values — one parser, one range check, one documented default.
46
+ --
47
+ -- A key that has never been set has NO ROW. Absence means "use the code default",
48
+ -- which is what lets a fresh instance boot correctly with an empty table, and what
49
+ -- lets a settings page distinguish "never configured" from "deliberately set to
50
+ -- the same value the default happens to be".
51
+ CREATE TABLE IF NOT EXISTS project_settings (
52
+ key text PRIMARY KEY,
53
+ value text NOT NULL,
54
+ updated_at timestamptz NOT NULL DEFAULT now(),
55
+ updated_by bigint REFERENCES builders(id)
56
+ );
57
+
58
+ COMMENT ON TABLE project_settings IS
59
+ 'Live, DB-backed per-project knobs (task 1003279). Key/value so task 1003280''s settings page can grow a row without a migration. No row = never configured = use the code default; that absence is meaningful, so do not seed defaults here.';
60
+ COMMENT ON COLUMN project_settings.value IS
61
+ 'Stored as text and parsed by a typed accessor at the read edge (src/bongos/project-settings.js). A value that fails its range check is IGNORED with a receipt, never coerced silently — the ADR 0226 posture.';
62
+ COMMENT ON COLUMN project_settings.updated_by IS
63
+ 'The builder who last set this knob. NULL = set by a system path with no builder in the request (the goals.created_by convention, migration 160).';
64
+
65
+ -- ---------------------------------------------------------------------------
66
+ -- 2. The water mark, on both tiers that can rot.
67
+ -- ---------------------------------------------------------------------------
68
+ --
69
+ -- NULL = never watered, and the thing's own activity timestamp is the whole clock.
70
+ -- This is why the read expression is a GREATEST and not a bare COALESCE: a real edit
71
+ -- after a watering must move the clock forward again, and a watering must never make
72
+ -- a thing look OLDER than its last genuine touch.
73
+ --
74
+ -- THE ROT CLOCK, AND WHY IT IS SHAPED LIKE THIS:
75
+ --
76
+ -- tasks: GREATEST(updated_at, COALESCE(shipped_at, updated_at),
77
+ -- COALESCE(attention_watered_at, updated_at))
78
+ -- goals: GREATEST(updated_at, COALESCE(attention_watered_at, updated_at))
79
+ --
80
+ -- The first two terms are NOT a new definition of "touched" — they are exactly the
81
+ -- one modules/hall-ui/public/task-lib.js already publishes for the Tasks board and
82
+ -- the Goals page (`taskActivityMs = max(updated_at, shipped_at)`, task 1003278, this
83
+ -- same criterion), which folds in shipped_at because a ship path can stamp it
84
+ -- without touching updated_at. This migration adds a THIRD term to that definition
85
+ -- and nothing else. Had the server invented its own two-term clock, the board and
86
+ -- the inbox would disagree about which rows are stale — which is the 6h-vs-24h
87
+ -- failure of ADR 0226 reappearing one tier up, and the reason the indexes below
88
+ -- spell the expression out in full rather than approximating it.
89
+ -- (Goals carry no shipped_at column, so their clock has two terms, not three.)
90
+ ALTER TABLE tasks
91
+ ADD COLUMN IF NOT EXISTS attention_watered_at timestamptz,
92
+ ADD COLUMN IF NOT EXISTS attention_watered_by bigint REFERENCES builders(id);
93
+
94
+ ALTER TABLE goals
95
+ ADD COLUMN IF NOT EXISTS attention_watered_at timestamptz,
96
+ ADD COLUMN IF NOT EXISTS attention_watered_by bigint REFERENCES builders(id);
97
+
98
+ COMMENT ON COLUMN tasks.attention_watered_at IS
99
+ 'When a human last said "leave it, it is fine" about this task rotting (task 1003279). Resets the rot clock without touching the work. NULL = never watered.';
100
+ COMMENT ON COLUMN goals.attention_watered_at IS
101
+ 'When a human last said "leave it, it is fine" about this goal rotting (task 1003279). Resets the rot clock without touching the work. NULL = never watered.';
102
+
103
+ -- The rot read is "open things whose clock is older than N days", so the index is
104
+ -- on (status, clock) — status first because it is the equality predicate and it
105
+ -- cuts the table to the open rows before the range scan ever runs.
106
+ CREATE INDEX IF NOT EXISTS idx_tasks_attention_rot
107
+ ON tasks (status, (GREATEST(updated_at,
108
+ COALESCE(shipped_at, updated_at),
109
+ COALESCE(attention_watered_at, updated_at))));
110
+ CREATE INDEX IF NOT EXISTS idx_goals_attention_rot
111
+ ON goals (status, (GREATEST(updated_at, COALESCE(attention_watered_at, updated_at))));
112
+
113
+ -- ---------------------------------------------------------------------------
114
+ -- 3. Rebuild the two `t.*` views so the new columns are projected.
115
+ -- ---------------------------------------------------------------------------
116
+ --
117
+ -- A Postgres view created with `SELECT t.*` pins its column list at CREATE VIEW
118
+ -- time. core_227's header records six columns that silently never reached the claim
119
+ -- feed exactly this way; bodies below are identical to core_227's (the wildcard
120
+ -- re-expands to every current column).
121
+
122
+ DROP VIEW IF EXISTS claimable_tasks;
123
+ CREATE VIEW claimable_tasks AS
124
+ SELECT t.*,
125
+ pms_calibrated_minutes(t.est_minutes, t.kind) AS est_minutes_calibrated
126
+ FROM tasks t
127
+ WHERE t.status = 'ready'
128
+ AND t.title NOT LIKE '\_\_smoke\_\_%' ESCAPE '\'
129
+ AND NOT EXISTS (
130
+ SELECT 1 FROM task_blockers tb WHERE tb.task_id = t.id
131
+ )
132
+ AND NOT EXISTS (
133
+ SELECT 1 FROM claims c
134
+ JOIN tasks t2 ON t2.id = c.task_id
135
+ WHERE c.released_at IS NULL
136
+ AND t2.touches && t.touches
137
+ );
138
+
139
+ DROP VIEW IF EXISTS tasks_session_fit;
140
+ CREATE VIEW tasks_session_fit AS
141
+ SELECT t.*,
142
+ CASE
143
+ WHEN t.automation_tag = 'overnight-safe' AND COALESCE(t.manual_degree, 5) <= 2
144
+ AND NOT EXISTS (SELECT 1 FROM task_blockers tb WHERE tb.task_id = t.id)
145
+ THEN ARRAY['autonomous','check-in','active']
146
+ WHEN t.automation_tag = 'live-only' OR COALESCE(t.manual_degree, 5) >= 4
147
+ THEN ARRAY['active']
148
+ ELSE ARRAY['check-in','active']
149
+ END AS fits_session_modes,
150
+ pms_calibrated_minutes(t.est_minutes, t.kind) AS est_minutes_calibrated,
151
+ CASE
152
+ WHEN t.credits_reward > 0
153
+ AND pms_calibrated_minutes(t.est_minutes, t.kind) IS NOT NULL
154
+ AND pms_calibrated_minutes(t.est_minutes, t.kind) > 0
155
+ THEN ROUND(t.credits_reward::numeric / pms_calibrated_minutes(t.est_minutes, t.kind), 3)
156
+ ELSE NULL
157
+ END AS credits_per_minute
158
+ FROM tasks t;
159
+
160
+ COMMIT;
161
+
162
+ INSERT INTO schema_migrations (version) VALUES ('core_231_project_settings_and_attention_water')
163
+ ON CONFLICT (version) DO NOTHING;
@@ -386,6 +386,8 @@ function createClient(opts = {}) {
386
386
  postGoalsIdTransfer: (args) => request("POST", "/goals/{id}/transfer", { hasBody: true }, args),
387
387
  // PATCH /goals/{id}/visibility — rank: any-builder — PATCH /goals/:id/visibility
388
388
  patchGoalsIdVisibility: (args) => request("PATCH", "/goals/{id}/visibility", { hasBody: true }, args),
389
+ // POST /goals/{id}/water — rank: any-builder — POST /goals/:id/water
390
+ postGoalsIdWater: (args) => request("POST", "/goals/{id}/water", { hasBody: true }, args),
389
391
  // GET /goals/inbox — rank: any-builder — GET /goals/inbox
390
392
  getGoalsInbox: (args) => request("GET", "/goals/inbox", { hasBody: false }, args),
391
393
  // POST /goals/reorder — rank: metic+archon — POST /goals/reorder
@@ -466,6 +468,8 @@ function createClient(opts = {}) {
466
468
  getInboxAwaitingNod: (args) => request("GET", "/inbox/awaiting-nod", { hasBody: false }, args),
467
469
  // GET /inbox/by-builder/{id} — rank: any-builder — GET /inbox/by-builder/:id
468
470
  getInboxByBuilderId: (args) => request("GET", "/inbox/by-builder/{id}", { hasBody: false }, args),
471
+ // GET /inbox/rotting — rank: any-builder — GET /inbox/rotting
472
+ getInboxRotting: (args) => request("GET", "/inbox/rotting", { hasBody: false }, args),
469
473
  // POST /inbox/score — rank: any-builder — POST /inbox/score
470
474
  postInboxScore: (args) => request("POST", "/inbox/score", { hasBody: true }, args),
471
475
  // GET /inbox/sparks — rank: any-builder — GET /inbox/sparks
@@ -838,6 +842,8 @@ function createClient(opts = {}) {
838
842
  postTasksIdVisual: (args) => request("POST", "/tasks/{id}/visual", { hasBody: true }, args),
839
843
  // POST /tasks/{id}/vote — rank: metic+archon — POST /tasks/:id/vote
840
844
  postTasksIdVote: (args) => request("POST", "/tasks/{id}/vote", { hasBody: true }, args),
845
+ // POST /tasks/{id}/water — rank: any-builder — POST /tasks/:id/water
846
+ postTasksIdWater: (args) => request("POST", "/tasks/{id}/water", { hasBody: true }, args),
841
847
  // GET /tasks/claimable — rank: any-builder — GET /tasks/claimable
842
848
  getTasksClaimable: (args) => request("GET", "/tasks/claimable", { hasBody: false }, args),
843
849
  // GET /tasks/newcomer-floor — rank: any-builder — GET /tasks/newcomer-floor