Compile-on-ingest, second half: let the extraction see the page it is revising #106
Closed
opened 2026-08-22 11:59:32 +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#106
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.
Follow-on from #103, which shipped the deterministic half. Filed so the remaining half is tracked rather than implied.
What #103 does and does not do
Does: ingest now merges into the page already on disk instead of clobbering it, asymmetrically in the existing page's favour — on-disk sections and frontmatter win, extraction-only sections are added, lists union. Re-ingest is byte-identical when nothing changed.
Does not: the extraction itself is still page-blind. The model produces a page as if writing it fresh, and
wiki.Mergereconciles the two texts afterwards by heading. So:That is a real ceiling: the merge can only combine texts, it cannot compile them.
The second half
Feed the current page into the extraction prompt, so the model revises with knowledge of what exists:
BuildPromptwhen the target path already existsWhy it wants #104 alongside it
"The model rewrote this claim" and "the model contradicted this claim" are the same event observed twice. A revision pass without contradiction detection produces silent overwrites of meaning while preserving the text's shape — which is harder to notice than a clobber, not easier.
So the sequencing is #104 first or together, never this alone.
Also carried from #103
last_updatedkeeps its first-ingest date on a revised page, because the on-disk frontmatter wins. Deliberate for now — the alternative is the writer editing frontmatter it does not own — but if a generator-owned frontmatter block lands (see #105's ownership question), this is the natural field to put in it.Risk: MEDIUM — larger prompt, more expensive extraction, and a model given the current page will sometimes echo it back verbatim. Needs a test that a revision pass on unchanged input still produces a byte-identical page.
Refs: #103, #104, #105, #43.
Review of PR #130 (feat/106-page-aware-extraction) — BLOCKED, not merged
What's solid:
BuildPrompt's existing-page plumbing is real (empty string confirmed on a fresh source, non-empty only whenwiki/sources/<slug>.mdexists), the revision framing is scoped correctly to the source page only.TestRun_RevisionEchoingExistingPageStaysByteIdenticalis genuinely end-to-end (Run → mockedComplete→Merge→ written file) and usesassert.Equal, notContains. Flip-testednormalizeHeadingby making it a no-op: bothTestMerge_HeadingCaseDriftMatchesSameSectionandTestMerge_HeadingWhitespaceDriftMatchesSameSectionwent red as expected — not decoration. Full module suite (go build ./... && go vet ./... && go test ./... -race -count=1) green, 31 packages, no races — both on the PR branch tip and after merging currentmain(which now includes #104'scdafdf6, landed on main after this branch was cut) into it locally; the auto-merge is clean (no conflict markers) and stays green.The blocking finding — normalizeHeading's
idxmap silently drops lookup visibility for pre-existing duplicate-by-case/whitespace headings ina.Merge/MergeReportbuildsidxby iteratingsecsAand writingidx[normalizeHeading(s.heading)] = i— last write wins. Before this PR,idxwas keyed by the exact heading string, so two on-disk sections differing only by case (a state the issue's own body cites as real:"Key Claims"vs"Key claims") never collided. After this PR, they do collide, and only the last one registered inidxis reachable for matching againstb.Reproduced directly (adversarial test, not in the PR's suite):
Result: the fresh bullet from
bmerges into the second, differently-cased duplicate (## KEY CLAIMS), not the first.- claim twoin the original section never receives it — and won't on any future compile-on-ingest pass, since the same collision recurs every merge. No conflict is reported either (bullet/append sections don't go throughMergeReport's disagreement path). This is exactly the bug class the file's own comment onparseSectionswarns about ("two entries sharing one key in Merge's idx map means one of them silently loses lookup visibility — found in review as real, reproducible data loss, not a hypothetical") — reintroduced by this PR's own normalization, for any page with natural pre-existing heading-casing drift, which is precisely the failure mode #106 was filed to fix for newly arriving headings, not guard against for existing ones.Not a hypothetical corpus state: any wiki page hand-edited or ingested before this normalization existed could carry such a duplicate today, and this PR silently starts routing all future updates to only one of them.
Not blocking, confirmed fine:
bulletSectionsNorm/appendSectionsNormare symmetric with theidxbuild/lookup sides (no naming/call-site asymmetry). Interaction with the preamble mechanism (heading"") is unaffected — preamble is never part ofsecsA. Composition with #104'sMergeReport/canonicalBlockconflict-reporting (merged to main after this branch was cut) is textually clean and semantically consistent.CI green: run 7858 (Lint/Test/Vet + Mirror), head SHA
3564498a1bddd65ce8546a2f3d4285b21614275b.Recommendation: fix
idxconstruction to detect same-a-side collisions (at minimum: keep the first match rather than silently overwriting, and ideally surface the pre-existing ambiguity rather than picking either silently) before merging. Reopening the loop on this file's own track record — this is bug #7 today, same class as #1–6.Review of PR #132 — NOT merged, blocking finding
Rebase mechanics: clean.
origin/mainHEAD iscdafdf6(#104's merge commit), exactly the PR's merge-base — no drift since opening.mergeable: truevia API.var conflicts []stringis declared before any append (only appended inside thesBloop), so its position relative toidx-building is provably inert — moving it earlier changes nothing observable. #104'scanonicalBlockcomparison anddefault:disagreement-report case are both present, unduplicated, unreordered inMergeReport. Confirmed viagrep/git grep(not the PR body's claim) that the production write path ispipeline.go:230reviseOrCreate→wiki.MergeReportdirectly — the fix is live on the real path, not the vestigialMergewrapper (used elsewhere for pre-write raw-page dedup,pipeline.go:127, discards conflicts by design). CI green (run 7862, head921bea8).go build/vet/test -race -count=1: 31 packages, all green, no races.Blocking finding — MergeReport reports a false "source disagrees" conflict when merging a page against an unchanged copy of itself, if the page carries pre-existing duplicate-by-normalized-heading sections.
idxnow keys bynormalizeHeading, first occurrence wins, so a page with## Notesand## NOTES(different content — the exact drift #106 exists to handle) only exposes the first occurrence's index for lookup. Whenb's sections mirrora's structure (the "model echoes the existing page back verbatim" case #106's own body flags as expected and the review round required a byte-identical test for),b's secondNOTESsection gets compared againstsecsA[idx["notes"]]— the first occurrence — not its own counterpart. Since the two on-disk duplicates differ in content by construction,canonicalBlocksees a mismatch and appends a conflict, even though nothing changed betweenaandb.This directly breaks the suite's own stated invariant,
TestMerge_ReportsNothingWhenTheFreshVersionAgrees("identical content is not a disagreement") — that test just never carries a pre-existing duplicate heading, so the gap wasn't visible. Reproduced live againstMergeReporton the PR branch:Effect: every wiki page with pre-existing case/whitespace-duplicate headings (confirmed real by prior review, cited in #106's own body) now emits a permanent, deterministic false-positive "source now disagrees on section... on-disk version kept" warning on every future ingest that echoes structure back — undermining #104's disagreement signal specifically for the corpus condition this PR was built to fix. Not data loss (content is unaffected), but it is new observable behavior on the production write path, since #130's version of this same commit fed the vestigial
Mergewrapper that silently discardedconflicts— this bug was inert until this rebase wired it intoMergeReport.Other adversarial cases (3-way case collision with a genuine content disagreement, bullet-section union against a first-occurrence target with a pre-existing duplicate) behaved correctly — only the self-merge/no-op-echo case against a duplicate-heading target is wrong.
Not merged. Recommend: before matching a normalized
idxcollision, canonicalize/consolidate duplicate on-disk sections themselves (or comparesBagainst its own positional counterpart whenb's section list itself contains the same duplicate structure) so an unchanged round-trip stays conflict-free even on already-drifted pages. #103/#105/#104 remain resolved; #106 is not closeable on this branch as-is — bug #8 on this file today.Round 3 review of PR #132, head
bdba49b— merged as9a37881441.What changed since round 2: MergeReport now pairs duplicate-heading occurrences positionally (byKey map[string][]int + cursor per key) instead of routing every occurrence of a normalized heading to a single index. This fixes the round-2 bug where merging a self-echoed page with pre-existing case-duplicate headings (e.g. "## Notes" / "## NOTES") falsely reported a conflict.
Independent verification performed:
921bea8.pos := cursor[key]topos := 0(reverting to round-2 behavior). Result: TestMergeReport_SelfMergeOfPreExistingDuplicateHeadingsReportsNoConflict went red (reported a false [NOTES] conflict), while TestMerge_PreExistingCaseDuplicateHeadingsRouteToTheFirstOccurrence (the #130 regression test) stayed green — confirms first-occurrence-wins is the pos==0 special case, not a conflicting behavior.byKey[key]=append(...,len(secsA))beforesecsA=append(secsA,sB)is correct); 2 in a / 0 in b (both survive verbatim); bullet-strategy heading with 2 dupes on each side (unions into the correct respective duplicate, no cross-leak). All four held — no ninth bug found.go build ./... && go vet ./... && go test ./... -race -count=1from ingestion/: all green.mergeable: trueagainst current main at merge time.bdba49bvia commit status API: combined statesuccess(CI / Lint / Test / Vet, run 7865).The #103/#104/#105/#106 arc touching wiki/merge.go is now resolved: 8 real bugs found across 7 review rounds today, all fixed and verified, full test suite + race detector green, no regressions reintroduced by the fix-for-the-fix.