Name the subject a conflict is actually about

A shared verb is not a shared fact. 「客厅的恒温器调到 22 度」 and
「卧室的恒温器调到 24 度」 agree with each other; the store had been
recording them as one thermostat moving, and the older side then left the
answer page — asking about the living room returned the bedroom's reading
as the only evidence. A conflict now requires the two memories to lead
with the same subject, and the subject is the first content word rather
than the first word, so 「The router forwards 220 packets」 still leads
with its router behind the article.

The rule trades one error for another, and both directions are measured
and pinned: a qualifier on the subject reads as a different thing, and
the first number under a label is the value that gets compared. Neither
hides a memory — each leaves a disagreement unrecorded for a person to
supersede by hand.

Six review findings, all measured before being changed:
- the archive rule string hid that an unset importance archives too
- a never-archived id reported {"restored": 1} while nothing moved
- the suppression condition (both sides in the page) was undocumented
- a determinism claim had no reproducible path, two latency numbers were
  never committed, and one cited test name did not exist

Gates: 374 passed, six acceptance files 73 passed, ruff clean, mypy cold
cache 24 errors in 9 files (the recorded 9cb46c1 baseline, net 0),
pyright 0 on the touched files, go build/vet/test + gofmt clean, npm
bundle unchanged, product audit clean, MCP smoke passing across processes.
This commit is contained in:
ZacharyZhang-NY committed 2026-10-09 09:49:25 -04:00
1 parent deabbf76b8
commit 3daface332
7 files changed
+185 -26

No files matched your search

+9 -9
View File
@@ -137,18 +137,18 @@ class MemoryService:
"""A number that moves inside one holder's scenario is a disagreement, not a second fact.
Only a memory that replaces nothing can start a conflict: a supersede has already
picked a side, and recording it as unresolved would contradict that.
picked a side, and recording it as unresolved would contradict that. And only a memory that
leads with the same subject as its neighbour: two rooms' thermostats are two facts.
"""
if atom.supersedes:
return
stated = trust.numeric_values(atom.text)
if not stated:
if not trust.numeric_values(atom.text).values:
return
for earlier in lifecycle_store.lane_atoms(self.db, atom.holder_id, atom.scenario):
if earlier.atom_id == atom.atom_id:
continue
term = trust.conflicting_number(trust.numeric_values(earlier.text), stated)
if term is None:
clash = trust.quantity_conflict(earlier.text, atom.text)
if clash is None:
continue
lifecycle_store.record_conflict(
self.db,
@@ -156,9 +156,9 @@ class MemoryService:
newer_atom_id=atom.atom_id,
holder_id=atom.holder_id,
scenario=atom.scenario,
terms=[term],
older_value=trust.numeric_values(earlier.text)[term],
newer_value=stated[term],
terms=[clash.term],
older_value=clash.older_value,
newer_value=clash.newer_value,
detected_at=utc_now(),
)
@@ -360,7 +360,7 @@ class MemoryService:
moved = 0 if dry_run else lifecycle_store.archive_atoms(self.db, candidates, moment)
if not dry_run and moved != len(candidates):
raise ValueError(f"{len(candidates)} memories were chosen for the archive but {moved} were moved")
floor = "any importance" if max_importance is None else f"importance at or under {max_importance}"
floor = "any importance" if max_importance is None else f"importance unset or at or under {max_importance}"
return ArchiveReport(
atom_ids=candidates,
archived_at=moment,