Skip to content

PersonResolver: canonical identity resolution in dotty-behaviour (fixes 4 audit identity bugs)#159

Merged
BrettKinny merged 2 commits into
mainfrom
feat/person-resolver
Jun 11, 2026
Merged

PersonResolver: canonical identity resolution in dotty-behaviour (fixes 4 audit identity bugs)#159
BrettKinny merged 2 commits into
mainfrom
feat/person-resolver

Conversation

@BrettKinny

Copy link
Copy Markdown
Owner

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, with Person.id as the canonical key space.

Audit findings fixed

Finding (AUDIT-REPORT 2026-06-06) Verification Fix
room_view roster recognition silently fails when id != display_name (core path) confirmed 3/3 parse_room_view_response now takes the resolver and maps the echoed display name → Person.id
same id/display_name mismatch on the greeter path confirmed 3/3 same fix — the bus now always carries canonical ids
room_view NAME parser cannot match multi-word display names ("Mary Anne" = 100% silent miss) confirmed 3/3 NAME regex allows internal spaces/apostrophes; resolver folds case + whitespace
greeter calendar lookup never matches an identified person ([Hudson]hudson) confirmed 2/3 summarize_for_prompt matches case-insensitively and accepts the resolver's equivalent-tag set (id, display name, calendar_prefix)
(bonus, LOW) bracketless calendar_prefix: YAML never matched store + lookup both normalise through one helper

The 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 id fields, plus regression fixtures for id != display_name ("Greg" → dad) and multi-word names.

Design

PersonResolver is stateless and cheap (delegates to the hot-reloading registry per call, so household.yaml edits 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.py stays decoupled via a structural protocol.

Test plan

  • ruff check . clean
  • Full suite: 97 (root + pi_voice) + 230 (dotty-behaviour) passing; combined coverage 69.5% (gate 56%)
  • 17 new/updated cases: test_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 reaches hudson's greeting prompt)

Refs the AUDIT-REPORT 2026-06-06 findings (no tracker issue exists for these; happy to file one retroactively if preferred).

AI assistance: drafted by Claude Fable 5 (analysis, implementation, tests, and this PR body); human review pending per AI_TRANSPARENCY.md.

🤖 Generated with Claude Code

BrettKinny and others added 2 commits June 11, 2026 19:43
…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>
@BrettKinny BrettKinny merged commit fff90f1 into main Jun 11, 2026
8 checks passed
@BrettKinny BrettKinny deleted the feat/person-resolver branch June 11, 2026 10:56
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.

1 participant