fix(cycle): thread cycleSourceId into the schema-suggest phase (#3701)

The phase was calling runSchemaSuggestPhase(engine, { dryRun }) with no
sourceId, so it silently fell back to 'default' on every source's dream
cycle -- same bug class as upstream #1586 (synthesize) and #2666
(patterns/synthesize), just an undiscovered instance for this phase.
Confirmed live: schema-events audit log shows only source=default across
41 entries this week despite calendar/mail/mem/social cycles all running
the phase.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Time Attakc <89218912+time-attack@users.noreply.github.com>
This commit is contained in:
Tyler Singletary
2026-08-01 05:02:48 +08:00
committed by GitHub
co-authored by Claude Sonnet 5 Time Attakc
parent 13d95ba0ab
commit 4c0ec60275
2 changed files with 25 additions and 1 deletions
+1 -1
View File
@@ -2441,7 +2441,7 @@ export async function runCycle(
try {
const { runSchemaSuggestPhase } = await import('./cycle/schema-suggest.ts');
const { result, duration_ms } = await timePhase(async () => {
const r = await runSchemaSuggestPhase(engine, { dryRun: !!opts.dryRun });
const r = await runSchemaSuggestPhase(engine, { sourceId: cycleSourceId, dryRun: !!opts.dryRun });
return {
phase: 'schema-suggest' as const,
status: (r.skipped ? 'skipped' : 'ok') as PhaseStatus,
+24
View File
@@ -22,6 +22,7 @@ let extractCalls: Array<{ mode: string; dir: string; slugs: string[] | undefined
let embedCalls: Array<{ stale: boolean | undefined; dryRun: boolean | undefined }> = [];
let orphansCalls: number = 0;
let orphansOpts: Array<{ sourceId?: string } | undefined> = [];
let schemaSuggestOpts: Array<{ sourceId?: string; dryRun?: boolean } | undefined> = [];
// Mock lint
mock.module('../../src/commands/lint.ts', () => ({
@@ -116,6 +117,14 @@ mock.module('../../src/commands/orphans.ts', () => ({
formatOrphansText: () => '',
}));
// Mock schema-suggest
mock.module('../../src/core/cycle/schema-suggest.ts', () => ({
runSchemaSuggestPhase: async (_engine: any, opts?: { sourceId?: string; dryRun?: boolean }) => {
schemaSuggestOpts.push(opts);
return { suggestions_emitted: 0, source_id: opts?.sourceId ?? 'default', skipped: false };
},
}));
// Import after mocks.
const { runCycle, ALL_PHASES } = await import('../../src/core/cycle.ts');
const { PGLiteEngine } = await import('../../src/core/pglite-engine.ts');
@@ -151,6 +160,7 @@ beforeEach(() => {
embedCalls = [];
orphansCalls = 0;
orphansOpts = [];
schemaSuggestOpts = [];
});
// ─── dryRun propagation (regression guards) ────────────────────────
@@ -519,6 +529,20 @@ describe('runCycle — sourceId resolution (regression #475)', () => {
expect(orphansOpts.at(-1)).toEqual({ sourceId: 'alpha' });
});
// schema-suggest (T12 cathedral phase) was never threaded through
// cycleSourceId — it silently fell back to 'default' for every source,
// the same bug class as #1586 (synthesize) and #2666 (patterns), just
// undiscovered for this phase. Pins the fix: the resolved per-source id
// must reach runSchemaSuggestPhase the same way it reaches orphans/sync.
test('seeded sources row → schema-suggest phase receives matching sourceId (not "default")', async () => {
await (sharedEngine as any).db.query(
`INSERT INTO sources (id, name, local_path) VALUES ($1, $2, $3)`,
['bravo', 'bravo', '/tmp/brain-schema-suggest-bravo'],
);
await runCycle(sharedEngine, { brainDir: '/tmp/brain-schema-suggest-bravo', phases: ['schema-suggest'] });
expect(schemaSuggestOpts.at(-1)?.sourceId).toBe('bravo');
});
test('forceGlobalOrphans keeps orphans brain-wide even when brainDir maps to a source', async () => {
await (sharedEngine as any).db.query(
`INSERT INTO sources (id, name, local_path) VALUES ($1, $2, $3)`,