@bongos/core 1.19.28 → 1.19.29

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.28",
6
- "core_contract": "1.19.28",
7
- "source_commit": "10e31a3212982a5385e83bec33a5ba12b733929f",
5
+ "core_version": "1.19.29",
6
+ "core_contract": "1.19.29",
7
+ "source_commit": "653afa8efb0b5167d51ed1510c45977a56a575dd",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-08-11T14:01:24.503Z",
9
+ "built_at": "2026-08-11T14:19:03.503Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 285,
@@ -17,7 +17,7 @@
17
17
  "gate": "passed"
18
18
  },
19
19
  "file_count": 1511,
20
- "tree_sha256": "e1dee62e1e6e30b75671474f69e5e6b97f0ea598c9a66b5ad49867906f85e51d",
20
+ "tree_sha256": "0076ae91d3bb7f715ca250d05367d1eae9a660b5c015b88c0a6687d7a01a9794",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/blocker-review/SKILL.md",
@@ -2882,7 +2882,7 @@
2882
2882
  {
2883
2883
  "path": "modules/grading/grader.js",
2884
2884
  "mode": "0000644",
2885
- "sha256": "bc2b580d2be98f5b2acb9a0dd9416dd3c2cad387bbe61a6823e37102d3468675"
2885
+ "sha256": "3461862dc1af26875958697291128fbf4d914fff9b00b7920e5f25dae73f6ba4"
2886
2886
  },
2887
2887
  {
2888
2888
  "path": "modules/grading/module.json",
@@ -4002,12 +4002,12 @@
4002
4002
  {
4003
4003
  "path": "package-lock.json",
4004
4004
  "mode": "0000644",
4005
- "sha256": "8ecf54691f56a0579f3d732323aebbe9620b3b46fa6f8711a959f20dac1cffaa"
4005
+ "sha256": "7a14171b353dbfb6da83df462c031ecce9d4c97b085504845035072e5e97bbfa"
4006
4006
  },
4007
4007
  {
4008
4008
  "path": "package.json",
4009
4009
  "mode": "0000644",
4010
- "sha256": "d7dcf7da61008463a0e5ddb5da440f85bd845a2a0ea8d8125c2684dad24cdac6"
4010
+ "sha256": "3ff6057a4a6120421ce79d9593bdf54e424fdd143bd0a20fc616ba92582ee910"
4011
4011
  },
4012
4012
  {
4013
4013
  "path": "public-docs/index.html",
@@ -5627,7 +5627,7 @@
5627
5627
  {
5628
5628
  "path": "src/module-api.js",
5629
5629
  "mode": "0000644",
5630
- "sha256": "b97afd8c79d048c057170fb03010be8d89f051cc8d4c43a2ecb85343785fe194"
5630
+ "sha256": "576d6afdcdf097366f36831d4b56ba300f71beefdc29c853605f9c0788cd260c"
5631
5631
  },
5632
5632
  {
5633
5633
  "path": "src/module-loader/loader.js",
@@ -6597,7 +6597,7 @@
6597
6597
  {
6598
6598
  "path": "tests/grader.mjs",
6599
6599
  "mode": "0000644",
6600
- "sha256": "380ef7c395dea5ba988a1b0f9492451c621ebc01ba5e04c2674ebb54fe32cddd"
6600
+ "sha256": "4832bcffc7385cdd8deb0c1ff9bb44542190a3da106d2f00ccacd823c90103ec"
6601
6601
  },
6602
6602
  {
6603
6603
  "path": "tests/grader_bypass.mjs",
@@ -365,6 +365,19 @@ function isNum(n) {
365
365
  return Number.isFinite(n);
366
366
  }
367
367
 
368
+ // #1002653 corroboration helpers for validateAndScore's null-ff lens branch.
369
+ // A worker entry is untrusted input — w.issues may be any shape. Absent issues
370
+ // on an errored worker is the normal outage shape (treated as none); a truthy
371
+ // NON-ARRAY issues is malformed and must DISQUALIFY corroboration (fail-closed
372
+ // schema_error downstream), never TypeError into a 500 on grade submission.
373
+ const isNarcErroredClean = (w) => {
374
+ if (!w || w.kind !== 'narc' || !w.error) return false;
375
+ const issues = w.issues == null ? [] : w.issues;
376
+ if (!Array.isArray(issues)) return false;
377
+ return !issues.some((i) => i && (i.severity === 'blocker' || i.severity === 'major'));
378
+ };
379
+ const isNarcHealthy = (w) => !!w && w.kind === 'narc' && !w.error;
380
+
368
381
  // Validates ONE shot's subagent JSON and produces a per-shot grade-result.
369
382
  // Never throws — malformed inputs produce a passed=false result with notes_md.
370
383
  // The downstream aggregateShots collapses N of these into a final verdict.
@@ -394,7 +407,25 @@ function validateAndScore(rawSubagent, meta = {}) {
394
407
  };
395
408
  }
396
409
 
397
- const ff = Number(parsed.functional_fidelity);
410
+ // #1002653: Number(null) === 0, so an errored ADVISORY Narc forwarding an
411
+ // EXPLICIT ff:null fabricated "FAIL — functional_fidelity below 6" at
412
+ // (0+cq)/2. Explicit null = the lens did not run (the #395 confidence
413
+ // precedent): gate/score on the axes that ran. A bare null is NOT enough —
414
+ // a prompt-injected Narc emitting null must stay fail-closed — so the branch
415
+ // needs POSITIVE corroboration: workers[] carries an errored
416
+ // no-substantive-finding Narc (#473 missing-signal semantics) and no healthy
417
+ // Narc. Empty/narc-less/absent workers, ABSENT ff, and every other shape
418
+ // fall through to the fail-closed schema_error below. Quality has no such
419
+ // branch — gating lens, absence stays fail-closed.
420
+ // Trust note: workers[] is client-supplied like every SR-9 grade input — a
421
+ // forger gains nothing here they cannot get by sending ff:9 directly; the
422
+ // server-derivable-signals redesign is tracked separately (SR-9 residual).
423
+ const metaWorkers = Array.isArray(meta.workers) ? meta.workers : null;
424
+ const narcMissingSignal = !!metaWorkers &&
425
+ metaWorkers.some(isNarcErroredClean) && !metaWorkers.some(isNarcHealthy);
426
+ const ffExplicitNull = parsed.functional_fidelity === null;
427
+ const ffLensUnavailable = ffExplicitNull && narcMissingSignal;
428
+ const ff = ffExplicitNull ? null : Number(parsed.functional_fidelity);
398
429
  const cq = Number(parsed.code_quality);
399
430
  const economyRaw = Number(parsed.economy);
400
431
  const economy =
@@ -427,8 +458,9 @@ function validateAndScore(rawSubagent, meta = {}) {
427
458
 
428
459
  signals.confidence = confidence;
429
460
  signals.issue_count = issues.length;
461
+ if (ffLensUnavailable) signals.ff_lens_unavailable = true;
430
462
 
431
- if (!isNum(ff) || !isNum(cq) || ff < 0 || ff > 10 || cq < 0 || cq > 10) {
463
+ if ((!ffLensUnavailable && (!isNum(ff) || ff < 0 || ff > 10)) || !isNum(cq) || cq < 0 || cq > 10) {
432
464
  return {
433
465
  score: 0,
434
466
  passed: false,
@@ -437,12 +469,15 @@ function validateAndScore(rawSubagent, meta = {}) {
437
469
  grader_version: VERSION,
438
470
  rubric_scores: {},
439
471
  signals: { ...signals, schema_error: true },
440
- notes_md: `Subagent grader ${VERSION}: subagent returned scores outside [0,10] or non-numeric. Got functional_fidelity=${parsed.functional_fidelity}, code_quality=${parsed.code_quality}.`,
472
+ notes_md: `Subagent grader ${VERSION}: subagent returned scores outside [0,10] or non-numeric. Got functional_fidelity=${parsed.functional_fidelity}, code_quality=${parsed.code_quality}.`
473
+ + (ffExplicitNull && !narcMissingSignal
474
+ ? ' Explicit null functional_fidelity without a corroborated Narc outage in signals.workers is fail-closed.'
475
+ : ''),
441
476
  issues,
442
477
  };
443
478
  }
444
479
 
445
- const ffR = r1(clamp(ff, 0, 10));
480
+ const ffR = ffLensUnavailable ? null : r1(clamp(ff, 0, 10));
446
481
  const cqR = r1(clamp(cq, 0, 10));
447
482
  const economyR = economy != null ? r1(clamp(economy, 0, 10)) : null;
448
483
  const securityR = securityScore != null ? r1(clamp(securityScore, 0, 10)) : null;
@@ -450,7 +485,9 @@ function validateAndScore(rawSubagent, meta = {}) {
450
485
  const economyPenalty =
451
486
  economyR != null ? Math.max(0, (THRESHOLD - economyR) / 2) : 0;
452
487
  const cqEffective = economyR != null ? r1(clamp(cqR - economyPenalty, 0, 10)) : cqR;
453
- const blended = r1((ffR + cqR) / 2);
488
+ // With the ff lens out, the score reflects only the axis that ran — never a
489
+ // blend with a synthetic 0 (the fabricated 4.5/10) or a synthetic threshold.
490
+ const blended = ffLensUnavailable ? cqR : r1((ffR + cqR) / 2);
454
491
 
455
492
  // confidence must be present (not null) for a PASS. A subagent that omits
456
493
  // confidence entirely silently bypassed the low-confidence gate under the
@@ -461,7 +498,11 @@ function validateAndScore(rawSubagent, meta = {}) {
461
498
  // so aggregateShots can never reach unanimousPASS with a null confidenceMedian.
462
499
  const confidenceAbsent = confidence === null;
463
500
  const lowConfidence = !confidenceAbsent && confidence < LOW_CONFIDENCE;
464
- const bothPass = ffR >= THRESHOLD && cqR >= THRESHOLD;
501
+ // ffR is null when the lens is unavailable — it must not fail the gate
502
+ // (null < THRESHOLD is true via JS coercion), and must not appear in the
503
+ // verdict's below-threshold branches.
504
+ const ffBelow = !ffLensUnavailable && ffR < THRESHOLD;
505
+ const bothPass = !ffBelow && cqR >= THRESHOLD;
465
506
  const passed = bothPass && !lowConfidence && !confidenceAbsent;
466
507
 
467
508
  const verdict = passed
@@ -472,15 +513,15 @@ function validateAndScore(rawSubagent, meta = {}) {
472
513
  ? 'FAIL — low confidence'
473
514
  : bothPass
474
515
  ? 'FAIL'
475
- : ffR < THRESHOLD && cqR < THRESHOLD
516
+ : ffBelow && cqR < THRESHOLD
476
517
  ? `FAIL — both axes below ${THRESHOLD}`
477
- : ffR < THRESHOLD
518
+ : ffBelow
478
519
  ? `FAIL — functional_fidelity below ${THRESHOLD}`
479
520
  : `FAIL — code_quality below ${THRESHOLD}`;
480
521
 
481
522
  const notesLines = [];
482
523
  notesLines.push(`Shot ${signals.shot_index != null ? signals.shot_index : '?'} (${signals.model}) — ${verdict}`);
483
- notesLines.push(` ff=${ffR}/10 cq=${cqR}/10${economyR != null ? ` econ=${economyR}/10 (advisory)` : ''} conf=${confidence ?? '?'}`);
524
+ notesLines.push(` ff=${ffLensUnavailable ? 'n/a (lens unavailable)' : `${ffR}/10`} cq=${cqR}/10${economyR != null ? ` econ=${economyR}/10 (advisory)` : ''} conf=${confidence ?? '?'}`);
484
525
  if (notesFromSubagent) {
485
526
  notesLines.push('');
486
527
  notesLines.push(notesFromSubagent.trim());
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.28",
3
+ "version": "1.19.29",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.28",
9
+ "version": "1.19.29",
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.28",
3
+ "version": "1.19.29",
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",
package/src/module-api.js CHANGED
@@ -483,8 +483,10 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
483
483
  // landed since 1.19.26 with no explicit bump. run 31497212656. (task 1002620)
484
484
  // 1.19.28 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
485
485
  // landed since 1.19.27 with no explicit bump. run 31499228797. (task 1002620)
486
+ // 1.19.29 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
487
+ // landed since 1.19.28 with no explicit bump. run 31500810303. (task 1002620)
486
488
  // ---------------------------------------------------------------------------
487
- const CORE_VERSION = '1.19.28'; // CI auto-patch carrier (ADR 0161); see changelog above
489
+ const CORE_VERSION = '1.19.29'; // CI auto-patch carrier (ADR 0161); see changelog above
488
490
 
489
491
  // A namespaced logger so a module's log lines are attributable + consistent.
490
492
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
package/tests/grader.mjs CHANGED
@@ -415,6 +415,165 @@ test('#489 validateAndScore: explicit null confidence → FAIL (same as absent)'
415
415
  assert.match(r.notes_md, /confidence absent/i);
416
416
  });
417
417
 
418
+ // Task 1002653: an errored ADVISORY Narc forwards an EXPLICIT
419
+ // functional_fidelity: null on an otherwise-passing panel (advisory since
420
+ // #481). Number(null) === 0 used to fabricate "FAIL — functional_fidelity
421
+ // below 6" at (0+cq)/2. Explicit null = the ff lens did not run: gate + score
422
+ // on the axes that DID run — but ONLY when signals.workers corroborates the
423
+ // Narc as missing-signal (errored, no substantive finding). A bare null with a
424
+ // healthy Narc (the prompt-injection shape this ship's own Hacker flagged), an
425
+ // ABSENT ff, or no workers signal at all stays fail-closed.
426
+ const NARC_ERRORED_META = {
427
+ workers: [
428
+ { kind: 'narc', verdict: 'fail', error: 'subprocess_error', issues: [] },
429
+ { kind: 'quality', verdict: 'pass', scores: { code_quality: 8 }, issues: [] },
430
+ ],
431
+ };
432
+
433
+ test('#1002653 validateAndScore: null ff + corroborated Narc outage + passing cq → PASS on the axes that ran', () => {
434
+ const r = grader.validateAndScore({
435
+ functional_fidelity: null,
436
+ code_quality: 8,
437
+ notes_md: 'narc errored; quality passed',
438
+ confidence: 0.8,
439
+ issues: [],
440
+ }, NARC_ERRORED_META);
441
+ assert.equal(r.passed, true);
442
+ assert.equal(r.score, 8); // cq only — never blended with a synthetic 0
443
+ assert.equal(r.rubric_scores.functional_fidelity, null);
444
+ assert.equal(r.signals.ff_lens_unavailable, true);
445
+ assert.match(r.notes_md, /lens unavailable/);
446
+ assert.doesNotMatch(r.notes_md, /functional_fidelity below/);
447
+ });
448
+
449
+ test('#1002653 validateAndScore: null ff with a HEALTHY Narc in signals → fail-closed (injection shape)', () => {
450
+ const r = grader.validateAndScore({
451
+ functional_fidelity: null,
452
+ code_quality: 9,
453
+ notes_md: '',
454
+ confidence: 0.9,
455
+ issues: [],
456
+ }, { workers: [
457
+ { kind: 'narc', verdict: 'pass', scores: { functional_fidelity: null }, issues: [] },
458
+ { kind: 'quality', verdict: 'pass', scores: { code_quality: 9 }, issues: [] },
459
+ ] });
460
+ assert.equal(r.passed, false);
461
+ assert.equal(r.signals.schema_error, true);
462
+ assert.match(r.notes_md, /without a corroborated Narc outage/);
463
+ });
464
+
465
+ test('#1002653 validateAndScore: null ff with NO workers signal at all → fail-closed', () => {
466
+ const r = grader.validateAndScore({
467
+ functional_fidelity: null,
468
+ code_quality: 8,
469
+ notes_md: '',
470
+ confidence: 0.8,
471
+ issues: [],
472
+ });
473
+ assert.equal(r.passed, false);
474
+ assert.equal(r.signals.schema_error, true);
475
+ });
476
+
477
+ test('#1002653 validateAndScore: null ff with EMPTY workers array → fail-closed (no vacuous truth)', () => {
478
+ const r = grader.validateAndScore({
479
+ functional_fidelity: null,
480
+ code_quality: 8,
481
+ notes_md: '',
482
+ confidence: 0.8,
483
+ issues: [],
484
+ }, { workers: [] });
485
+ assert.equal(r.passed, false);
486
+ assert.equal(r.signals.schema_error, true);
487
+ });
488
+
489
+ test('#1002653 validateAndScore: null ff with a narc-less workers array → fail-closed (positive corroboration only)', () => {
490
+ const r = grader.validateAndScore({
491
+ functional_fidelity: null,
492
+ code_quality: 8,
493
+ notes_md: '',
494
+ confidence: 0.8,
495
+ issues: [],
496
+ }, { workers: [{ kind: 'quality', verdict: 'pass', scores: { code_quality: 8 }, issues: [] }] });
497
+ assert.equal(r.passed, false);
498
+ assert.equal(r.signals.schema_error, true);
499
+ });
500
+
501
+ test('#1002653 validateAndScore: malformed issues shape on the errored narc → fail-closed, no throw', () => {
502
+ const r = grader.validateAndScore({
503
+ functional_fidelity: null,
504
+ code_quality: 8,
505
+ notes_md: '',
506
+ confidence: 0.8,
507
+ issues: [],
508
+ }, { workers: [{ kind: 'narc', verdict: 'fail', error: 'subprocess_error', issues: 'none' }] });
509
+ assert.equal(r.passed, false);
510
+ assert.equal(r.signals.schema_error, true);
511
+ });
512
+
513
+ test('#1002653 validateAndScore: an errored narc alongside a HEALTHY narc does not excuse null ff', () => {
514
+ const r = grader.validateAndScore({
515
+ functional_fidelity: null,
516
+ code_quality: 8,
517
+ notes_md: '',
518
+ confidence: 0.8,
519
+ issues: [],
520
+ }, { workers: [
521
+ { kind: 'narc', verdict: 'fail', error: 'subprocess_error', issues: [] },
522
+ { kind: 'narc', verdict: 'pass', scores: { functional_fidelity: 8 }, issues: [] },
523
+ ] });
524
+ assert.equal(r.passed, false);
525
+ assert.equal(r.signals.schema_error, true);
526
+ });
527
+
528
+ test('#1002653 validateAndScore: null ff does NOT rescue a failing cq (verdict names cq alone)', () => {
529
+ const r = grader.validateAndScore({
530
+ functional_fidelity: null,
531
+ code_quality: 4,
532
+ notes_md: '',
533
+ confidence: 0.9,
534
+ issues: [{ severity: 'major', file: 'x.js', summary: 'real problem' }],
535
+ }, NARC_ERRORED_META);
536
+ assert.equal(r.passed, false);
537
+ assert.match(r.notes_md, /code_quality below/);
538
+ assert.doesNotMatch(r.notes_md, /both axes below/);
539
+ });
540
+
541
+ test('#1002653 validateAndScore: ABSENT ff stays a schema_error FAIL (omission cannot dodge the gate)', () => {
542
+ const r = grader.validateAndScore({
543
+ code_quality: 9,
544
+ notes_md: '',
545
+ confidence: 0.9,
546
+ issues: [],
547
+ }, NARC_ERRORED_META);
548
+ assert.equal(r.passed, false);
549
+ assert.equal(r.signals.schema_error, true);
550
+ });
551
+
552
+ test('#1002653 validateAndScore: a judged ff of 0 is still a real FAIL (only corroborated null is a lens outage)', () => {
553
+ const r = grader.validateAndScore({
554
+ functional_fidelity: 0,
555
+ code_quality: 9,
556
+ notes_md: '',
557
+ confidence: 0.9,
558
+ issues: [],
559
+ }, NARC_ERRORED_META);
560
+ assert.equal(r.passed, false);
561
+ assert.match(r.notes_md, /functional_fidelity below/);
562
+ assert.notEqual(r.signals.ff_lens_unavailable, true);
563
+ });
564
+
565
+ test('#1002653 validateAndScore: null ff + null confidence still FAILs on confidence', () => {
566
+ const r = grader.validateAndScore({
567
+ functional_fidelity: null,
568
+ code_quality: 9,
569
+ notes_md: '',
570
+ confidence: null,
571
+ issues: [],
572
+ }, NARC_ERRORED_META);
573
+ assert.equal(r.passed, false);
574
+ assert.match(r.notes_md, /confidence absent/i);
575
+ });
576
+
418
577
  // #489 aggregateShots safety: a unanimousPASS result can never produce a null
419
578
  // confidenceMedian, because any shot that passes must now have non-null confidence.
420
579
  test('#489 aggregateShots unanimousPASS always has non-null median confidence', () => {