Skip to content

R-X5: a guest id cannot yet be held as an external reference - #19

Merged
robertblust merged 1 commit into
mainfrom
docs/guest-id-as-external-reference
Aug 21, 2026
Merged

R-X5: a guest id cannot yet be held as an external reference#19
robertblust merged 1 commit into
mainfrom
docs/guest-id-as-external-reference

Conversation

@robertblust

Copy link
Copy Markdown
Contributor

The point of a golden profile is that other systems can hold its guestId as the authoritative reference for a person — a PMS or CRM storing it as a foreign key. That is what makes GuestGraph the system of record for identity rather than a de-duplication report.

They cannot today, and the way it fails is quiet.

The gap

A merge deletes the absorbed guest (ResolutionEngine.executedeleteGuest), so the id simply stops existing. Observed, not inferred — ingest two records that resolve separately, then one carrying both identifiers:

merge status : MERGED
GET /guests/{absorbedId} -> 404 NOT_FOUND
{"detail":"No guest 600b49b7-… in this tenant","status":404,"title":"Resource not found"}

The reference breaks on exactly the event this product exists to produce. An unmerge that empties a guest retires an id the same way (UnmergeOperation), so the mapping is not always 1:1:

What happenedThe old id should resolve to
X absorbed into YY — transitively, if Y was later absorbed into Z
X fully unmergedthe several current guests, or none

No test covered this, which is why it went unnoticed.

Nothing is lost — no query walks it

merge_event is append-only and already records both the survivor (guest_id) and absorbed_guest_ids. The answer is stored; there is simply no path that dereferences a retired id.

What R-X5 asks for: GET /guests/{id} on a retired id returns 200 {"status":"MERGED","currentGuestId":…,"mergedAt":…} instead of 404 — an HTTP-shaped redirect for identity.

Why slice 4 now carries a directive to consume it

Connectors are the first real holders of a guestId. Writing one back into a PMS is precisely the "authoritative reference in another system" case — and it is unsafe while a merge can silently invalidate the key, because the connector cannot tell that it broke. Its foreign key looks fine right up until it doesn't.

So slice 4 must either build the resolution endpoint or state the constraint in the connector contract, so no integrator stores an id believing it is stable.

What is deliberately not here

No test asserting the 404. It is the current behaviour but the wrong behaviour, and a characterization test would lock in something we intend to change. The probe ran, proved the point, and was deleted.

Documentation only — no code changes.

🤖 Generated with Claude Code

The point of a golden profile is that other systems can store its guestId as
the authoritative reference for a person. They cannot today, and the way it
fails is quiet.
A merge deletes the absorbed guest, so GET /guests/{absorbedId} answers a bare
404 — "No guest … in this tenant", as if the person had never existed. The
reference breaks on exactly the event this product exists to produce. An
unmerge that empties a guest retires an id the same way, so the mapping is not
always one-to-one either.
Observed rather than inferred: ingest two records that resolve separately, then
one carrying both identifiers. The merge reports MERGED and the absorbed id
404s. No test covered this, which is why it went unnoticed.
Nothing is actually lost. merge_event is append-only and already records both
the survivor and absorbed_guest_ids — the answer is stored, no query walks it.
What is missing is that a retired id should resolve instead of disappearing:
200 {"status":"MERGED","currentGuestId":…}, an HTTP-shaped redirect for
identity, transitive across merge chains and fanning out when an unmerge split
one id into several.
Slice 4 now carries a directive to consume this. Connectors are the first real
holders of a guestId — writing one back into a PMS is what makes GuestGraph the
system of record rather than a report — and that write-back is unsafe while a
merge can silently invalidate the key, because the connector cannot tell that
it broke.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@robertblust
robertblust merged commit d1d253c into mainAug 21, 2026
2 checks passed
Sign up for freeto 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

@robertblust