@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.
- package/.bongos-core.json +322 -117
- package/.claude/skills/builder-start/SKILL.md +1 -1
- package/.claude/skills/grade-audit/SKILL.md +1 -1
- package/.claude/skills/grade-recover/SKILL.md +2 -1
- package/.claude/skills/grader-health/SKILL.md +5 -1
- package/.claude/skills/merge-mode/SKILL.md +1 -1
- package/docs/adr/0089-modular-multi-model-adversarial-grader.md +15 -0
- package/docs/adr/0175-the-board-room-full-idea-ratification.md +3 -2
- package/docs/adr/0178-the-copy-desk-flag-and-queue.md +3 -3
- package/docs/adr/0188-patrol-runs-on-ci-not-the-server.md +2 -2
- package/docs/adr/0189-baseline-floors-and-the-freeze-detector.md +1 -1
- package/docs/adr/0233-a-copy-proposal-is-a-task-carrying-a-patch.md +1 -1
- package/docs/adr/0241-the-artist-gate-is-a-per-project-deploy-gate-that-reads-a-state.md +11 -3
- package/docs/adr/README.md +4 -4
- package/docs/api/openapi.json +1 -1
- package/docs/architecture.md +4 -3
- package/docs/canonical-permissions.md +2 -2
- package/docs/copy-inventory.md +34 -30
- package/docs/copy-registry.json +69 -33
- package/docs/file-map.md +4 -3
- package/docs/module-api-changelog.md +4 -0
- package/docs/recipes/autobongos-windows-host.md +3 -1
- package/docs/recipes/local-dev.md +45 -3
- package/docs/recipes/packaging-the-core.md +3 -1
- package/docs/recipes/redteam-patrol.md +2 -2
- package/docs/recipes/upgrading-the-core.md +1 -1
- package/modules/autonomy/cadence.js +52 -0
- package/modules/autonomy/gauge.js +57 -9
- package/modules/government/protected-surfaces.json +1 -0
- package/modules/grading/grade-engine.js +267 -0
- package/modules/grading/grader-subagent.js +4 -3
- package/modules/grading/grader-workers/index.js +67 -11
- package/modules/grading/grader-workers/worker-verdict.js +32 -2
- package/modules/grading/grader.js +109 -88
- package/modules/hall-ui/public/deploy.js +35 -0
- package/modules/hall-ui/public/project-settings.css +7 -0
- package/modules/hall-ui/records/builders-roster.md +10 -0
- package/modules/hall-ui/records/catalog.md +10 -0
- package/modules/hall-ui/records/collaboration.md +14 -0
- package/modules/hall-ui/records/goal-dependency-forest.md +11 -0
- package/modules/hall-ui/records/oversight-family.md +19 -0
- package/modules/hall-ui/records/panel-family.md +19 -0
- package/modules/hall-ui/records/reading-rooms.md +12 -0
- package/modules/hall-ui/records/record-family.md +13 -0
- package/modules/hall-ui/records/reference-room.md +9 -0
- package/modules/hall-ui/records/roster-and-ledger.md +9 -0
- package/modules/hall-ui/records/sky.md +23 -0
- package/modules/lifecycle/dependency-advisory.js +2 -1
- package/modules/lifecycle/routes/tasks.js +19 -4
- package/modules/lifecycle/ship-preflight.js +91 -0
- package/modules/provisioning/control-plane-guard.js +14 -13
- package/modules/provisioning/core-upgrade.js +30 -17
- package/modules/provisioning/public-refusal.js +44 -0
- package/modules/provisioning/routes/disconnect.js +2 -1
- package/modules/provisioning/routes/env-manifest.js +5 -4
- package/modules/provisioning/routes/provisioning.js +16 -15
- package/modules/provisioning/routes/repo-private.js +2 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +7481 -0
- package/scripts/gds/address-park-sweep.sh +3 -1
- package/scripts/gds/autobongos-run.js +91 -3
- package/scripts/gds/backfill-cost.js +20 -9
- package/scripts/gds/baseline-staleness.js +1 -1
- package/scripts/gds/claim.js +2 -2
- package/scripts/gds/credential-presence.js +281 -0
- package/scripts/gds/db-backup-nightly.sh +9 -4
- package/scripts/gds/db-backup-offsite.sh +4 -2
- package/scripts/gds/deploy-main-audit-gate.sh +11 -4
- package/scripts/gds/dev-lib.js +212 -5
- package/scripts/gds/dev.js +102 -50
- package/scripts/gds/doctor.js +3 -2
- package/scripts/gds/grade-correlation-audit.js +280 -0
- package/scripts/gds/grade-replay.js +237 -0
- package/scripts/gds/hooks/pre-push.js +7 -3
- package/scripts/gds/main-audit.js +19 -10
- package/scripts/gds/memory-map.js +3 -1
- package/scripts/gds/onboarding-config.js +3 -2
- package/scripts/gds/package-core.js +72 -1
- package/scripts/gds/plain-cards.js +4 -0
- package/scripts/gds/preflight.js +12 -4
- package/scripts/gds/provision-core-upgrade.js +282 -80
- package/scripts/gds/provision-repo-auth.js +15 -0
- package/scripts/gds/provision.js +1 -1
- package/scripts/gds/publish-credential-check.js +3 -0
- package/scripts/gds/push-main.js +14 -3
- package/scripts/gds/push-path-brief.js +3 -2
- package/scripts/gds/redteam-patrol.js +4 -4
- package/scripts/gds/release-notes.js +271 -0
- package/scripts/gds/run-routine.js +18 -5
- package/scripts/gds/run-unit-tests.js +14 -3
- package/scripts/gds/seed-board-room-tasks.js +1 -1
- package/scripts/gds/session-cost.js +12 -3
- package/scripts/gds/ship-deploy-target.js +11 -6
- package/scripts/gds/ship-flow.js +5 -3
- package/scripts/gds/ship-grade-steps.js +33 -0
- package/scripts/gds/ship-land.js +241 -16
- package/scripts/gds/ship-merge.js +3 -2
- package/scripts/gds/ship-preflight-steps.js +86 -11
- package/scripts/gds/ship.js +1 -1
- package/scripts/gds/stray-unit-sweep.sh +3 -1
- package/scripts/gds/upgrade-migrate.js +67 -0
- package/scripts/gds/upgrade.js +9 -7
- package/scripts/hall-preview/README.md +20 -3
- package/scripts/hall-preview/live-identity.js +1 -1
- package/scripts/hall-preview/refresh-fixtures.js +71 -11
- package/scripts/hall-preview/scrub-identity.js +296 -0
- package/src/bongos/routes/security.js +3 -3
- package/src/build-info.js +4 -2
- package/src/module-api.js +1 -1
- package/tests/adr_0089_scope.mjs +70 -0
- package/tests/adr_0162_citation.mjs +184 -0
- package/tests/autobongos_cadence.mjs +74 -0
- package/tests/autobongos_loop.mjs +114 -0
- package/tests/autonomy_gauge.mjs +56 -0
- package/tests/consumer_layout_boot.mjs +363 -31
- package/tests/core_upgrade_door.mjs +40 -6
- package/tests/core_upgrade_runner.mjs +329 -5
- package/tests/credential_presence.mjs +153 -0
- package/tests/db_backup_nightly.mjs +21 -3
- package/tests/db_backup_offsite.mjs +81 -0
- package/tests/deploy_audit_gate.mjs +5 -2
- package/tests/deploy_page_projects.mjs +63 -2
- package/tests/dev_lib.mjs +2 -1
- package/tests/dev_windows.mjs +324 -0
- package/tests/env_knobs_neutral_prefix.mjs +203 -0
- package/tests/fixtures/grade-replay/recorded-panel-grades.json +27 -0
- package/tests/government_protected_surfaces.mjs +10 -0
- package/tests/grade_correlation_audit.mjs +159 -0
- package/tests/grade_engine.mjs +249 -0
- package/tests/grade_engine_replay.mjs +99 -0
- package/tests/grader_prompt_window_bound.mjs +132 -0
- package/tests/grader_unbacked_score.mjs +194 -0
- package/tests/hall_preview_scrub.mjs +214 -0
- package/tests/hall_records.mjs +85 -0
- package/tests/helpers.mjs +25 -0
- package/tests/instance_db_migrated_on_code_move.mjs +8 -1
- package/tests/integration_parked_ratchet.mjs +24 -1
- package/tests/plain_cards.mjs +1 -1
- package/tests/provisioning_public_refusal.mjs +195 -0
- package/tests/publish_status_branch.mjs +128 -0
- package/tests/redteam_patrol.mjs +1 -1
- package/tests/release_notes.mjs +327 -0
- package/tests/shell_stat_portability.mjs +49 -0
- package/tests/ship_land_conflict.mjs +181 -0
- package/tests/ship_land_red_pr.mjs +236 -0
- package/tests/ship_preflight.mjs +226 -3
- package/tests/start_env_guard.mjs +16 -0
- package/tests/sweep_app_account.mjs +15 -2
- package/tests/upgrade.mjs +20 -1
- package/tests/upgrade_migrate.mjs +195 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
// tests/env_knobs_neutral_prefix.mjs — the operational env knobs answer to the
|
|
2
|
+
// instance's own prefix, not only to OTB_ (task 1001441, the R61 follow-up).
|
|
3
|
+
//
|
|
4
|
+
// R61 (task 1200) put the config dir and the auth/API-base env vars behind
|
|
5
|
+
// src/instance-config.js resolveEnv(), and task 1003703 made BONGOS the canonical
|
|
6
|
+
// prefix. The operational knobs below still read `process.env.OTB_*` directly, so a
|
|
7
|
+
// fresh instance setting BONGOS_DEPLOY_MODE (or any knob here) was silently ignored.
|
|
8
|
+
// Each test sets ONLY the canonical `<PREFIX>_<KNOB>` spelling — the prefix is
|
|
9
|
+
// read from the branding contract, never assumed — and asserts the reader obeys
|
|
10
|
+
// it. Every one of them fails against the direct OTB_ reads this task replaced.
|
|
11
|
+
// The legacy OTB_ spellings keep working through resolveEnv's warned fallback;
|
|
12
|
+
// the existing tests that set them (pre_push_hook, preflight_checks,
|
|
13
|
+
// push_path_brief, ship_broadcast_server, autobongos_loop) are that proof.
|
|
14
|
+
//
|
|
15
|
+
// SCOPE, knob by knob (the task named ~17):
|
|
16
|
+
// converted here — DEPLOY_MODE, PUSH_VIA_SERVER, ALLOW_MAIN_PUSH (reader AND
|
|
17
|
+
// push-main.js's writer), CLAUDE_BIN (grader, preflight/doctor, the Autobongos
|
|
18
|
+
// worker and its env allowlist), COST_CRON_ROUTINE (run-routine.js's writer AND
|
|
19
|
+
// session-cost.js's reader), COST_LEDGER, GIT_SHA, MEMORY_DIR,
|
|
20
|
+
// ONBOARDING_WINDOW_DAYS, ROUTINE_ALERT_CHANNEL, and AUDIT_RANGE. AUDIT_RANGE's
|
|
21
|
+
// two writers are a bash deploy gate and a CI workflow, which cannot compute the
|
|
22
|
+
// branded prefix — so they now hand the range to main-audit.js as --range, and
|
|
23
|
+
// only an operator's own override goes through the env knob. Left as an env
|
|
24
|
+
// handoff, every deploy and CI audit would print a deprecation warning about a
|
|
25
|
+
// name no operator sets (pinned in tests/deploy_audit_gate.mjs).
|
|
26
|
+
// already done before this task — the ACTOR, PUBLIC_ORIGIN, SECURITY_ADR_PATH,
|
|
27
|
+
// STALE_CLAIM_HOURS and STATUS_DEFAULT_VERSION knobs, whose readers went through
|
|
28
|
+
// resolveEnv in task 1003703 and its predecessors (only their operator-facing
|
|
29
|
+
// text still named the old spelling, fixed here).
|
|
30
|
+
// gone — OTB_PREVIEW_STAMP has no reader or writer left anywhere.
|
|
31
|
+
//
|
|
32
|
+
// Run: node --test tests/env_knobs_neutral_prefix.mjs
|
|
33
|
+
|
|
34
|
+
import { test } from 'node:test';
|
|
35
|
+
import assert from 'node:assert/strict';
|
|
36
|
+
import { spawnSync } from 'node:child_process';
|
|
37
|
+
import { createRequire } from 'node:module';
|
|
38
|
+
import fs from 'node:fs';
|
|
39
|
+
import os from 'node:os';
|
|
40
|
+
import path from 'node:path';
|
|
41
|
+
import { fileURLToPath } from 'node:url';
|
|
42
|
+
|
|
43
|
+
const require = createRequire(import.meta.url);
|
|
44
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
45
|
+
const { envPrefix, LEGACY_ENV_PREFIXES } = require('../src/instance-config.js');
|
|
46
|
+
|
|
47
|
+
const PREFIX = envPrefix();
|
|
48
|
+
const canonical = (knob) => `${PREFIX}_${knob}`;
|
|
49
|
+
|
|
50
|
+
// Every spelling of a knob, so a test can clear the ones it is not about — a
|
|
51
|
+
// developer's shell exporting OTB_DEPLOY_MODE must not decide these results.
|
|
52
|
+
const allSpellings = (knob) => [...new Set([PREFIX, ...LEGACY_ENV_PREFIXES])].map((p) => `${p}_${knob}`);
|
|
53
|
+
function envWith(knob, value, base = process.env) {
|
|
54
|
+
const env = { ...base };
|
|
55
|
+
for (const name of allSpellings(knob)) delete env[name];
|
|
56
|
+
env[canonical(knob)] = value;
|
|
57
|
+
return env;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Run a snippet in a fresh node, so module-level reads (and the per-process
|
|
61
|
+
// deprecation-warning ledger) start clean.
|
|
62
|
+
function runNode(code, env) {
|
|
63
|
+
const r = spawnSync(process.execPath, ['-e', code], { cwd: ROOT, env, encoding: 'utf8' });
|
|
64
|
+
assert.equal(r.status, 0, `child failed: ${r.stderr}`);
|
|
65
|
+
return r.stdout.trim();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// A PATH with nothing on it: any `gh` / `claude` probe fails instantly and
|
|
69
|
+
// deterministically, so an answer can only have come from the env override.
|
|
70
|
+
const EMPTY_BIN = fs.mkdtempSync(path.join(os.tmpdir(), 'knobs-nobin-'));
|
|
71
|
+
test.after(() => fs.rmSync(EMPTY_BIN, { recursive: true, force: true }));
|
|
72
|
+
|
|
73
|
+
test('DEPLOY_MODE: <PREFIX>_DEPLOY_MODE pins the mode, with no gh probe', () => {
|
|
74
|
+
// 'laptop' from the env is honoured as-is. Ignored, the same run would probe gh
|
|
75
|
+
// (absent here), find no credential and fall back to 'ci'.
|
|
76
|
+
const out = runNode(
|
|
77
|
+
"console.log(require('./scripts/gds/ship-deploy-target.js').resolveDeployMode())",
|
|
78
|
+
{ ...envWith('DEPLOY_MODE', 'laptop'), PATH: EMPTY_BIN },
|
|
79
|
+
);
|
|
80
|
+
assert.equal(out.split('\n').pop(), 'laptop');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('PUSH_VIA_SERVER: <PREFIX>_PUSH_VIA_SERVER=0 forces the local push path', () => {
|
|
84
|
+
const out = runNode(
|
|
85
|
+
"console.log(require('./scripts/gds/ship-deploy-target.js').pushVia())",
|
|
86
|
+
{ ...envWith('PUSH_VIA_SERVER', '0'), PATH: EMPTY_BIN },
|
|
87
|
+
);
|
|
88
|
+
assert.equal(out, 'local', 'with no gh on PATH, only the override can answer local');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('ALLOW_MAIN_PUSH: push-main.js writes the canonical spelling, and the hook obeys it', () => {
|
|
92
|
+
const { pushEnv } = require('../scripts/gds/push-main.js');
|
|
93
|
+
const env = pushEnv({ PATH: process.env.PATH, HOME: process.env.HOME });
|
|
94
|
+
assert.equal(env[canonical('ALLOW_MAIN_PUSH')], '1', 'the writer uses this instance\'s own prefix');
|
|
95
|
+
|
|
96
|
+
const hook = path.join(ROOT, 'scripts', 'gds', 'hooks', 'pre-push.js');
|
|
97
|
+
const sha = 'a'.repeat(40);
|
|
98
|
+
const push = (e) => spawnSync(process.execPath, [hook, 'origin', 'https://example.invalid/repo.git'], {
|
|
99
|
+
cwd: ROOT, input: `refs/heads/main ${sha} refs/heads/main ${sha}\n`, encoding: 'utf8', env: e,
|
|
100
|
+
});
|
|
101
|
+
const cleared = { ...process.env };
|
|
102
|
+
for (const name of allSpellings('ALLOW_MAIN_PUSH')) delete cleared[name];
|
|
103
|
+
assert.equal(push(cleared).status, 13, 'without the override a main push is blocked (exit 13)');
|
|
104
|
+
assert.equal(push({ ...cleared, ...pushEnv({}) }).status, 0, 'the canonical override lets the pipeline push');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('CLAUDE_BIN: the preflight probe, the Autobongos worker and its env allowlist', () => {
|
|
108
|
+
const { preflightChecks } = require('../scripts/gds/preflight.js');
|
|
109
|
+
let seen = null;
|
|
110
|
+
const spy = (bin) => { seen = bin; return { status: 0, stdout: 'x\n' }; };
|
|
111
|
+
const fsOk = { existsSync: () => true };
|
|
112
|
+
const r = preflightChecks({ nodeVersion: 'v22.0.0', fs: fsOk, spawn: spy, env: { [canonical('CLAUDE_BIN')]: '/opt/claude' } });
|
|
113
|
+
assert.equal(seen, '/opt/claude');
|
|
114
|
+
const row = r.find((x) => x.title === 'claude CLI');
|
|
115
|
+
assert.match(row.detail, new RegExp(`via ${canonical('CLAUDE_BIN')}`), 'the label names the spelling that supplied it');
|
|
116
|
+
|
|
117
|
+
const runner = require('../scripts/gds/autobongos-run.js');
|
|
118
|
+
assert.equal(runner.resolveWorkerBin({ [canonical('CLAUDE_BIN')]: '/x/claude' }, 'linux').bin, '/x/claude');
|
|
119
|
+
const wenv = runner.workerEnv({ [canonical('CLAUDE_BIN')]: '/x/claude', DATABASE_URL: 'postgres://secret' });
|
|
120
|
+
assert.equal(wenv[canonical('CLAUDE_BIN')], '/x/claude', 'the worker receives the canonical spelling');
|
|
121
|
+
assert.equal(wenv.DATABASE_URL, undefined, 'the allowlist still refuses everything else');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test('CLAUDE_BIN: the grader spawns the binary <PREFIX>_CLAUDE_BIN names', async () => {
|
|
125
|
+
const { runSubagent } = require('../modules/grading/grader-subagent.js');
|
|
126
|
+
const probe = path.join(EMPTY_BIN, 'claude-probe-does-not-exist');
|
|
127
|
+
const saved = { ...process.env };
|
|
128
|
+
try {
|
|
129
|
+
for (const name of [...allSpellings('CLAUDE_BIN'), 'CLAUDE_BIN']) delete process.env[name];
|
|
130
|
+
process.env[canonical('CLAUDE_BIN')] = probe;
|
|
131
|
+
// An empty PATH as well: if the override were ignored, a bare `claude` must fail
|
|
132
|
+
// to resolve rather than launch a real, paid session.
|
|
133
|
+
process.env.PATH = EMPTY_BIN;
|
|
134
|
+
await assert.rejects(runSubagent({ prompt: 'x', opts: { timeoutMs: 5000 } }), (err) => {
|
|
135
|
+
assert.equal(err.code, 'SUBAGENT_SPAWN_FAILED');
|
|
136
|
+
assert.ok(err.message.includes(probe), `spawned the override, not a PATH lookup: ${err.message}`);
|
|
137
|
+
return true;
|
|
138
|
+
});
|
|
139
|
+
} finally {
|
|
140
|
+
for (const k of Object.keys(process.env)) if (!(k in saved)) delete process.env[k];
|
|
141
|
+
Object.assign(process.env, saved);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('COST_CRON_ROUTINE: run-routine.js writes the canonical name and session-cost.js reads it', () => {
|
|
146
|
+
const { stepEnv } = require('../scripts/gds/run-routine.js');
|
|
147
|
+
const { cronRoutineFromEnv } = require('../scripts/gds/session-cost.js');
|
|
148
|
+
const env = stepEnv('bfg', {});
|
|
149
|
+
assert.equal(env[canonical('COST_CRON_ROUTINE')], 'bfg', 'the writer uses this instance\'s own prefix');
|
|
150
|
+
assert.equal(cronRoutineFromEnv(env), 'bfg', 'the reader finds what the writer wrote');
|
|
151
|
+
assert.equal(cronRoutineFromEnv({ [canonical('COST_CRON_ROUTINE')]: ' nightly ' }), 'nightly');
|
|
152
|
+
assert.equal(cronRoutineFromEnv({}), '');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test('ROUTINE_ALERT_CHANNEL: <PREFIX>_ROUTINE_ALERT_CHANNEL picks the alert channel', () => {
|
|
156
|
+
const { alertChannel } = require('../scripts/gds/run-routine.js');
|
|
157
|
+
assert.equal(alertChannel({ [canonical('ROUTINE_ALERT_CHANNEL')]: 'ops' }), 'ops');
|
|
158
|
+
assert.equal(alertChannel({}), 'ship_feed');
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test('COST_LEDGER: <PREFIX>_COST_LEDGER is the first place a ledger is looked for', () => {
|
|
162
|
+
const { knownLedgerPaths } = require('../scripts/gds/backfill-cost.js');
|
|
163
|
+
assert.equal(knownLedgerPaths({ [canonical('COST_LEDGER')]: '/data/cost_ledger.jsonl' })[0], '/data/cost_ledger.jsonl');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test('GIT_SHA: <PREFIX>_GIT_SHA is the deployed commit /version reports', () => {
|
|
167
|
+
const sha = 'abcdef0123456789abcdef0123456789abcdef01';
|
|
168
|
+
const out = JSON.parse(runNode(
|
|
169
|
+
"console.log(JSON.stringify(require('./src/build-info.js').buildInfo()))",
|
|
170
|
+
envWith('GIT_SHA', sha),
|
|
171
|
+
));
|
|
172
|
+
assert.equal(out.commit, sha);
|
|
173
|
+
assert.equal(out.resolvedVia, 'env');
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test('MEMORY_DIR: <PREFIX>_MEMORY_DIR is where memory-map.js reads MEMORY.md', () => {
|
|
177
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'knobs-memory-'));
|
|
178
|
+
try {
|
|
179
|
+
fs.writeFileSync(path.join(dir, 'MEMORY.md'), '# Memory index\n\n- [a note](a.md) — one line\n');
|
|
180
|
+
const r = spawnSync(process.execPath, ['scripts/gds/memory-map.js'], { cwd: ROOT, env: envWith('MEMORY_DIR', dir), encoding: 'utf8' });
|
|
181
|
+
assert.equal(r.status, 0, r.stderr);
|
|
182
|
+
assert.match(r.stdout, /^map: \d+ bytes/m, 'it measured the map in that directory (ignored, it prints usage and exits 2)');
|
|
183
|
+
} finally {
|
|
184
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test('AUDIT_RANGE: <PREFIX>_AUDIT_RANGE is the range main-audit.js audits, and --range still wins', () => {
|
|
189
|
+
const { requestedRange } = require('../scripts/gds/main-audit.js');
|
|
190
|
+
assert.equal(requestedRange(undefined, { [canonical('AUDIT_RANGE')]: 'abc123..HEAD' }), 'abc123..HEAD');
|
|
191
|
+
assert.equal(requestedRange('def456..HEAD', { [canonical('AUDIT_RANGE')]: 'abc123..HEAD' }), 'def456..HEAD',
|
|
192
|
+
'the flag the deploy gate and CI pass beats any env value');
|
|
193
|
+
assert.equal(requestedRange(true, {}), null, 'a bare --range with no value falls back to the default window');
|
|
194
|
+
assert.equal(requestedRange(undefined, {}), null);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test('ONBOARDING_WINDOW_DAYS: <PREFIX>_ONBOARDING_WINDOW_DAYS sets the window', () => {
|
|
198
|
+
const out = runNode(
|
|
199
|
+
"console.log(require('./scripts/gds/onboarding-config.js').windowDays())",
|
|
200
|
+
envWith('ONBOARDING_WINDOW_DAYS', '3'),
|
|
201
|
+
);
|
|
202
|
+
assert.equal(out, '3');
|
|
203
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{"note":"Recorded panel grades for the differential replay (tests/grade_engine_replay.mjs). Generated by scripts/gds/grade-replay.js --live --write-fixture: only grades whose replay agreed with the stored row, one per panel shape. Issue summaries, their files and stderr are hashed.",
|
|
2
|
+
"grades":[
|
|
3
|
+
{"task_id":"1004274","graded_at":"2026-09-25T21:46:43.130Z","recorded":{"panel_outcome":"pass","pass_count":4,"concerns_count":0,"fail_count":0,"error_count":0,"worker_count":4,"panel_cost_usd":3.0816240000000006,"confidence":0.8,"rubric":{"functional_fidelity":9,"code_quality":8,"economy":8,"security_score":9,"efficiency_score":8}},"workers":[{"kind":"narc","verdict":"pass","error":null,"scores":{"functional_fidelity":9},"confidence":0.75,"model":"claude-sonnet-5","cost_usd":0.433313,"retried":false,"issues":[]},{"kind":"quality","verdict":"pass","error":null,"scores":{"economy":8,"code_quality":8},"confidence":0.85,"model":"claude-sonnet-5","cost_usd":1.7134175000000003,"retried":false,"issues":[{"severity":"nit","file":"h:127350d220","summary":"h:3305acb211"}]},{"kind":"hacker","verdict":"pass","error":null,"scores":{"security_score":9},"confidence":0.8,"model":"claude-sonnet-5","cost_usd":0.493828,"retried":false,"issues":[{"severity":"nit","file":"h:a00915aecd","summary":"h:bd972ce028"}]},{"kind":"efficiency","verdict":"pass","error":null,"scores":{"efficiency_score":8},"confidence":0.8,"model":"claude-sonnet-5","cost_usd":0.4410655,"retried":false,"issues":[{"severity":"minor","file":"h:127350d220","summary":"h:e96dc885ad"}]}]},
|
|
4
|
+
{"task_id":"1001436","graded_at":"2026-09-25T21:40:47.504Z","recorded":{"panel_outcome":"pass","pass_count":4,"concerns_count":0,"fail_count":0,"error_count":0,"worker_count":4,"panel_cost_usd":2.76109025,"confidence":0.825,"rubric":{"functional_fidelity":10,"code_quality":8,"economy":6,"security_score":9,"efficiency_score":8}},"workers":[{"kind":"narc","verdict":"pass","error":null,"scores":{"functional_fidelity":10},"confidence":0.9,"model":"claude-sonnet-5","cost_usd":0.43844425,"retried":true,"issues":[]},{"kind":"quality","verdict":"pass","error":null,"scores":{"economy":6,"code_quality":8},"confidence":0.8,"model":"claude-sonnet-5","cost_usd":1.46089875,"retried":false,"issues":[{"severity":"minor","file":"h:d3eb82782a","summary":"h:35c1a97948"},{"severity":"minor","file":"h:d3eb82782a","summary":"h:ec38cb5123"},{"severity":"nit","file":"h:9b13fff019","summary":"h:c7a56db356"}]},{"kind":"hacker","verdict":"pass","error":null,"scores":{"security_score":9},"confidence":0.85,"model":"claude-sonnet-5","cost_usd":0.40146299999999996,"retried":false,"issues":[]},{"kind":"efficiency","verdict":"pass","error":null,"scores":{"efficiency_score":8},"confidence":0.75,"model":"claude-sonnet-5","cost_usd":0.46028425,"retried":false,"issues":[{"severity":"minor","file":"h:d3eb82782a","summary":"h:0ba7cc5a2a"}]}]},
|
|
5
|
+
{"task_id":"1003353","graded_at":"2026-09-25T20:42:26.827Z","recorded":{"panel_outcome":"pass","pass_count":3,"concerns_count":1,"fail_count":0,"error_count":0,"worker_count":4,"panel_cost_usd":3.6595056,"confidence":0.7849999999999999,"rubric":{"functional_fidelity":7,"code_quality":8,"economy":8,"security_score":9,"efficiency_score":10}},"workers":[{"kind":"narc","verdict":"concerns","error":null,"scores":{"functional_fidelity":7},"confidence":0.6,"model":"claude-sonnet-5","cost_usd":0.766103,"retried":false,"issues":[{"severity":"minor","file":"h:63ee851ae2","summary":"h:6c36c72067"},{"severity":"minor","file":"h:411c4cd89b","summary":"h:165ac64e1e"}]},{"kind":"quality","verdict":"pass","error":null,"scores":{"economy":8,"code_quality":8},"confidence":0.72,"model":"claude-sonnet-5","cost_usd":1.1870192,"retried":false,"issues":[{"severity":"minor","file":"h:63ee851ae2","summary":"h:1ec56d00d6"},{"severity":"minor","file":"h:63ee851ae2","summary":"h:e80eb42770"},{"severity":"nit","file":"h:63ee851ae2","summary":"h:ead9e52ba9"},{"severity":"nit","file":"h:503a1e4a26","summary":"h:a8aebc6618"},{"severity":"nit","file":"h:c1df437481","summary":"h:67c5f63451"}]},{"kind":"hacker","verdict":"pass","error":null,"scores":{"security_score":9},"confidence":0.85,"model":"claude-sonnet-5","cost_usd":0.73215,"retried":false,"issues":[{"severity":"nit","file":"h:f4a2e1bea9","summary":"h:06a8e88ec8"}]},{"kind":"efficiency","verdict":"pass","error":null,"scores":{"efficiency_score":10},"confidence":0.92,"model":"claude-sonnet-5","cost_usd":0.3183964,"retried":false,"issues":[]},{"kind":"reader","verdict":"advisory","error":null,"scores":{"copy_score":8},"confidence":0.7,"model":"claude-sonnet-5","cost_usd":0.655837,"retried":false,"issues":[{"severity":"nit","file":"h:f4a2e1bea9","summary":"h:effa6ed6c5"}]}]},
|
|
6
|
+
{"task_id":"1004233","graded_at":"2026-09-25T00:55:57.826Z","recorded":{"panel_outcome":"pass","pass_count":5,"concerns_count":0,"fail_count":0,"error_count":0,"worker_count":5,"panel_cost_usd":5.059474600000001,"confidence":0.75,"rubric":{"functional_fidelity":10,"code_quality":8,"economy":8,"security_score":8,"efficiency_score":8}},"workers":[{"kind":"narc","verdict":"pass","error":null,"scores":{"functional_fidelity":10},"confidence":0.85,"model":"claude-sonnet-5","cost_usd":1.2121266,"retried":false,"issues":[]},{"kind":"quality","verdict":"pass","error":null,"scores":{"economy":8,"code_quality":8},"confidence":0.75,"model":"claude-sonnet-5","cost_usd":2.0584278000000005,"retried":false,"issues":[{"severity":"minor","file":"h:3777db4de5","summary":"h:623cf4fc2f"},{"severity":"minor","file":"h:ccd049c40a","summary":"h:0a2979d4f5"},{"severity":"nit","file":"h:b1e6394ff9","summary":"h:fa25fe295b"}]},{"kind":"hacker","verdict":"pass","error":null,"scores":{"security_score":8},"confidence":0.72,"model":"claude-sonnet-5","cost_usd":0.5786758000000001,"retried":false,"issues":[{"severity":"minor","file":"h:5551af7021","summary":"h:b8d3018634"}]},{"kind":"efficiency","verdict":"pass","error":null,"scores":{"efficiency_score":8},"confidence":0.75,"model":"claude-sonnet-5","cost_usd":0.5601807999999999,"retried":false,"issues":[{"severity":"minor","file":"h:3777db4de5","summary":"h:5b67a83015"}]},{"kind":"reader","verdict":"advisory","error":null,"scores":{"copy_score":8},"confidence":0.8,"model":"claude-sonnet-5","cost_usd":0.6500636,"retried":false,"issues":[{"severity":"nit","file":"h:1fd827b51f","summary":"h:c3c3b9ab36"}]},{"kind":"route-rank","verdict":"pass","error":null,"scores":{},"confidence":1,"model":"deterministic","cost_usd":null,"retried":false,"issues":[]}]},
|
|
7
|
+
{"task_id":"1004232","graded_at":"2026-09-25T00:31:54.069Z","recorded":{"panel_outcome":"pass","pass_count":4,"concerns_count":0,"fail_count":0,"error_count":0,"worker_count":4,"panel_cost_usd":5.747325999999999,"confidence":0.8,"rubric":{"functional_fidelity":9,"code_quality":7,"economy":7,"security_score":9,"efficiency_score":9}},"workers":[{"kind":"narc","verdict":"pass","error":null,"scores":{"functional_fidelity":9},"confidence":0.85,"model":"claude-sonnet-5","cost_usd":1.1428184,"retried":false,"issues":[{"severity":"nit","file":"h:2fa74b4ef2","summary":"h:1c2a97f3f8"}]},{"kind":"quality","verdict":"pass","error":null,"scores":{"economy":7,"code_quality":7},"confidence":0.75,"model":"claude-sonnet-5","cost_usd":1.3643480000000001,"retried":false,"issues":[{"severity":"nit","file":"h:2fa74b4ef2","summary":"h:07170cd687"}]},{"kind":"hacker","verdict":"pass","error":null,"scores":{"security_score":9},"confidence":0.85,"model":"claude-sonnet-5","cost_usd":1.137575,"retried":false,"issues":[{"severity":"nit","file":"h:71ebac794f","summary":"h:c088bc7c82"}]},{"kind":"efficiency","verdict":"pass","error":null,"scores":{"efficiency_score":9},"confidence":0.75,"model":"claude-sonnet-5","cost_usd":0.9614498,"retried":false,"issues":[]},{"kind":"reader","verdict":"advisory","error":null,"scores":{"copy_score":10},"confidence":0.75,"model":"claude-sonnet-5","cost_usd":1.1411348,"retried":false,"issues":[]}]},
|
|
8
|
+
{"task_id":"1004231","graded_at":"2026-09-24T23:23:35.527Z","recorded":{"panel_outcome":"pass","pass_count":4,"concerns_count":1,"fail_count":0,"error_count":0,"worker_count":5,"panel_cost_usd":7.8164818,"confidence":0.7,"rubric":{"functional_fidelity":7,"code_quality":8,"economy":7,"security_score":8,"efficiency_score":8}},"workers":[{"kind":"narc","verdict":"concerns","error":null,"scores":{"functional_fidelity":7},"confidence":0.75,"model":"claude-sonnet-5","cost_usd":1.5480209999999999,"retried":false,"issues":[{"severity":"major","file":"h:157e25dda2","summary":"h:b0912bc039"},{"severity":"nit","file":"h:82b2beaae5","summary":"h:8fc2ce463e"}]},{"kind":"quality","verdict":"pass","error":null,"scores":{"economy":7,"code_quality":8},"confidence":0.65,"model":"claude-sonnet-5","cost_usd":1.8102475999999998,"retried":false,"issues":[{"severity":"nit","file":"h:2fa74b4ef2","summary":"h:1ad62f6d68"},{"severity":"nit","file":"h:2a01d47f8b","summary":"h:c86d0c45e3"},{"severity":"nit","file":"h:0470f0809f","summary":"h:77e0342f1f"}]},{"kind":"hacker","verdict":"pass","error":null,"scores":{"security_score":8},"confidence":0.7,"model":"claude-sonnet-5","cost_usd":1.3475988,"retried":false,"issues":[]},{"kind":"efficiency","verdict":"pass","error":null,"scores":{"efficiency_score":8},"confidence":0.62,"model":"claude-sonnet-5","cost_usd":1.5321918,"retried":false,"issues":[{"severity":"minor","file":"h:db18fe0df6","summary":"h:e2cd3addcb"}]},{"kind":"reader","verdict":"advisory","error":null,"scores":{"copy_score":6},"confidence":0.65,"model":"claude-sonnet-5","cost_usd":1.5784226,"retried":false,"issues":[{"severity":"major","file":"h:144d00738e","summary":"h:1879492c5b"},{"severity":"minor","file":"h:2a01d47f8b","summary":"h:22b84fb977"}]},{"kind":"route-rank","verdict":"pass","error":null,"scores":{},"confidence":1,"model":"deterministic","cost_usd":null,"retried":false,"issues":[]}]},
|
|
9
|
+
{"task_id":"1004230","graded_at":"2026-09-24T22:12:49.539Z","recorded":{"panel_outcome":"pass","pass_count":4,"concerns_count":1,"fail_count":0,"error_count":0,"worker_count":5,"panel_cost_usd":2.8529818000000002,"confidence":0.85,"rubric":{"functional_fidelity":8,"code_quality":9,"economy":8,"security_score":8,"efficiency_score":8}},"workers":[{"kind":"narc","verdict":"concerns","error":null,"scores":{"functional_fidelity":8},"confidence":0.85,"model":"claude-sonnet-5","cost_usd":0.42845880000000003,"retried":false,"issues":[{"severity":"minor","file":"h:5551af7021","summary":"h:f0686d9966"}]},{"kind":"quality","verdict":"pass","error":null,"scores":{"economy":8,"code_quality":9},"confidence":0.9,"model":"claude-sonnet-5","cost_usd":1.4735852000000003,"retried":false,"issues":[{"severity":"nit","file":"h:5551af7021","summary":"h:8f9ef63f56"},{"severity":"nit","file":"h:de87550524","summary":"h:518535f1bf"}]},{"kind":"hacker","verdict":"pass","error":null,"scores":{"security_score":8},"confidence":0.85,"model":"claude-sonnet-5","cost_usd":0.516923,"retried":false,"issues":[{"severity":"minor","file":"h:5551af7021","summary":"h:c7e9e1feeb"},{"severity":"nit","file":"h:5551af7021","summary":"h:1291c756bc"}]},{"kind":"efficiency","verdict":"pass","error":null,"scores":{"efficiency_score":8},"confidence":0.85,"model":"claude-sonnet-5","cost_usd":0.4340148,"retried":false,"issues":[{"severity":"minor","file":"h:de87550524","summary":"h:ad0dba5181"},{"severity":"minor","file":"h:5551af7021","summary":"h:b9bc712665"},{"severity":"nit","file":"h:5551af7021","summary":"h:d15c9545af"}]},{"kind":"route-rank","verdict":"pass","error":null,"scores":{},"confidence":1,"model":"deterministic","cost_usd":null,"retried":false,"issues":[]}]},
|
|
10
|
+
{"task_id":"1004148","graded_at":"2026-09-24T14:55:32.886Z","recorded":{"panel_outcome":"pass","pass_count":5,"concerns_count":0,"fail_count":0,"error_count":0,"worker_count":5,"panel_cost_usd":3.6300847999999997,"confidence":0.85,"rubric":{"functional_fidelity":8,"code_quality":8,"economy":8,"security_score":9,"efficiency_score":9}},"workers":[{"kind":"narc","verdict":"pass","error":null,"scores":{"functional_fidelity":8},"confidence":0.75,"model":"claude-sonnet-5","cost_usd":0.9732858,"retried":false,"issues":[{"severity":"nit","file":"h:10d87c6f85","summary":"h:c87690ef35"},{"severity":"nit","file":"h:9f21143ae6","summary":"h:8d7a2cdcd0"}]},{"kind":"quality","verdict":"pass","error":null,"scores":{"economy":8,"code_quality":8},"confidence":0.72,"model":"claude-sonnet-5","cost_usd":0.9691062,"retried":false,"issues":[{"severity":"minor","file":"h:10d87c6f85","summary":"h:bbe06ff066"},{"severity":"minor","file":"h:9f21143ae6","summary":"h:7e29754bf6"},{"severity":"nit","file":"h:10d87c6f85","summary":"h:077ba4aae2"},{"severity":"nit","file":"h:b1e6394ff9","summary":"h:700e676a78"}]},{"kind":"hacker","verdict":"pass","error":null,"scores":{"security_score":9},"confidence":0.85,"model":"claude-sonnet-5","cost_usd":0.847472,"retried":false,"issues":[{"severity":"nit","file":"h:10d87c6f85","summary":"h:f48d7c3fe5"}]},{"kind":"efficiency","verdict":"pass","error":null,"scores":{"efficiency_score":9},"confidence":0.85,"model":"claude-sonnet-5","cost_usd":0.8402208,"retried":false,"issues":[{"severity":"nit","file":"h:10d87c6f85","summary":"h:c2bb909961"}]},{"kind":"route-rank","verdict":"pass","error":null,"scores":{},"confidence":1,"model":"deterministic","cost_usd":null,"retried":false,"issues":[]}]},
|
|
11
|
+
{"task_id":"1004175","graded_at":"2026-09-23T02:06:16.884Z","recorded":{"panel_outcome":"pass","pass_count":3,"concerns_count":1,"fail_count":0,"error_count":0,"worker_count":4,"panel_cost_usd":1.784195,"confidence":0.8999999999999999,"rubric":{"functional_fidelity":6,"code_quality":9,"economy":9,"security_score":10,"efficiency_score":10}},"workers":[{"kind":"narc","verdict":"concerns","error":null,"scores":{"functional_fidelity":6},"confidence":0.6,"model":"claude-sonnet-5","cost_usd":0.45443649999999997,"retried":false,"issues":[{"severity":"major","file":"h:3aa650846e","summary":"h:490ff4daf7"}]},{"kind":"quality","verdict":"pass","error":null,"scores":{"economy":9,"code_quality":9},"confidence":0.85,"model":"claude-sonnet-5","cost_usd":0.5501855,"retried":false,"issues":[{"severity":"nit","file":"h:c50d619d83","summary":"h:7365cebeec"}]},{"kind":"hacker","verdict":"pass","error":null,"scores":{"security_score":10},"confidence":0.95,"model":"claude-sonnet-5","cost_usd":0.41305525000000004,"retried":false,"issues":[{"severity":"nit","file":"h:53b8a2c4db","summary":"h:5f66c3fee9"}]},{"kind":"efficiency","verdict":"pass","error":null,"scores":{"efficiency_score":10},"confidence":0.95,"model":"claude-sonnet-5","cost_usd":0.36651775000000003,"retried":false,"issues":[]}]},
|
|
12
|
+
{"task_id":"1001426","graded_at":"2026-09-19T10:43:45.991Z","recorded":{"panel_outcome":"pass","pass_count":3,"concerns_count":1,"fail_count":0,"error_count":0,"worker_count":4,"panel_cost_usd":2.5981449999999997,"confidence":0.85,"rubric":{"functional_fidelity":9,"code_quality":8,"economy":7,"security_score":8,"efficiency_score":9}},"workers":[{"kind":"narc","verdict":"concerns","error":null,"scores":{"functional_fidelity":9},"confidence":0.85,"model":"claude-sonnet-5","cost_usd":0.52210075,"retried":false,"issues":[{"severity":"nit","file":"h:6727d747e4","summary":"h:4a6dab43da"}]},{"kind":"quality","verdict":"pass","error":null,"scores":{"economy":7,"code_quality":8},"confidence":0.85,"model":"claude-sonnet-5","cost_usd":1.1843039999999998,"retried":false,"issues":[{"severity":"nit","file":"h:6727d747e4","summary":"h:7695813db0"},{"severity":"nit","file":"h:2ad12a56c4","summary":"h:266cf3b18c"}]},{"kind":"hacker","verdict":"pass","error":null,"scores":{"security_score":8},"confidence":0.75,"model":"claude-sonnet-5","cost_usd":0.46327075,"retried":true,"issues":[{"severity":"nit","file":"h:2ad12a56c4","summary":"h:b954545f5d"},{"severity":"nit","file":"h:f071ccee8e","summary":"h:53ab92e62a"}]},{"kind":"efficiency","verdict":"pass","error":null,"scores":{"efficiency_score":9},"confidence":0.85,"model":"claude-sonnet-5","cost_usd":0.4284695,"retried":false,"issues":[]}]},
|
|
13
|
+
{"task_id":"1003878","graded_at":"2026-09-14T01:52:49.556Z","recorded":{"panel_outcome":"pass","pass_count":3,"concerns_count":0,"fail_count":0,"error_count":1,"worker_count":4,"panel_cost_usd":3.1948585000000005,"confidence":0.875,"rubric":{"functional_fidelity":null,"code_quality":9,"economy":8,"security_score":9,"efficiency_score":10}},"workers":[{"kind":"narc","verdict":"fail","error":"SUBAGENT_TIMEOUT","scores":{},"confidence":0,"model":"claude-sonnet-5","cost_usd":null,"retried":false,"issues":[]},{"kind":"quality","verdict":"pass","error":null,"scores":{"economy":8,"code_quality":9},"confidence":0.9,"model":"claude-sonnet-5","cost_usd":1.6250995000000001,"retried":false,"issues":[{"severity":"nit","file":"h:492eba910a","summary":"h:1aad1668d5"}]},{"kind":"hacker","verdict":"pass","error":null,"scores":{"security_score":9},"confidence":0.85,"model":"claude-sonnet-5","cost_usd":0.8037920000000001,"retried":false,"issues":[{"severity":"nit","file":"h:93b12b601e","summary":"h:feb13a1e17"}]},{"kind":"efficiency","verdict":"pass","error":null,"scores":{"efficiency_score":10},"confidence":0.9,"model":"claude-sonnet-5","cost_usd":0.7659670000000001,"retried":false,"issues":[]}]},
|
|
14
|
+
{"task_id":"1003750","graded_at":"2026-09-09T06:49:24.763Z","recorded":{"panel_outcome":"concerns","pass_count":4,"concerns_count":0,"fail_count":1,"error_count":0,"worker_count":5,"panel_cost_usd":3.9950229999999998,"confidence":0.3,"rubric":{"functional_fidelity":5,"code_quality":9,"economy":9,"security_score":9,"efficiency_score":10}},"workers":[{"kind":"narc","verdict":"fail","error":null,"scores":{"functional_fidelity":5},"confidence":0.78,"model":"claude-sonnet-5","cost_usd":0.68180775,"retried":false,"issues":[{"severity":"major","file":"h:84177396b5","summary":"h:41853eebb7"}]},{"kind":"quality","verdict":"pass","error":null,"scores":{"economy":9,"code_quality":9},"confidence":0.9,"model":"claude-sonnet-5","cost_usd":1.4039994999999998,"retried":false,"issues":[{"severity":"nit","file":"h:972fc09ade","summary":"h:93e2e92f70"}]},{"kind":"hacker","verdict":"pass","error":null,"scores":{"security_score":9},"confidence":0.72,"model":"claude-sonnet-5","cost_usd":1.33469425,"retried":false,"issues":[{"severity":"nit","file":"h:0e40eaa3b8","summary":"h:b148aec4ef"}]},{"kind":"efficiency","verdict":"pass","error":null,"scores":{"efficiency_score":10},"confidence":0.92,"model":"claude-sonnet-5","cost_usd":0.5745215,"retried":false,"issues":[]},{"kind":"route-rank","verdict":"pass","error":null,"scores":{},"confidence":1,"model":"deterministic","cost_usd":null,"retried":false,"issues":[]}]},
|
|
15
|
+
{"task_id":"1003445","graded_at":"2026-09-03T19:18:35.956Z","recorded":{"panel_outcome":"pass","pass_count":4,"concerns_count":0,"fail_count":0,"error_count":0,"worker_count":4,"panel_cost_usd":2.2818312,"confidence":0.85,"rubric":{"functional_fidelity":9,"code_quality":9,"economy":8,"security_score":9,"efficiency_score":9}},"workers":[{"kind":"narc","verdict":"pass","error":null,"scores":{"functional_fidelity":9},"confidence":0.85,"model":"claude-sonnet-5","cost_usd":0.5429154,"retried":false,"issues":[{"severity":"nit","file":"h:470c02c760","summary":"h:f1c63e97a3"}]},{"kind":"quality","verdict":"pass","error":null,"scores":{"economy":8,"code_quality":9},"confidence":0.85,"model":"claude-sonnet-5","cost_usd":0.671361,"retried":false,"issues":[]},{"kind":"hacker","verdict":"pass","error":null,"scores":{"security_score":9},"confidence":0.85,"model":"claude-sonnet-5","cost_usd":0.5381194,"retried":true,"issues":[{"severity":"nit","file":"h:facfae576a","summary":"h:75321410be"}]},{"kind":"efficiency","verdict":"pass","error":null,"scores":{"efficiency_score":9},"confidence":0.85,"model":"claude-sonnet-5","cost_usd":0.5294354,"retried":false,"issues":[]}]},
|
|
16
|
+
{"task_id":"1003175","graded_at":"2026-09-02T17:52:46.502Z","recorded":{"panel_outcome":"pass","pass_count":5,"concerns_count":0,"fail_count":0,"error_count":0,"worker_count":5,"panel_cost_usd":1.7976148,"confidence":0.85,"rubric":{"functional_fidelity":10,"code_quality":9,"economy":8,"security_score":9,"efficiency_score":9}},"workers":[{"kind":"narc","verdict":"pass","error":null,"scores":{"functional_fidelity":10},"confidence":0.85,"model":"claude-sonnet-5","cost_usd":0.387192,"retried":false,"issues":[]},{"kind":"quality","verdict":"pass","error":null,"scores":{"economy":8,"code_quality":9},"confidence":0.9,"model":"claude-sonnet-5","cost_usd":0.5571676000000001,"retried":true,"issues":[{"severity":"nit","file":"h:87d810e524","summary":"h:ecfffdd5a6"}]},{"kind":"hacker","verdict":"pass","error":null,"scores":{"security_score":9},"confidence":0.85,"model":"claude-sonnet-5","cost_usd":0.4811592,"retried":false,"issues":[]},{"kind":"efficiency","verdict":"pass","error":null,"scores":{"efficiency_score":9},"confidence":0.8,"model":"claude-sonnet-5","cost_usd":0.372096,"retried":false,"issues":[]},{"kind":"route-rank","verdict":"pass","error":null,"scores":{},"confidence":1,"model":"deterministic","cost_usd":null,"retried":false,"issues":[]}]},
|
|
17
|
+
{"task_id":"1003298","graded_at":"2026-08-27T23:12:53.322Z","recorded":{"panel_outcome":"concerns","pass_count":4,"concerns_count":0,"fail_count":0,"error_count":1,"worker_count":5,"panel_cost_usd":3.2470860000000004,"confidence":0.3,"rubric":{"functional_fidelity":9,"code_quality":0,"economy":null,"security_score":9,"efficiency_score":8}},"workers":[{"kind":"narc","verdict":"pass","error":null,"scores":{"functional_fidelity":9},"confidence":0.85,"model":"claude-sonnet-5","cost_usd":1.1821806000000001,"retried":false,"issues":[{"severity":"nit","file":"h:3180859548","summary":"h:2e9a36938f"}]},{"kind":"quality","verdict":"fail","error":"SUBAGENT_NONZERO_EXIT","scores":{},"confidence":0,"model":"claude-haiku-4-5-20251001","cost_usd":null,"retried":true,"issues":[],"exit_code":1,"stderr_tail":"h:daafa47568"},{"kind":"hacker","verdict":"pass","error":null,"scores":{"security_score":9},"confidence":0.75,"model":"claude-sonnet-5","cost_usd":0.9619244000000001,"retried":false,"issues":[{"severity":"nit","file":"h:6e356932ba","summary":"h:fbac6e060e"}]},{"kind":"efficiency","verdict":"pass","error":null,"scores":{"efficiency_score":8},"confidence":0.75,"model":"claude-sonnet-5","cost_usd":1.102981,"retried":false,"issues":[{"severity":"nit","file":"h:6e356932ba","summary":"h:db7256c9a5"}]},{"kind":"route-rank","verdict":"pass","error":null,"scores":{},"confidence":1,"model":"deterministic","cost_usd":null,"retried":false,"issues":[]}]},
|
|
18
|
+
{"task_id":"1003297","graded_at":"2026-08-27T17:59:20.501Z","recorded":{"panel_outcome":"concerns","pass_count":3,"concerns_count":0,"fail_count":0,"error_count":1,"worker_count":4,"panel_cost_usd":2.1315774,"confidence":0.3,"rubric":{"functional_fidelity":9,"code_quality":0,"economy":null,"security_score":10,"efficiency_score":9}},"workers":[{"kind":"narc","verdict":"pass","error":null,"scores":{"functional_fidelity":9},"confidence":0.85,"model":"claude-sonnet-5","cost_usd":0.7508158,"retried":false,"issues":[{"severity":"nit","file":"h:1f689d0b84","summary":"h:59dc4a5843"}]},{"kind":"quality","verdict":"fail","error":"SUBAGENT_NONZERO_EXIT","scores":{},"confidence":0,"model":"claude-haiku-4-5-20251001","cost_usd":null,"retried":true,"issues":[]},{"kind":"hacker","verdict":"pass","error":null,"scores":{"security_score":10},"confidence":0.95,"model":"claude-sonnet-5","cost_usd":0.6944058,"retried":false,"issues":[]},{"kind":"efficiency","verdict":"pass","error":null,"scores":{"efficiency_score":9},"confidence":0.85,"model":"claude-sonnet-5","cost_usd":0.6863558,"retried":false,"issues":[]}]},
|
|
19
|
+
{"task_id":"1003224","graded_at":"2026-08-24T01:31:09.496Z","recorded":{"panel_outcome":"pass","pass_count":5,"concerns_count":0,"fail_count":0,"error_count":0,"worker_count":5,"panel_cost_usd":1.471463,"confidence":0.85,"rubric":{"functional_fidelity":9,"code_quality":8,"economy":9,"security_score":9,"efficiency_score":10}},"workers":[{"kind":"narc","verdict":"pass","error":null,"scores":{"functional_fidelity":9},"confidence":0.85,"model":"claude-sonnet-5","cost_usd":0.44815799999999995,"retried":true,"issues":[{"severity":"nit","file":"h:f0aae9b1f0","summary":"h:b1417a465b"}]},{"kind":"quality","verdict":"pass","error":null,"scores":{"economy":9,"code_quality":8},"confidence":0.95,"model":"claude-haiku-4-5-20251001","cost_usd":0.2275078,"retried":false,"issues":[]},{"kind":"hacker","verdict":"pass","error":null,"scores":{"security_score":9},"confidence":0.85,"model":"claude-sonnet-5","cost_usd":0.45007579999999997,"retried":false,"issues":[{"severity":"nit","file":"h:f0aae9b1f0","summary":"h:3e9bc71e3e"}]},{"kind":"efficiency","verdict":"pass","error":null,"scores":{"efficiency_score":10},"confidence":0.85,"model":"claude-sonnet-5","cost_usd":0.3457214,"retried":false,"issues":[]},{"kind":"route-rank","verdict":"pass","error":null,"scores":{},"confidence":1,"model":"deterministic","cost_usd":null,"retried":false,"issues":[]}]},
|
|
20
|
+
{"task_id":"1002567","graded_at":"2026-08-15T03:40:05.655Z","recorded":{"panel_outcome":"fail","pass_count":2,"concerns_count":0,"fail_count":2,"error_count":0,"worker_count":4,"panel_cost_usd":2.6416746,"confidence":0,"rubric":{"functional_fidelity":4,"code_quality":9,"economy":9,"security_score":8,"efficiency_score":9}},"workers":[{"kind":"narc","verdict":"fail","error":null,"scores":{"functional_fidelity":4},"confidence":0.85,"model":"claude-sonnet-5","cost_usd":0.6630807000000001,"retried":false,"issues":[{"severity":"blocker","file":"h:61d7acf831","summary":"h:0db1adea60"}]},{"kind":"quality","verdict":"fail","error":null,"scores":{"economy":9,"code_quality":9},"confidence":0.8,"model":"claude-haiku-4-5-20251001","cost_usd":0.1602525,"retried":false,"issues":[{"severity":"blocker","file":"h:61d7acf831","summary":"h:b0b7c5a8d5"}]},{"kind":"hacker","verdict":"pass","error":null,"scores":{"security_score":8},"confidence":0.8,"model":"claude-sonnet-5","cost_usd":1.2236937,"retried":false,"issues":[{"severity":"nit","file":"h:61d7acf831","summary":"h:66eb617721"},{"severity":"nit","file":"h:0ac4f005a4","summary":"h:090043980b"},{"severity":"nit","file":"h:19d7a758ef","summary":"h:321fcfbbb1"}]},{"kind":"efficiency","verdict":"pass","error":null,"scores":{"efficiency_score":9},"confidence":0.9,"model":"claude-sonnet-5","cost_usd":0.5946477,"retried":false,"issues":[{"severity":"minor","file":"h:0ac4f005a4","summary":"h:2f3a34b24a"}]}]},
|
|
21
|
+
{"task_id":"1001916","graded_at":"2026-08-15T03:20:41.228Z","recorded":{"panel_outcome":"fail","pass_count":3,"concerns_count":0,"fail_count":1,"error_count":0,"worker_count":4,"panel_cost_usd":1.8740299,"confidence":0,"rubric":{"functional_fidelity":10,"code_quality":0,"economy":0,"security_score":9,"efficiency_score":10}},"workers":[{"kind":"narc","verdict":"pass","error":null,"scores":{"functional_fidelity":10},"confidence":0.95,"model":"claude-sonnet-5","cost_usd":1.146498,"retried":false,"issues":[]},{"kind":"quality","verdict":"fail","error":null,"scores":{"economy":0,"code_quality":0},"confidence":1,"model":"claude-haiku-4-5-20251001","cost_usd":0.16489269999999998,"retried":false,"issues":[{"severity":"blocker","file":"h:2d89f98489","summary":"h:a086d0d4e9"},{"severity":"blocker","file":"h:2d89f98489","summary":"h:047523a0d1"},{"severity":"blocker","file":"h:2d89f98489","summary":"h:b55f2c92cd"},{"severity":"blocker","file":"h:2d89f98489","summary":"h:93545e2b5c"},{"severity":"blocker","file":"h:8a81fbc705","summary":"h:14244e5e2b"}]},{"kind":"hacker","verdict":"pass","error":null,"scores":{"security_score":9},"confidence":0.85,"model":"claude-sonnet-5","cost_usd":0.2887386,"retried":false,"issues":[]},{"kind":"efficiency","verdict":"pass","error":null,"scores":{"efficiency_score":10},"confidence":0.9,"model":"claude-sonnet-5","cost_usd":0.2739006,"retried":false,"issues":[]}]},
|
|
22
|
+
{"task_id":"1002737","graded_at":"2026-08-12T23:35:40.270Z","recorded":{"panel_outcome":"unavailable","pass_count":0,"concerns_count":0,"fail_count":0,"error_count":4,"worker_count":4,"panel_cost_usd":0,"confidence":0.3,"rubric":{"functional_fidelity":null,"code_quality":0,"economy":null,"security_score":null,"efficiency_score":null}},"workers":[{"kind":"narc","verdict":"fail","error":"SUBAGENT_NONZERO_EXIT","scores":{},"confidence":0,"model":"claude-opus-5","cost_usd":null,"retried":true,"issues":[]},{"kind":"quality","verdict":"fail","error":"SUBAGENT_NONZERO_EXIT","scores":{},"confidence":0,"model":"claude-haiku-4-5-20251001","cost_usd":null,"retried":true,"issues":[]},{"kind":"hacker","verdict":"fail","error":"SUBAGENT_NONZERO_EXIT","scores":{},"confidence":0,"model":"claude-opus-5","cost_usd":null,"retried":true,"issues":[]},{"kind":"efficiency","verdict":"fail","error":"SUBAGENT_NONZERO_EXIT","scores":{},"confidence":0,"model":"claude-opus-5","cost_usd":null,"retried":true,"issues":[]}]},
|
|
23
|
+
{"task_id":"1002653","graded_at":"2026-08-11T14:19:49.327Z","recorded":{"panel_outcome":"concerns","pass_count":3,"concerns_count":1,"fail_count":0,"error_count":0,"worker_count":4,"panel_cost_usd":2.2648416,"confidence":0.3,"rubric":{"functional_fidelity":9,"code_quality":8,"economy":7,"security_score":8,"efficiency_score":9}},"workers":[{"kind":"narc","verdict":"pass","error":null,"scores":{"functional_fidelity":9},"confidence":0.72,"model":"claude-sonnet-4-6","cost_usd":0.8756592,"retried":false,"issues":[]},{"kind":"quality","verdict":"pass","error":null,"scores":{"economy":7,"code_quality":8},"confidence":0.85,"model":"claude-sonnet-4-6","cost_usd":0.5879475000000001,"retried":false,"issues":[{"severity":"nit","file":"h:332822d131","summary":"h:6c3215c552"}]},{"kind":"hacker","verdict":"concerns","error":null,"scores":{"security_score":8},"confidence":0.78,"model":"claude-sonnet-4-6","cost_usd":0.5962068,"retried":false,"issues":[{"severity":"major","file":"h:332822d131","summary":"h:832e4bf1a7"}]},{"kind":"efficiency","verdict":"pass","error":null,"scores":{"efficiency_score":9},"confidence":0.88,"model":"claude-sonnet-4-6","cost_usd":0.2050281,"retried":false,"issues":[]}]},
|
|
24
|
+
{"task_id":"1002554","graded_at":"2026-08-05T23:04:34.451Z","recorded":{"panel_outcome":"fail","pass_count":3,"concerns_count":0,"fail_count":1,"error_count":0,"worker_count":4,"panel_cost_usd":1.8783215999999998,"confidence":0,"rubric":{"functional_fidelity":4,"code_quality":8,"economy":7,"security_score":8,"efficiency_score":9}},"workers":[{"kind":"narc","verdict":"fail","error":null,"scores":{"functional_fidelity":4},"confidence":0.9,"model":"claude-sonnet-4-6","cost_usd":0.4524978,"retried":false,"issues":[{"severity":"blocker","file":"h:0f2f745307","summary":"h:5a16de420b"}]},{"kind":"quality","verdict":"pass","error":null,"scores":{"economy":7,"code_quality":8},"confidence":0.9,"model":"claude-sonnet-4-6","cost_usd":0.5554047,"retried":false,"issues":[{"severity":"minor","file":"h:0f2f745307","summary":"h:cddbd7df8e"},{"severity":"nit","file":"h:eea03bad91","summary":"h:50263ab993"}]},{"kind":"hacker","verdict":"pass","error":null,"scores":{"security_score":8},"confidence":0.72,"model":"claude-sonnet-4-6","cost_usd":0.6754245,"retried":false,"issues":[{"severity":"minor","file":"h:3a72359d4c","summary":"h:16a58096be"},{"severity":"minor","file":"h:2d211b60f4","summary":"h:38f437afc9"}]},{"kind":"efficiency","verdict":"pass","error":null,"scores":{"efficiency_score":9},"confidence":0.85,"model":"claude-sonnet-4-6","cost_usd":0.1949946,"retried":false,"issues":[]}]},
|
|
25
|
+
{"task_id":"1002553","graded_at":"2026-07-30T18:34:51.581Z","recorded":{"panel_outcome":"pass","pass_count":3,"concerns_count":1,"fail_count":0,"error_count":0,"worker_count":4,"panel_cost_usd":3.4621722,"confidence":0.835,"rubric":{"functional_fidelity":7,"code_quality":8,"economy":8,"security_score":9,"efficiency_score":9}},"workers":[{"kind":"narc","verdict":"concerns","error":null,"scores":{"functional_fidelity":7},"confidence":0.75,"model":"claude-sonnet-4-6","cost_usd":0.405582,"retried":true,"issues":[{"severity":"major","file":"h:ae3930574d","summary":"h:6e98ed3323"}]},{"kind":"quality","verdict":"pass","error":null,"scores":{"economy":8,"code_quality":8},"confidence":0.82,"model":"claude-sonnet-4-6","cost_usd":1.8247271999999997,"retried":false,"issues":[{"severity":"minor","file":"h:9efc99d028","summary":"h:003a5e3c43"},{"severity":"minor","file":"h:9efc99d028","summary":"h:33b97e99b5"},{"severity":"minor","file":"h:ae3930574d","summary":"h:c47a0a25af"}]},{"kind":"hacker","verdict":"pass","error":null,"scores":{"security_score":9},"confidence":0.88,"model":"claude-sonnet-4-6","cost_usd":0.779004,"retried":false,"issues":[{"severity":"minor","file":"h:9efc99d028","summary":"h:66243d8143"},{"severity":"nit","file":"h:9efc99d028","summary":"h:230e1c2756"}]},{"kind":"efficiency","verdict":"pass","error":null,"scores":{"efficiency_score":9},"confidence":0.85,"model":"claude-sonnet-4-6","cost_usd":0.452859,"retried":false,"issues":[]}]},
|
|
26
|
+
{"task_id":"1002525","graded_at":"2026-07-29T16:00:37.887Z","recorded":{"panel_outcome":"pass","pass_count":4,"concerns_count":0,"fail_count":0,"error_count":0,"worker_count":4,"panel_cost_usd":1.5763158,"confidence":0.875,"rubric":{"functional_fidelity":9,"code_quality":9,"economy":9,"security_score":9,"efficiency_score":9}},"workers":[{"kind":"narc","verdict":"pass","error":null,"scores":{"functional_fidelity":9},"confidence":0.85,"model":"claude-sonnet-4-6","cost_usd":0.46919940000000004,"retried":false,"issues":[]},{"kind":"quality","verdict":"pass","error":null,"scores":{"economy":9,"code_quality":9},"confidence":0.85,"model":"claude-sonnet-4-6","cost_usd":0.7438973999999999,"retried":true,"issues":[]},{"kind":"hacker","verdict":"pass","error":null,"scores":{"security_score":9},"confidence":0.9,"model":"claude-sonnet-4-6","cost_usd":0.17472000000000001,"retried":false,"issues":[]},{"kind":"efficiency","verdict":"pass","error":null,"scores":{"efficiency_score":9},"confidence":0.95,"model":"claude-sonnet-4-6","cost_usd":0.188499,"retried":false,"issues":[]}]}
|
|
27
|
+
]}
|
|
@@ -295,6 +295,16 @@ const ADDED_SINCE_R101 = [
|
|
|
295
295
|
'ship-surprise', 'ship-visual'],
|
|
296
296
|
'the 2026-08-23 ship.js carve: the land path, the ship state machine, the grade gate '
|
|
297
297
|
+ 'steps and pushVia, ~6,200 lines behind a 373-line protected entry point.'),
|
|
298
|
+
// task 1002839 (ADR 0089 §1). Not a `grader-*.js` sibling, so the carve tripwire
|
|
299
|
+
// below would not demand it — registered by hand because it owns the gate.
|
|
300
|
+
{
|
|
301
|
+
after: 'modules/grading/grader-subagent.js',
|
|
302
|
+
glob: 'modules/grading/grade-engine.js',
|
|
303
|
+
why: 'the central grading engine every work-type is graded through. It runs the '
|
|
304
|
+
+ 'deterministic route-rank and permission-path pre-passes and applies the '
|
|
305
|
+
+ 'deterministic veto, so an edit here can switch off the protected-path gate '
|
|
306
|
+
+ 'for every grade at once.',
|
|
307
|
+
},
|
|
298
308
|
];
|
|
299
309
|
|
|
300
310
|
// Surfaces RENAMED since R101 — still protected, under a new path. Distinct from
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// tests/grade_correlation_audit.mjs — the grader error-correlation audit
|
|
2
|
+
// (task 1002843, ADR 0089 §5).
|
|
3
|
+
//
|
|
4
|
+
// Pins the arithmetic against hand-computed values, the vote and label rules,
|
|
5
|
+
// and — the load-bearing part — the minimum-N guard: a kappa over too few
|
|
6
|
+
// observations, or over a rater that never changes its mind, is REFUSED with a
|
|
7
|
+
// reason, never printed as a number. The CLI is exercised end to end on a saved
|
|
8
|
+
// dataset, so the report path needs no network.
|
|
9
|
+
//
|
|
10
|
+
// Run: node --test tests/grade_correlation_audit.mjs
|
|
11
|
+
|
|
12
|
+
import { test } from 'node:test';
|
|
13
|
+
import assert from 'node:assert/strict';
|
|
14
|
+
import fs from 'node:fs';
|
|
15
|
+
import os from 'node:os';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
import { execFileSync } from 'node:child_process';
|
|
18
|
+
import { createRequire } from 'node:module';
|
|
19
|
+
import { fileURLToPath } from 'node:url';
|
|
20
|
+
|
|
21
|
+
const require = createRequire(import.meta.url);
|
|
22
|
+
const A = require('../scripts/gds/grade-correlation-audit.js');
|
|
23
|
+
const ROOT = fileURLToPath(new URL('..', import.meta.url));
|
|
24
|
+
|
|
25
|
+
const close = (a, b, eps = 1e-9) => assert.ok(Math.abs(a - b) < eps, `${a} != ${b}`);
|
|
26
|
+
const repeat = (n, v) => Array.from({ length: n }, () => v);
|
|
27
|
+
|
|
28
|
+
// A dataset row with the four panel workers' verdicts.
|
|
29
|
+
const W = (kind, verdict, extra = {}) => ({ kind, verdict, error: null, issues: [], ...extra });
|
|
30
|
+
function row(id, { narc = 'pass', quality = 'pass', hacker = 'pass', efficiency = 'pass', status = 'shipped', passed = true } = {}) {
|
|
31
|
+
return { task_id: String(id), task_status: status, passed, workers: [W('narc', narc), W('quality', quality), W('hacker', hacker), W('efficiency', efficiency)] };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
test('agreementStats matches a hand-computed 2x2 table', () => {
|
|
35
|
+
// both object 20, only first 5, only second 10, both clear 15 (n = 50)
|
|
36
|
+
const pairs = [...repeat(20, [1, 1]), ...repeat(5, [1, 0]), ...repeat(10, [0, 1]), ...repeat(15, [0, 0])];
|
|
37
|
+
const s = A.agreementStats(pairs);
|
|
38
|
+
assert.equal(s.n, 50);
|
|
39
|
+
close(s.agreement, 0.7);
|
|
40
|
+
close(s.kappa, 0.4); // po 0.7, pe 0.5*0.6 + 0.5*0.4 = 0.5 → (0.7-0.5)/(1-0.5)
|
|
41
|
+
close(s.phi, 250 / Math.sqrt(25 * 25 * 30 * 20));
|
|
42
|
+
assert.deepEqual(s.table, { both_object: 20, only_first: 5, only_second: 10, both_clear: 15 });
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('a vote: pass clears, concerns/fail objects, an errored worker with no finding casts none', () => {
|
|
46
|
+
assert.equal(A.voteOf(W('quality', 'pass')), 0);
|
|
47
|
+
assert.equal(A.voteOf(W('quality', 'concerns')), 1);
|
|
48
|
+
assert.equal(A.voteOf(W('quality', 'fail')), 1);
|
|
49
|
+
assert.equal(A.voteOf(W('quality', 'fail', { error: 'SUBAGENT_TIMEOUT' })), null, 'missing signal is not a vote');
|
|
50
|
+
assert.equal(A.voteOf(W('hacker', 'fail', { error: 'parse_error', issues: [{ severity: 'blocker' }] })), 1,
|
|
51
|
+
'an errored worker that still raised a blocker is a real objection (#473)');
|
|
52
|
+
assert.deepEqual(A.panelVotes([W('narc', 'pass'), W('reader', 'advisory'), { kind: 'route-rank', verdict: 'fail', model: 'deterministic' }]),
|
|
53
|
+
{ narc: 0 }, 'the reader and the deterministic pseudo-judges are not panel voters');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('owner labels: overrides and manual confirms only — an auto-landed pass is never a label', () => {
|
|
57
|
+
const labels = A.ownerLabels({
|
|
58
|
+
grades: [
|
|
59
|
+
{ task_id: '1', task_status: 'shipped', passed: false }, // confirmed by hand after a non-pass
|
|
60
|
+
{ task_id: '2', task_status: 'shipped', passed: true }, // auto-landed: the grader agreeing with itself
|
|
61
|
+
{ task_id: '3', task_status: 'completed', passed: false }, // still parked: no decision yet
|
|
62
|
+
{ task_id: '4', task_status: 'shipped', passed: false },
|
|
63
|
+
],
|
|
64
|
+
overrides: [{ task_id: '4', status: 'denied' }, { task_id: '4', status: 'approved' }, { task_id: '5', status: 'denied' }],
|
|
65
|
+
});
|
|
66
|
+
assert.deepEqual(Object.fromEntries(labels), {
|
|
67
|
+
4: { label: 'accept', source: 'override_approved' }, // the approval outranks the earlier denial
|
|
68
|
+
5: { label: 'reject', source: 'override_denied' },
|
|
69
|
+
1: { label: 'accept', source: 'manual_confirm_after_fail' },
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('THE GUARD: a kappa over too few observations, or over a constant rater, is refused — never printed', () => {
|
|
74
|
+
const few = { grades: repeat(5, 0).map((_, i) => row(i, { narc: i % 2 ? 'fail' : 'pass' })), overrides: [] };
|
|
75
|
+
const r = A.audit(few);
|
|
76
|
+
assert.equal(r.min_n, A.MIN_N);
|
|
77
|
+
for (const p of r.pairwise) {
|
|
78
|
+
assert.match(p.refused, /below the minimum of 30/);
|
|
79
|
+
assert.equal(p.kappa, undefined, 'no number rides along with a refusal');
|
|
80
|
+
}
|
|
81
|
+
assert.equal(r.effective_votes, null);
|
|
82
|
+
|
|
83
|
+
// Plenty of panels, but hacker never objects: every pair with it is undefined.
|
|
84
|
+
const constant = { grades: repeat(60, 0).map((_, i) => row(i, { narc: i % 3 ? 'pass' : 'concerns', quality: i % 4 ? 'pass' : 'fail' })), overrides: [] };
|
|
85
|
+
const rc = A.audit(constant);
|
|
86
|
+
const nh = rc.pairwise.find((p) => p.a === 'narc' && p.b === 'hacker');
|
|
87
|
+
assert.match(nh.refused, /only 0 of one class/);
|
|
88
|
+
assert.ok(!rc.pairwise.find((p) => p.a === 'narc' && p.b === 'quality').refused, 'a pair of changing raters is computed');
|
|
89
|
+
assert.equal(rc.effective_votes, null, 'the panel figure needs every pair');
|
|
90
|
+
|
|
91
|
+
assert.equal(A.audit(few, { minN: 3 }).min_n, A.MIN_N, '--min-n can raise the bar, never lower it');
|
|
92
|
+
assert.equal(A.audit(few, { minN: 80 }).min_n, 80);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('two workers that always vote alike read as redundant; unrelated ones as independent', () => {
|
|
96
|
+
const grades = repeat(80, 0).map((_, i) => row(i, {
|
|
97
|
+
narc: i % 2 ? 'fail' : 'pass',
|
|
98
|
+
quality: i % 2 ? 'fail' : 'pass', // a copy of narc
|
|
99
|
+
hacker: i % 4 < 2 ? 'concerns' : 'pass', // unrelated to narc's parity
|
|
100
|
+
efficiency: i % 5 ? 'pass' : 'fail',
|
|
101
|
+
}));
|
|
102
|
+
const r = A.audit({ grades, overrides: [] });
|
|
103
|
+
const nq = r.pairwise.find((p) => p.a === 'narc' && p.b === 'quality');
|
|
104
|
+
close(nq.kappa, 1);
|
|
105
|
+
assert.equal(nq.reading, 'redundant');
|
|
106
|
+
const nh = r.pairwise.find((p) => p.a === 'narc' && p.b === 'hacker');
|
|
107
|
+
close(nh.kappa, 0);
|
|
108
|
+
assert.equal(nh.reading, 'independent');
|
|
109
|
+
assert.ok(r.effective_votes > 1 && r.effective_votes < 4);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('per-worker kappa against the owner, and error correlation, once there are enough labels', () => {
|
|
113
|
+
// 40 labelled tasks: the owner accepted 20, rejected 20. Quality objects to
|
|
114
|
+
// exactly the rejected ones (kappa 1); narc objects to everything (constant → refused).
|
|
115
|
+
const grades = repeat(40, 0).map((_, i) => row(i, { narc: 'concerns', quality: i < 20 ? 'fail' : 'pass', status: 'shipped', passed: false }));
|
|
116
|
+
const overrides = repeat(40, 0).map((_, i) => ({ task_id: String(i), status: i < 20 ? 'denied' : 'approved' }));
|
|
117
|
+
const r = A.audit({ grades, overrides });
|
|
118
|
+
assert.deepEqual(r.labels, { total: 40, accept: 20, reject: 20 });
|
|
119
|
+
close(r.vs_owner.find((w) => w.kind === 'quality').kappa, 1);
|
|
120
|
+
assert.match(r.vs_owner.find((w) => w.kind === 'narc').refused, /only 0 of one class/);
|
|
121
|
+
const qh = r.pairwise.find((p) => p.a === 'quality' && p.b === 'hacker');
|
|
122
|
+
assert.ok(qh.errors.n === 40, 'errors are paired on the labelled tasks both voted on');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('effectiveVotes: 4 uncorrelated votes are 4, perfectly correlated are 1, negative is clamped', () => {
|
|
126
|
+
close(A.effectiveVotes(4, 0), 4);
|
|
127
|
+
close(A.effectiveVotes(4, 1), 1);
|
|
128
|
+
close(A.effectiveVotes(4, -0.3), 4);
|
|
129
|
+
close(A.effectiveVotes(4, 1 / 3), 2);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test('toAuditRow keeps the fate and the verdicts, and hashes the review prose', () => {
|
|
133
|
+
const r = A.toAuditRow({ task_id: 9, task_status: 'shipped', grade: { passed: false, signals: { workers: [
|
|
134
|
+
{ kind: 'quality', verdict: 'fail', error: null, issues: [{ severity: 'major', file: 'a.js', summary: 'real words' }], notes_md: 'prose' },
|
|
135
|
+
] } } });
|
|
136
|
+
assert.equal(r.task_id, '9');
|
|
137
|
+
assert.equal(r.task_status, 'shipped');
|
|
138
|
+
assert.equal(r.passed, false);
|
|
139
|
+
assert.equal(r.workers[0].verdict, 'fail');
|
|
140
|
+
assert.match(r.workers[0].issues[0].summary, /^h:[0-9a-f]{10}$/);
|
|
141
|
+
assert.equal(r.workers[0].notes_md, undefined);
|
|
142
|
+
assert.equal(A.toAuditRow({ task_id: 1, grade: { signals: {} } }), null, 'not a panel grade');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('the CLI reports a saved dataset — refusals by name, and --json — with no network', () => {
|
|
146
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'grade-audit-'));
|
|
147
|
+
const file = path.join(dir, 'dataset.json');
|
|
148
|
+
fs.writeFileSync(file, JSON.stringify({ grades: [row(1), row(2, { quality: 'fail' })], overrides: [] }));
|
|
149
|
+
try {
|
|
150
|
+
const text = execFileSync(process.execPath, ['scripts/gds/grade-correlation-audit.js', '--file', file], { cwd: ROOT, encoding: 'utf8' });
|
|
151
|
+
assert.match(text, /2 recorded panel grade\(s\), minimum n = 30/);
|
|
152
|
+
assert.match(text, /narc × quality\s+REFUSED — n=2 is below the minimum of 30/);
|
|
153
|
+
const json = JSON.parse(execFileSync(process.execPath, ['scripts/gds/grade-correlation-audit.js', '--file', file, '--json'], { cwd: ROOT, encoding: 'utf8' }));
|
|
154
|
+
assert.equal(json.panels, 2);
|
|
155
|
+
assert.equal(json.pairwise.length, 6);
|
|
156
|
+
} finally {
|
|
157
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
158
|
+
}
|
|
159
|
+
});
|