feat(pipeline): report when a re-ingest disagrees with the page on disk (#104)
The asymmetry that makes unattended ingest safe (#103) also discarded a real signal. When a fresh extraction produced a different version of a section already on disk, the on-disk version won and the model's version went in the bin without a word — so "the source is unchanged" and "the source changed its mind and we ignored it" were indistinguishable. wiki.MergeReport now returns the headings where the incoming version disagreed and lost, and the ingest run reports them as warnings, which AppendLog already writes into log.md. This is the simplified answer to contradiction flagging at ingest. The issue proposed calling brain-gardener's Judge from the ingest path, which needs either a shared Go module across two repos or an HTTP service that does not exist — and puts an LLM call on the write path, where a timeout must neither fail the ingest nor silently mark nothing as contested. None of that is necessary to get the signal. The merge already computes the disagreement; it was throwing it away. Surfacing it costs nothing, needs no model, no cue-word matcher, no cross-repo dependency, and is deterministic. Deliberately textual disagreement, not semantic contradiction. Deciding whether a disagreement is a genuine contradiction stays with the Judge-gated enrich pass that already runs hourly and is approval-gated by design (brain-gardener#175). Detection here, adjudication there. Merge is kept as a thin wrapper over MergeReport so existing callers are untouched. Refs #104, #103, brain-gardener#175, brain#21.
This commit is contained in:
@@ -117,10 +117,18 @@ func buildAndWrite(rawPages []RawPage, sourceSlug, date, brainDir, source string
|
||||
if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil {
|
||||
return Result{}, fmt.Errorf("mkdir for %s: %w", page.Path, err)
|
||||
}
|
||||
content, err := reviseOrCreate(dest, page)
|
||||
content, conflicts, err := reviseOrCreate(dest, page)
|
||||
if err != nil {
|
||||
return Result{}, err
|
||||
}
|
||||
for _, heading := range conflicts {
|
||||
// Reported, not resolved. The on-disk version stands; whether
|
||||
// the disagreement is a genuine contradiction is the
|
||||
// Judge-gated enrich pass's call, not the write path's.
|
||||
warnings = append(warnings, fmt.Sprintf(
|
||||
"%s: source now disagrees on section %q — on-disk version kept",
|
||||
page.Path, heading))
|
||||
}
|
||||
if err := os.WriteFile(dest, []byte(content), 0o644); err != nil {
|
||||
return Result{}, fmt.Errorf("write %s: %w", page.Path, err)
|
||||
}
|
||||
@@ -198,15 +206,16 @@ func storedSourceHash(brainDir, sourceSlug string) string {
|
||||
// its first-ingest date on a revised page. Left deliberately — the alternative
|
||||
// is this function editing frontmatter it does not own, and no lint reads that
|
||||
// field today.
|
||||
func reviseOrCreate(dest string, page wiki.Page) (string, error) {
|
||||
func reviseOrCreate(dest string, page wiki.Page) (string, []string, error) {
|
||||
existing, err := os.ReadFile(dest)
|
||||
switch {
|
||||
case os.IsNotExist(err):
|
||||
return page.Content, nil
|
||||
return page.Content, nil, nil
|
||||
case err != nil:
|
||||
return "", fmt.Errorf("read existing %s: %w", page.Path, err)
|
||||
return "", nil, fmt.Errorf("read existing %s: %w", page.Path, err)
|
||||
}
|
||||
return wiki.Merge(wiki.Page{Path: page.Path, Content: string(existing)}, page).Content, nil
|
||||
merged, conflicts := wiki.MergeReport(wiki.Page{Path: page.Path, Content: string(existing)}, page)
|
||||
return merged.Content, conflicts, nil
|
||||
}
|
||||
|
||||
// mergeAll deduplicates pages by path, merging content from later occurrences.
|
||||
|
||||
Reference in New Issue
Block a user