@bongos/core 1.19.52 → 1.19.54

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,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.52",
6
- "core_contract": "1.19.52",
7
- "source_commit": "ce8e0d15694aed30b1640891c4256c96322a2a6f",
5
+ "core_version": "1.19.54",
6
+ "core_contract": "1.19.54",
7
+ "source_commit": "e2b4fc873e01aa41550165626426c3ef649b58a0",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-08-11T19:06:37.776Z",
9
+ "built_at": "2026-08-11T20:06:11.854Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 291,
13
13
  "agent_docs_stubbed": 21,
14
- "functional_verbatim": 1218,
14
+ "functional_verbatim": 1219,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 1530,
20
- "tree_sha256": "72953759b072de03029b81979095699aeb2d45b2363ba7047d8503457761956f",
19
+ "file_count": 1531,
20
+ "tree_sha256": "38fdb87b02fa9c74f9de182cf78ba53c058873b93498cb731f5840c876d30fc2",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/blocker-review/SKILL.md",
@@ -367,7 +367,7 @@
367
367
  {
368
368
  "path": "clients/bongos-client/index.d.ts",
369
369
  "mode": "0000644",
370
- "sha256": "a5b9642b383c8c415158e091785ff40f28490d8341b4010f4c051b1a68942daf"
370
+ "sha256": "5c89524ec76f5f5df365a188541d50aa31167ae7920124baf562fb32ad30632c"
371
371
  },
372
372
  {
373
373
  "path": "clients/bongos-client/index.mjs",
@@ -1312,7 +1312,7 @@
1312
1312
  {
1313
1313
  "path": "docs/api/openapi.json",
1314
1314
  "mode": "0000644",
1315
- "sha256": "612c7a81756f61f2c18d4b35540379cf63f32a4caa9e8d84a64b247d497ad7a3"
1315
+ "sha256": "0076d08133c342fa905fa90682e606eefa02747d5b11aea9f0bdb3563b293f76"
1316
1316
  },
1317
1317
  {
1318
1318
  "path": "docs/architecture.md",
@@ -2229,6 +2229,11 @@
2229
2229
  "mode": "0000644",
2230
2230
  "sha256": "5d970696a87e6f663f9fda83a268dbe99ced00ac02fb805d91114f54d4a8fece"
2231
2231
  },
2232
+ {
2233
+ "path": "migrations/core_219_grade_attempts_attempted_at_idx.sql",
2234
+ "mode": "0000644",
2235
+ "sha256": "80ff025953285762f9fdfc8de813db3471ade4d02cb6db5a65f72fa13797a0fc"
2236
+ },
2232
2237
  {
2233
2238
  "path": "modules/autonomy/CLAUDE.md",
2234
2239
  "mode": "0000644",
@@ -3317,7 +3322,7 @@
3317
3322
  {
3318
3323
  "path": "modules/hall-ui/public/watch.js",
3319
3324
  "mode": "0000644",
3320
- "sha256": "31dc79e6aaf72260cae3b46eb0fbb356e4e2bb01e3a2cbce3c420dad5221f25e"
3325
+ "sha256": "e8821ce2c96b637e703879ea3e4840622f2f1846bf8d726d05c455f9bcfbf13e"
3321
3326
  },
3322
3327
  {
3323
3328
  "path": "modules/hall-ui/public/work.css",
@@ -3387,7 +3392,7 @@
3387
3392
  {
3388
3393
  "path": "modules/lifecycle/db.js",
3389
3394
  "mode": "0000644",
3390
- "sha256": "8e67cd1325da44cf95d5d8c34427222e555e0458bb17f23f33c1055b1ae7e211"
3395
+ "sha256": "89febe6a0002d2277a61b14cc08c4076d1ab6c738fdea8f8a4a1348e330c0b9e"
3391
3396
  },
3392
3397
  {
3393
3398
  "path": "modules/lifecycle/done-when.js",
@@ -3422,7 +3427,7 @@
3422
3427
  {
3423
3428
  "path": "modules/lifecycle/lifecycle.js",
3424
3429
  "mode": "0000644",
3425
- "sha256": "7d1be0defe08ca1905b04b646b988f9be3baae519ca2393166ce91f5a31e0c5d"
3430
+ "sha256": "5c0d0da5d6a8bcfb920bc432558066840e649cbc624e064dc4e55a926a3d8bb4"
3426
3431
  },
3427
3432
  {
3428
3433
  "path": "modules/lifecycle/merge-lock.js",
@@ -4037,12 +4042,12 @@
4037
4042
  {
4038
4043
  "path": "package-lock.json",
4039
4044
  "mode": "0000644",
4040
- "sha256": "37bb9183f916ffafd19c5c88408f5f776bdb091afc46b1bfbad64669d642102f"
4045
+ "sha256": "f8dd394f37580dfb0ced82e0d1d4a2b2bcc123af04ef1abb4f4d3de54e6a1d87"
4041
4046
  },
4042
4047
  {
4043
4048
  "path": "package.json",
4044
4049
  "mode": "0000644",
4045
- "sha256": "80465cb4506affe5fc47c835df7a0f4f1634a0743c2de6d14f843f342d487c45"
4050
+ "sha256": "af388d2580a869d00473d4905e52b17f35954f6f9142a0faca178cee0033f205"
4046
4051
  },
4047
4052
  {
4048
4053
  "path": "public-docs/index.html",
@@ -5612,7 +5617,7 @@
5612
5617
  {
5613
5618
  "path": "src/bongos/routes/public.js",
5614
5619
  "mode": "0000644",
5615
- "sha256": "fd3dd30a194cd4638e7ef6fe34d17c1e94183926543f3c662b8cb9ec2b4a6e97"
5620
+ "sha256": "fbfc3422f111e6d3f1429cefec78741d4a919c89985bc9d69f0a7a8e070c52b5"
5616
5621
  },
5617
5622
  {
5618
5623
  "path": "src/bongos/routes/security.js",
@@ -5662,7 +5667,7 @@
5662
5667
  {
5663
5668
  "path": "src/module-api.js",
5664
5669
  "mode": "0000644",
5665
- "sha256": "fc779475caf64dd09a7a3f952b4dadfcf2f123ca1a3d0ea9af20762b3de5e6c8"
5670
+ "sha256": "db31f3722a0328065e9c7ded61857e1501cb356e148c17ade17d9987edde4674"
5666
5671
  },
5667
5672
  {
5668
5673
  "path": "src/module-loader/loader.js",
@@ -6627,7 +6632,7 @@
6627
6632
  {
6628
6633
  "path": "tests/grade_attempts.mjs",
6629
6634
  "mode": "0000644",
6630
- "sha256": "2fe60fa9d070ba6946509413561ae5a273d902ed3b24ff113bb089fd9d9097f1"
6635
+ "sha256": "68792cfc57d211958190732e5c20051ff66c4b4d2ee3e39431db66e9dbfceae0"
6631
6636
  },
6632
6637
  {
6633
6638
  "path": "tests/grade_attribution.mjs",
@@ -77,7 +77,7 @@ export interface GetProvisioningInstancesResponse { instances: unknown }
77
77
  export interface GetProvisioningOnboardPlanResponse { mode: unknown; steps: unknown }
78
78
  export interface GetPublicBlockersIdResponse { blocker: unknown }
79
79
  export interface GetPublicBountyTableResponse { table: unknown; currency: unknown }
80
- export interface GetPublicGradesResponse { window_days: unknown; rolling: unknown; trend: unknown; threshold: unknown; grader_version: unknown }
80
+ export interface GetPublicGradesResponse { window_days: unknown; rolling: unknown; trend: unknown; attempts: unknown; threshold: unknown; grader_version: unknown }
81
81
  export interface GetPublicIdeasIdResponse { idea: unknown }
82
82
  export interface GetPublicLeaderboardResponse { leaderboard: unknown; page: PageInfo }
83
83
  export interface GetPublicProgressResponse { progress: unknown }
@@ -13070,6 +13070,7 @@
13070
13070
  "window_days": {},
13071
13071
  "rolling": {},
13072
13072
  "trend": {},
13073
+ "attempts": {},
13073
13074
  "threshold": {},
13074
13075
  "grader_version": {}
13075
13076
  },
@@ -13077,6 +13078,7 @@
13077
13078
  "window_days",
13078
13079
  "rolling",
13079
13080
  "trend",
13081
+ "attempts",
13080
13082
  "threshold",
13081
13083
  "grader_version"
13082
13084
  ]
@@ -0,0 +1,7 @@
1
+ -- lifecycle_003_grade_attempts_attempted_at_idx.sql — task 1002661 (panel finding).
2
+ --
3
+ -- gradeAttemptStats windows grade_attempts on attempted_at, but migration 064
4
+ -- only indexed task_id — every /public/grades call would seq-scan an
5
+ -- append-only table that grows on every grader run. Index the window column.
6
+ CREATE INDEX IF NOT EXISTS idx_grade_attempts_attempted_at
7
+ ON grade_attempts (attempted_at DESC);
@@ -644,11 +644,21 @@
644
644
  const rolling = data.rolling || {};
645
645
  const n = num(rolling.n);
646
646
  const passRate = n > 0 ? num(rolling.n_passed) / n : null;
647
+ // task 1002661: grade_attempts remediation reader — attempts per graded
648
+ // task and, of tasks that failed a round, the share that ended passed.
649
+ const at = data.attempts || null;
650
+ const meanAttempts = at && at.graded_tasks > 0
651
+ ? Math.round((at.total_attempts / at.graded_tasks) * 10) / 10
652
+ : null;
647
653
  stats.innerHTML = statCards([
648
654
  { num: (rolling.avg_score ?? '—') === '—' ? '—' : escapeHtml(String(rolling.avg_score)), label: 'Avg score' },
649
655
  { num: fmtPct(passRate), label: 'Pass rate' },
650
656
  { num: fmtInt(n), label: 'Sample size' },
651
657
  { num: data.threshold !== undefined && data.threshold !== null ? escapeHtml(String(data.threshold)) : '—', label: 'Threshold' },
658
+ ...(at ? [
659
+ { num: meanAttempts === null ? '—' : escapeHtml(String(meanAttempts)), label: 'Attempts/task' },
660
+ { num: at.remediation_rate === null ? '—' : fmtPct(at.remediation_rate), label: 'Remediation' },
661
+ ] : []),
652
662
  ]);
653
663
 
654
664
  const trend = Array.isArray(data.trend) ? data.trend.slice(-14) : [];
@@ -5309,6 +5309,56 @@ async function countGradeAttempts(taskId, queryFn) {
5309
5309
  return rows[0] ? rows[0].n : 0;
5310
5310
  }
5311
5311
 
5312
+ // gradeAttemptStats — the grade_attempts READER (task 1002661). The table has
5313
+ // been written on every grade since 6D.6 and read by nothing, leaving
5314
+ // remediation rate structurally unmeasurable. Windowed per-task rollup:
5315
+ // remediation_rate = of the tasks that FAILED at least one attempt, the share
5316
+ // whose latest attempt passed (null when no task failed — no denominator).
5317
+ // waived_zero_issue_passes counts the ADR 0164 implausible_pass_waived audit
5318
+ // markers in the same window, so the views can tell "waived on panel
5319
+ // evidence" from "never evaluated" at a glance.
5320
+ async function gradeAttemptStats(days = 7, queryFn) {
5321
+ const q = queryFn || ((text, params) => pool.query(text, params));
5322
+ const d = Number.isFinite(Number(days)) && Number(days) > 0 ? Math.min(Number(days), 90) : 7;
5323
+ const [{ rows }, waived] = await Promise.all([q(
5324
+ `SELECT
5325
+ COALESCE(SUM(n), 0)::int AS total_attempts,
5326
+ COUNT(*)::int AS graded_tasks,
5327
+ COUNT(*) FILTER (WHERE n > 1)::int AS retried_tasks,
5328
+ COUNT(*) FILTER (WHERE had_fail)::int AS tasks_with_fail,
5329
+ COUNT(*) FILTER (WHERE had_fail AND last_passed)::int AS remediated_tasks
5330
+ FROM (
5331
+ SELECT task_id,
5332
+ COUNT(*) AS n,
5333
+ BOOL_OR(NOT passed) AS had_fail,
5334
+ (ARRAY_AGG(passed ORDER BY attempted_at DESC))[1] AS last_passed
5335
+ FROM grade_attempts
5336
+ WHERE attempted_at > now() - ($1 * interval '1 day')
5337
+ GROUP BY task_id
5338
+ ) t`,
5339
+ [d]
5340
+ ), q(
5341
+ `SELECT COUNT(*)::int AS n
5342
+ FROM task_grades
5343
+ WHERE graded_at > now() - ($1 * interval '1 day')
5344
+ AND signals->'server_guard'->>'implausible_pass_waived' IS NOT NULL`,
5345
+ [d]
5346
+ )]);
5347
+ const s = rows[0] || {};
5348
+ const tasksWithFail = Number(s.tasks_with_fail) || 0;
5349
+ const remediated = Number(s.remediated_tasks) || 0;
5350
+ return {
5351
+ window_days: d,
5352
+ total_attempts: Number(s.total_attempts) || 0,
5353
+ graded_tasks: Number(s.graded_tasks) || 0,
5354
+ retried_tasks: Number(s.retried_tasks) || 0,
5355
+ tasks_with_fail: tasksWithFail,
5356
+ remediated_tasks: remediated,
5357
+ remediation_rate: tasksWithFail > 0 ? Math.round((remediated / tasksWithFail) * 100) / 100 : null,
5358
+ waived_zero_issue_passes: (waived.rows[0] && Number(waived.rows[0].n)) || 0,
5359
+ };
5360
+ }
5361
+
5312
5362
  // ---- security_docs: Archon-only sensitive security documents (task 991) -----
5313
5363
  //
5314
5364
  // The DATED security engagement reports (exploit detail) live in the DB, NOT in
@@ -5561,6 +5611,7 @@ module.exports = {
5561
5611
  decideOverrideRequest,
5562
5612
  logGradeAttempt,
5563
5613
  countGradeAttempts,
5614
+ gradeAttemptStats, // task 1002661: the grade_attempts remediation-rate reader
5564
5615
  acquireMergeLease,
5565
5616
  releaseMergeLease,
5566
5617
  mergeLeaseStatus,
@@ -41,6 +41,7 @@ module.exports = {
41
41
  recentShippedTasks: (...a) => db.recentShippedTasks(...a),
42
42
  estimationDriftSummary: (...a) => db.estimationDriftSummary(...a),
43
43
  rollingAvgGrade: (...a) => db.rollingAvgGrade(...a),
44
+ gradeAttemptStats: (...a) => db.gradeAttemptStats(...a), // task 1002661: remediation-rate reader
44
45
  gradeTrendByDay: (...a) => db.gradeTrendByDay(...a),
45
46
  // me.js (the profile reader): per-builder claims + estimation + grade reads.
46
47
  getActiveClaimsForBuilder: (...a) => db.getActiveClaimsForBuilder(...a),
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.52",
3
+ "version": "1.19.54",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.52",
9
+ "version": "1.19.54",
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.52",
3
+ "version": "1.19.54",
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",
@@ -293,9 +293,12 @@ module.exports = function buildPublicRouter() {
293
293
  const rawDays = parseInt(req.query.days, 10);
294
294
  const days = Number.isFinite(rawDays) && rawDays > 0 && rawDays <= 90 ? rawDays : 7;
295
295
  const trendDays = days >= 30 ? days : 30;
296
- const [rolling, trend] = await Promise.all([
296
+ const [rolling, trend, attempts] = await Promise.all([
297
297
  lc().rollingAvgGrade(days),
298
298
  lc().gradeTrendByDay(trendDays),
299
+ // task 1002661: grade_attempts remediation reader. Best-effort — a
300
+ // mid-migration instance without the table must not break the tile.
301
+ lc().gradeAttemptStats(days).catch(() => null),
299
302
  ]);
300
303
  // Rubric meta via the `grade` kernel PORT (BV1.R73). resolveOptional → null
301
304
  // when grading is disabled, so the tile still renders (threshold/version null)
@@ -307,6 +310,7 @@ module.exports = function buildPublicRouter() {
307
310
  window_days: days,
308
311
  rolling,
309
312
  trend,
313
+ attempts, // task 1002661: attempt counts + remediation rate (null pre-migration)
310
314
  threshold: rubric ? rubric.threshold : null, // single source of truth — grader-rubric.json
311
315
  grader_version: rubric ? rubric.version : null,
312
316
  });
package/src/module-api.js CHANGED
@@ -531,8 +531,12 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
531
531
  // landed since 1.19.50 with no explicit bump. run 31525032273. (task 1002620)
532
532
  // 1.19.52 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
533
533
  // landed since 1.19.51 with no explicit bump. run 31526131183. (task 1002620)
534
+ // 1.19.53 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
535
+ // landed since 1.19.52 with no explicit bump. run 31527057565. (task 1002620)
536
+ // 1.19.54 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
537
+ // landed since 1.19.53 with no explicit bump. run 31531157867. (task 1002620)
534
538
  // ---------------------------------------------------------------------------
535
- const CORE_VERSION = '1.19.52'; // CI auto-patch carrier (ADR 0161); see changelog above
539
+ const CORE_VERSION = '1.19.54'; // CI auto-patch carrier (ADR 0161); see changelog above
536
540
 
537
541
  // A namespaced logger so a module's log lines are attributable + consistent.
538
542
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
@@ -68,6 +68,51 @@ await test('countGradeAttempts returns the count (0 when none)', async () => {
68
68
  assert.equal(await db.countGradeAttempts(999, () => Promise.resolve({ rows: [] })), 0);
69
69
  });
70
70
 
71
+ // ---- task 1002661: gradeAttemptStats — the remediation-rate reader ----
72
+
73
+ function statsQueryFn({ agg, waived }) {
74
+ return (text, params) => {
75
+ if (/FROM grade_attempts/.test(text)) return Promise.resolve({ rows: [{ ...agg, _params: params }] });
76
+ if (/implausible_pass_waived/.test(text)) return Promise.resolve({ rows: [{ n: waived }] });
77
+ return Promise.reject(new Error(`unexpected SQL: ${text.slice(0, 60)}`));
78
+ };
79
+ }
80
+
81
+ await test('gradeAttemptStats maps the rollup + computes remediation rate', async () => {
82
+ const s = await db.gradeAttemptStats(7, statsQueryFn({
83
+ agg: { total_attempts: 14, graded_tasks: 10, retried_tasks: 3, tasks_with_fail: 4, remediated_tasks: 3 },
84
+ waived: 2,
85
+ }));
86
+ assert.equal(s.window_days, 7);
87
+ assert.equal(s.total_attempts, 14);
88
+ assert.equal(s.graded_tasks, 10);
89
+ assert.equal(s.retried_tasks, 3);
90
+ assert.equal(s.tasks_with_fail, 4);
91
+ assert.equal(s.remediated_tasks, 3);
92
+ assert.equal(s.remediation_rate, 0.75, '3 of 4 failed-at-least-once tasks ended passed');
93
+ assert.equal(s.waived_zero_issue_passes, 2, 'ADR 0164 waived markers counted in-window');
94
+ });
95
+
96
+ await test('gradeAttemptStats: no failed tasks → remediation_rate null (no denominator)', async () => {
97
+ const s = await db.gradeAttemptStats(7, statsQueryFn({
98
+ agg: { total_attempts: 5, graded_tasks: 5, retried_tasks: 0, tasks_with_fail: 0, remediated_tasks: 0 },
99
+ waived: 0,
100
+ }));
101
+ assert.equal(s.remediation_rate, null);
102
+ });
103
+
104
+ await test('gradeAttemptStats clamps the window (bad input → 7, oversize → 90)', async () => {
105
+ const seen = [];
106
+ const qf = (text, params) => {
107
+ seen.push(params[0]);
108
+ if (/FROM grade_attempts/.test(text)) return Promise.resolve({ rows: [{}] });
109
+ return Promise.resolve({ rows: [{ n: 0 }] });
110
+ };
111
+ await db.gradeAttemptStats('junk', qf);
112
+ await db.gradeAttemptStats(500, qf);
113
+ assert.deepEqual(seen, [7, 7, 90, 90], 'both queries share the clamped window');
114
+ });
115
+
71
116
  console.log('');
72
117
  console.log(`Grade-attempts (6D.6) tests: ${passed} passed, ${failed} failed`);
73
118
  if (failed > 0) process.exit(1);