@bongos/core 1.19.487 → 1.19.488

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.19.487",
6
- "core_contract": "1.19.487",
7
- "source_commit": "71ab4261b5804066611e986c2372ef9f5b21ab3f",
5
+ "core_version": "1.19.488",
6
+ "core_contract": "1.19.488",
7
+ "source_commit": "47b82e47bd160e9d3c6f92ff6ab3979d5638c3c4",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-09-02T21:17:37.696Z",
9
+ "built_at": "2026-09-02T21:47:00.422Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 422,
@@ -17,7 +17,7 @@
17
17
  "gate": "passed"
18
18
  },
19
19
  "file_count": 2299,
20
- "tree_sha256": "ee7902bc51f7b7081ed0e074ba5656fb11df18e76286e01cce5b10f25016402e",
20
+ "tree_sha256": "a6f60f8c438272d2d5fc31bcc2529b65883daa934f70e98c65d09f616f7eae19",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/blocker-review/SKILL.md",
@@ -2162,7 +2162,7 @@
2162
2162
  {
2163
2163
  "path": "docs/module-api-changelog.md",
2164
2164
  "mode": "0000644",
2165
- "sha256": "25fc5fcc358a52f1cbd1e6b0b3d6c1ec5636a9bc35bf0d0a6448b8b3b4f386ee"
2165
+ "sha256": "9398d4da8d9cb14b453d1c71e991bd2cd32f0e33dcf4aa9d2e462584340c295b"
2166
2166
  },
2167
2167
  {
2168
2168
  "path": "docs/modules-contract.md",
@@ -4812,7 +4812,7 @@
4812
4812
  {
4813
4813
  "path": "modules/lifecycle/db-claim-reads.js",
4814
4814
  "mode": "0000644",
4815
- "sha256": "7aafa6f538811bd29d7f8ce3ebbf59fea72a90fea99de1fdfb6c62124eff23d5"
4815
+ "sha256": "f927a56ba06659ffb51bf744edd80352da52fd0e7eac9c705cfaa00aaaba2ec7"
4816
4816
  },
4817
4817
  {
4818
4818
  "path": "modules/lifecycle/db-claims.js",
@@ -6792,12 +6792,12 @@
6792
6792
  {
6793
6793
  "path": "package-lock.json",
6794
6794
  "mode": "0000644",
6795
- "sha256": "0f4dc55c4ca65e7ae596a287086fd476260cb20e767f21294a089bba4b217e78"
6795
+ "sha256": "4300998231014d004c90c769abca14044ffc689e0fd5545162fd5532e4f3797e"
6796
6796
  },
6797
6797
  {
6798
6798
  "path": "package.json",
6799
6799
  "mode": "0000644",
6800
- "sha256": "7f5e6da8cdf0dfc5b93b8589469aef923a150ddf6bf4e2b7584d503646797ed1"
6800
+ "sha256": "f3b425c5aedc4856ef43f5f525d1fb7d42abe1ae4624fc5a0bfa978ed3b01394"
6801
6801
  },
6802
6802
  {
6803
6803
  "path": "public-docs/index.html",
@@ -7862,7 +7862,7 @@
7862
7862
  {
7863
7863
  "path": "scripts/gds/ship-grade-steps.js",
7864
7864
  "mode": "0000644",
7865
- "sha256": "f47c4a0d6a6dfdc386e3b7e1da456c0c983b727672646cb1d033d8b11c03f5f0"
7865
+ "sha256": "1c30c7ff4cd52cbf53004e196436ad51cc3d5205e9697567747a7f7380236335"
7866
7866
  },
7867
7867
  {
7868
7868
  "path": "scripts/gds/ship-grade.js",
@@ -7932,7 +7932,7 @@
7932
7932
  {
7933
7933
  "path": "scripts/gds/ship.js",
7934
7934
  "mode": "0000644",
7935
- "sha256": "7264827d7dc6a5d8babf5b0f6aa8384fa9c14fca71ba07a649226ce98dd84e0d"
7935
+ "sha256": "a612a450145476ea8d6705d7a45b8a84e2dc861ad8cb5be8c6aa69252b5c54c5"
7936
7936
  },
7937
7937
  {
7938
7938
  "path": "scripts/gds/skill-preflight.js",
@@ -8457,7 +8457,7 @@
8457
8457
  {
8458
8458
  "path": "src/module-api.js",
8459
8459
  "mode": "0000644",
8460
- "sha256": "ff62f569bf6ffc073ef475c0460eb315f1161aef08287569e8c0049bc72a74a6"
8460
+ "sha256": "4c4a1ef880078313e11a941039e614270cbde15b4e4cdf015927d286f8fe531d"
8461
8461
  },
8462
8462
  {
8463
8463
  "path": "src/module-loader/catalog.js",
@@ -9822,7 +9822,7 @@
9822
9822
  {
9823
9823
  "path": "tests/grader_regrade_delta.mjs",
9824
9824
  "mode": "0000644",
9825
- "sha256": "9532fa686519ebfef7e5d3c3b323cafbf9e8a467c24da3ddc9257d29a5a3b942"
9825
+ "sha256": "50a785eb6e2c71d5a043c2ec2c37a835777da6397dac3ffc2542acdde14b57a8"
9826
9826
  },
9827
9827
  {
9828
9828
  "path": "tests/grader_severity_gate.mjs",
@@ -1411,5 +1411,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
1411
1411
  landed since 1.19.485 with no explicit bump. run 33682949187. (task 1002620)
1412
1412
  1.19.487 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1413
1413
  landed since 1.19.486 with no explicit bump. run 33684267982. (task 1002620)
1414
+ 1.19.488 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1415
+ landed since 1.19.487 with no explicit bump. run 33686951657. (task 1002620)
1414
1416
  ---------------------------------------------------------------------------
1415
1417
  ```
@@ -140,9 +140,19 @@ async function getLastClaimHolder(taskId) {
140
140
 
141
141
  // getTaskGrade — fetch the current grade row for a task (V4.R08 diff-hash
142
142
  // reuse). Returns { diff_hash, passed, score, threshold, grader_kind,
143
- // grader_version, rubric_scores, signals, notes_md } or null when no grade
144
- // exists yet. Used by ship.js before the grade step to detect "same diff →
143
+ // grader_version, rubric_scores, signals, notes_md, issues } or null when no
144
+ // grade exists yet. Used by ship.js before the grade step to detect "same diff →
145
145
  // skip the panel".
146
+ //
147
+ // `issues` is LIFTED out of the signals jsonb (task 1003488). The panel's
148
+ // findings are stored there because `task_grades` has no column for them
149
+ // (db-grade.js), but every in-memory grade carries them at the top level, so a
150
+ // reader that did not know the storage detail saw an empty `grade.issues` on a
151
+ // row that in fact held findings — which is how the delta re-grade was silently
152
+ // running with no prior-round context, and how a builder following
153
+ // /grade-recover reads a failed grade as having no findings at all. Lifting is
154
+ // additive: `signals.issues` stays exactly where it is for anything already
155
+ // reading it.
146
156
  async function getTaskGrade(taskId) {
147
157
  const { rows } = await pool.query(
148
158
  `SELECT diff_hash, passed, score, threshold, grader_kind, grader_version,
@@ -152,7 +162,10 @@ async function getTaskGrade(taskId) {
152
162
  LIMIT 1`,
153
163
  [taskId]
154
164
  );
155
- return rows[0] ?? null;
165
+ const row = rows[0] ?? null;
166
+ if (!row) return null;
167
+ const fromSignals = row.signals && row.signals.issues;
168
+ return { ...row, issues: Array.isArray(fromSignals) ? fromSignals : [] };
156
169
  }
157
170
 
158
171
  module.exports = {
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.487",
3
+ "version": "1.19.488",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.487",
9
+ "version": "1.19.488",
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.19.487",
3
+ "version": "1.19.488",
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",
@@ -94,10 +94,31 @@ async function logGraderPanelCost(taskId, gradeResult) {
94
94
  // audit — the compact regrade block recorded into the new grade's signals
95
95
  // so grade_attempts rounds read as remediation history (the
96
96
  // task-1002661 reader consumes this).
97
+ // The prior round's findings, from EITHER grade shape (task 1003488).
98
+ //
99
+ // A grade exists in two shapes and this function is handed both. The in-memory
100
+ // result `validateAndScore`/`serverSideGuards` return carries `issues` at the top
101
+ // level; a grade READ BACK from the API does not, because `task_grades` has no
102
+ // `issues` column and `db-grade.js` deliberately writes them into the signals
103
+ // jsonb instead. `--regrade` passes the DB row (`ship-flow.js`: `tRes.data.grade`),
104
+ // so reading only the top-level key silently yielded [] on every re-grade — the
105
+ // panel was told it was on round N while being handed nothing about round N-1,
106
+ // and the "answered findings can only re-veto with NEW evidence" protection
107
+ // (task 1002662) cannot hold when the answered findings are invisible.
108
+ //
109
+ // Top level wins when present so an in-memory result keeps its exact behaviour;
110
+ // `signals.issues` is the stored fallback. Mirrors how the caller already reaches
111
+ // for `signals.graded_head_sha` three lines from where it passes this row in.
112
+ function priorFindings(storedGrade) {
113
+ if (Array.isArray(storedGrade.issues)) return storedGrade.issues;
114
+ const fromSignals = storedGrade.signals && storedGrade.signals.issues;
115
+ return Array.isArray(fromSignals) ? fromSignals : [];
116
+ }
117
+
97
118
  const PRIOR_ROUND_TEXT_CAP = 4000;
98
119
  function buildPriorRoundContext({ storedGrade, responseText = '', interimCommits = '', interimDiffStat = '' }) {
99
120
  if (!storedGrade || typeof storedGrade !== 'object') return null;
100
- const issues = Array.isArray(storedGrade.issues) ? storedGrade.issues : [];
121
+ const issues = priorFindings(storedGrade);
101
122
  const notes = typeof storedGrade.notes_md === 'string' ? storedGrade.notes_md.trim() : '';
102
123
  if (issues.length === 0 && !notes) return null;
103
124
  const verdictLine = `Prior verdict: ${storedGrade.passed ? 'PASS' : 'FAIL'} · score ${storedGrade.score ?? '?'}/10 · ${issues.length} finding(s)`;
@@ -436,6 +457,7 @@ function printCreditsAndAchievements({ postGradeStatus, credits, creditsRaw, cre
436
457
  module.exports = {
437
458
  buildGradePayload,
438
459
  buildPriorRoundContext,
460
+ priorFindings,
439
461
  computeGradeResult,
440
462
  formatGradeOutcome,
441
463
  gradeAndRecord,
@@ -145,6 +145,7 @@ const {
145
145
  buildGradePayload,
146
146
  buildPriorRoundContext,
147
147
  computeGradeResult,
148
+ priorFindings,
148
149
  formatGradeOutcome,
149
150
  gradeAndRecord,
150
151
  } = require('./ship-grade-steps.js');
@@ -195,6 +196,9 @@ module.exports = {
195
196
  // task 1002662: delta-aware re-grade — the prior-round payload builder,
196
197
  // exported pure so the panel/audit shapes are pinned without git/network.
197
198
  buildPriorRoundContext,
199
+ // task 1003488: the two-shape finding reader behind it (a stored grade keeps
200
+ // its findings in signals.issues; an in-memory one at the top level).
201
+ priorFindings,
198
202
  // #943: pure grade-outcome formatter — exported so the grader-unavailable vs
199
203
  // FAIL display branch is unit-tested without capturing stdout.
200
204
  formatGradeOutcome,
package/src/module-api.js CHANGED
@@ -45,7 +45,7 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
45
45
  // there. scripts/gds/bump-version.js still rewrites the literal below; it appends
46
46
  // the entry to that file. Look for a version's history there, not here.
47
47
  // ---------------------------------------------------------------------------
48
- const CORE_VERSION = '1.19.487'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
48
+ const CORE_VERSION = '1.19.488'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
49
49
 
50
50
  // A namespaced logger so a module's log lines are attributable + consistent.
51
51
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
@@ -184,5 +184,62 @@ test('buildPriorRoundContext caps oversized notes/response/interim text', () =>
184
184
  assert.ok(ctx.forPanel.interim_md.length < 5000, 'interim capped');
185
185
  });
186
186
 
187
+ // ---- task 1003488: the prior round is read from a STORED grade, not just an
188
+ // ---- in-memory one. A grade exists in two shapes and this function is handed
189
+ // ---- both; reading only the top-level key made every --regrade run blind.
190
+
191
+ test('prior round: a STORED grade carries its findings under signals.issues and they still reach the panel', () => {
192
+ // exactly what ship-flow.js passes on --regrade: tRes.data.grade, a DB row.
193
+ // task_grades has no issues column, so db-grade.js writes them into signals.
194
+ const dbRow = {
195
+ passed: false, score: 5, notes_md: '',
196
+ signals: {
197
+ issue_count: 2,
198
+ issues: [
199
+ { severity: 'major', worker: 'efficiency', file: 'a.js', summary: 'redundant sync reads' },
200
+ { severity: 'nit', worker: 'quality', file: 'b.js', summary: 'label drift' },
201
+ ],
202
+ },
203
+ };
204
+ const ctx = ship.buildPriorRoundContext({ storedGrade: dbRow });
205
+ assert.ok(ctx, 'a stored grade with findings is delta-aware context, not null');
206
+ assert.equal(ctx.audit.prior_findings_count, 2, 'the count is the stored findings, not 0');
207
+ assert.match(ctx.forPanel.findings_md, /2 finding\(s\)/);
208
+ assert.match(ctx.forPanel.findings_md, /redundant sync reads/, 'the finding text rides to the panel');
209
+ assert.match(ctx.forPanel.findings_md, /\[major\] efficiency · a\.js/, 'severity, worker and file survive');
210
+ assert.ok(!/no per-finding rows/.test(ctx.forPanel.findings_md),
211
+ 'it must not fall through to the "no rows" placeholder');
212
+ });
213
+
214
+ test('prior round: an IN-MEMORY grade keeps its exact top-level behaviour', () => {
215
+ const inMemory = {
216
+ passed: false, score: 5, notes_md: '',
217
+ issues: [{ severity: 'major', worker: 'quality', file: 'c.js', summary: 'top level wins' }],
218
+ signals: { issues: [{ severity: 'nit', worker: 'x', file: 'd.js', summary: 'must not be preferred' }] },
219
+ };
220
+ const ctx = ship.buildPriorRoundContext({ storedGrade: inMemory });
221
+ assert.equal(ctx.audit.prior_findings_count, 1);
222
+ assert.match(ctx.forPanel.findings_md, /top level wins/);
223
+ assert.ok(!/must not be preferred/.test(ctx.forPanel.findings_md),
224
+ 'top-level issues take precedence over signals.issues');
225
+ });
226
+
227
+ test('priorFindings: reads either shape, and never throws on a malformed one', () => {
228
+ const f = ship.priorFindings;
229
+ assert.deepEqual(f({ issues: [{ severity: 'nit' }] }).length, 1, 'top level');
230
+ assert.deepEqual(f({ signals: { issues: [{ severity: 'nit' }, { severity: 'major' }] } }).length, 2, 'stored');
231
+ for (const bad of [{}, { issues: null }, { signals: null }, { signals: {} },
232
+ { signals: { issues: 'nope' } }, { issues: 'nope', signals: { issues: [{ severity: 'nit' }] } }]) {
233
+ const out = f(bad);
234
+ assert.ok(Array.isArray(out), `array for ${JSON.stringify(bad)}`);
235
+ }
236
+ // a non-array top level must fall through to the stored shape, not swallow it
237
+ assert.equal(f({ issues: 'nope', signals: { issues: [{ severity: 'nit' }] } }).length, 1);
238
+ });
239
+
240
+ test('prior round: a stored grade with NO findings anywhere is still null (unchanged)', () => {
241
+ assert.equal(ship.buildPriorRoundContext({ storedGrade: { passed: true, notes_md: '', signals: {} } }), null);
242
+ });
243
+
187
244
  console.log(`\n${passed} passed, ${failed} failed`);
188
245
  process.exit(failed === 0 ? 0 : 1);