@bongos/core 1.19.316 → 1.19.318

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.
Files changed (42) hide show
  1. package/.bongos-core.json +108 -33
  2. package/modules/economy/cost.js +84 -19
  3. package/modules/lifecycle/db-analytics.js +719 -0
  4. package/modules/lifecycle/db-claim-reads.js +165 -0
  5. package/modules/lifecycle/db-claims.js +934 -0
  6. package/modules/lifecycle/db-deps-criteria.js +441 -0
  7. package/modules/lifecycle/db-goals.js +763 -0
  8. package/modules/lifecycle/db-grade.js +233 -0
  9. package/modules/lifecycle/db-overrides.js +181 -0
  10. package/modules/lifecycle/db-rank-authz.js +446 -0
  11. package/modules/lifecycle/db-shared.js +115 -0
  12. package/modules/lifecycle/db-ship.js +994 -0
  13. package/modules/lifecycle/db-tasks.js +810 -0
  14. package/modules/lifecycle/db-versions.js +118 -0
  15. package/modules/lifecycle/db.js +167 -5564
  16. package/modules/lifecycle/publish-reconciler.js +7 -2
  17. package/package-lock.json +2 -2
  18. package/package.json +1 -1
  19. package/scripts/gds/coreB-carve-proof.sh +18 -9
  20. package/scripts/gds/fitness.js +34 -26
  21. package/scripts/gds/identity-strip.js +75 -0
  22. package/scripts/gds/provision.js +7 -3
  23. package/scripts/gds/run-unit-tests.js +8 -1
  24. package/scripts/gds/session-cost.js +16 -0
  25. package/scripts/gds/session-digest-build.js +261 -0
  26. package/scripts/gds/ship.js +7 -198
  27. package/src/module-api.js +5 -1
  28. package/tests/auto_satisfy_criteria.mjs +3 -2
  29. package/tests/dependencies_migration.mjs +3 -2
  30. package/tests/fitness.mjs +43 -0
  31. package/tests/goal_abuse_matrix.mjs +3 -2
  32. package/tests/goal_advisory.mjs +3 -2
  33. package/tests/government_claim_eligibility.mjs +3 -1
  34. package/tests/government_rank_sync.mjs +9 -2
  35. package/tests/helpers.mjs +19 -0
  36. package/tests/migration_allocation.mjs +3 -1
  37. package/tests/publish_reconciler.mjs +41 -1
  38. package/tests/rank_gate.mjs +3 -1
  39. package/tests/reward_gate.mjs +3 -1
  40. package/tests/session_cost.mjs +64 -0
  41. package/tests/session_cost_row_raise.mjs +139 -0
  42. package/tests/session_records.mjs +72 -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.316",
6
- "core_contract": "1.19.316",
7
- "source_commit": "8248fac14dda1af6df3a5414027a4bf9d6ba23e0",
5
+ "core_version": "1.19.318",
6
+ "core_contract": "1.19.318",
7
+ "source_commit": "01d0a25617f7ca9212b310552724d7eb8c183a32",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-08-23T22:02:43.920Z",
9
+ "built_at": "2026-08-23T22:11:19.148Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 323,
13
13
  "agent_docs_stubbed": 22,
14
- "functional_verbatim": 1423,
14
+ "functional_verbatim": 1438,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 1768,
20
- "tree_sha256": "6e3e95467f3b58d962df6fc2490011be711c43787e43adff339b3da3390c2da6",
19
+ "file_count": 1783,
20
+ "tree_sha256": "24fa1a6a45d9a8ad89550dcdf06b82e5aeffa56776dcd6666a1be6524d9d4a6e",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/blocker-review/SKILL.md",
@@ -2987,7 +2987,7 @@
2987
2987
  {
2988
2988
  "path": "modules/economy/cost.js",
2989
2989
  "mode": "0000644",
2990
- "sha256": "5d125e8b884ebfeb5687ee6748c49bf752d5757114ede98d0cabf2eee4a38b76"
2990
+ "sha256": "01bc7725fe16388971fd4def2544d0de9af6b3a522973df7ef86386737933d40"
2991
2991
  },
2992
2992
  {
2993
2993
  "path": "modules/economy/credits.js",
@@ -3824,10 +3824,70 @@
3824
3824
  "mode": "0000644",
3825
3825
  "sha256": "4a4a513ed0759e1e999454e6b6972b921789a5c2a88521755812d97fb6194f83"
3826
3826
  },
3827
+ {
3828
+ "path": "modules/lifecycle/db-analytics.js",
3829
+ "mode": "0000644",
3830
+ "sha256": "b0cb5fb6853d39fd99feaa0b8e3b1040b0877a8476273789dad8976be42070cd"
3831
+ },
3832
+ {
3833
+ "path": "modules/lifecycle/db-claim-reads.js",
3834
+ "mode": "0000644",
3835
+ "sha256": "7aafa6f538811bd29d7f8ce3ebbf59fea72a90fea99de1fdfb6c62124eff23d5"
3836
+ },
3837
+ {
3838
+ "path": "modules/lifecycle/db-claims.js",
3839
+ "mode": "0000644",
3840
+ "sha256": "dbce7de39b91cdd1843ba26efc3f3be76690a7112aaa3b1b0406a9b9f98dfb32"
3841
+ },
3842
+ {
3843
+ "path": "modules/lifecycle/db-deps-criteria.js",
3844
+ "mode": "0000644",
3845
+ "sha256": "b3906a4704be5df9cdfdff9f13e2a18878f93ff7285c42edf25856ec1c4531be"
3846
+ },
3847
+ {
3848
+ "path": "modules/lifecycle/db-goals.js",
3849
+ "mode": "0000644",
3850
+ "sha256": "29b950000d31618de168c6128d9b78c03daad7e68b9a9b6d3e83aa40abd3d9fb"
3851
+ },
3852
+ {
3853
+ "path": "modules/lifecycle/db-grade.js",
3854
+ "mode": "0000644",
3855
+ "sha256": "e6c9a996c11a73847317db47620007c8d21f70b19c73ade4499361c1c0c330f9"
3856
+ },
3857
+ {
3858
+ "path": "modules/lifecycle/db-overrides.js",
3859
+ "mode": "0000644",
3860
+ "sha256": "fa3f251ef2bd0d2caaf49ecc2a250ca65ea7814ee25d606e00c23db0572b23fe"
3861
+ },
3862
+ {
3863
+ "path": "modules/lifecycle/db-rank-authz.js",
3864
+ "mode": "0000644",
3865
+ "sha256": "c93780ea71bb2f8f3d2b21f042c21d029f1ed5336a3aaf178fb3e14925dcc19b"
3866
+ },
3867
+ {
3868
+ "path": "modules/lifecycle/db-shared.js",
3869
+ "mode": "0000644",
3870
+ "sha256": "17a6ddfb4a9415c67dfbd06b052a3a7686df7a55287aa78ff2b55c6a2ca3d954"
3871
+ },
3872
+ {
3873
+ "path": "modules/lifecycle/db-ship.js",
3874
+ "mode": "0000644",
3875
+ "sha256": "0a313d9c20cc0da5952e3a5acbca83cd1fd3df7ecccbbd109701d865b4dd2fe6"
3876
+ },
3877
+ {
3878
+ "path": "modules/lifecycle/db-tasks.js",
3879
+ "mode": "0000644",
3880
+ "sha256": "43748950aee899125bc40b8fb6f26a7df5645746e23fcbe6bbe3ea486f0b7248"
3881
+ },
3882
+ {
3883
+ "path": "modules/lifecycle/db-versions.js",
3884
+ "mode": "0000644",
3885
+ "sha256": "aa98bd22cf237579b065a9a85cb64dda30a0b6b9894ebc5d6ca0592bbb7a977f"
3886
+ },
3827
3887
  {
3828
3888
  "path": "modules/lifecycle/db.js",
3829
3889
  "mode": "0000644",
3830
- "sha256": "f4d3df748f39109c775ae167712926cd44832aba0651251f508adc4a8d4e57fe"
3890
+ "sha256": "70448ebd079d5167bcf0c9c2b4473d46e996a54202f09f5a8ebc30980aba4166"
3831
3891
  },
3832
3892
  {
3833
3893
  "path": "modules/lifecycle/dead-deps.js",
@@ -3912,7 +3972,7 @@
3912
3972
  {
3913
3973
  "path": "modules/lifecycle/publish-reconciler.js",
3914
3974
  "mode": "0000644",
3915
- "sha256": "8f08c6d58db41ba247aaf5b04b5a8988365b469e96c0ad04c336f272ef308c4e"
3975
+ "sha256": "28c61d55fe2b492f0fce4c99b60e88f0cbeb542c47c04482558818d65628e0d7"
3916
3976
  },
3917
3977
  {
3918
3978
  "path": "modules/lifecycle/routes/analytics.js",
@@ -4582,12 +4642,12 @@
4582
4642
  {
4583
4643
  "path": "package-lock.json",
4584
4644
  "mode": "0000644",
4585
- "sha256": "6c10d47c4db2e1e159b3c4aad631491bf7818c3a89fbf3f02bc1de512b1163b3"
4645
+ "sha256": "509e4a76d5eb6f21fd8c926c6bf7ec9f1938d62ed4055558e2d3911858166b38"
4586
4646
  },
4587
4647
  {
4588
4648
  "path": "package.json",
4589
4649
  "mode": "0000644",
4590
- "sha256": "2bfd8f95e547fbbf060361a024395b256fe5eb7bde0796600d44494a718da5cb"
4650
+ "sha256": "5e3c53bb9627f4dcf747e5e7bc8fa7fb0e136554f04d96081137ca1bc05654ad"
4591
4651
  },
4592
4652
  {
4593
4653
  "path": "public-docs/index.html",
@@ -5122,7 +5182,7 @@
5122
5182
  {
5123
5183
  "path": "scripts/gds/coreB-carve-proof.sh",
5124
5184
  "mode": "0000644",
5125
- "sha256": "52daeb629c872ad4f596955257f60d43444db4c91ffa96e6e3752145c92f0fec"
5185
+ "sha256": "55ac5440a1c5481766198d6a89cef601fde816024b592b1e77f535c465a2df47"
5126
5186
  },
5127
5187
  {
5128
5188
  "path": "scripts/gds/cost.js",
@@ -5237,7 +5297,7 @@
5237
5297
  {
5238
5298
  "path": "scripts/gds/fitness.js",
5239
5299
  "mode": "0000644",
5240
- "sha256": "a300efecfa69f8ad597a8125df6dd1999d7cdffc8317ac8616b7081555c8b303"
5300
+ "sha256": "fc4af6845de5bf3775629d492bb793f96cf707dfd9837e338cb2344250f733b5"
5241
5301
  },
5242
5302
  {
5243
5303
  "path": "scripts/gds/gate-review.js",
@@ -5339,6 +5399,11 @@
5339
5399
  "mode": "0000644",
5340
5400
  "sha256": "4bf1e6c352e83a63a4747b892c5f9310dc860f8b2f5fc7c607ed432dcb2dd110"
5341
5401
  },
5402
+ {
5403
+ "path": "scripts/gds/identity-strip.js",
5404
+ "mode": "0000644",
5405
+ "sha256": "a5ac6e41f30fd13738e52800616c2e0e3b1f8150dbb5e3f43e1fdbced5f58fe0"
5406
+ },
5342
5407
  {
5343
5408
  "path": "scripts/gds/init.js",
5344
5409
  "mode": "0000644",
@@ -5532,7 +5597,7 @@
5532
5597
  {
5533
5598
  "path": "scripts/gds/provision.js",
5534
5599
  "mode": "0000644",
5535
- "sha256": "1b1e4ca721a9052206265736712ea17e96b2b724c2926faba49839def36a1818"
5600
+ "sha256": "b445702caa082164d8b733a44be2407f3e3889f28838dfd38222a6a0eb5ec672"
5536
5601
  },
5537
5602
  {
5538
5603
  "path": "scripts/gds/publish-credential-check.js",
@@ -5607,7 +5672,7 @@
5607
5672
  {
5608
5673
  "path": "scripts/gds/run-unit-tests.js",
5609
5674
  "mode": "0000644",
5610
- "sha256": "e2e3cd6299f63b838ed5a78121eedcb44e76b3cc62b622581cd537c154b1d8ae"
5675
+ "sha256": "c96e50e533f4e20dba91c6a26ac6efd2c6ad33ffcdc856eeb46af21a17e67975"
5611
5676
  },
5612
5677
  {
5613
5678
  "path": "scripts/gds/sandbox-stage.js",
@@ -5767,7 +5832,12 @@
5767
5832
  {
5768
5833
  "path": "scripts/gds/session-cost.js",
5769
5834
  "mode": "0000644",
5770
- "sha256": "52d6c931fa0d7213632a365984ed360cb8df2d369762dbb417d0c0a1b74145a6"
5835
+ "sha256": "fdf3b9d8e0089078da550ac65d0c012583cc21103a668deecf481383a73c9da1"
5836
+ },
5837
+ {
5838
+ "path": "scripts/gds/session-digest-build.js",
5839
+ "mode": "0000644",
5840
+ "sha256": "95e5c21533f67588fd02fae311cef8985d3eee58ad3217fcb6e9998e1f4da965"
5771
5841
  },
5772
5842
  {
5773
5843
  "path": "scripts/gds/session-digest.js",
@@ -5802,7 +5872,7 @@
5802
5872
  {
5803
5873
  "path": "scripts/gds/ship.js",
5804
5874
  "mode": "0000644",
5805
- "sha256": "f3005175abc0ba20e958381df6aee02402267e9956da466386656dd4e86ad8cf"
5875
+ "sha256": "8f2c0ca63abae081cccfa96059ae07b008bd108f85866a248075cb4a1b7e025e"
5806
5876
  },
5807
5877
  {
5808
5878
  "path": "scripts/gds/skill-preflight.js",
@@ -6262,7 +6332,7 @@
6262
6332
  {
6263
6333
  "path": "src/module-api.js",
6264
6334
  "mode": "0000644",
6265
- "sha256": "049a62ecc239e85005e2f79fea63f46edf406b53eb2b4c36e21e81281365afc7"
6335
+ "sha256": "af9d10cbaf1d6320df892f4d0e1403c0f5f1bb6e8c314695e89477690ba7fc85"
6266
6336
  },
6267
6337
  {
6268
6338
  "path": "src/module-loader/catalog.js",
@@ -6492,7 +6562,7 @@
6492
6562
  {
6493
6563
  "path": "tests/auto_satisfy_criteria.mjs",
6494
6564
  "mode": "0000644",
6495
- "sha256": "1a5524b0e63631a0ebdc5e3e87c04eb862a9be7021b3b1caf5a44afb2dcb455b"
6565
+ "sha256": "135d0af6b1535e50d68ed62baa36b616ba28888e899940cabf2f2940f3cd6f9d"
6496
6566
  },
6497
6567
  {
6498
6568
  "path": "tests/autonomous_fit_protected_paths.mjs",
@@ -6902,7 +6972,7 @@
6902
6972
  {
6903
6973
  "path": "tests/dependencies_migration.mjs",
6904
6974
  "mode": "0000644",
6905
- "sha256": "cd5010d2794a23eac2f84a19d6abc8fc5da75400d023373d289f61cbb08ede2c"
6975
+ "sha256": "daf81be50910d712cdfe10ac2c50578f70dc1d8cf261d435db23a120ec310968"
6906
6976
  },
6907
6977
  {
6908
6978
  "path": "tests/dependencies_polymorphic.mjs",
@@ -7112,7 +7182,7 @@
7112
7182
  {
7113
7183
  "path": "tests/fitness.mjs",
7114
7184
  "mode": "0000644",
7115
- "sha256": "de84ceb6c73818975d5da77949c8ff4823f6e9d4613d9c95ea21c96a92b66760"
7185
+ "sha256": "465c7ef58342b6e03947858fe34649f5a13a5bcf3658478601baed1e8b2420ef"
7116
7186
  },
7117
7187
  {
7118
7188
  "path": "tests/fitness_ratchets.mjs",
@@ -7192,7 +7262,7 @@
7192
7262
  {
7193
7263
  "path": "tests/goal_abuse_matrix.mjs",
7194
7264
  "mode": "0000644",
7195
- "sha256": "daf3a5942ce3f154d29618e4242557758db50d8c70046562995fc2c10d5ee954"
7265
+ "sha256": "b9fea609f0dc0aa1c0d05c1794290a65e48996c817a4a435098e91220ade89c1"
7196
7266
  },
7197
7267
  {
7198
7268
  "path": "tests/goal_achievement.mjs",
@@ -7202,7 +7272,7 @@
7202
7272
  {
7203
7273
  "path": "tests/goal_advisory.mjs",
7204
7274
  "mode": "0000644",
7205
- "sha256": "f5a6985502bc85589d688bf96b3e9242d84efec39d52f5a451e92c23cbb9175c"
7275
+ "sha256": "b9fe4a36c7b60512c71e1d4837e8639381d441c19261007e186cb8c369d5b2c6"
7206
7276
  },
7207
7277
  {
7208
7278
  "path": "tests/goal_cli.mjs",
@@ -7332,7 +7402,7 @@
7332
7402
  {
7333
7403
  "path": "tests/government_claim_eligibility.mjs",
7334
7404
  "mode": "0000644",
7335
- "sha256": "2750f75bc0ef7f5286a2abe1d59e49ff2ea6ea300d3c1760dda31ae8ad74dc61"
7405
+ "sha256": "7eb103c6a201345728af5002aae1be09c1f3b1459b74bef7ea14e990a28dd05b"
7336
7406
  },
7337
7407
  {
7338
7408
  "path": "tests/government_config.mjs",
@@ -7392,7 +7462,7 @@
7392
7462
  {
7393
7463
  "path": "tests/government_rank_sync.mjs",
7394
7464
  "mode": "0000644",
7395
- "sha256": "8e430f7286ec798eceaba7db962bb3725c1de2e9cbaeee70739aae6287695d7e"
7465
+ "sha256": "8750d0bbfc33403ff451b780935496a037dbab4a8e30bdc089abde8da8b7400a"
7396
7466
  },
7397
7467
  {
7398
7468
  "path": "tests/government_require_permission.mjs",
@@ -7532,7 +7602,7 @@
7532
7602
  {
7533
7603
  "path": "tests/helpers.mjs",
7534
7604
  "mode": "0000644",
7535
- "sha256": "56321847cf62d27db361b7b4f2511f3573967e3d1b95630f7efd48bad74a7a8f"
7605
+ "sha256": "7a306648d5fd083313126ce98d364e19d130313edf7d37dd23e3767c7e527c8f"
7536
7606
  },
7537
7607
  {
7538
7608
  "path": "tests/hierarchy-config.mjs",
@@ -7847,7 +7917,7 @@
7847
7917
  {
7848
7918
  "path": "tests/migration_allocation.mjs",
7849
7919
  "mode": "0000644",
7850
- "sha256": "6755c9568cd6cdb7b8600ae2c6651856d63c053684dcb8b765f7329590a9fc08"
7920
+ "sha256": "017770d7639ca5dfb204b613b9ed287fb01c1f768c4f6e0550cc0ac7cad5fb5e"
7851
7921
  },
7852
7922
  {
7853
7923
  "path": "tests/migration_namespace.mjs",
@@ -8222,12 +8292,12 @@
8222
8292
  {
8223
8293
  "path": "tests/publish_reconciler.mjs",
8224
8294
  "mode": "0000644",
8225
- "sha256": "c61741aebfad2e85e55f0e734b3195d9474f8dd0c7812c3ee1434b05d088c1de"
8295
+ "sha256": "5a9ea0bf7b4d776a6f9036b8bd060fef76335533274ed748e9b9d742da6960d3"
8226
8296
  },
8227
8297
  {
8228
8298
  "path": "tests/rank_gate.mjs",
8229
8299
  "mode": "0000644",
8230
- "sha256": "2745778ec6ff188a9e65b0a5964f07d5a399aaafdd8854aa962e3cb6728244e6"
8300
+ "sha256": "d8fff57f5fee95bf11a5c1df3e2c0e342d39ef5b22c5e87f47a61dd9a3d2a8cb"
8231
8301
  },
8232
8302
  {
8233
8303
  "path": "tests/rank_tier_single_source.mjs",
@@ -8292,7 +8362,7 @@
8292
8362
  {
8293
8363
  "path": "tests/reward_gate.mjs",
8294
8364
  "mode": "0000644",
8295
- "sha256": "a21d9f4d3ff937d59b9361b330a2e5e93ded9c9a12d73434eb843cae9d1a3c74"
8365
+ "sha256": "52f9997d60ce03c60137376dc2bba1940c669617f0ca69fdafd3d90091411df3"
8296
8366
  },
8297
8367
  {
8298
8368
  "path": "tests/reward_mode_cost_plus_only.mjs",
@@ -8442,7 +8512,12 @@
8442
8512
  {
8443
8513
  "path": "tests/session_cost.mjs",
8444
8514
  "mode": "0000644",
8445
- "sha256": "6531fd14dbfc025543c4855c326746c126cfcc94173b9a724e56f74a989a1d4e"
8515
+ "sha256": "5f330f79eee8c39dc589d892ba5fa6835906e7b61ff5fe52aec3285721e84876"
8516
+ },
8517
+ {
8518
+ "path": "tests/session_cost_row_raise.mjs",
8519
+ "mode": "0000644",
8520
+ "sha256": "74a106432222df7d8e845ec409bd2e1d49aff18bf80cdeff032cfe91b1d6dc3f"
8446
8521
  },
8447
8522
  {
8448
8523
  "path": "tests/session_digest.mjs",
@@ -8462,7 +8537,7 @@
8462
8537
  {
8463
8538
  "path": "tests/session_records.mjs",
8464
8539
  "mode": "0000644",
8465
- "sha256": "c33e4f5bb635eb523bc7014fe8cfad343b7ab7753822644ce987cd419520c748"
8540
+ "sha256": "8c62fe0bec07aea4c30f84d296eb7b40c9550606aacf6cb63f9644ca34f12069"
8466
8541
  },
8467
8542
  {
8468
8543
  "path": "tests/session_reward_on_land.mjs",
@@ -105,23 +105,51 @@ async function logCost({
105
105
  outputUsd = null,
106
106
  // task #385 / migration 181: which skill invocation incurred this cost.
107
107
  skillName = null,
108
+ // task 1003217 — opt-in MONOTONIC RAISE on (source, source_ref) conflict.
109
+ //
110
+ // Default false, so every existing caller keeps the plain insert-once
111
+ // idempotency it relies on. The session reconciler below sets it true: a
112
+ // long session's digest GROWS on every ship, and a cost row written from an
113
+ // early digest must be allowed to catch up, or the session is billed at its
114
+ // first hour forever (the bug this task fixes).
115
+ //
116
+ // Safe because the update SETS an absolute amount rather than adding to one:
117
+ // re-running with the same figure is a no-op, so there is no double-count
118
+ // path, and the WHERE guard means a row can only ever move UP.
119
+ raiseOnConflict = false,
108
120
  }) {
109
121
  // V3.R11 (#91): builder_id is the per-builder attribution column added by
110
122
  // migration 046. Routes default it to the authed builder, so the only
111
123
  // legitimately-null builder_id is for historical rows or system-injected
112
124
  // costs (infra, domain, etc.) that don't belong to any one person.
125
+ // Monotonic raise (opt-in) vs the historical insert-once. `recorded_at` moves
126
+ // with the amount so the reconciler's "digest is newer than the priced row"
127
+ // predicate settles instead of re-firing every sweep.
128
+ const conflictAction = raiseOnConflict
129
+ ? `DO UPDATE SET
130
+ amount_usd = EXCLUDED.amount_usd,
131
+ description = EXCLUDED.description,
132
+ fresh_input_usd = EXCLUDED.fresh_input_usd,
133
+ cache_read_usd = EXCLUDED.cache_read_usd,
134
+ cache_write_usd = EXCLUDED.cache_write_usd,
135
+ output_usd = EXCLUDED.output_usd,
136
+ recorded_at = EXCLUDED.recorded_at
137
+ WHERE EXCLUDED.amount_usd > cost_log.amount_usd`
138
+ : 'DO NOTHING';
113
139
  const { rows } = await pool.query(
114
140
  `INSERT INTO cost_log (task_id, amount_usd, category, description, source, source_ref, recorded_at, builder_id,
115
141
  fresh_input_usd, cache_read_usd, cache_write_usd, output_usd, skill_name)
116
142
  VALUES ($1, $2, $3, $4, $5, $6, COALESCE($7, now()), $8, $9, $10, $11, $12, $13)
117
143
  ON CONFLICT (source, source_ref)
118
144
  WHERE source IS NOT NULL AND source_ref IS NOT NULL
119
- DO NOTHING
145
+ ${conflictAction}
120
146
  RETURNING id, recorded_at`,
121
147
  [taskId, amountUsd, category, description, source, sourceRef, recordedAt, builderId,
122
148
  freshInputUsd, cacheReadUsd, cacheWriteUsd, outputUsd, skillName || null]
123
149
  );
124
- return rows[0] ?? null; // null when ON CONFLICT skipped the insert
150
+ // null when the conflict clause skipped the write (DO NOTHING, or a raise that
151
+ // would not have moved the row upward).
152
+ return rows[0] ?? null;
125
153
  }
126
154
 
127
155
  // task 1366 (System 1) — server-side session-cost reconciler.
@@ -144,25 +172,45 @@ async function logCost({
144
172
  // first crack at posting (avoids racing it). 15m default.
145
173
  // - limit : per-sweep cap (runaway guard; there are normally few).
146
174
  //
147
- // The NOT EXISTS checks BOTH the interactive key (session_id) AND the subagent
148
- // key (session_id || ':sub') — session-cost.js splits a captured session into
149
- // those two rows (task 1344), so a session the client captured even partially
150
- // is skipped entirely (we only have the combined model_usage, so we can't split
151
- // it; better to defer to the client's split than to add a duplicate total).
152
- // Returns { scanned, reconciled, totalUsd, unknownModels } for logging.
175
+ // A session the CLIENT split into interactive + subagent rows (session_id ||
176
+ // ':sub', task 1344) is skipped entirely — we hold only the combined
177
+ // model_usage, so we cannot reproduce that split and should defer to it.
178
+ //
179
+ // task 1003217 — WHY THIS NO LONGER SKIPS EVERY PRICED SESSION. It used to
180
+ // exclude any session that had a cost row at all. But `session_records` is
181
+ // upserted on EVERY ship, so a session that ships twelve tasks has a digest
182
+ // from its FIRST ship, and this sweep priced that partial digest ~15 minutes
183
+ // later and then never looked again — logCost's DO NOTHING silently dropped
184
+ // every subsequent, larger figure. Measured on prod: a 9.3-hour session was
185
+ // billed at its first 56 minutes, and across four audited sessions the ledger
186
+ // captured 14%, 14%, 16% and 54% of real spend. Now a session is revisited
187
+ // while its digest is NEWER than the row we priced from it, and logCost raises
188
+ // the row monotonically. `recorded_at` advances on each raise, so a settled
189
+ // session stops matching and the sweep converges.
190
+ //
191
+ // Historical rows are NOT re-priced downward: llm-pricing.js is explicit that a
192
+ // paid row records what was charged, and ADR 0054 paid credits from it. The
193
+ // raise is upward-only, so pre-fix inflated rows stay as they are — expect a
194
+ // visible step change in cost-per-ship at deploy rather than a rewritten past.
195
+ // Returns { scanned, reconciled, raised, totalUsd, unknownModels } for logging.
153
196
  async function reconcileSessionCosts({ olderThanMinutes = 15, limit = 500 } = {}) {
154
197
  const mins = Number.isFinite(Number(olderThanMinutes)) ? Number(olderThanMinutes) : 15;
155
198
  const lim = Number.isFinite(Number(limit)) ? Number(limit) : 500;
156
199
  const { rows } = await pool.query(
157
- `SELECT sr.session_id, sr.builder_id, sr.model_usage
200
+ `SELECT sr.session_id, sr.builder_id, sr.model_usage,
201
+ c.amount_usd AS prior_usd
158
202
  FROM session_records sr
203
+ LEFT JOIN cost_log c
204
+ ON c.source = 'session:interactive'
205
+ AND c.source_ref = sr.session_id
159
206
  WHERE sr.model_usage IS NOT NULL
160
207
  AND sr.uploaded_at < now() - ($1 || ' minutes')::interval
161
208
  AND NOT EXISTS (
162
- SELECT 1 FROM cost_log c
163
- WHERE c.source_ref = sr.session_id
164
- OR c.source_ref = sr.session_id || ':sub'
209
+ SELECT 1 FROM cost_log s
210
+ WHERE s.source_ref = sr.session_id || ':sub'
165
211
  )
212
+ -- Never priced, or the digest has grown since we priced it.
213
+ AND (c.id IS NULL OR c.recorded_at < sr.uploaded_at)
166
214
  ORDER BY sr.uploaded_at DESC
167
215
  LIMIT $2`,
168
216
  [String(mins), lim]
@@ -170,6 +218,7 @@ async function reconcileSessionCosts({ olderThanMinutes = 15, limit = 500 } = {}
170
218
 
171
219
  const round4 = (n) => Math.round((Number(n) || 0) * 10000) / 10000;
172
220
  let reconciled = 0;
221
+ let raised = 0;
173
222
  let totalUsd = 0;
174
223
  const unknownModels = new Set();
175
224
  for (const r of rows) {
@@ -177,34 +226,50 @@ async function reconcileSessionCosts({ olderThanMinutes = 15, limit = 500 } = {}
177
226
  for (const m of comp.unknown_models) unknownModels.add(m);
178
227
  const amount = round4(comp.total_usd);
179
228
  if (!(amount > 0)) continue; // nothing priceable on this row — skip
229
+ const prior = round4(r.prior_usd);
230
+ // A row already at or above this figure needs no write: the digest grew but
231
+ // the price did not (or the stored row predates a pricing fix and is higher,
232
+ // which we deliberately leave alone).
233
+ if (prior > 0 && amount <= prior) continue;
180
234
  const id8 = String(r.session_id).slice(0, 8);
181
235
  const unkNote = comp.unknown_models.length
182
236
  ? ` [unpriced model(s) priced as opus: ${comp.unknown_models.join(',')}]`
183
237
  : '';
238
+ const provenance = prior > 0
239
+ ? `raised from $${prior.toFixed(4)} as the session grew`
240
+ : 'reconciled from digest; client cost-hook did not post';
184
241
  const inserted = await logCost({
185
242
  amountUsd: amount,
186
243
  category: 'api',
187
244
  source: 'session:interactive',
188
- // SAME key the client would use → ON CONFLICT DO NOTHING prevents any
189
- // double-count if the client also posts later.
245
+ // SAME key the client would use, so a client post and this sweep can
246
+ // never both land as separate rows.
190
247
  sourceRef: r.session_id,
191
248
  builderId: r.builder_id,
192
- description: `session ${id8} — $${amount.toFixed(4)} (reconciled from digest; client cost-hook did not post)${unkNote}`.slice(0, 1000),
249
+ // Upward-only: catches a long session up as it ships more tasks, and can
250
+ // never lower a row that has already been paid from.
251
+ raiseOnConflict: true,
252
+ description: `session ${id8} — $${amount.toFixed(4)} (${provenance})${unkNote}`.slice(0, 1000),
193
253
  freshInputUsd: round4(comp.fresh_input_usd),
194
254
  cacheReadUsd: round4(comp.cache_read_usd),
195
255
  cacheWriteUsd: round4(comp.cache_write_usd),
196
256
  outputUsd: round4(comp.output_usd),
197
257
  });
198
- // inserted === null means ON CONFLICT skipped it (client raced in between the
199
- // SELECT and the INSERT) — expected + idempotent, not counted.
258
+ // inserted === null means the conflict clause skipped the write (the client
259
+ // raced in between the SELECT and the INSERT, or the raise would not have
260
+ // moved the row up) — expected + idempotent, not counted.
200
261
  if (inserted) {
201
- reconciled++;
202
- totalUsd += amount;
262
+ if (prior > 0) raised++;
263
+ else reconciled++;
264
+ // Bill only the INCREMENT on a raise, so this figure stays a true
265
+ // "spend recorded by this sweep" rather than re-counting the whole row.
266
+ totalUsd += amount - prior;
203
267
  }
204
268
  }
205
269
  return {
206
270
  scanned: rows.length,
207
271
  reconciled,
272
+ raised,
208
273
  totalUsd: round4(totalUsd),
209
274
  unknownModels: Array.from(unknownModels),
210
275
  };