@bongos/core 1.19.920 → 1.19.922
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 +71 -31
- package/.claude/skills/grade-audit/SKILL.md +9 -0
- package/docs/adr/0320-an-empty-diff-is-not-an-ungraded-diff.md +159 -0
- package/docs/adr/0321-brightness-is-a-weighted-share-and-the-targets-are-a-row.md +163 -0
- package/docs/adr/README.md +2 -0
- package/docs/module-api-changelog.md +4 -0
- package/modules/economy/idea-brightness.js +212 -0
- package/modules/economy/idea-size-historical.js +22 -3
- package/modules/economy/idea-size.js +30 -3
- package/modules/grading/grader-no-artifact.js +347 -0
- package/modules/grading/grader.js +6 -0
- package/modules/lifecycle/cascade.js +36 -0
- package/modules/lifecycle/migrations/lifecycle_010_removal_targets.sql +116 -0
- package/modules/lifecycle/module.json +2 -1
- package/modules/lifecycle/removal-targets.js +217 -0
- package/modules/lifecycle/routes/lifecycle.js +9 -0
- package/modules/lifecycle/routes/removal-proposals.js +33 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/provision-core-upgrade.js +26 -2
- package/scripts/gds/run-unit-tests.js +17 -1
- package/scripts/gds/ship-flow.js +54 -7
- package/scripts/gds/ship-grade-steps.js +43 -5
- package/scripts/gds/ship-grade.js +36 -0
- package/src/module-api.js +1 -1
- package/tests/core_upgrade_runner.mjs +56 -0
- package/tests/git_merge_regen.mjs +87 -3
- package/tests/grader_no_artifact.mjs +360 -0
- package/tests/grader_prompt_fencing.mjs +46 -0
- package/tests/idea_brightness.mjs +550 -0
- package/tests/no_artifact_escalation.mjs +13 -3
- package/tests/skip_is_not_pass.mjs +91 -1
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.922",
|
|
6
|
+
"core_contract": "1.19.922",
|
|
7
|
+
"source_commit": "b57f79a73cefa42840ff10d77aab958af1fe4ed4",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-
|
|
9
|
+
"built_at": "2026-09-20T20:40:52.111Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
|
-
"docs_redacted":
|
|
12
|
+
"docs_redacted": 510,
|
|
13
13
|
"agent_docs_stubbed": 26,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 2309,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 2845,
|
|
20
|
+
"tree_sha256": "36516d370c40955c53b7b0b70845d49e03d34aaecf7d5e58a54bf78ed454f694",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/ask-for-help/SKILL.md",
|
|
@@ -152,7 +152,7 @@
|
|
|
152
152
|
{
|
|
153
153
|
"path": ".claude/skills/grade-audit/SKILL.md",
|
|
154
154
|
"mode": "0000644",
|
|
155
|
-
"sha256": "
|
|
155
|
+
"sha256": "c32df53b87b71a15e6b6e605c0c9698a07ff99a94bc6325fd99648d9c2d38122"
|
|
156
156
|
},
|
|
157
157
|
{
|
|
158
158
|
"path": ".claude/skills/grade-recover/SKILL.md",
|
|
@@ -2089,10 +2089,20 @@
|
|
|
2089
2089
|
"mode": "0000644",
|
|
2090
2090
|
"sha256": "2fbb3e040b55162284242f545af76b1254fafbecfe042b8c792dc0dca475c4f8"
|
|
2091
2091
|
},
|
|
2092
|
+
{
|
|
2093
|
+
"path": "docs/adr/0320-an-empty-diff-is-not-an-ungraded-diff.md",
|
|
2094
|
+
"mode": "0000644",
|
|
2095
|
+
"sha256": "e582b4ece7a5b0acf4392d8b3d4942d55b52dc487d7c6a7c3a159203485ec633"
|
|
2096
|
+
},
|
|
2097
|
+
{
|
|
2098
|
+
"path": "docs/adr/0321-brightness-is-a-weighted-share-and-the-targets-are-a-row.md",
|
|
2099
|
+
"mode": "0000644",
|
|
2100
|
+
"sha256": "3a743808de440eebd5fcdd74a4c88e41acc82cc02ce5632cb392b2aa0c05b48e"
|
|
2101
|
+
},
|
|
2092
2102
|
{
|
|
2093
2103
|
"path": "docs/adr/README.md",
|
|
2094
2104
|
"mode": "0000644",
|
|
2095
|
-
"sha256": "
|
|
2105
|
+
"sha256": "caece332e048f6ee15517740255c4d715efc025c82756ac095ccde7814b60daa"
|
|
2096
2106
|
},
|
|
2097
2107
|
{
|
|
2098
2108
|
"path": "docs/api-reference.md",
|
|
@@ -2662,7 +2672,7 @@
|
|
|
2662
2672
|
{
|
|
2663
2673
|
"path": "docs/module-api-changelog.md",
|
|
2664
2674
|
"mode": "0000644",
|
|
2665
|
-
"sha256": "
|
|
2675
|
+
"sha256": "d657d8b2fc7eb2bb4379a22a41d95b835ac655848626120a2218ab7603df5313"
|
|
2666
2676
|
},
|
|
2667
2677
|
{
|
|
2668
2678
|
"path": "docs/modules-contract.md",
|
|
@@ -4224,15 +4234,20 @@
|
|
|
4224
4234
|
"mode": "0000644",
|
|
4225
4235
|
"sha256": "736eed2c89863d6eb28072627a40ac4f8945832cfb7b1d1aba10e6cc65483901"
|
|
4226
4236
|
},
|
|
4237
|
+
{
|
|
4238
|
+
"path": "modules/economy/idea-brightness.js",
|
|
4239
|
+
"mode": "0000644",
|
|
4240
|
+
"sha256": "745da28c27ebcd50b349c3fe027f7e3f14888f9e012e0c9443656de249444ba1"
|
|
4241
|
+
},
|
|
4227
4242
|
{
|
|
4228
4243
|
"path": "modules/economy/idea-size-historical.js",
|
|
4229
4244
|
"mode": "0000644",
|
|
4230
|
-
"sha256": "
|
|
4245
|
+
"sha256": "215eefac80aaf3624a8ff5d5284f29388a2e1b492424dd152b3d9bbe074e3a01"
|
|
4231
4246
|
},
|
|
4232
4247
|
{
|
|
4233
4248
|
"path": "modules/economy/idea-size.js",
|
|
4234
4249
|
"mode": "0000644",
|
|
4235
|
-
"sha256": "
|
|
4250
|
+
"sha256": "8818bea53d2ce90fe0bc98b53bfed1af11c4eeb541942b96c4cf50a162791634"
|
|
4236
4251
|
},
|
|
4237
4252
|
{
|
|
4238
4253
|
"path": "modules/economy/migrations/economy_001_fix_gds_shipper_description.sql",
|
|
@@ -4514,6 +4529,11 @@
|
|
|
4514
4529
|
"mode": "0000644",
|
|
4515
4530
|
"sha256": "597f883d4878a8fb585585331e11dc219e8a57d1459be4039116b60e7fdc556d"
|
|
4516
4531
|
},
|
|
4532
|
+
{
|
|
4533
|
+
"path": "modules/grading/grader-no-artifact.js",
|
|
4534
|
+
"mode": "0000644",
|
|
4535
|
+
"sha256": "ab20c06268f30d0ed7198774d7435237f63440550235bdc5b926870db56886e7"
|
|
4536
|
+
},
|
|
4517
4537
|
{
|
|
4518
4538
|
"path": "modules/grading/grader-prompt.js",
|
|
4519
4539
|
"mode": "0000644",
|
|
@@ -4592,7 +4612,7 @@
|
|
|
4592
4612
|
{
|
|
4593
4613
|
"path": "modules/grading/grader.js",
|
|
4594
4614
|
"mode": "0000644",
|
|
4595
|
-
"sha256": "
|
|
4615
|
+
"sha256": "968363ed43dfd3792462996b061e90cb7af30d9c31a6d6bd39e68ec812b47706"
|
|
4596
4616
|
},
|
|
4597
4617
|
{
|
|
4598
4618
|
"path": "modules/grading/module.json",
|
|
@@ -5512,7 +5532,7 @@
|
|
|
5512
5532
|
{
|
|
5513
5533
|
"path": "modules/lifecycle/cascade.js",
|
|
5514
5534
|
"mode": "0000644",
|
|
5515
|
-
"sha256": "
|
|
5535
|
+
"sha256": "176f7463d6f3c7bf2297760d0934f88528ee180dfb0c9e49a4347a449af2820d"
|
|
5516
5536
|
},
|
|
5517
5537
|
{
|
|
5518
5538
|
"path": "modules/lifecycle/check-rollup.js",
|
|
@@ -5754,6 +5774,11 @@
|
|
|
5754
5774
|
"mode": "0000644",
|
|
5755
5775
|
"sha256": "e98efbb1537f0d99adb6b6b66c1fdccfd4bbac16b97362d479af903eb9de492c"
|
|
5756
5776
|
},
|
|
5777
|
+
{
|
|
5778
|
+
"path": "modules/lifecycle/migrations/lifecycle_010_removal_targets.sql",
|
|
5779
|
+
"mode": "0000644",
|
|
5780
|
+
"sha256": "e3e29e791eeb94ab1380ae2e2a13eafaea413faf0d0324a4e2007ab2b3002fca"
|
|
5781
|
+
},
|
|
5757
5782
|
{
|
|
5758
5783
|
"path": "modules/lifecycle/mingle.js",
|
|
5759
5784
|
"mode": "0000644",
|
|
@@ -5762,7 +5787,7 @@
|
|
|
5762
5787
|
{
|
|
5763
5788
|
"path": "modules/lifecycle/module.json",
|
|
5764
5789
|
"mode": "0000644",
|
|
5765
|
-
"sha256": "
|
|
5790
|
+
"sha256": "36a03e5d8a4fc1129439356ba6daaf284d2cd2ca3d98d35f990e3215ff11198a"
|
|
5766
5791
|
},
|
|
5767
5792
|
{
|
|
5768
5793
|
"path": "modules/lifecycle/pg-constraint-fail.js",
|
|
@@ -5794,6 +5819,11 @@
|
|
|
5794
5819
|
"mode": "0000644",
|
|
5795
5820
|
"sha256": "8270ce55255998a4f0bbf574a54df0ba2597013c727b4afb1ce2abad9c9d4d2a"
|
|
5796
5821
|
},
|
|
5822
|
+
{
|
|
5823
|
+
"path": "modules/lifecycle/removal-targets.js",
|
|
5824
|
+
"mode": "0000644",
|
|
5825
|
+
"sha256": "e80304e7b43fe86852763e2f859bc05c1b88bb50a3eea407342ae6d46b8f9eda"
|
|
5826
|
+
},
|
|
5797
5827
|
{
|
|
5798
5828
|
"path": "modules/lifecycle/rot.js",
|
|
5799
5829
|
"mode": "0000644",
|
|
@@ -5862,7 +5892,7 @@
|
|
|
5862
5892
|
{
|
|
5863
5893
|
"path": "modules/lifecycle/routes/lifecycle.js",
|
|
5864
5894
|
"mode": "0000644",
|
|
5865
|
-
"sha256": "
|
|
5895
|
+
"sha256": "330e42fad79812a05a90256fe31931df2156093b80a648d7c838ecac136c91e4"
|
|
5866
5896
|
},
|
|
5867
5897
|
{
|
|
5868
5898
|
"path": "modules/lifecycle/routes/mingle.js",
|
|
@@ -5882,7 +5912,7 @@
|
|
|
5882
5912
|
{
|
|
5883
5913
|
"path": "modules/lifecycle/routes/removal-proposals.js",
|
|
5884
5914
|
"mode": "0000644",
|
|
5885
|
-
"sha256": "
|
|
5915
|
+
"sha256": "e0a0e14a8818c011334e8f61a50c8dd7ad11b4387acfd6ab9e18e570f54ec6c4"
|
|
5886
5916
|
},
|
|
5887
5917
|
{
|
|
5888
5918
|
"path": "modules/lifecycle/routes/rot.js",
|
|
@@ -7872,12 +7902,12 @@
|
|
|
7872
7902
|
{
|
|
7873
7903
|
"path": "package-lock.json",
|
|
7874
7904
|
"mode": "0000644",
|
|
7875
|
-
"sha256": "
|
|
7905
|
+
"sha256": "0169c3fafa67ebf63c9ae35566e8104bcf210ae80973c8e1837874109fe85354"
|
|
7876
7906
|
},
|
|
7877
7907
|
{
|
|
7878
7908
|
"path": "package.json",
|
|
7879
7909
|
"mode": "0000644",
|
|
7880
|
-
"sha256": "
|
|
7910
|
+
"sha256": "b62dd828c8e8b167dfc4bca7d54fdec8c76b13f89a44e61365d48962abf77ba9"
|
|
7881
7911
|
},
|
|
7882
7912
|
{
|
|
7883
7913
|
"path": "public-docs/index.html",
|
|
@@ -8822,7 +8852,7 @@
|
|
|
8822
8852
|
{
|
|
8823
8853
|
"path": "scripts/gds/provision-core-upgrade.js",
|
|
8824
8854
|
"mode": "0000644",
|
|
8825
|
-
"sha256": "
|
|
8855
|
+
"sha256": "1fd6e38184bb0943fdc22e314b9e6cad0b5ebeae7f7370992adbd3b4f2ad5a9e"
|
|
8826
8856
|
},
|
|
8827
8857
|
{
|
|
8828
8858
|
"path": "scripts/gds/provision-disconnect.js",
|
|
@@ -8977,7 +9007,7 @@
|
|
|
8977
9007
|
{
|
|
8978
9008
|
"path": "scripts/gds/run-unit-tests.js",
|
|
8979
9009
|
"mode": "0000644",
|
|
8980
|
-
"sha256": "
|
|
9010
|
+
"sha256": "d3567553fb963ad78c0ab36dd283fbf5c46e188dd7538e00fed36442f7ab2515"
|
|
8981
9011
|
},
|
|
8982
9012
|
{
|
|
8983
9013
|
"path": "scripts/gds/runner-drift.js",
|
|
@@ -9212,7 +9242,7 @@
|
|
|
9212
9242
|
{
|
|
9213
9243
|
"path": "scripts/gds/ship-flow.js",
|
|
9214
9244
|
"mode": "0000644",
|
|
9215
|
-
"sha256": "
|
|
9245
|
+
"sha256": "b6198e134156b755bcffa3c385c9b2ec780dcd7681791c1d3fb938217bd8102b"
|
|
9216
9246
|
},
|
|
9217
9247
|
{
|
|
9218
9248
|
"path": "scripts/gds/ship-git.js",
|
|
@@ -9222,12 +9252,12 @@
|
|
|
9222
9252
|
{
|
|
9223
9253
|
"path": "scripts/gds/ship-grade-steps.js",
|
|
9224
9254
|
"mode": "0000644",
|
|
9225
|
-
"sha256": "
|
|
9255
|
+
"sha256": "204834c41dd184ebc076dcd57d6a5370a910f4fc9c276bb3984b08b43f550012"
|
|
9226
9256
|
},
|
|
9227
9257
|
{
|
|
9228
9258
|
"path": "scripts/gds/ship-grade.js",
|
|
9229
9259
|
"mode": "0000644",
|
|
9230
|
-
"sha256": "
|
|
9260
|
+
"sha256": "40c6a9f6aa6a75cdf8a5e41bfa8318766636cba64e47aee870c26a719676713d"
|
|
9231
9261
|
},
|
|
9232
9262
|
{
|
|
9233
9263
|
"path": "scripts/gds/ship-honesty.js",
|
|
@@ -9877,7 +9907,7 @@
|
|
|
9877
9907
|
{
|
|
9878
9908
|
"path": "src/module-api.js",
|
|
9879
9909
|
"mode": "0000644",
|
|
9880
|
-
"sha256": "
|
|
9910
|
+
"sha256": "86c69951f0059e5374245ef798c5cd24cd82d836a4ccf706e80f186de986b473"
|
|
9881
9911
|
},
|
|
9882
9912
|
{
|
|
9883
9913
|
"path": "src/module-loader/catalog.js",
|
|
@@ -10792,7 +10822,7 @@
|
|
|
10792
10822
|
{
|
|
10793
10823
|
"path": "tests/core_upgrade_runner.mjs",
|
|
10794
10824
|
"mode": "0000644",
|
|
10795
|
-
"sha256": "
|
|
10825
|
+
"sha256": "5d0b4fa1ec48d40ebc801902b5279475533ae2dc8819e587bb95bc17783cba06"
|
|
10796
10826
|
},
|
|
10797
10827
|
{
|
|
10798
10828
|
"path": "tests/cosmos_token_contrast.mjs",
|
|
@@ -11217,7 +11247,7 @@
|
|
|
11217
11247
|
{
|
|
11218
11248
|
"path": "tests/git_merge_regen.mjs",
|
|
11219
11249
|
"mode": "0000644",
|
|
11220
|
-
"sha256": "
|
|
11250
|
+
"sha256": "79208b90f1b785af56ab057c858e96cea4e67ebf247858457addedce55e71b06"
|
|
11221
11251
|
},
|
|
11222
11252
|
{
|
|
11223
11253
|
"path": "tests/github_push_ancestry.mjs",
|
|
@@ -11649,6 +11679,11 @@
|
|
|
11649
11679
|
"mode": "0000644",
|
|
11650
11680
|
"sha256": "1823c173f811d52d93b63201d0ebb0d85f2b1e42a569e6331580ef097f5c05cc"
|
|
11651
11681
|
},
|
|
11682
|
+
{
|
|
11683
|
+
"path": "tests/grader_no_artifact.mjs",
|
|
11684
|
+
"mode": "0000644",
|
|
11685
|
+
"sha256": "c8450702a2d805d1cf5e826a3dedf49fff6cd01c6ca540fa9c64444fc874d5d3"
|
|
11686
|
+
},
|
|
11652
11687
|
{
|
|
11653
11688
|
"path": "tests/grader_panel.mjs",
|
|
11654
11689
|
"mode": "0000644",
|
|
@@ -11657,7 +11692,7 @@
|
|
|
11657
11692
|
{
|
|
11658
11693
|
"path": "tests/grader_prompt_fencing.mjs",
|
|
11659
11694
|
"mode": "0000644",
|
|
11660
|
-
"sha256": "
|
|
11695
|
+
"sha256": "b626e614de000078eef1b305f6322d94326f032bcbeba2808baaa9dcc8ed3103"
|
|
11661
11696
|
},
|
|
11662
11697
|
{
|
|
11663
11698
|
"path": "tests/grader_quality_window_overflow.mjs",
|
|
@@ -11879,6 +11914,11 @@
|
|
|
11879
11914
|
"mode": "0000644",
|
|
11880
11915
|
"sha256": "0d8f07c64f4d17c8c894b2d6395281882efd4e186c5714fe9aab61e2808d4314"
|
|
11881
11916
|
},
|
|
11917
|
+
{
|
|
11918
|
+
"path": "tests/idea_brightness.mjs",
|
|
11919
|
+
"mode": "0000644",
|
|
11920
|
+
"sha256": "d67d60b1236ccedd01591356f5a5a9a72e4f1efd895ddf5af00c744219a2b3b6"
|
|
11921
|
+
},
|
|
11882
11922
|
{
|
|
11883
11923
|
"path": "tests/idea_capture_grades.mjs",
|
|
11884
11924
|
"mode": "0000644",
|
|
@@ -12507,7 +12547,7 @@
|
|
|
12507
12547
|
{
|
|
12508
12548
|
"path": "tests/no_artifact_escalation.mjs",
|
|
12509
12549
|
"mode": "0000644",
|
|
12510
|
-
"sha256": "
|
|
12550
|
+
"sha256": "f1523ee48247d18c7326b9aa0b442ca2c9fab8bd41dbe9f23a4cb89e1986bee0"
|
|
12511
12551
|
},
|
|
12512
12552
|
{
|
|
12513
12553
|
"path": "tests/oauth_secret.mjs",
|
|
@@ -13682,7 +13722,7 @@
|
|
|
13682
13722
|
{
|
|
13683
13723
|
"path": "tests/skip_is_not_pass.mjs",
|
|
13684
13724
|
"mode": "0000644",
|
|
13685
|
-
"sha256": "
|
|
13725
|
+
"sha256": "db9e246f1ce7ef6306b938d325c450e31f2e834244f64dd2f0aaea96e760d987"
|
|
13686
13726
|
},
|
|
13687
13727
|
{
|
|
13688
13728
|
"path": "tests/smoke_preflight.mjs",
|
|
@@ -20,6 +20,15 @@ node scripts/gds/api.js GET "/api/bongos/tasks/<id>?include=grade"
|
|
|
20
20
|
|
|
21
21
|
Bucket by grade shape: `passed`, failed, `signals.panel_outcome='unavailable'` (outage — no quality signal existed), or no grade row at all.
|
|
22
22
|
|
|
23
|
+
**Then split off the no-artifact species before any leg reads the bucket** (task 1004011, ADR 0320). A grade with `signals.no_artifact_grade === true` judged the builder's **claim that nothing was needed** — the handoff notes and value summary against the task description — not a diff. There is no diff, no panel, and one generalist shot (`signals.lenses_dropped` names the four lenses that did not run). It is a real grade and a real gate, but it measures a different thing, so:
|
|
24
|
+
|
|
25
|
+
- **Do not average or count it alongside diff grades.** A 7.5 over three paragraphs of prose and a 7.5 over a 400-line diff are not the same number.
|
|
26
|
+
- **Leg 2 still applies, and matters more here.** A `blocker`/`major` on a no-artifact pass usually means the *evidence* was thin — queue it the same way.
|
|
27
|
+
- **Leg 3 does not apply** — there is no merged diff to re-read for runtime behaviour. The equivalent question, worth asking on any no-artifact pass that shipped on assertion alone, is: *could a reader today re-run or re-read what the notes claim?* If not, queue it.
|
|
28
|
+
- **A no-artifact FAIL is not an override candidate for Leg 1** unless it also landed. The builder's remedy is better evidence, not a permission.
|
|
29
|
+
|
|
30
|
+
Note `task_grades.grader_kind` is **not** the discriminator: the server records `'subagent'` for everything arriving through `POST /tasks/:id/grade`. `signals.no_artifact_grade` is the durable marker.
|
|
31
|
+
|
|
23
32
|
## Leg 1 — the override ledger
|
|
24
33
|
|
|
25
34
|
For every shipped task whose grade **failed** (or is absent), the land needed an override. Cross-reference the ledger (Archon — `override_request.decide`):
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# ADR 0320 — An empty diff is not an ungraded diff: grade the claim instead
|
|
2
|
+
|
|
3
|
+
- **Status:** Accepted
|
|
4
|
+
- **Date:** 2026-09-20
|
|
5
|
+
- **Task:** 1004011 · goal 1000118 · scoped out of task 1003950 (shape 2)
|
|
6
|
+
- **Supersedes nothing.** Narrows [ADR 0041](0041-temporary-grader-bypass-killswitch.md)'s reach: the `--allow-empty` close is no longer one of the things `task.confirm.grade_bypass` gates.
|
|
7
|
+
- **Drops** task 1004010, the alternative shape (a new permission atom for the same close). Owner decision, 2026-09-20 — see §5.
|
|
8
|
+
|
|
9
|
+
## 1. Context
|
|
10
|
+
|
|
11
|
+
`scripts/gds/ship.js --allow-empty` is how a builder closes a task that produced no
|
|
12
|
+
committed artifact: the premise turned out to be false, the work was a pure
|
|
13
|
+
verification, the change landed directly in the database or on the host.
|
|
14
|
+
|
|
15
|
+
Until now that flag did two things at once. It set `skipGrade = true` — the subagent
|
|
16
|
+
grader never ran — and it therefore routed the close through
|
|
17
|
+
`POST /tasks/:id/confirm`, whose gate is `task.confirm.grade_bypass`, one of the four
|
|
18
|
+
trust-boundary keys [ADR 0157](0157-archon-is-rank-and-identity-only.md) names. In
|
|
19
|
+
practice a Metic could not close an artifact-free task at all without asking an Archon.
|
|
20
|
+
|
|
21
|
+
Task 1003950 shipped a terminating escalation on that refusal so the builder at least
|
|
22
|
+
stopped looping. It deliberately left the underlying shape alone and filed two
|
|
23
|
+
alternatives: give the close its own lower-floored permission atom (1004010), or remove
|
|
24
|
+
the need for a permission by grading something real (1004011, this one).
|
|
25
|
+
|
|
26
|
+
## 2. The two things that were wrong
|
|
27
|
+
|
|
28
|
+
**The permission was the wrong question.** `task.confirm.grade_bypass` means *ratify
|
|
29
|
+
this past a grade that FAILED*. That is a genuine trust-boundary act: a verdict exists
|
|
30
|
+
and a human is overriding it. An `--allow-empty` close is not that. No verdict exists,
|
|
31
|
+
because there was nothing for the grader to read. Charging the same key for "it was
|
|
32
|
+
judged and rejected" and "there was nothing to judge" conflates a override with an
|
|
33
|
+
absence, and the cost of the conflation was that the cheapest, least scrutinised ship
|
|
34
|
+
in the system needed the most powerful key to perform.
|
|
35
|
+
|
|
36
|
+
**Nothing was being checked.** This is the more serious half. On a no-artifact ship the
|
|
37
|
+
grader was skipped *entirely*, so the assertions in the handoff notes — "the premise was
|
|
38
|
+
already fixed", "this was a pure ops verification", "the row was updated directly" —
|
|
39
|
+
went into the ledger unexamined. The ledger is the complete record of what happened to
|
|
40
|
+
the project (CLAUDE.md §8), and on exactly the ships where the record is the *only*
|
|
41
|
+
evidence, nothing read it. A permission gate is not a substitute for scrutiny: an Archon
|
|
42
|
+
clicking confirm is not the same as someone checking whether the claim is true.
|
|
43
|
+
|
|
44
|
+
## 3. Decision
|
|
45
|
+
|
|
46
|
+
**On an `--allow-empty` ship, grade the NOTES (and the value summary) against the task
|
|
47
|
+
description, instead of skipping the grade.** The grader's question becomes: *is this
|
|
48
|
+
task genuinely artifact-free, and does the evidence given support that?* A pass is a
|
|
49
|
+
real passing grade that confirms through the ordinary rank-open path and needs no bypass
|
|
50
|
+
permission. A fail is a real fail, and the remedy is better evidence, not a key.
|
|
51
|
+
|
|
52
|
+
Four sub-decisions carry most of the weight:
|
|
53
|
+
|
|
54
|
+
**D1 — The rubric axes are re-asked, not replaced.** The gate downstream is
|
|
55
|
+
`functional_fidelity >= THRESHOLD AND code_quality >= THRESHOLD AND confidence >= 0.4`,
|
|
56
|
+
and `/grade-audit`, the aggregates and the cost model all read those columns. Inventing
|
|
57
|
+
a third pair of axis names would have meant teaching every one of them a synonym. So the
|
|
58
|
+
axes keep their names and the prompt re-asks them for this species:
|
|
59
|
+
`functional_fidelity` → *is this genuinely artifact-free?*; `code_quality` → *is the
|
|
60
|
+
evidence specific and checkable, or bare assertion?*. The gate arithmetic is untouched.
|
|
61
|
+
|
|
62
|
+
**D2 — Two generalist shots, not the four-lens panel.** The panel is Hacker / Quality /
|
|
63
|
+
Efficiency / (Reader) — lenses over code. Over three paragraphs of prose they are four
|
|
64
|
+
readings of the same short text at four times the cost, so the lenses go, recorded in
|
|
65
|
+
`signals.lenses_dropped` rather than left to be inferred from their absence (the
|
|
66
|
+
trivial-diff precedent, #513: lighter, never silent).
|
|
67
|
+
|
|
68
|
+
The panel's unanimous gate, however, was also the cross-check that makes injection
|
|
69
|
+
hard — an attacker must fool *every* gating worker. This decision's first implementation
|
|
70
|
+
kept a single shot and lost that property; the grader failed the ship on it, correctly
|
|
71
|
+
(see D4). So the species runs **two** independent readings of the same prompt, collapsed
|
|
72
|
+
by **unanimity, not median**: the worst score on each axis, and a pass only if every shot
|
|
73
|
+
passed. Under a median the collapse would be actively harmful — the median of two is
|
|
74
|
+
their mean, so a fooled 10 averages with a suspicious 3 into a passing 6.5 and the second
|
|
75
|
+
reading launders the first. A split verdict is recorded as `signals.shot_dissent`, since
|
|
76
|
+
two readings of the same fenced text disagreeing is itself worth a human's eye.
|
|
77
|
+
|
|
78
|
+
The collapse covers **all three gate axes, `confidence` included** — a point the second
|
|
79
|
+
round of grading caught, because the first fix collapsed only `functional_fidelity` and
|
|
80
|
+
`code_quality`. Confidence is easy to miss: it is the one gate axis that lives in
|
|
81
|
+
`signals` rather than `rubric_scores`. Leaving it at the median would have reintroduced
|
|
82
|
+
the same laundering on the axis where it matters most, since the server's
|
|
83
|
+
`validateAndScore` derives `lowConfidence` purely from the submitted value and never sees
|
|
84
|
+
the client's `unanimous` boolean — a shot honestly reporting 0.2 ("I cannot judge this
|
|
85
|
+
claim", the value that exists to fall through to a human) would average with a confident
|
|
86
|
+
0.9 and clear the 0.4 gate at 0.55.
|
|
87
|
+
|
|
88
|
+
**D4 — Every builder-controlled field is scrubbed and fenced (SR-15 / #995).** The panel
|
|
89
|
+
workers have wrapped the diff, title, description and value summary in
|
|
90
|
+
`grader-workers/fence.js` since #995, because a builder can embed steering text in their
|
|
91
|
+
own artifact. This species carries the same inputs and is the worse case on both sides of
|
|
92
|
+
the ledger: the notes are not context around an artifact the model can independently
|
|
93
|
+
check, they *are* the evidence; and a successful injection does not merely flip one of
|
|
94
|
+
four workers, it closes a task past the scrutiny this ADR introduced to replace the
|
|
95
|
+
Archon-only gate. So fencing here is not defence in depth, it is the boundary. The
|
|
96
|
+
no-follow rule is also restated *after* the artifact, because the notes may run to 24,000
|
|
97
|
+
characters and a rule stated once at the top is a long way behind by the end of an
|
|
98
|
+
adversarial payload.
|
|
99
|
+
|
|
100
|
+
**D3 — The species marker lives in `signals`, not in `grader_kind`.** The server's
|
|
101
|
+
`validateAndScore` hardcodes `task_grades.grader_kind` to `'subagent'` for everything
|
|
102
|
+
arriving through `POST /tasks/:id/grade`, so the column cannot carry the distinction
|
|
103
|
+
without teaching every consumer of that enum a new member. `signals.no_artifact_grade`
|
|
104
|
+
is submitted verbatim and stored, so it is the durable discriminator.
|
|
105
|
+
`signals.graded_subject = 'notes'` says what was read.
|
|
106
|
+
|
|
107
|
+
## 4. Consequences
|
|
108
|
+
|
|
109
|
+
- A Metic can now close a genuinely artifact-free task on the strength of a graded
|
|
110
|
+
claim. `task.confirm.grade_bypass` keeps exactly the meaning ADR 0041 gave it and
|
|
111
|
+
loses a use it never should have had.
|
|
112
|
+
- **A second grade species exists in `task_grades`.** Every consumer that averages,
|
|
113
|
+
counts or spot-checks grades must split on `signals.no_artifact_grade` first: a score
|
|
114
|
+
over prose and a score over a 400-line diff are not the same measurement.
|
|
115
|
+
`/grade-audit` is updated with that rule in this change.
|
|
116
|
+
- **No-artifact closes get harder, not easier, for the dishonest.** The permission wall
|
|
117
|
+
was indiscriminate — it stopped the honest Metic and waved through any Archon. The
|
|
118
|
+
grade is discriminating: it reads the claim. "Confirmed working" with nothing a reader
|
|
119
|
+
could re-run now fails an axis that previously did not exist.
|
|
120
|
+
- **The diff-hash reuse key is deliberately not set** for this species. Hashing the empty
|
|
121
|
+
diff would let the *next* empty-diff ship of the same task reuse this grade — a
|
|
122
|
+
different claim, different notes, silently inheriting a pass it never earned.
|
|
123
|
+
- **Below the protected-path floor, nothing changes.** The server's Guard 1b (task
|
|
124
|
+
1002646) fails a passing grade closed when a below-floor builder submits no
|
|
125
|
+
`committed_files`, and a no-artifact grade submits none by definition. That guard is
|
|
126
|
+
correct and untouched: for a sub-floor builder the permission-path check is genuinely
|
|
127
|
+
unprovable. This decision moves the Metic-and-above case, which is the case that was
|
|
128
|
+
filed.
|
|
129
|
+
- `--skip-grade` is unchanged and still reaches the Archon gate. The escalation split
|
|
130
|
+
task 1003950 added still decides which refusal prints there.
|
|
131
|
+
|
|
132
|
+
## 5. Alternatives considered
|
|
133
|
+
|
|
134
|
+
**Task 1004010 — a new permission atom for the no-artifact close.** The sibling shape,
|
|
135
|
+
and the one this drops. It would have added a lower-floored relative of a trust-boundary
|
|
136
|
+
key, plus a governance migration and a `system: true` decision of its own. Rejected on
|
|
137
|
+
two grounds: it answers "who may skip the check" when the better question is "why is
|
|
138
|
+
there no check", and it permanently widens the permission surface — the same class of
|
|
139
|
+
change the owner declined three days earlier for `version.create` (task 1003853,
|
|
140
|
+
[ADR 0250](0250-strict-versioning-the-version-boundary-is-the-scope-gate.md) §6a).
|
|
141
|
+
Solving the problem without a new atom is the consistent call. The row is closed, with
|
|
142
|
+
its problem statement kept should this fail to land.
|
|
143
|
+
|
|
144
|
+
**Keep skipping the grade, lower the floor on the existing key.** Cheapest, and wrong
|
|
145
|
+
for the same reason as 1004010: it treats the gate as the point. Nothing would read the
|
|
146
|
+
notes.
|
|
147
|
+
|
|
148
|
+
**Grade the notes with the full panel.** Rejected under D2 — four code lenses over prose
|
|
149
|
+
is cost without signal. Note what the rejection had to preserve separately: the panel's
|
|
150
|
+
value here was never its lenses, it was the unanimity of its gate, and D2 keeps that with
|
|
151
|
+
two shots rather than four lenses.
|
|
152
|
+
|
|
153
|
+
**A single generalist shot.** This decision's first implementation, and wrong for a
|
|
154
|
+
reason that only became visible once the prompt was written: it leaves one reading
|
|
155
|
+
between a crafted set of handoff notes and a closed task. Corrected before landing.
|
|
156
|
+
|
|
157
|
+
**A new pair of rubric axes for prose.** Rejected under D1 — honest about the
|
|
158
|
+
measurement, but it moves the gate arithmetic and every consumer of it, for a naming
|
|
159
|
+
gain.
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# ADR 0321 — Brightness is a payout-weighted share, and a removal's targets are rows
|
|
2
|
+
|
|
3
|
+
- **Status:** Accepted
|
|
4
|
+
- **Date:** 2026-09-20
|
|
5
|
+
- **Task:** [task 1004099](https://cloudbongos.com/builders#/task/1004099) (BV2.R16, goal 1000095 — BONGOS-V2)
|
|
6
|
+
- **Spec:** `docs/specs/<redacted>.md` decisions 4 and 20
|
|
7
|
+
- **Under:** [ADR 0313](0313-a-removal-is-proposed-by-the-ship-and-confirmed-by-a-person.md), [ADR 0319](0319-a-removal-verdict-is-a-row-beside-the-question.md)
|
|
8
|
+
|
|
9
|
+
## Context
|
|
10
|
+
|
|
11
|
+
An idea's **size** is its total ideator payout from its descent and never falls
|
|
12
|
+
([ADR 0312](0312-provenance-is-a-second-ideas-owned-edge-table.md) and
|
|
13
|
+
`modules/economy/idea-size.js`). **Brightness** is the other channel: how much of that
|
|
14
|
+
work is still alive. The pair is what lets a thought whose version the project later tore
|
|
15
|
+
out draw *large and dark* — visibly different from a thought that never caused anything
|
|
16
|
+
and draws *small and dark*. One number cannot express both, which is why there are two.
|
|
17
|
+
|
|
18
|
+
Two things had to be settled before it could be computed.
|
|
19
|
+
|
|
20
|
+
**First, what a share is a share OF.** The spec says graded, not binary (decision 20): a
|
|
21
|
+
descent of five things with three alive is partly lit, and goes out only when nothing
|
|
22
|
+
survives.
|
|
23
|
+
|
|
24
|
+
**Second, and the real blocker: the removal facts are stored in the wrong direction.**
|
|
25
|
+
ADR 0319 gave a confirmed removal a row — `lifecycle_removal_verdicts`, keyed on the
|
|
26
|
+
proposal, carrying the verdict and `ship_task_id`. It also said, in the migration and in
|
|
27
|
+
its own text, that the list of tasks whose work was removed was deliberately absent, and
|
|
28
|
+
that *"brightness designs its own storage for targets when it needs one."* That deferral
|
|
29
|
+
is now due.
|
|
30
|
+
|
|
31
|
+
The gap is not cosmetic. `ship_task_id` is the task whose merge **deleted** the files.
|
|
32
|
+
Brightness needs the previously-shipped tasks that **stopped counting**. Those are
|
|
33
|
+
different rows, almost always under different ideas — a later, unrelated piece of work
|
|
34
|
+
tears out an earlier one. Reading the ship as the removed task dims the wrong star, and
|
|
35
|
+
dims it for the wrong idea, while looking like a working query.
|
|
36
|
+
|
|
37
|
+
The attribution itself already exists. `modules/lifecycle/removal-derivation.js` blames
|
|
38
|
+
each deleted path back to the task that introduced it, and the cascade renders the result
|
|
39
|
+
as prose in the proposal's body. `removal-proposals.js` refuses to parse that prose back
|
|
40
|
+
and is right to: *"a body is written for a person to read, and a reader that silently
|
|
41
|
+
depends on its line shape breaks the day somebody improves the wording."*
|
|
42
|
+
|
|
43
|
+
## Decision
|
|
44
|
+
|
|
45
|
+
### D1 — The targets are rows, written at proposal time
|
|
46
|
+
|
|
47
|
+
`modules/lifecycle/migrations/lifecycle_010_removal_targets.sql` adds
|
|
48
|
+
`lifecycle_removal_targets (proposal_task_id, removed_task_id, paths)`, written by
|
|
49
|
+
`recordRemovalTargets` from the same `ctx.removals` objects the proposal body renders.
|
|
50
|
+
**One derivation, two renderings** — the prose and the rows cannot disagree, and
|
|
51
|
+
`removal-proposals.js` keeps its refusal intact because something else now carries the
|
|
52
|
+
fact.
|
|
53
|
+
|
|
54
|
+
It must be written at **proposal** time rather than at verdict time. The attribution comes
|
|
55
|
+
from GitHub commit-history reads, capped at eight paths per ship
|
|
56
|
+
(`MAX_ATTRIBUTED_PATHS`) and running on the server's shared push credential — the same
|
|
57
|
+
credential the publish path depends on. Re-deriving when a person clicks Confirm, days
|
|
58
|
+
later, would spend that budget again against a history that has moved on.
|
|
59
|
+
|
|
60
|
+
This is **not** the cascade runner fanning out, which [ADR 0242](0242-a-cascade-is-a-declaration-table-on-the-event-that-already-exists.md)
|
|
61
|
+
forbids: the removal rule still files exactly one task. This records a side fact *about*
|
|
62
|
+
that one task — the same shape as the verdict row its answer writes.
|
|
63
|
+
|
|
64
|
+
`ON CONFLICT DO NOTHING`, never `DO UPDATE`. A second walk can legitimately find *fewer*
|
|
65
|
+
paths, because the cap and the rate limit make an exhausted page yield no attribution at
|
|
66
|
+
all; overwriting would let a degraded re-read erase a good first one. For the same
|
|
67
|
+
reason the writer runs on the cascade's `skipped` row as well as its `created` one, so a
|
|
68
|
+
re-ship **repairs** an attribution that a derivation outage missed.
|
|
69
|
+
|
|
70
|
+
### D2 — Brightness is weighted by payout, not by task count
|
|
71
|
+
|
|
72
|
+
The share is `live / credits`, where each descent task is weighted by what it actually
|
|
73
|
+
earned the idea.
|
|
74
|
+
|
|
75
|
+
Counting tasks instead would make deleting a one-line fix dim a star as hard as deleting
|
|
76
|
+
the feature it belongs to. Weighting by payout makes brightness fall in proportion to how
|
|
77
|
+
much of the idea's *worth* died — and since size is drawn from the same quantity, the two
|
|
78
|
+
channels become a share of one number rather than two unrelated measurements sitting side
|
|
79
|
+
by side. It also makes "large and dead" exact: full size, zero brightness.
|
|
80
|
+
|
|
81
|
+
The basis is **paid + estimated**, matching size. A historical idea's descent was never
|
|
82
|
+
paid for (`idea-size-historical.js` prices those hops); counting only banked credits would
|
|
83
|
+
mean removing an estimated hop dims nothing while the size drawn beside it still counts
|
|
84
|
+
that hop. To support this, `priceDescentForIdea` and `ideaSizes` now return a per-task
|
|
85
|
+
`byTask` breakdown that **sums exactly to `credits`** — assembled from the priced descent
|
|
86
|
+
plus any ledger payment for a task the descent no longer reaches, because `idea-size.js`
|
|
87
|
+
deliberately keeps those in `credits` so size cannot fall, and dropping them here would
|
|
88
|
+
make the parts sum to less than the whole and silently overstate brightness.
|
|
89
|
+
|
|
90
|
+
### D3 — An idea that earned nothing is LIT, not dark
|
|
91
|
+
|
|
92
|
+
Zero over zero is not zero. A freshly-filed thought has had nothing removed from it, so
|
|
93
|
+
`share: 1` with `hasWork: false`. Returning 0 would make it pixel-identical to the thought
|
|
94
|
+
whose version was torn out — which is precisely the distinction BV2.R19's proof exists to
|
|
95
|
+
check, and would quietly defeat the reason size and brightness are two channels.
|
|
96
|
+
`hasWork` is what lets the five states (BV2.R17) tell a protostar from a remnant without
|
|
97
|
+
re-deriving it.
|
|
98
|
+
|
|
99
|
+
### D4 — "Could not look" is not "nothing was removed"
|
|
100
|
+
|
|
101
|
+
When the removal facts cannot be read — the lifecycle module is off, the port is
|
|
102
|
+
unregistered — `share` is `null` and `attributable` is `false`, never a confident 1. Same
|
|
103
|
+
posture `idea-size.js` already takes with `historical: null` when the descent walk is
|
|
104
|
+
unavailable: an established absence and an unreadable one are different answers, and only
|
|
105
|
+
one of them may be drawn as a plain full-brightness star.
|
|
106
|
+
|
|
107
|
+
A **confirmed removal that names no target** is the residual case D1 cannot close: it is
|
|
108
|
+
genuinely gone, and brightness does not know which idea to dim. It happens for proposals
|
|
109
|
+
filed before `lifecycle_010`, for attributions where every deletion predates
|
|
110
|
+
`claims.published_head_sha`, and for a derivation outage. Guessing in either direction is
|
|
111
|
+
worse than not dimming — so nothing dims, and `countUnattributedConfirmed()` exists so a
|
|
112
|
+
surface can say *"the sky may read brighter than the truth by up to N confirmed
|
|
113
|
+
removals"* instead of being silently optimistic.
|
|
114
|
+
|
|
115
|
+
### D5 — Brightness lives in `economy`, behind a port
|
|
116
|
+
|
|
117
|
+
It is a share of the same payouts size is built from, so it sits beside size in
|
|
118
|
+
`modules/economy/idea-brightness.js`; putting it in `lifecycle` would mean that module
|
|
119
|
+
doing money arithmetic. That makes the removal facts a cross-module read, so they come
|
|
120
|
+
through a kernel-mediated port — `lifecycle.removals` — and never a `require`
|
|
121
|
+
([ADR 0083](<redacted>.md) rule 1), exactly as economy already
|
|
122
|
+
reaches `ideas.descent`.
|
|
123
|
+
|
|
124
|
+
Economy resolves it with `resolveOptional` and **does not declare it in `consumes`**.
|
|
125
|
+
`consumes` means *required*, and `verifyEnabledPorts` refuses to boot when an enabled
|
|
126
|
+
module consumes an unprovided port — declaring it would turn "lifecycle off" into
|
|
127
|
+
"instance will not start". The same non-declaration, for the same reason, as
|
|
128
|
+
`ideas.descent`.
|
|
129
|
+
|
|
130
|
+
## Consequences
|
|
131
|
+
|
|
132
|
+
- Brightness is computable per idea and falls only when a **person** confirms a removal.
|
|
133
|
+
A dismissed or unanswered proposal dims nothing, which is what stops refactors, renames
|
|
134
|
+
and moves from dimming the sky continuously — ADR 0313's central finding.
|
|
135
|
+
- **No credit is ever clawed back.** `modules/economy/idea-brightness.js` contains no
|
|
136
|
+
writer and is unreachable from any ship path. `shipped` stays terminal, the ledger is
|
|
137
|
+
never rewritten, and size does not move. Only the picture changes.
|
|
138
|
+
- `ideaSizes` grew a field. Callers that spread its result now carry a `Map`; nothing
|
|
139
|
+
serialises it to JSON today, and a surface that wants to must project it rather than
|
|
140
|
+
pass it through.
|
|
141
|
+
- BV2.R17 (the five states) can resolve protostar-vs-remnant from `hasWork` and `share`
|
|
142
|
+
without re-deriving either, and BV2.R18 (the sky) has `removedTasks` to explain *why* a
|
|
143
|
+
star dimmed rather than only that it did.
|
|
144
|
+
|
|
145
|
+
## Rejected
|
|
146
|
+
|
|
147
|
+
- **Parsing the proposal body.** The attribution is right there as
|
|
148
|
+
` - task <id> — <title>`. It is also documentation prose that a copy edit may reword at
|
|
149
|
+
any time, and ADR 0319 already refused this for that reason. A reader that depends on a
|
|
150
|
+
human-facing line shape fails silently and dims nothing.
|
|
151
|
+
- **Re-deriving targets at verdict time.** Spends the capped GitHub budget a second time,
|
|
152
|
+
against a history that has moved, on the interactive path where a person is waiting.
|
|
153
|
+
- **Treating `ship_task_id` as the removed task.** The wrong direction; dims the ship's
|
|
154
|
+
own idea instead of the one whose work died. Named here because the column is right
|
|
155
|
+
there on the verdict row and is the obvious thing to reach for.
|
|
156
|
+
- **Counting tasks rather than payouts.** Simpler, and makes a one-line fix weigh the same
|
|
157
|
+
as a feature.
|
|
158
|
+
- **Clamping the share at both ends.** The bottom is clamped so an anomaly renders as a
|
|
159
|
+
legible "nothing survives" rather than a negative; the top is not, and `live` / `removed`
|
|
160
|
+
are reported unclamped, so an impossible figure stays visible instead of being hidden by
|
|
161
|
+
the thing that claims to prevent it — `idea-size.js`'s argument against clamping size.
|
|
162
|
+
- **Storing the removed task's title beside the row.** It lives on `tasks` and can be
|
|
163
|
+
joined for; a copy goes stale the first time a title is edited.
|