Repository navigation
refactor(detection): establish deterministic entity candidate lifecycle - #305
lipikaramaswamy wants to merge 10 commits into
Conversation
Signed-off-by: lipikaramaswamy <lramaswamy@nvidia.com>
Signed-off-by: lipikaramaswamy <lramaswamy@nvidia.com>
Signed-off-by: lipikaramaswamy <lramaswamy@nvidia.com>
Signed-off-by: lipikaramaswamy <lramaswamy@nvidia.com>
Signed-off-by: lipikaramaswamy <lramaswamy@nvidia.com>
Signed-off-by: lipikaramaswamy <lramaswamy@nvidia.com>
binaryaaron
left a comment
There was a problem hiding this comment.
agent (
review-pr): Plan feedback — shared detection semantics and composable backends.
The shared lifecycle is the right foundation for #265 and the eventual #302 integration. Following a council review, I suggest adding a narrow internal entity-detection backend contract to this plan, with GLiNER first and regex added when #265 is rebuilt. The four inline comments identify contracts to clarify before implementation; this is a review of the plan at fd1fea9b, not an approval of production code.
The intended sequential composition is:
source -> regex -> regex observations --------------------+
| |
+-> optional GLiNER context |
source -----------------> GLiNER -> new observations -----+
|
collect
|
shared admission / derivation / reconciliation
Without the regex-to-GLiNER data binding, the two backends can execute independently. Ordering and data consumption should be explicit: running GLiNER after regex does not automatically mean it consumes regex output.
Ownership
- Backends produce source-bound observations and distinguish successful empty output from failure.
- The shared detection lifecycle owns candidate admission, label eligibility, accepted provenance, propagation, and overlap reconciliation.
- Graph bindings own execution dependencies, request associations, retries, resource limits, and artifact provenance. Qualification of the final transformed output remains separate from detection acceptance.
Identity and reuse
Assign immutable, source-scoped observation references before publishing a batch for downstream reuse. Collection preserves them; short validator tokens such as c1 can be request-local aliases. Do not allocate identity from completion order or regenerate it during merging.
GLiNER can produce new claims while using regex context, and those claims follow its own admission route. Forwarded regex observations retain their original identity and restrictions; passing them through GLiNER cannot create another supporting origin. Derived claims retain explicit parent references. Graph-level dependency records influence, while entity-level provenance determines authority. The simplest initial collection contract retains the original regex batch and adds GLiNER's newly produced observations.
Scope
A private _EntityDetectionBackend ABC is reasonable. Introduce the observation contract and a narrow GLiNER boundary while preserving the existing DataDesigner execution/export path. Add the regex implementation with #265 and graph execution bindings during #302 integration. An async detect() method alone does not express the effect, batching, cancellation, and resource contracts the SDK needs. Keep graph imports out of the domain types.
Define a concrete supported reuse behavior before standardizing a generic hints interface. Supplying context, selecting labels, masking text, and skipping detector work have different coverage consequences. Unsupported context should not silently be ignored. Keep augmentation's invocation contract separate initially while allowing it to produce compatible observations and use the shared lifecycle.
Validation
Use shared semantic fixtures for the pure helpers, serialized DataDesigner execution, and eventual graph adapter. Include the four inline scenarios, sequential reuse without duplicated authority, preservation of both source batches, and serialization round trips. Pin benchmark inputs, configuration, baseline, and acceptance criteria, including coverage changes from fallback and seed-over-augmentation precedence.
The excluded-rule export finding remains a separate #265 item. Also align this plan's older allow_model_detection / validate_with_llm references with #265's current detect_additional_matches / validate_matches_with_llm names.
Signed-off-by: lipikaramaswamy <lramaswamy@nvidia.com>
|
Addressed the plan review in 1bce7b7, with terminology clarified in 5f38e04. The revised plan now defines:
I also updated #304 so unusable label proposals receive exact-span fallback, while usable but excluded or disallowed proposals are rejected and traced separately. |
Signed-off-by: lipikaramaswamy <lramaswamy@nvidia.com>
Signed-off-by: lipikaramaswamy <lramaswamy@nvidia.com>
|
When we have overlapping spans where one is contained in another, I definitely see that taking the longer span makes sense. It's just not clear to me what the right thing to do is when the spans overlap. Ideally, we would use sensitivity but we won't have that information until after the sensitivity disposition. I'm thinking this situation won't happen often and it's probably not worth worrying about. Taking the longest span is a good default. |
|
It's not clear to me what happens when we have "May called yesterday. The appointment is in May". Could we end up tagging both "May"s as names? |
binaryaaron
left a comment
There was a problem hiding this comment.
The source/view identity and execution-scoped admission changes address the main concerns Andrea raised. A few implementation details will matter when these components are used in #302:
- Collect all observations before resolving overlaps. With A=
[0,4), B=[3,9), and C=[8,15), longest-first selection across all three retains A and C. Resolving A/B first and then adding C retains only C. Add a test showing that collecting results from graph branches does not discard candidates before the final reconciliation step. - Preserve each origin’s permissions after merging equivalent candidates. If a regex observation prohibits propagation and an accepted augmentation observation permits it, only the augmentation observation supports protecting a new occurrence. A combined propagation flag and a list of origin IDs would lose that distinction.
- Convert offsets to the original source coordinates exactly once. The source section says backends do this before publishing observations, while Step 2 also describes converting offsets. Specify which step performs the conversion and which checks the result.
- Keep detector behavior separate from workflow execution. The private backend interface should describe detector inputs and results. NDD and future graph adapters should manage transport, retries, scheduling, and result publication. Validation and merging should remain shared operations outside the backend classes.
#302 can track which inputs and artifact versions an operation used. The detection adapter will still need to check serialized observations and associate each validation result with the source text, context, and policy used to produce it. Reusing an observation should preserve its detector identity, but should not automatically reuse an earlier validation result. Accepting an entity also does not establish that the graph’s final output qualifies for Protected release.
These details can be specified and tested in #305 without implementing graph integration or validation under multiple policies yet.
|
Say we had: |
Yes, this can happen today. This behavior has existed since the original replace-mode implementation: GLiNER finds one occurrence → validator confirms the value is sensitive → protect every identical occurrence. The motivation was reasonable at the time
So we have been making the semantic assumption that if two spans have the same text, they have the same label and meaning. However, this assumes that equal text has equal meaning. That is not true for your example, or something even more complex like I no longer think global blind propagation should be the standard final stage. A better target workflow would be: The augmenter currently returns: Because that result has no occurrence anchor, the engine materializes it at every matching occurrence. The proposed response would be:
We should benchmark the recall effect of removing blind propagation. For highly stable identifiers, exact expansion may still be useful as an explicit policy, but perhaps relying on regex / gliner is sufficient. For contextual entities such as names, locations, and organizations, additional occurrences should receive independent decisions. I think #305 should establish occurrence-specific authority now; otherwise the new lifecycle risks formalizing the existing assumption that accepting one occurrence authorizes every equal string. |
|
Regarding the "May" example. Your suggestions may work. My gut feeling is it will happen way more often that gliner/augment will miss repeated instances of the same value/label than we will have ambiguous cases like "May". |
Good catch @asteier2026. We probably should not use normalization or response ordering to choose between duplicate entries. How about this: we discard the invalid response entirely, and retry the same validation request. With a validator pool, the retry proceeds to the next alias in pool order. With a single validator alias, we will allow one bounded same-alias semantic retry. If all attempts return invalid responses, the request follows the existing terminal FailedRecord path. This is distinct from an omitted alias or a single unusable entry in an otherwise valid response. Those cases retain exact-span fallback protection without propagation rather than retrying the request. |
Signed-off-by: lipikaramaswamy <lramaswamy@nvidia.com>
|
Follow-up on @asteier2026's concern that GLiNER or augmentation may miss repeated occurrences more often than equal-text ambiguity occurs: I audited 12 completed trace artifacts covering 986 full-run records across Gemma, GPT-OSS, and Qwen on RAT-Bench, Nemotron-PII, and OpenPII.
This does not prove that repeated-mention recall is unimportant. It does show that unconditional propagation is doing much more than recovering missed repeated names and identifiers, and that the assumption "equal text means equal entity meaning" creates substantial noise. The revised plan therefore removes blind |
|
The implementation plan has been substantially revised in 9854bff, and the settled review threads have been updated and resolved. One experiment remains explicit in the plan: compare seed-tagged versus canonical plain-text augmenter input on the same RAT-Bench, Nemotron-PII, and OpenPII samples, especially duplicate rediscovery versus genuinely new recovery. If there are no additional comments by end of day Wednesday, October 21, I will start implementation. |
Related Issue
Related to #262.
Plan Document
plans/262/detection-semantics.mdSummary
Establishes a shared, deterministic lifecycle for detection candidates before
the regex work in #265 is rebuilt on top of it.
The implementation will provide:
DetectionResultper invocation and distinguishes no findings from failure;SourceTextReffor each canonical record field and aSourceViewmapping backend-local spans into canonical source offsets;observation IDs, plus short validation aliases scoped to one dispatched
request inventory;
repeated-value candidates survive validation without duplicating context;
without adding another merge path;
authority;
and derived candidates;
The first commit contains the reviewed implementation plan. Production code and
tests will be added to this same draft PR.
Review focus
DataDesigner execution path while publishing reusable detection results?
SourceTextRefandSourceViewcleanly distinguish records and fieldswhile mapping overlapping or chunked views into canonical offsets?
source-aware normalized semantic identity separated precisely enough?
request inventory, including for chunked validation and retries?
accepted,rejected, andfallback_protectedthe correct terminaladmission states?
excerpts, and groups crossing validation chunks?
granting authority to rejected or undecided origins?
without transferring authority between coalesced origins?
coverage before source and reserve source priority for equal geometry?
from output admission so fix: switch augment prompt to plain text input, drop GLiNER seed entities #267 can switch to plain text without adding another
merge path?
the sources currently on
mainand the later regex integration?observable and prevent decision replay?
Type of Change
Contributor Checklist
Validation
Plan validation completed:
git diff --cached --checkmake copyright-checkmake checkthrough the pre-commit hookImplementation tests and results will be recorded here as the code lands.
Documentation and Artifacts
plans/262/path