PersonResolver: canonical identity resolution in dotty-behaviour (fixes 4 audit identity bugs)#159
Merged
Merged
Conversation
…ge labels, domain docs)
Adds docs/agents/{issue-tracker,triage-labels,domain}.md and an
## Agent skills section in CLAUDE.md so the Matt Pocock engineering
skills know: issues live on GitHub (BrettKinny/dotty-stackchan via gh),
the five triage roles map to default label strings (orthogonal to the
existing status:/area: labels), and domain docs are single-context.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Identity-bearing strings reach dotty-behaviour from three name spaces (VLM room_view replies = display names, perception-bus identities = person ids, calendar [Name] title prefixes = free-typed), and each consumer re-implemented its own mapping. The 2026-06-06 audit found four separate identity bugs as a result. All resolution now funnels through household.PersonResolver; the canonical key is Person.id. Fixed by the consolidation: - room_view roster recognition silently failed whenever id != display_name — the prompt vocabulary was display names but validation compared ids (audit, confirmed 3/3; core + greeter paths). parse_room_view_response now resolves the echoed name to Person.id. - multi-word display names never matched — the NAME regex was a single whitespace-free token, so "Mary Anne" was a 100% silent miss (confirmed 3/3). The regex now allows internal spaces/apostrophes. - the greeter's calendar lookup dropped a person's own events on a case mismatch ([Hudson] != hudson, confirmed 2/3). summarize_for_prompt matches case-insensitively and accepts the resolver's equivalent-tag set (id, display name, calendar_prefix). - bracketless `calendar_prefix:` YAML never matched — get_by_calendar_prefix stored bare but looked up bracketed; both sides now normalise through one helper. The room_view test fakes were also fixed to carry real `id` fields — the old fakes re-derived ids from display names, which is exactly what masked the production bug — plus regression fixtures for id != display_name and multi-word names. Tests: 17 new (test_person_resolver.py + regression cases across the room_view / route / registry / calendar / greeter suites); full suite 327 passing, coverage 69.5% (gate 56%). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
What
Candidate 2 from the 2026-06-11 pre-release architecture review: identity resolution was smeared across consumers — the VLM speaks display names, the perception bus speaks person ids, and the calendar speaks free-typed
[Name]prefixes — and each consumer re-implemented its own mapping. The 2026-06-06 audit found four separate identity bugs in those re-implementations. This PR funnels all resolution through one deep module,household.PersonResolver, withPerson.idas the canonical key space.Audit findings fixed
id != display_name(core path)parse_room_view_responsenow takes the resolver and maps the echoed display name →Person.id[Hudson]≠hudson)summarize_for_promptmatches case-insensitively and accepts the resolver's equivalent-tag set (id, display name, calendar_prefix)calendar_prefix:YAML never matchedThe audit also called out that the room_view test fakes masked the core bug by re-deriving ids from display names — the fakes now carry real
idfields, plus regression fixtures forid != display_name("Greg" →dad) and multi-word names.Design
PersonResolveris stateless and cheap (delegates to the hot-reloading registry per call, sohousehold.yamledits still apply without a restart). Interface:resolve(identity),resolve_vlm_name(name),resolve_calendar_tag(tag),calendar_tags(identity). Consumers stop parsing names; future consumers (curiosity mode, security roster) query the same seam.vision/room_view.pystays decoupled via a structural protocol.Test plan
ruff check .cleantest_person_resolver.py(id/display/multi-word/case/punctuation/calendar-tag/empty-registry), room_view unit + route regressions, registry bracketless-prefix, calendar case + tag-set, and a greeter end-to-end ([Hudson]event reacheshudson's greeting prompt)Refs the AUDIT-REPORT 2026-06-06 findings (no tracker issue exists for these; happy to file one retroactively if preferred).
🤖 Generated with Claude Code