Skip to content

fix(engine): skip tag-shaped candidate entity names at intake - #3277

Open
JoshFunnell wants to merge 4 commits into
vectorize-io:mainfrom
JoshFunnell:upstream/entity-name-hygiene
Open

fix(engine): skip tag-shaped candidate entity names at intake#3277
JoshFunnell wants to merge 4 commits into
vectorize-io:mainfrom
JoshFunnell:upstream/entity-name-hygiene

Conversation

@JoshFunnell

@JoshFunnell JoshFunnell commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

⚠️ Scope reduced — the whitespace half of this PR has been removed

When this PR was opened it did two things: whitespace-normalize candidate entity names, and skip tag-shaped category labels. The normalization half landed upstream independently as #3338 (44b597c4), which went further and also added per-fact case-insensitive dedupe and an empty-candidate drop.

That half is now deleted from this PR. Upstream's implementation is taken wholesale — this branch merges main and keeps none of its own normalization code or tests. What remains is only the tag-shape filter, which #3338 does not cover. Nothing here duplicates or re-litigates #3338.

Extraction was minting category labels as entitiesdomain:lens, domain:host, domain:memory — which behave like broad tags and measurably poison entity-based scoping: 61.5% vs 94.0% precision on one subject on a production bank. This skips tag-shaped candidate names at retain intake.

What is left in this PR

  • _is_tag_shaped_name() + a conservative shape ^[a-z][a-z0-9_-]{1,15}:[a-z][a-z0-9_-]{1,24}$
  • the skip itself in _prepare_entities_for_resolution, with a skipped_tag_shaped counter and a debug log line
  • entity_labels threaded through so configured labels can be exempted
  • tag-shape tests only — our normalization tests were dropped as duplicates of upstream's

Check order is load-bearing

normalize -> empty-drop -> tag-shape (label-exempt) -> dedupe

Tag-shape must run before dedupe: a skipped tag then never enters seen_in_fact, so every tag-shaped occurrence increments skipped_tag_shaped and dedupe only tracks survivors. If it ran after dedupe, a second identical tag would be silently deduped instead of counted. Upstream's normalization, empty-drop and dedupe behaviour is unchanged.

Configured labels stay exempt

A tag-shaped name that is a configured entity label (e.g. use:use-001 from a tag-type label group) is exempt via is_label_entity and still reaches entity resolution — label values are deliberately key:value shaped (GH-1558 exact-match path).

The shape deliberately does not match URLs, Windows paths, times (12:30), names with spaces or dots (re: subject), or names with no colon.

Production banks were measured (2026-08-08) with entity canonical_name
values containing embedded newlines -- extraction artifacts -- which
shears any line-oriented consumer (psql -A output, logs, exports).
Add _normalize_entity_name() and apply it at candidate-entity intake
in _prepare_entities_for_resolution(), collapsing all internal
whitespace runs (including \n, \r, \t) to a single space and
stripping ends. Case handling is unchanged (the registry matches on
LOWER(canonical_name) separately).

Tested by tests/test_entity_name_hygiene.py.
Measured (2026-08-08): extraction minted entities named domain:lens,
domain:host, domain:memory -- category labels, not entities. They
behave like broad tags and measurably poisoned entity-based scoping
(61.5% vs 94.0% precision on one subject). Add a conservative
tag-shape regex (_is_tag_shaped_name) and apply it in
_prepare_entities_for_resolution(), after whitespace normalization
and against a lowercased copy of the name, silently skipping matches
(logged at debug level).

Configured entity labels are deliberately "key:value" shaped
(vectorize-ioGH-1558, e.g. tag-type label values like use:use-001), so
_prepare_entities_for_resolution() now also accepts entity_labels and
exempts any name that resolves as a configured label from the skip --
without this exemption, test_intrabatch_dedup_leaves_label_values_separate
and test_entity_resolution_does_not_merge_distinct_label_values
regressed.

Tested by tests/test_entity_name_hygiene.py (attacking cases for URLs,
Windows paths, times, and configured labels come first, per the
false-positive risk).
Upstream merged the whitespace-normalization half of this PR as vectorize-io#3338 (44b597c)
and went further, adding per-fact case-insensitive dedupe and an empty-candidate
drop. Upstream's implementation is taken wholesale; only this branch's unique
contribution is re-applied on top: skipping tag-shaped category-label candidates
(domain:lens) that extraction was minting as entities.

Check order is load-bearing: normalize -> empty-drop -> tag-shape -> dedupe. The
tag-shape skip must precede dedupe so a skipped tag never enters seen_in_fact and
every occurrence increments the counter; after dedupe a repeat tag would be
silently deduped instead of counted. Configured entity labels (use:use-001) stay
exempt via is_label_entity and still reach resolution.

Our normalization tests are dropped as duplicates of upstream's; the tag-shape
tests are kept.
verify-generated-files also runs ./scripts/hooks/lint.sh, and the re-applied
is_label_entity call was wrapped by hand rather than by ruff. Formatting only;
matches the +1/-3 the job reported.
@JoshFunnell JoshFunnell changed the title fix(engine): entity-name hygiene at resolution intake — whitespace normalization and tag-shaped category-label skip fix(engine): skip tag-shaped candidate entity names at intake Aug 11, 2026
@JoshFunnell

Copy link
Copy Markdown
Contributor Author

Heads-up on a scope change, since a body edit doesn't notify anyone.

The whitespace-normalization half of this PR is removed. That work landed independently as #3338 (44b597c4), which also added per-fact case-insensitive dedupe and an empty-candidate drop — all of which is taken wholesale here. This branch now merges main and keeps none of its own normalization code or tests.

What remains is only the tag-shape filter, which #3338 does not cover: extraction was minting category labels (domain:lens, domain:host) as entities, measured at 61.5% vs 94.0% precision for one subject on a production bank. Configured entity labels (use:use-001) stay exempt via is_label_entity.

One ordering detail worth a look during review: the skip runs before the dedupe, so a skipped tag never enters seen_in_fact and each occurrence is counted rather than silently deduped. Upstream's normalization, empty-drop and dedupe behaviour is otherwise unchanged.

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