mirror of
https://github.com/garrytan/gbrain.git
synced 2026-08-14 00:48:18 +00:00
* fix(calibration): resolve owner holder via config (default 'self'), fixes #2464 Takeover of #2467 (rebased onto master). consolidate writes owner takes with holder='self' while calibration-profile, calibration CLI/op, think's calibration block, emotional-weight, and doctor's calibration_freshness all defaulted to a hardcoded 'garry' — so getScorecard returned 0 resolved and the calibration profile never built on non-upstream brains. New src/core/owner-holder.ts is the single source of truth: resolveOwnerHolder({override, configValue}) = override > emotional_weight.user_holder config > 'self'. All six call sites route through it; doctor's freshness SQL is parameterized (). Upgrade note: upstream-owner brains with historical holder='garry' profiles should `gbrain config set emotional_weight.user_holder garry` to keep reading them. Co-authored-by: devty <devty@users.noreply.github.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(calibration): replace real-name holder fixture with charlie-example placeholder Privacy iron rule: no real people's names in checked-in code. The sanctioned placeholder mapping uses people/charlie-example. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Sinabina <sinabina@Sinabinas-MacBook-Pro-4.local> Co-authored-by: devty <devty@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Garry Tan <garrytan@gmail.com>
191 lines
7.5 KiB
TypeScript
191 lines
7.5 KiB
TypeScript
/**
|
|
* v0.36.1.0 (T12) — calibration doctor check tests.
|
|
*
|
|
* Hermetic. Mock engine + injected executeRaw responses.
|
|
*
|
|
* Tests cover:
|
|
* - checkAbandonedThreads: zero count → ok; non-zero → ok with count
|
|
* - checkCalibrationFreshness: missing profile → ok cold-brain; fresh → ok;
|
|
* stale > 7 days → warn with hint
|
|
* - checkGradeConfidenceDrift: < 30 applied → ok ("math arrives in v0.37+");
|
|
* >= 30 → ok placeholder
|
|
* - checkVoiceGateHealth: 0 in window → ok; high fail rate → warn
|
|
* - all checks return status='warn' with diagnostic on executeRaw throw
|
|
*/
|
|
|
|
import { describe, test, expect } from 'bun:test';
|
|
import {
|
|
checkAbandonedThreads,
|
|
checkCalibrationFreshness,
|
|
checkGradeConfidenceDrift,
|
|
checkVoiceGateHealth,
|
|
} from '../src/commands/doctor.ts';
|
|
import type { BrainEngine } from '../src/core/engine.ts';
|
|
|
|
function buildMockEngine(opts: {
|
|
abandonedCount?: number;
|
|
freshGeneratedAt?: Date | null;
|
|
gradeAppliedCount?: number;
|
|
voiceTotal?: number;
|
|
voiceFailures?: number;
|
|
throwOn?: RegExp;
|
|
}): BrainEngine {
|
|
return {
|
|
kind: 'pglite',
|
|
// #2464: checkCalibrationFreshness resolves the owner holder via
|
|
// resolveOwnerHolder(config emotional_weight.user_holder, else 'self'), so the
|
|
// mock must implement getConfig. null = key unset → resolver falls back to 'self'.
|
|
async getConfig(): Promise<string | null> {
|
|
return null;
|
|
},
|
|
async executeRaw<T>(sql: string): Promise<T[]> {
|
|
if (opts.throwOn && opts.throwOn.test(sql)) {
|
|
throw new Error('mock engine error: ' + sql.slice(0, 50));
|
|
}
|
|
if (sql.includes('FROM takes')) {
|
|
return [{ count: opts.abandonedCount ?? 0 } as unknown as T];
|
|
}
|
|
if (sql.includes('FROM calibration_profiles WHERE holder')) {
|
|
return [{ generated_at: opts.freshGeneratedAt ?? null } as unknown as T];
|
|
}
|
|
if (sql.includes('FROM take_grade_cache')) {
|
|
return [{ applied_count: opts.gradeAppliedCount ?? 0 } as unknown as T];
|
|
}
|
|
if (sql.includes('FROM calibration_profiles\n WHERE generated_at')) {
|
|
return [
|
|
{
|
|
total: opts.voiceTotal ?? 0,
|
|
failures: opts.voiceFailures ?? 0,
|
|
} as unknown as T,
|
|
];
|
|
}
|
|
return [] as T[];
|
|
},
|
|
} as unknown as BrainEngine;
|
|
}
|
|
|
|
// ─── abandoned_threads ──────────────────────────────────────────────
|
|
|
|
describe('checkAbandonedThreads', () => {
|
|
test('zero count → ok with no-abandoned message', async () => {
|
|
const out = await checkAbandonedThreads(buildMockEngine({ abandonedCount: 0 }));
|
|
expect(out.status).toBe('ok');
|
|
expect(out.message).toContain('No abandoned high-conviction threads');
|
|
});
|
|
|
|
test('non-zero count → ok with count + hint', async () => {
|
|
const out = await checkAbandonedThreads(buildMockEngine({ abandonedCount: 4 }));
|
|
expect(out.status).toBe('ok');
|
|
expect(out.message).toContain('4 high-conviction take(s)');
|
|
expect(out.message).toContain('gbrain calibration');
|
|
});
|
|
|
|
test('engine throw → warn with diagnostic (non-blocking)', async () => {
|
|
const out = await checkAbandonedThreads(buildMockEngine({ throwOn: /FROM takes/ }));
|
|
expect(out.status).toBe('warn');
|
|
expect(out.message).toContain('Could not check abandoned threads');
|
|
});
|
|
});
|
|
|
|
// ─── calibration_freshness ──────────────────────────────────────────
|
|
|
|
describe('checkCalibrationFreshness', () => {
|
|
test('no profile yet → ok cold-brain message', async () => {
|
|
const out = await checkCalibrationFreshness(buildMockEngine({ freshGeneratedAt: null }));
|
|
expect(out.status).toBe('ok');
|
|
expect(out.message).toContain('No calibration profile yet');
|
|
});
|
|
|
|
test('fresh profile (1 day old) → ok', async () => {
|
|
const d = new Date();
|
|
d.setDate(d.getDate() - 1);
|
|
const out = await checkCalibrationFreshness(buildMockEngine({ freshGeneratedAt: d }));
|
|
expect(out.status).toBe('ok');
|
|
expect(out.message).toContain('1d ago');
|
|
});
|
|
|
|
test('stale profile (>7 days) → warn with regenerate hint', async () => {
|
|
const d = new Date();
|
|
d.setDate(d.getDate() - 10);
|
|
const out = await checkCalibrationFreshness(buildMockEngine({ freshGeneratedAt: d }));
|
|
expect(out.status).toBe('warn');
|
|
expect(out.message).toContain('10 days old');
|
|
expect(out.message).toContain('gbrain calibration --regenerate');
|
|
});
|
|
|
|
test('boundary: 7 days old → still ok (NOT warn)', async () => {
|
|
const d = new Date();
|
|
d.setDate(d.getDate() - 7);
|
|
d.setMinutes(d.getMinutes() + 1); // slightly less than 7 full days
|
|
const out = await checkCalibrationFreshness(buildMockEngine({ freshGeneratedAt: d }));
|
|
expect(out.status).toBe('ok');
|
|
});
|
|
|
|
test('engine throw → warn with diagnostic', async () => {
|
|
const out = await checkCalibrationFreshness(
|
|
buildMockEngine({ throwOn: /FROM calibration_profiles WHERE holder/ }),
|
|
);
|
|
expect(out.status).toBe('warn');
|
|
expect(out.message).toContain('Could not check calibration freshness');
|
|
});
|
|
});
|
|
|
|
// ─── grade_confidence_drift ─────────────────────────────────────────
|
|
|
|
describe('checkGradeConfidenceDrift', () => {
|
|
test('fewer than 30 applied → ok placeholder', async () => {
|
|
const out = await checkGradeConfidenceDrift(buildMockEngine({ gradeAppliedCount: 12 }));
|
|
expect(out.status).toBe('ok');
|
|
expect(out.message).toContain('12 auto-applied verdicts');
|
|
expect(out.message).toContain('need 30');
|
|
});
|
|
|
|
test('>= 30 applied → ok placeholder with math-pending note', async () => {
|
|
const out = await checkGradeConfidenceDrift(buildMockEngine({ gradeAppliedCount: 50 }));
|
|
expect(out.status).toBe('ok');
|
|
expect(out.message).toContain('50 auto-applied verdicts');
|
|
expect(out.message).toContain('v0.37');
|
|
});
|
|
|
|
test('engine throw → warn with diagnostic', async () => {
|
|
const out = await checkGradeConfidenceDrift(buildMockEngine({ throwOn: /FROM take_grade_cache/ }));
|
|
expect(out.status).toBe('warn');
|
|
expect(out.message).toContain('Could not check grade confidence drift');
|
|
});
|
|
});
|
|
|
|
// ─── voice_gate_health ──────────────────────────────────────────────
|
|
|
|
describe('checkVoiceGateHealth', () => {
|
|
test('no profile in window → ok', async () => {
|
|
const out = await checkVoiceGateHealth(buildMockEngine({ voiceTotal: 0, voiceFailures: 0 }));
|
|
expect(out.status).toBe('ok');
|
|
expect(out.message).toContain('No calibration profile generation');
|
|
});
|
|
|
|
test('low fail rate → ok', async () => {
|
|
const out = await checkVoiceGateHealth(
|
|
buildMockEngine({ voiceTotal: 10, voiceFailures: 1 }),
|
|
);
|
|
expect(out.status).toBe('ok');
|
|
expect(out.message).toContain('1/10 failed');
|
|
});
|
|
|
|
test('30%+ fail rate → warn with rubric-review hint', async () => {
|
|
const out = await checkVoiceGateHealth(
|
|
buildMockEngine({ voiceTotal: 10, voiceFailures: 4 }),
|
|
);
|
|
expect(out.status).toBe('warn');
|
|
expect(out.message).toContain('4/10');
|
|
expect(out.message).toContain('voice-gate.ts');
|
|
});
|
|
|
|
test('engine throw → warn with diagnostic', async () => {
|
|
const out = await checkVoiceGateHealth(
|
|
buildMockEngine({ throwOn: /WHERE generated_at/ }),
|
|
);
|
|
expect(out.status).toBe('warn');
|
|
expect(out.message).toContain('Could not check voice gate health');
|
|
});
|
|
});
|