@bongos/core 1.21.80 → 1.21.81

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 CHANGED
@@ -2,11 +2,11 @@
2
2
  "artifact": "bongos-core",
3
3
  "manifest_schema": 1,
4
4
  "generator": "scripts/gds/package-core.js",
5
- "core_version": "1.21.80",
6
- "core_contract": "1.21.80",
7
- "source_commit": "b836a5577c82fe4d7594fc0a26a3e4f13076750a",
5
+ "core_version": "1.21.81",
6
+ "core_contract": "1.21.81",
7
+ "source_commit": "cf13896f2510e85aa87ab7ad8fbe400a57f37d54",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-10-09T00:19:50.271Z",
9
+ "built_at": "2026-10-09T00:28:08.709Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 590,
@@ -17,7 +17,7 @@
17
17
  "gate": "passed"
18
18
  },
19
19
  "file_count": 3611,
20
- "tree_sha256": "669bec1b067c38b5bf3b4bc4dd955774f73039820ab806892e1f5a05305813d8",
20
+ "tree_sha256": "ae8df2494a02571e7e6cdc50e626b98cf07fbb4e76b8c8d9f7f851518dc121e8",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/ask-for-help/SKILL.md",
@@ -2862,7 +2862,7 @@
2862
2862
  {
2863
2863
  "path": "docs/module-api-changelog.md",
2864
2864
  "mode": "0000644",
2865
- "sha256": "32d825702934aa11e29c66c61e8cc5d3b7ba8133217227656308140c05315062"
2865
+ "sha256": "b32998694f39731350f5e5eff315a2732e48be195f458ccc3de891de9c2442a7"
2866
2866
  },
2867
2867
  {
2868
2868
  "path": "docs/modules-contract.md",
@@ -4802,7 +4802,7 @@
4802
4802
  {
4803
4803
  "path": "modules/economy/credits.js",
4804
4804
  "mode": "0000644",
4805
- "sha256": "37eac58e02d10d734513251857e92050ab8c5c62844ad156bc005b274dbd5147"
4805
+ "sha256": "50c51a283fc75facdce611db04c97cef6ec048a080e10f446716a841a449d8aa"
4806
4806
  },
4807
4807
  {
4808
4808
  "path": "modules/economy/earnings.js",
@@ -4902,7 +4902,7 @@
4902
4902
  {
4903
4903
  "path": "modules/government/board.js",
4904
4904
  "mode": "0000644",
4905
- "sha256": "d95e53c9aec8dc4614aec18da37644da44256d0977195ae32ad5951a1d50ba09"
4905
+ "sha256": "d3009a87799108ab8bc58022b74ddb7cbd2f6cad44e7c767bf6bdfc396021f15"
4906
4906
  },
4907
4907
  {
4908
4908
  "path": "modules/government/catalog.js",
@@ -10087,12 +10087,12 @@
10087
10087
  {
10088
10088
  "path": "package-lock.json",
10089
10089
  "mode": "0000644",
10090
- "sha256": "12eba8a8f76736d82a5a1cf8f991f43415491163325465b2197cac227c6c3544"
10090
+ "sha256": "f8c13471f23a50668aa5ec8c79f68f3e039be8e38ec84510551b69637b16f699"
10091
10091
  },
10092
10092
  {
10093
10093
  "path": "package.json",
10094
10094
  "mode": "0000644",
10095
- "sha256": "186a539924b6c2d566c4c23a84d99b3574567b41babe3eddc4e8f3406480e96e"
10095
+ "sha256": "257169d46b1ec312f90e5062c1dc92d85f865608a14f06173cf2110b6b3abe79"
10096
10096
  },
10097
10097
  {
10098
10098
  "path": "public-docs/index.html",
@@ -10112,7 +10112,7 @@
10112
10112
  {
10113
10113
  "path": "release-notes.json",
10114
10114
  "mode": "0000644",
10115
- "sha256": "fced1b5988cd7b0496a0f28a532cec8d233d2889b4ca667f2dc0485bf62fb678"
10115
+ "sha256": "4aca4ff0ed7bb607cde50892af1d128ac444fe5e8f823b8587f559414f14da01"
10116
10116
  },
10117
10117
  {
10118
10118
  "path": "scripts/cron/style_guide_audit.sh",
@@ -12362,7 +12362,7 @@
12362
12362
  {
12363
12363
  "path": "src/module-api.js",
12364
12364
  "mode": "0000644",
12365
- "sha256": "5e3a905a711427b31f0965112e50a85872a8d45c3bcf8e2c7f4c6b436206d436"
12365
+ "sha256": "02c4342378ad3dd61bd2bb44b0c2e8077a45d1388cbe16cabce84038ac188f56"
12366
12366
  },
12367
12367
  {
12368
12368
  "path": "src/module-loader/catalog.js",
@@ -15137,7 +15137,7 @@
15137
15137
  {
15138
15138
  "path": "tests/idea_task_ship_credit.mjs",
15139
15139
  "mode": "0000644",
15140
- "sha256": "e7f0807491c094fddedd518380af73a98c7ac17ac9d42736d9ed9a7cd6d4bf51"
15140
+ "sha256": "beadb4d87a4fcab2437f85746f5d2943ab55a3a0725a24d1b6bdc0cbc8076f62"
15141
15141
  },
15142
15142
  {
15143
15143
  "path": "tests/idea_templates.mjs",
@@ -16157,7 +16157,7 @@
16157
16157
  {
16158
16158
  "path": "tests/profile_earnings.mjs",
16159
16159
  "mode": "0000644",
16160
- "sha256": "f6d853c7c9d75027fb7f64cba76242726a68f69be8d0a3501d4b80d8b6ee32a3"
16160
+ "sha256": "cab6e081d224a192df1d8d1834ddf4b6cdfeeb1be2a1fde520faa39edb04da18"
16161
16161
  },
16162
16162
  {
16163
16163
  "path": "tests/profile_fields.mjs",
@@ -2992,5 +2992,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
2992
2992
  landed since 1.21.78 with no explicit bump. run 37838546662. (task 1002620)
2993
2993
  1.21.80 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2994
2994
  landed since 1.21.79 with no explicit bump. run 37864152237. (task 1002620)
2995
+ 1.21.81 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2996
+ landed since 1.21.80 with no explicit bump. run 37864877677. (task 1002620)
2995
2997
  ---------------------------------------------------------------------------
2996
2998
  ```
@@ -602,10 +602,36 @@ async function awardIdeaTaskShipCredit(client, { taskId, config = rewardConfig()
602
602
  LIMIT 1`,
603
603
  [taskId]
604
604
  );
605
- if (rows.length === 0 || rows[0].captured_by == null) {
605
+ // THE GOAL AN IDEA BECAME (task 1004442). A Full Idea ratified into a goal
606
+ // writes ideas_goal_ratifications and NO lineage row, so every task later
607
+ // filed under that goal shipped paying the idea nothing. With no lineage row
608
+ // the goal's idea is the source, at the same full share; a lineage row wins.
609
+ let source = rows[0];
610
+ let viaGoalId = null;
611
+ if (!source) {
612
+ const born = await client.query(
613
+ `SELECT r.idea_id,
614
+ r.goal_id,
615
+ i.captured_by,
616
+ COALESCE(g.grade, 'quick') AS grade,
617
+ g.completeness_score AS completeness,
618
+ g.developed_by AS developed_by
619
+ FROM tasks t
620
+ JOIN ideas_goal_ratifications r ON r.goal_id = t.goal_id
621
+ JOIN idea_inbox i ON i.id = r.idea_id
622
+ LEFT JOIN ideas_grades g ON g.idea_id = r.idea_id
623
+ WHERE t.id = $1
624
+ ORDER BY r.ratified_at ASC, r.idea_id ASC
625
+ LIMIT 1`,
626
+ [taskId]
627
+ );
628
+ source = born.rows[0];
629
+ viaGoalId = source ? source.goal_id : null;
630
+ }
631
+ if (!source || source.captured_by == null) {
606
632
  return { credited: 0, reason: 'no_source_idea', ideaId: null, authorId: null };
607
633
  }
608
- const { idea_id: ideaId, captured_by: authorId, grade, completeness, developed_by: developedBy } = rows[0];
634
+ const { idea_id: ideaId, captured_by: authorId, grade, completeness, developed_by: developedBy } = source;
609
635
  const factor = ideaGradeFactor(grade, completeness);
610
636
  const delta = Math.round(IDEA_TASK_SHIP_BASE * factor);
611
637
  if (delta <= 0) {
@@ -625,7 +651,7 @@ async function awardIdeaTaskShipCredit(client, { taskId, config = rewardConfig()
625
651
  payees,
626
652
  reason,
627
653
  taskId,
628
- describe: (p) => `Task #${taskId} shipped under idea #${ideaId} (${gradeLabel}) → ${p.delta}c to its ${p.role.replace(/_/g, ' ')} (ideator lane stream 1, ADR 0172${p.role === 'developer' || p.role === 'spark_author' ? '; split ADR 0185' : ''})`,
654
+ describe: (p) => `Task #${taskId} shipped under ${viaGoalId != null ? `goal #${viaGoalId}, ratified from ` : ''}idea #${ideaId} (${gradeLabel}) → ${p.delta}c to its ${p.role.replace(/_/g, ' ')} (ideator lane stream 1, ADR 0172${p.role === 'developer' || p.role === 'spark_author' ? '; split ADR 0185' : ''})`,
629
655
  });
630
656
  return credited > 0
631
657
  ? { credited, reason: 'paid', ideaId: Number(ideaId), authorId: Number(authorId), payees: booked }
@@ -556,10 +556,10 @@ function passPayload(closed, { authorId, completenessScore, passedAt }) {
556
556
  // credit is booked through the `reward` PORT inside the same transaction that
557
557
  // writes the outcome — if the credit write fails, the close rolls back and the
558
558
  // item stays open, so the vote is retried rather than silently unpaid.
559
- // `resolveOptional` returning null (no economy module), or a reward port that
560
- // does not yet carry awardIdeaGovernancePassCredit (IC5 unbuilt), is a CLEAN
559
+ // The payout is economy's `awardIdeaGovernancePassCredit` (stream 3, IC5). A null
560
+ // `resolveOptional` (no economy), or a port without it (an older one), is a CLEAN
561
561
  // NO-OP: the item still closes, nothing is booked, and the durable
562
- // `outcome = 'passed'` row lets IC5's re-drive sweep pay later. The port is
562
+ // `outcome = 'passed'` row is what a re-drive sweep would pay from. The port is
563
563
  // resolved and the payload's reads happen BEFORE the transaction opens.
564
564
  //
565
565
  // `board.item.passed` is then emitted AFTER commit, emitAsync, for non-money
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.21.80",
3
+ "version": "1.21.81",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.21.80",
9
+ "version": "1.21.81",
10
10
  "license": "AGPL-3.0-or-later",
11
11
  "dependencies": {
12
12
  "express": "^4.21.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.21.80",
3
+ "version": "1.21.81",
4
4
  "description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "main": "src/platform-server.js",
@@ -9306,5 +9306,11 @@
9306
9306
  "id": "1004393",
9307
9307
  "text": "The tweak editor is ready to show landing and status pages side by side with the words, instead of in a separate window. It stays off until a small web-server change is made on the host; those steps are waiting in the owner's"
9308
9308
  }
9309
+ ],
9310
+ "1.21.81": [
9311
+ {
9312
+ "id": "1004442",
9313
+ "text": "When the board turns someone's idea into a goal, every task later finished under that goal now pays the idea's author, as promised. Before this, only work promoted straight from the idea paid them, so a goal full of finished w"
9314
+ }
9309
9315
  ]
9310
9316
  }
package/src/module-api.js CHANGED
@@ -75,7 +75,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
75
75
  // MAJOR (see allowBoxScope below): passes the request through untouched.
76
76
  function deprecatedNoopMiddleware(_req, _res, next) { next(); }
77
77
 
78
- const CORE_VERSION = '1.21.80'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
78
+ const CORE_VERSION = '1.21.81'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
79
79
 
80
80
  // A namespaced logger so a module's log lines are attributable + consistent.
81
81
  // Usage: const log = api.logger('discord'); log.info('mounted');
@@ -113,5 +113,50 @@ await test('the lane opted out, or a task with no source idea, pays nothing', as
113
113
  assert.equal(r.reason, 'no_source_idea');
114
114
  });
115
115
 
116
+ // task 1004442: a Full Idea ratified into a GOAL writes no lineage row, so a
117
+ // task filed later under that goal is found through the goal instead.
118
+ function goalStubClient(lineage, born) {
119
+ const c = stubClient(lineage);
120
+ const inner = c.query;
121
+ c.goalAsked = 0;
122
+ c.query = async (sql, params) => {
123
+ if (/FROM tasks t\s+JOIN ideas_goal_ratifications/i.test(sql)) {
124
+ c.goalAsked++;
125
+ assert.equal(params[0], 100, 'asks about the shipping task');
126
+ assert.match(sql, /r\.goal_id = t\.goal_id/, 'joined on the task\'s own goal');
127
+ return { rows: born ? [born] : [] };
128
+ }
129
+ return inner(sql, params);
130
+ };
131
+ return c;
132
+ }
133
+
134
+ await test('a task filed under a goal RATIFIED FROM an idea pays that idea, at the full share', async () => {
135
+ const c = goalStubClient(null, { idea_id: 9, goal_id: 55, captured_by: 4, grade: 'full', completeness: 80 });
136
+ const r = await credits.awardIdeaTaskShipCredit(c, { taskId: 100, config: ON });
137
+ assert.equal(r.credited, 4, '5 × 0.80, the same as a lineage task');
138
+ assert.equal(r.ideaId, 9);
139
+ assert.equal(c.credited[0].builder_id, 4);
140
+ assert.equal(c.credited[0].reason, 'idea.credit.task_ship:9:100', 'the stream-1 key, so size and the trail count it');
141
+ const again = await credits.awardIdeaTaskShipCredit(c, { taskId: 100, config: ON });
142
+ assert.equal(again.credited, 0, 'once per (idea, task)');
143
+ });
144
+
145
+ await test('a lineage row WINS: the goal is not asked and its idea is not paid as well', async () => {
146
+ const c = goalStubClient({ idea_id: 7, captured_by: 3, grade: 'full', completeness: 80 },
147
+ { idea_id: 9, goal_id: 55, captured_by: 4, grade: 'full', completeness: 80 });
148
+ const r = await credits.awardIdeaTaskShipCredit(c, { taskId: 100, config: ON });
149
+ assert.equal(r.ideaId, 7);
150
+ assert.equal(c.goalAsked, 0);
151
+ assert.equal(c.credited.length, 1);
152
+ });
153
+
154
+ await test('a goal no idea was ratified into pays nothing', async () => {
155
+ const c = goalStubClient(null, null);
156
+ const r = await credits.awardIdeaTaskShipCredit(c, { taskId: 100, config: ON });
157
+ assert.equal(r.reason, 'no_source_idea');
158
+ assert.equal(c.goalAsked, 1);
159
+ });
160
+
116
161
  console.log(`\nidea_task_ship_credit: ${passed} passed, ${failed} failed`);
117
162
  if (failed > 0) process.exit(1);
@@ -47,7 +47,7 @@ await test('a descent hop is told from a direct payout by the WRITER\'s own word
47
47
  const src = fs.readFileSync(path.join(ROOT, 'modules/economy/credits.js'), 'utf8');
48
48
  // the descent writer and the direct writer, by their own describe() lines
49
49
  const descent = src.match(/describe: \(p\) => `Task #\$\{taskId\} shipped \$\{depth\} hop[^\n]*/);
50
- const direct = src.match(/describe: \(p\) => `Task #\$\{taskId\} shipped under idea[^\n]*/);
50
+ const direct = src.match(/describe: \(p\) => `Task #\$\{taskId\} shipped under [^\n]*/); // "under idea" or, since task 1004442, "under goal …, ratified from idea"
51
51
  assert.ok(descent && direct, 'both writers are where this test reads them');
52
52
  assert.ok(descent[0].includes(earnings.DESCENT_MARK.trim()), 'the descent writer says DESCENT_MARK');
53
53
  assert.ok(!direct[0].includes(earnings.DESCENT_MARK.trim()), 'and the direct one does not, or every idea payout would read as descent');