Skip to content

DR-105: entity_mentions.metadata.curation_pinned is the cross-surface curation pin marker

DR-105: entity_mentions.metadata.curation_pinned is the cross-surface curation pin marker

Section titled “DR-105: entity_mentions.metadata.curation_pinned is the cross-surface curation pin marker”

Census #41 recorded a real failure: an admin entity merge was reverted by the next ingestion walk. id-400 closed it (Inv-9) by stamping a marker on the surviving mentions so the walk cannot un-merge curated state. S514 deliberately did not mint a decision for the marker, deferring it to id-405: “revisits the marker at RPC time; mint then if it survives”.

It survived. id-405 rewrote the write path from a per-row loop into a single jsonb_set RPC and the marker came through unchanged — same key, same true value, same row-level semantics, now written by SQL rather than a route-side object spread.

Four independent readers, across two languages and both the app and pipeline surfaces, now agree on it: stage_5.py (×2 — write-back domain, cross-op survivor rule), flow.py (em-declare carry-forward), and the RPC. That is a cross-surface contract, and the id-405 predicate bug below is the evidence for what happens when it is re-derived per site instead of stated once.

The curation pin is entity_mentions.metadata.curation_pinned = true — a jsonb marker on the mention row, not a column, not a separate table.

It is matched on the effective entity type, COALESCE(entity_type_override, entity_type), consistent with get_entity_summary and merge_entities’ own dedup PARTITION BY. Writes go through pin_entity_mentions (public + the api INVOKER wrapper), which sets it for a whole (canonical_name, effective type) pair in one statement and returns the authoritative row count.

Every walk-side consumer must honour it; no consumer may re-derive its own pin predicate.

  • A dedicated curation_pinned boolean column. Rejected for now: the marker is metadata about a curation act rather than an attribute of the mention, and jsonb avoided a migration on the hot table at the time. Revisit if the pin ever needs indexing.
  • Matching on the base entity_type column. This was the actual implementation and it was a live bug. merge_entities repoints a merged row by writing entity_type_override and never rewrites the base column, so any merge whose target type differed from a row’s raw extracted type left that row unpinned and still revertible, while reporting a healthy pin count. Demonstrated on the ID-70 fixture: base-type matching pins 0 of 2 survivors, effective-type matching pins both.
  • Minting this at S514. Deliberately deferred so the marker had to survive a rewrite before being fixed as a contract. It did; the deferral worked as intended.
  • The pin predicate is stated once. A new consumer reads this decision rather than inferring the rule from whichever site it happened to read.
  • pin_entity_mentions is atomic and uncapped: the previous loop read at most 1000 rows, so a winner with more silently under-pinned and under-reported. A very large merge now performs one large UPDATE.
  • No backfill (DR-093, pre-launch). Rows pinned by the old loop keep their marker — the jsonb_set is idempotent over them — and the effective-type gap is a go-forward fix.
  • Applied to Platform staging and prod (S515); both carry the functions with prosecdef=false, anon EXECUTE denied by DR-035’s born-locked trigger.