@bongos/core 1.19.904 → 1.19.905
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 +42 -17
- package/docs/copy-inventory.md +4 -4
- package/docs/copy-registry.json +4 -4
- package/docs/module-api-changelog.md +2 -0
- package/migrations/core_251_credit_log_idea_size_index.sql +61 -0
- package/modules/economy/credits.js +8 -0
- package/modules/economy/idea-size-historical.js +149 -0
- package/modules/economy/idea-size.js +381 -0
- package/modules/economy/reward.js +17 -0
- package/modules/hall-ui/public/profile.js +42 -5
- package/modules/ideas/routes/inbox.js +9 -0
- package/modules/ideas/trail-size.js +88 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/src/module-api.js +1 -1
- package/tests/idea_size.mjs +776 -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.905",
|
|
6
|
+
"core_contract": "1.19.905",
|
|
7
|
+
"source_commit": "dc5ca8cc91d1d88f957f722664f60778d2bdc4f1",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-
|
|
9
|
+
"built_at": "2026-09-20T13:17:59.483Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 502,
|
|
13
13
|
"agent_docs_stubbed": 26,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 2283,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 2811,
|
|
20
|
+
"tree_sha256": "85790109607eeddc8439f8473f642747ed158cfb88a269f55222c30bc45016e5",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/ask-for-help/SKILL.md",
|
|
@@ -2097,12 +2097,12 @@
|
|
|
2097
2097
|
{
|
|
2098
2098
|
"path": "docs/copy-inventory.md",
|
|
2099
2099
|
"mode": "0000644",
|
|
2100
|
-
"sha256": "
|
|
2100
|
+
"sha256": "3e657714f3647bfb041f4eda9e4982a2b60747c4aceec0d8d8cbf8ee0de6eb47"
|
|
2101
2101
|
},
|
|
2102
2102
|
{
|
|
2103
2103
|
"path": "docs/copy-registry.json",
|
|
2104
2104
|
"mode": "0000644",
|
|
2105
|
-
"sha256": "
|
|
2105
|
+
"sha256": "1ba7f556256ee4cbb637b9809698c69aff529c080131949b4055058f8fb4716b"
|
|
2106
2106
|
},
|
|
2107
2107
|
{
|
|
2108
2108
|
"path": "docs/design/apex-pass-2-direction.md",
|
|
@@ -2632,7 +2632,7 @@
|
|
|
2632
2632
|
{
|
|
2633
2633
|
"path": "docs/module-api-changelog.md",
|
|
2634
2634
|
"mode": "0000644",
|
|
2635
|
-
"sha256": "
|
|
2635
|
+
"sha256": "ade94268540de319c8d071b7b70654fca4cb4bdd7bcfbbfe8ddca5710329545b"
|
|
2636
2636
|
},
|
|
2637
2637
|
{
|
|
2638
2638
|
"path": "docs/modules-contract.md",
|
|
@@ -3734,6 +3734,11 @@
|
|
|
3734
3734
|
"mode": "0000644",
|
|
3735
3735
|
"sha256": "599449a48b3850eb457a34630a7bd5d95d354602f1a9e9b887593183a457cd21"
|
|
3736
3736
|
},
|
|
3737
|
+
{
|
|
3738
|
+
"path": "migrations/core_251_credit_log_idea_size_index.sql",
|
|
3739
|
+
"mode": "0000644",
|
|
3740
|
+
"sha256": "75bb644b32a70b52ac544edb901abff91aad07701848a470cc52103dc7e74234"
|
|
3741
|
+
},
|
|
3737
3742
|
{
|
|
3738
3743
|
"path": "modules/agents/CLAUDE.md",
|
|
3739
3744
|
"mode": "0000644",
|
|
@@ -4182,7 +4187,17 @@
|
|
|
4182
4187
|
{
|
|
4183
4188
|
"path": "modules/economy/credits.js",
|
|
4184
4189
|
"mode": "0000644",
|
|
4185
|
-
"sha256": "
|
|
4190
|
+
"sha256": "736eed2c89863d6eb28072627a40ac4f8945832cfb7b1d1aba10e6cc65483901"
|
|
4191
|
+
},
|
|
4192
|
+
{
|
|
4193
|
+
"path": "modules/economy/idea-size-historical.js",
|
|
4194
|
+
"mode": "0000644",
|
|
4195
|
+
"sha256": "f99f62eb4eaeda10b32284dbe27f87e64d5b6b82c9eb4aa442057d0de13be761"
|
|
4196
|
+
},
|
|
4197
|
+
{
|
|
4198
|
+
"path": "modules/economy/idea-size.js",
|
|
4199
|
+
"mode": "0000644",
|
|
4200
|
+
"sha256": "d1fa5fda6b34bd2a8cf8d563c780bc4b12f1cdfb955e13f51799a589609ac1c0"
|
|
4186
4201
|
},
|
|
4187
4202
|
{
|
|
4188
4203
|
"path": "modules/economy/migrations/economy_001_fix_gds_shipper_description.sql",
|
|
@@ -4207,7 +4222,7 @@
|
|
|
4207
4222
|
{
|
|
4208
4223
|
"path": "modules/economy/reward.js",
|
|
4209
4224
|
"mode": "0000644",
|
|
4210
|
-
"sha256": "
|
|
4225
|
+
"sha256": "ef4b939d83324c927dd1b1c8e3200212d0b5da973a87db6f9810c9c7edb942ad"
|
|
4211
4226
|
},
|
|
4212
4227
|
{
|
|
4213
4228
|
"path": "modules/economy/routes/cost.js",
|
|
@@ -5037,7 +5052,7 @@
|
|
|
5037
5052
|
{
|
|
5038
5053
|
"path": "modules/hall-ui/public/profile.js",
|
|
5039
5054
|
"mode": "0000644",
|
|
5040
|
-
"sha256": "
|
|
5055
|
+
"sha256": "04015850c00627f2439ec919358516c841d4c32c64a7ba18ca758b3804e804b5"
|
|
5041
5056
|
},
|
|
5042
5057
|
{
|
|
5043
5058
|
"path": "modules/hall-ui/public/profile.states.json",
|
|
@@ -5377,7 +5392,7 @@
|
|
|
5377
5392
|
{
|
|
5378
5393
|
"path": "modules/ideas/routes/inbox.js",
|
|
5379
5394
|
"mode": "0000644",
|
|
5380
|
-
"sha256": "
|
|
5395
|
+
"sha256": "cd4e4d741dcbc4bb581f86aeec5b1e17dfe630712a0e5466a108daba78c74429"
|
|
5381
5396
|
},
|
|
5382
5397
|
{
|
|
5383
5398
|
"path": "modules/ideas/routing.js",
|
|
@@ -5399,6 +5414,11 @@
|
|
|
5399
5414
|
"mode": "0000644",
|
|
5400
5415
|
"sha256": "2d0693b00dded081b6a0082c450762763d324d54f098a8f42a9aa25ec27370c4"
|
|
5401
5416
|
},
|
|
5417
|
+
{
|
|
5418
|
+
"path": "modules/ideas/trail-size.js",
|
|
5419
|
+
"mode": "0000644",
|
|
5420
|
+
"sha256": "f1e60dbc8c6dc0e9f5f5cb93bf26c659422503e8ec66937a3dcee5b64971e995"
|
|
5421
|
+
},
|
|
5402
5422
|
{
|
|
5403
5423
|
"path": "modules/lifecycle/CLAUDE.md",
|
|
5404
5424
|
"mode": "0000644",
|
|
@@ -7762,12 +7782,12 @@
|
|
|
7762
7782
|
{
|
|
7763
7783
|
"path": "package-lock.json",
|
|
7764
7784
|
"mode": "0000644",
|
|
7765
|
-
"sha256": "
|
|
7785
|
+
"sha256": "3b60452f407da16a6c09df92482e570ca061e920dd7537a6f2f84569a13e0545"
|
|
7766
7786
|
},
|
|
7767
7787
|
{
|
|
7768
7788
|
"path": "package.json",
|
|
7769
7789
|
"mode": "0000644",
|
|
7770
|
-
"sha256": "
|
|
7790
|
+
"sha256": "3af5e528e00f1047c76f56189ef168ea8c879f09762b735160b5c6d2e81f9d24"
|
|
7771
7791
|
},
|
|
7772
7792
|
{
|
|
7773
7793
|
"path": "public-docs/index.html",
|
|
@@ -9762,7 +9782,7 @@
|
|
|
9762
9782
|
{
|
|
9763
9783
|
"path": "src/module-api.js",
|
|
9764
9784
|
"mode": "0000644",
|
|
9765
|
-
"sha256": "
|
|
9785
|
+
"sha256": "2489905dea4c9defd22089221f6dce7c522f9860e6b684c9e6cea03a2600a17d"
|
|
9766
9786
|
},
|
|
9767
9787
|
{
|
|
9768
9788
|
"path": "src/module-loader/catalog.js",
|
|
@@ -11849,6 +11869,11 @@
|
|
|
11849
11869
|
"mode": "0000644",
|
|
11850
11870
|
"sha256": "3a2c5d2e6c3231f69847e1e2aea073e78caf26228b500dbdf612b5bae354d0a2"
|
|
11851
11871
|
},
|
|
11872
|
+
{
|
|
11873
|
+
"path": "tests/idea_size.mjs",
|
|
11874
|
+
"mode": "0000644",
|
|
11875
|
+
"sha256": "c307e7c796e7e9f30b38b04144b426895642ea2d978ea5ace0f701ba7744420c"
|
|
11876
|
+
},
|
|
11852
11877
|
{
|
|
11853
11878
|
"path": "tests/idea_spark_hall.mjs",
|
|
11854
11879
|
"mode": "0000644",
|
package/docs/copy-inventory.md
CHANGED
|
@@ -308,7 +308,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
308
308
|
| `7775fec0fa85` | certain | Could not load sessions. | `modules/hall-ui/public/sessions.js:437` |
|
|
309
309
|
| `a2f2e8babef5` | certain | Could not load that page. Try again. | `modules/hall-ui/public/blockers.js:274` |
|
|
310
310
|
| `b2147cc90e36` | certain | Could not load the idea template. Reload, or file from the terminal with capture.js. | `modules/hall-ui/public/ideas.js:626` |
|
|
311
|
-
| `7604af13c40e` | certain | Could not load the profile. | `modules/hall-ui/public/profile.js:
|
|
311
|
+
| `7604af13c40e` | certain | Could not load the profile. | `modules/hall-ui/public/profile.js:873` |
|
|
312
312
|
| `8b69e445592a` | certain | Could not load the question labels; showing the answers as stored. | `modules/hall-ui/public/idea.js:244` |
|
|
313
313
|
| `edd5bafbe075` | certain | Could not load the queue just now. | `modules/hall-ui/public/ideas.js:565` |
|
|
314
314
|
| `02bb0570996a` | certain | Could not load the ranks. | `modules/hall-ui/public/ranks.js:197` |
|
|
@@ -600,7 +600,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
600
600
|
| `6e73716aec6c` | certain | May be offered for sale later | `modules/hall-ui/public/settings.html:297` |
|
|
601
601
|
| `df335a1224ae` | certain | Member since {…} | `modules/hall-ui/public/hall-render.js:563` |
|
|
602
602
|
| `df335a1224ae` | certain | Member since {…} | `modules/hall-ui/public/profile.js:94` |
|
|
603
|
-
| `c7a670e9552d` | certain | Merged into | `modules/hall-ui/public/profile.js:
|
|
603
|
+
| `c7a670e9552d` | certain | Merged into | `modules/hall-ui/public/profile.js:489` |
|
|
604
604
|
| `dce0546c9e34` | certain | Merging work does not put it in front of anyone. This is the step that does: it moves this platform — or any p | `modules/hall-ui/public/deploy.html:39` |
|
|
605
605
|
| `7637613c231b` | certain | Metic rank and above may vote | `modules/hall-ui/public/builders.js:781` |
|
|
606
606
|
| `91328a97fe68` | certain | Mingle pairs you with someone from another role. Saying no costs nothing. | `modules/hall-ui/public/collab.html:87` |
|
|
@@ -918,7 +918,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
918
918
|
| `9d4372b173cf` | certain | Standing sections | `modules/hall-ui/public/index.html:79` |
|
|
919
919
|
| `ec1c2dae4a90` | certain | Standing there | `modules/hall-ui/public/profile.js:314` |
|
|
920
920
|
| `eaad12cacb9d` | certain | Start over | `modules/hall-ui/public/studio.js:235` |
|
|
921
|
-
| `efe530c0e3a7` | certain | Still open | `modules/hall-ui/public/profile.js:
|
|
921
|
+
| `efe530c0e3a7` | certain | Still open | `modules/hall-ui/public/profile.js:494` |
|
|
922
922
|
| `9a4ced1afb6c` | certain | Stop waiting on task {…} | `modules/hall-ui/public/task.js:278` |
|
|
923
923
|
| `b0861c874a2f` | certain | Stopped using it. | `modules/hall-ui/public/settings-specialities.js:200` |
|
|
924
924
|
| `6c6544f5c011` | certain | Stored encrypted; never shown again after saving — only its last four digits. | `modules/hall-ui/public/settings.html:193` |
|
|
@@ -1352,7 +1352,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
1352
1352
|
| `cc5106af4b35` | certain | {…} of {…} criteria satisfied | `modules/hall-ui/public/goals-lib.js:32` |
|
|
1353
1353
|
| `cc5106af4b35` | certain | {…} of {…} criteria satisfied | `modules/hall-ui/public/goals-render.js:682` |
|
|
1354
1354
|
| `cc5106af4b35` | certain | {…} of {…} criteria satisfied | `modules/hall-ui/public/goals.js:45` |
|
|
1355
|
-
| `c3d9687e2375` | certain | {…} of {…} shipped | `modules/hall-ui/public/profile.js:
|
|
1355
|
+
| `c3d9687e2375` | certain | {…} of {…} shipped | `modules/hall-ui/public/profile.js:595` |
|
|
1356
1356
|
| `f32710d0ef37` | certain | {…} of {…} steps cleared. | `modules/hall-ui/public/hall-render.js:1037` |
|
|
1357
1357
|
| `60743fe1e40d` | certain | {…} per page | `modules/hall-ui/public/hall-kit.js:74` |
|
|
1358
1358
|
| `154bc518bab4` | certain | {…} proposed | `modules/hall-ui/public/government.js:695` |
|
package/docs/copy-registry.json
CHANGED
|
@@ -3815,7 +3815,7 @@
|
|
|
3815
3815
|
"surface": "builders-hall",
|
|
3816
3816
|
"text": "Could not load the profile.",
|
|
3817
3817
|
"file": "modules/hall-ui/public/profile.js",
|
|
3818
|
-
"line":
|
|
3818
|
+
"line": 873,
|
|
3819
3819
|
"origin": "js-notice",
|
|
3820
3820
|
"confidence": "certain"
|
|
3821
3821
|
},
|
|
@@ -6443,7 +6443,7 @@
|
|
|
6443
6443
|
"surface": "builders-hall",
|
|
6444
6444
|
"text": "Merged into",
|
|
6445
6445
|
"file": "modules/hall-ui/public/profile.js",
|
|
6446
|
-
"line":
|
|
6446
|
+
"line": 489,
|
|
6447
6447
|
"origin": "js-markup",
|
|
6448
6448
|
"confidence": "certain"
|
|
6449
6449
|
},
|
|
@@ -9305,7 +9305,7 @@
|
|
|
9305
9305
|
"surface": "builders-hall",
|
|
9306
9306
|
"text": "Still open",
|
|
9307
9307
|
"file": "modules/hall-ui/public/profile.js",
|
|
9308
|
-
"line":
|
|
9308
|
+
"line": 494,
|
|
9309
9309
|
"origin": "js-markup",
|
|
9310
9310
|
"confidence": "certain"
|
|
9311
9311
|
},
|
|
@@ -13211,7 +13211,7 @@
|
|
|
13211
13211
|
"surface": "builders-hall",
|
|
13212
13212
|
"text": "{…} of {…} shipped",
|
|
13213
13213
|
"file": "modules/hall-ui/public/profile.js",
|
|
13214
|
-
"line":
|
|
13214
|
+
"line": 595,
|
|
13215
13215
|
"origin": "js-markup",
|
|
13216
13216
|
"confidence": "certain"
|
|
13217
13217
|
},
|
|
@@ -2282,5 +2282,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
2282
2282
|
landed since 1.19.902 with no explicit bump. run 35510679104. (task 1002620)
|
|
2283
2283
|
1.19.904 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2284
2284
|
landed since 1.19.903 with no explicit bump. run 35511494246. (task 1002620)
|
|
2285
|
+
1.19.905 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2286
|
+
landed since 1.19.904 with no explicit bump. run 35513060618. (task 1002620)
|
|
2285
2287
|
---------------------------------------------------------------------------
|
|
2286
2288
|
```
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
-- core_251 an index for SIZE -- the per-idea rollup of the stream-1 ledger
|
|
2
|
+
-- Applied under task 1004093 (BV2.R12, goal 1000095).
|
|
3
|
+
--
|
|
4
|
+
-- WHY. modules/economy/idea-size.js answers "what has this idea's descent
|
|
5
|
+
-- paid" by summing credit_log rows whose reason embeds the idea:
|
|
6
|
+
--
|
|
7
|
+
-- idea.credit.task_ship:<ideaId>:<taskId>
|
|
8
|
+
--
|
|
9
|
+
-- That needs no column and no new table -- the ledger already carries the
|
|
10
|
+
-- attribution, which is why this task needed no SCHEMA change. What it does
|
|
11
|
+
-- need is a way to reach those rows without reading the whole ledger.
|
|
12
|
+
--
|
|
13
|
+
-- WHAT IT COSTS WITHOUT ONE. credit_log is the whole-instance, append-only
|
|
14
|
+
-- credit ledger: every task payout, every session reward, every bonus, for
|
|
15
|
+
-- every builder, forever. The size read runs on GET /inbox/by-builder/:id --
|
|
16
|
+
-- an ordinary page view of a builder's idea trail -- so an unindexed version
|
|
17
|
+
-- is a sequential scan of the entire lifetime ledger on every such view, and
|
|
18
|
+
-- its cost grows with total credit events rather than with anything about the
|
|
19
|
+
-- idea being looked at. A grader panel flagged exactly this on the first round
|
|
20
|
+
-- of task 1004093, and it was right to.
|
|
21
|
+
--
|
|
22
|
+
-- THE SHAPE. A PARTIAL EXPRESSION index:
|
|
23
|
+
--
|
|
24
|
+
-- * the EXPRESSION is split_part(reason, ':', 2) -- the idea id, taken off
|
|
25
|
+
-- the separator. The read groups on that same expression rather than on a
|
|
26
|
+
-- per-idea LIKE, because a prefix match is correct only by virtue of its
|
|
27
|
+
-- trailing colon and dropping it makes idea 1 absorb every payout of ideas
|
|
28
|
+
-- 10, 100 and 1000, silently. An index on the expression is what lets the
|
|
29
|
+
-- honest form of the query stay cheap.
|
|
30
|
+
-- * PARTIAL on the stream-1 prefix, so the index covers only ideator
|
|
31
|
+
-- task-ship rows. Those are a small minority of credit_log: the bulk is
|
|
32
|
+
-- task.shipped and session.token_reward, whose split_part(reason,':',2) is
|
|
33
|
+
-- the empty string and which no size read will ever want. A full index
|
|
34
|
+
-- would be several times the size to answer the same questions.
|
|
35
|
+
--
|
|
36
|
+
-- THE PREDICATE IS A LITERAL ON BOTH SIDES, AND THAT IS LOAD-BEARING. Postgres
|
|
37
|
+
-- only uses a partial index when it can prove the query's WHERE implies the
|
|
38
|
+
-- index predicate, and it cannot prove that about a parameterised
|
|
39
|
+
-- `reason LIKE $1 || '%'` -- the parameter's value is not known when a generic
|
|
40
|
+
-- plan is made. So idea-size.js spells the prefix into the SQL TEXT as a
|
|
41
|
+
-- literal (validated at module load), matching the string below character for
|
|
42
|
+
-- character. If the two ever drift, nothing errors and nothing renders
|
|
43
|
+
-- differently: the index is simply never used again and the scan quietly comes
|
|
44
|
+
-- back. tests/idea_size.mjs reads THIS FILE and holds its predicate against the
|
|
45
|
+
-- economy constant, so the drift reds a test instead of a page.
|
|
46
|
+
--
|
|
47
|
+
-- THE UNDERSCORE IS ESCAPED, and it is not decoration. In LIKE, _ matches any
|
|
48
|
+
-- single character, so an unescaped 'idea.credit.task_ship:%' also matches
|
|
49
|
+
-- 'idea.credit.taskXship:'. No such key exists, which is precisely why nobody
|
|
50
|
+
-- would ever notice; backslash is LIKE's default escape in Postgres, so no
|
|
51
|
+
-- ESCAPE clause is needed and idea-size.js generates the identical string.
|
|
52
|
+
--
|
|
53
|
+
-- Additive and idempotent: it creates an index and touches no row. Not
|
|
54
|
+
-- CONCURRENTLY -- the migration runner wraps each file in a transaction and
|
|
55
|
+
-- CREATE INDEX CONCURRENTLY cannot run inside one. On an instance whose ledger
|
|
56
|
+
-- is large enough for that lock to matter, build it by hand outside the runner
|
|
57
|
+
-- and this statement becomes a no-op.
|
|
58
|
+
|
|
59
|
+
CREATE INDEX IF NOT EXISTS idx_credit_log_idea_task_ship
|
|
60
|
+
ON credit_log ((split_part(reason, ':', 2)))
|
|
61
|
+
WHERE reason LIKE 'idea.credit.task\_ship:%';
|
|
@@ -1273,6 +1273,14 @@ module.exports = {
|
|
|
1273
1273
|
IDEA_FULL_PASS_CREDIT,
|
|
1274
1274
|
IDEA_FULL_PASS_BAR,
|
|
1275
1275
|
IDEA_TASK_SHIP_BASE,
|
|
1276
|
+
// The two pieces of the stream-1 payout ARITHMETIC, exported for
|
|
1277
|
+
// modules/economy/idea-size.js (task 1004097 / BV2.R18). A pre-existing
|
|
1278
|
+
// idea backfills the payouts its descent never received, and a second copy
|
|
1279
|
+
// of this arithmetic would make the backfilled figure differ from the money
|
|
1280
|
+
// the same descent will earn from now on — the exact drift the
|
|
1281
|
+
// ideaGradeFactor comment above exists to prevent.
|
|
1282
|
+
ideaGradeFactor,
|
|
1283
|
+
descentPayoutMaxDepth,
|
|
1276
1284
|
QUICK_IDEA_FACTOR,
|
|
1277
1285
|
IDEA_GOVERNANCE_PASS_MAX,
|
|
1278
1286
|
IDEA_GOVERNANCE_PASS_FLOOR,
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// modules/economy/idea-size-historical.js — THE HISTORICAL HALF OF SIZE
|
|
4
|
+
// (task 1004097, BV2.R18, goal 1000095; spec
|
|
5
|
+
// docs/specs/bongos-v2-ideator-surface.md decision 17).
|
|
6
|
+
//
|
|
7
|
+
// WHAT IS MISSING WITHOUT THIS, and it is not what the task title suggests.
|
|
8
|
+
// A pre-existing idea's ledger figure is not ABSENT, it is PARTIAL. Stream 1's
|
|
9
|
+
// direct payout has existed since ADR 0172, so a task promoted from an idea and
|
|
10
|
+
// shipped since then really was paid and really is in credit_log. What never
|
|
11
|
+
// happened is the DESCENT payout: awardIdeaDescentShipCredit only started
|
|
12
|
+
// paying with task 1004088, so every hop below the first, for every task that
|
|
13
|
+
// shipped before that, earned its origin nothing and never will.
|
|
14
|
+
//
|
|
15
|
+
// So the backfill is not "compute a size for ideas that have none". It is the
|
|
16
|
+
// GAP between the descent an idea actually has and the part of that descent the
|
|
17
|
+
// ledger was there to pay for.
|
|
18
|
+
//
|
|
19
|
+
// NO MONEY IS BOOKED (spec decision 17, the owner's ruling on interview Q6).
|
|
20
|
+
// Nothing here writes to credit_log, and nothing here is reachable from a ship
|
|
21
|
+
// path. The estimate is computed at READ time, every time, from the descent as
|
|
22
|
+
// it stands. Re-paying work already paid for once is exactly what the owner
|
|
23
|
+
// declined, and a backfill that wrote rows would also break stream 1's
|
|
24
|
+
// idempotency key — the ledger would then hold a payment for a ship that never
|
|
25
|
+
// triggered one.
|
|
26
|
+
//
|
|
27
|
+
// WHICH IS WHY THE TWO HALVES ARE KEPT APART RATHER THAN ADDED UP AND FORGOTTEN.
|
|
28
|
+
// `paid` is money that moved; `estimated` is money that did not. A single total
|
|
29
|
+
// with no marker is the number the spec calls a quiet lie, and the done-when
|
|
30
|
+
// asks for the opposite — that anywhere size is shown makes clear which kind of
|
|
31
|
+
// number it is. So this returns both, always, and the surfaces carry the split
|
|
32
|
+
// rather than a boolean bolted on at the end.
|
|
33
|
+
//
|
|
34
|
+
// THE ARITHMETIC IS NOT REIMPLEMENTED HERE. `ideaGradeFactor`,
|
|
35
|
+
// `descentPayoutMaxDepth` and `IDEA_TASK_SHIP_BASE` come from credits.js, which
|
|
36
|
+
// is where the real payout gets them. A second copy would make an idea's
|
|
37
|
+
// backfilled past disagree with the money the same descent earns from tomorrow
|
|
38
|
+
// — the same drift the "EXTRACTED so the two cannot disagree" note on
|
|
39
|
+
// ideaGradeFactor was written to prevent, one module over.
|
|
40
|
+
|
|
41
|
+
const {
|
|
42
|
+
IDEA_TASK_SHIP_BASE,
|
|
43
|
+
ideaGradeFactor,
|
|
44
|
+
descentPayoutMaxDepth,
|
|
45
|
+
} = require('./credits');
|
|
46
|
+
|
|
47
|
+
// priceDescentForIdea — PURE. What one idea's descent is worth, split into the
|
|
48
|
+
// part the ledger actually paid and the part it never did.
|
|
49
|
+
//
|
|
50
|
+
// tasks — every task under this idea, each { task_id, depth, status }.
|
|
51
|
+
// Depth 0 is a task the idea was promoted INTO; depth n is n
|
|
52
|
+
// hops below one of those. Shortest depth per task, which is the
|
|
53
|
+
// rule the walk and the payout both already apply.
|
|
54
|
+
// grade — the idea's grade ('quick' | 'spark' | 'full')
|
|
55
|
+
// completeness— its completeness score, or null
|
|
56
|
+
// decay — the resolved per-instance dial (reward-policy ideaDescentDecay)
|
|
57
|
+
// paidByTask — Map<taskIdString, creditsPaid> from credit_log, per task
|
|
58
|
+
//
|
|
59
|
+
// Returns { paid, estimated, payments, estimatedPayments }.
|
|
60
|
+
//
|
|
61
|
+
// ONLY A SHIPPED TASK COUNTS. The payout fires at ship, so a ready or abandoned
|
|
62
|
+
// descendant has earned its origin nothing and estimating for it would credit
|
|
63
|
+
// work that has not happened — the surface would then show a size that FELL
|
|
64
|
+
// when the task was later abandoned. Everything here is anchored on ships that
|
|
65
|
+
// actually occurred.
|
|
66
|
+
//
|
|
67
|
+
// PAID ALWAYS WINS FOR A TASK THE LEDGER KNOWS. If credit_log holds a payment
|
|
68
|
+
// for (idea, task), that figure is used verbatim and no estimate is made for it
|
|
69
|
+
// — money that moved is not re-derived. This is what keeps the paid half
|
|
70
|
+
// immutable while the arithmetic around it is free to change: a later decay dial
|
|
71
|
+
// or a re-scored completeness cannot retroactively alter what was banked.
|
|
72
|
+
//
|
|
73
|
+
// ROUNDED ONCE PER PAYMENT, the way the writer rounds — per (idea, task), never
|
|
74
|
+
// on the total. Rounding the sum would make the backfilled figure differ from
|
|
75
|
+
// what the same descent would have been paid hop by hop, which is the whole
|
|
76
|
+
// thing this function exists to reproduce.
|
|
77
|
+
function priceDescentForIdea({ tasks, grade, completeness, decay, paidByTask } = {}) {
|
|
78
|
+
const out = { paid: 0, estimated: 0, payments: 0, estimatedPayments: 0 };
|
|
79
|
+
const list = Array.isArray(tasks) ? tasks : [];
|
|
80
|
+
const paidMap = paidByTask instanceof Map ? paidByTask : new Map();
|
|
81
|
+
|
|
82
|
+
// A decay of 0 is a legal instance setting meaning "only the direct task
|
|
83
|
+
// pays", and it makes the cap 0 — so depth 0 is still priced and nothing
|
|
84
|
+
// below it is. Guarding on the cap rather than on the rate keeps that in one
|
|
85
|
+
// place.
|
|
86
|
+
const rate = Number.isFinite(Number(decay)) && Number(decay) >= 0 && Number(decay) < 1 ? Number(decay) : null;
|
|
87
|
+
if (rate === null) return out;
|
|
88
|
+
const maxDepth = descentPayoutMaxDepth(rate);
|
|
89
|
+
const factor = ideaGradeFactor(grade, completeness);
|
|
90
|
+
|
|
91
|
+
// One entry per task: a task reachable twice is one ship and owes one
|
|
92
|
+
// payment, at its shortest depth. The caller hands shortest-depth rows, and
|
|
93
|
+
// this keeps the guarantee local rather than trusting it.
|
|
94
|
+
const seen = new Set();
|
|
95
|
+
for (const t of list) {
|
|
96
|
+
const key = String(t && t.task_id);
|
|
97
|
+
if (!/^[1-9]\d*$/.test(key) || seen.has(key)) continue;
|
|
98
|
+
seen.add(key);
|
|
99
|
+
|
|
100
|
+
if (paidMap.has(key)) {
|
|
101
|
+
out.paid += Number(paidMap.get(key)) || 0;
|
|
102
|
+
out.payments += 1;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (t.status !== 'shipped') continue;
|
|
107
|
+
const depth = Number(t.depth);
|
|
108
|
+
if (!Number.isInteger(depth) || depth < 0) continue;
|
|
109
|
+
// Depth 0 is the direct payout at full share; below that the cap applies,
|
|
110
|
+
// because past it even a perfect idea's decayed share rounds away.
|
|
111
|
+
if (depth > 0 && (rate === 0 || depth > maxDepth)) continue;
|
|
112
|
+
const delta = Math.round(IDEA_TASK_SHIP_BASE * factor * Math.pow(rate, depth));
|
|
113
|
+
if (delta <= 0) continue;
|
|
114
|
+
out.estimated += delta;
|
|
115
|
+
out.estimatedPayments += 1;
|
|
116
|
+
}
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// descentRowsFromTrail — the tasks under an idea, as the TRAIL already holds
|
|
121
|
+
// them. Pure, and deliberately not a walk.
|
|
122
|
+
//
|
|
123
|
+
// modules/ideas/trail-descent.js has already walked the whole page by the time
|
|
124
|
+
// size is attached (the route composes them in that order), so the trail path
|
|
125
|
+
// re-uses that result and costs no extra round trip at all. `lineage_tasks` are
|
|
126
|
+
// the idea's own tasks — depth 0, the full-share hop — and `descent_tasks` are
|
|
127
|
+
// everything below them, already carrying their shortest depth and their status.
|
|
128
|
+
//
|
|
129
|
+
// A lineage row that also appears in the descent is dropped by the depth-0 entry
|
|
130
|
+
// winning: the Set in priceDescentForIdea keeps the first sighting, and the
|
|
131
|
+
// direct tasks are emitted first for exactly that reason.
|
|
132
|
+
function descentRowsFromTrail(idea) {
|
|
133
|
+
const rows = [];
|
|
134
|
+
const direct = Array.isArray(idea && idea.lineage_tasks) ? idea.lineage_tasks : [];
|
|
135
|
+
for (const t of direct) rows.push({ task_id: t.task_id, depth: 0, status: t.status });
|
|
136
|
+
// The legacy single-FK promotion, for rows that predate ideas_task_lineage —
|
|
137
|
+
// the same fallback the trail and trail-descent both apply.
|
|
138
|
+
if (direct.length === 0 && idea && idea.promoted_to_task_id != null) {
|
|
139
|
+
rows.push({ task_id: idea.promoted_to_task_id, depth: 0, status: idea.promoted_task_status ?? null });
|
|
140
|
+
}
|
|
141
|
+
const below = Array.isArray(idea && idea.descent_tasks) ? idea.descent_tasks : [];
|
|
142
|
+
for (const t of below) rows.push({ task_id: t.task_id, depth: t.depth, status: t.status });
|
|
143
|
+
return rows;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
module.exports = {
|
|
147
|
+
priceDescentForIdea,
|
|
148
|
+
descentRowsFromTrail,
|
|
149
|
+
};
|