fix(docs): correct entity-resolution framing around merged provenance (#427) - #438
Merged
Conversation
…#427) Prove #379 criterion 1 end to end and fix the docs that misrepresented how the good-life-demo bundle's two-source provenance comes to exist. - Add a CLI-level regression guard asserting find_candidates -> adjudicate_candidates (SAME) -> merge unions provenance from both source documents, with a stubbed LLMBackend and no real Ollama call. It runs the real resolution chain rather than monkeypatching it, and was mutation- checked against okf.py's _union_dedup to confirm it fails when the union breaks. - examples/README.md: ingest alone cannot produce two-source provenance; it requires entity resolution too. - docs/knowledge-object-model.md: the worked example's provenance is the product of a SAME merge, not a single compile; "the engine rewrites existing objects... as new sources arrive" overclaimed automatic behavior -- name the real mechanism. - Record the exploration, which corrects a false first-pass claim: the good-life-demo fixtures are reachable via a SAME merge and consistent with their own citations, so the fix is the prose around them, not the fixtures. The one real reachability gap, strict okf_type partitioning, is filed as #437. Closes #427
This was referenced Aug 6, 2026
jasonssdev
added a commit
that referenced
this pull request
Aug 9, 2026
`openspec/changes/` held three folders containing only `exploration.md`: `bound-generation-length` (#422), `reach-same-verdicts` (#427) and `surface-merged-body-contradictions` (#409). All three issues shipped and closed WITHOUT an SDD cycle -- #422 via PR #439, #427 via PR #438, #409 via PRs #442/#443 -- so these are abandoned exploration notes, not parked work. They are deleted rather than archived: every one of the 30+ entries under `openspec/changes/archive/` is a completed SDD cycle carrying proposal/design/tasks/specs plus an archive report, and none holds a single file. Moving exploration-only stubs there would redefine the archive from "finished cycle" to "folder I stopped using". Git history keeps the notes if they are ever wanted. `openspec/changes/` now holds only `archive/`.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #427.
Unblocks #379 criterion 1, the P0 validation gate's only remaining criterion that needed code.
What this is
#427's product decision was already made: lean on entity resolution, do not build a cross-source synthesis step. This ships the two halves that decision implies.
Half A — the accumulation path was already sound
The 8-source run's evidence said the union path was broken. It is not. Verified with
file:lineevidence:provenanceis not in_SPECIAL_KEYS(src/openkos/model/okf.py:1029-1035), so it falls to the list branch and is unioned via_union_dedup(:1040-1043).SequenceMatcher("creator","creation").ratio() ≈ 0.8clears the0.75threshold).DIFFERENTcorrectly —adjudication.py:52-58instructs the model to reject part-whole and aspect relationships as duplicates._MAX_CANDIDATE_GROUPS = 50was never the binding constraint; the run produced 2 groups.Candidate generation worked, the adjudicator was right, and the corpus simply contained no true duplicate. So Half A is a proof obligation, not a repair — which is what #379 criterion 1 anticipated: "If no natural
SAMEpair arises in the corpus, construct one."The new test constructs one. It runs the real
find_candidates→adjudicate_candidates→ merge chain, stubbing onlyOllamaClient— unlike its neighbours in the same module, which monkeypatch the resolution functions themselves.The test was mutation-checked
It passed on its first run with no production change. That is expected for a regression guard over existing behavior, but it is also the signature of a test that asserts nothing. So a mutant was injected at
okf.py:1043, replacing the union withlist(survivor_list):The guard detects a real regression. The mutant was reverted and the tree restored byte-for-byte.
Half B — three prose corrections, zero fixture changes
A first analysis concluded the good-life-demo fixtures' two-source
provenance:was unreachable and should be trimmed to one source. That was wrong on two counts, and the exploration records the correction:stoicism.md's body carries[1]/[2]markers and a Citations block naming both sources. Trimming would leave the document citing a source its own provenance denies.Concepttitled "Stoicism" share onenormalize_key(title), which is the HIGH tier's trigger; aSAMEverdict merges them and unions the provenance. It is a picture of entity resolution having fired.What was actually wrong is the framing around the fixtures:
examples/README.md:5ingestshould produce" —ingestalone writes single-element provenance literals (cli/main.py:1955,:2795)docs/knowledge-object-model.md:91docs/knowledge-object-model.md:311The fixtures themselves are untouched.
Left out, deliberately
The one genuine reachability gap found is strict
okf_typepartitioning in_keyed_docs_by_type(candidates.py:208-246, pinned bytest_cross_type_identical_normalized_title_produces_no_candidate): a duplicate whose two sources were classified into different OKF types is never nominated. It is filed separately as issue #437 rather than folded in here — it breaksCandidateGroup's single-okf_typecontract and changes a partitioning invariant two other call sites depend on.Verification
Review lineage
review-bc49ea0e43232da2—review-reliabilitylens, inspection completed, zero findings;pre-commit/pre-push/pre-prgates allallow.