@bongos/core 1.19.38 → 1.19.40

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.38",
6
- "core_contract": "1.19.38",
7
- "source_commit": "ba6a8f6285476ab3cd4e33f9278b333ac0fc3a29",
5
+ "core_version": "1.19.40",
6
+ "core_contract": "1.19.40",
7
+ "source_commit": "512e419a012e048b5e7d5904053cbc45df054a09",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-08-11T16:17:20.571Z",
9
+ "built_at": "2026-08-11T16:35:25.688Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
- "docs_redacted": 288,
12
+ "docs_redacted": 289,
13
13
  "agent_docs_stubbed": 21,
14
- "functional_verbatim": 1212,
14
+ "functional_verbatim": 1213,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 1521,
20
- "tree_sha256": "9d55773209aeb85fef771e85aa30e901c52e54e155601030e44cd16fd5c46bda",
19
+ "file_count": 1523,
20
+ "tree_sha256": "39b4dbb47e0100a520bddafb358c1c89d7a74bc6975261774d9dd12059d544dc",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/blocker-review/SKILL.md",
@@ -154,6 +154,11 @@
154
154
  "mode": "0000644",
155
155
  "sha256": "e789b9157c9817bac354eaeed8152b53f3013f677934b062bca1c8e07f1f2dc8"
156
156
  },
157
+ {
158
+ "path": ".claude/skills/grade-audit/SKILL.md",
159
+ "mode": "0000644",
160
+ "sha256": "2b4d33396291f58f1c0d7c01d173b64d587b2a0a483b17328e57eb602a8df9fe"
161
+ },
157
162
  {
158
163
  "path": ".claude/skills/grade-recover/SKILL.md",
159
164
  "mode": "0000644",
@@ -1362,7 +1367,7 @@
1362
1367
  {
1363
1368
  "path": "docs/file-map.md",
1364
1369
  "mode": "0000644",
1365
- "sha256": "f51d3978f50ec9b2a34dc12b8fa3b9e8874543e5ab3503f72a09d7d2b06c565b"
1370
+ "sha256": "15801d1c51e7e46ccf4e52dad5d3d34e4adc96b72eed60e3ef2c9ccccf94d1da"
1366
1371
  },
1367
1372
  {
1368
1373
  "path": "docs/handoff-template.md",
@@ -3382,7 +3387,7 @@
3382
3387
  {
3383
3388
  "path": "modules/lifecycle/github-push.js",
3384
3389
  "mode": "0000644",
3385
- "sha256": "60b9338ed47d01e9f38fe3fa9f2f2cde6ff241b8e47efcb8fcf4c4f19d32c766"
3390
+ "sha256": "9658ccf0dd332d6c4ce1e269c2966ebe2e2bed5f909d5d503722d8e8cfb1ebbc"
3386
3391
  },
3387
3392
  {
3388
3393
  "path": "modules/lifecycle/goal-advisory.js",
@@ -3437,7 +3442,7 @@
3437
3442
  {
3438
3443
  "path": "modules/lifecycle/publish-reconciler.js",
3439
3444
  "mode": "0000644",
3440
- "sha256": "964af2331007a4cfb9142e7245d7625cf21c973e4f885e0171855b9e188b9e95"
3445
+ "sha256": "ad57ad242956c6ee3dc4aeddd66e980af48737fab644765aa3c400720f4a9668"
3441
3446
  },
3442
3447
  {
3443
3448
  "path": "modules/lifecycle/routes/analytics.js",
@@ -4022,12 +4027,12 @@
4022
4027
  {
4023
4028
  "path": "package-lock.json",
4024
4029
  "mode": "0000644",
4025
- "sha256": "413f575fce07ba5b167f6492a4f1aed1f7eacb4f4351457303d3875f04557787"
4030
+ "sha256": "290c88cdeae475bf124eb3e74292108c669006b69414d258ae3d961b9e80f743"
4026
4031
  },
4027
4032
  {
4028
4033
  "path": "package.json",
4029
4034
  "mode": "0000644",
4030
- "sha256": "d0d31ac616a949591bca2975236bbf6d98437e969d1618bd68811620c2a130c3"
4035
+ "sha256": "316768e98e388920e8710c944cd4fbfd7c2bf947a742a3aa6698d66b35e838e6"
4031
4036
  },
4032
4037
  {
4033
4038
  "path": "public-docs/index.html",
@@ -5647,7 +5652,7 @@
5647
5652
  {
5648
5653
  "path": "src/module-api.js",
5649
5654
  "mode": "0000644",
5650
- "sha256": "2dfd2867ab83689497aff7a3b7f213e756c182e047ef8da754e098a945491ce7"
5655
+ "sha256": "a122aca901bb83b94b1d475a651eae90c7a7f72251703ce3a81cd14dcf39fe66"
5651
5656
  },
5652
5657
  {
5653
5658
  "path": "src/module-loader/loader.js",
@@ -6417,7 +6422,7 @@
6417
6422
  {
6418
6423
  "path": "tests/github_push_merge_driver.mjs",
6419
6424
  "mode": "0000644",
6420
- "sha256": "1e038780532078d8d1a20feebb95ee6762fb300101f71cfff7076d2fa0d38b1b"
6425
+ "sha256": "850113ed91babc9eb54dc22231f194d283fd3f94cee9deef1e6e0bf6f3a969c2"
6421
6426
  },
6422
6427
  {
6423
6428
  "path": "tests/github_repos_helpers.mjs",
@@ -7147,7 +7152,7 @@
7147
7152
  {
7148
7153
  "path": "tests/publish_reconciler.mjs",
7149
7154
  "mode": "0000644",
7150
- "sha256": "240337de03354de198d6baaaca9f33b267aceb40a8ba43d31303eb21fad9226c"
7155
+ "sha256": "1aeb87a14e411160784c94ee5392f323a2ad0a373bd9acf91337289041e8d2bc"
7151
7156
  },
7152
7157
  {
7153
7158
  "path": "tests/rank_gate.mjs",
@@ -7464,6 +7469,11 @@
7464
7469
  "mode": "0000644",
7465
7470
  "sha256": "0eeccae9320f419667cf9ede3c13671a5b03571f7f2f17067bff1857234ddf91"
7466
7471
  },
7472
+ {
7473
+ "path": "tests/skill_grade_audit.mjs",
7474
+ "mode": "0000644",
7475
+ "sha256": "75ad6cd89f6e84e88764a99350e093df31a525eff392e3f84ce99fa32cb952c4"
7476
+ },
7467
7477
  {
7468
7478
  "path": "tests/skill_grade_recover.mjs",
7469
7479
  "mode": "0000644",
@@ -0,0 +1,77 @@
1
+ ---
2
+ name: grade-audit
3
+ description: Manual-cadence accountability sweep over recent grades — the override ledger (what shipped despite a failing grade, and was it accounted for), dropped blocker/major findings on passing grades, a false-pass spot-check for runtime-behavior blindness, and an outage roll-up. Triggers when the user says "/grade-audit", "audit recent grades", "run the grade audit", "what shipped past the grader", or "weekly grader accountability sweep". Describes and queues follow-up work ONLY — it NEVER confirms a task, flips a status, or re-grades anything (ADR 0162; verdicts are advisory, never self-executing). Manually invoked; do not depend on scheduled routines.
4
+ ---
5
+
6
+ You are auditing the last week of grades for accountability gaps: work that shipped past a failing grade unaccounted for, real findings that rode passing grades into production and evaporated, and passes the panel structurally could not have judged. You **describe and queue**. You never confirm, flip, re-grade, or add a gate — every follow-up lands as a queued idea or a handoff to another skill.
7
+
8
+ ## The sweep window
9
+
10
+ ```bash
11
+ node scripts/gds/api.js GET "/api/gds/public/recent-shipped?days=7"
12
+ ```
13
+
14
+ For each shipped task in the window, read its persisted grade:
15
+
16
+ ```bash
17
+ node scripts/gds/api.js GET "/api/gds/tasks/<id>?include=grade"
18
+ ```
19
+
20
+ Bucket by grade shape: `passed`, failed, `signals.panel_outcome='unavailable'` (outage — no quality signal existed), or no grade row at all.
21
+
22
+ ## Leg 1 — the override ledger
23
+
24
+ For every shipped task whose grade **failed** (or is absent), the land needed an override. Cross-reference the ledger (Archon — `override_request.decide`):
25
+
26
+ ```bash
27
+ node scripts/gds/api.js GET "/api/gds/override-requests?status=approved"
28
+ ```
29
+
30
+ - Split **infra vs real** first: a fail that is actually `panel_outcome='unavailable'` (or the errored-worker zero-issue shape) is an outage artifact, not an overridden quality verdict — route those to the Leg 4 roll-up.
31
+ - A shipped task with a real failing grade and **no approved override-request row** landed via the raw confirm — the un-audited path. Name it in the report and print its unaddressed `issues[]` verbatim. That set is the ledger's debt.
32
+ - For each such task, queue the debt (see the queueing rule below) — do not chase the lander, do not un-ship anything.
33
+
34
+ ## Leg 2 — dropped findings on passing grades
35
+
36
+ A pass with `blocker`/`major` entries in `issues[]` shipped real findings that no process ever picks up (the evaluation counted 274 findings on passing ships with zero conversion paths). For each:
37
+
38
+ - **Worker-attribution filter:** findings attributed to the advisory **Narc** are the known noise class — triage them by hand (read the finding, decide), never auto-trust them into the queue. Findings from the gating workers (Quality, Hacker, Efficiency) or the deterministic pre-passes are higher-confidence — queue them unless plainly stale.
39
+ - **Queueing rule (idempotent):** first read the open inbox (`node scripts/gds/api.js GET /api/gds/inbox`) and skip any finding already queued — the marker is the deterministic title prefix. Then:
40
+
41
+ ```bash
42
+ node scripts/gds/capture.js "[grade-audit] task <id>: <finding summary>" --kind bug
43
+ ```
44
+
45
+ One idea per surviving finding, titled exactly `[grade-audit] task <id>: …` so a re-run of this audit files nothing twice.
46
+
47
+ ## Leg 3 — false-pass spot-check (runtime-behavior blindness)
48
+
49
+ The re-review found 2/8 material misses, both the same root cause: **the panel grades diff text and never reasons about runtime** — a CI workflow whose default shallow checkout broke on first run scored functional_fidelity 10/10, and an SSRF guard bypassable via HTTP redirect passed at 9.5. For the riskiest ships in the window — security surfaces, CI/workflow files, the largest diffs, and 9.5s with suspiciously few findings — re-read the merged diff asking one question: *what does this code do at runtime that the diff text does not show?* (defaults it inherits, redirects it follows, first-run behavior, tool/CI defaults). A suspicion is queued via the Leg 2 rule, prefixed the same way — never a re-grade, never a flipped verdict.
50
+
51
+ ## Leg 4 — outage roll-up
52
+
53
+ Count the window's `panel_outcome='unavailable'` rows. One-off outages just get the `--regrade` pointer in the report; a streak (2+ consecutive, or clustered on one builder) is a health problem — hand off to **/grader-health**, which owns outage diagnosis. Do not diagnose here.
54
+
55
+ ## Report shape
56
+
57
+ End with the ledger, most severe first:
58
+
59
+ 1. **Un-accounted overrides** — task, findings printed verbatim, queued-idea ref. A task that landed with no approved override-request row is the headline.
60
+ 2. **Queued findings** — what was filed this run, what was skipped as already-queued, what was held back by the Narc filter (and why).
61
+ 3. **Spot-check suspicions** — task, the runtime question the panel couldn't answer, queued-idea ref.
62
+ 4. **Outage roll-up** — count + the /grader-health handoff if a streak.
63
+
64
+ A clean week is four one-liners.
65
+
66
+ ## Constraints
67
+
68
+ - **Never confirm, never flip, never re-grade (ADR 0162).** Verdicts are advisory and never self-executing here — this skill's entire output is a report plus queued `idea_inbox` rows. If a leg tempts you to "just fix it", the fix is a queued idea or a claimed task, not an action inside the audit.
69
+ - **Manual cadence.** Invoke by hand (weekly is the intended rhythm). Do not wire it to cron or depend on `.claude/scheduled-tasks/`.
70
+ - **Idempotent re-runs.** The `[grade-audit] task <id>:` title marker + the inbox pre-check are what make running it twice harmless — keep both.
71
+ - **Archon surfaces degrade gracefully.** Without `override_request.decide`, Leg 1 can only report "ledger unreadable at this rank" — say so rather than skipping silently.
72
+
73
+ ## Files this skill touches
74
+
75
+ - Reads: `GET /api/gds/public/recent-shipped`, `GET /api/gds/tasks/:id?include=grade`, `GET /api/gds/override-requests?status=approved` (Archon), `GET /api/gds/inbox`.
76
+ - Writes: `idea_inbox` rows via `node scripts/gds/capture.js` (queued follow-ups only).
77
+ - Never calls: any confirm, status, grade, or regrade endpoint.
package/docs/file-map.md CHANGED
@@ -350,6 +350,7 @@ tests/
350
350
  ├── fix-task/SKILL.md ← /fix-task N — claim, fix (test-first), verify, and ship a single GDS task by id end to end; defers to /builder-claim + /builder-ship for their own mechanics
351
351
  ├── goal-create/SKILL.md ← /goal-create — plan and create ONE goal (the module-scoped workspace between version and criteria, ADR 0086) in one session: <redacted>, scope wall, 2-4 criteria, seed tasks; also the auto-pickup when a task is created with no goal to live in (goal_advisory); Metic+ (Archon for a protected-module scope)
352
352
  ├── goal-review/SKILL.md ← /goal-review — walk the criterion review queue: each auto-flagged 'met — pending review' criterion gets confirm / reject / defer; confirming a goal's last criterion auto-achieves it (ADR 0086 §6); Metic+
353
+ ├── grade-audit/SKILL.md ← /grade-audit — manual-cadence accountability sweep: override ledger (shipped-despite-fail, infra vs real, un-requested confirms named), dropped blocker/major findings on passes queued via capture.js with the advisory-Narc filter, runtime-blindness false-pass spot-check, outage roll-up → /grader-health; describes and queues only, never confirms (ADR 0162, task 1002657)
353
354
  ├── grade-recover/SKILL.md ← /grade-recover N — the 'my grade failed, now what' loop: diagnose outage vs fabricated fail vs genuine fail from the persisted grade, then run the right recovery (--regrade or the auditable override-request); adds no ship-path gate (ADR 0162, task 1002655)
354
355
  ├── grader-health/SKILL.md ← /grader-health — read-only grader health check: outage runs (n_unavailable streaks, outage-vs-rejection confirmation), pass-rate drift vs baseline, per-grade cost anomalies, stale model pins; emits exact --regrade remediation lines; optional --probe live calibration (Metic+, paid) (task 1002656)
355
356
  ├── idea-triage/SKILL.md ← /idea-triage — daily walk through the GDS idea_inbox (promote / discard / merge); Metic+
@@ -675,6 +675,40 @@ async function mergeIfGreen({ branch }, deps = {}) {
675
675
  return { ...base, reason: `merge_api_${res.status}`, detail: detail.slice(0, 160) };
676
676
  }
677
677
 
678
+ // task 1002666 — the carrier race. Publish-on-merge (ADR 0161) pushes a carrier
679
+ // commit to main after EVERY land, so under an up-to-date branch-protection rule
680
+ // a PR whose checks report after the previous merge's carrier goes BEHIND:
681
+ // green, mergeable, and permanently stalled (GitHub's update-branch also disarms
682
+ // auto-merge, so re-arming alone cannot recover it — observed on PR #48,
683
+ // 2026-08-11). Server-side remedy: update the branch; checks re-run on the fresh
684
+ // head and the next sweep's mergeIfGreen lands it. Fires ONLY on the definitive
685
+ // BEHIND shape — GitHub reports the strict-up-to-date green-PR stall as BEHIND
686
+ // once checks have reported; BLOCKED means checks are pending/missing and stays
687
+ // untouched (updating there would loop on PRs that can never go green).
688
+ // The state is re-read here even though the sweep's mergeIfGreen just read it:
689
+ // update-branch is a WRITE, and the re-read guards against acting on a state
690
+ // that changed between the two calls (e.g. the PR merged meanwhile) — the same
691
+ // read-before-write discipline mergeIfGreen itself applies.
692
+ async function updateBranchIfBehind({ branch }, deps = {}) {
693
+ const s = await getMergeState({ branch }, deps);
694
+ const base = { number: s.number, updated_now: false, merge_state: s.merge_state, reason: null };
695
+ if (s.merged) return { ...base, reason: 'already_merged' };
696
+ if (s.pr_state !== 'open') return { ...base, reason: 'no_open_pr' };
697
+ if (s.merge_state !== 'BEHIND') return { ...base, reason: mergeBlockReason(s) };
698
+ const token = await resolveToken(deps);
699
+ const repoInfo = deps.repoInfo || loadRepoInfo();
700
+ const { owner, repo } = repoInfo;
701
+ const doFetch = deps.fetchImpl || fetch;
702
+ const res = await doFetch(`https://api.github.com/repos/${owner}/${repo}/pulls/${s.number}/update-branch`, {
703
+ method: 'PUT',
704
+ headers: { ...ghHeaders(token), 'Content-Type': 'application/json' },
705
+ body: JSON.stringify(s.head_sha ? { expected_head_sha: s.head_sha } : {}),
706
+ });
707
+ if (res.ok) return { ...base, updated_now: true };
708
+ const detail = await res.text().catch(() => '');
709
+ return { ...base, reason: `update_api_${res.status}`, detail: detail.slice(0, 160) };
710
+ }
711
+
678
712
  // task 1139 (ADR 0058): server-set commit status — the TRUSTLESS rank attestation the
679
713
  // gate-review CI check reads. Only the server (holding the App/PAT credential AND the
680
714
  // authenticated builder's server-resolved rank) can set it, so CI can trust it where it
@@ -1043,6 +1077,7 @@ module.exports = {
1043
1077
  getPublishStatus,
1044
1078
  getMergeState,
1045
1079
  mergeIfGreen,
1080
+ updateBranchIfBehind,
1046
1081
  setCommitStatus,
1047
1082
  setGateOwnerApproved,
1048
1083
  listOpenPullRequests,
@@ -200,6 +200,7 @@ async function runSweep(deps = {}) {
200
200
  let merged = 0;
201
201
  let resolvedConflicts = 0;
202
202
  let refreshedStale = 0; // task 1915: PRs whose stale generated indexes we regen+pushed
203
+ let behindUpdated = 0; // task 1002666: green PRs behind main (carrier race) whose branch we updated
203
204
  let errors = 0;
204
205
  let unprovable = 0; // confirmed tasks the sweep could not prove landed this pass
205
206
  let staleStrands = 0; // of those, the ones older than STALE_CONFIRMED_MS (WARN'd)
@@ -305,6 +306,38 @@ async function runSweep(deps = {}) {
305
306
  } catch (e) {
306
307
  errFn(`[publish-reconciler] conflict-resolve for task ${r.task_id} (non-blocking): ${e && e.message}`);
307
308
  }
309
+ } else if (
310
+ m && !m.merged && m.reason === 'branch_behind_base' &&
311
+ typeof push.updateBranchIfBehind === 'function'
312
+ ) {
313
+ // task 1002666: the ADR 0161 carrier race — every land pushes a
314
+ // carrier commit to main, so a green PR whose checks reported after
315
+ // the previous merge sits BEHIND forever (GitHub's update-branch
316
+ // disarms auto-merge, so it cannot recover on its own). Update the
317
+ // branch server-side; checks re-run on the fresh head and the NEXT
318
+ // sweep's mergeIfGreen lands it. Attempt-capped per branch on the
319
+ // shared heal map — on a hot fleet every update re-opens the race
320
+ // window, and a treadmill must surface, not spin.
321
+ const bKey = `behind:${branch}`;
322
+ const bn = attempts.get(bKey) || 0;
323
+ if (bn >= MAX_STALE_HEAL_ATTEMPTS) {
324
+ logFn(
325
+ `[publish-reconciler] task ${r.task_id} (${branch}) PR #${m.number} still behind main after ${bn} update-branch attempt(s) — capped (merge traffic is outracing this PR); left for a human`
326
+ );
327
+ } else {
328
+ attempts.set(bKey, bn + 1);
329
+ try {
330
+ const u = await push.updateBranchIfBehind({ branch });
331
+ if (u && u.updated_now) {
332
+ behindUpdated++;
333
+ logFn(
334
+ `[publish-reconciler] task ${r.task_id} (${branch}) PR #${u.number} was green but behind main (carrier race) — server-updated the branch; checks re-run, lands next sweep`
335
+ );
336
+ }
337
+ } catch (e) {
338
+ errFn(`[publish-reconciler] update-branch for task ${r.task_id} (non-blocking): ${e && e.message}`);
339
+ }
340
+ }
308
341
  } else if (
309
342
  m && couldBeStaleGenerated(m) &&
310
343
  typeof resolver.refreshStaleGenerated === 'function'
@@ -544,14 +577,17 @@ async function runSweep(deps = {}) {
544
577
  }
545
578
 
546
579
  // Prune the treadmill guard so it can't leak: a branch that left the confirmed set
547
- // (landed, or released) never returns under the same name, so drop it.
580
+ // (landed, or released) never returns under the same name, so drop it. Keys may be
581
+ // namespaced ('behind:<branch>', task 1002666) — compare on the branch itself so a
582
+ // live branch's cap survives across sweeps and a departed branch's keys still prune.
548
583
  for (const b of attempts.keys()) {
549
- if (!branchesSeen.has(b)) attempts.delete(b);
584
+ const plain = b.startsWith('behind:') ? b.slice('behind:'.length) : b;
585
+ if (!branchesSeen.has(plain)) attempts.delete(b);
550
586
  }
551
587
 
552
- if (reconciled || merged || resolvedConflicts || refreshedStale) {
588
+ if (reconciled || merged || resolvedConflicts || refreshedStale || behindUpdated) {
553
589
  logFn(
554
- `[publish-reconciler] swept ${rows.length} confirmed task(s); merged ${merged} stalled PR(s); auto-resolved ${resolvedConflicts} generated/index conflict(s); regen-healed ${refreshedStale} stale-generated PR(s); reconciled ${reconciled} → shipped`
590
+ `[publish-reconciler] swept ${rows.length} confirmed task(s); merged ${merged} stalled PR(s); auto-resolved ${resolvedConflicts} generated/index conflict(s); regen-healed ${refreshedStale} stale-generated PR(s); branch-updated ${behindUpdated} behind-main PR(s); reconciled ${reconciled} → shipped`
555
591
  );
556
592
  }
557
593
  if (staleStrands) {
@@ -559,7 +595,7 @@ async function runSweep(deps = {}) {
559
595
  `[publish-reconciler] ${staleStrands} stale strand(s): confirmed >${Math.round(STALE_CONFIRMED_MS / 60_000)}min with no provable land — credited but unlanded; see STRAND lines above`
560
596
  );
561
597
  }
562
- return { checked: rows.length, reconciled, merged, resolvedConflicts, refreshedStale, errors, unprovable, staleStrands, strandsFlagged };
598
+ return { checked: rows.length, reconciled, merged, resolvedConflicts, refreshedStale, behindUpdated, errors, unprovable, staleStrands, strandsFlagged };
563
599
  }
564
600
 
565
601
  // task 1366 — server-side session-cost reconcile, piggybacked on the same 5-min
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.38",
3
+ "version": "1.19.40",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.38",
9
+ "version": "1.19.40",
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.38",
3
+ "version": "1.19.40",
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
@@ -503,8 +503,12 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
503
503
  // landed since 1.19.36 with no explicit bump. run 31510590459. (task 1002620)
504
504
  // 1.19.38 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
505
505
  // landed since 1.19.37 with no explicit bump. run 31511537558. (task 1002620)
506
+ // 1.19.39 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
507
+ // landed since 1.19.38 with no explicit bump. run 31512284325. (task 1002620)
508
+ // 1.19.40 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
509
+ // landed since 1.19.39 with no explicit bump. run 31513136724. (task 1002620)
506
510
  // ---------------------------------------------------------------------------
507
- const CORE_VERSION = '1.19.38'; // CI auto-patch carrier (ADR 0161); see changelog above
511
+ const CORE_VERSION = '1.19.40'; // CI auto-patch carrier (ADR 0161); see changelog above
508
512
 
509
513
  // A namespaced logger so a module's log lines are attributable + consistent.
510
514
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
@@ -169,6 +169,64 @@ test('mergeBlockReason maps the common blocker states', () => {
169
169
  assert.equal(gh.mergeBlockReason({ ...open, merged: true }), null);
170
170
  });
171
171
 
172
+ // --- task 1002666: updateBranchIfBehind (the carrier-race branch updater) ---
173
+
174
+ // fetchImpl serving graphql + recording the update-branch PUT.
175
+ function mkUpdateFetch({ node, updateStatus = 202 } = {}) {
176
+ const calls = { graphql: 0, update: 0, updateArgs: null };
177
+ const fetchImpl = async (url, opts = {}) => {
178
+ if (String(url).includes('/graphql')) {
179
+ calls.graphql++;
180
+ return {
181
+ ok: true, status: 200,
182
+ json: async () => ({ data: { repository: { pullRequests: { nodes: node ? [node] : [] } } } }),
183
+ };
184
+ }
185
+ if (String(url).includes('/update-branch')) {
186
+ calls.update++;
187
+ calls.updateArgs = { url, body: JSON.parse(opts.body || '{}') };
188
+ return {
189
+ ok: updateStatus >= 200 && updateStatus < 300,
190
+ status: updateStatus,
191
+ text: async () => '{}',
192
+ };
193
+ }
194
+ throw new Error(`unexpected fetch: ${url}`);
195
+ };
196
+ return { fetchImpl, calls };
197
+ }
198
+
199
+ test('updateBranchIfBehind updates a BEHIND PR, pinning the expected head sha', async () => {
200
+ const { fetchImpl, calls } = mkUpdateFetch({ node: prNode({ mergeStateStatus: 'BEHIND', oid: 'headsha' }) });
201
+ const u = await gh.updateBranchIfBehind({ branch: 'claude/wt' }, DEPS(fetchImpl));
202
+ assert.equal(u.updated_now, true);
203
+ assert.equal(calls.update, 1);
204
+ assert.match(String(calls.updateArgs.url), /\/pulls\/125\/update-branch$/);
205
+ assert.equal(calls.updateArgs.body.expected_head_sha, 'headsha', 'a raced head 422s instead of updating blind');
206
+ });
207
+
208
+ test('updateBranchIfBehind refuses every non-BEHIND shape without calling the API', async () => {
209
+ for (const [node, reason] of [
210
+ [prNode({ mergeStateStatus: 'CLEAN' }), 'clean'],
211
+ [prNode({ merged: true }), 'already_merged'],
212
+ [prNode({ state: 'CLOSED' }), 'no_open_pr'],
213
+ [prNode({ mergeStateStatus: 'BLOCKED' }), 'blocked_required_review_or_pending_check'],
214
+ ]) {
215
+ const { fetchImpl, calls } = mkUpdateFetch({ node });
216
+ const u = await gh.updateBranchIfBehind({ branch: 'claude/wt' }, DEPS(fetchImpl));
217
+ assert.equal(u.updated_now, false);
218
+ assert.equal(calls.update, 0, 'no update PUT for a non-BEHIND PR');
219
+ if (reason) assert.equal(u.reason, reason);
220
+ }
221
+ });
222
+
223
+ test('updateBranchIfBehind surfaces an API refusal as update_api_<status>', async () => {
224
+ const { fetchImpl } = mkUpdateFetch({ node: prNode({ mergeStateStatus: 'BEHIND' }), updateStatus: 403 });
225
+ const u = await gh.updateBranchIfBehind({ branch: 'claude/wt' }, DEPS(fetchImpl));
226
+ assert.equal(u.updated_now, false);
227
+ assert.equal(u.reason, 'update_api_403');
228
+ });
229
+
172
230
  setTimeout(() => {
173
231
  console.log(`\ngithub-push merge driver (task 1162) tests: ${passed} passed, ${failed} failed`);
174
232
  process.exit(failed > 0 ? 1 : 0);
@@ -57,9 +57,10 @@ function mkDeps({
57
57
  broadcastImpl,
58
58
  conflictResolveImpl,
59
59
  refreshStaleImpl,
60
+ updateByBranch = {},
60
61
  healAttempts = new Map(),
61
62
  } = {}) {
62
- const calls = { ship: [], broadcast: [], status: [], merge: [], ancestry: [], search: [], resolve: [], refresh: [], flag: [], events: [], logs: [], errors: [] };
63
+ const calls = { ship: [], broadcast: [], status: [], merge: [], ancestry: [], search: [], resolve: [], refresh: [], update: [], flag: [], events: [], logs: [], errors: [] };
63
64
  // isSafeBranch mirrors github-push's pattern enough for the test (rejects
64
65
  // option-like / traversal refs); we use the REAL module so the contract can't drift.
65
66
  const githubPush = require('../modules/lifecycle/github-push.js');
@@ -110,6 +111,15 @@ function mkDeps({
110
111
  if (f instanceof Error) throw f;
111
112
  return f || null;
112
113
  },
114
+ // task 1002666: the carrier-race branch updater. Default: no open PR (a
115
+ // no-op), so pre-1002666 tests keep their exact behavior. Script per
116
+ // branch via updateByBranch; an Error value throws (swallowed, non-blocking).
117
+ updateBranchIfBehind: async ({ branch }) => {
118
+ calls.update.push(branch);
119
+ const u = updateByBranch[branch];
120
+ if (u instanceof Error) throw u;
121
+ return u || { updated_now: false, reason: 'no_open_pr' };
122
+ },
113
123
  // task 1313 head_sha land-check. Default: not on main. An Error value throws
114
124
  // (to prove a transient compare-API miss is swallowed, leaving it for next sweep).
115
125
  isAncestorOfDefaultBranch: async ({ sha }) => {
@@ -790,6 +800,79 @@ test('1915 mergeable-but-red PR → regen-heal fires on the right branch; refres
790
800
  assert.equal(deps._calls.ship.length, 0, 'the heal never ships (no phantom-ship — land still needs the tip proof)');
791
801
  });
792
802
 
803
+ // --- task 1002666: the carrier race — green PR behind main gets its branch updated ---
804
+
805
+ // mergeIfGreen shape for a green, mergeable PR that fell behind main (the ADR 0161
806
+ // carrier landed after this PR's checks reported).
807
+ const behindGreen = (number = 48) => ({
808
+ pr_state: 'open', number, merged: false, merged_now: false, mergeable: 'MERGEABLE', reason: 'branch_behind_base',
809
+ });
810
+
811
+ test('1002666 green-but-behind PR (carrier race) → server updates the branch; NOT shipped this sweep', async () => {
812
+ const deps = mkDeps({
813
+ rows: [{ task_id: 500, builder_id: 5, worktree_name: 'wt-behind' }],
814
+ statusByBranch: { 'claude/wt-behind': PENDING },
815
+ mergeByBranch: { 'claude/wt-behind': behindGreen() },
816
+ updateByBranch: { 'claude/wt-behind': { number: 48, updated_now: true, merge_state: 'BEHIND', reason: null } },
817
+ });
818
+ const r = await reconciler.runSweep(deps);
819
+ assert.equal(r.behindUpdated, 1, 'the behind PR was branch-updated');
820
+ assert.deepEqual(deps._calls.update, ['claude/wt-behind'], 'the update is invoked on exactly this task\'s branch');
821
+ assert.equal(r.reconciled, 0, 'no ship this sweep — checks re-run on the new head, lands next sweep');
822
+ assert.equal(deps._calls.ship.length, 0, 'the update never ships (land still needs the tip proof)');
823
+ assert.ok(deps._calls.logs.some((l) => /carrier race/.test(l)), 'the log names the carrier race');
824
+ });
825
+
826
+ test('1002666 update-branch attempts are capped on the shared heal map (a hot fleet must surface, not spin)', async () => {
827
+ const healAttempts = new Map([['behind:claude/wt-behind', 3]]);
828
+ const deps = mkDeps({
829
+ rows: [{ task_id: 501, builder_id: 5, worktree_name: 'wt-behind' }],
830
+ statusByBranch: { 'claude/wt-behind': PENDING },
831
+ mergeByBranch: { 'claude/wt-behind': behindGreen() },
832
+ updateByBranch: { 'claude/wt-behind': { number: 48, updated_now: true } },
833
+ healAttempts,
834
+ });
835
+ const r = await reconciler.runSweep(deps);
836
+ assert.equal(deps._calls.update.length, 0, 'at the cap the updater is not invoked');
837
+ assert.equal(r.behindUpdated, 0);
838
+ assert.ok(deps._calls.logs.some((l) => /capped/.test(l)), 'the cap is logged, not silent');
839
+ });
840
+
841
+ test('1002666 the behind-cap ACCUMULATES across sweeps for a live branch (the prune must not reset it)', async () => {
842
+ const healAttempts = new Map();
843
+ const mk = () => mkDeps({
844
+ rows: [{ task_id: 503, builder_id: 5, worktree_name: 'wt-behind' }],
845
+ statusByBranch: { 'claude/wt-behind': PENDING },
846
+ mergeByBranch: { 'claude/wt-behind': behindGreen() },
847
+ updateByBranch: { 'claude/wt-behind': { number: 48, updated_now: true } },
848
+ healAttempts,
849
+ });
850
+ for (let i = 1; i <= 3; i++) {
851
+ await reconciler.runSweep(mk());
852
+ assert.equal(healAttempts.get('behind:claude/wt-behind'), i, `attempt ${i} is remembered after the sweep's prune`);
853
+ }
854
+ const fourth = mk();
855
+ const r = await reconciler.runSweep(fourth);
856
+ assert.equal(fourth._calls.update.length, 0, 'the 4th sweep is capped — a permanently-behind PR surfaces instead of spinning');
857
+ assert.equal(r.behindUpdated, 0);
858
+ // A branch that LEAVES the confirmed set prunes its namespaced key too (no leak).
859
+ const empty = mkDeps({ rows: [], healAttempts });
860
+ await reconciler.runSweep(empty);
861
+ assert.equal(healAttempts.has('behind:claude/wt-behind'), false, 'departed branch prunes its behind: key');
862
+ });
863
+
864
+ test('1002666 an update-branch API failure is swallowed (non-blocking) and the sweep continues', async () => {
865
+ const deps = mkDeps({
866
+ rows: [{ task_id: 502, builder_id: 5, worktree_name: 'wt-behind' }],
867
+ statusByBranch: { 'claude/wt-behind': PENDING },
868
+ mergeByBranch: { 'claude/wt-behind': behindGreen() },
869
+ updateByBranch: { 'claude/wt-behind': new Error('update-branch 403') },
870
+ });
871
+ const r = await reconciler.runSweep(deps);
872
+ assert.equal(r.behindUpdated, 0);
873
+ assert.ok(deps._calls.errors.some((l) => /update-branch/.test(l)), 'the failure is logged to errFn');
874
+ });
875
+
793
876
  test('1915 mergeable-but-red PR where regen yields NO diff → not healed, logged as a real check failure', async () => {
794
877
  const deps = mkDeps({
795
878
  rows: [{ task_id: 401, builder_id: 5, worktree_name: 'wt-realfail' }],
@@ -0,0 +1,50 @@
1
+ // tests/skill_grade_audit.mjs — the /grade-audit skill exists and covers the
2
+ // accountability sweep from the grader-hardening evaluation §6 (task 1002657):
3
+ // the override ledger, dropped blocker/major findings on passing grades (with
4
+ // the advisory-Narc attribution filter), the false-pass spot-check, and the
5
+ // outage roll-up handoff — describing and queueing ONLY (ADR 0162/0158: it
6
+ // never confirms a task or flips status).
7
+ import assert from 'node:assert/strict';
8
+ import { test } from 'node:test';
9
+ import { readFileSync, existsSync } from 'node:fs';
10
+ import { fileURLToPath } from 'node:url';
11
+ import path from 'node:path';
12
+
13
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
14
+ const SKILL = path.join(ROOT, '.claude', 'skills', 'grade-audit', 'SKILL.md');
15
+
16
+ // .claude/skills is INSTANCE-materialized; a neutral core checkout carries none
17
+ // (task 1002470) — skip there, same guard shape as file_map.mjs.
18
+ const skillsDirExists = existsSync(path.join(ROOT, '.claude', 'skills'));
19
+
20
+ test('/grade-audit exists, covers the sweep legs, and stays describe-and-queue only', (t) => {
21
+ if (!skillsDirExists) return t.skip('.claude/skills not materialized in this checkout');
22
+ assert.ok(existsSync(SKILL), '.claude/skills/grade-audit/SKILL.md must exist (task 1002657)');
23
+ const md = readFileSync(SKILL, 'utf8');
24
+
25
+ // Data surfaces.
26
+ assert.match(md, /\/api\/gds\/public\/recent-shipped/, 'sweeps the recent-shipped window');
27
+ assert.match(md, /include=grade/, 'reads each task\'s persisted grade');
28
+ assert.match(md, /\/api\/gds\/override-requests/, 'cross-references the override ledger');
29
+
30
+ // Leg 1 — override ledger: shipped-with-failing-grade, infra vs real split.
31
+ assert.match(md, /panel_outcome='unavailable'/, 'splits infra outage from a real fail');
32
+ assert.match(md, /no approved (override-)?request/i, 'names tasks that landed with no request row');
33
+
34
+ // Leg 2 — dropped findings with the worker-attribution filter.
35
+ assert.match(md, /blocker|major/, 'targets blocker/major findings on passing grades');
36
+ assert.match(md, /Narc/i, 'carries the advisory-Narc attribution filter');
37
+ assert.match(md, /capture\.js/, 'queues findings via capture.js');
38
+ assert.match(md, /\[grade-audit\]/, 'uses the deterministic title marker for idempotency');
39
+
40
+ // Leg 3 — false-pass spot-check (the 2/8 runtime-blindness classes).
41
+ assert.match(md, /runtime/i, 'spot-checks for runtime-behavior blindness');
42
+
43
+ // Leg 4 — outage roll-up handoff.
44
+ assert.match(md, /\/grader-health/, 'hands outage windows to /grader-health');
45
+
46
+ // The hard constraint.
47
+ assert.match(md, /never confirm/i, 'states it never confirms');
48
+ assert.match(md, /ADR 0162/, 'anchors the no-new-gates constraint');
49
+ assert.ok(!/curl[^\n]*\/confirm/.test(md), 'carries no ready-to-paste confirm curl');
50
+ });