@bongos/core 1.19.469 → 1.19.471
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 +54 -34
- package/clients/bongos-client/index.d.ts +2 -2
- package/docs/adr/0226-one-stale-timer-reporting-degrades-enforcement-refuses.md +121 -0
- package/docs/adr/README.md +1 -0
- package/docs/api/openapi.json +9 -5
- package/docs/copy-inventory.md +40 -43
- package/docs/copy-registry.json +155 -182
- package/docs/module-api-changelog.md +4 -0
- package/modules/hall-ui/public/atlas.css +96 -88
- package/modules/hall-ui/public/atlas.html +43 -20
- package/modules/hall-ui/public/atlas.js +118 -20
- package/modules/hall-ui/public/atlas.states.json +52 -1
- package/modules/hall-ui/public/modules.css +67 -86
- package/modules/hall-ui/public/modules.html +46 -35
- package/modules/hall-ui/public/modules.js +195 -103
- package/modules/hall-ui/public/modules.states.json +48 -1
- package/modules/hall-ui/public/style.css +8 -8
- package/modules/hall-ui/public/task-lib.js +7 -3
- package/modules/lifecycle/db-ship.js +22 -11
- package/modules/sessions/pulse.js +15 -5
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/fitness.js +6 -0
- package/scripts/gds/pulse.js +48 -24
- package/src/bongos/routes/builders.js +56 -24
- package/src/module-api.js +16 -1
- package/src/stale-timer.js +177 -0
- package/tests/hall_catalog_world.mjs +181 -0
- package/tests/module_api.mjs +1 -0
- package/tests/onboarding_observability.mjs +41 -1
- package/tests/stale_timer.mjs +230 -0
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.
|
|
6
|
-
"core_contract": "1.19.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.19.471",
|
|
6
|
+
"core_contract": "1.19.471",
|
|
7
|
+
"source_commit": "d001dec252ae168f7579878b56235836287043b2",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-02T14:
|
|
9
|
+
"built_at": "2026-09-02T14:57:21.318Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
|
-
"docs_redacted":
|
|
12
|
+
"docs_redacted": 411,
|
|
13
13
|
"agent_docs_stubbed": 24,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 1833,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 2268,
|
|
20
|
+
"tree_sha256": "c11bd5934d037a6d54010a5392df908dcc5965abd13817a89fd5a14ced8be5f6",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/blocker-review/SKILL.md",
|
|
@@ -382,7 +382,7 @@
|
|
|
382
382
|
{
|
|
383
383
|
"path": "clients/bongos-client/index.d.ts",
|
|
384
384
|
"mode": "0000644",
|
|
385
|
-
"sha256": "
|
|
385
|
+
"sha256": "6e1ae5c410c8796df7efcdcd9cbd0ccebe1075da8bc18d503c50196d0e7db683"
|
|
386
386
|
},
|
|
387
387
|
{
|
|
388
388
|
"path": "clients/bongos-client/index.mjs",
|
|
@@ -1634,6 +1634,11 @@
|
|
|
1634
1634
|
"mode": "0000644",
|
|
1635
1635
|
"sha256": "f9f3a0b8ebe2d161e17e1c632bbbda71842c9476958294330420f28c6301f7db"
|
|
1636
1636
|
},
|
|
1637
|
+
{
|
|
1638
|
+
"path": "docs/adr/0226-one-stale-timer-reporting-degrades-enforcement-refuses.md",
|
|
1639
|
+
"mode": "0000644",
|
|
1640
|
+
"sha256": "a3a854a3d04095c04abbe31239aa9931a482a6720d4a04077481e08d6deb6845"
|
|
1641
|
+
},
|
|
1637
1642
|
{
|
|
1638
1643
|
"path": "docs/adr/0227-a-task-network-map-is-refused-the-goal-graph-is-the-map.md",
|
|
1639
1644
|
"mode": "0000644",
|
|
@@ -1642,7 +1647,7 @@
|
|
|
1642
1647
|
{
|
|
1643
1648
|
"path": "docs/adr/README.md",
|
|
1644
1649
|
"mode": "0000644",
|
|
1645
|
-
"sha256": "
|
|
1650
|
+
"sha256": "93a49ca61bb1ea3e3bf9c3325bcae8b174278c8504fb1cc993c03d87bb6ba2f5"
|
|
1646
1651
|
},
|
|
1647
1652
|
{
|
|
1648
1653
|
"path": "docs/api-reference.md",
|
|
@@ -1652,7 +1657,7 @@
|
|
|
1652
1657
|
{
|
|
1653
1658
|
"path": "docs/api/openapi.json",
|
|
1654
1659
|
"mode": "0000644",
|
|
1655
|
-
"sha256": "
|
|
1660
|
+
"sha256": "fa675542b54c8bbd96530ecaac630deaf9873cc9091714eb9fde660e5af2ad4e"
|
|
1656
1661
|
},
|
|
1657
1662
|
{
|
|
1658
1663
|
"path": "docs/architecture.md",
|
|
@@ -1677,12 +1682,12 @@
|
|
|
1677
1682
|
{
|
|
1678
1683
|
"path": "docs/copy-inventory.md",
|
|
1679
1684
|
"mode": "0000644",
|
|
1680
|
-
"sha256": "
|
|
1685
|
+
"sha256": "7726b8ee862ebd75d1a95fafa9a5fb13993b4ad79bacba17f6e401e950082be4"
|
|
1681
1686
|
},
|
|
1682
1687
|
{
|
|
1683
1688
|
"path": "docs/copy-registry.json",
|
|
1684
1689
|
"mode": "0000644",
|
|
1685
|
-
"sha256": "
|
|
1690
|
+
"sha256": "34b21e7e0c7135f4a20cc821174fc2e05ccfee4aa3691855197aa3356356e308"
|
|
1686
1691
|
},
|
|
1687
1692
|
{
|
|
1688
1693
|
"path": "docs/design/apex-pass-2-direction.md",
|
|
@@ -2132,7 +2137,7 @@
|
|
|
2132
2137
|
{
|
|
2133
2138
|
"path": "docs/module-api-changelog.md",
|
|
2134
2139
|
"mode": "0000644",
|
|
2135
|
-
"sha256": "
|
|
2140
|
+
"sha256": "8ce1c73b94d8342cc8b9eadfab359c9feea250b74587b58b70055e671a29388c"
|
|
2136
2141
|
},
|
|
2137
2142
|
{
|
|
2138
2143
|
"path": "docs/modules-contract.md",
|
|
@@ -3992,22 +3997,22 @@
|
|
|
3992
3997
|
{
|
|
3993
3998
|
"path": "modules/hall-ui/public/atlas.css",
|
|
3994
3999
|
"mode": "0000644",
|
|
3995
|
-
"sha256": "
|
|
4000
|
+
"sha256": "135d4554ff853a52ce5536fe8e3a39864055301457afd3d30eb726686df7795a"
|
|
3996
4001
|
},
|
|
3997
4002
|
{
|
|
3998
4003
|
"path": "modules/hall-ui/public/atlas.html",
|
|
3999
4004
|
"mode": "0000644",
|
|
4000
|
-
"sha256": "
|
|
4005
|
+
"sha256": "5be54f6e574fd171cfccad405e006873ff110206777c95bd6f482befe0e9c600"
|
|
4001
4006
|
},
|
|
4002
4007
|
{
|
|
4003
4008
|
"path": "modules/hall-ui/public/atlas.js",
|
|
4004
4009
|
"mode": "0000644",
|
|
4005
|
-
"sha256": "
|
|
4010
|
+
"sha256": "c94e2c4f04cdaaff36185f5e6b572c0716185cc615c03ccd4dbef63ca9f14577"
|
|
4006
4011
|
},
|
|
4007
4012
|
{
|
|
4008
4013
|
"path": "modules/hall-ui/public/atlas.states.json",
|
|
4009
4014
|
"mode": "0000644",
|
|
4010
|
-
"sha256": "
|
|
4015
|
+
"sha256": "cad620c960fb6bb8533d40a1e1e9b841b998b30ff073dabaca8623ee183d142c"
|
|
4011
4016
|
},
|
|
4012
4017
|
{
|
|
4013
4018
|
"path": "modules/hall-ui/public/box-panel.js",
|
|
@@ -4322,22 +4327,22 @@
|
|
|
4322
4327
|
{
|
|
4323
4328
|
"path": "modules/hall-ui/public/modules.css",
|
|
4324
4329
|
"mode": "0000644",
|
|
4325
|
-
"sha256": "
|
|
4330
|
+
"sha256": "9a00c3965acc3defb8d711950ee9a15e941bbd9cc005a48babfa9b0f31a5068d"
|
|
4326
4331
|
},
|
|
4327
4332
|
{
|
|
4328
4333
|
"path": "modules/hall-ui/public/modules.html",
|
|
4329
4334
|
"mode": "0000644",
|
|
4330
|
-
"sha256": "
|
|
4335
|
+
"sha256": "493de4531267d2b0b0ce268cc5998e33d54481442af619771f05dc27182e6f18"
|
|
4331
4336
|
},
|
|
4332
4337
|
{
|
|
4333
4338
|
"path": "modules/hall-ui/public/modules.js",
|
|
4334
4339
|
"mode": "0000644",
|
|
4335
|
-
"sha256": "
|
|
4340
|
+
"sha256": "b2f8498403c739fb39e2391da13e3df3fdcbc52b4365f1c1faf7cfd83e602ff5"
|
|
4336
4341
|
},
|
|
4337
4342
|
{
|
|
4338
4343
|
"path": "modules/hall-ui/public/modules.states.json",
|
|
4339
4344
|
"mode": "0000644",
|
|
4340
|
-
"sha256": "
|
|
4345
|
+
"sha256": "80931a71335a98bdc435d37a2feaeae039e933db69d50d37307e66a252d5d285"
|
|
4341
4346
|
},
|
|
4342
4347
|
{
|
|
4343
4348
|
"path": "modules/hall-ui/public/my-projects.css",
|
|
@@ -4547,7 +4552,7 @@
|
|
|
4547
4552
|
{
|
|
4548
4553
|
"path": "modules/hall-ui/public/style.css",
|
|
4549
4554
|
"mode": "0000644",
|
|
4550
|
-
"sha256": "
|
|
4555
|
+
"sha256": "9bb44b2d5ad45342ebee328430c793d0e48ce921362e87501002a5c73fbe71c9"
|
|
4551
4556
|
},
|
|
4552
4557
|
{
|
|
4553
4558
|
"path": "modules/hall-ui/public/task-detail.js",
|
|
@@ -4557,7 +4562,7 @@
|
|
|
4557
4562
|
{
|
|
4558
4563
|
"path": "modules/hall-ui/public/task-lib.js",
|
|
4559
4564
|
"mode": "0000644",
|
|
4560
|
-
"sha256": "
|
|
4565
|
+
"sha256": "3efb1199b1fc3c979b1c3aaa23d817241b48104dd1dfd40cbc20ed80b1a971cc"
|
|
4561
4566
|
},
|
|
4562
4567
|
{
|
|
4563
4568
|
"path": "modules/hall-ui/public/task.css",
|
|
@@ -4782,7 +4787,7 @@
|
|
|
4782
4787
|
{
|
|
4783
4788
|
"path": "modules/lifecycle/db-ship.js",
|
|
4784
4789
|
"mode": "0000644",
|
|
4785
|
-
"sha256": "
|
|
4790
|
+
"sha256": "d300392b7bf802d00ae79c55ad3b0d9413dc9bb56e7e1105f49656b39abb874a"
|
|
4786
4791
|
},
|
|
4787
4792
|
{
|
|
4788
4793
|
"path": "modules/lifecycle/db-tasks.js",
|
|
@@ -6227,7 +6232,7 @@
|
|
|
6227
6232
|
{
|
|
6228
6233
|
"path": "modules/sessions/pulse.js",
|
|
6229
6234
|
"mode": "0000644",
|
|
6230
|
-
"sha256": "
|
|
6235
|
+
"sha256": "50e7009581961fc1c29cfead85f8d4278e948bfc2a43d5c6b3eaf4e7821ca1af"
|
|
6231
6236
|
},
|
|
6232
6237
|
{
|
|
6233
6238
|
"path": "modules/sessions/routes/sessions.js",
|
|
@@ -6662,12 +6667,12 @@
|
|
|
6662
6667
|
{
|
|
6663
6668
|
"path": "package-lock.json",
|
|
6664
6669
|
"mode": "0000644",
|
|
6665
|
-
"sha256": "
|
|
6670
|
+
"sha256": "df5e2bd3061c2af6b8838339eb4deddc9704c971486718b28b95ebe50d57e9e6"
|
|
6666
6671
|
},
|
|
6667
6672
|
{
|
|
6668
6673
|
"path": "package.json",
|
|
6669
6674
|
"mode": "0000644",
|
|
6670
|
-
"sha256": "
|
|
6675
|
+
"sha256": "ec979bb66bae8d6426a0dfe844c4e7b5e623970a8a0fddf86dcf1f6585f56a35"
|
|
6671
6676
|
},
|
|
6672
6677
|
{
|
|
6673
6678
|
"path": "public-docs/index.html",
|
|
@@ -7097,7 +7102,7 @@
|
|
|
7097
7102
|
{
|
|
7098
7103
|
"path": "scripts/gds/fitness.js",
|
|
7099
7104
|
"mode": "0000644",
|
|
7100
|
-
"sha256": "
|
|
7105
|
+
"sha256": "a7322e6d2510bee2780ca76c68a1c46e7d771cd6675b5f4c223adcf8e001a6c5"
|
|
7101
7106
|
},
|
|
7102
7107
|
{
|
|
7103
7108
|
"path": "scripts/gds/gate-review.js",
|
|
@@ -7447,7 +7452,7 @@
|
|
|
7447
7452
|
{
|
|
7448
7453
|
"path": "scripts/gds/pulse.js",
|
|
7449
7454
|
"mode": "0000644",
|
|
7450
|
-
"sha256": "
|
|
7455
|
+
"sha256": "21cf8741c5e046f61fbe94164d348d476f41bd45cac62c72609580032b1244ba"
|
|
7451
7456
|
},
|
|
7452
7457
|
{
|
|
7453
7458
|
"path": "scripts/gds/push-main.js",
|
|
@@ -8217,7 +8222,7 @@
|
|
|
8217
8222
|
{
|
|
8218
8223
|
"path": "src/bongos/routes/builders.js",
|
|
8219
8224
|
"mode": "0000644",
|
|
8220
|
-
"sha256": "
|
|
8225
|
+
"sha256": "0d27bd6e4db13e9aae324efab98829480fed43abe615c5c56d8e707c6a3465d3"
|
|
8221
8226
|
},
|
|
8222
8227
|
{
|
|
8223
8228
|
"path": "src/bongos/routes/github.js",
|
|
@@ -8322,7 +8327,7 @@
|
|
|
8322
8327
|
{
|
|
8323
8328
|
"path": "src/module-api.js",
|
|
8324
8329
|
"mode": "0000644",
|
|
8325
|
-
"sha256": "
|
|
8330
|
+
"sha256": "abdfc62aa930c361942dfa5aeb9cc8172a20a14ef3ac575bff5d1891f34f96e4"
|
|
8326
8331
|
},
|
|
8327
8332
|
{
|
|
8328
8333
|
"path": "src/module-loader/catalog.js",
|
|
@@ -8364,6 +8369,11 @@
|
|
|
8364
8369
|
"mode": "0000644",
|
|
8365
8370
|
"sha256": "4ba8c3b2e17c2faa38a872fe679e0dc7d032e79e05eb7277d76736201311dcdd"
|
|
8366
8371
|
},
|
|
8372
|
+
{
|
|
8373
|
+
"path": "src/stale-timer.js",
|
|
8374
|
+
"mode": "0000644",
|
|
8375
|
+
"sha256": "2e46d071e5c61d302f34507215dd0a5f20a0887841ae0f7b033be865c2cb8af5"
|
|
8376
|
+
},
|
|
8367
8377
|
{
|
|
8368
8378
|
"path": "tests/abandon_task.mjs",
|
|
8369
8379
|
"mode": "0000644",
|
|
@@ -9694,6 +9704,11 @@
|
|
|
9694
9704
|
"mode": "0000644",
|
|
9695
9705
|
"sha256": "bb66cec6b745cd6e50efd8828d05a75aa68fbc50ed43d8a49988c1246bfd3324"
|
|
9696
9706
|
},
|
|
9707
|
+
{
|
|
9708
|
+
"path": "tests/hall_catalog_world.mjs",
|
|
9709
|
+
"mode": "0000644",
|
|
9710
|
+
"sha256": "79c8759cbb0d17477139acae6e0057caa70021ff0edfeba9c419c14ad729a1ac"
|
|
9711
|
+
},
|
|
9697
9712
|
{
|
|
9698
9713
|
"path": "tests/hall_collab_world.mjs",
|
|
9699
9714
|
"mode": "0000644",
|
|
@@ -10187,7 +10202,7 @@
|
|
|
10187
10202
|
{
|
|
10188
10203
|
"path": "tests/module_api.mjs",
|
|
10189
10204
|
"mode": "0000644",
|
|
10190
|
-
"sha256": "
|
|
10205
|
+
"sha256": "a4980667646d3a3dda1a67906d0735d8acf462989de937e3d342461b275e74eb"
|
|
10191
10206
|
},
|
|
10192
10207
|
{
|
|
10193
10208
|
"path": "tests/module_catalog.mjs",
|
|
@@ -10297,7 +10312,7 @@
|
|
|
10297
10312
|
{
|
|
10298
10313
|
"path": "tests/onboarding_observability.mjs",
|
|
10299
10314
|
"mode": "0000644",
|
|
10300
|
-
"sha256": "
|
|
10315
|
+
"sha256": "973847bd475916e1418a8c70ff9c0f2a1186d55c31e0e8ba4f8b24fabb4c35dc"
|
|
10301
10316
|
},
|
|
10302
10317
|
{
|
|
10303
10318
|
"path": "tests/onboarding_route_signals.mjs",
|
|
@@ -11074,6 +11089,11 @@
|
|
|
11074
11089
|
"mode": "0000644",
|
|
11075
11090
|
"sha256": "27bd2a87e001eec2bc58e13cfa58e53f88c73ebe7504b944c718182247d98ca7"
|
|
11076
11091
|
},
|
|
11092
|
+
{
|
|
11093
|
+
"path": "tests/stale_timer.mjs",
|
|
11094
|
+
"mode": "0000644",
|
|
11095
|
+
"sha256": "770fb94ecdb37da2f7cf74b95d734b789cbbbd5414a97ae5275089302064a71d"
|
|
11096
|
+
},
|
|
11077
11097
|
{
|
|
11078
11098
|
"path": "tests/stale_worktrees.mjs",
|
|
11079
11099
|
"mode": "0000644",
|
|
@@ -31,12 +31,12 @@ export interface GetBoxSshKeyResponse { keys: unknown }
|
|
|
31
31
|
export interface GetBoxTerminalResponse { state: unknown; url: unknown; credential: unknown; hint: unknown; warming_up?: boolean; terminal_at?: unknown }
|
|
32
32
|
export interface GetBoxesResponse { boxes: unknown }
|
|
33
33
|
export interface GetBuildersDirectoryResponse { builders: unknown }
|
|
34
|
-
export interface GetBuildersIdActiveClaimsResponse { builder_id: unknown; claims: unknown; stale_hours: unknown }
|
|
34
|
+
export interface GetBuildersIdActiveClaimsResponse { builder_id: unknown; claims: unknown; stale_hours: unknown; stale_hours_source: unknown }
|
|
35
35
|
export interface GetBuildersIdActivityResponse { builder_id: unknown; section: unknown; role: unknown; rows: unknown; total: unknown }
|
|
36
36
|
export interface GetBuildersIdOnboardingResponse { builder_id: unknown; onboarding: unknown }
|
|
37
37
|
export interface GetBuildersIdProfileResponse { builder: unknown; shipped_count: unknown; recent_ships: unknown; achievements: unknown; cross_project?: unknown; handle?: unknown }
|
|
38
38
|
export interface GetBuildersMePendingRankChangeResponse { change: unknown; greeting?: unknown }
|
|
39
|
-
export interface GetBuildersRosterResponse { builders: unknown; stale_hours: unknown }
|
|
39
|
+
export interface GetBuildersRosterResponse { builders: unknown; stale_hours: unknown; stale_hours_source: unknown }
|
|
40
40
|
export interface GetClosenessForMeResponse { closeness: unknown; count: unknown }
|
|
41
41
|
export interface GetClosenessWeightsResponse { weights: unknown; window_days: unknown; note: unknown }
|
|
42
42
|
export interface GetClosenessWithBuilderIdResponse { closeness: unknown; why: unknown }
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# 0226 — One stale-claim timer; reporting degrades where enforcement refuses
|
|
2
|
+
|
|
3
|
+
- **Status:** Accepted
|
|
4
|
+
- **Date:** 2026-08-28
|
|
5
|
+
- **Tasks:** [#1003476](https://cloudbongos.com/builders#/task/1003476)
|
|
6
|
+
- **Goal:** [#1000072](https://cloudbongos.com/builders#/goal/1000072) — Human legibility, criterion `legibility-sort-and-staleness`.
|
|
7
|
+
- **Builds on:** [#1003278](https://cloudbongos.com/builders#/task/1003278), which gave both work boards a shared *task* staleness vocabulary and left this one — the *claim* timer — named in a comment as the thing still to wire.
|
|
8
|
+
- **Split out of:** [#1003279](https://cloudbongos.com/builders#/task/1003279) on 2026-09-02. This work was authored under that task’s claim, but it answers a different question than that task’s DONE WHEN: 1003279 is the needs-attention **inbox item** a stale *goal or task* raises (with the verbs prune / kill / prioritise / water), and §4 below records that the *task* staleness buckets and a settings knob were deliberately **rejected** as out of scope here. It ships under its own task so the ledger says what the diff does; 1003279 stays open for its inbox half.
|
|
9
|
+
|
|
10
|
+
## 1. The problem: one question, four answers, none of them the project's
|
|
11
|
+
|
|
12
|
+
"How long may a claim go silent before this project calls it abandoned" was
|
|
13
|
+
answered independently in four places:
|
|
14
|
+
|
|
15
|
+
| Surface | Number | Where it came from | What it does |
|
|
16
|
+
|---|---|---|---|
|
|
17
|
+
| `scripts/gds/pulse.js --release-stale` | **6h** | `GDS_STALE_CLAIM_HOURS`, else a literal `6` | **acts** — releases the claim, stamps the session log `[auto-released:stale]`, returns the task to `ready`, pings the builder |
|
|
18
|
+
| `GET /builders/roster` → `getBuildersRoster` | **24h** | a literal, twice (route and db helper) | **reports** — the `stale_claim` flag on the Archon roster |
|
|
19
|
+
| `GET /builders/:id/active-claims` → `<redacted>` | **24h** | a second, byte-identical literal | **reports** — which claim to target for release |
|
|
20
|
+
| `sweepStaleClaims` / `getStaleClaimsForBuilder` | **30 min** | a default parameter | whatever an argument-less caller wanted |
|
|
21
|
+
|
|
22
|
+
The 6-vs-24 pair is the one that hurt. The roster is the screen an Archon opens
|
|
23
|
+
to decide whether to intervene, and for **eighteen hours** after the sweeper had
|
|
24
|
+
already released a claim, tagged it and handed the task back to the queue, that
|
|
25
|
+
screen still showed it healthy. Neither file is wrong when read alone — the
|
|
26
|
+
roster's own comment even says it "mirrors the threshold the stale-claim-release
|
|
27
|
+
sweeper uses", which was true of the *definition* and false of the *number*. A
|
|
28
|
+
mirror that copies the shape and not the value is worse than no mirror: it reads
|
|
29
|
+
as a guarantee.
|
|
30
|
+
|
|
31
|
+
This is the shape goal 1000072 exists to close. A person steering an AI-run
|
|
32
|
+
project is steering by what the surfaces tell them, so a surface that reports a
|
|
33
|
+
different rule from the one the machine enforces is not a cosmetic defect.
|
|
34
|
+
|
|
35
|
+
## 2. Decision
|
|
36
|
+
|
|
37
|
+
**One resolver owns the timer, and every surface that reports or enforces
|
|
38
|
+
staleness reads it.** `src/stale-timer.js` — a KERNEL file, in the same category
|
|
39
|
+
as `instance-config.js` and `branding.js`: host configuration a module reads
|
|
40
|
+
rather than hardcodes. Exposed to modules through the doorway
|
|
41
|
+
(`api.resolveStaleClaimHours`), required directly by core routes and by the CLI.
|
|
42
|
+
|
|
43
|
+
Precedence, widest scope last:
|
|
44
|
+
|
|
45
|
+
1. **An explicit override** — `--threshold-hours`, `?stale_hours=N`. A *lens*, not
|
|
46
|
+
a reconfiguration: an Archon asking "what would 48h show me?" must not change
|
|
47
|
+
what the project does, and the response labels the answer `query` so the
|
|
48
|
+
distinction survives into the payload.
|
|
49
|
+
2. **`<PREFIX>_STALE_CLAIM_HOURS`** — the project's configured timer, resolved
|
|
50
|
+
through `instance-config.resolveEnv`, whose legacy prefix chain still honours
|
|
51
|
+
the `GDS_STALE_CLAIM_HOURS` the live sweeper has always read. **Nothing in a
|
|
52
|
+
running deployment has to change for this to resolve to what it resolves to
|
|
53
|
+
today.** This is also the channel a platform-managed project already has: the
|
|
54
|
+
provisioning runner patches `web.env` in place and restarts ([ADR 0190](<redacted>.md)).
|
|
55
|
+
3. **`6`** — the default. Deliberately the *sweeper's* historical number and not
|
|
56
|
+
the roster's 24: the sweeper is the surface that acts, and adopting 24 would
|
|
57
|
+
have quietly made every project three times more patient with a dead claim
|
|
58
|
+
while looking like a tidy-up.
|
|
59
|
+
|
|
60
|
+
The bound is **1..720 hours** — not taste: `GET /builders/roster` has published
|
|
61
|
+
`1..720` as its `stale_hours` contract since V3.R14, and one timer means one
|
|
62
|
+
bound. (Sub-hour values were previously accepted by that route despite the
|
|
63
|
+
published range; they are now rejected, consistent with what it always claimed.)
|
|
64
|
+
|
|
65
|
+
**Every surface that prints the timer prints where it came from.** The sweep
|
|
66
|
+
header and the auto-release note carry the source; both roster reads return
|
|
67
|
+
`stale_hours_source`. A number with no provenance is precisely what let 6 and 24
|
|
68
|
+
sit side by side in the ops log for a year.
|
|
69
|
+
|
|
70
|
+
## 3. The part that is not obvious: the fail posture is asymmetric
|
|
71
|
+
|
|
72
|
+
A misconfigured timer must not silently become a different policy. But the two
|
|
73
|
+
kinds of caller cannot want the same thing from a bad value, so the resolver
|
|
74
|
+
**reports** rather than throws, and each caller states its own posture:
|
|
75
|
+
|
|
76
|
+
- **Reporting degrades.** A read-only surface takes the default and labels the
|
|
77
|
+
source `default (invalid <VAR>="…": …)`. A 500 here would take down the roster
|
|
78
|
+
an Archon opened *to find the problem*, and hide the typo behind an error page;
|
|
79
|
+
answering with the default and naming the bad value puts it on the screen of
|
|
80
|
+
the one person who can fix it.
|
|
81
|
+
- **Enforcement refuses.** The sweep declines to run at all. It releases other
|
|
82
|
+
people's claims and stamps their session logs, and no reload undoes that — so
|
|
83
|
+
"probably 6" is not an answer it is allowed to give. An explicit
|
|
84
|
+
`--threshold-hours` that is unusable is likewise a usage error, as before.
|
|
85
|
+
|
|
86
|
+
Stated as a rule: **a surface that only reads may guess and say so; a surface
|
|
87
|
+
that acts may not guess.**
|
|
88
|
+
|
|
89
|
+
## 4. What was rejected
|
|
90
|
+
|
|
91
|
+
- **A new `config/stale.json` pair.** The env route already *is* the per-project
|
|
92
|
+
channel (the runner writes `web.env`), and a config file would have added a
|
|
93
|
+
fourth tier to a precedence whose whole point is that there is one place to
|
|
94
|
+
look.
|
|
95
|
+
- **Making the roster's 24 the shared default.** It is the number nothing acted
|
|
96
|
+
on. Unifying on it changes real behaviour under cover of a refactor.
|
|
97
|
+
- **Throwing from the resolver.** It would have forced the roster to either 500
|
|
98
|
+
or re-implement the fallback, which is a second reader — the original bug.
|
|
99
|
+
- **Folding in the boards' 14/30/60-day task buckets.** A different question:
|
|
100
|
+
those are *days*, about a *task* going untouched, and they are a browsing lens
|
|
101
|
+
with no enforcement behind them. Two questions may have two vocabularies; only
|
|
102
|
+
one of them may be a number typed twice.
|
|
103
|
+
- **A DB row + settings UI for the timer.** A reasonable next step, and out of
|
|
104
|
+
scope here: the defect was four numbers, not the absence of a knob. Adding a
|
|
105
|
+
row before the readers agreed would have produced a fifth.
|
|
106
|
+
|
|
107
|
+
## 5. Consequences
|
|
108
|
+
|
|
109
|
+
- `GET /builders/roster` and `GET /builders/:id/active-claims` gain
|
|
110
|
+
`stale_hours_source`; their default `stale_hours` changes from `24` to the
|
|
111
|
+
project timer (`6` unless configured). Additive to the payload; the default is
|
|
112
|
+
a **deliberate** behaviour change — it is the fix.
|
|
113
|
+
- Both routes now share one `staleHoursFor(req, res)` helper. Two copies of a
|
|
114
|
+
literal is how the drift happened twice in one file.
|
|
115
|
+
- `sweepStaleClaims` / `getStaleClaimsForBuilder` no longer have a number of
|
|
116
|
+
their own; an argument-less call means the project's timer, resolved per call
|
|
117
|
+
so an env change lands on the next sweep and not the next restart.
|
|
118
|
+
- Pinned by `tests/stale_timer.mjs` (the resolver executed, the sameness of every
|
|
119
|
+
surface asserted, and both fail postures) plus the updated roster assertions in
|
|
120
|
+
`tests/onboarding_observability.mjs` — whose old `stale_hours === 24` line *was*
|
|
121
|
+
the eighteen-hour lie, written down as an expectation.
|
package/docs/adr/README.md
CHANGED
|
@@ -313,5 +313,6 @@ This keeps the decision history honest and traceable.
|
|
|
313
313
|
| 0224 | [**Module-owned skills reach the core checkout as untracked, self-ignored copies with a manifest: the self-copy guard stays, the dev path is a flag, and the file map skips what the module documents** ([task 1003330](https://cloudbongos.com/builders#/task/1003330) · goal 1000083). `materializeClaude`'s self-copy guard is right (a core copying itself is a no-op at best), but Claude Code discovers only `.claude/skills/` at the project root, so a builder IN the core checkout could not invoke any of the nine ui-design skills written there. Decisions: **D1** `node scripts/gds/claude-materialize.js --module-skills-only [--dry-run] [--clean]` calls `materializeModuleSkillsIntoCore`: the same roster, gate and slug rule as step 1b, verbatim copies (no bongosify: the checkout runs `node scripts/gds/…`), a core-owned name kept, a disabled module's excluded; the guard untouched. **D2** untracked and self-ignored: a generated `.claude/skills/.module-skills.json` manifest and a `.gitignore` that ignores itself, the manifest and every materialised dir; each run removes what the last manifest named then lands the current set (byte-identical on repeat; a dropped declaration disappears); `--clean` stops after the removal. **D3** `gen-file-map.js` and `tests/file_map.mjs` skip materialised names by the manifest (a module skill is documented by its module's README, not by the notes sidecar). **D4** init from a checkout that carries the copies still lands them through step 1b, transformed, and never the dotfiles. **D5** pinned end to end in `tests/claude_materialize.mjs`. Rejected: dropping the guard; committing the copies; symlinks or junctions; notes for dirs not in the repository.](<redacted>.md) | materialiser / skills |
|
|
314
314
|
| 0225 | [**Privacy suppresses cross-project surfaces only: a private builder still appears on their own project's roster** ([task 1003443](https://cloudbongos.com/builders#/task/1003443) · goal 1000075). The accepted privacy spec defined its two flags inconsistently for one case: `hide_stats` suppresses "everything derived from the CROSS-PROJECT rollup" (qualified, so an in-project leaderboard is out of scope) while `account_visibility: private` said "absent from search, LEADERBOARDS, sitemap" (unqualified, so it read as covering the in-project one too) — and PV2 demands "ONE shared predicate used by every public read: … leaderboards …" while `modules/economy/credits.js`'s `leaderboard()` is a bare `SELECT … FROM builders` with no join and no predicate. Read one way that is an unshipped PV2 violation in main; read the other it is correct, and nothing settled which. **Owner decision (2026-08-30): privacy suppresses CROSS-PROJECT surfaces only.** A `private` builder is absent from `/u/<handle>`, platform search, the sitemap, Explore and the recruiting reach, and still appears on the roster of a project they build on — a project's record of its own work is the project's business. Two consequences are the point of recording it: `credits.leaderboard()` is **correct as written** (adding a predicate there would be a regression, not a fix — exactly what a literal PV2 reading would have done), and PV2's "leaderboards" means the cross-project ones. The spec was amended rather than annotated: the D1 row and both `active · public` matrix rows now say *cross-project*, the two `active · private` rows drop "absent everywhere" (the file's most misreadable phrase) for "absent from every cross-project surface", PV2's ship-table row is qualified, and the boundary lands as the **third rule** under D2 beside "`hide_stats` beats everything". Rejected: making `private` hide a builder from projects they build on (it reads as more protective and breaks the ledger — a roster that omits contributors cannot show who built the thing, and credits would accrue to someone invisible on the surface explaining them); and leaving it as a dated spec note (it pins shipped code as deliberately predicate-free, which is an ADR's job).](<redacted>.md) | privacy / spec |
|
|
315
315
|
| 0227 | [**A task network map is refused; the goal graph is the map, and search answers findability** ([task 1003282](https://cloudbongos.com/builders#/task/1003282) · goal 1000072, criterion `legibility-project-map`, which gates itself on this spike). Measured across a swept density range (p=2%–40% of tasks carrying a dependency): a node-link view of ~1,357 tasks is confetti, not structure, and densifying it draws forty goals badly while claiming to show a thousand tasks — a confident picture of nothing is the opposite of the legibility this goal serves. Decision: NO whole-corpus task map at any density; the readable surface is the ~40-node GOAL graph as the entry point with a per-goal DEPENDENCY FOREST as the drill-down, queried when a goal is opened rather than zoomed out of a global map. Every edge a map could draw already exists in the DB and none needs a new table — `task_dependencies` (020), `tasks.parent_task_id`, `tasks.goal_id`, `task_criteria`→`done_when_criteria`, `idea_inbox.promoted_to_task_id`, `idea_inbox.merged_into_idea_id` (`core_195`), `tasks.version_id` (membership, never an edge). **Findability (goal 1000027) answers the owner’s question more cheaply than a map**: the ask splits into “find what I was brainstorming”, which `/recall` (ADR 0060) plus the two lineage columns already answers end to end and which wants a text LINEAGE TRAIL — one thing and its ancestry — not a picture; and “see the shape of the portfolio”, the only half a graph earns, which is about ~40 goals and not about tasks. So the trail outranks the map and is sequenced first. Accessibility: the TABLE is the primary surface and the graph the enhancement (not an `aria-label` on an SVG blob, not a toggle hiding the accessible path), no sideways scroll on the page (the graph scrolls inside its own container), and category is text as well as colour since the ADR 0192 set is not colour-distinguishable at forty nodes. Ranked list: (1) measure the real density against the live DB — it GATES and may cancel the rest, (2) the lineage trail, (3) the goal graph, (4) the per-goal forest. Rejected: view A (every task); view D (densified); a prototype as the output (the criterion forbade it — `spike-task-map.js` is a measuring instrument under `docs/research/`, wired to no route); and building before measuring. Honest limit: the corpus is SYNTHETIC (the live DB was unreachable), so the verdict is robust across the sweep but the specific numbers are model output — which is why item 1 is a measurement against the real DB.](<redacted>.md) | GDS / legibility + hall |
|
|
316
|
+
| 0226 | [**One stale-claim timer; reporting degrades where enforcement refuses** ([task 1003476](https://cloudbongos.com/builders#/task/1003476), goal 1000072, criterion `legibility-sort-and-staleness`). "How long may a claim go silent" had FOUR answers and none of them was the project's: the sweeper that actually releases a claim read `GDS_STALE_CLAIM_HOURS` (else 6h); `GET /builders/roster` and `GET /builders/:id/active-claims` each hardcoded 24h in byte-identical blocks; and the two `session_pulses` helpers defaulted to 30 minutes. The 6-vs-24 pair is the harm: the roster is the screen an Archon opens to decide whether to intervene, and for **eighteen hours** after the sweeper had released a claim, stamped its session log `[auto-released:stale]` and returned the task to `ready`, that screen still showed it healthy — while the roster's own comment claimed to "mirror" the sweeper, true of the definition and false of the number. Decision: ONE resolver, `src/stale-timer.js` (a KERNEL file, the `instance-config.js`/`branding.js` category — host config a module reads, never hardcodes), reached by modules through the doorway and by core routes and the CLI directly. Precedence: explicit override (`--threshold-hours`, `?stale_hours=` — a LENS, labelled `query`, never a reconfiguration) → `<PREFIX>_STALE_CLAIM_HOURS` via `instance-config.resolveEnv`, whose legacy prefix chain still honours the live `GDS_STALE_CLAIM_HOURS` so **nothing in a running deployment has to change** (and which is the channel ADR 0190's runner already patches into `web.env`) → **6**, the SWEEP's historical number and deliberately not the roster's 24, because the sweep is the surface that acts and unifying on 24 would have made every project three times more patient with a dead claim under cover of a refactor. Bound 1..720h — the range `/builders/roster` has published since V3.R14; one timer, one bound. Every surface that prints the timer prints its PROVENANCE (`stale_hours_source`, the sweep header, the auto-release note): a number without it is exactly what let 6 and 24 coexist unnoticed. **The fail posture is asymmetric and that is the decision**: the resolver reports an unusable value rather than throwing, then a surface that only READS degrades to the default and names the bad var (500-ing the roster an Archon opened to find the problem would hide the problem), while the SWEEP refuses to run at all (it releases other people's claims and stamps their logs; no reload undoes that) — a surface that only reads may guess and say so, a surface that acts may not guess. Both routes now share one `staleHoursFor(req, res)`; two copies of a literal is how the drift happened twice inside one file. Rejected: a new `config/stale.json` tier (the env route already IS the per-project channel); making 24 the shared default; throwing from the resolver (forces a second reader — the original bug); folding in the boards' 14/30/60-DAY task buckets (a different question — days, about a task, a browsing lens with no enforcement behind it); a DB row + settings UI (a reasonable next step, but the defect was four numbers, not a missing knob — a row added before the readers agreed would have been a fifth).](<redacted>.md) | GDS / claims + ops |
|
|
316
317
|
|
|
317
318
|
> ⚠️ **Numbering collisions are now machine-enforced, not narrated.** Twenty numbers are shared by two ADRs each — assigned in parallel sessions before anything checked. The files keep their filenames (renumbering would break every existing citation, and a number once assigned is never reused), and this table disambiguates each pair as `NNNN-a` / `NNNN-b`. The authoritative list is `LEGACY_DUPLICATE_ADRS` in [`scripts/gds/adr-namespace.js`](../../scripts/gds/adr-namespace.js), frozen by exact filename: a **new** collision — or a third file joining a legacy number, or a rename of either half — hard-fails the `unit` CI gate, as does an index row that points at no file or an ADR with no row. The list may only shrink. This paragraph used to narrate the collisions one by one and had fallen eight behind reality; see [ADR 0195](<redacted>.md) for why the check exists and why the pairs are grandfathered rather than renumbered.
|
package/docs/api/openapi.json
CHANGED
|
@@ -2591,7 +2591,7 @@
|
|
|
2591
2591
|
"builders"
|
|
2592
2592
|
],
|
|
2593
2593
|
"summary": "GET /builders/roster",
|
|
2594
|
-
"description": "GET /builders/roster — V3.R14 (task 238) onboarding observability. Archon-only roster of every builder with their identity + onboarding signals + active-claim posture. Powers the management panel in the /builders hall so the Archon can promote / demote / release-stale-claim without dropping into psql. Optional query param: ?stale_hours=N
|
|
2594
|
+
"description": "GET /builders/roster — V3.R14 (task 238) onboarding observability. Archon-only roster of every builder with their identity + onboarding signals + active-claim posture. Powers the management panel in the /builders hall so the Archon can promote / demote / release-stale-claim without dropping into psql. Optional query param: ?stale_hours=N. Defines what counts as \"stale\" for the per-row stale_claim flag. The DEFAULT is the project's configured stale-claim timer (task 1003476, goal 1000072) — literally the number the sweeper releases on, not a lookalike: this route defaulted to a hardcoded 24 while the sweeper acted at 6, so the Archon's screen called a claim healthy for eighteen hours after the machine had already taken it away. The param stays as a LENS (\"what would 48h show me?\"), and the response reports both the number and where it came from so the two can never quietly diverge again. rank: archon — surfaces every builder's onboarding state. Gate: requirePermission('builder.roster.read') — BV1.R104; the same atom /grades/by-builder and /builders/:id/active-claims already gate on (ADR 0151).\n\n**Rank:** `metic+archon` — Metic or Archon rank (review/triage powers).\n\n**Permissions:** `builder.roster.read` (all required).",
|
|
2595
2595
|
"x-rank": "metic+archon",
|
|
2596
2596
|
"x-source": "src/bongos/routes/builders.js",
|
|
2597
2597
|
"x-permissions": [
|
|
@@ -15830,12 +15830,14 @@
|
|
|
15830
15830
|
"properties": {
|
|
15831
15831
|
"builder_id": {},
|
|
15832
15832
|
"claims": {},
|
|
15833
|
-
"stale_hours": {}
|
|
15833
|
+
"stale_hours": {},
|
|
15834
|
+
"stale_hours_source": {}
|
|
15834
15835
|
},
|
|
15835
15836
|
"required": [
|
|
15836
15837
|
"builder_id",
|
|
15837
15838
|
"claims",
|
|
15838
|
-
"stale_hours"
|
|
15839
|
+
"stale_hours",
|
|
15840
|
+
"stale_hours_source"
|
|
15839
15841
|
]
|
|
15840
15842
|
},
|
|
15841
15843
|
"GetBuildersIdActivityResponse": {
|
|
@@ -15897,11 +15899,13 @@
|
|
|
15897
15899
|
"type": "object",
|
|
15898
15900
|
"properties": {
|
|
15899
15901
|
"builders": {},
|
|
15900
|
-
"stale_hours": {}
|
|
15902
|
+
"stale_hours": {},
|
|
15903
|
+
"stale_hours_source": {}
|
|
15901
15904
|
},
|
|
15902
15905
|
"required": [
|
|
15903
15906
|
"builders",
|
|
15904
|
-
"stale_hours"
|
|
15907
|
+
"stale_hours",
|
|
15908
|
+
"stale_hours_source"
|
|
15905
15909
|
]
|
|
15906
15910
|
},
|
|
15907
15911
|
"GetClosenessForMeResponse": {
|