@duckcodeailabs/dql-agent 1.9.5 → 1.10.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.
Files changed (94) hide show
  1. package/dist/agent-run-engine.d.ts +13 -0
  2. package/dist/agent-run-engine.d.ts.map +1 -1
  3. package/dist/agent-run-engine.js +65 -2
  4. package/dist/agent-run-engine.js.map +1 -1
  5. package/dist/analytical-compatibility.d.ts +61 -0
  6. package/dist/analytical-compatibility.d.ts.map +1 -0
  7. package/dist/analytical-compatibility.js +515 -0
  8. package/dist/analytical-compatibility.js.map +1 -0
  9. package/dist/analytical-execution-graph.d.ts +174 -0
  10. package/dist/analytical-execution-graph.d.ts.map +1 -0
  11. package/dist/analytical-execution-graph.js +599 -0
  12. package/dist/analytical-execution-graph.js.map +1 -0
  13. package/dist/analytical-failure-repair.d.ts +91 -0
  14. package/dist/analytical-failure-repair.d.ts.map +1 -0
  15. package/dist/analytical-failure-repair.js +325 -0
  16. package/dist/analytical-failure-repair.js.map +1 -0
  17. package/dist/analytical-frame.d.ts +19 -0
  18. package/dist/analytical-frame.d.ts.map +1 -0
  19. package/dist/analytical-frame.js +299 -0
  20. package/dist/analytical-frame.js.map +1 -0
  21. package/dist/analytical-period-resolution.d.ts +53 -0
  22. package/dist/analytical-period-resolution.d.ts.map +1 -0
  23. package/dist/analytical-period-resolution.js +218 -0
  24. package/dist/analytical-period-resolution.js.map +1 -0
  25. package/dist/analytical-readiness.d.ts +17 -0
  26. package/dist/analytical-readiness.d.ts.map +1 -0
  27. package/dist/analytical-readiness.js +95 -0
  28. package/dist/analytical-readiness.js.map +1 -0
  29. package/dist/analytical-result-facts.d.ts +79 -0
  30. package/dist/analytical-result-facts.d.ts.map +1 -0
  31. package/dist/analytical-result-facts.js +301 -0
  32. package/dist/analytical-result-facts.js.map +1 -0
  33. package/dist/answer-loop.d.ts +82 -2
  34. package/dist/answer-loop.d.ts.map +1 -1
  35. package/dist/answer-loop.js +1129 -38
  36. package/dist/answer-loop.js.map +1 -1
  37. package/dist/embeddings/provider.d.ts +1 -1
  38. package/dist/embeddings/provider.d.ts.map +1 -1
  39. package/dist/embeddings/provider.js.map +1 -1
  40. package/dist/governed-relational-compiler.d.ts +189 -0
  41. package/dist/governed-relational-compiler.d.ts.map +1 -0
  42. package/dist/governed-relational-compiler.js +458 -0
  43. package/dist/governed-relational-compiler.js.map +1 -0
  44. package/dist/index.d.ts +25 -5
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.js +15 -4
  47. package/dist/index.js.map +1 -1
  48. package/dist/intent-controller.d.ts +5 -0
  49. package/dist/intent-controller.d.ts.map +1 -1
  50. package/dist/intent-controller.js.map +1 -1
  51. package/dist/kg/build.d.ts +2 -2
  52. package/dist/kg/build.d.ts.map +1 -1
  53. package/dist/kg/build.js +329 -9
  54. package/dist/kg/build.js.map +1 -1
  55. package/dist/meaning-resolution.d.ts +17 -0
  56. package/dist/meaning-resolution.d.ts.map +1 -1
  57. package/dist/meaning-resolution.js +83 -4
  58. package/dist/meaning-resolution.js.map +1 -1
  59. package/dist/metadata/analysis-planner.d.ts.map +1 -1
  60. package/dist/metadata/analysis-planner.js +37 -7
  61. package/dist/metadata/analysis-planner.js.map +1 -1
  62. package/dist/metadata/catalog.d.ts +86 -3
  63. package/dist/metadata/catalog.d.ts.map +1 -1
  64. package/dist/metadata/catalog.js +607 -36
  65. package/dist/metadata/catalog.js.map +1 -1
  66. package/dist/metadata/meaning-evidence.d.ts +18 -2
  67. package/dist/metadata/meaning-evidence.d.ts.map +1 -1
  68. package/dist/metadata/meaning-evidence.js +182 -7
  69. package/dist/metadata/meaning-evidence.js.map +1 -1
  70. package/dist/plan-execution-adapter.d.ts +131 -0
  71. package/dist/plan-execution-adapter.d.ts.map +1 -0
  72. package/dist/plan-execution-adapter.js +447 -0
  73. package/dist/plan-execution-adapter.js.map +1 -0
  74. package/dist/research-governance.d.ts +56 -0
  75. package/dist/research-governance.d.ts.map +1 -0
  76. package/dist/research-governance.js +68 -0
  77. package/dist/research-governance.js.map +1 -0
  78. package/dist/research-loop.d.ts +14 -0
  79. package/dist/research-loop.d.ts.map +1 -1
  80. package/dist/research-loop.js +77 -23
  81. package/dist/research-loop.js.map +1 -1
  82. package/dist/resolved-analytical-plan.d.ts +123 -0
  83. package/dist/resolved-analytical-plan.d.ts.map +1 -0
  84. package/dist/resolved-analytical-plan.js +400 -0
  85. package/dist/resolved-analytical-plan.js.map +1 -0
  86. package/dist/router.d.ts +3 -0
  87. package/dist/router.d.ts.map +1 -1
  88. package/dist/router.js +195 -25
  89. package/dist/router.js.map +1 -1
  90. package/dist/skills/loader.d.ts +17 -0
  91. package/dist/skills/loader.d.ts.map +1 -1
  92. package/dist/skills/loader.js +62 -4
  93. package/dist/skills/loader.js.map +1 -1
  94. package/package.json +4 -4
@@ -11,20 +11,21 @@ import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync
11
11
  import { createRequire } from 'node:module';
12
12
  import { dirname, join } from 'node:path';
13
13
  import { brotliCompressSync, brotliDecompressSync, constants as zlibConstants } from 'node:zlib';
14
- import { buildManifest, buildManifestKnowledgeGraph, loadDomainPackageRegistry, loadManifestKnowledgeSkills, loadProjectConfig, parseContractRef, resolveDataLexManifestPath, resolveDbtManifestPath, resolveSemanticLayerWithDiagnostics, extractColumnLineage, normalizeDqlArtifactReference, composeEffectiveTrust, } from '@duckcodeailabs/dql-core';
15
- import { buildKGFromManifest, buildKGFromSemanticLayer } from '../kg/build.js';
16
- import { buildBlockBusinessFingerprint, buildBlockSqlFingerprints } from './block-fingerprints.js';
14
+ import { buildManifest, buildManifestKnowledgeGraph, loadDomainPackageRegistry, loadManifestKnowledgeSkills, loadProjectConfig, parseContractRef, resolveDataLexManifestPath, resolveDbtManifestPath, resolveSemanticLayerWithDiagnostics, extractColumnLineage, normalizeDqlArtifactReference, composeEffectiveTrust, normalizeMetricCapabilityContract, } from "@duckcodeailabs/dql-core";
15
+ import { buildKGFromManifest, buildKGFromSemanticLayer } from "../kg/build.js";
16
+ import { domainContextSearchDomains, } from "../domain-context.js";
17
+ import { buildBlockBusinessFingerprint, buildBlockSqlFingerprints, } from "./block-fingerprints.js";
17
18
  import { buildAnalysisQuestionPlan, certifiedApplicabilityForObject, scoreAllowedSqlRelationWithAnalysisPlan, scoreMetadataObjectWithAnalysisPlan, sortAllowedSqlContextForAnalysisPlan, } from './analysis-planner.js';
18
19
  import { extractSimpleSelectShape, sourceSqlShapeColumns } from './sql-shape.js';
19
20
  import { grainMatches, requestedGrainFromPlan, } from './grain-gate.js';
20
21
  import { certifiedFitAllowsTier1, certifiedTerminationVerdict, evaluateCertifiedBlockFit, } from './block-fit.js';
21
22
  import { retrieveScopedHints } from '../hints/retrieval.js';
22
23
  import { buildFtsMatch } from '../memory/fts-query.js';
23
- import { envEmbeddingProvider } from '../embeddings/provider.js';
24
+ import { cosineSimilarity, envEmbeddingProvider, HashedTokenEmbeddingProvider, } from '../embeddings/provider.js';
24
25
  import { matchExampleParaphrase } from './example-match.js';
25
26
  import { loadSkills, selectRelevantSkills } from '../skills/loader.js';
26
27
  import { buildMeaningEvidencePackage, } from './meaning-evidence.js';
27
- export { toAgentRetrievalEvidence } from './meaning-evidence.js';
28
+ export { applyContextPackCompatibility, toAgentRetrievalEvidence } from './meaning-evidence.js';
28
29
  const require = createRequire(import.meta.url);
29
30
  let databaseCtor = null;
30
31
  function loadDatabase() {
@@ -93,26 +94,36 @@ const OBJECT_PRIORITY = {
93
94
  };
94
95
  const COLUMN_OBJECT_TYPES = new Set(['dbt_column', 'warehouse_column', 'runtime_column', 'runtime_value']);
95
96
  // v3 removes the legacy persisted plaintext runtime-value cache (SEC-003).
96
- const METADATA_INDEX_VERSION = 'metadata-index-v4-content-addressed-skills';
97
+ const METADATA_INDEX_VERSION = 'metadata-index-v5-qualified-vector-lanes';
98
+ const DEFAULT_VECTOR_PROVIDER = new HashedTokenEmbeddingProvider();
97
99
  export function defaultMetadataPath(projectRoot) {
98
100
  return join(projectRoot, '.dql', 'cache', 'metadata.sqlite');
99
101
  }
100
102
  export function metadataSnapshotPath(projectRoot, fingerprint) {
101
- return join(projectRoot, '.dql', 'cache', 'snapshots', `${fingerprint}.sqlite`);
103
+ return join(projectRoot, '.dql', 'cache', 'snapshots', `${METADATA_INDEX_VERSION}-${fingerprint}.sqlite`);
102
104
  }
103
- export function activeMetadataSnapshotPath(projectRoot) {
105
+ function readActiveMetadataSnapshotPointer(projectRoot) {
104
106
  const pointerPath = join(projectRoot, '.dql', 'cache', 'active-snapshot.json');
105
107
  if (!existsSync(pointerPath))
106
108
  return null;
107
109
  try {
108
110
  const parsed = JSON.parse(readFileSync(pointerPath, 'utf8'));
109
- const path = typeof parsed.snapshotPath === 'string' ? parsed.snapshotPath : '';
110
- return path && existsSync(path) ? path : null;
111
+ const snapshotId = typeof parsed.snapshotId === 'string' ? parsed.snapshotId : '';
112
+ const snapshotPath = typeof parsed.snapshotPath === 'string' ? parsed.snapshotPath : '';
113
+ const indexSchemaVersion = typeof parsed.indexSchemaVersion === 'string'
114
+ ? parsed.indexSchemaVersion
115
+ : undefined;
116
+ if (!snapshotId || !snapshotPath || !existsSync(snapshotPath))
117
+ return null;
118
+ return { snapshotId, snapshotPath, indexSchemaVersion };
111
119
  }
112
120
  catch {
113
121
  return null;
114
122
  }
115
123
  }
124
+ export function activeMetadataSnapshotPath(projectRoot) {
125
+ return readActiveMetadataSnapshotPointer(projectRoot)?.snapshotPath ?? null;
126
+ }
116
127
  /** Read the prepared catalog identity without rebuilding the metadata snapshot. */
117
128
  export function currentMetadataFingerprint(projectRoot) {
118
129
  if (!existsSync(defaultMetadataPath(projectRoot)))
@@ -150,6 +161,47 @@ export function openActiveKnowledgeSnapshot(projectRoot) {
150
161
  const path = activeMetadataSnapshotPath(projectRoot) ?? defaultMetadataPath(projectRoot);
151
162
  return new MetadataCatalog(path, { readOnly: path !== defaultMetadataPath(projectRoot) });
152
163
  }
164
+ const activeKnowledgeSnapshotLeases = new Map();
165
+ /**
166
+ * Acquire one exact immutable SQLite file for a request lifetime. The active
167
+ * pointer is read once; later activations cannot redirect this lease to a
168
+ * different catalog. Callers must release in `finally`.
169
+ *
170
+ * Acceptance: CTX-002, CTX-005.
171
+ */
172
+ export function acquireActiveKnowledgeSnapshot(projectRoot) {
173
+ const pointer = readActiveMetadataSnapshotPointer(projectRoot);
174
+ const path = pointer?.snapshotPath ?? defaultMetadataPath(projectRoot);
175
+ const catalog = new MetadataCatalog(path, { readOnly: Boolean(pointer) });
176
+ const catalogFingerprint = catalog.state('fingerprint') ?? 'metadata-unavailable';
177
+ if (pointer && pointer.snapshotId !== catalogFingerprint) {
178
+ catalog.close();
179
+ throw new Error(`Active metadata snapshot pointer mismatch (pointer ${pointer.snapshotId}, catalog ${catalogFingerprint}).`);
180
+ }
181
+ activeKnowledgeSnapshotLeases.set(path, (activeKnowledgeSnapshotLeases.get(path) ?? 0) + 1);
182
+ let released = false;
183
+ return Object.freeze({
184
+ snapshotId: catalogFingerprint,
185
+ path,
186
+ catalog,
187
+ release() {
188
+ if (released)
189
+ return;
190
+ released = true;
191
+ catalog.close();
192
+ const remaining = (activeKnowledgeSnapshotLeases.get(path) ?? 1) - 1;
193
+ if (remaining > 0)
194
+ activeKnowledgeSnapshotLeases.set(path, remaining);
195
+ else
196
+ activeKnowledgeSnapshotLeases.delete(path);
197
+ },
198
+ });
199
+ }
200
+ export function activeKnowledgeSnapshotLeaseCount(path) {
201
+ if (path)
202
+ return activeKnowledgeSnapshotLeases.get(path) ?? 0;
203
+ return Array.from(activeKnowledgeSnapshotLeases.values()).reduce((sum, count) => sum + count, 0);
204
+ }
153
205
  export function readIndexedDomainKnowledge(projectRoot, domainId) {
154
206
  const catalog = openActiveKnowledgeSnapshot(projectRoot);
155
207
  try {
@@ -295,6 +347,92 @@ export function upsertMetadataSnapshot(projectRoot, snapshot) {
295
347
  catalog.close();
296
348
  }
297
349
  }
350
+ /**
351
+ * Run exact, BM25/lexical, vector, and graph candidate generation as
352
+ * independent lanes over one acquired snapshot. Domain/import eligibility is
353
+ * applied in SQL or before graph admission, never as a post-ranking boost.
354
+ *
355
+ * Acceptance: CTX-005, AGT-009, AGT-010.
356
+ */
357
+ export async function retrieveMetadataSnapshotCandidates(catalog, input) {
358
+ const limit = Math.max(1, input.limit ?? 80);
359
+ const domains = domainContextSearchDomains(input.domainContext);
360
+ const isEligible = (object) => (object.objectType !== 'skill'
361
+ && (!input.objectTypes?.length || input.objectTypes.includes(object.objectType))
362
+ && (domains.length === 0 || !object.domain || domains.includes(object.domain)));
363
+ const explicitIdentities = explicitMetadataIdentities(input.question);
364
+ const exactObjects = explicitIdentities.flatMap((identity) => {
365
+ const resolution = catalog.resolveIdentity(identity);
366
+ return resolution.status === 'resolved' && resolution.object && isEligible(resolution.object)
367
+ ? [resolution.object]
368
+ : [];
369
+ });
370
+ const queries = uniqueMetadataSearchQueries(input.searchQueries?.length
371
+ ? input.searchQueries
372
+ : [input.question]);
373
+ const lexicalObjects = mergeObjects(queries.flatMap((query) => catalog.searchObjects({
374
+ query,
375
+ objectTypes: input.objectTypes,
376
+ domains: domains.length > 0 ? domains : undefined,
377
+ limit,
378
+ }))).filter(isEligible).slice(0, limit);
379
+ const vector = await catalog.searchVectorObjects({
380
+ query: input.question,
381
+ objectTypes: input.objectTypes,
382
+ domains: domains.length > 0 ? domains : undefined,
383
+ limit: Math.min(limit, 24),
384
+ provider: input.embeddingProvider,
385
+ });
386
+ const vectorObjects = vector.candidates.filter(isEligible);
387
+ const seedKeys = mergeObjects([...exactObjects, ...lexicalObjects.slice(0, 12), ...vectorObjects.slice(0, 12)])
388
+ .map((object) => object.objectKey);
389
+ const graphEdges = catalog.edgesForKeys(seedKeys, 1);
390
+ const graphObjects = catalog.getObjectsByKeys(Array.from(new Set(graphEdges.flatMap((edge) => [edge.fromKey, edge.toKey])))).filter((object) => isEligible(object) && !seedKeys.includes(object.objectKey)).slice(0, Math.min(limit, 24));
391
+ const lanes = [
392
+ retrievalLane('exact', exactObjects, (object) => explicitIdentities.includes(object.objectKey)
393
+ ? 'exact object-key reference'
394
+ : 'resolved qualified/native/alias reference'),
395
+ retrievalLane('lexical', lexicalObjects, () => 'BM25/lexical candidate from the immutable snapshot'),
396
+ {
397
+ ...retrievalLane('vector', vectorObjects, () => `independent vector candidate from ${vector.providerId}`),
398
+ provider: vector.providerId,
399
+ unavailableReason: vector.unavailableReason,
400
+ },
401
+ retrievalLane('graph', graphObjects, () => 'one-hop neighbor of an eligible exact/lexical/vector seed'),
402
+ ];
403
+ const selected = mergeObjects([...exactObjects, ...lexicalObjects, ...vectorObjects, ...graphObjects])
404
+ .slice(0, limit * 2);
405
+ return {
406
+ snapshotId: catalog.state('fingerprint') ?? 'metadata-unavailable',
407
+ selected,
408
+ lanes,
409
+ };
410
+ }
411
+ function retrievalLane(lane, objects, reason) {
412
+ return {
413
+ lane,
414
+ candidates: objects.map((object, index) => ({
415
+ objectKey: object.objectKey,
416
+ objectType: object.objectType,
417
+ name: object.name,
418
+ rank: index + 1,
419
+ score: Number((object.score ?? (1 / (index + 1))).toFixed(6)),
420
+ reason: reason(object),
421
+ })),
422
+ };
423
+ }
424
+ function explicitMetadataIdentities(question) {
425
+ const identities = [];
426
+ for (const match of question.matchAll(/@(metric|dimension|measure|entity|model|block|term)\(([^)]+)\)/gi)) {
427
+ const identity = match[2]?.trim();
428
+ if (identity)
429
+ identities.push(identity);
430
+ }
431
+ for (const match of question.matchAll(/\b(?:semantic|dql|dbt|skill):[a-z0-9_.:-]+/gi)) {
432
+ identities.push(match[0]);
433
+ }
434
+ return Array.from(new Set(identities));
435
+ }
298
436
  export async function buildLocalContextPack(projectRoot, request) {
299
437
  let prepared = false;
300
438
  if (request.preparedMetadataFingerprint) {
@@ -311,8 +449,9 @@ export async function buildLocalContextPack(projectRoot, request) {
311
449
  // Static governed knowledge is read from the immutable content-addressed
312
450
  // snapshot selected at request start. Mutable run history, runtime schema,
313
451
  // and context packs stay in the working catalog and never alter that view.
314
- const snapshotPath = activeMetadataSnapshotPath(projectRoot) ?? defaultMetadataPath(projectRoot);
315
- const catalog = openActiveKnowledgeSnapshot(projectRoot);
452
+ const snapshotLease = acquireActiveKnowledgeSnapshot(projectRoot);
453
+ const snapshotPath = snapshotLease.path;
454
+ const catalog = snapshotLease.catalog;
316
455
  const runtimeCatalog = openMetadataCatalog(projectRoot);
317
456
  try {
318
457
  const mode = request.mode ?? 'question';
@@ -400,11 +539,14 @@ export async function buildLocalContextPack(projectRoot, request) {
400
539
  // Leaving it in generic FTS results would bypass status/domain/area/exclusion
401
540
  // eligibility simply because a word in its body matched the question.
402
541
  const retrievalObjects = (rows) => scopeObjects(rows).filter((row) => row.objectType !== 'skill');
403
- const searchRows = retrievalObjects(mergeObjects(searchQueries.flatMap((query) => catalog.searchObjects({
404
- query,
542
+ const snapshotRetrieval = await retrieveMetadataSnapshotCandidates(catalog, {
543
+ question: request.question,
544
+ searchQueries,
405
545
  objectTypes: request.objectTypes,
546
+ domainContext: effectiveDomainContext,
406
547
  limit: Math.max(request.limit ?? 80, 20),
407
- }))));
548
+ });
549
+ const searchRows = retrievalObjects(snapshotRetrieval.selected);
408
550
  const schemaShapeCandidates = schemaShapeCandidateObjects(catalog, questionPlan, request, mergeObjects([...runtimeObjects, ...runtimeValueObjects]));
409
551
  const schemaShapeObjects = schemaShapeCandidates.map((candidate) => candidate.object);
410
552
  const exactCandidate = request.focusObjectKey ? catalog.getObject(request.focusObjectKey) : null;
@@ -550,6 +692,7 @@ export async function buildLocalContextPack(projectRoot, request) {
550
692
  hintConflicts: hintResult.conflicts,
551
693
  retrievalDiagnostics: {
552
694
  strategy: usedFullCatalog ? 'full_catalog' : 'sqlite_fts',
695
+ lanes: snapshotRetrieval.lanes,
553
696
  focusedModelAreaId: focusedArea?.id,
554
697
  modelAreaSource: focusedArea?.source,
555
698
  selectedObjects: objects.length,
@@ -588,7 +731,7 @@ export async function buildLocalContextPack(projectRoot, request) {
588
731
  return { ...payload, id };
589
732
  }
590
733
  finally {
591
- catalog.close();
734
+ snapshotLease.release();
592
735
  runtimeCatalog.close();
593
736
  }
594
737
  }
@@ -611,7 +754,10 @@ function buildKnowledgeLens(catalog, context, selectedSkills) {
611
754
  modelAreaId: context?.modelAreaId,
612
755
  purpose: context?.purpose,
613
756
  skillRefs,
614
- snapshotId: context?.snapshotId ?? catalog.state('fingerprint') ?? 'metadata-unavailable',
757
+ // The immutable search snapshot is authoritative. A caller-provided
758
+ // envelope may have been resolved against an earlier manifest signature;
759
+ // it cannot relabel the catalog that actually supplied evidence.
760
+ snapshotId: catalog.state('fingerprint') ?? 'metadata-unavailable',
615
761
  capsuleFingerprint: stringValue(capsule?.payload?.fingerprint)
616
762
  ?? stringValue(capsule?.payload?.sourceFingerprint),
617
763
  skillFingerprints: Object.keys(skillFingerprints).length > 0 ? skillFingerprints : undefined,
@@ -689,6 +835,7 @@ export function buildMetadataSnapshot(projectRoot, manifest, semanticLayer, skil
689
835
  addRawDbtManifestCatalogObjects(projectRoot, manifest, objects, edges, diagnostics);
690
836
  addBlockDependencyEdges(manifest, edges);
691
837
  addBlockOutputLineageObjects(manifest, objects, edges);
838
+ addCertifiedBlockAnalyticalCapabilities(objects);
692
839
  // Manifest v3 is the unified DQL runtime. DataLex is migration input only;
693
840
  // retaining it here would reintroduce a second analytical source of truth.
694
841
  if (manifest.manifestVersion !== 3) {
@@ -732,6 +879,171 @@ export function buildMetadataSnapshot(projectRoot, manifest, semanticLayer, skil
732
879
  snapshot.fingerprint = fingerprintSnapshot(snapshot);
733
880
  return snapshot;
734
881
  }
882
+ /**
883
+ * Upgrade only fully declared certified blocks into RFC 0005 capabilities.
884
+ * Local/display-name matching is intentionally insufficient: every reusable
885
+ * metric, grain, dimension/filter, and output role must bind exactly to the
886
+ * already-normalized semantic capability in this immutable snapshot.
887
+ *
888
+ * Acceptance: CONTRACT-002, AGT-018.
889
+ */
890
+ function addCertifiedBlockAnalyticalCapabilities(objects) {
891
+ const semanticCapabilities = [...objects.values()].flatMap((object) => {
892
+ if (object.objectType !== "semantic_metric")
893
+ return [];
894
+ const capability = normalizeMetricCapabilityContract(object.payload?.analyticalCapability);
895
+ if (!capability)
896
+ return [];
897
+ const payload = object.payload ?? {};
898
+ return [
899
+ {
900
+ object,
901
+ capability,
902
+ exactRefs: new Set(uniqueNonBlank([
903
+ capability.metricId,
904
+ object.objectKey,
905
+ object.fullName,
906
+ stringValue(payload.qualifiedId),
907
+ stringValue(payload.sourceNativeId),
908
+ ...metadataStringArray(payload.aliases),
909
+ ])),
910
+ },
911
+ ];
912
+ });
913
+ for (const block of objects.values()) {
914
+ if (block.objectType !== "dql_block" || block.status !== "certified")
915
+ continue;
916
+ const payload = block.payload ?? {};
917
+ const metricRefs = uniqueNonBlank([
918
+ ...metadataStringArray(payload.metricRefs),
919
+ ]);
920
+ if (metricRefs.length !== 1)
921
+ continue;
922
+ const metrics = semanticCapabilities.filter((candidate) => candidate.exactRefs.has(metricRefs[0]));
923
+ if (metrics.length !== 1)
924
+ continue;
925
+ const semantic = metrics[0];
926
+ const grain = stringValue(payload.grain);
927
+ if (!grain || !semantic.capability.resultGrainIds.includes(grain))
928
+ continue;
929
+ const groupedRefs = uniqueNonBlank([
930
+ ...metadataStringArray(payload.dimensions),
931
+ ...metadataStringArray(payload.dimensionsRef),
932
+ ]);
933
+ const filterRefs = uniqueNonBlank(metadataStringArray(payload.allowedFilters));
934
+ const declaredRefs = new Set([...groupedRefs, ...filterRefs]);
935
+ const capabilityDimensionIds = new Set(semantic.capability.dimensions.map((item) => item.dimensionId));
936
+ const capabilityTimeIds = new Set(semantic.capability.timeDimensions.map((item) => item.dimensionId));
937
+ if ([...declaredRefs].some((id) => !capabilityDimensionIds.has(id) && !capabilityTimeIds.has(id)))
938
+ continue;
939
+ const dimensions = semantic.capability.dimensions.flatMap((dimension) => {
940
+ if (!declaredRefs.has(dimension.dimensionId))
941
+ return [];
942
+ const roles = dimension.supportedRoles.filter((role) => (role === "filter" && filterRefs.includes(dimension.dimensionId)) ||
943
+ (role === "group_by" &&
944
+ groupedRefs.includes(dimension.dimensionId)) ||
945
+ (role === "display" &&
946
+ groupedRefs.includes(dimension.dimensionId)) ||
947
+ (role === "rank_entity" &&
948
+ payload.pattern === "ranking" &&
949
+ groupedRefs.includes(dimension.dimensionId)));
950
+ return roles.length > 0
951
+ ? [{ ...dimension, supportedRoles: roles }]
952
+ : [];
953
+ });
954
+ if ([...declaredRefs].some((id) => capabilityDimensionIds.has(id) &&
955
+ !dimensions.some((dimension) => dimension.dimensionId === id)))
956
+ continue;
957
+ const timeDimensions = semantic.capability.timeDimensions.filter((dimension) => declaredRefs.has(dimension.dimensionId));
958
+ const outputs = analyticalBlockOutputs(payload.outputContract);
959
+ if (!outputs ||
960
+ outputs.ids.length === 0 ||
961
+ !outputs.kinds.includes("metric_value"))
962
+ continue;
963
+ const operations = [];
964
+ if (filterRefs.length > 0)
965
+ operations.push("filter");
966
+ if (groupedRefs.some((id) => capabilityDimensionIds.has(id)))
967
+ operations.push("group");
968
+ if (payload.pattern === "trend")
969
+ operations.push("trend");
970
+ if (payload.pattern === "ranking" && outputs.kinds.includes("rank"))
971
+ operations.push("rank");
972
+ if (outputs.kinds.includes("delta") ||
973
+ outputs.kinds.includes("percent_delta"))
974
+ operations.push("compare");
975
+ const blockId = stringValue(payload.qualifiedId) ?? block.objectKey;
976
+ const fingerprint = stringValue(recordPayload(payload.businessFingerprint)?.hash) ??
977
+ sha256(stableStringify({
978
+ blockId,
979
+ metricRefs,
980
+ grain,
981
+ groupedRefs,
982
+ filterRefs,
983
+ outputs,
984
+ }));
985
+ const capability = {
986
+ ...semantic.capability,
987
+ defaultResultGrainId: grain,
988
+ resultGrainIds: uniqueNonBlank([
989
+ grain,
990
+ ...dimensions.map((dimension) => dimension.entityId),
991
+ ]),
992
+ dimensions,
993
+ timeDimensions,
994
+ operations: [...new Set(operations)],
995
+ supportedOutputKinds: outputs.kinds,
996
+ declaredOutputIds: outputs.ids,
997
+ executionCapabilities: [
998
+ { route: "certified", adapterId: block.objectKey },
999
+ ],
1000
+ sourceFingerprint: fingerprint.startsWith("sha256:")
1001
+ ? fingerprint
1002
+ : `sha256:${fingerprint}`,
1003
+ };
1004
+ const normalized = normalizeMetricCapabilityContract(capability);
1005
+ if (!normalized)
1006
+ continue;
1007
+ objects.set(block.objectKey, {
1008
+ ...block,
1009
+ payload: { ...payload, analyticalCapability: normalized },
1010
+ });
1011
+ }
1012
+ }
1013
+ function analyticalBlockOutputs(value) {
1014
+ if (!Array.isArray(value) || value.length === 0)
1015
+ return undefined;
1016
+ const ids = [];
1017
+ const kinds = [];
1018
+ for (const item of value) {
1019
+ const output = recordPayload(item);
1020
+ const id = stringValue(output?.name);
1021
+ const role = stringValue(output?.role)?.toLowerCase();
1022
+ if (!id || !role)
1023
+ return undefined;
1024
+ const kind = role === "metric" || role === "measure" || role === "value"
1025
+ ? "metric_value"
1026
+ : role === "dimension" || role === "grain"
1027
+ ? "dimension"
1028
+ : role === "rank"
1029
+ ? "rank"
1030
+ : role === "delta"
1031
+ ? "delta"
1032
+ : role === "percent_delta"
1033
+ ? "percent_delta"
1034
+ : undefined;
1035
+ if (!kind)
1036
+ return undefined;
1037
+ ids.push(id);
1038
+ kinds.push(kind);
1039
+ }
1040
+ return { ids: uniqueNonBlank(ids), kinds: [...new Set(kinds)] };
1041
+ }
1042
+ function recordPayload(value) {
1043
+ return value && typeof value === "object" && !Array.isArray(value)
1044
+ ? value
1045
+ : undefined;
1046
+ }
735
1047
  /**
736
1048
  * Manifest graph v2 intentionally omits verbose objects and edges. Snapshot
737
1049
  * construction rematerializes that compiler graph once, then persists the
@@ -836,8 +1148,16 @@ function addManifestKnowledgeGraph(manifest, objects, edges) {
836
1148
  sourceSystem: 'DQL compiled cross-domain policy',
837
1149
  payload: compactObject(route),
838
1150
  });
839
- addMetadataEdge(edges, 'cross_domain_route', keyByGraphId.get(`domain::${route.providerDomainId}`) ?? `domain:${route.providerDomainId}`, objectKey, { state: route.state, reasonCodes: route.reasonCodes });
840
- addMetadataEdge(edges, 'cross_domain_route', objectKey, keyByGraphId.get(`domain::${route.consumerDomainId}`) ?? `domain:${route.consumerDomainId}`, { state: route.state, reasonCodes: route.reasonCodes });
1151
+ addMetadataEdge(edges, "cross_domain_route", keyByGraphId.get(`domain::${route.providerDomainId}`) ??
1152
+ `domain:${route.providerDomainId}`, objectKey, {
1153
+ state: route.state,
1154
+ reasonCodes: route.reasonCodes,
1155
+ });
1156
+ addMetadataEdge(edges, "cross_domain_route", objectKey, keyByGraphId.get(`domain::${route.consumerDomainId}`) ??
1157
+ `domain:${route.consumerDomainId}`, {
1158
+ state: route.state,
1159
+ reasonCodes: route.reasonCodes,
1160
+ });
841
1161
  }
842
1162
  for (const edge of graph.edges ?? []) {
843
1163
  const fromKey = keyByGraphId.get(edge.from) ?? edge.from;
@@ -1036,6 +1356,15 @@ export class MetadataCatalog {
1036
1356
  tokenize = 'porter unicode61'
1037
1357
  );
1038
1358
 
1359
+ CREATE TABLE IF NOT EXISTS metadata_vector_index (
1360
+ object_key TEXT PRIMARY KEY,
1361
+ provider_id TEXT NOT NULL,
1362
+ dimensions INTEGER NOT NULL,
1363
+ vector BLOB NOT NULL,
1364
+ text_hash TEXT NOT NULL
1365
+ );
1366
+ CREATE INDEX IF NOT EXISTS idx_metadata_vector_provider ON metadata_vector_index(provider_id);
1367
+
1039
1368
  CREATE TABLE IF NOT EXISTS metadata_edges (
1040
1369
  edge_type TEXT NOT NULL,
1041
1370
  from_key TEXT NOT NULL,
@@ -1192,6 +1521,10 @@ export class MetadataCatalog {
1192
1521
  const insertFts = this.db.prepare(`
1193
1522
  INSERT INTO metadata_fts (object_key, name, full_name, description, domain, owner, payload)
1194
1523
  VALUES (?, ?, ?, ?, ?, ?, ?)
1524
+ `);
1525
+ const insertVector = this.db.prepare(`
1526
+ INSERT INTO metadata_vector_index (object_key, provider_id, dimensions, vector, text_hash)
1527
+ VALUES (?, ?, ?, ?, ?)
1195
1528
  `);
1196
1529
  const insertEdge = this.db.prepare(`
1197
1530
  INSERT OR IGNORE INTO metadata_edges (
@@ -1226,6 +1559,7 @@ export class MetadataCatalog {
1226
1559
  const txn = this.db.transaction(() => {
1227
1560
  this.db.prepare('DELETE FROM metadata_edges').run();
1228
1561
  this.db.prepare('DELETE FROM metadata_fts').run();
1562
+ this.db.prepare('DELETE FROM metadata_vector_index').run();
1229
1563
  this.db.prepare('DELETE FROM metadata_objects').run();
1230
1564
  this.db.prepare('DELETE FROM metadata_diagnostics').run();
1231
1565
  this.db.prepare('DELETE FROM metadata_source_fingerprints').run();
@@ -1235,6 +1569,11 @@ export class MetadataCatalog {
1235
1569
  const payload = object.payload ?? {};
1236
1570
  insertObject.run(object.objectKey, object.objectType, object.name, object.fullName ?? null, object.domain ?? null, object.owner ?? null, object.status ?? null, object.description ?? null, object.sourcePath ?? null, object.sourceSystem ?? null, JSON.stringify(payload), object.updatedAt ?? now);
1237
1571
  insertFts.run(object.objectKey, object.name, object.fullName ?? '', object.description ?? '', object.domain ?? '', object.owner ?? '', searchableMetadataPayload(payload));
1572
+ if (isVectorIndexObject(object)) {
1573
+ const vectorText = metadataVectorText(object);
1574
+ const vector = DEFAULT_VECTOR_PROVIDER.embedOne(vectorText);
1575
+ insertVector.run(object.objectKey, DEFAULT_VECTOR_PROVIDER.id, vector.length, encodeFloat32Vector(vector), sha256(vectorText));
1576
+ }
1238
1577
  }
1239
1578
  for (const edge of snapshot.edges) {
1240
1579
  insertEdge.run(edge.edgeType, edge.fromKey, edge.toKey, edge.confidence ?? 1, JSON.stringify(edge.payload ?? {}), now);
@@ -1262,6 +1601,8 @@ export class MetadataCatalog {
1262
1601
  setState.run('compile_conflicts_json', JSON.stringify(snapshot.compileConflicts ?? []));
1263
1602
  setState.run('manifest_generated_at', snapshot.manifest.generatedAt);
1264
1603
  setState.run('index_version', METADATA_INDEX_VERSION);
1604
+ setState.run('vector_provider', DEFAULT_VECTOR_PROVIDER.id);
1605
+ setState.run('vector_dimensions', String(DEFAULT_VECTOR_PROVIDER.dimensions));
1265
1606
  });
1266
1607
  txn();
1267
1608
  }
@@ -1303,6 +1644,9 @@ export class MetadataCatalog {
1303
1644
  const insertFts = this.db.prepare(`
1304
1645
  INSERT INTO metadata_fts (object_key, name, full_name, description, domain, owner, payload)
1305
1646
  VALUES (?, ?, ?, ?, ?, ?, ?)`);
1647
+ const insertVector = this.db.prepare(`
1648
+ INSERT INTO metadata_vector_index (object_key, provider_id, dimensions, vector, text_hash)
1649
+ VALUES (?, ?, ?, ?, ?)`);
1306
1650
  const insertEdge = this.db.prepare(`
1307
1651
  INSERT OR IGNORE INTO metadata_edges (edge_type, from_key, to_key, confidence, payload_json, created_at)
1308
1652
  VALUES (?, ?, ?, ?, ?, ?)`);
@@ -1324,15 +1668,18 @@ export class MetadataCatalog {
1324
1668
  INSERT OR REPLACE INTO skill_bodies (body_hash, encoding, compressed_body, original_length)
1325
1669
  VALUES (?, 'br', ?, ?)`);
1326
1670
  const deleteFtsForSource = this.db.prepare('DELETE FROM metadata_fts WHERE object_key IN (SELECT object_key FROM metadata_objects WHERE COALESCE(source_path, source_system) = ?)');
1671
+ const deleteVectorsForSource = this.db.prepare('DELETE FROM metadata_vector_index WHERE object_key IN (SELECT object_key FROM metadata_objects WHERE COALESCE(source_path, source_system) = ?)');
1327
1672
  const deleteObjectsForSource = this.db.prepare('DELETE FROM metadata_objects WHERE COALESCE(source_path, source_system) = ?');
1328
1673
  const txn = this.db.transaction(() => {
1329
1674
  // Drop changed + removed sources' rows, and ALL sourceless rows (never
1330
1675
  // fingerprinted, so always refreshed).
1331
1676
  for (const source of [...changedSources, ...removedSources]) {
1332
1677
  deleteFtsForSource.run(source);
1678
+ deleteVectorsForSource.run(source);
1333
1679
  deleteObjectsForSource.run(source);
1334
1680
  }
1335
1681
  this.db.prepare('DELETE FROM metadata_fts WHERE object_key IN (SELECT object_key FROM metadata_objects WHERE source_path IS NULL AND source_system IS NULL)').run();
1682
+ this.db.prepare('DELETE FROM metadata_vector_index WHERE object_key IN (SELECT object_key FROM metadata_objects WHERE source_path IS NULL AND source_system IS NULL)').run();
1336
1683
  this.db.prepare('DELETE FROM metadata_objects WHERE source_path IS NULL AND source_system IS NULL').run();
1337
1684
  // Re-insert only objects from changed sources or sourceless; unchanged
1338
1685
  // sources' rows are left untouched.
@@ -1343,6 +1690,11 @@ export class MetadataCatalog {
1343
1690
  const payload = object.payload ?? {};
1344
1691
  insertObject.run(object.objectKey, object.objectType, object.name, object.fullName ?? null, object.domain ?? null, object.owner ?? null, object.status ?? null, object.description ?? null, object.sourcePath ?? null, object.sourceSystem ?? null, JSON.stringify(payload), object.updatedAt ?? now);
1345
1692
  insertFts.run(object.objectKey, object.name, object.fullName ?? '', object.description ?? '', object.domain ?? '', object.owner ?? '', searchableMetadataPayload(payload));
1693
+ if (isVectorIndexObject(object)) {
1694
+ const vectorText = metadataVectorText(object);
1695
+ const vector = DEFAULT_VECTOR_PROVIDER.embedOne(vectorText);
1696
+ insertVector.run(object.objectKey, DEFAULT_VECTOR_PROVIDER.id, vector.length, encodeFloat32Vector(vector), sha256(vectorText));
1697
+ }
1346
1698
  }
1347
1699
  // Cross-source tables: rebuild fully.
1348
1700
  this.db.prepare('DELETE FROM metadata_edges').run();
@@ -1378,12 +1730,14 @@ export class MetadataCatalog {
1378
1730
  setState.run('compile_conflicts_json', JSON.stringify(snapshot.compileConflicts ?? []));
1379
1731
  setState.run('manifest_generated_at', snapshot.manifest.generatedAt);
1380
1732
  setState.run('index_version', METADATA_INDEX_VERSION);
1733
+ setState.run('vector_provider', DEFAULT_VECTOR_PROVIDER.id);
1734
+ setState.run('vector_dimensions', String(DEFAULT_VECTOR_PROVIDER.dimensions));
1381
1735
  });
1382
1736
  txn();
1383
1737
  return { mode: 'incremental', changedSources: changedSources.size };
1384
1738
  }
1385
1739
  searchObjects(options) {
1386
- const { query, objectTypes, domain, limit = 40 } = options;
1740
+ const { query, objectTypes, domain, domains, limit = 40 } = options;
1387
1741
  const match = buildFtsMatch(query, { prefix: true });
1388
1742
  if (!match.or)
1389
1743
  return this.listObjects({ objectTypes, domain, limit });
@@ -1397,6 +1751,10 @@ export class MetadataCatalog {
1397
1751
  filters.push('o.domain = ?');
1398
1752
  extraParams.push(domain);
1399
1753
  }
1754
+ else if (domains?.length) {
1755
+ filters.push(`(o.domain IS NULL OR o.domain IN (${domains.map(() => '?').join(', ')}))`);
1756
+ extraParams.push(...domains);
1757
+ }
1400
1758
  const whereExtra = filters.length > 0 ? ` AND ${filters.join(' AND ')}` : '';
1401
1759
  const runMatch = (matchExpr) => this.db.prepare(`
1402
1760
  SELECT o.*,
@@ -1429,6 +1787,106 @@ export class MetadataCatalog {
1429
1787
  snippet: row.snip ?? undefined,
1430
1788
  }));
1431
1789
  }
1790
+ /** Replace the snapshot vector lane with an explicitly configured provider. */
1791
+ async rebuildVectorIndex(provider, batchSize = 96) {
1792
+ const objects = [];
1793
+ this.scanObjects({ batchSize: 500 }, (rows) => {
1794
+ objects.push(...rows.filter(isVectorIndexObject));
1795
+ });
1796
+ const encoded = [];
1797
+ let dimensions = 0;
1798
+ for (let offset = 0; offset < objects.length; offset += Math.max(1, batchSize)) {
1799
+ const batch = objects.slice(offset, offset + Math.max(1, batchSize));
1800
+ const texts = batch.map(metadataVectorText);
1801
+ const vectors = await provider.embed(texts);
1802
+ if (vectors.length !== batch.length) {
1803
+ throw new Error(`Embedding provider ${provider.id} returned ${vectors.length} vectors for ${batch.length} objects.`);
1804
+ }
1805
+ for (let index = 0; index < batch.length; index += 1) {
1806
+ const vector = vectors[index] ?? [];
1807
+ if (vector.length === 0)
1808
+ throw new Error(`Embedding provider ${provider.id} returned an empty vector.`);
1809
+ if (dimensions === 0)
1810
+ dimensions = vector.length;
1811
+ if (vector.length !== dimensions) {
1812
+ throw new Error(`Embedding provider ${provider.id} changed dimensions from ${dimensions} to ${vector.length}.`);
1813
+ }
1814
+ encoded.push({
1815
+ objectKey: batch[index].objectKey,
1816
+ dimensions,
1817
+ vector: encodeFloat32Vector(vector),
1818
+ textHash: sha256(texts[index]),
1819
+ });
1820
+ }
1821
+ }
1822
+ const insert = this.db.prepare(`
1823
+ INSERT INTO metadata_vector_index (object_key, provider_id, dimensions, vector, text_hash)
1824
+ VALUES (?, ?, ?, ?, ?)
1825
+ `);
1826
+ const setState = this.db.prepare('INSERT OR REPLACE INTO metadata_state (key, value) VALUES (?, ?)');
1827
+ this.db.transaction(() => {
1828
+ this.db.prepare('DELETE FROM metadata_vector_index').run();
1829
+ for (const item of encoded) {
1830
+ insert.run(item.objectKey, provider.id, item.dimensions, item.vector, item.textHash);
1831
+ }
1832
+ setState.run('vector_provider', provider.id);
1833
+ setState.run('vector_dimensions', String(dimensions));
1834
+ })();
1835
+ }
1836
+ /** Independent vector candidate generation over all eligible snapshot cards. */
1837
+ async searchVectorObjects(options) {
1838
+ const indexedProviderId = this.state('vector_provider') ?? DEFAULT_VECTOR_PROVIDER.id;
1839
+ const indexedDimensions = Number(this.state('vector_dimensions') ?? DEFAULT_VECTOR_PROVIDER.dimensions);
1840
+ const provider = options.provider ?? DEFAULT_VECTOR_PROVIDER;
1841
+ if (provider.id !== indexedProviderId) {
1842
+ return {
1843
+ providerId: indexedProviderId,
1844
+ dimensions: indexedDimensions,
1845
+ candidates: [],
1846
+ unavailableReason: `requested provider ${provider.id} does not match snapshot vector provider ${indexedProviderId}`,
1847
+ };
1848
+ }
1849
+ const [queryVector] = provider === DEFAULT_VECTOR_PROVIDER
1850
+ ? [DEFAULT_VECTOR_PROVIDER.embedOne(options.query)]
1851
+ : await provider.embed([options.query]);
1852
+ if (!queryVector || queryVector.length !== indexedDimensions) {
1853
+ return {
1854
+ providerId: indexedProviderId,
1855
+ dimensions: indexedDimensions,
1856
+ candidates: [],
1857
+ unavailableReason: `query vector dimensions do not match snapshot index (${queryVector?.length ?? 0} vs ${indexedDimensions})`,
1858
+ };
1859
+ }
1860
+ const filters = ['v.provider_id = ?'];
1861
+ const params = [indexedProviderId];
1862
+ if (options.objectTypes?.length) {
1863
+ filters.push(`o.object_type IN (${options.objectTypes.map(() => '?').join(', ')})`);
1864
+ params.push(...options.objectTypes);
1865
+ }
1866
+ if (options.domains?.length) {
1867
+ filters.push(`(o.domain IS NULL OR o.domain IN (${options.domains.map(() => '?').join(', ')}))`);
1868
+ params.push(...options.domains);
1869
+ }
1870
+ const rows = this.db.prepare(`
1871
+ SELECT o.*, v.vector AS vector_blob
1872
+ FROM metadata_vector_index AS v
1873
+ JOIN metadata_objects AS o ON o.object_key = v.object_key
1874
+ WHERE ${filters.join(' AND ')}
1875
+ ORDER BY o.object_key
1876
+ `).all(...params);
1877
+ const candidates = rows
1878
+ .map((row) => {
1879
+ const cosine = cosineSimilarity(queryVector, decodeFloat32Vector(row.vector_blob));
1880
+ return { ...rowToObject(row), score: Number(((cosine + 1) / 2).toFixed(6)) };
1881
+ })
1882
+ // A cosine of zero maps to 0.5 and is not evidence. Requiring positive
1883
+ // separation prevents hash collisions/unrelated cards from flooding the
1884
+ // downstream bounded context while retaining independent vector recall.
1885
+ .filter((row) => (row.score ?? 0) > 0.55)
1886
+ .sort((left, right) => (right.score ?? 0) - (left.score ?? 0) || left.objectKey.localeCompare(right.objectKey))
1887
+ .slice(0, Math.max(1, options.limit ?? 40));
1888
+ return { providerId: indexedProviderId, dimensions: indexedDimensions, candidates };
1889
+ }
1432
1890
  listObjects(options = {}) {
1433
1891
  const filters = [];
1434
1892
  const params = [];
@@ -1556,13 +2014,49 @@ export class MetadataCatalog {
1556
2014
  return rows;
1557
2015
  }
1558
2016
  findObjectByIdentity(identity) {
2017
+ return this.resolveIdentity(identity).object ?? null;
2018
+ }
2019
+ /** Resolve exact/qualified/native/alias identities without first-match wins. */
2020
+ resolveIdentity(identity) {
2021
+ const normalized = identity.trim();
2022
+ if (!normalized)
2023
+ return { identity, status: 'missing', candidates: [] };
2024
+ const exact = this.db.prepare(`
2025
+ SELECT * FROM metadata_objects WHERE object_key = ?
2026
+ `).get(normalized);
2027
+ if (exact) {
2028
+ const object = rowToObject(exact);
2029
+ return { identity, status: 'resolved', matchedBy: 'object_key', object, candidates: [object] };
2030
+ }
1559
2031
  const rows = this.db.prepare(`
1560
- SELECT * FROM metadata_objects
1561
- WHERE object_key = ? OR full_name = ?
1562
- ORDER BY CASE WHEN object_key = ? THEN 0 ELSE 1 END, object_key
1563
- LIMIT 2
1564
- `).all(identity, identity, identity);
1565
- return rows.length === 1 ? rowToObject(rows[0]) : rows[0] ? rowToObject(rows[0]) : null;
2032
+ SELECT DISTINCT o.*
2033
+ FROM metadata_objects AS o
2034
+ WHERE o.full_name = ?
2035
+ OR json_extract(o.payload_json, '$.sourceNativeId') = ?
2036
+ OR EXISTS (
2037
+ SELECT 1
2038
+ FROM json_each(COALESCE(json_extract(o.payload_json, '$.aliases'), '[]')) AS alias
2039
+ WHERE alias.value = ?
2040
+ )
2041
+ ORDER BY o.object_key
2042
+ LIMIT 21
2043
+ `).all(normalized, normalized, normalized);
2044
+ const candidates = rows.map(rowToObject);
2045
+ if (candidates.length === 0)
2046
+ return { identity, status: 'missing', candidates: [] };
2047
+ if (candidates.length > 1)
2048
+ return { identity, status: 'ambiguous', candidates };
2049
+ const object = candidates[0];
2050
+ const payload = object.payload ?? {};
2051
+ const aliases = metadataStringArray(payload.aliases);
2052
+ const matchedBy = object.fullName === normalized
2053
+ ? 'qualified_id'
2054
+ : stringValue(payload.sourceNativeId) === normalized
2055
+ ? 'source_native_id'
2056
+ : aliases.includes(normalized)
2057
+ ? 'alias'
2058
+ : 'qualified_id';
2059
+ return { identity, status: 'resolved', matchedBy, object, candidates };
1566
2060
  }
1567
2061
  edgesForKeys(keys, hops = 1) {
1568
2062
  let frontier = new Set(keys.filter(Boolean));
@@ -1944,11 +2438,11 @@ function objectFromKGNode(node) {
1944
2438
  reviewCadence: node.reviewCadence,
1945
2439
  pattern: node.pattern,
1946
2440
  grain: node.grain,
1947
- entities: node.entities ?? [],
2441
+ entities: node.entities ?? node.payload?.entities ?? [],
1948
2442
  declaredOutputs: node.declaredOutputs ?? [],
1949
2443
  outputs: node.outputs ?? [],
1950
2444
  outputContract: node.outputContract ?? [],
1951
- dimensions: node.dimensions ?? [],
2445
+ dimensions: node.dimensions ?? node.payload?.dimensions ?? [],
1952
2446
  allowedFilters: node.allowedFilters ?? [],
1953
2447
  parameterPolicy: node.parameterPolicy ?? [],
1954
2448
  parameters: node.parameters ?? [],
@@ -2087,7 +2581,8 @@ function addSkillObjects(skills, objects, edges) {
2087
2581
  domain: skill.domain,
2088
2582
  domains: skill.domains ?? [],
2089
2583
  modelAreaRefs: skill.modelAreaRefs ?? [],
2090
- status: skill.status ?? 'active',
2584
+ status: skill.status ?? "active",
2585
+ analyticalPolicy: skill.analyticalPolicy,
2091
2586
  bodyHash,
2092
2587
  }));
2093
2588
  const objectKey = `skill:${identity}`;
@@ -2122,6 +2617,7 @@ function addSkillObjects(skills, objects, edges) {
2122
2617
  examples: skill.examples ?? [],
2123
2618
  sourceRefs: skill.sourceRefs ?? [],
2124
2619
  vocabulary: skill.vocabulary,
2620
+ analyticalPolicy: skill.analyticalPolicy,
2125
2621
  bodyHash,
2126
2622
  sourceFingerprint,
2127
2623
  isStarter: skill.isStarter,
@@ -3190,7 +3686,7 @@ async function planContextPackRoute(input) {
3190
3686
  plan: input.questionPlan,
3191
3687
  block: object,
3192
3688
  exactExampleMatch: hasExactExampleQuestion(input.request.question, object),
3193
- definitionLookup: intent === 'definition_lookup',
3689
+ definitionLookup: intent === 'definition_lookup' && objectNameInQuestion(input.request.question, object),
3194
3690
  }),
3195
3691
  applicabilityScore: applicabilityByKey.get(object.objectKey)?.score ?? 0,
3196
3692
  }))
@@ -3238,7 +3734,7 @@ async function planContextPackRoute(input) {
3238
3734
  : undefined;
3239
3735
  const exactExampleMatch = exact ? hasExactExampleQuestion(input.request.question, exact) : false;
3240
3736
  const directCertifiedBypass = Boolean(exact && (exactExampleMatch
3241
- || intent === 'definition_lookup'
3737
+ || (intent === 'definition_lookup' && objectNameInQuestion(input.request.question, exact))
3242
3738
  || objectNameInQuestion(input.request.question, exact)));
3243
3739
  const blockFitObject = exact
3244
3740
  ?? (contextApplicability ? input.objects.find((object) => object.objectKey === contextApplicability.objectKey) : undefined);
@@ -3248,7 +3744,8 @@ async function planContextPackRoute(input) {
3248
3744
  plan: input.questionPlan,
3249
3745
  block: blockFitObject,
3250
3746
  exactExampleMatch: exact ? exactExampleMatch : false,
3251
- definitionLookup: Boolean(exact && intent === 'definition_lookup'),
3747
+ definitionLookup: Boolean(exact && intent === 'definition_lookup'
3748
+ && objectNameInQuestion(input.request.question, blockFitObject)),
3252
3749
  })
3253
3750
  : undefined;
3254
3751
  const blockFit = rawBlockFit && blockFitObject
@@ -3635,8 +4132,14 @@ function isGeneratedMetadataIntent(intent) {
3635
4132
  function findExactCertifiedObject(question, intent, objects) {
3636
4133
  const candidates = objects.filter((object) => isCertifiedMetadataObject(object));
3637
4134
  if (intent === 'definition_lookup') {
3638
- return candidates.find((object) => object.objectType === 'dql_term' || object.objectType === 'business_view')
3639
- ?? candidates.find((object) => object.objectType === 'dql_block' && objectNameInQuestion(question, object));
4135
+ // A definition-shaped sentence is not itself evidence of a match. Small
4136
+ // catalogs often return every certified term; selecting the first one made
4137
+ // unrelated questions (for example weather) look governed. Prefer an
4138
+ // explicitly named/example block, then require the term/view name itself.
4139
+ return candidates.find((object) => object.objectType === 'dql_block'
4140
+ && (hasExactExampleQuestion(question, object) || objectNameInQuestion(question, object)))
4141
+ ?? candidates.find((object) => (object.objectType === 'dql_term' || object.objectType === 'business_view')
4142
+ && objectNameInQuestion(question, object));
3640
4143
  }
3641
4144
  const namedExact = candidates.find((object) => object.objectType === 'dql_block' &&
3642
4145
  objectNameInQuestion(question, object) &&
@@ -5555,6 +6058,32 @@ function searchableMetadataPayload(payload) {
5555
6058
  visit(payload, 0);
5556
6059
  return tokens.join(' ');
5557
6060
  }
6061
+ const VECTOR_EXCLUDED_OBJECT_TYPES = new Set([
6062
+ ...COLUMN_OBJECT_TYPES,
6063
+ 'skill',
6064
+ 'runtime_value',
6065
+ ]);
6066
+ function isVectorIndexObject(object) {
6067
+ return !VECTOR_EXCLUDED_OBJECT_TYPES.has(object.objectType);
6068
+ }
6069
+ function metadataVectorText(object) {
6070
+ return [
6071
+ object.name,
6072
+ object.fullName ?? '',
6073
+ object.description ?? '',
6074
+ object.domain ?? '',
6075
+ object.owner ?? '',
6076
+ searchableMetadataPayload(object.payload ?? {}),
6077
+ ].filter(Boolean).join('\n').slice(0, 16_000);
6078
+ }
6079
+ function encodeFloat32Vector(vector) {
6080
+ const values = Float32Array.from(vector);
6081
+ return Buffer.from(values.buffer, values.byteOffset, values.byteLength);
6082
+ }
6083
+ function decodeFloat32Vector(blob) {
6084
+ const bytes = blob.buffer.slice(blob.byteOffset, blob.byteOffset + blob.byteLength);
6085
+ return new Float32Array(bytes);
6086
+ }
5558
6087
  function rowToObject(row) {
5559
6088
  return {
5560
6089
  objectKey: row.object_key,
@@ -5907,13 +6436,22 @@ function skillFromMetadataObject(object) {
5907
6436
  examples: stringArrayPayload(payload.examples),
5908
6437
  sourceRefs: stringArrayPayload(payload.sourceRefs),
5909
6438
  vocabulary: stringRecordPayload(payload.vocabulary),
5910
- body: '',
5911
- sourcePath: object.sourcePath ?? '',
6439
+ analyticalPolicy: skillAnalyticalPolicyPayload(payload.analyticalPolicy),
6440
+ body: "",
6441
+ sourcePath: object.sourcePath ?? "",
5912
6442
  isStarter: payload.isStarter === true,
5913
6443
  };
5914
6444
  }
5915
6445
  function localContextSkillFromParsed(skill, object) {
5916
6446
  const guidance = skill.body.slice(0, 4_000);
6447
+ const sourceHash = stringPayload(object.payload?.sourceFingerprint);
6448
+ const policy = skill.analyticalPolicy && sourceHash
6449
+ ? {
6450
+ policyId: `${skill.qualifiedId ?? skill.id}#analytical`,
6451
+ sourceHash,
6452
+ ...skill.analyticalPolicy,
6453
+ }
6454
+ : undefined;
5917
6455
  return {
5918
6456
  objectKey: object.objectKey,
5919
6457
  id: skill.id,
@@ -5933,6 +6471,7 @@ function localContextSkillFromParsed(skill, object) {
5933
6471
  requiredFilters: [...(skill.requiredFilters ?? [])],
5934
6472
  clarifyWhen: [...(skill.clarifyWhen ?? [])],
5935
6473
  vocabulary: { ...skill.vocabulary },
6474
+ ...(policy ? { analyticalPolicy: policy } : {}),
5936
6475
  guidance,
5937
6476
  guidanceTruncated: guidance.length < skill.body.length,
5938
6477
  sourceRefs: [...(skill.sourceRefs ?? [])],
@@ -5940,6 +6479,38 @@ function localContextSkillFromParsed(skill, object) {
5940
6479
  sourcePath: object.sourcePath,
5941
6480
  };
5942
6481
  }
6482
+ function skillAnalyticalPolicyPayload(value) {
6483
+ if (!value || typeof value !== "object" || Array.isArray(value))
6484
+ return undefined;
6485
+ const record = value;
6486
+ const completenessPolicy = stringPayload(record.completenessPolicy);
6487
+ const comparisonAlignment = stringPayload(record.comparisonAlignment);
6488
+ const defaultRankingPeriod = stringPayload(record.defaultRankingPeriod);
6489
+ const policy = {
6490
+ metricIds: stringArrayPayload(record.metricIds),
6491
+ timeRole: stringPayload(record.timeRole),
6492
+ calendarId: stringPayload(record.calendarId),
6493
+ timezone: stringPayload(record.timezone),
6494
+ completenessPolicy: completenessPolicy === "partial_current" ||
6495
+ completenessPolicy === "latest_complete" ||
6496
+ completenessPolicy === "closed_period"
6497
+ ? completenessPolicy
6498
+ : undefined,
6499
+ comparisonAlignment: comparisonAlignment === "elapsed_period" ||
6500
+ comparisonAlignment === "calendar_period" ||
6501
+ comparisonAlignment === "fiscal_period"
6502
+ ? comparisonAlignment
6503
+ : undefined,
6504
+ defaultRankingPeriod: defaultRankingPeriod === "current" ||
6505
+ defaultRankingPeriod === "comparison"
6506
+ ? defaultRankingPeriod
6507
+ : undefined,
6508
+ narrativeGuidance: stringArrayPayload(record.narrativeGuidance),
6509
+ };
6510
+ return Object.values(policy).some((item) => Array.isArray(item) ? item.length > 0 : Boolean(item))
6511
+ ? policy
6512
+ : undefined;
6513
+ }
5943
6514
  function stringPayload(value) {
5944
6515
  return typeof value === 'string' && value.trim() ? value : undefined;
5945
6516
  }