Contradiction flagging at ingest — reuse brain-gardener's Judge, do not build a second one #104
Closed
opened 2026-08-22 11:58:49 +00:00 by mathias
·
3 comments
No Branch/Tag Specified
main
fix/pipeline-writes-158
fix/edit-path-io-192
fix/create-regular-188
fix/guard-special-179
fix/linkcheck-keeps-write-178
fix/write-dir-redirect-173
fix/tunnel-detect-174
fix/pipeline-root-157
w3/154-mcp-auth-wiring
fix/tunnel-traversal
test/in-brain-targets-171
fix/brain-scan-root-157
fix/graph-wiki-root-157
fix/vectorstore-root-157
fix/notes-root-157
fix/search-root-157
fix/brainwide-root-157
fix/raw-symlinks-153
feat/symlink-tripwire-157
fix/rest-auth-149
fix/123-brain-edit-envelope-check
feat/106-page-aware-extraction
fix/105-source-drift-on-main
fix/104-surface-merge-conflicts-on-main
agent/b1-compile-on-ingest
agent/104-surface-merge-conflicts
agent/105-source-drift
agent/107-loud-unconfigured-alerting
feat/brain-read-edit
fix/capture-i1-refusal-names-target
renovate/configure
feat/consolidate-single-harness
icebox/cmd-routing
feat/brain-pending-promote
fix/close-session-classification-refresh
fix/capture-summarywriter
v0.24.0
v0.23.1
v0.23.0
v0.22.0
v0.21.1
v0.21.0
v0.20.1
v0.20.0
v0.19.1
v0.19.0
v0.18.4
v0.18.3
v0.18.2
v0.18.1
v0.18.0
v0.17.0
v0.16.1
v0.16.0
v0.15.0
v0.14.0
v0.13.2
v0.13.1
v0.13.0
v0.12.1
v0.12.0
v0.11.2
icebox/cmd-routing-2026-07-01
v0.11.1
v0.11.0
v0.10.0
v0.9.0
v0.8.0
v0.7.0
v0.6.0
v0.5.0
v0.4.1
v0.4.0
v0.3.0
v0.2.0
v0.1.0
Labels
Clear labels
agent/ready
blocked/client
blocked/supplier
blocked/upstream
risk/high
risk/low
risk/medium
size/l
size/m
size/s
size/xs
status:dispatch
status:dispatched
status:review
status:reviewing
type/chore
type/decision
type/docs
type/feat
type/fix
type/ops
type/research
type/test
Meets the agent-ready contract; authorizes unattended execution
Waiting on the client
Waiting on a supplier
Waiting on an upstream project or vendor
Irreversible, security-relevant, or control plane
Reversible, local, test-covered
Touches shared state, deploys or data
Too big - split it
Under two days
Under half a day
Under an hour
Maintenance, no behaviour change
Human decision required; never agent/ready
Documentation only
New capability
Corrects broken behaviour
Infrastructure, deploy, runtime
Investigate; output is a finding, not code
Test coverage only
Milestone
No items
No Milestone
Projects
Clear projects
No projects
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: mathias/hyperguild#104
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Sprint B item 2, rescoped after checking what already exists. Filed so it stops living only in a conversation.
The original framing, and why it's wrong
The item was written as "contradiction flagging at ingest, against existing pages. Not a future batch job — inline, at write time. Local 35B is the intended model."
The inline-at-write-time part is right. "Build a contradiction detector" is not — one already exists and runs hourly.
mathias/brain-gardenerhas a Judge-gated contradiction pipeline ininternal/enrich:match.Contradictsfrom a cue-word matcher (brain-gardener#164)OldClaimSpanbefore any write (brain-gardener#175) — because a cue-word match is not evidencebrain-enrichCronJob,37 * * * *, live on koalaBuilding a second detector at ingest time would mean two implementations of "does A contradict B", drifting apart, with no shared calibration — and the harder half (the Judge, and knowing what a span is) is the part already solved.
What this issue actually is
Move the existing Judge to a second call site, not write a new one.
brain-enrichinto something callable fromingestion/internal/pipeline— a shared package, or an HTTP call to a service that owns it. Decide which; the two repos do not share a module today, which is the main design question here.contested: <slug>from brain#21's strength family. That field was specified to be a pointer, not a boolean, precisely so this detector's output survives capture.Sequencing
This is what makes brain#21's strength half worth shipping —
contestedhas nothing to point at until a detector populates it. So: this issue, then brain#21'sconfidence/contested+ lint.Watch for
The enrich pipeline's own history is a warning list. brain-gardener#177 found it never proposed Contradicts claims because there was no way to derive
OldClaimSpan; #175 found a cue-word match being trusted without a Judge. A naive ingest-time port would reintroduce both.Risk: MEDIUM — it puts an LLM call on the ingest write path. Latency and failure-mode behaviour need deciding: a Judge timeout must not fail the ingest, and must not silently mark nothing as contested either (the nil-notifier lesson, hyperguild#100).
Refs: brain#21, #103, brain-gardener#164, #175, #177, #181, brain-gardener ADR-0001.
Solved without the cross-repo problem — PR #110
The blocking question on this issue was where the Judge lives: shared Go module across two repos, or an HTTP service. Neither, as it turns out.
The merge was already computing the answer and discarding it. #103's asymmetry means a fresh extraction that produces a different version of an existing section loses to the on-disk copy — and that discarded text is the detection: the source now says something different about a section we already hold. It was going in the bin unremarked.
So #110 returns it instead of dropping it:
No LLM on the write path, no cue-word matcher, no shared module, no service that does not exist. Deterministic, and it lands in
log.mdfor free becauseAppendLogalready writes run warnings there.What I deliberately did not do
Not semantic contradiction detection. Two versions differing is not proof one contradicts the other — it might be a rewording or an expansion. Adjudicating that stays with the Judge-gated
brain-enrichpass, which already runs hourly, already derivesOldClaimSpan, and is approval-gated by design. Detection here, adjudication there — and the detection is free rather than a second judgement pipeline drifting from the first, which was this issue's main risk.Not populating brain#21's
contested. That pointer marks a note contradicting another note, established by a Judge. Filling it from a textual diff would put an unjudged claim in a field whose entire purpose is to mark judged ones.What is left of this issue
The genuinely-inline semantic check — an LLM confirming at write time that a new claim contradicts an existing one — is still unbuilt, and now needs a reason to exist beyond "it was on the list. The cheap signal is shipped; the expensive one should be justified by a case the cheap one misses.
If that case turns up, the design constraints stand and are worth keeping on record: latency on the write path, a timeout that must neither fail the ingest nor silently mark nothing as contested, and brain-gardener#177's warning that the enrich pipeline once never proposed a Contradicts claim at all because nothing could derive the span.
Closing on merge of #110. Reopen against that narrower scope if the gap proves real.
Review of PR #129 (fix/104-surface-merge-conflicts-v2) — ESCALATED, not merged.
Code quality: solid. Reviewed the full diff, reconstructed merge_test.go against
9345a0a(clean append, 20 unique test funcs, no drops/dupes from the tangled rebase), rango build/vet/test -raceclean across all packages, flip-testedTestMerge_ReportsSectionsWhereTheFreshVersionWasDiscarded(goes red when the comparison is disabled — not decoration), and confirmed by direct adversarial test that bullet/append sections never false-conflict, multiple conflicts report in order with no dupes, and pure leading/trailing whitespace does not false-positive. CI green (run 7850, Lint/Test/Vet). One minor finding: the conflict comparison usesstrings.TrimSpaceonly, so an internal whitespace-run difference (e.g. a double space mid-line from re-extraction) DOES false-positive as a conflict, unlikeappendNew/mergeBulletswhich usecanonicalLine(per-line whitespace-run normalization). Low severity — warning-only, no data loss — but untested and inconsistent with this file's established whitespace-tolerance policy; worth a follow-up.The actual blocker: this PR does not satisfy this issue's acceptance criteria, and the scope change was never confirmed by Mathias.
The issue's checklist asks for: (1) extracting the Judge-gated contradiction check from
brain-gardener'sinternal/enrichinto something callable from ingest, (2) calling it during merge, (3) populatingcontested: <slug>(brain#21's strength-family pointer) on a confirmed contradiction, (4) never auto-superseding. PR #129 does none of these — it adds a purely textual, non-semantic diff check that logs a warning line tolog.md. It does not call any Judge, does not touchcontested, and by its own doc comment explicitly declines to ("Deliberately NOT a semantic contradiction check").That narrowing is real and defensible engineering — but it was announced in a single self-authored issue comment (2026-08-22, same account/voice as the PR's own code comments) that unilaterally redefines what "done" means for this issue and states "Closing on merge of #110" with no visible reply or approval from Mathias. Per this estate's own rule, an architectural/requirements decision like this must surface as
DECISION NEEDEDand wait — it cannot be self-granted by the same agent building the PR, and no agent comment is ever standing in for Mathias's consent.Recommendation, not yet actioned: either (a) Mathias confirms the descoped interim (textual flag, no Judge, no
contested:) is what he wants #104 to mean, and this PR merges with the issue closed on that explicit basis, or (b) the PR merges but #104 is NOT closed — reworded/narrowed to track the still-unbuilt semantic Judge integration, with a fresh issue capturing what actually shipped. Not merging until that call is made.Head SHA reviewed:
719eb8c2d6. CI: run 7850, success.Closed by PR #129, merge commit
cdafdf6e0f0d34d0d0e2e1220e2f6a5b0a588f90.Final review verified: scope decision (Option A, textual-diff-only, semantic detection deferred) confirmed genuinely by Mathias in PR comment 12594, cross-referenced onto this issue. Reviewed commit
b05e9c4(canonicalBlock whitespace-run fix) directly: flip-tested by making canonicalBlock a no-op — the new whitespace test (TestMerge_ReportsNothingForInternalWhitespaceVariantsOnly) went red as expected, while TestMerge_ReportsSectionsWhereTheFreshVersionWasDiscarded (genuine content difference) stayed green throughout. Adversarial tests confirmed canonicalBlock does not collapse distinct lines together and still catches genuine word-level changes. Full module suite (go build ./... && go vet ./... && go test ./... -race -count=1) green across all 31 packages, no races. CI green on headb05e9c4(run 7851, Lint/Test/Vet success).This closes out the #103/#105/#104 stack.