@bongos/core 1.19.348 → 1.19.350
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 +82 -42
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +8 -0
- package/clients/bongos-client/index.cjs +8 -0
- package/clients/bongos-client/index.d.ts +11 -0
- package/clients/bongos-client/index.mjs +8 -0
- package/docs/adr/0184-a-task-visual-inherits-its-summary-audience.md +49 -0
- package/docs/adr/README.md +1 -0
- package/docs/api/openapi.json +208 -3
- package/docs/api-reference.md +10 -2
- package/docs/architecture.md +6 -1
- package/docs/copy-inventory.md +38 -38
- package/docs/copy-registry.json +40 -40
- package/docs/file-map.md +2 -1
- package/migrations/core_223_task_visual.sql +33 -0
- package/modules/dev-box/app/src/vendor/bongos-client.cjs +8 -0
- package/modules/hall-ui/public/hall-kit.js +33 -0
- package/modules/hall-ui/public/hall-render.js +1 -0
- package/modules/hall-ui/public/profile.html +4 -1
- package/modules/hall-ui/public/profile.js +7 -1
- package/modules/hall-ui/public/style.css +29 -0
- package/modules/hall-ui/public/task.js +30 -0
- package/modules/lifecycle/db-analytics.js +3 -1
- package/modules/lifecycle/github-push.js +51 -4
- package/modules/lifecycle/module.json +2 -1
- package/modules/lifecycle/routes/tasks.js +22 -2
- package/modules/lifecycle/routes/visuals.js +216 -0
- package/modules/lifecycle/task-visual-db.js +46 -0
- package/modules/lifecycle/task-visuals.js +270 -0
- package/modules/status-ui/public/status.js +33 -0
- package/modules/status-ui/public/style.css +14 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/ship-flow.js +11 -1
- package/scripts/gds/ship-land.js +24 -2
- package/scripts/gds/ship-visual.js +68 -0
- package/scripts/gds/task.js +62 -0
- package/src/bongos/routes/public.js +8 -0
- package/src/bongos/routes.js +5 -1
- package/src/module-api.js +5 -1
- package/tests/ship_publish_error.mjs +86 -2
- package/tests/task_visual_route_gate.mjs +151 -0
- package/tests/task_visuals.mjs +200 -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.350",
|
|
6
|
+
"core_contract": "1.19.350",
|
|
7
|
+
"source_commit": "b3898b596361d562a903c957d10018ca30a88239",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-08-
|
|
9
|
+
"built_at": "2026-08-25T04:58:04.601Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
|
-
"docs_redacted":
|
|
12
|
+
"docs_redacted": 329,
|
|
13
13
|
"agent_docs_stubbed": 22,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 1506,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 1857,
|
|
20
|
+
"tree_sha256": "5f758a2af4bf5a44b2f4f32cbc908d1eff7b70dca35b889880a63f5c768fb78d",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/blocker-review/SKILL.md",
|
|
@@ -347,12 +347,12 @@
|
|
|
347
347
|
{
|
|
348
348
|
"path": "clients/bongos-client/README.md",
|
|
349
349
|
"mode": "0000644",
|
|
350
|
-
"sha256": "
|
|
350
|
+
"sha256": "8c0c4d213d8968fe1180a945aa847c5cca97e73ae3497290f69200a02f0e3995"
|
|
351
351
|
},
|
|
352
352
|
{
|
|
353
353
|
"path": "clients/bongos-client/bongos-client.global.js",
|
|
354
354
|
"mode": "0000644",
|
|
355
|
-
"sha256": "
|
|
355
|
+
"sha256": "762aa91de1b50fa63f8e8d1b1dbdd775b6103c6b8a833dd5f6de4721df7cae15"
|
|
356
356
|
},
|
|
357
357
|
{
|
|
358
358
|
"path": "clients/bongos-client/examples/hello-world.mjs",
|
|
@@ -362,17 +362,17 @@
|
|
|
362
362
|
{
|
|
363
363
|
"path": "clients/bongos-client/index.cjs",
|
|
364
364
|
"mode": "0000644",
|
|
365
|
-
"sha256": "
|
|
365
|
+
"sha256": "5f5c9e0ebb6ef5cded0454b08270938e2ade486e13ede3edd89c23fe93f0798d"
|
|
366
366
|
},
|
|
367
367
|
{
|
|
368
368
|
"path": "clients/bongos-client/index.d.ts",
|
|
369
369
|
"mode": "0000644",
|
|
370
|
-
"sha256": "
|
|
370
|
+
"sha256": "8a03a6a6fa27ca367cef614e2d03bdf8c2363b50e5a11eca3b9e6faa803080f7"
|
|
371
371
|
},
|
|
372
372
|
{
|
|
373
373
|
"path": "clients/bongos-client/index.mjs",
|
|
374
374
|
"mode": "0000644",
|
|
375
|
-
"sha256": "
|
|
375
|
+
"sha256": "1eb6f3523f67f7a017cb5c28c087aa00363f05335679f3379859aab43bb4d29c"
|
|
376
376
|
},
|
|
377
377
|
{
|
|
378
378
|
"path": "clients/bongos-client/package.json",
|
|
@@ -1399,6 +1399,11 @@
|
|
|
1399
1399
|
"mode": "0000644",
|
|
1400
1400
|
"sha256": "e7d0245b21406ddcd118dce29ffb1df5a4797e33d66231aa6bc734316c19530f"
|
|
1401
1401
|
},
|
|
1402
|
+
{
|
|
1403
|
+
"path": "docs/adr/0184-a-task-visual-inherits-its-summary-audience.md",
|
|
1404
|
+
"mode": "0000644",
|
|
1405
|
+
"sha256": "e9b240d6586225b4b71bfa5cbb9ee06b9067394c8787c228e6572f14c9383391"
|
|
1406
|
+
},
|
|
1402
1407
|
{
|
|
1403
1408
|
"path": "docs/adr/0184-ship-requires-an-assurance.md",
|
|
1404
1409
|
"mode": "0000644",
|
|
@@ -1437,22 +1442,22 @@
|
|
|
1437
1442
|
{
|
|
1438
1443
|
"path": "docs/adr/README.md",
|
|
1439
1444
|
"mode": "0000644",
|
|
1440
|
-
"sha256": "
|
|
1445
|
+
"sha256": "a1fc3b27fff2453bc6d419b118f93cf34be562bb264441b44184176049f878eb"
|
|
1441
1446
|
},
|
|
1442
1447
|
{
|
|
1443
1448
|
"path": "docs/api-reference.md",
|
|
1444
1449
|
"mode": "0000644",
|
|
1445
|
-
"sha256": "
|
|
1450
|
+
"sha256": "f8f2920b3448a54530c8da2ae8a2c809c46b3ad8425fe24e3320d782c92e48bb"
|
|
1446
1451
|
},
|
|
1447
1452
|
{
|
|
1448
1453
|
"path": "docs/api/openapi.json",
|
|
1449
1454
|
"mode": "0000644",
|
|
1450
|
-
"sha256": "
|
|
1455
|
+
"sha256": "86c346871a6181f8396b8daa5dcaa59180109a414162389786a23a43b51b93d6"
|
|
1451
1456
|
},
|
|
1452
1457
|
{
|
|
1453
1458
|
"path": "docs/architecture.md",
|
|
1454
1459
|
"mode": "0000644",
|
|
1455
|
-
"sha256": "
|
|
1460
|
+
"sha256": "92cbe2a55fff82a842e7ac3668c99d4eef05e922079d036a5984f607a64496ae"
|
|
1456
1461
|
},
|
|
1457
1462
|
{
|
|
1458
1463
|
"path": "docs/branding-contract.md",
|
|
@@ -1472,12 +1477,12 @@
|
|
|
1472
1477
|
{
|
|
1473
1478
|
"path": "docs/copy-inventory.md",
|
|
1474
1479
|
"mode": "0000644",
|
|
1475
|
-
"sha256": "
|
|
1480
|
+
"sha256": "c9747b5e5e84179f9f5b33abd89c7b38e520328273a2f977a8d2abacacf1f318"
|
|
1476
1481
|
},
|
|
1477
1482
|
{
|
|
1478
1483
|
"path": "docs/copy-registry.json",
|
|
1479
1484
|
"mode": "0000644",
|
|
1480
|
-
"sha256": "
|
|
1485
|
+
"sha256": "44564011072520a55824d40f2ed824a3a4238c362f41416cf3e43b2bac6a06eb"
|
|
1481
1486
|
},
|
|
1482
1487
|
{
|
|
1483
1488
|
"path": "docs/design-contract.md",
|
|
@@ -1587,7 +1592,7 @@
|
|
|
1587
1592
|
{
|
|
1588
1593
|
"path": "docs/file-map.md",
|
|
1589
1594
|
"mode": "0000644",
|
|
1590
|
-
"sha256": "
|
|
1595
|
+
"sha256": "74102dbb050a3e9df3ade05ef25d8704319e7b1fac801b784f1ed8c3ea1b7566"
|
|
1591
1596
|
},
|
|
1592
1597
|
{
|
|
1593
1598
|
"path": "docs/handoff-template.md",
|
|
@@ -2479,6 +2484,11 @@
|
|
|
2479
2484
|
"mode": "0000644",
|
|
2480
2485
|
"sha256": "8fc38c0353811699e19d861321d4fca8fe8fd5cca1f66ff96312d7f44fb0290e"
|
|
2481
2486
|
},
|
|
2487
|
+
{
|
|
2488
|
+
"path": "migrations/core_223_task_visual.sql",
|
|
2489
|
+
"mode": "0000644",
|
|
2490
|
+
"sha256": "45e57200c3fe78481afb0be2941002ec052bb85bd0953385f08ee82243ba0281"
|
|
2491
|
+
},
|
|
2482
2492
|
{
|
|
2483
2493
|
"path": "migrations/core_224_goal_subtitle.sql",
|
|
2484
2494
|
"mode": "0000644",
|
|
@@ -2847,7 +2857,7 @@
|
|
|
2847
2857
|
{
|
|
2848
2858
|
"path": "modules/dev-box/app/src/vendor/bongos-client.cjs",
|
|
2849
2859
|
"mode": "0000644",
|
|
2850
|
-
"sha256": "
|
|
2860
|
+
"sha256": "5f5c9e0ebb6ef5cded0454b08270938e2ade486e13ede3edd89c23fe93f0798d"
|
|
2851
2861
|
},
|
|
2852
2862
|
{
|
|
2853
2863
|
"path": "modules/dev-box/app/test/unit.mjs",
|
|
@@ -3537,12 +3547,12 @@
|
|
|
3537
3547
|
{
|
|
3538
3548
|
"path": "modules/hall-ui/public/hall-kit.js",
|
|
3539
3549
|
"mode": "0000644",
|
|
3540
|
-
"sha256": "
|
|
3550
|
+
"sha256": "eea1102877233794d84a6f76187a9fbd26c4a58cf523df3bf0a31c041ead9933"
|
|
3541
3551
|
},
|
|
3542
3552
|
{
|
|
3543
3553
|
"path": "modules/hall-ui/public/hall-render.js",
|
|
3544
3554
|
"mode": "0000644",
|
|
3545
|
-
"sha256": "
|
|
3555
|
+
"sha256": "f9be7f42df75e5cac2ef917ddac0f22c294ab0eba687c0dbffc15a5fe31a36dd"
|
|
3546
3556
|
},
|
|
3547
3557
|
{
|
|
3548
3558
|
"path": "modules/hall-ui/public/hall-widgets.js",
|
|
@@ -3687,12 +3697,12 @@
|
|
|
3687
3697
|
{
|
|
3688
3698
|
"path": "modules/hall-ui/public/profile.html",
|
|
3689
3699
|
"mode": "0000644",
|
|
3690
|
-
"sha256": "
|
|
3700
|
+
"sha256": "b40e046ca91cb99f0f8a701bd4cc96c03b23432691e256c97cd55527c0be604b"
|
|
3691
3701
|
},
|
|
3692
3702
|
{
|
|
3693
3703
|
"path": "modules/hall-ui/public/profile.js",
|
|
3694
3704
|
"mode": "0000644",
|
|
3695
|
-
"sha256": "
|
|
3705
|
+
"sha256": "a63cb32c0c8b8fa5628e80bcf1d27b8a556340d585c38f98a7c3bee629eb6804"
|
|
3696
3706
|
},
|
|
3697
3707
|
{
|
|
3698
3708
|
"path": "modules/hall-ui/public/ranks.html",
|
|
@@ -3767,7 +3777,7 @@
|
|
|
3767
3777
|
{
|
|
3768
3778
|
"path": "modules/hall-ui/public/style.css",
|
|
3769
3779
|
"mode": "0000644",
|
|
3770
|
-
"sha256": "
|
|
3780
|
+
"sha256": "2c2f2196d75881495c8ba4dcec5e4e69bb78860e993ed3d22a8ee5299d250dcb"
|
|
3771
3781
|
},
|
|
3772
3782
|
{
|
|
3773
3783
|
"path": "modules/hall-ui/public/task-detail.js",
|
|
@@ -3787,7 +3797,7 @@
|
|
|
3787
3797
|
{
|
|
3788
3798
|
"path": "modules/hall-ui/public/task.js",
|
|
3789
3799
|
"mode": "0000644",
|
|
3790
|
-
"sha256": "
|
|
3800
|
+
"sha256": "f2c4c154920e8f1019fb1628e187201e6e582ec61b04fa9fb9620be097f44b62"
|
|
3791
3801
|
},
|
|
3792
3802
|
{
|
|
3793
3803
|
"path": "modules/hall-ui/public/watch.css",
|
|
@@ -3917,7 +3927,7 @@
|
|
|
3917
3927
|
{
|
|
3918
3928
|
"path": "modules/lifecycle/db-analytics.js",
|
|
3919
3929
|
"mode": "0000644",
|
|
3920
|
-
"sha256": "
|
|
3930
|
+
"sha256": "1291eaf95b6518537312e894b15a4ad3ebbfea783d2e3bd2fbab1d0b9a08ea90"
|
|
3921
3931
|
},
|
|
3922
3932
|
{
|
|
3923
3933
|
"path": "modules/lifecycle/db-claim-reads.js",
|
|
@@ -3997,7 +4007,7 @@
|
|
|
3997
4007
|
{
|
|
3998
4008
|
"path": "modules/lifecycle/github-push.js",
|
|
3999
4009
|
"mode": "0000644",
|
|
4000
|
-
"sha256": "
|
|
4010
|
+
"sha256": "d50b04376a9725bae0f8fe9c81166224eb47242655fec49f33f89b0f87b555f0"
|
|
4001
4011
|
},
|
|
4002
4012
|
{
|
|
4003
4013
|
"path": "modules/lifecycle/goal-advisory.js",
|
|
@@ -4062,7 +4072,7 @@
|
|
|
4062
4072
|
{
|
|
4063
4073
|
"path": "modules/lifecycle/module.json",
|
|
4064
4074
|
"mode": "0000644",
|
|
4065
|
-
"sha256": "
|
|
4075
|
+
"sha256": "2c7c98cd6d811d23abfbd6448572ec44c6199f7c15514b0c09924126199e23f9"
|
|
4066
4076
|
},
|
|
4067
4077
|
{
|
|
4068
4078
|
"path": "modules/lifecycle/pollers/publish-reconciler.js",
|
|
@@ -4132,13 +4142,18 @@
|
|
|
4132
4142
|
{
|
|
4133
4143
|
"path": "modules/lifecycle/routes/tasks.js",
|
|
4134
4144
|
"mode": "0000644",
|
|
4135
|
-
"sha256": "
|
|
4145
|
+
"sha256": "b29d9bffce9c81e0f5e02ea2da387e15dc6f0bf8d96820931c29275861eeaf66"
|
|
4136
4146
|
},
|
|
4137
4147
|
{
|
|
4138
4148
|
"path": "modules/lifecycle/routes/versions.js",
|
|
4139
4149
|
"mode": "0000644",
|
|
4140
4150
|
"sha256": "044ca86e5605c19dd8e523da9eb061b4a90ed0af199ec1288c3c5f1b1bb8f46b"
|
|
4141
4151
|
},
|
|
4152
|
+
{
|
|
4153
|
+
"path": "modules/lifecycle/routes/visuals.js",
|
|
4154
|
+
"mode": "0000644",
|
|
4155
|
+
"sha256": "e6e64fe479722f147dce98b9ff190348cd40949ada3a207236655d632bdef94b"
|
|
4156
|
+
},
|
|
4142
4157
|
{
|
|
4143
4158
|
"path": "modules/lifecycle/ship-assurance.js",
|
|
4144
4159
|
"mode": "0000644",
|
|
@@ -4159,6 +4174,16 @@
|
|
|
4159
4174
|
"mode": "0000644",
|
|
4160
4175
|
"sha256": "205bbe37f27191c0404fe03d1e84ce313fc3fdbde0bfcd00c65aca2971c6e163"
|
|
4161
4176
|
},
|
|
4177
|
+
{
|
|
4178
|
+
"path": "modules/lifecycle/task-visual-db.js",
|
|
4179
|
+
"mode": "0000644",
|
|
4180
|
+
"sha256": "f37de3d572a5a283986222c223b91447e0994d4b5253a51c5dc83073ed08bf45"
|
|
4181
|
+
},
|
|
4182
|
+
{
|
|
4183
|
+
"path": "modules/lifecycle/task-visuals.js",
|
|
4184
|
+
"mode": "0000644",
|
|
4185
|
+
"sha256": "f5133f1a27404712a7cce9e9fa43ff016ba2342571fd8e109228e46486941c47"
|
|
4186
|
+
},
|
|
4162
4187
|
{
|
|
4163
4188
|
"path": "modules/lifecycle/version-close.js",
|
|
4164
4189
|
"mode": "0000644",
|
|
@@ -4752,22 +4777,22 @@
|
|
|
4752
4777
|
{
|
|
4753
4778
|
"path": "modules/status-ui/public/status.js",
|
|
4754
4779
|
"mode": "0000644",
|
|
4755
|
-
"sha256": "
|
|
4780
|
+
"sha256": "792c478f89a21a71161417a5a1842d603d48cc6d46a1c984abf9b2797e6fc6e2"
|
|
4756
4781
|
},
|
|
4757
4782
|
{
|
|
4758
4783
|
"path": "modules/status-ui/public/style.css",
|
|
4759
4784
|
"mode": "0000644",
|
|
4760
|
-
"sha256": "
|
|
4785
|
+
"sha256": "75a40d70207669ab57b0be05e4b6be70a2936cb9febf333b0a34224a80047e7e"
|
|
4761
4786
|
},
|
|
4762
4787
|
{
|
|
4763
4788
|
"path": "package-lock.json",
|
|
4764
4789
|
"mode": "0000644",
|
|
4765
|
-
"sha256": "
|
|
4790
|
+
"sha256": "2fd58f9d0189864a4c8d2c3162a8770850b9c2e40a424f770d7674680215ccb6"
|
|
4766
4791
|
},
|
|
4767
4792
|
{
|
|
4768
4793
|
"path": "package.json",
|
|
4769
4794
|
"mode": "0000644",
|
|
4770
|
-
"sha256": "
|
|
4795
|
+
"sha256": "ed85b50d7ee00f78cda9cd034c43d319d0d99c2350a63f48b92fc374df73f5e4"
|
|
4771
4796
|
},
|
|
4772
4797
|
{
|
|
4773
4798
|
"path": "public-docs/index.html",
|
|
@@ -6072,7 +6097,7 @@
|
|
|
6072
6097
|
{
|
|
6073
6098
|
"path": "scripts/gds/ship-flow.js",
|
|
6074
6099
|
"mode": "0000644",
|
|
6075
|
-
"sha256": "
|
|
6100
|
+
"sha256": "9cc28796753779cc054036bbaae841ff798d27c92ad98e37ff877662b3f063ce"
|
|
6076
6101
|
},
|
|
6077
6102
|
{
|
|
6078
6103
|
"path": "scripts/gds/ship-git.js",
|
|
@@ -6102,7 +6127,7 @@
|
|
|
6102
6127
|
{
|
|
6103
6128
|
"path": "scripts/gds/ship-land.js",
|
|
6104
6129
|
"mode": "0000644",
|
|
6105
|
-
"sha256": "
|
|
6130
|
+
"sha256": "e091557b4cc29c081cb82f9cd650b4c110f1dede4e72b704893f8397cd7bbaf2"
|
|
6106
6131
|
},
|
|
6107
6132
|
{
|
|
6108
6133
|
"path": "scripts/gds/ship-memory-sync.js",
|
|
@@ -6144,6 +6169,11 @@
|
|
|
6144
6169
|
"mode": "0000644",
|
|
6145
6170
|
"sha256": "ff7e9d74c4349a3c5bb41df121165f4868fd78913211872c4b055a7282463144"
|
|
6146
6171
|
},
|
|
6172
|
+
{
|
|
6173
|
+
"path": "scripts/gds/ship-visual.js",
|
|
6174
|
+
"mode": "0000644",
|
|
6175
|
+
"sha256": "21ce15f1e49388bb3f8a46b9cfd8b05b23b77efbe93228ca04d9b13740e6d901"
|
|
6176
|
+
},
|
|
6147
6177
|
{
|
|
6148
6178
|
"path": "scripts/gds/ship.js",
|
|
6149
6179
|
"mode": "0000644",
|
|
@@ -6212,7 +6242,7 @@
|
|
|
6212
6242
|
{
|
|
6213
6243
|
"path": "scripts/gds/task.js",
|
|
6214
6244
|
"mode": "0000644",
|
|
6215
|
-
"sha256": "
|
|
6245
|
+
"sha256": "88b9b721b3911957b13aa5e8023e6d41534e34afd4bbc70f40435efc52e2017b"
|
|
6216
6246
|
},
|
|
6217
6247
|
{
|
|
6218
6248
|
"path": "scripts/gds/triage.js",
|
|
@@ -6492,7 +6522,7 @@
|
|
|
6492
6522
|
{
|
|
6493
6523
|
"path": "src/bongos/routes.js",
|
|
6494
6524
|
"mode": "0000644",
|
|
6495
|
-
"sha256": "
|
|
6525
|
+
"sha256": "ede634ea623ec035a5ebab896f1f97e67e142c882cade5afaeb3e3c253fc28f8"
|
|
6496
6526
|
},
|
|
6497
6527
|
{
|
|
6498
6528
|
"path": "src/bongos/routes/CLAUDE.md",
|
|
@@ -6567,7 +6597,7 @@
|
|
|
6567
6597
|
{
|
|
6568
6598
|
"path": "src/bongos/routes/public.js",
|
|
6569
6599
|
"mode": "0000644",
|
|
6570
|
-
"sha256": "
|
|
6600
|
+
"sha256": "b4f0f1d10bebd66259b48bb72af121ae9ef94e774c8b840456aa7c2e0aabdf52"
|
|
6571
6601
|
},
|
|
6572
6602
|
{
|
|
6573
6603
|
"path": "src/bongos/routes/security.js",
|
|
@@ -6622,7 +6652,7 @@
|
|
|
6622
6652
|
{
|
|
6623
6653
|
"path": "src/module-api.js",
|
|
6624
6654
|
"mode": "0000644",
|
|
6625
|
-
"sha256": "
|
|
6655
|
+
"sha256": "60a8faf9804d21e1e7a71b48b407d0f1e3ef9fcc01532ca382dce975ef440e6d"
|
|
6626
6656
|
},
|
|
6627
6657
|
{
|
|
6628
6658
|
"path": "src/module-loader/catalog.js",
|
|
@@ -8977,7 +9007,7 @@
|
|
|
8977
9007
|
{
|
|
8978
9008
|
"path": "tests/ship_publish_error.mjs",
|
|
8979
9009
|
"mode": "0000644",
|
|
8980
|
-
"sha256": "
|
|
9010
|
+
"sha256": "5c0d5961408ac202eb53fe648c4c8b00e5552b231b35f39a39e006b705cb34f1"
|
|
8981
9011
|
},
|
|
8982
9012
|
{
|
|
8983
9013
|
"path": "tests/ship_resume.mjs",
|
|
@@ -9134,6 +9164,16 @@
|
|
|
9134
9164
|
"mode": "0000644",
|
|
9135
9165
|
"sha256": "cfb57e35ce65f991309e4de64c03f9252e19117a1ad406cf9bb04c1716f57746"
|
|
9136
9166
|
},
|
|
9167
|
+
{
|
|
9168
|
+
"path": "tests/task_visual_route_gate.mjs",
|
|
9169
|
+
"mode": "0000644",
|
|
9170
|
+
"sha256": "35654aab981213d7a944183e8710f1bef967c109886f996dfeb7dfc82bf665e7"
|
|
9171
|
+
},
|
|
9172
|
+
{
|
|
9173
|
+
"path": "tests/task_visuals.mjs",
|
|
9174
|
+
"mode": "0000644",
|
|
9175
|
+
"sha256": "3ff18b286a68b244a272b3f2606ae33b298622347aae97305152f235ca57b330"
|
|
9176
|
+
},
|
|
9137
9177
|
{
|
|
9138
9178
|
"path": "tests/tool-scan-fixtures/README.md",
|
|
9139
9179
|
"mode": "0000644",
|
|
@@ -5,7 +5,7 @@ A **generated**, zero-dependency typed client for the Bongos API — produced fr
|
|
|
5
5
|
by hand; it regenerates when the spec changes, so it can never drift from the routes.
|
|
6
6
|
|
|
7
7
|
- API version: **v1** (served at `/api/bongos/v1`)
|
|
8
|
-
-
|
|
8
|
+
- 307 operations across 49 resource groups
|
|
9
9
|
|
|
10
10
|
## Install
|
|
11
11
|
|
|
@@ -716,6 +716,10 @@ function createClient(opts = {}) {
|
|
|
716
716
|
// POST /sso/token — rank: public — POST /sso/token
|
|
717
717
|
postSsoToken: (args) => request("POST", "/sso/token", { hasBody: true }, args),
|
|
718
718
|
},
|
|
719
|
+
"taskVisuals": {
|
|
720
|
+
// GET /task-visuals/{name} — rank: public — GET /task-visuals/:name
|
|
721
|
+
getTaskVisualsName: (args) => request("GET", "/task-visuals/{name}", { hasBody: false }, args),
|
|
722
|
+
},
|
|
719
723
|
"tasks": {
|
|
720
724
|
// GET /tasks — rank: any-builder — GET /tasks
|
|
721
725
|
getTasks: (args) => request("GET", "/tasks", { hasBody: false }, args),
|
|
@@ -759,6 +763,10 @@ function createClient(opts = {}) {
|
|
|
759
763
|
getTasksIdPublishStatus: (args) => request("GET", "/tasks/{id}/publish-status", { hasBody: false }, args),
|
|
760
764
|
// POST /tasks/{id}/ship — rank: any-builder — POST /tasks/:id/ship
|
|
761
765
|
postTasksIdShip: (args) => request("POST", "/tasks/{id}/ship", { hasBody: true }, args),
|
|
766
|
+
// DELETE /tasks/{id}/visual — rank: any-builder — DELETE /tasks/:id/visual
|
|
767
|
+
deleteTasksIdVisual: (args) => request("DELETE", "/tasks/{id}/visual", { hasBody: false }, args),
|
|
768
|
+
// POST /tasks/{id}/visual — rank: any-builder — POST /tasks/:id/visual
|
|
769
|
+
postTasksIdVisual: (args) => request("POST", "/tasks/{id}/visual", { hasBody: true }, args),
|
|
762
770
|
// POST /tasks/{id}/vote — rank: metic+archon — POST /tasks/:id/vote
|
|
763
771
|
postTasksIdVote: (args) => request("POST", "/tasks/{id}/vote", { hasBody: true }, args),
|
|
764
772
|
// GET /tasks/claimable — rank: any-builder — GET /tasks/claimable
|
|
@@ -715,6 +715,10 @@ function createClient(opts = {}) {
|
|
|
715
715
|
// POST /sso/token — rank: public — POST /sso/token
|
|
716
716
|
postSsoToken: (args) => request("POST", "/sso/token", { hasBody: true }, args),
|
|
717
717
|
},
|
|
718
|
+
"taskVisuals": {
|
|
719
|
+
// GET /task-visuals/{name} — rank: public — GET /task-visuals/:name
|
|
720
|
+
getTaskVisualsName: (args) => request("GET", "/task-visuals/{name}", { hasBody: false }, args),
|
|
721
|
+
},
|
|
718
722
|
"tasks": {
|
|
719
723
|
// GET /tasks — rank: any-builder — GET /tasks
|
|
720
724
|
getTasks: (args) => request("GET", "/tasks", { hasBody: false }, args),
|
|
@@ -758,6 +762,10 @@ function createClient(opts = {}) {
|
|
|
758
762
|
getTasksIdPublishStatus: (args) => request("GET", "/tasks/{id}/publish-status", { hasBody: false }, args),
|
|
759
763
|
// POST /tasks/{id}/ship — rank: any-builder — POST /tasks/:id/ship
|
|
760
764
|
postTasksIdShip: (args) => request("POST", "/tasks/{id}/ship", { hasBody: true }, args),
|
|
765
|
+
// DELETE /tasks/{id}/visual — rank: any-builder — DELETE /tasks/:id/visual
|
|
766
|
+
deleteTasksIdVisual: (args) => request("DELETE", "/tasks/{id}/visual", { hasBody: false }, args),
|
|
767
|
+
// POST /tasks/{id}/visual — rank: any-builder — POST /tasks/:id/visual
|
|
768
|
+
postTasksIdVisual: (args) => request("POST", "/tasks/{id}/visual", { hasBody: true }, args),
|
|
761
769
|
// POST /tasks/{id}/vote — rank: metic+archon — POST /tasks/:id/vote
|
|
762
770
|
postTasksIdVote: (args) => request("POST", "/tasks/{id}/vote", { hasBody: true }, args),
|
|
763
771
|
// GET /tasks/claimable — rank: any-builder — GET /tasks/claimable
|
|
@@ -13,6 +13,7 @@ export interface DeleteInboxIdSparkResponse { idea: unknown; sparked: boolean; u
|
|
|
13
13
|
export interface DeleteMemoryFileResponse { deleted: boolean; builder_id: unknown; path: unknown }
|
|
14
14
|
export interface DeleteTasksIdCriteriaCriterionIdResponse { ok: boolean; removed: unknown }
|
|
15
15
|
export interface DeleteTasksIdDependenciesDepIdResponse { ok: boolean; removed: unknown }
|
|
16
|
+
export interface DeleteTasksIdVisualResponse { ok: boolean; visual_url: unknown }
|
|
16
17
|
export interface ErrorEnvelope { error: ApiErrorObject }
|
|
17
18
|
export interface GetAccessRequestsResponse { requests: unknown }
|
|
18
19
|
export interface GetAccessRequestsStatusResponse { github_login: unknown; status: unknown; admitted: unknown }
|
|
@@ -332,6 +333,8 @@ export interface PostTasksIdOverrideRequestResponse { ok: boolean; request: unkn
|
|
|
332
333
|
export interface PostTasksIdPublishBranchRequest { branch: string; head_sha: string; bundle_b64: string; value_summary?: string }
|
|
333
334
|
export interface PostTasksIdPublishBranchResponse { ok: boolean; pr_url: unknown; pr_number: unknown; head_sha: unknown; auto_merge: unknown }
|
|
334
335
|
export interface PostTasksIdShipResponse { ok: boolean; task: unknown }
|
|
336
|
+
export interface PostTasksIdVisualRequest { image_b64: string; content_type: string; alt?: string }
|
|
337
|
+
export interface PostTasksIdVisualResponse { ok: boolean; visual_url: unknown; visual_alt: unknown; bytes: unknown }
|
|
335
338
|
export interface PostTasksIdVoteRequest { vote: -1 | 0 | 1 }
|
|
336
339
|
export interface PostTasksIdVoteResponse { ok: boolean; task_id: unknown; my_vote: unknown; net_votes: unknown; total_votes: unknown }
|
|
337
340
|
export interface PostTasksNewcomerRestockResponse { ok: boolean; stamped: unknown; results: unknown }
|
|
@@ -989,6 +992,10 @@ export interface BongosClient {
|
|
|
989
992
|
/** POST /sso/token — rank: public */
|
|
990
993
|
postSsoToken(args: RequestArgs & { body: PostSsoTokenRequest }): Promise<PostSsoTokenResponse>;
|
|
991
994
|
};
|
|
995
|
+
"taskVisuals": {
|
|
996
|
+
/** GET /task-visuals/{name} — rank: public */
|
|
997
|
+
getTaskVisualsName(args?: RequestArgs): Promise<ApiResponse>;
|
|
998
|
+
};
|
|
992
999
|
"tasks": {
|
|
993
1000
|
/** GET /tasks — rank: any-builder */
|
|
994
1001
|
getTasks(args?: RequestArgs): Promise<GetTasksResponse>;
|
|
@@ -1032,6 +1039,10 @@ export interface BongosClient {
|
|
|
1032
1039
|
getTasksIdPublishStatus(args?: RequestArgs): Promise<ApiResponse>;
|
|
1033
1040
|
/** POST /tasks/{id}/ship — rank: any-builder */
|
|
1034
1041
|
postTasksIdShip(args?: RequestArgs): Promise<PostTasksIdShipResponse>;
|
|
1042
|
+
/** DELETE /tasks/{id}/visual — rank: any-builder */
|
|
1043
|
+
deleteTasksIdVisual(args?: RequestArgs): Promise<DeleteTasksIdVisualResponse>;
|
|
1044
|
+
/** POST /tasks/{id}/visual — rank: any-builder */
|
|
1045
|
+
postTasksIdVisual(args: RequestArgs & { body: PostTasksIdVisualRequest }): Promise<PostTasksIdVisualResponse>;
|
|
1035
1046
|
/** POST /tasks/{id}/vote — rank: metic+archon */
|
|
1036
1047
|
postTasksIdVote(args: RequestArgs & { body: PostTasksIdVoteRequest }): Promise<PostTasksIdVoteResponse>;
|
|
1037
1048
|
/** GET /tasks/claimable — rank: any-builder */
|
|
@@ -712,6 +712,10 @@ export function createClient(opts = {}) {
|
|
|
712
712
|
// POST /sso/token — rank: public — POST /sso/token
|
|
713
713
|
postSsoToken: (args) => request("POST", "/sso/token", { hasBody: true }, args),
|
|
714
714
|
},
|
|
715
|
+
"taskVisuals": {
|
|
716
|
+
// GET /task-visuals/{name} — rank: public — GET /task-visuals/:name
|
|
717
|
+
getTaskVisualsName: (args) => request("GET", "/task-visuals/{name}", { hasBody: false }, args),
|
|
718
|
+
},
|
|
715
719
|
"tasks": {
|
|
716
720
|
// GET /tasks — rank: any-builder — GET /tasks
|
|
717
721
|
getTasks: (args) => request("GET", "/tasks", { hasBody: false }, args),
|
|
@@ -755,6 +759,10 @@ export function createClient(opts = {}) {
|
|
|
755
759
|
getTasksIdPublishStatus: (args) => request("GET", "/tasks/{id}/publish-status", { hasBody: false }, args),
|
|
756
760
|
// POST /tasks/{id}/ship — rank: any-builder — POST /tasks/:id/ship
|
|
757
761
|
postTasksIdShip: (args) => request("POST", "/tasks/{id}/ship", { hasBody: true }, args),
|
|
762
|
+
// DELETE /tasks/{id}/visual — rank: any-builder — DELETE /tasks/:id/visual
|
|
763
|
+
deleteTasksIdVisual: (args) => request("DELETE", "/tasks/{id}/visual", { hasBody: false }, args),
|
|
764
|
+
// POST /tasks/{id}/visual — rank: any-builder — POST /tasks/:id/visual
|
|
765
|
+
postTasksIdVisual: (args) => request("POST", "/tasks/{id}/visual", { hasBody: true }, args),
|
|
758
766
|
// POST /tasks/{id}/vote — rank: metic+archon — POST /tasks/:id/vote
|
|
759
767
|
postTasksIdVote: (args) => request("POST", "/tasks/{id}/vote", { hasBody: true }, args),
|
|
760
768
|
// GET /tasks/claimable — rank: any-builder — GET /tasks/claimable
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# ADR 0184 — A task's visual is its value summary in another medium, and inherits that audience
|
|
2
|
+
|
|
3
|
+
- **Status:** accepted
|
|
4
|
+
- **Date:** 2026-08-25
|
|
5
|
+
- **Task:** 1003109 (from owner idea 1000400, filed in Discord `#ideas` 2026-06-24)
|
|
6
|
+
- **Goal:** 1000072 — Human legibility
|
|
7
|
+
|
|
8
|
+
## Context
|
|
9
|
+
|
|
10
|
+
`value_summary` — the plain-language "what this means for a reader" line collected at ship time — already answers *what changed* for someone who does not read code. What it cannot answer is *what it looks like*. The owner's ask was explicit: a builder should be able to show "what they have built in the task … for the whole city to understand the impacts."
|
|
11
|
+
|
|
12
|
+
Everything else in that idea already shipped (the note itself, and the spend half via `/cost`). What was missing was a picture. The only visual path a builder had was a Discord post — outside the ledger, and on a CDN whose links expire.
|
|
13
|
+
|
|
14
|
+
Adding an image to a work-tracking record raises one question that is easy to answer badly: **who may see it?** A screenshot is not a sentence. It carries whatever was on the screen — an unshipped surface, another builder's name, a token in a terminal pane. The tempting move is to give visuals their own visibility control.
|
|
15
|
+
|
|
16
|
+
## Decision
|
|
17
|
+
|
|
18
|
+
**A visual has no audience of its own. It is the same claim as the task's `value_summary`, in another medium, and inherits that summary's audience exactly.**
|
|
19
|
+
|
|
20
|
+
Consequences, in the order they bite:
|
|
21
|
+
|
|
22
|
+
1. **One predicate, stated once.** `task-visuals.isPubliclyViewable(task)` — public means `status ∈ {shipped, confirmed}`, byte-for-byte the test `routes/public.js:projectPublicTask` already applies to a title and a summary. Anything else is served to any authenticated builder, which is the ordinary GDS posture: **not-public means un-published, never hidden**.
|
|
23
|
+
2. **The gate lives inside the serve handler, not in the mounted middleware.** The audience is a property of the *row*, and Express middleware cannot express that. The stored filename therefore carries the task id (`task-<id>-<16hex>.<ext>`), so one lookup resolves it. Every step fails closed — unparseable name → absent task → not-public → `auth.requireBuilder`, never past it.
|
|
24
|
+
3. **The public shipped feed mirrors the predicate in SQL** (`db.recentShippedTasks` nulls the URL for a `security_sensitive` row) so the unauthenticated status dashboard never advertises an image the serve route would refuse. Nulling the URL, rather than selecting the flag and filtering in JS, also keeps `security_sensitive` itself off the public wire. **Change one, change the other** — the two are noted in each other's comments.
|
|
25
|
+
4. **One deliberate narrowing vs `value_summary`:** a `security_sensitive` task's image stays builders-only even once shipped. A summary is a sentence its author chose; a screenshot is whatever was on the screen, and on a security task that is the wrong default to publish.
|
|
26
|
+
|
|
27
|
+
### The optionality is load-bearing
|
|
28
|
+
|
|
29
|
+
"Where relevant" was the owner's own wording, so *optional* is a contract, not a default:
|
|
30
|
+
|
|
31
|
+
- Nothing on the ship path calls the upload. `ship.js --visual` screens the file **before** the claim resolves (bad input costs a retyped command, never a claim) and uploads it **after** (a failed picture prints a warning and the re-attach command; it never fails a ship that already landed).
|
|
32
|
+
- A task with no visual renders **nothing at all** — no placeholder, no empty frame, no "no visual" line. A task without one must never read as unfinished.
|
|
33
|
+
- It is removable. `DELETE /tasks/:id/visual` exists because an image is not prose: the write-once rule of task 1003102 guards a written *claim*, not the picture beside it.
|
|
34
|
+
|
|
35
|
+
### Not an arbitrary-file host
|
|
36
|
+
|
|
37
|
+
Uploads are screened by an extension allowlist, a magic-byte sniff (a mislabeled content-type cannot smuggle a non-image onto disk), a 4 MB cap, and a server-minted filename. **SVG is deliberately absent** from the allowlist: it is an executable document, and these are served from our own origin. The serve route adds `nosniff`, `Content-Disposition: inline`, and a `default-src 'none'; sandbox` CSP. Files live under `var/task-visuals/` — outside the git tree, like the `#bugs` re-hosts (task 936), so a deploy's `git reset --hard` never wipes them.
|
|
38
|
+
|
|
39
|
+
## Alternatives considered
|
|
40
|
+
|
|
41
|
+
- **A per-visual visibility flag.** Rejected: it invents a second privacy axis over the same fact, and two axes over one fact drift. The first time they disagreed, a reader would have to guess which one was the truth.
|
|
42
|
+
- **Reusing `modules/discord/bug-attachments.js` directly.** Rejected: ADR 0083 forbids one module requiring another, and the intake genuinely differs (that lib *downloads* from a CDN and needs an SSRF host allowlist; this one receives bytes from the shipping builder). The shapes are re-stated under the ADR 0091 §2 boundary-forced-duplication rule and cross-referenced in both file headers.
|
|
43
|
+
- **Rendering the image full-size in every feed.** Rejected as unreadable at scale: the ship ledger, a profile, and the status dashboard are scannable lists, so they show a small thumbnail that links to the full file. The task page renders it properly — that is where someone goes to *look*.
|
|
44
|
+
|
|
45
|
+
## Consequences
|
|
46
|
+
|
|
47
|
+
- A non-engineer can open the public status dashboard and **see** what shipped, not only read about it.
|
|
48
|
+
- Any future surface that renders `value_summary` inherits a settled answer for the visual beside it: same place, same audience, no new decision.
|
|
49
|
+
- The two mirrored copies of the audience rule (JS predicate, feed SQL) are a maintenance obligation, accepted deliberately over the alternative of the public feed learning what `security_sensitive` means.
|
package/docs/adr/README.md
CHANGED
|
@@ -257,6 +257,7 @@ This keeps the decision history honest and traceable.
|
|
|
257
257
|
| 0181 | [**Teardown accepts every state but `torn_down`, and deliberately keeps the address** ([task 1003042](https://cloudbongos.com/builders#/task/1003042), goal 1000058). Walk finding N5: an instance at `status='error'` could never be removed — the owner's teardown answered 409 `not_teardownable` and the archon `force-teardown`, which exists as the escape hatch, answered the SAME refusal, because both read one hand-kept whitelist `['provisioning','active','tearing_down']` that omitted `requested` and `error`. No role had a path out, so errored rows accumulated permanently (instance 15 `swag` from 2026-08-07). The whitelist justified itself as "nothing running to tear down", which is FALSE for `error`: a provision failing mid-run lands there AFTER it may have created the database, taken a port, and written the systemd unit, Caddy block and DNS record — a partial standup is the thing most in need of reclaiming. `TEARDOWN_FROM` is now DERIVED from `INSTANCE_STATES` minus `torn_down`, so a future state is teardownable by DEFAULT — the bug was an allow-list that silently omitted states, and for a failure mode whose signature is "a row with no exit", the safe default is that an exit exists. The runner needed no change (its steps are each `allowFail`); only the web guard refused. The one surviving refusal is now ONE shared helper: the copies had drifted, and force-teardown's positional `res.fail` form put the bare code `not_teardownable` on the wire as the human text. **The `port` and `domain` are deliberately NOT released** — releasing the port alone would be WORSE than the status quo, because the `<slug>.caddy` block survives teardown and proxies the hostname to a PORT NUMBER, so a later instance given that port would answer at the torn-down project's still-live address (verified live: two projects torn down 2026-08-13 still resolved on 2026-08-19). Holding the port is what keeps the address inert at a harmless 502; releasing both is [task 1002897](https://cloudbongos.com/builders#/task/1002897). Retention is also correct for revive, which re-uses the same hostname. Copy was corrected to match: it had promised "the address stops answering", which was never true.](0181-teardown-state-gate-and-address-retention.md) | provisioning / lifecycle |
|
|
258
258
|
| 0182 | [**Project visibility is TWO axes: a derived publish gate and a stored join door** ([task 1002320](https://cloudbongos.com/builders#/task/1002320), goal 1000046). Goal 1000046 seeded 31 tasks each citing "per R01" — this ADR — so nothing in the goal could start until it landed: it gated **29 open tasks**, the largest single chokepoint in the repo, while itself parked at `backlog` with no dependencies. The spec its planning task cites was never written, so this is the design of record. The goal's criterion says **four** states (Public/Private/Stealth/Unlisted) and R04 says **three** stored plus a derived dark-matter; both are right and conflating them is the trap. There are two INDEPENDENT axes: **publishability** (DERIVED from the identity gate — name + description, per the owner interview) decides whether a project is on the map at all, and **the join door** (STORED `visibility ∈ public|private|stealth`, owner-set) decides how it renders and who gets in. Four user-facing states = 3 × publishable + one derived state that collapses all three. The column name `visibility` is a mild lie kept for consistency with `goals.visibility` (ADR 0112) — it does NOT decide visibility, publishability does — and the ADR is the mitigation. All three stored states are VISIBLE; only the derived state is invisible, which is what makes the 3+1 split honest rather than a workaround. `private` inherits ADR 0112's exact meaning (visible-but-closed); `stealth` exposes EXISTENCE but not IDENTITY, and its redaction is **server-side in the feed** — the naive client-side version leaks to anyone opening the network tab, and the orbs page is a static site OUTSIDE this repo (R09) so it could never be enforced there. Three mechanisms touch "does this appear" and are separated: `status` (operational), `visibility` (owner), `featured` (Archon editorial) — on the map iff `status='active' AND publishable`, with `featured` only ranking among those. That is a real behaviour change: today's predicate is `featured=true AND status='active'`, so **featuring is currently load-bearing for visibility at all** and the goal's owner-control premise silently does not work without it. Visibility is owned by the INSTANCE and the hub row is a CACHE (ADR 0141 §3 + the admin-console spike's D1) — so `owner_github_id` being "advisory, not an authority" is CORRECT rather than a gap, R05 moves to an instance surface, and R08's absent-report default is fail-closed (unreported ⇒ dark matter, off the map rather than exposed by omission). Per-state join rules compose with the separate `open|closed` join policy ([task 1002817](https://cloudbongos.com/builders#/task/1002817)) by one rule: **visibility sets the ceiling, join policy may only narrow** — otherwise the two settings are a coin-flip decided by evaluation order. Finally, `stealth` COLLIDES with the existing instance-wide pre-launch password gate (`STEALTH_PASSWORD`, `src/bongos/stealth-gate.js`), which means nearly the opposite (a whole instance made unreadable vs a project publicly on the map, anonymously); per ADR 0174 the product meaning keeps the word and the infra rename is filed separately rather than decided by an ADR about visibility. Rejected: a stored 4-valued enum (makes `unlisted` compete with `private`, and lets publishable+unlisted disagree), a separate `published` boolean (can drift from the identity it summarises; derived cannot), and keeping `featured` as the visibility gate (an owner who sets public and appears nowhere has not been given control).](<redacted>.md) | platform-identity / project visibility |
|
|
259
259
|
| 0183 | [**A done-when criterion closes itself; the review queue becomes a place to look** ([task 1002069](https://cloudbongos.com/builders#/task/1002069), goal 1000072, owner request 2026-07-05). ADR 0086 §6 made `satisfied` reachable exactly one way — a Metic+ calling `POST /done-when/:id/satisfy` — even though the signal it gated on is unambiguous (**every** gating task shipped) and a human confirming it adds no information. When nobody runs `/goal-review` the result is not a careful pause but a **lying board**: this session measured goal 1000072 at `6/9 shipped, 0/5 criteria`. Now one helper, `doneWhen.autoSatisfyShippedCriteria(exec, {taskId})`, runs from **two** places for two different reasons — inside `shipTask`'s txn (scoped, **immediate**, on the txn client so a rolled-back ship cannot leave a goal achieved) and unscoped on every `publish-reconciler` tick (**backlog drain** for criteria whose tasks shipped before this existed, since nothing will ship against them again, plus **self-heal**). Idempotent by construction — it reads only `satisfied=false` rows — which is what makes an every-tick sweep safe. **The load-bearing subtlety is the delivery guard.** Auto-close needs BOTH "nothing still pending" AND **≥ 1 task actually `shipped`**; the second is not redundant, because since [task 1002651](https://cloudbongos.com/builders#/task/1002651) made `abandoned` terminal-not-pending, a criterion whose tasks were **all cut** satisfies the first clause *vacuously* and would auto-close on ZERO delivered work, cascading its goal to `achieved` (measured: `buildCriterionProgress` returns `pending_review:true` for exactly that shape). Yet `pending_review` **keeps** that wider meaning on purpose — it asks "should a human LOOK at this", and 1002651 chose that an all-abandoned criterion surfaces so someone decides whether it still means anything. So: everything that auto-closes was flagged; not everything flagged auto-closes. An earlier draft "aligned" the two predicates and broke that intent — `tests/criterion_progress.mjs` caught it; **do not re-align them**, the gap is the design. `/goal-review` is kept, demoted from gate to viewing surface; manual `POST /satisfy` stays for what automation cannot judge (a criterion with no linked tasks never auto-closes). Honest limits recorded: auto-satisfy cannot help a criterion nothing is LINKED to, and the in-txn close logs before commit (accepted — threading the summary out through `withTx` would cost ~5 lines in the over-threshold `db.js` under shrink-only ratchet policy). Rejected: sweep-only (up to 5 min to see your own goal close), hook-only (never drains the backlog), a stored `pending_review` column (second source of truth), deleting `/goal-review`.](<redacted>.md) | lifecycle / goal tier |
|
|
260
|
+
| 0184 | [**A task's visual is its value summary in another medium, and inherits that audience** ([task 1003109](https://cloudbongos.com/builders#/task/1003109), goal 1000072, from owner idea 1000400 filed in Discord `#ideas` 2026-06-24). A shipped task can now carry ONE optional image beside its `value_summary` — a screenshot of the surface that changed, a before/after, a diagram — so a reader who does not read code can **see** what shipped instead of reading about it. The rest of that idea had already landed (the note itself; the spend half via `/cost`); the picture was the whole gap, and the only path a builder had was a Discord post — outside the ledger, on a CDN whose links expire. The decision that keeps it from rotting is a REFUSAL to invent a second privacy axis: **a visual has no audience of its own.** One predicate says who may see it — `task-visuals.isPubliclyViewable` (public = `shipped|confirmed`, byte-for-byte the test `routes/public.js:projectPublicTask` already applies to a title/summary) — so any future surface rendering `value_summary` inherits a settled answer for the picture beside it. Three consequences are load-bearing. (1) The serve gate lives **inside** the handler, not in mounted middleware, because the audience is a property of the ROW; the stored filename therefore carries the task id (`task-<id>-<16hex>.<ext>`), which is simultaneously the path-traversal defense and how the route finds the task, and every step fails CLOSED (unparseable name → absent task → not-public → `requireBuilder`, never past it). (2) The public shipped feed **mirrors the predicate in SQL** — `db.recentShippedTasks` nulls the URL for a `security_sensitive` row, rather than selecting the flag and filtering in JS, so the unauthenticated dashboard never advertises an image the serve route would refuse AND never learns the flag; change one, change the other. (3) The single deliberate narrowing vs `value_summary`: a security-sensitive task's image stays builders-only even once shipped — a summary is a sentence its author chose, a screenshot is whatever was on the screen. Nothing is HIDDEN by that; non-public means un-published, not invisible. **Optionality is a contract, not a default** ("where relevant" is the owner's wording): `ship.js --visual` screens the file BEFORE the claim resolves (bad input costs a retyped command, never a claim) and uploads AFTER (a failed picture warns and prints the re-attach command; it never fails a ship that already landed), a task with no visual renders **nothing at all** — no placeholder, so it never reads as unfinished — and `DELETE /tasks/:id/visual` exists because an image is not prose (task 1003102's write-once rule guards a written CLAIM, not the picture beside it). Not an arbitrary-file host: extension allowlist + magic-byte sniff + 4 MB cap + server-minted name, **SVG deliberately excluded** (an executable document served from our own origin), `nosniff`/`inline`/`default-src 'none'; sandbox` on the way out, files under `var/task-visuals/` outside the git tree so a deploy's `git reset --hard` cannot wipe them. Rejected: a per-visual visibility flag (two axes over one fact drift, and the first disagreement leaves a reader guessing which is true); requiring `modules/discord/bug-attachments.js` (ADR 0083 forbids module→module, and the intake genuinely differs — that one DOWNLOADS from a CDN and needs an SSRF allowlist; duplication is re-stated under ADR 0091 §2 and cross-referenced in both headers); full-size images in every feed (the ledger, a profile and the dashboard are scannable lists — they get a thumbnail linking to the file; the task page is where you go to look).](<redacted>.md) | lifecycle / hall-ui / status-ui |
|
|
260
261
|
| 0184 | [**A ship must account for itself: `shipTask` requires a land proof or a declared no-artifact** ([task 1002483](https://cloudbongos.com/builders#/task/1002483), goal 1000024). `db.shipTask` is the confirmed→shipped flip and, since [ADR 0120](<redacted>.md), where the CREDIT is booked — yet its only precondition was `WHERE status = 'confirmed'`, nothing about whether code reached main. The two AUTOMATED paths each proved the land first; the MANUAL route passed nothing, so a task confirmed with no pushed code could be shipped by its claim holder **or by any Metic+ sweeping `/merge-mode`**, booking its `task.shipped` credit. It stayed untracked for two months because its tracking task was closed in June asserting task 1001547 had delivered the gate — it had not, blocker 1000047's own final update says so, and the code agreed with the blocker rather than the closure note: **a hole hidden by a record nobody checked**, which is the same failure the gate is about. Decision: the gate lives at the WRITE, not the route (a route gate leaves the invariant unenforced at the layer that moves the ledger, so the next caller reopens it silently), and `shipTask` is FAIL-CLOSED on a required `landAssurance`. TWO kinds, and the second is why this does not make things worse: `proof` (this task's commit is on main) and **`no_artifact`** — a deliberate DB-only ship, an explicitly supported shape (CLAUDE.md "Direct DB writes are work too") that [task 1002478](https://cloudbongos.com/builders#/task/1002478) documents as a dead end below Archon. A naive proof-only gate would have made 1002478 strictly worse; naming no-artifact as a KIND turns it from an absence the gate must be talked out of into a recorded answer. Both carry a reason, audited post-commit and best-effort (an audit miss must never roll back a landed ship). `ship.js` now declares its own `--db-only`/`--allow-empty` lands, without which this fix would have regressed the very flow CLAUDE.md calls supported. Rejected: (b) gate only the route — leaves the write unguarded; (c) record the residual as tolerable — defensible on blast radius and genuinely close, but it would have been the FOURTH record asserting this was handled with nothing checkable behind it. Does NOT gate confirm: a codeless confirm is still accepted, it simply cannot reach `shipped` or pay.](<redacted>.md) | lifecycle / integrity |
|
|
261
262
|
| 0185 | [**The spark-handoff credit split: 70% developer, 30% spark author** ([task 1002923](https://cloudbongos.com/builders#/task/1002923), BV1.R147, goal 1000062 — completing the chain [task 1002920](https://cloudbongos.com/builders#/task/1002920) mark → [task 1002921](https://cloudbongos.com/builders#/task/1002921) queue → [task 1002922](https://cloudbongos.com/builders#/task/1002922) pickup). [ADR 0172](<redacted>.md) built the ideator streams and paid `captured_by`, the only person an idea then had. The spark handoff created a second: R146 develops a spark IN PLACE, preserving the author while recording a `developed_by`. **R146 therefore shipped paying NOTHING on purpose** — not an oversight but a refusal to guess, because `awardIdeaFullPassCredit`'s idempotency predicate is `(builder_id, reason)`, so paying the DEVELOPER would not have blocked a later payment to the AUTHOR and one idea would have paid twice; the cost of that caution was that a developed Full Idea received stream 2 **never** unless its author resubmitted. Owner decision (2026-08-22): **70/30 favouring the developer**, on BOTH existing streams, one ratio so there is one rule to explain. The reasoning is behavioural, not sentimental — developing someone else's spark is the scarce act the queue depends on, and at an even split it pays HALF what authoring your own Full Idea pays for the same writing work, so the rational move is always to write your own and the R145 queue stays a list nobody reads. The author keeps 30% plus their share of stream 1 on every task shipped under the idea, forever. `IDEA_DEVELOPER_SHARE = 0.70` is a **policy constant in `credits.js`** beside the pass bar and quick factor (ADR 0172's own consequence, the ADR 0023 precedent): a share of someone's earnings deserves a review and a commit, not a runtime edit. **The structural half is what retires R146's caution:** the economy module now derives the payees itself (`ideaCreditParties` reads `captured_by` + `developed_by`), so the caller can no longer choose a recipient — the develop route hands over an idea and a score, never a payee, and books the split like any other path (fail-open; the development has already committed). Three arithmetic properties, each a real bug if missed: the total is **conserved** (developer rounded, author gets the REMAINDER — rounding both pays 6 out of a 5-credit payout); a **self-developed** idea collapses to ONE payee, because two rows for one builder would silently UNDER-pay under the per-builder idempotency predicate, not double-pay (the comparison normalises via `String()` since `bigint` arrives as a string, so a type mismatch cannot become a phantom payee); and **zero-value rows are dropped**. Ids otherwise pass through unconverted — `Number()`-ing a `bigint` risks precision. `builder_id` in the idempotency predicate, which entered as an index optimisation (the R143/IC2 grader finding), is now **load-bearing for correctness**: two payees share one `reason`, so a reason-only predicate would refuse the second, and a partially-booked split completes on the next call. **Not decided here:** stream 3's split (ships with the Board Room, task 1002999), and whether `origin='develop'` in `ideas_task_lineage` belongs to whoever promotes a DEVELOPED idea into a task — ADR 0172 §5 says the row is "set at promotion and at develop (R146)", but R146 creates no task, so that origin stays deliberately unwired rather than invented; it changes who stream 1 pays.](<redacted>.md) | economy / ideator lane |
|
|
262
263
|
| 0186 | [**Per-project idea templates: the seam is already built; the economics are the open problem** ([task 1002932](https://cloudbongos.com/builders#/task/1002932), BV1.R156, goal 1000062 — a SPIKE, output is this ADR plus a task list, no implementation). The literal question — how does a founder define their own idea shape without a schema change or forking the scorer — is **already answered by shipped work**: R141 gave every template function an optional `registry` defaulting to the built-in one, so validation and scoring both read the declaration as data; `ideas_grades.template_key` is per-row with a format CHECK whose own comment says a project key "stays addressable"; and `provisioning_instances.settings jsonb` is an existing per-project home. **No schema change, no fork.** Decision A: a **`project-settings` kernel port**, sibling of `builder-settings`, read via `resolveOptional` so a vanilla instance with no provisioning module degrades to the built-in registry; `base-v1` stays the default and the fallback for an unresolvable key. Rejected a new `ideas_templates` table (second home for data that has one) and a config file (per-instance, but one instance hosts many projects). **Decision B is the finding that matters.** Completeness is normalised 0–100 WITHIN its template (mean of per-field ratios), while karma reads it against an absolute bar (`IDEA_FULL_PASS_BAR = 60`, `IDEA_FULL_PASS_CREDIT = 5`) — so template choice becomes a credit-minting lever: `base-v1` needs ~200 characters for 5 credits, a 1-field template with `substanceMin` 10 needs ~10, a ~20× arbitrage handed to exactly the projects this ADR empowers. Shipping A without B would make the feature a way to print credits. The fix separates the two questions one number is answering: **completeness stays relative** (it is the right signal for triage ordering and the hall meter) and **karma stops reading it**, switching to an absolute substance total the scorer already computes per field and currently discards after averaging. An interim guard (≥3 required fields, ≥120 total `substanceMin`) bounds the arbitrage without fixing it. Decision C: an optional per-field `weight` (default 1) turning the unweighted mean into a weighted one — today's behaviour is the all-weights-1 case, so it is a data addition and one arithmetic line. Decision D: the five base fields are **not** mandatory (forcing them would make "define your own" cosmetic), replaced by two constraints — a template must declare ≥1 required field, and a stored template is validated at WRITE time so a malformed one is refused there rather than discovered inside the capture path, which must never throw. **Boundary note:** goal 1000062's wall is `[ideas, hall-ui]`, so the port task (provisioning + kernel) and the karma task (economy) cannot be claimed under it.](<redacted>.md) | ideas / templates |
|