Summary
InferredResultsWriter detects duplicate semantics on the Inferred definition pattern and Inferred Navigation Pattern (and logs a WARN with the canonical and duplicate NIDs), but it currently leaves the duplicates in place. We should provide a one-shot cleanup task that writes a new State.WITHDRAWN version on each duplicate semantic, leaving the canonical NID active.
Background
InferredResultsWriter#findCanonicalSemanticNid (reasoner-service/.../InferredResultsWriter.java#L604-L678) recomputes the deterministic UUID via UuidT5Generator.singleSemanticUuid(pattern, component.publicId()), picks the matching NID as canonical, and lists the rest as duplicates. The trailing log line is:
Found N duplicate semantic(s) ... Canonical UUID: ..., Canonical NID: ..., Duplicate NIDs: [...]. These duplicates were likely created by a past race condition in UUID generation. Consider running a cleanup task to remove the duplicates.
That cleanup task does not yet exist. This issue tracks adding it.
Observed duplicates (from a recent classifier run)
| Pattern |
Component |
Canonical UUID |
Canonical NID |
Duplicate NIDs |
| Inferred definition pattern |
Definition |
b4673aa7-9392-5faf-b862-7337592976cc |
945100050 |
[939524310] |
| Inferred Navigation Pattern |
Definition |
96a78a7c-4cf2-521b-9829-3cb2c7c84185 |
1012208926 |
[1006633174] |
| Inferred definition pattern |
Preferred |
92ebe328-80a0-55ea-be30-a9327adb8c32 |
945100051 |
[939524118] |
| Inferred Navigation Pattern |
Preferred |
51374140-f539-5e53-a96c-183ed8f7a7d5 |
1012208927 |
[1006632982] |
| Inferred definition pattern |
Fully qualified name |
a5c2ba0a-6582-5663-8a5f-fa525bdbac12 |
945100052 |
[939524374] |
These five rows make a good fixture for verifying the cleanup task end-to-end.
Proposed approach
- New cleanup task (e.g.
WithdrawDuplicateInferredSemanticsTask) in reasoner-service, runnable both as a TrackingCallable and from a CLI entry point.
- Discovery: walk every component referenced by the inferred-definition and inferred-navigation patterns, call the same
findCanonicalSemanticNid logic, and collect (canonicalNid, [duplicateNids]) pairs. Factor the existing private method out so the writer and the cleanup task share one implementation.
- Withdrawal: for each duplicate NID, open a transaction and write a new
SemanticVersion whose STAMP has State.WITHDRAWN, copying field values from the latest version. Do not modify the canonical NID.
- Idempotence: if the latest version of a duplicate is already
WITHDRAWN, skip it and count it as already-withdrawn — re-running the task must be a no-op.
- Reporting: log a single summary line per pattern (
withdrew N, skipped M already-withdrawn, K canonical untouched) plus per-NID DEBUG entries; return a structured result the caller can assert against in tests.
- Dry-run flag: the task should accept a
dryRun parameter that performs discovery and logs the plan without writing.
Acceptance criteria
Out of scope
- Any concept-level (as opposed to semantic-level) changes — the duplicates are extra semantic versions; the referenced concepts stay active.
- The "wrong pattern" corruption case logged at ERROR in the same method — that needs a separate investigation.
Self-Service
I would be willing to fix this issue.
Summary
InferredResultsWriterdetects duplicate semantics on theInferred definition patternandInferred Navigation Pattern(and logs aWARNwith the canonical and duplicate NIDs), but it currently leaves the duplicates in place. We should provide a one-shot cleanup task that writes a newState.WITHDRAWNversion on each duplicate semantic, leaving the canonical NID active.Background
InferredResultsWriter#findCanonicalSemanticNid(reasoner-service/.../InferredResultsWriter.java#L604-L678) recomputes the deterministic UUID viaUuidT5Generator.singleSemanticUuid(pattern, component.publicId()), picks the matching NID as canonical, and lists the rest as duplicates. The trailing log line is:That cleanup task does not yet exist. This issue tracks adding it.
Observed duplicates (from a recent classifier run)
b4673aa7-9392-5faf-b862-7337592976cc96a78a7c-4cf2-521b-9829-3cb2c7c8418592ebe328-80a0-55ea-be30-a9327adb8c3251374140-f539-5e53-a96c-183ed8f7a7d5a5c2ba0a-6582-5663-8a5f-fa525bdbac12These five rows make a good fixture for verifying the cleanup task end-to-end.
Proposed approach
WithdrawDuplicateInferredSemanticsTask) inreasoner-service, runnable both as aTrackingCallableand from a CLI entry point.findCanonicalSemanticNidlogic, and collect(canonicalNid, [duplicateNids])pairs. Factor the existing private method out so the writer and the cleanup task share one implementation.SemanticVersionwhose STAMP hasState.WITHDRAWN, copying field values from the latest version. Do not modify the canonical NID.WITHDRAWN, skip it and count it as already-withdrawn — re-running the task must be a no-op.withdrew N, skipped M already-withdrawn, K canonical untouched) plus per-NID DEBUG entries; return a structured result the caller can assert against in tests.dryRunparameter that performs discovery and logs the plan without writing.Acceptance criteria
WITHDRAWNversions, one per duplicate NID, with the canonical NIDs unchanged.Found N duplicate semantic(s)warning for these patterns.dryRun=truewrites nothing and the summary log matches a real run's plan.Out of scope
Self-Service
I would be willing to fix this issue.