A name is a fact, and the extractor reports the other names it reads - #670
Merged
Merged
Conversation
WaylandYang
force-pushed
the
feat/a-name-is-a-fact
branch
from
September 13, 2026 10:13
8c88b32 to
054a7d8
Compare
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: WaylandYang <wayland0916@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: WaylandYang <wayland0916@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: WaylandYang <wayland0916@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: WaylandYang <wayland0916@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: WaylandYang <wayland0916@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: WaylandYang <wayland0916@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: WaylandYang <wayland0916@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: WaylandYang <wayland0916@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: WaylandYang <wayland0916@gmail.com>
WaylandYang
force-pushed
the
docs/a-name-is-a-claim-about-an-entity
branch
from
September 13, 2026 10:51
7799424 to
668f2e4
Compare
WaylandYang
force-pushed
the
feat/a-name-is-a-fact
branch
from
September 13, 2026 10:55
054a7d8 to
6d6a631
Compare
Signed-off-by: WaylandYang <wayland0916@gmail.com> # Conflicts: # docs/decisions/README.md
Base automatically changed from
docs/a-name-is-a-claim-about-an-entity
to
dev
September 13, 2026 13:51
…te stays builtin, a memory waits for a nod Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: WaylandYang <wayland0916@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: WaylandYang <wayland0916@gmail.com>
This was referenced Sep 13, 2026
Signed-off-by: WaylandYang <wayland0916@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: WaylandYang <wayland0916@gmail.com>
This was referenced Sep 13, 2026
Merged
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.
Implements cut 1 of 0041. The record and the identity bench landed in #663; the status line now also carries the re-measurement below.
What changes
Names are facts. Migration 0055 creates a builtin
known_asattribute per base, turns every entity's canonical name into a value fact on it, turnsentities.aliasesinto name facts and drops the column. A name keeps its string inobject_valuewith noobject_id, so it never becomes a node and the canvas never draws it. The temporal engine only closes and conflicts functional or inverse-functional state relations, andknown_asis neither.For entities already merged when the migration runs, the name fact is placed on the survivor at the end of the
merged_intochain and appended tomoved_subject_factsof every active merge on the chain (and toinvalidated_factswhen the survivor already has that name), so reverting any link sends the name back exactly as a live merge would.Resolution reads name facts. Same-type recall, cross-type drift,
existing_by_nameand the containment scan all readknown_asfacts, former names included.merge_entitiesandrevert_mergelose their alias special cases: names move with the other facts.The extractor reports names. The contract gets
names: [{ref, name, quote}]and rule 1b (other names only, never a pronoun, a description, or something that belongs to the entity). The server keeps a name only if the name is in its quote and the quote is in the chunk, and drops it (name_claimed_by_another) if the same response or an earlier chunk of the document declares that name for a different entity. The name the model wrote for an entity gets the chunk as evidence when it appears there.A shared name is sent to the adjudicator. A name another entity of a compatible type already has queues the pair (
shared_name|…, adjudicating stage), never a merge. The adjudicator and the Review card see an entity's non-canonical names as analso known as:line; the canonical name is not listed, so two namesakes do not appear to share evidence.Readers. Node degree,
entity_detailfacts, Review card fact lines and side degree exclude names. The entity detail API and theentity_factstool (chat and MCP) returnnamesseparately.known_asis removed from the extraction prompt lists, the ontology bootstrap and the ontology vector index.Measured
Identity bench from #663, DeepSeek-V3.2 + bge-m3, one run per order:
A full name and its abbreviation now resolve to one entity in both arrival orders. Two cut-1 runs differed by 0.07 forward with no code change in between, so treat single-run numbers as approximate. Still split: a former name, a name in another script (Li Na, Haitan-2) — cut 2; the two 张伟 — cut 3. Not caught: a description the model reports as a name without declaring it as an entity ("海探1项目").
Verified
cargo testfor store, server and extract (491 passed), clippy with-D warnings, fmt.a_name_is_a_fact(5) and the extractor'snamesparsing, contract text and claimed-name check.Review fixes, 2026-09-13
A review before the release found defects outside the paths the tests covered. All are fixed on this branch, each with a test that fails on the previous head where one could be written.
1. A pair kept apart was queued again.
pair_shared_namerelied oncreate_review, which only de-duplicates pending pairs. Every later chunk repeating the alias opened a new card and another adjudicator call after someone had decided the two are different. It now skips pairs with akeptreview. The Review page wordsshared_nameinstead of showing the code. Test:a_pair_kept_apart_is_not_queued_again.2.
known_ascould be reached as an ordinary attribute. The ontology vector index still embedded it, so proposals and attribute adoption could map "former_name"-like values onto it throughrelation_type_id_by_key; the uniqueness panel suggested it for every entity with two names; andupdate_relation_typeacceptedfunctionalon it, which would turn every second name into a conflict. Now it is not embedded, not found by key, cannot be edited (builtin_name_attribute), and is not a uniqueness candidate. Test:the_name_attribute_is_not_an_ordinary_attribute.3. Names from a memory log skipped the nod (0018). Other facts from a memory document wait in
pending_facts; the names loop wrote live name facts and pairings. A name read from a memory now becomes a pendingknown_asproposal; confirming it writes the name fact. No pairing happens at that point.4. The governance agent was handed bare names as quotes.
quotes_ofkeeps one quote per chunk ordered by confidence, and name facts (confidence 1.0, quote = the name) won. Name evidence whose quote is only the name is left out, and other facts come first within a chunk. An alias with a sentence (简称海探1) is still offered. Test:a_bare_name_is_not_a_quote_and_a_deleted_document_keeps_the_name.5. Name lookups scanned every base's facts. The correlated
EXISTSnever filtered the fact row's base, sofacts_value_text_idxcould not be used.has_name_inis now an uncorrelatedINover the base with the index predicate. On a synthetic base (200k entities, 45k merges) the same-type recall query went from 393 ms to 46 ms; the name part is an index lookup (0.5 ms, was 198 ms), and what remains is the scan over entities caused by theOR, which predates this PR.6. The migration was quadratic. Survivor lookup,
moved_subject_factsand duplicate detection ran a correlated subquery per row over unindexed temp tables. They are now one pass each (DISTINCT ON, grouped arrays joined once,row_number()). On the synthetic base: 707 s before, 30 s now, same results (202,800 name facts, 15,000 duplicates invalidated, 90,000 moved and 30,000 invalidated ids recorded on merges). A user-defined non-builtinknown_asrelation now gives up its key first instead of silently receiving every name.Also:
changes) no longer lists "X known as X" for every new entity; new aliases still appear.scripts/bench/govern.mjsandask.mjsno longer readentities.aliases.Checked again
cargo test --no-fail-fastfor store, server and extract on a migrated scratch database withUTOPIA_TEST_REQUIRE_DB=1: 521 passed, 0 failed, three times. The three new store tests fail on the previous head. Clippy-D warnings, fmt, web style guard,tsc, Vitest (51) clean.moved_subject_facts; every invalidated duplicate is in a merge'sinvalidated_facts. Reverting all 394 merges newest-first left every entity with exactly its own name.rememberon a running server): "海洋探测器1号(简称海探1)…" produced a pendingknown_as= 海探1 and no live name fact; confirming it created the fact with its evidence, and an entity search for 海探1 then found 海洋探测器1号.Forward is identical across all runs. The reverse difference is one adjudicator ruling on the two 李娜 (merged at 0.95 in one run, kept apart at 0.90 in the other two); the inputs of that pair do not pass through anything changed here. On this model the adjudicator keeps 海洋探测器1号 and 海探1 apart (0.95) in the reverse order even though the full-name entity lists 海探1 — before and after the fixes — so the reverse-order gain reported with V3.2 above does not reproduce on V3. That is the adjudicator's judgement, which is what cut 3 ("evidence decides") addresses.
🤖 Generated with Claude Code