Skip to content

fix(docs): correct entity-resolution framing around merged provenance (#427) - #438

Merged
jasonssdev merged 1 commit into
mainfrom
feat/427-reach-same-verdicts
Aug 6, 2026
Merged

fix(docs): correct entity-resolution framing around merged provenance (#427)#438
jasonssdev merged 1 commit into
mainfrom
feat/427-reach-same-verdicts

Conversation

@jasonssdev

Copy link
Copy Markdown
Owner

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:line evidence:

  • provenance is 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).
  • Both measured pairs were nominated correctly: "MCP Workflows"/"Model Context Protocol" by the ACRONYM tier, "Skill Creation Process"/"Skill Creator" by the LOW tier (SequenceMatcher("creator","creation").ratio() ≈ 0.8 clears the 0.75 threshold).
  • Both were adjudicated DIFFERENT correctlyadjudication.py:52-58 instructs the model to reject part-whole and aspect relationships as duplicates.
  • _MAX_CANDIDATE_GROUPS = 50 was 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 SAME pair arises in the corpus, construct one."

The new test constructs one. It runs the real find_candidatesadjudicate_candidates → merge chain, stubbing only OllamaClient — 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 with list(survivor_list):

AssertionError: assert ['sources/not...-enchiridion'] == ['sources/not...l-with-maria']
  Right contains one more item: 'sources/call-with-maria'
1 failed, 93 deselected

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:

  1. 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.
  2. The two-source list is reachable — two sources each yielding a Concept titled "Stoicism" share one normalize_key(title), which is the HIGH tier's trigger; a SAME verdict 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:

File Problem
examples/README.md:5 "what the MVP 1 ingest should produce" — ingest alone writes single-element provenance literals (cli/main.py:1955, :2795)
docs/knowledge-object-model.md:91 quoted verbatim with no indication the two-source provenance is the product of a merge
docs/knowledge-object-model.md:311 "as new sources arrive, the engine rewrites existing objects" — overclaims passive, automatic behavior

The fixtures themselves are untouched.

Left out, deliberately

The one genuine reachability gap found is strict okf_type partitioning in _keyed_docs_by_type (candidates.py:208-246, pinned by test_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 breaks CandidateGroup's single-okf_type contract and changes a partitioning invariant two other call sites depend on.

Verification

pytest -q              -> 3706 passed in 120.98s
ruff check .           -> All checks passed!
ruff format --check .  -> 171 files already formatted
mypy . --strict        -> Success: no issues found in 171 source files

Review lineage review-bc49ea0e43232da2review-reliability lens, inspection completed, zero findings; pre-commit/pre-push/pre-pr gates all allow.

…#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
@jasonssdev
jasonssdev merged commit e6dd8af into main Aug 6, 2026
6 checks passed
@jasonssdev
jasonssdev deleted the feat/427-reach-same-verdicts branch August 6, 2026 10:58
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/`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

entity resolution is the accumulation path: make SAME reachable, and correct the docs that promise a synthesis step

1 participant