mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-30 11:22:34 +00:00
fix(extract): preserve facts on parse warnings (#3494)
This commit is contained in:
@@ -17,11 +17,12 @@
|
||||
* DB rows need cleanup (#1781 — the unconditional wipe-and-reinsert
|
||||
* made every cycle non-idempotent, re-appending duplicate rows).
|
||||
*
|
||||
* After the phase, the DB index for every affected page matches the
|
||||
* fence's canonical (claim, source) row set (modulo embeddings +
|
||||
* runtime-derived fields). Pages with no fence wipe DB rows for that
|
||||
* page coordinate only; legacy NULL-source_markdown_slug rows survive
|
||||
* because deleteFactsForPage targets source_markdown_slug = slug only.
|
||||
* After the phase, the DB index for every cleanly parsed affected page
|
||||
* matches the fence's canonical (claim, source) row set (modulo embeddings
|
||||
* + runtime-derived fields). Warning-bearing parses are non-authoritative
|
||||
* and preserve that page's existing index. Pages with no fence wipe DB rows
|
||||
* for that page coordinate only; legacy NULL-source_markdown_slug rows
|
||||
* survive because deleteFactsForPage targets source_markdown_slug = slug only.
|
||||
*
|
||||
* Empty-fence guard (Codex R2-#7; #2484; #2646): the phase refuses to do
|
||||
* its destructive reconciliation pass when genuinely-backfillable legacy
|
||||
@@ -333,6 +334,11 @@ export async function runExtractFacts(
|
||||
result.warnings.push(
|
||||
...parsed.warnings.map(w => `${slug}: ${w}`),
|
||||
);
|
||||
// The parser deliberately skips malformed rows and returns any rows it
|
||||
// could still recover. That partial result is not authoritative: using
|
||||
// it for reconciliation would interpret skipped rows as deletions.
|
||||
// Preserve this page's existing index and continue with other pages.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (parsed.facts.length > 0) result.pagesWithFacts += 1;
|
||||
|
||||
@@ -231,6 +231,44 @@ describe('runExtractFacts — happy path', () => {
|
||||
expect(rows.rows[0].fact).toBe('A');
|
||||
});
|
||||
|
||||
test('malformed fence rows make the page non-authoritative and preserve its indexed facts', async () => {
|
||||
await putPage('people/alice', FACT_FENCE(
|
||||
`| 1 | A | fact | 1.0 | world | medium | 2026-01-01 | | s | |
|
||||
| 2 | B | fact | 1.0 | world | medium | 2026-01-01 | | s | |`,
|
||||
));
|
||||
await runExtractFacts(engine, { slugs: ['people/alice'] });
|
||||
|
||||
// A hand edit corrupts row 2. The parser can still recover row 1, but
|
||||
// that partial result is not an authoritative replacement for the page.
|
||||
await putPage('people/alice', FACT_FENCE(
|
||||
`| 1 | A | fact | 1.0 | world | medium | 2026-01-01 | | s | |
|
||||
| 2 | B | bogus | 1.0 | world | medium | 2026-01-01 | | s | |`,
|
||||
));
|
||||
await putPage('people/bob', FACT_FENCE(
|
||||
`| 1 | Clean | fact | 1.0 | world | medium | 2026-01-01 | | s | |`,
|
||||
));
|
||||
|
||||
const r = await runExtractFacts(engine, { slugs: ['people/alice', 'people/bob'] });
|
||||
|
||||
expect(r.warnings.some(w => w.includes('FACTS_TABLE_MALFORMED'))).toBe(true);
|
||||
expect(r.pagesScanned).toBe(2);
|
||||
expect(r.factsInserted).toBe(1);
|
||||
expect(r.factsDeleted).toBe(0);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const rows = await (engine as any).db.query(
|
||||
`SELECT fact FROM facts WHERE source_markdown_slug = 'people/alice' ORDER BY row_num`,
|
||||
);
|
||||
expect(rows.rows.map((row: { fact: string }) => row.fact)).toEqual(['A', 'B']);
|
||||
|
||||
// A warning is page-local: clean pages in the same cycle still reconcile.
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const cleanRows = await (engine as any).db.query(
|
||||
`SELECT fact FROM facts WHERE source_markdown_slug = 'people/bob'`,
|
||||
);
|
||||
expect(cleanRows.rows.map((row: { fact: string }) => row.fact)).toEqual(['Clean']);
|
||||
});
|
||||
|
||||
test('page with no facts fence → DB facts for that page wiped (empty fence reconciles to empty index)', async () => {
|
||||
await putPage('people/alice', FACT_FENCE(
|
||||
`| 1 | seeded | fact | 1.0 | world | medium | 2026-01-01 | | s | |`,
|
||||
|
||||
Reference in New Issue
Block a user