@indigoai-us/hq-cli 5.194.0 → 5.194.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.194.1] — 2026-09-25
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- `hq core ontology apply` no longer creates a new entity that is probably an
|
|
10
|
+
existing one. A candidate whose alias is already another entity's name or
|
|
11
|
+
alias, or a person whose name contains every word of an existing person's
|
|
12
|
+
full name (for example "Chaudhry Hassaan Saleem" and "Hassaan Saleem"), is
|
|
13
|
+
held back and reported as a suspect duplicate.
|
|
14
|
+
|
|
5
15
|
## [5.194.0] — 2026-09-25
|
|
6
16
|
|
|
7
17
|
### Added
|
|
@@ -108,6 +108,13 @@ type Resolution = {
|
|
|
108
108
|
};
|
|
109
109
|
/** Design section 5.3, tiers 1 to 4. */
|
|
110
110
|
export declare function resolveCandidate(index: EntityIndex, type: string, canonical: string): Resolution;
|
|
111
|
+
/**
|
|
112
|
+
* Existing entities of `type` that a new candidate probably is: one of the
|
|
113
|
+
* candidate's aliases is already their name or alias, or, for people, every
|
|
114
|
+
* word of their name (two words or more) is in the candidate's name
|
|
115
|
+
* (`Chaudhry Hassaan Saleem` and `Hassaan Saleem`).
|
|
116
|
+
*/
|
|
117
|
+
export declare function overlappingEntities(index: EntityIndex, type: string, canonical: string, aliases: string[]): string[];
|
|
111
118
|
export declare function applyOntologyRun(options: ApplyOptions): ApplyReport;
|
|
112
119
|
export {};
|
|
113
120
|
//# sourceMappingURL=apply.d.ts.map
|
|
@@ -81,6 +81,38 @@ export function resolveCandidate(index, type, canonical) {
|
|
|
81
81
|
return { tier: "type_conflict", types: otherTypes };
|
|
82
82
|
return { tier: "new" };
|
|
83
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* Existing entities of `type` that a new candidate probably is: one of the
|
|
86
|
+
* candidate's aliases is already their name or alias, or, for people, every
|
|
87
|
+
* word of their name (two words or more) is in the candidate's name
|
|
88
|
+
* (`Chaudhry Hassaan Saleem` and `Hassaan Saleem`).
|
|
89
|
+
*/
|
|
90
|
+
export function overlappingEntities(index, type, canonical, aliases) {
|
|
91
|
+
const paths = new Set();
|
|
92
|
+
for (const alias of aliases) {
|
|
93
|
+
const normalized = normalizeEntityName(alias, type);
|
|
94
|
+
// A generic alias still counts when an existing entity already holds it.
|
|
95
|
+
if (!normalized)
|
|
96
|
+
continue;
|
|
97
|
+
for (const rel of index.canonical.get(key(type, normalized)) ?? [])
|
|
98
|
+
paths.add(rel);
|
|
99
|
+
for (const rel of index.alias.get(key(type, normalized)) ?? [])
|
|
100
|
+
paths.add(rel);
|
|
101
|
+
}
|
|
102
|
+
if (type === "person") {
|
|
103
|
+
const words = new Set(normalizeEntityName(canonical, type).split(" "));
|
|
104
|
+
const prefix = key(type, "");
|
|
105
|
+
for (const [name, rels] of index.canonical) {
|
|
106
|
+
if (!name.startsWith(prefix))
|
|
107
|
+
continue;
|
|
108
|
+
const other = name.slice(prefix.length).split(" ");
|
|
109
|
+
if (other.length >= 2 && other.every((word) => words.has(word)))
|
|
110
|
+
for (const rel of rels)
|
|
111
|
+
paths.add(rel);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return [...paths].sort();
|
|
115
|
+
}
|
|
84
116
|
/**
|
|
85
117
|
* Aliases worth adding to the entity at `self`: new to it, and held by no
|
|
86
118
|
* other entity of any type, so an alias can never make a later match
|
|
@@ -301,6 +333,12 @@ export function applyOntologyRun(options) {
|
|
|
301
333
|
note(report.rejected, "type_conflict");
|
|
302
334
|
continue;
|
|
303
335
|
}
|
|
336
|
+
const overlap = overlappingEntities(index, candidate.type, canonical, candidate.aliases);
|
|
337
|
+
if (overlap.length > 0) {
|
|
338
|
+
note(report.deferred, "suspect_duplicate");
|
|
339
|
+
report.suspect_duplicates.push({ name: canonical, type: candidate.type, paths: overlap });
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
304
342
|
// Tier 5: create.
|
|
305
343
|
if (candidate.confidence < MIN_CREATE_CONFIDENCE) {
|
|
306
344
|
note(report.rejected, "low_confidence");
|
|
@@ -569,7 +569,7 @@ async function withReconcileLock(hqRoot, fn) {
|
|
|
569
569
|
}
|
|
570
570
|
}
|
|
571
571
|
}
|
|
572
|
-
function classifyDeadLocalLane(hqRoot, laneId) {
|
|
572
|
+
function classifyDeadLocalLane(hqRoot, laneId, now) {
|
|
573
573
|
const runDir = laneRunDir(hqRoot, laneId);
|
|
574
574
|
const manifest = readSpawnManifest(runDir);
|
|
575
575
|
const minEnvelopeName = manifest.envelope_path ? path.basename(manifest.envelope_path) : undefined;
|
|
@@ -586,7 +586,6 @@ function classifyDeadLocalLane(hqRoot, laneId) {
|
|
|
586
586
|
// the quota signal, any other becomes provider_rejected, and only a stream
|
|
587
587
|
// with no failure object falls back to the quota reader.
|
|
588
588
|
const workerOutOffset = readSpawnManifest(runDir).worker_out_offset ?? 0;
|
|
589
|
-
const now = new Date();
|
|
590
589
|
const routed = envelope
|
|
591
590
|
? { quota: null, rejection: null }
|
|
592
591
|
: routeProviderFailure(readProviderRejection(runDir, lane.provider, workerOutOffset), () => readQuotaSignal(runDir, lane.model, now, workerOutOffset), lane.model, now);
|
|
@@ -894,7 +893,7 @@ async function heartbeatAndDeadHosts(hqRoot, lanes, summary, deps, now) {
|
|
|
894
893
|
continue;
|
|
895
894
|
}
|
|
896
895
|
try {
|
|
897
|
-
const { classification, latest, quota, providerRejection, lane: classified } = classifyDeadLocalLane(hqRoot, lane.lane_id);
|
|
896
|
+
const { classification, latest, quota, providerRejection, lane: classified } = classifyDeadLocalLane(hqRoot, lane.lane_id, now);
|
|
898
897
|
// Match the watcher's one-repair decision before classifying an
|
|
899
898
|
// unparseable envelope after both processes have died. As in the
|
|
900
899
|
// watcher, a provider rejection or a quota signal skips the repair.
|