@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 +22 -17
- package/clients/bongos-client/index.d.ts +1 -1
- package/docs/api/openapi.json +2 -0
- package/migrations/core_219_grade_attempts_attempted_at_idx.sql +7 -0
- package/modules/hall-ui/public/watch.js +10 -0
- package/modules/lifecycle/db.js +51 -0
- package/modules/lifecycle/lifecycle.js +1 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/src/bongos/routes/public.js +5 -1
- package/src/module-api.js +5 -1
- package/tests/grade_attempts.mjs +45 -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.54",
|
|
6
|
+
"core_contract": "1.19.54",
|
|
7
|
+
"source_commit": "e2b4fc873e01aa41550165626426c3ef649b58a0",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-08-
|
|
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":
|
|
14
|
+
"functional_verbatim": 1219,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
4045
|
+
"sha256": "f8dd394f37580dfb0ced82e0d1d4a2b2bcc123af04ef1abb4f4d3de54e6a1d87"
|
|
4041
4046
|
},
|
|
4042
4047
|
{
|
|
4043
4048
|
"path": "package.json",
|
|
4044
4049
|
"mode": "0000644",
|
|
4045
|
-
"sha256": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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 }
|
package/docs/api/openapi.json
CHANGED
|
@@ -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) : [];
|
package/modules/lifecycle/db.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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');
|
package/tests/grade_attempts.mjs
CHANGED
|
@@ -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);
|