mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-31 04:07:52 +00:00
Rebase-port of #2452 (spinsirr:fix/calibration-profile-scope-and-cli) onto current master after tonight's merges made the fork branch conflict. - cli: add 'calibration' to CLI_ONLY so dispatch reaches its existing handler instead of falling through to "Unknown command" (#2035); honor --source / GBRAIN_SOURCE in the calibration CLI. - takes: route takes_list / takes_search / takes_scorecard / takes_calibration through sourceScopeOpts(ctx) (federated array > scalar > nothing) and scope engine reads via the take's page.source_id — JOIN filter for list/search, EXISTS for scorecard/curve — on both engines (#2200-class). - bigint: shared takeHitRowToHit coercion in searchTakes / searchTakesVector (both engines) + bigintToStringReplacer on the cli.ts output normalizer and the `gbrain call` exit, so int8/BIGSERIAL columns no longer crash JSON.stringify (#2450); calibration profile id BIGSERIAL → string. - calibration: default model ids route through TIER_DEFAULTS (provider-prefixed) instead of bare model strings; admin calibration chart endpoints fixed (takes has no page_slug column; month-precision since_date; Date generated_at; bigint id in drill-down). The think/gather source-scope slice of the original PR was dropped: it already landed on master via #2739. Co-authored-by: Sinabina <sinabina@Sinabinas-MacBook-Pro-4.local> Co-authored-by: spinsirr <ID+spinsirr@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
260 lines
9.4 KiB
TypeScript
260 lines
9.4 KiB
TypeScript
/**
|
|
* v0.36.1.0 (T14 / E8 + D18) — cross-brain calibration query tests.
|
|
*
|
|
* Hermetic. Mock engines stand in for local + mounted brains. The four
|
|
* D18 e2e test cases are pinned here so cross-brain leak surfaces don't
|
|
* regress silently.
|
|
*
|
|
* Tests cover:
|
|
* D18-1: published=false profile on mount → returns null (no leak)
|
|
* D18-2: published=true but consumer lacks mount-read scope → null (subagent)
|
|
* D18-3: subagent context attempts mount fallback → returns local-only
|
|
* D18-4: attribution: profile returns with source_brain_id surfaced
|
|
* + local-first ordering (rule 1)
|
|
* + mount priority order (first match wins)
|
|
* + null when neither local nor mount has it
|
|
* + canReadMountsForCtx classifier table
|
|
*/
|
|
|
|
import { describe, test, expect } from 'bun:test';
|
|
import {
|
|
queryAcrossBrains,
|
|
canReadMountsForCtx,
|
|
attributionSuffix,
|
|
type CrossBrainQueryOpts,
|
|
} from '../src/core/calibration/cross-brain.ts';
|
|
import type { BrainEngine } from '../src/core/engine.ts';
|
|
import type { CalibrationProfileRow } from '../src/commands/calibration.ts';
|
|
|
|
function buildProfile(opts: { published: boolean; source_id?: string; holder?: string } = { published: false }): CalibrationProfileRow {
|
|
return {
|
|
id: '1',
|
|
source_id: opts.source_id ?? 'default',
|
|
holder: opts.holder ?? 'garry',
|
|
wave_version: 'v0.36.1.0',
|
|
generated_at: '2026-05-17T00:00:00Z',
|
|
published: opts.published,
|
|
total_resolved: 12,
|
|
brier: 0.21,
|
|
accuracy: 0.6,
|
|
partial_rate: 0.1,
|
|
grade_completion: 1.0,
|
|
pattern_statements: ['some pattern'],
|
|
active_bias_tags: ['over-confident-geography'],
|
|
voice_gate_passed: true,
|
|
voice_gate_attempts: 1,
|
|
model_id: 'claude-sonnet-4-6',
|
|
};
|
|
}
|
|
|
|
function buildEngine(profile: CalibrationProfileRow | null): BrainEngine {
|
|
return {
|
|
kind: 'pglite',
|
|
async executeRaw<T>(_sql: string): Promise<T[]> {
|
|
return profile ? ([profile] as unknown as T[]) : ([] as T[]);
|
|
},
|
|
} as unknown as BrainEngine;
|
|
}
|
|
|
|
// ─── D18-1: published=false on mount → null ────────────────────────
|
|
|
|
describe('D18-1: published=false profile on mount stays hidden', () => {
|
|
test('returns null when local empty AND only mount profile has published=false', async () => {
|
|
const localEngine = buildEngine(null);
|
|
const mountEngine = buildEngine(buildProfile({ published: false, source_id: 'mount-team' }));
|
|
const out = await queryAcrossBrains(localEngine, {
|
|
holder: 'garry',
|
|
localBrainId: 'garry-personal',
|
|
canReadMounts: true,
|
|
mountResolver: async () => [{ brainId: 'team-brain', engine: mountEngine }],
|
|
});
|
|
expect(out).toBeNull();
|
|
});
|
|
});
|
|
|
|
// ─── D18-2 / D18-3: subagent context cannot read mounts ────────────
|
|
|
|
describe('D18-2/3: subagent context cannot fall back to mounts', () => {
|
|
test('canReadMounts=false short-circuits to null when local has no profile', async () => {
|
|
const localEngine = buildEngine(null);
|
|
const mountEngine = buildEngine(buildProfile({ published: true }));
|
|
const out = await queryAcrossBrains(localEngine, {
|
|
holder: 'garry',
|
|
localBrainId: 'garry-personal',
|
|
canReadMounts: false,
|
|
mountResolver: async () => [{ brainId: 'team-brain', engine: mountEngine }],
|
|
});
|
|
expect(out).toBeNull();
|
|
});
|
|
|
|
test('canReadMounts=false but local hit → local result still returned', async () => {
|
|
const localEngine = buildEngine(buildProfile({ published: false }));
|
|
const out = await queryAcrossBrains(localEngine, {
|
|
holder: 'garry',
|
|
localBrainId: 'garry-personal',
|
|
canReadMounts: false,
|
|
});
|
|
expect(out).not.toBeNull();
|
|
expect(out!.from_mount).toBe(false);
|
|
expect(out!.source_brain_id).toBe('garry-personal');
|
|
});
|
|
});
|
|
|
|
// ─── D18-4: attribution surfaces source_brain_id ───────────────────
|
|
|
|
describe('D18-4: cross-brain attribution', () => {
|
|
test('mount answer carries from_mount=true + source_brain_id', async () => {
|
|
const localEngine = buildEngine(null);
|
|
const mountEngine = buildEngine(buildProfile({ published: true, source_id: 'team-default' }));
|
|
const out = await queryAcrossBrains(localEngine, {
|
|
holder: 'garry',
|
|
localBrainId: 'garry-personal',
|
|
canReadMounts: true,
|
|
mountResolver: async () => [{ brainId: 'partners-team', engine: mountEngine }],
|
|
});
|
|
expect(out).not.toBeNull();
|
|
expect(out!.from_mount).toBe(true);
|
|
expect(out!.source_brain_id).toBe('partners-team');
|
|
});
|
|
|
|
test('local hit carries from_mount=false + local brain id', async () => {
|
|
const localEngine = buildEngine(buildProfile({ published: false }));
|
|
const out = await queryAcrossBrains(localEngine, {
|
|
holder: 'garry',
|
|
localBrainId: 'garry-personal',
|
|
canReadMounts: true,
|
|
});
|
|
expect(out!.from_mount).toBe(false);
|
|
expect(out!.source_brain_id).toBe('garry-personal');
|
|
});
|
|
|
|
test('attributionSuffix emits "from mounted brain" only when from_mount=true', () => {
|
|
const mountResult = {
|
|
...buildProfile({ published: true }),
|
|
source_brain_id: 'team-brain',
|
|
from_mount: true,
|
|
};
|
|
expect(attributionSuffix(mountResult)).toContain('from mounted brain: team-brain');
|
|
|
|
const localResult = {
|
|
...buildProfile({ published: false }),
|
|
source_brain_id: 'garry-personal',
|
|
from_mount: false,
|
|
};
|
|
expect(attributionSuffix(localResult)).toBe('');
|
|
});
|
|
});
|
|
|
|
// ─── Rule 1: LOCAL-FIRST ordering ──────────────────────────────────
|
|
|
|
describe('local-first ordering (D18 rule 1)', () => {
|
|
test('local hit short-circuits — mountResolver NOT called', async () => {
|
|
const localEngine = buildEngine(buildProfile({ published: false }));
|
|
let mountResolverCalls = 0;
|
|
const opts: CrossBrainQueryOpts = {
|
|
holder: 'garry',
|
|
localBrainId: 'garry-personal',
|
|
canReadMounts: true,
|
|
mountResolver: async () => {
|
|
mountResolverCalls++;
|
|
return [];
|
|
},
|
|
};
|
|
await queryAcrossBrains(localEngine, opts);
|
|
expect(mountResolverCalls).toBe(0);
|
|
});
|
|
|
|
test('local empty + mount populated → mountResolver IS called', async () => {
|
|
const localEngine = buildEngine(null);
|
|
const mountEngine = buildEngine(buildProfile({ published: true }));
|
|
let mountResolverCalls = 0;
|
|
await queryAcrossBrains(localEngine, {
|
|
holder: 'garry',
|
|
localBrainId: 'garry-personal',
|
|
canReadMounts: true,
|
|
mountResolver: async () => {
|
|
mountResolverCalls++;
|
|
return [{ brainId: 'team', engine: mountEngine }];
|
|
},
|
|
});
|
|
expect(mountResolverCalls).toBe(1);
|
|
});
|
|
});
|
|
|
|
// ─── Mount priority order: first match wins ────────────────────────
|
|
|
|
describe('mount priority order', () => {
|
|
test('first published=true mount in the list wins', async () => {
|
|
const localEngine = buildEngine(null);
|
|
const mountA = buildEngine(buildProfile({ published: false, source_id: 'a' }));
|
|
const mountB = buildEngine(buildProfile({ published: true, source_id: 'b' }));
|
|
const mountC = buildEngine(buildProfile({ published: true, source_id: 'c' }));
|
|
const out = await queryAcrossBrains(localEngine, {
|
|
holder: 'garry',
|
|
localBrainId: 'garry-personal',
|
|
canReadMounts: true,
|
|
mountResolver: async () => [
|
|
{ brainId: 'mount-a', engine: mountA },
|
|
{ brainId: 'mount-b', engine: mountB },
|
|
{ brainId: 'mount-c', engine: mountC },
|
|
],
|
|
});
|
|
// mount-a has published=false, skipped; mount-b is first published=true.
|
|
expect(out!.source_brain_id).toBe('mount-b');
|
|
});
|
|
|
|
test('all mounts have published=false → returns null', async () => {
|
|
const localEngine = buildEngine(null);
|
|
const mountA = buildEngine(buildProfile({ published: false }));
|
|
const mountB = buildEngine(buildProfile({ published: false }));
|
|
const out = await queryAcrossBrains(localEngine, {
|
|
holder: 'garry',
|
|
localBrainId: 'garry-personal',
|
|
canReadMounts: true,
|
|
mountResolver: async () => [
|
|
{ brainId: 'a', engine: mountA },
|
|
{ brainId: 'b', engine: mountB },
|
|
],
|
|
});
|
|
expect(out).toBeNull();
|
|
});
|
|
|
|
test('no mounts configured + local empty → null without throwing', async () => {
|
|
const localEngine = buildEngine(null);
|
|
const out = await queryAcrossBrains(localEngine, {
|
|
holder: 'garry',
|
|
localBrainId: 'garry-personal',
|
|
canReadMounts: true,
|
|
});
|
|
expect(out).toBeNull();
|
|
});
|
|
});
|
|
|
|
// ─── canReadMountsForCtx classifier ────────────────────────────────
|
|
|
|
describe('canReadMountsForCtx classifier', () => {
|
|
test('local CLI (remote=false) → true', () => {
|
|
expect(canReadMountsForCtx({ remote: false })).toBe(true);
|
|
});
|
|
|
|
test('MCP non-subagent (remote=true, viaSubagent=undefined) → true', () => {
|
|
expect(canReadMountsForCtx({ remote: true })).toBe(true);
|
|
});
|
|
|
|
test('subagent without trusted-workspace prefixes → false (D18 rule 4)', () => {
|
|
expect(
|
|
canReadMountsForCtx({ remote: true, viaSubagent: true, allowedSlugPrefixes: [] }),
|
|
).toBe(false);
|
|
});
|
|
|
|
test('subagent with trusted-workspace prefixes (cycle synthesize/patterns) → true', () => {
|
|
expect(
|
|
canReadMountsForCtx({
|
|
remote: true,
|
|
viaSubagent: true,
|
|
allowedSlugPrefixes: ['wiki/agents/synthesize/*'],
|
|
}),
|
|
).toBe(true);
|
|
});
|
|
});
|