@bongos/core 1.19.1032 → 1.19.1034

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.
Files changed (151) hide show
  1. package/.bongos-core.json +322 -117
  2. package/.claude/skills/builder-start/SKILL.md +1 -1
  3. package/.claude/skills/grade-audit/SKILL.md +1 -1
  4. package/.claude/skills/grade-recover/SKILL.md +2 -1
  5. package/.claude/skills/grader-health/SKILL.md +5 -1
  6. package/.claude/skills/merge-mode/SKILL.md +1 -1
  7. package/docs/adr/0089-modular-multi-model-adversarial-grader.md +15 -0
  8. package/docs/adr/0175-the-board-room-full-idea-ratification.md +3 -2
  9. package/docs/adr/0178-the-copy-desk-flag-and-queue.md +3 -3
  10. package/docs/adr/0188-patrol-runs-on-ci-not-the-server.md +2 -2
  11. package/docs/adr/0189-baseline-floors-and-the-freeze-detector.md +1 -1
  12. package/docs/adr/0233-a-copy-proposal-is-a-task-carrying-a-patch.md +1 -1
  13. package/docs/adr/0241-the-artist-gate-is-a-per-project-deploy-gate-that-reads-a-state.md +11 -3
  14. package/docs/adr/README.md +4 -4
  15. package/docs/api/openapi.json +1 -1
  16. package/docs/architecture.md +4 -3
  17. package/docs/canonical-permissions.md +2 -2
  18. package/docs/copy-inventory.md +34 -30
  19. package/docs/copy-registry.json +69 -33
  20. package/docs/file-map.md +4 -3
  21. package/docs/module-api-changelog.md +4 -0
  22. package/docs/recipes/autobongos-windows-host.md +3 -1
  23. package/docs/recipes/local-dev.md +45 -3
  24. package/docs/recipes/packaging-the-core.md +3 -1
  25. package/docs/recipes/redteam-patrol.md +2 -2
  26. package/docs/recipes/upgrading-the-core.md +1 -1
  27. package/modules/autonomy/cadence.js +52 -0
  28. package/modules/autonomy/gauge.js +57 -9
  29. package/modules/government/protected-surfaces.json +1 -0
  30. package/modules/grading/grade-engine.js +267 -0
  31. package/modules/grading/grader-subagent.js +4 -3
  32. package/modules/grading/grader-workers/index.js +67 -11
  33. package/modules/grading/grader-workers/worker-verdict.js +32 -2
  34. package/modules/grading/grader.js +109 -88
  35. package/modules/hall-ui/public/deploy.js +35 -0
  36. package/modules/hall-ui/public/project-settings.css +7 -0
  37. package/modules/hall-ui/records/builders-roster.md +10 -0
  38. package/modules/hall-ui/records/catalog.md +10 -0
  39. package/modules/hall-ui/records/collaboration.md +14 -0
  40. package/modules/hall-ui/records/goal-dependency-forest.md +11 -0
  41. package/modules/hall-ui/records/oversight-family.md +19 -0
  42. package/modules/hall-ui/records/panel-family.md +19 -0
  43. package/modules/hall-ui/records/reading-rooms.md +12 -0
  44. package/modules/hall-ui/records/record-family.md +13 -0
  45. package/modules/hall-ui/records/reference-room.md +9 -0
  46. package/modules/hall-ui/records/roster-and-ledger.md +9 -0
  47. package/modules/hall-ui/records/sky.md +23 -0
  48. package/modules/lifecycle/dependency-advisory.js +2 -1
  49. package/modules/lifecycle/routes/tasks.js +19 -4
  50. package/modules/lifecycle/ship-preflight.js +91 -0
  51. package/modules/provisioning/control-plane-guard.js +14 -13
  52. package/modules/provisioning/core-upgrade.js +30 -17
  53. package/modules/provisioning/public-refusal.js +44 -0
  54. package/modules/provisioning/routes/disconnect.js +2 -1
  55. package/modules/provisioning/routes/env-manifest.js +5 -4
  56. package/modules/provisioning/routes/provisioning.js +16 -15
  57. package/modules/provisioning/routes/repo-private.js +2 -1
  58. package/package-lock.json +2 -2
  59. package/package.json +1 -1
  60. package/release-notes.json +7481 -0
  61. package/scripts/gds/address-park-sweep.sh +3 -1
  62. package/scripts/gds/autobongos-run.js +91 -3
  63. package/scripts/gds/backfill-cost.js +20 -9
  64. package/scripts/gds/baseline-staleness.js +1 -1
  65. package/scripts/gds/claim.js +2 -2
  66. package/scripts/gds/credential-presence.js +281 -0
  67. package/scripts/gds/db-backup-nightly.sh +9 -4
  68. package/scripts/gds/db-backup-offsite.sh +4 -2
  69. package/scripts/gds/deploy-main-audit-gate.sh +11 -4
  70. package/scripts/gds/dev-lib.js +212 -5
  71. package/scripts/gds/dev.js +102 -50
  72. package/scripts/gds/doctor.js +3 -2
  73. package/scripts/gds/grade-correlation-audit.js +280 -0
  74. package/scripts/gds/grade-replay.js +237 -0
  75. package/scripts/gds/hooks/pre-push.js +7 -3
  76. package/scripts/gds/main-audit.js +19 -10
  77. package/scripts/gds/memory-map.js +3 -1
  78. package/scripts/gds/onboarding-config.js +3 -2
  79. package/scripts/gds/package-core.js +72 -1
  80. package/scripts/gds/plain-cards.js +4 -0
  81. package/scripts/gds/preflight.js +12 -4
  82. package/scripts/gds/provision-core-upgrade.js +282 -80
  83. package/scripts/gds/provision-repo-auth.js +15 -0
  84. package/scripts/gds/provision.js +1 -1
  85. package/scripts/gds/publish-credential-check.js +3 -0
  86. package/scripts/gds/push-main.js +14 -3
  87. package/scripts/gds/push-path-brief.js +3 -2
  88. package/scripts/gds/redteam-patrol.js +4 -4
  89. package/scripts/gds/release-notes.js +271 -0
  90. package/scripts/gds/run-routine.js +18 -5
  91. package/scripts/gds/run-unit-tests.js +14 -3
  92. package/scripts/gds/seed-board-room-tasks.js +1 -1
  93. package/scripts/gds/session-cost.js +12 -3
  94. package/scripts/gds/ship-deploy-target.js +11 -6
  95. package/scripts/gds/ship-flow.js +5 -3
  96. package/scripts/gds/ship-grade-steps.js +33 -0
  97. package/scripts/gds/ship-land.js +241 -16
  98. package/scripts/gds/ship-merge.js +3 -2
  99. package/scripts/gds/ship-preflight-steps.js +86 -11
  100. package/scripts/gds/ship.js +1 -1
  101. package/scripts/gds/stray-unit-sweep.sh +3 -1
  102. package/scripts/gds/upgrade-migrate.js +67 -0
  103. package/scripts/gds/upgrade.js +9 -7
  104. package/scripts/hall-preview/README.md +20 -3
  105. package/scripts/hall-preview/live-identity.js +1 -1
  106. package/scripts/hall-preview/refresh-fixtures.js +71 -11
  107. package/scripts/hall-preview/scrub-identity.js +296 -0
  108. package/src/bongos/routes/security.js +3 -3
  109. package/src/build-info.js +4 -2
  110. package/src/module-api.js +1 -1
  111. package/tests/adr_0089_scope.mjs +70 -0
  112. package/tests/adr_0162_citation.mjs +184 -0
  113. package/tests/autobongos_cadence.mjs +74 -0
  114. package/tests/autobongos_loop.mjs +114 -0
  115. package/tests/autonomy_gauge.mjs +56 -0
  116. package/tests/consumer_layout_boot.mjs +363 -31
  117. package/tests/core_upgrade_door.mjs +40 -6
  118. package/tests/core_upgrade_runner.mjs +329 -5
  119. package/tests/credential_presence.mjs +153 -0
  120. package/tests/db_backup_nightly.mjs +21 -3
  121. package/tests/db_backup_offsite.mjs +81 -0
  122. package/tests/deploy_audit_gate.mjs +5 -2
  123. package/tests/deploy_page_projects.mjs +63 -2
  124. package/tests/dev_lib.mjs +2 -1
  125. package/tests/dev_windows.mjs +324 -0
  126. package/tests/env_knobs_neutral_prefix.mjs +203 -0
  127. package/tests/fixtures/grade-replay/recorded-panel-grades.json +27 -0
  128. package/tests/government_protected_surfaces.mjs +10 -0
  129. package/tests/grade_correlation_audit.mjs +159 -0
  130. package/tests/grade_engine.mjs +249 -0
  131. package/tests/grade_engine_replay.mjs +99 -0
  132. package/tests/grader_prompt_window_bound.mjs +132 -0
  133. package/tests/grader_unbacked_score.mjs +194 -0
  134. package/tests/hall_preview_scrub.mjs +214 -0
  135. package/tests/hall_records.mjs +85 -0
  136. package/tests/helpers.mjs +25 -0
  137. package/tests/instance_db_migrated_on_code_move.mjs +8 -1
  138. package/tests/integration_parked_ratchet.mjs +24 -1
  139. package/tests/plain_cards.mjs +1 -1
  140. package/tests/provisioning_public_refusal.mjs +195 -0
  141. package/tests/publish_status_branch.mjs +128 -0
  142. package/tests/redteam_patrol.mjs +1 -1
  143. package/tests/release_notes.mjs +327 -0
  144. package/tests/shell_stat_portability.mjs +49 -0
  145. package/tests/ship_land_conflict.mjs +181 -0
  146. package/tests/ship_land_red_pr.mjs +236 -0
  147. package/tests/ship_preflight.mjs +226 -3
  148. package/tests/start_env_guard.mjs +16 -0
  149. package/tests/sweep_app_account.mjs +15 -2
  150. package/tests/upgrade.mjs +20 -1
  151. package/tests/upgrade_migrate.mjs +195 -0
package/src/module-api.js CHANGED
@@ -71,7 +71,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
71
71
  // there. scripts/gds/bump-version.js still rewrites the literal below; it appends
72
72
  // the entry to that file. Look for a version's history there, not here.
73
73
  // ---------------------------------------------------------------------------
74
- const CORE_VERSION = '1.19.1032'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
74
+ const CORE_VERSION = '1.19.1034'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
75
75
 
76
76
  // A namespaced logger so a module's log lines are attributable + consistent.
77
77
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
@@ -0,0 +1,70 @@
1
+ // tests/adr_0089_scope.mjs — ADR 0089 §8 names what the grading engine does NOT own,
2
+ // and this file keeps that list true (task 1002844).
3
+ //
4
+ // WHY. Four surfaces spend model tokens on the repository or guard the pipeline,
5
+ // so a reader planning the engine can take them for graders: the BFG session
6
+ // evaluator, gate-review, main-audit and the Architect audit. §8 records, for each,
7
+ // exactly what it shares with the grader. A prose boundary rots the moment one of
8
+ // them quietly starts calling grading code, so the coupling the ADR states is
9
+ // asserted from the source: the Architect uses exactly the three plumbing helpers
10
+ // §8 names, and the other three import no grading module at all.
11
+ //
12
+ // Run: node tests/adr_0089_scope.mjs
13
+
14
+ import { strict as assert } from 'node:assert';
15
+ import fs from 'node:fs';
16
+
17
+ let passed = 0;
18
+ let failed = 0;
19
+ function test(name, fn) {
20
+ try { fn(); passed++; console.log(` ok ${name}`); }
21
+ catch (err) { failed++; console.log(` FAIL ${name}`); console.log(` ${err.message}`); }
22
+ }
23
+
24
+ const read = (rel) => fs.readFileSync(new URL(`../${rel}`, import.meta.url), 'utf8');
25
+ const adr = read('docs/adr/0089-modular-multi-model-adversarial-grader.md');
26
+ const section = adr.slice(adr.indexOf('### 8. Scope: what the engine does NOT own'), adr.indexOf('## Consequences'));
27
+
28
+ const SURFACES = [
29
+ 'scripts/gds/session-eval.js',
30
+ 'scripts/gds/gate-review.js',
31
+ 'scripts/gds/main-audit.js',
32
+ 'scripts/gds/architect-audit.js',
33
+ ];
34
+
35
+ test('ADR 0089 carries the §8 scope section, before Consequences', () => {
36
+ assert.ok(adr.includes('### 8. Scope: what the engine does NOT own'), 'the section heading');
37
+ assert.ok(section.length > 200, 'the section sits between §7 and Consequences');
38
+ });
39
+
40
+ test('§8 names each of the four surfaces by path, and each path exists', () => {
41
+ for (const p of SURFACES) {
42
+ assert.ok(section.includes(p), `§8 names ${p}`);
43
+ assert.ok(fs.existsSync(new URL(`../${p}`, import.meta.url)), `${p} exists`);
44
+ }
45
+ });
46
+
47
+ test('the Architect uses exactly the three grader helpers §8 names — nothing from the grade path', () => {
48
+ // Code only: a comment that mentions grader.<thing> is not a call.
49
+ const src = read('scripts/gds/architect-audit.js').replace(/\/\*[\s\S]*?\*\//g, '').replace(/^\s*\/\/.*$/gm, '');
50
+ const used = [...new Set([...src.matchAll(/\bgrader\.([A-Za-z_]\w*)/g)].map((m) => m[1]))].sort();
51
+ assert.deepEqual(used, ['extractAssistantResult', 'extractJson', 'runSubagentCached'],
52
+ 'a new grader.* call from the Architect means §8 is out of date');
53
+ for (const name of used) assert.ok(section.includes(name), `§8 names ${name}`);
54
+ });
55
+
56
+ test('session-eval, gate-review and main-audit import no grading module', () => {
57
+ for (const p of SURFACES.filter((s) => !s.endsWith('architect-audit.js'))) {
58
+ assert.doesNotMatch(read(p), /require\([^)]*modules\/grading/, `${p} must not require grading code`);
59
+ }
60
+ });
61
+
62
+ test('main-audit and gate-review share only the rank-permission checker §8 names', () => {
63
+ for (const p of ['scripts/gds/main-audit.js', 'scripts/gds/gate-review.js']) {
64
+ assert.match(read(p), /permission-path-check/, `${p} reads the checker`);
65
+ }
66
+ assert.ok(section.includes('src/bongos/permission-path-check.js'), '§8 names the shared checker');
67
+ });
68
+
69
+ console.log(`\n${passed} passed, ${failed} failed`);
70
+ process.exit(failed === 0 ? 0 : 1);
@@ -0,0 +1,184 @@
1
+ // tests/adr_0162_citation.mjs — "a verdict is advisory, never self-executing" is
2
+ // ADR 0158 §2's rule, not ADR 0162's; no surface may cite 0162 for it (task 1003573).
3
+ //
4
+ // WHAT WENT WRONG. ADR 0162 retired REVIEW gates on `main`: no human approval sits in
5
+ // the land path, and the compensating controls it keeps are automatic — three of its
6
+ // four fail CLOSED, the grader among them. Surfaces across the repo nonetheless cited it
7
+ // by a slogan, "verdicts are advisory, never self-executing". ADR 0241 caught that the
8
+ // slogan is false as a blanket claim about gates. This task found the slogan is not
9
+ // 0162's wording at all: it is, nearly verbatim, the heading of ADR 0158 §2 — a rule
10
+ // about what an audit FINDING authorizes (nothing, until a human acts on it). So every
11
+ // gloss of 0162 with that slogan cited the wrong ADR, and the worst of them turned it
12
+ // into a claim 0162 contradicts ("grader verdicts are advisory").
13
+ //
14
+ // WHAT THIS PINS. (1) The premise, read from the two ADRs themselves: the slogan's text
15
+ // is in 0158 and absent from 0162. If either ADR is amended, this goes red and the
16
+ // question is re-asked, instead of the citations quietly drifting back. (2) No tracked
17
+ // surface glosses 0162 with the verdict rule, in any of the shapes the sweep found.
18
+ //
19
+ // Run: node --test tests/adr_0162_citation.mjs
20
+
21
+ import { test } from 'node:test';
22
+ import assert from 'node:assert/strict';
23
+ import { readFileSync } from 'node:fs';
24
+ import { spawnSync } from 'node:child_process';
25
+ import path from 'node:path';
26
+ import { fileURLToPath } from 'node:url';
27
+
28
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
29
+ const read = (rel) => readFileSync(path.join(ROOT, rel), 'utf8');
30
+
31
+ const ADR_0158 = 'docs/adr/0158-choke-point-verdict-taxonomy.md';
32
+ const ADR_0162 = 'docs/adr/0162-review-gates-retired-rank-consistent-ci-only.md';
33
+
34
+ // The shapes the 2026-09-25 sweep found, each named by the surface it came from.
35
+ // A match is a line that attributes the verdict rule to 0162.
36
+ const MISATTRIBUTIONS = [
37
+ // 0162 glossed with the rule: "0162 (verdicts advisory, never self-executing)",
38
+ // "0162 (grader verdicts are advisory…)", "[ADR 0162] (patrol findings are advisory…)",
39
+ // "ADR 0162 — verdicts never self-execute", "[ADR 0162]: verdicts advisory".
40
+ /\b0162\b[\])]?\s*(?:\(|—|–|:|=)\s*(?:[\w-]+\s+){0,2}(?:verdicts?|findings?)\b[^\n]{0,30}?\b(?:advisory|self-execut)/i,
41
+ // The rule, then 0162 as its source: "findings are advisory (ADR 0162)",
42
+ // "never self-executes (ADR 0162)", "advisory per ADR 0162", "(advisory, ADR 0162)".
43
+ /\b(?:advisory|self-execut\w*)\b\**\s*(?:\(ADR\s+0162\b|,\s*ADR 0162\b|per ADR 0162\b)/i,
44
+ // 0162's real content, then the rule tacked on as if it were 0162's too:
45
+ // "[ADR 0162] — review gates retired on `main`; a verdict is advisory and never …".
46
+ /\b0162\b[^\n]{0,70};\s*an? verdicts? (?:is|are) advisory/i,
47
+ // The rule's consequences cited to 0162: "filed for adjudication (ADR 0162)",
48
+ // "describe-only (ADR 0162)", "describes and queues only, never confirms (ADR 0162".
49
+ /\b(?:adjudicat\w*|describe-only|describes and queues only)\b[^\n]{0,45}?\(ADR 0162\b/i,
50
+ // The rule as the sentence that follows a 0162 citation:
51
+ // "(ADR 0162).** Verdicts are advisory", "The ADR 0162 discipline.** Verdicts are advisory".
52
+ /\b0162\)?(?: discipline)?\.?\**\s*Verdicts? (?:is|are) advisory/i,
53
+ // "the same family of error ADR 0162 rules out" — 0162 rules out no verdict-handling.
54
+ /\bADR 0162 rules out\b/i,
55
+ // "ADVISORY ONLY, same posture as ADR 0162" / "ADR 0162: advisory only".
56
+ /\badvisory only\b[^\n]{0,30}\bADR 0162\b|\bADR 0162\b[^\n]{0,30}\badvisory only\b/i,
57
+ // "it stays advisory in precisely 0162's sense" — the sense is 0158 §2's.
58
+ /\badvisory in (?:precisely|exactly) (?:ADR )?0162's sense\b/i,
59
+ // "ADR 0162's "advisory, never self-executing" holding" (a migration comment).
60
+ /\b0162's\s+["“]?(?:advisory|never self-execut)/i,
61
+ ];
62
+
63
+ // Deliberately NOT corrected, each with its reason. Shrink-only: when one of these is
64
+ // fixed, delete its row — the last test below fails until you do, so a stale exemption
65
+ // cannot outlive the thing it excused.
66
+ const KNOWN_DEFERRED = new Map([
67
+ // A comment on a HARD-FLOOR gate surface (scripts/gds/gate-review.js HARD_FLOOR_GLOBS
68
+ // lists `.github/`): even a comment-only edit there escalates to the owner, which a
69
+ // citation fix does not justify on its own. Correct it with the next real edit.
70
+ ['.github/workflows/redteam-patrol.yml', 'hard-floor gate surface'],
71
+ // An applied migration is never edited: its text is part of what was run.
72
+ ['modules/government/migrations/government_004_board_permissions.sql', 'applied migration'],
73
+ ]);
74
+
75
+ // A session log records what a session believed at the time; rewriting one would
76
+ // falsify history, which is worse than the stale citation it carries.
77
+ const HISTORICAL = /^docs\/session-logs\//;
78
+ // This file quotes every shape it hunts, as examples — so it would flag itself.
79
+ const SELF = 'tests/adr_0162_citation.mjs';
80
+ const TEXT = /\.(?:md|js|mjs|cjs|json|ya?ml|sql|sh|html|txt)$/;
81
+
82
+ // A line that NAMES the slogan as its subject is discussing it (ADR 0241's own
83
+ // analysis; the task-title list in a relevance audit), not citing by it.
84
+ const DISCUSSES_SLOGAN = /paraphrase|shorthand/i;
85
+
86
+ // Markdown link targets carry the ADR's filename ("0162-review-gates-…"), which would
87
+ // otherwise sit between a citation and its gloss.
88
+ const stripLinkTargets = (s) => s.replace(/\]\([^)\s]*\)/g, ']');
89
+ // The continuation of a hard-wrapped line, minus its comment/quote/list lead.
90
+ const continuation = (s) => (s || '').replace(/^\s*(?:\/\/+|#+|--|\*|>|-)?\s*/, '');
91
+
92
+ function offendersIn(rel, text) {
93
+ const lines = stripLinkTargets(text).split('\n');
94
+ const hits = [];
95
+ for (let i = 0; i < lines.length; i++) {
96
+ const here = lines[i];
97
+ if (DISCUSSES_SLOGAN.test(here)) continue;
98
+ // Join the next line so a citation wrapped mid-phrase ("(ADR\n0162)") still reads
99
+ // as one; only a match that STARTS on this line is this line's.
100
+ const window = `${here} ${continuation(lines[i + 1])}`;
101
+ if (!/0162/.test(window)) continue;
102
+ for (const re of MISATTRIBUTIONS) {
103
+ const m = re.exec(window);
104
+ if (m && m.index < here.length) { hits.push(`${rel}:${i + 1}: ${here.trim().slice(0, 140)}`); break; }
105
+ }
106
+ }
107
+ return hits;
108
+ }
109
+
110
+ function trackedTextFiles() {
111
+ const r = spawnSync('git', ['ls-files', '-z'], { cwd: ROOT, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 });
112
+ if (r.status !== 0) return null; // a packaged core is not a git checkout
113
+ return r.stdout.split('\0').filter((f) => f && TEXT.test(f));
114
+ }
115
+
116
+ test('the premise: the verdict rule is written in ADR 0158 §2, and ADR 0162 never states it', () => {
117
+ const adr0158 = read(ADR_0158);
118
+ assert.match(adr0158, /^### 2\. A verdict is advisory and never self-executing$/m,
119
+ 'ADR 0158 §2 is the rule\'s source — if it moved, re-point the citations this test guards');
120
+ const adr0162 = read(ADR_0162);
121
+ assert.doesNotMatch(adr0162, /self-execut/i, 'ADR 0162 does not say "self-executing"');
122
+ assert.doesNotMatch(adr0162, /advisory/i, 'ADR 0162 does not say "advisory"');
123
+ // What 0162 DOES say, so a citation of it has something true to point at.
124
+ assert.match(adr0162, /Review requirements on `main` are \*\*retired permanently\*\*/);
125
+ });
126
+
127
+ test('the detector recognises every shape the sweep corrected, and passes the true citations', () => {
128
+ const flag = (line) => offendersIn('probe.md', line).length > 0;
129
+ for (const bad of [
130
+ '- **Related:** 0157 (Archon is rank and identity only), 0162 (verdicts advisory, never self-executing), 0172',
131
+ '0016 (authority resolved server-side), 0162 (grader verdicts are advisory, never self-executing), 0175',
132
+ '- **Related:** [ADR 0162](0162-review-gates-retired-rank-consistent-ci-only.md) (patrol findings are advisory — verdicts never self-execute),',
133
+ '// doctrine (ADR 0162 — verdicts never self-execute): findings are FILED as',
134
+ 'deliberate. Findings are **advisory** (ADR 0162) — they are filed for',
135
+ 'description: `Weekly deterministic red-team patrol (task 1003128; advisory per ADR 0162 — adjudicate)`',
136
+ '// is that: history-wide, time-based, filed for adjudication (ADR 0162).',
137
+ '- **Read-only, describe-only (ADR 0162).** No confirms, no regrades run by this skill.',
138
+ '- **Never confirm, never flip, never re-grade (ADR 0162).** Verdicts are advisory and never self-executing here',
139
+ '**1. The ADR 0162 discipline.** Verdicts are advisory and never self-executing. A gate that',
140
+ 'judgement is finished because a hash moved — the same family of error ADR 0162 rules out for grader',
141
+ '// HARD CONSTRAINT, same as goal-advisory.js and ADR 0162: advisory only. This',
142
+ 'it stays advisory in precisely 0162\'s sense.',
143
+ '- **Reconciles:** [ADR 0162](0162-review-gates-retired-rank-consistent-ci-only.md) — review gates retired on `main`; a verdict is advisory and never self-executing',
144
+ '→ files security reports (advisory, ADR 0162). FIRED BY .github/workflows/redteam-patrol.yml',
145
+ '-- — which is also ADR 0162\'s "advisory, never self-executing" holding.',
146
+ 'decision 2026-08-18: AI review deferred), and findings are **advisory** (ADR\n0162): they are POSTed to',
147
+ ]) assert.ok(flag(bad), `should flag: ${bad}`);
148
+
149
+ for (const good of [
150
+ '- **Reconciles:** [ADR 0162](0162-review-gates-retired-rank-consistent-ci-only.md) — review gates retired on `main`, so no human approval sits in the land path',
151
+ '0016 (authority resolved server-side), 0158 §2 (a verdict is advisory, never self-executing), 0162 (review gates retired — no human approval in the land path), 0175',
152
+ 'deliberate. Findings are **advisory** (ADR 0158 §2) — they are filed for',
153
+ '- **Related:** ADR 0162 (no new human gates on the ship path)',
154
+ 'No review requirement — gates are CI checks, not human approvals (ADR 0162).',
155
+ 'It never holds a builder\'s ship, which is what keeps it compatible with ADR 0162.',
156
+ 'This matters, because the shorthand it is usually cited by — "a verdict is advisory" (ADR 0162) — is a paraphrase',
157
+ ]) assert.ok(!flag(good), `should pass: ${good}`);
158
+ });
159
+
160
+ test('no tracked surface cites ADR 0162 for the verdict rule', (t) => {
161
+ const files = trackedTextFiles();
162
+ if (!files) return t.skip('not a git checkout — nothing tracked to scan');
163
+ const offenders = [];
164
+ for (const rel of files) {
165
+ if (rel === SELF || HISTORICAL.test(rel) || KNOWN_DEFERRED.has(rel)) continue;
166
+ let text;
167
+ try { text = readFileSync(path.join(ROOT, rel), 'utf8'); } catch { continue; }
168
+ if (!text.includes('0162')) continue;
169
+ offenders.push(...offendersIn(rel, text));
170
+ }
171
+ assert.deepEqual(offenders, [],
172
+ 'these cite ADR 0162 for "a verdict is advisory, never self-executing" — that rule is ADR 0158 §2. '
173
+ + 'Cite 0158 §2 for the verdict rule, and 0162 only for what it retired (human approval in the land path).');
174
+ });
175
+
176
+ test('every deferred exemption still has something to excuse (shrink-only)', (t) => {
177
+ if (!trackedTextFiles()) return t.skip('not a git checkout');
178
+ for (const [rel, why] of KNOWN_DEFERRED) {
179
+ let text = '';
180
+ try { text = read(rel); } catch { /* deleted: the exemption is stale too */ }
181
+ assert.ok(offendersIn(rel, text).length > 0,
182
+ `${rel} (${why}) no longer misattributes the rule — delete its KNOWN_DEFERRED row`);
183
+ }
184
+ });
@@ -600,3 +600,77 @@ test('the suite never reads the real heartbeat file', () => {
600
600
  // writing right now.
601
601
  assert.match(runner.heartbeatPath(), /ab-cadence-/, 'the suite must point at a throwaway path, not the real one');
602
602
  });
603
+
604
+ // --- a worker that never RAN is breakage, not quiet (task 1004179) ----------
605
+ //
606
+ // Found by the 1003907 proof run. With the Windows argv bug in play every worker
607
+ // exited in ~2s with "worker output was not JSON". Each run filed a blocker,
608
+ // parked a task, and was classed `quiet` — which neither trips nor forgives the
609
+ // failure count. consecutive_failures stayed 0, mode stayed `full`, and the
610
+ // runner would have worked through the whole allowlisted goal one task every
611
+ // five minutes, parking every one, while the hall showed it alive and allowed to
612
+ // work. A human stopped it after one task.
613
+
614
+ // The exact row the runner logged on the night this was found.
615
+ const brokenWorkerRow = {
616
+ event: 'worked',
617
+ task_id: '1004047',
618
+ outcome: 'worker_failed',
619
+ reason: 'worker output was not JSON',
620
+ duration_s: 2,
621
+ cost_usd: null,
622
+ verified: false,
623
+ ledger_shape: 'still_held',
624
+ claim_released: true,
625
+ task_parked: true,
626
+ };
627
+
628
+ test('the real broken-worker row is a FAILURE, not quiet', () => {
629
+ const c = cadence.classifyEvent(brokenWorkerRow);
630
+ assert.notEqual(c.class, 'quiet', 'this is the exact misclassification the task exists to fix');
631
+ assert.ok(cadence.COUNTS_AS_FAILURE.has(c.class), 'and it must advance the failure count');
632
+ assert.match(c.detail, /never ran/);
633
+ });
634
+
635
+ test('three broken workers drop the runner out of full speed', () => {
636
+ let state = cadence.START;
637
+ for (let i = 0; i < 3; i++) {
638
+ state = cadence.nextCadence(state, cadence.classifyEvent(brokenWorkerRow));
639
+ }
640
+ assert.equal(state.mode, 'probing',
641
+ 'the whole point: a runner that cannot spawn must stop chewing the queue');
642
+ });
643
+
644
+ test('a worker that RAN and hit a real obstacle is still quiet', () => {
645
+ // 1003564: 31 minutes, real cost, stopped at a grader refusal. The machine is
646
+ // fine and the next task is the right move — escalating this would "fix" a
647
+ // working runner into a broken one.
648
+ const c = cadence.classifyEvent({
649
+ event: 'worked', outcome: 'needs_human', reason: 'the grader refused the ship',
650
+ duration_s: 1888, cost_usd: 13.74, verified: false, ledger_shape: 'ungraded',
651
+ });
652
+ assert.equal(c.class, 'quiet');
653
+ });
654
+
655
+ test('a worker that burned real time and then failed is NOT "never ran"', () => {
656
+ // A 90-minute timeout is breakage of a different kind, and this predicate must
657
+ // not swallow it by calling it a failed launch.
658
+ assert.equal(cadence.workerNeverRan({ outcome: 'worker_failed', duration_s: 5400, cost_usd: 9.1 }), false);
659
+ });
660
+
661
+ test('a spawn error is "never ran" whatever the numbers say', () => {
662
+ assert.equal(cadence.workerNeverRan({ outcome: 'worker_failed', spawn_error: 'ENOENT' }), true);
663
+ });
664
+
665
+ test('a fast worker that still cost something is not called a failed launch', () => {
666
+ assert.equal(cadence.workerNeverRan({ outcome: 'worker_failed', duration_s: 4, cost_usd: 0.4 }), false,
667
+ 'it ran; both conditions are required');
668
+ });
669
+
670
+ test('unworkable_queue is still never escalated', () => {
671
+ // The sibling distinction: a queue where every task halts is a HEALTHY machine
672
+ // with nothing to do. This fix must not blur the two.
673
+ const c = cadence.classifyEvent({ event: 'nothing_claimable', skipped: [{ id: '1', title: 't', reasons: ['x'] }] });
674
+ assert.equal(c.class, 'unworkable_queue');
675
+ assert.equal(cadence.COUNTS_AS_FAILURE.has('unworkable_queue'), false);
676
+ });
@@ -654,3 +654,117 @@ test('workerEnv passes Claude auth through, and still refuses the rest', () => {
654
654
  assert.equal(env[leak], undefined, `${leak} must never reach a bypassPermissions worker`);
655
655
  }
656
656
  });
657
+ // --- a derived gauge buys BOUNDED work (task 1004178) -----------------------
658
+ //
659
+ // `derived` retires a reading whose window has reset. That is sound for the
660
+ // five-hour window, which turns over during a night. The SEVEN-day one does
661
+ // not, so its stale reading is dropped and the weekly pace check — what the
662
+ // module's notes call the thing that actually binds a heavy week — does not run
663
+ // at all on a derived reading. The task cap is what stands in for it, so the
664
+ // cap has to work, and it has to survive a restart: the count comes from the
665
+ // run log, not from memory.
666
+
667
+ import { mkdtempSync, writeFileSync } from 'node:fs';
668
+ import { tmpdir } from 'node:os';
669
+ import { join } from 'node:path';
670
+
671
+ const derivedLog = () => {
672
+ const dir = mkdtempSync(join(tmpdir(), 'autobongos-cap-'));
673
+ return join(dir, 'runs.jsonl');
674
+ };
675
+
676
+ test('countWorkedSince counts only worked rows at or after the mark', () => {
677
+ const p = derivedLog();
678
+ process.env.AUTOBONGOS_LOG_FILE = p;
679
+ const at = (s) => new Date(s * 1000).toISOString();
680
+ writeFileSync(p, [
681
+ JSON.stringify({ at: at(1000), event: 'worked', task_id: '1' }), // before the mark
682
+ JSON.stringify({ at: at(3000), event: 'hold' }), // not a worked row
683
+ JSON.stringify({ at: at(3100), event: 'worked', task_id: '2' }),
684
+ '{ this line is torn', // costs one row, not the count
685
+ JSON.stringify({ at: at(3200), event: 'worked', task_id: '3' }),
686
+ ].join('\n'));
687
+ assert.equal(runner.countWorkedSince(2000), 2);
688
+ assert.equal(runner.countWorkedSince(0), 0, 'a zero/absent mark counts nothing rather than everything');
689
+ delete process.env.AUTOBONGOS_LOG_FILE;
690
+ });
691
+
692
+ test('countWorkedSince treats an unreadable log as zero, not as a refusal', () => {
693
+ process.env.AUTOBONGOS_LOG_FILE = join(tmpdir(), 'autobongos-cap-nope', 'missing.jsonl');
694
+ assert.equal(runner.countWorkedSince(1), 0,
695
+ 'the fence and the gauge are the gates; a bound that bricks the runner when its own log is gone is worse');
696
+ delete process.env.AUTOBONGOS_LOG_FILE;
697
+ });
698
+
699
+ test('a derived gauge HOLDS once the cap is reached, and the reason says why', async () => {
700
+ const p = derivedLog();
701
+ process.env.AUTOBONGOS_LOG_FILE = p;
702
+ process.env.AUTOBONGOS_DERIVED_TASK_CAP = '2';
703
+ const at = (s) => new Date(s * 1000).toISOString();
704
+ writeFileSync(p, [
705
+ JSON.stringify({ at: at(5100), event: 'worked', task_id: '1' }),
706
+ JSON.stringify({ at: at(5200), event: 'worked', task_id: '2' }),
707
+ ].join('\n'));
708
+
709
+ const events = [];
710
+ await runner.iteration({ goals: [], maxTasks: 1 }, {
711
+ readFence: async () => ({ raw: { enabled: true, goals: [{ goal_id: 7 }] }, graderBypassed: false }),
712
+ gauge: () => ({ decision: { go: true, unknown: false, derived: true, derivedSince: 5000, reason: 'derived' } }),
713
+ pickTask: async () => { throw new Error('CAP BREACHED: reached the picker'); },
714
+ record: (row) => { events.push(row); return row; },
715
+ run: async () => { throw new Error('CAP BREACHED: ran a command'); },
716
+ spawnWorker: async () => { throw new Error('CAP BREACHED: spawned a worker'); },
717
+ });
718
+ assert.equal(events[0].event, 'hold');
719
+ assert.equal(events[0].derived, true);
720
+ assert.equal(events[0].worked_since_derived, 2);
721
+ assert.match(events[0].reason, /cap is 2/);
722
+ delete process.env.AUTOBONGOS_LOG_FILE; delete process.env.AUTOBONGOS_DERIVED_TASK_CAP;
723
+ });
724
+
725
+ test('a derived gauge still WORKS while under the cap', async () => {
726
+ const p = derivedLog();
727
+ process.env.AUTOBONGOS_LOG_FILE = p;
728
+ process.env.AUTOBONGOS_DERIVED_TASK_CAP = '2';
729
+ writeFileSync(p, JSON.stringify({ at: new Date(5100 * 1000).toISOString(), event: 'worked', task_id: '1' }));
730
+ let reachedPicker = false;
731
+ const events = [];
732
+ await runner.iteration({ goals: [], maxTasks: 1 }, {
733
+ readFence: async () => ({ raw: { enabled: true, goals: [{ goal_id: 7 }] }, graderBypassed: false }),
734
+ gauge: () => ({ decision: { go: true, unknown: false, derived: true, derivedSince: 5000, reason: 'derived' } }),
735
+ pickTask: async () => { reachedPicker = true; return { none: true, skipped: [] }; },
736
+ record: (row) => { events.push(row); return row; },
737
+ });
738
+ assert.equal(reachedPicker, true, 'one task done against a cap of two must still proceed');
739
+ delete process.env.AUTOBONGOS_LOG_FILE; delete process.env.AUTOBONGOS_DERIVED_TASK_CAP;
740
+ });
741
+
742
+ test('countWorkedSince is bounded: a huge log does not mean a huge scan', () => {
743
+ const p = derivedLog();
744
+ process.env.AUTOBONGOS_LOG_FILE = p;
745
+ const at = (s) => new Date(s * 1000).toISOString();
746
+ // 40k old rows, then the two that matter. A whole-file parse would touch
747
+ // every one of them on EVERY iteration of a loop that never exits.
748
+ const old = [];
749
+ for (let i = 0; i < 40000; i++) old.push(JSON.stringify({ at: at(1000 + i), event: 'worked', task_id: String(i) }));
750
+ old.push(JSON.stringify({ at: at(900000), event: 'worked', task_id: 'recent-1' }));
751
+ old.push(JSON.stringify({ at: at(900100), event: 'worked', task_id: 'recent-2' }));
752
+ writeFileSync(p, old.join('\n'));
753
+ const t0 = Date.now();
754
+ assert.equal(runner.countWorkedSince(800000), 2, 'only the rows at or after the mark count');
755
+ assert.ok(Date.now() - t0 < 500, 'and it must not walk the whole log to say so');
756
+ delete process.env.AUTOBONGOS_LOG_FILE;
757
+ });
758
+
759
+ test('countWorkedSince stops at the first row older than the mark', () => {
760
+ const p = derivedLog();
761
+ process.env.AUTOBONGOS_LOG_FILE = p;
762
+ const at = (s) => new Date(s * 1000).toISOString();
763
+ writeFileSync(p, [
764
+ JSON.stringify({ at: at(100), event: 'worked', task_id: 'ancient' }),
765
+ JSON.stringify({ at: at(5000), event: 'worked', task_id: 'before-mark' }),
766
+ JSON.stringify({ at: at(9000), event: 'worked', task_id: 'after-mark' }),
767
+ ].join('\n'));
768
+ assert.equal(runner.countWorkedSince(8000), 1);
769
+ delete process.env.AUTOBONGOS_LOG_FILE;
770
+ });
@@ -342,3 +342,59 @@ test('evaluate on a missing file is UNKNOWN — the CLI never defaults to go', (
342
342
  assert.equal(d.unknown, true);
343
343
  });
344
344
  });
345
+
346
+ // --- a reading whose window has RESET is retired, not stale (task 1004178) ---
347
+ //
348
+ // An unattended host cannot refresh the gauge (see modules/autonomy/CLAUDE.md
349
+ // for the evidence). These pin the behaviour that makes it survivable.
350
+
351
+ test('an aged-out reading whose window has RESET is derived, not unknown', () => {
352
+ const now = 100000;
353
+ const g = {
354
+ written_at: now - 10 * HOUR,
355
+ five_hour: { used_percentage: 92, resets_at: now - 2 * HOUR, observed_at: now - 10 * HOUR },
356
+ };
357
+ const d = decideCapacity({ gauge: g, now });
358
+ assert.equal(d.go, true, 'the window it described has ended; 92% is no longer a fact about now');
359
+ assert.equal(d.unknown, false);
360
+ assert.equal(d.derived, true, 'and it must SAY so — the caller bounds work on a derived reading');
361
+ assert.equal(d.derivedSince, now - 2 * HOUR, 'the reset it stands on, so the cap has a start');
362
+ });
363
+
364
+ test('an aged-out reading whose window is STILL RUNNING stays unknown', () => {
365
+ const now = 100000;
366
+ const g = {
367
+ written_at: now - 10 * HOUR,
368
+ five_hour: { used_percentage: 92, resets_at: now + HOUR, observed_at: now - 10 * HOUR },
369
+ };
370
+ const d = decideCapacity({ gauge: g, now });
371
+ assert.equal(d.go, false, 'usage only GROWS inside a window, so an old 92% is a floor, never headroom');
372
+ assert.equal(d.unknown, true);
373
+ });
374
+
375
+ test('a fresh reading is unchanged and is never marked derived', () => {
376
+ const now = 100000;
377
+ const g = {
378
+ written_at: now - 60,
379
+ five_hour: { used_percentage: 10, resets_at: now + HOUR, observed_at: now - 60 },
380
+ seven_day: { used_percentage: 5, resets_at: now + 6 * 24 * HOUR, observed_at: now - 60 },
381
+ };
382
+ const d = decideCapacity({ gauge: g, now });
383
+ assert.equal(d.go, true);
384
+ assert.equal(d.derived, false);
385
+ assert.equal(d.derivedSince, null);
386
+ });
387
+
388
+ test('a derived seven-day window is NOT paced against a boundary we invented', () => {
389
+ const now = 100000;
390
+ // 90% used and only just past its reset: paced, this would refuse. But the
391
+ // boundary is inferred, so pacing it measures progress through a week nobody
392
+ // observed. Skipped rather than faked.
393
+ const g = {
394
+ written_at: now - 10 * HOUR,
395
+ seven_day: { used_percentage: 90, resets_at: now - HOUR, observed_at: now - 10 * HOUR },
396
+ };
397
+ const d = decideCapacity({ gauge: g, now });
398
+ assert.equal(d.derived, true);
399
+ assert.equal(d.go, true);
400
+ });