feat(pipeline): report when a re-ingest disagrees with the page on disk (#104) #110

Closed
mathias wants to merge 1 commits from agent/104-surface-merge-conflicts into agent/105-source-drift
Owner

Closes #104 by a much smaller route than the issue proposed. Stacked on #109 (which is stacked on #103) — review order is #103 → #109 → this.

The simplification

#104 proposed calling brain-gardener's Judge from the ingest path. That needs either a shared Go module across two repos that don't share one, or an HTTP service that doesn't exist (brain-enrich is a CronJob) — and it 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 needed to get the signal.

#103's asymmetry means that when a fresh extraction produces a different version of a section already on disk, the on-disk version wins and the model's version is discarded. That discard is the detection. The merge was already computing "the source now says something different about this" and throwing it away.

So: return it instead.

wiki/sources/shape-up.md: source now disagrees on section "Summary" — on-disk version kept

No model on the write path, no cue-word matcher, no cross-repo dependency, deterministic, and it lands in log.md for free because AppendLog already writes run warnings there.

What this is not

Textual disagreement, not semantic contradiction. Two sections differing is not proof that one contradicts the other — it might be a rewording, an expansion, or the model being sloppy.

Deciding that is the Judge-gated brain-enrich pass's job. It already runs hourly, already derives OldClaimSpan, and is approval-gated by design (brain-gardener ADR-0001: audit autonomous, enrich gated). Detection here, adjudication there — and the detection is now free rather than a second LLM pipeline that would drift from the first.

This also means I have not populated brain#21's contested field. That pointer is for a note contradicting another note, established by a Judge. Setting it from a textual diff would put an unjudged claim in a field whose whole purpose is to mark judged ones.

Why the signal matters on its own

Without it, "the source is unchanged" and "the source changed its mind and we ignored it" are indistinguishable from outside — and #109 makes the first case silent by design (skip, no work). Those two outcomes must not look alike.

Tests

Red-first, both levels. MergeReport names only the section that actually disagreed and reports nothing for identical content; the pipeline test asserts the on-disk version still wins and that the run names both the section and the page.

Merge is kept as a thin wrapper over MergeReport, so no existing caller changed.

task check exit 0, full suite green.

Risk: LOW — additive reporting. The merge result is byte-identical to #103's; only the return signature gained a second value.

Closes #104 by a much smaller route than the issue proposed. **Stacked on #109** (which is stacked on #103) — review order is #103 → #109 → this. ## The simplification #104 proposed calling brain-gardener's Judge from the ingest path. That needs either a shared Go module across two repos that don't share one, or an HTTP service that doesn't exist (brain-enrich is a CronJob) — and it 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 needed to get the signal.** #103's asymmetry means that when a fresh extraction produces a *different version* of a section already on disk, the on-disk version wins and the model's version is discarded. That discard **is the detection**. The merge was already computing "the source now says something different about this" and throwing it away. So: return it instead. ``` wiki/sources/shape-up.md: source now disagrees on section "Summary" — on-disk version kept ``` No model on the write path, no cue-word matcher, no cross-repo dependency, deterministic, and it lands in `log.md` for free because `AppendLog` already writes run warnings there. ## What this is not **Textual disagreement, not semantic contradiction.** Two sections differing is not proof that one contradicts the other — it might be a rewording, an expansion, or the model being sloppy. Deciding that is the Judge-gated `brain-enrich` pass's job. It already runs hourly, already derives `OldClaimSpan`, and is approval-gated by design (brain-gardener ADR-0001: audit autonomous, enrich gated). **Detection here, adjudication there** — and the detection is now free rather than a second LLM pipeline that would drift from the first. This also means I have *not* populated brain#21's `contested` field. That pointer is for a note contradicting another note, established by a Judge. Setting it from a textual diff would put an unjudged claim in a field whose whole purpose is to mark judged ones. ## Why the signal matters on its own Without it, "the source is unchanged" and "the source changed its mind and we ignored it" are indistinguishable from outside — and #109 makes the first case *silent by design* (skip, no work). Those two outcomes must not look alike. ## Tests Red-first, both levels. `MergeReport` names only the section that actually disagreed and reports nothing for identical content; the pipeline test asserts the on-disk version still wins **and** that the run names both the section and the page. `Merge` is kept as a thin wrapper over `MergeReport`, so no existing caller changed. `task check` exit 0, full suite green. **Risk:** LOW — additive reporting. The merge result is byte-identical to #103's; only the return signature gained a second value.
mathias added 1 commit 2026-08-22 20:52:54 +00:00
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.
Author
Owner

Superseded by #127: same commit, rebased onto #126 (which replaces #109) instead of force-pushing over this stale branch.


Created via git-mcp on behalf of @mathias

Superseded by #127: same commit, rebased onto #126 (which replaces #109) instead of force-pushing over this stale branch. --- _Created via git-mcp on behalf of @mathias_
mathias closed this pull request 2026-09-23 04:52:12 +00:00

Pull request closed

Please reopen this pull request to perform a merge.
Sign in to join this conversation.