@hazeljs/kag 0.0.0-stage → 2.0.9

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 (47) hide show
  1. package/README.md +122 -2
  2. package/dist/adapters/graphrag.adapter.d.ts +47 -0
  3. package/dist/adapters/graphrag.adapter.d.ts.map +1 -0
  4. package/dist/adapters/graphrag.adapter.js +111 -0
  5. package/dist/confidence.d.ts +7 -0
  6. package/dist/confidence.d.ts.map +1 -0
  7. package/dist/confidence.js +40 -0
  8. package/dist/errors/kag.error.d.ts +42 -0
  9. package/dist/errors/kag.error.d.ts.map +1 -0
  10. package/dist/errors/kag.error.js +80 -0
  11. package/dist/index.d.ts +15 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +30 -0
  14. package/dist/ingestion/ingest.d.ts +24 -0
  15. package/dist/ingestion/ingest.d.ts.map +1 -0
  16. package/dist/ingestion/ingest.js +61 -0
  17. package/dist/kag.module.d.ts +29 -0
  18. package/dist/kag.module.d.ts.map +1 -0
  19. package/dist/kag.module.js +58 -0
  20. package/dist/kag.service.d.ts +66 -0
  21. package/dist/kag.service.d.ts.map +1 -0
  22. package/dist/kag.service.js +92 -0
  23. package/dist/knowledge-graph.d.ts +50 -0
  24. package/dist/knowledge-graph.d.ts.map +1 -0
  25. package/dist/knowledge-graph.js +290 -0
  26. package/dist/planner/heuristic-query-planner.d.ts +36 -0
  27. package/dist/planner/heuristic-query-planner.d.ts.map +1 -0
  28. package/dist/planner/heuristic-query-planner.js +67 -0
  29. package/dist/planner/industry-profiles.d.ts +34 -0
  30. package/dist/planner/industry-profiles.d.ts.map +1 -0
  31. package/dist/planner/industry-profiles.js +83 -0
  32. package/dist/resolution/entity-resolver.d.ts +21 -0
  33. package/dist/resolution/entity-resolver.d.ts.map +1 -0
  34. package/dist/resolution/entity-resolver.js +123 -0
  35. package/dist/security/authorizer.d.ts +27 -0
  36. package/dist/security/authorizer.d.ts.map +1 -0
  37. package/dist/security/authorizer.js +57 -0
  38. package/dist/stores/in-memory-knowledge.store.d.ts +22 -0
  39. package/dist/stores/in-memory-knowledge.store.d.ts.map +1 -0
  40. package/dist/stores/in-memory-knowledge.store.js +186 -0
  41. package/dist/temporal.d.ts +7 -0
  42. package/dist/temporal.d.ts.map +1 -0
  43. package/dist/temporal.js +63 -0
  44. package/dist/types.d.ts +207 -0
  45. package/dist/types.d.ts.map +1 -0
  46. package/dist/types.js +15 -0
  47. package/package.json +78 -4
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Industry / domain query templates for HeuristicQueryPlanner.
3
+ *
4
+ * Core KAG stays industry-agnostic. Apps compose a profile (or merge several)
5
+ * that matches their relation vocabulary — commerce, healthcare, ops, etc.
6
+ */
7
+ export interface QueryTemplate {
8
+ name: string;
9
+ /** Matched against the natural-language question. */
10
+ pattern: RegExp;
11
+ /** Optional relation-type filter for BFS. Omit to traverse all edges. */
12
+ relationTypes?: string[];
13
+ depth?: number;
14
+ }
15
+ export interface IndustryProfile {
16
+ /** Stable id, e.g. "commerce", "healthcare", "generic". */
17
+ id: string;
18
+ templates: QueryTemplate[];
19
+ /** Default traversal depth when no template matches. */
20
+ defaultDepth?: number;
21
+ }
22
+ /**
23
+ * Domain-neutral defaults: intent shaping only — no industry relation names.
24
+ * Relation filters are left open so any graph vocabulary works.
25
+ */
26
+ export declare const GENERIC_INDUSTRY_PROFILE: IndustryProfile;
27
+ /**
28
+ * Optional commerce / payments vocabulary (orders, refunds, settlements).
29
+ * Pass explicitly — never the default for all KAG apps.
30
+ */
31
+ export declare const COMMERCE_INDUSTRY_PROFILE: IndustryProfile;
32
+ /** Merge profiles; later templates win on same `name`. */
33
+ export declare function mergeIndustryProfiles(...profiles: IndustryProfile[]): IndustryProfile;
34
+ //# sourceMappingURL=industry-profiles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"industry-profiles.d.ts","sourceRoot":"","sources":["../../src/planner/industry-profiles.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,qDAAqD;IACrD,OAAO,EAAE,MAAM,CAAC;IAChB,yEAAyE;IACzE,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,2DAA2D;IAC3D,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,aAAa,EAAE,CAAC;IAC3B,wDAAwD;IACxD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;GAGG;AACH,eAAO,MAAM,wBAAwB,EAAE,eAoBtC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,yBAAyB,EAAE,eAuBvC,CAAC;AAEF,0DAA0D;AAC1D,wBAAgB,qBAAqB,CAAC,GAAG,QAAQ,EAAE,eAAe,EAAE,GAAG,eAAe,CAkBrF"}
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ /**
3
+ * Industry / domain query templates for HeuristicQueryPlanner.
4
+ *
5
+ * Core KAG stays industry-agnostic. Apps compose a profile (or merge several)
6
+ * that matches their relation vocabulary — commerce, healthcare, ops, etc.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.COMMERCE_INDUSTRY_PROFILE = exports.GENERIC_INDUSTRY_PROFILE = void 0;
10
+ exports.mergeIndustryProfiles = mergeIndustryProfiles;
11
+ /**
12
+ * Domain-neutral defaults: intent shaping only — no industry relation names.
13
+ * Relation filters are left open so any graph vocabulary works.
14
+ */
15
+ exports.GENERIC_INDUSTRY_PROFILE = {
16
+ id: 'generic',
17
+ defaultDepth: 3,
18
+ templates: [
19
+ {
20
+ name: 'causal',
21
+ pattern: /\b(why|cause|because|reason|led to|resulted|affect|impact|driven)\b/i,
22
+ depth: 4,
23
+ },
24
+ {
25
+ name: 'temporal',
26
+ pattern: /\b(when|was|were|before|after|changed|previously|currently|as of)\b/i,
27
+ depth: 3,
28
+ },
29
+ {
30
+ name: 'comparison',
31
+ pattern: /\b(lower|higher|vs|versus|compared|difference|delta|increase|decrease)\b/i,
32
+ depth: 3,
33
+ },
34
+ ],
35
+ };
36
+ /**
37
+ * Optional commerce / payments vocabulary (orders, refunds, settlements).
38
+ * Pass explicitly — never the default for all KAG apps.
39
+ */
40
+ exports.COMMERCE_INDUSTRY_PROFILE = {
41
+ id: 'commerce',
42
+ defaultDepth: 3,
43
+ templates: [
44
+ {
45
+ name: 'settlement',
46
+ pattern: /settlement|payout|profit|revenue|merchant/i,
47
+ relationTypes: ['HAS_ORDER', 'HAS_PAYMENT', 'HAS_REFUND', 'HAS_FEE', 'AFFECTS', 'SETTLED_BY'],
48
+ depth: 4,
49
+ },
50
+ {
51
+ name: 'refund',
52
+ pattern: /refund/i,
53
+ relationTypes: ['HAS_ORDER', 'HAS_PAYMENT', 'HAS_REFUND', 'REFUNDED_BY'],
54
+ depth: 3,
55
+ },
56
+ {
57
+ name: 'price',
58
+ pattern: /price|catalog|sku|product cost/i,
59
+ relationTypes: ['HAS_PRODUCT', 'PRICED_AS'],
60
+ depth: 2,
61
+ },
62
+ ],
63
+ };
64
+ /** Merge profiles; later templates win on same `name`. */
65
+ function mergeIndustryProfiles(...profiles) {
66
+ const byName = new Map();
67
+ let defaultDepth = 3;
68
+ const ids = [];
69
+ for (const profile of profiles) {
70
+ ids.push(profile.id);
71
+ if (profile.defaultDepth !== undefined) {
72
+ defaultDepth = profile.defaultDepth;
73
+ }
74
+ for (const template of profile.templates) {
75
+ byName.set(template.name, template);
76
+ }
77
+ }
78
+ return {
79
+ id: ids.join('+') || 'custom',
80
+ defaultDepth,
81
+ templates: [...byName.values()],
82
+ };
83
+ }
@@ -0,0 +1,21 @@
1
+ import type { EntityResolutionCandidate, EntityResolutionResult, EntityResolutionStrategy, KnowledgeEntity } from '../types';
2
+ export interface EntityResolverOptions {
3
+ strategies?: EntityResolutionStrategy[];
4
+ mergeThreshold?: number;
5
+ embed?: (text: string) => Promise<number[]> | number[];
6
+ custom?: (candidate: KnowledgeEntity, existing: KnowledgeEntity[]) => Promise<EntityResolutionCandidate[]> | EntityResolutionCandidate[];
7
+ }
8
+ /**
9
+ * Extensible entity resolution. Uncertain matches are returned as candidates
10
+ * and are never auto-merged below mergeThreshold.
11
+ */
12
+ export declare class EntityResolver {
13
+ private readonly strategies;
14
+ private readonly mergeThreshold;
15
+ private readonly embed?;
16
+ private readonly custom?;
17
+ constructor(options?: EntityResolverOptions);
18
+ resolve(candidate: KnowledgeEntity, existing: KnowledgeEntity[]): Promise<EntityResolutionResult>;
19
+ }
20
+ export declare function normalizeName(name: string): string;
21
+ //# sourceMappingURL=entity-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity-resolver.d.ts","sourceRoot":"","sources":["../../src/resolution/entity-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,yBAAyB,EACzB,sBAAsB,EACtB,wBAAwB,EACxB,eAAe,EAChB,MAAM,UAAU,CAAC;AAElB,MAAM,WAAW,qBAAqB;IACpC,UAAU,CAAC,EAAE,wBAAwB,EAAE,CAAC;IACxC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC;IACvD,MAAM,CAAC,EAAE,CACP,SAAS,EAAE,eAAe,EAC1B,QAAQ,EAAE,eAAe,EAAE,KACxB,OAAO,CAAC,yBAAyB,EAAE,CAAC,GAAG,yBAAyB,EAAE,CAAC;CACzE;AAED;;;GAGG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA6B;IACxD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAiC;IACxD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAkC;gBAE9C,OAAO,GAAE,qBAA0B;IAOzC,OAAO,CACX,SAAS,EAAE,eAAe,EAC1B,QAAQ,EAAE,eAAe,EAAE,GAC1B,OAAO,CAAC,sBAAsB,CAAC;CA+DnC;AAgBD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOlD"}
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EntityResolver = void 0;
4
+ exports.normalizeName = normalizeName;
5
+ /**
6
+ * Extensible entity resolution. Uncertain matches are returned as candidates
7
+ * and are never auto-merged below mergeThreshold.
8
+ */
9
+ class EntityResolver {
10
+ constructor(options = {}) {
11
+ this.strategies = options.strategies ?? ['exact', 'normalized'];
12
+ this.mergeThreshold = options.mergeThreshold ?? 0.9;
13
+ this.embed = options.embed;
14
+ this.custom = options.custom;
15
+ }
16
+ async resolve(candidate, existing) {
17
+ const candidates = [];
18
+ for (const strategy of this.strategies) {
19
+ if (strategy === 'exact') {
20
+ for (const entity of existing) {
21
+ if (entity.id === candidate.id) {
22
+ candidates.push({ entity, confidence: 1, strategy: 'exact' });
23
+ }
24
+ else if (namesMatchExact(entity, candidate)) {
25
+ candidates.push({ entity, confidence: 0.99, strategy: 'exact' });
26
+ }
27
+ }
28
+ }
29
+ if (strategy === 'normalized') {
30
+ const candNorm = normalizeName(displayName(candidate));
31
+ for (const entity of existing) {
32
+ const entNorm = normalizeName(displayName(entity));
33
+ if (candNorm && entNorm && candNorm === entNorm) {
34
+ candidates.push({ entity, confidence: 0.95, strategy: 'normalized' });
35
+ }
36
+ }
37
+ }
38
+ if (strategy === 'semantic' && this.embed) {
39
+ const candVec = await this.embed(displayName(candidate));
40
+ for (const entity of existing) {
41
+ const entVec = await this.embed(displayName(entity));
42
+ const sim = cosineSimilarity(candVec, entVec);
43
+ if (sim >= 0.8) {
44
+ candidates.push({ entity, confidence: sim, strategy: 'semantic' });
45
+ }
46
+ }
47
+ }
48
+ if (strategy === 'custom' && this.custom) {
49
+ candidates.push(...(await this.custom(candidate, existing)));
50
+ }
51
+ }
52
+ // Deduplicate by entity id, keep highest confidence
53
+ const byId = new Map();
54
+ for (const c of candidates) {
55
+ const prev = byId.get(c.entity.id);
56
+ if (!prev || c.confidence > prev.confidence) {
57
+ byId.set(c.entity.id, c);
58
+ }
59
+ }
60
+ const unique = [...byId.values()].sort((a, b) => b.confidence - a.confidence);
61
+ const best = unique[0];
62
+ if (best && best.confidence >= this.mergeThreshold) {
63
+ return {
64
+ merged: {
65
+ ...best.entity,
66
+ aliases: uniqueAliases(best.entity, candidate),
67
+ properties: { ...best.entity.properties, ...candidate.properties },
68
+ confidence: best.confidence,
69
+ },
70
+ candidates: unique,
71
+ };
72
+ }
73
+ return { candidates: unique };
74
+ }
75
+ }
76
+ exports.EntityResolver = EntityResolver;
77
+ function displayName(entity) {
78
+ const name = entity.properties.name ?? entity.properties.title ?? entity.id;
79
+ return String(name);
80
+ }
81
+ function namesMatchExact(a, b) {
82
+ const an = displayName(a);
83
+ const bn = displayName(b);
84
+ if (an === bn)
85
+ return true;
86
+ const aliasesA = a.aliases ?? [];
87
+ const aliasesB = b.aliases ?? [];
88
+ return aliasesA.includes(bn) || aliasesB.includes(an);
89
+ }
90
+ function normalizeName(name) {
91
+ return name
92
+ .toLowerCase()
93
+ .replace(/[.,]/g, ' ')
94
+ .replace(/\b(inc|incorporated|llc|ltd|limited|corp|corporation|co)\b/g, '')
95
+ .replace(/\s+/g, '')
96
+ .trim();
97
+ }
98
+ function uniqueAliases(base, incoming) {
99
+ const set = new Set([
100
+ ...(base.aliases ?? []),
101
+ ...(incoming.aliases ?? []),
102
+ displayName(incoming),
103
+ displayName(base),
104
+ ]);
105
+ set.delete(displayName(base));
106
+ return [...set].filter(Boolean);
107
+ }
108
+ function cosineSimilarity(a, b) {
109
+ const len = Math.min(a.length, b.length);
110
+ if (len === 0)
111
+ return 0;
112
+ let dot = 0;
113
+ let na = 0;
114
+ let nb = 0;
115
+ for (let i = 0; i < len; i++) {
116
+ dot += a[i] * b[i];
117
+ na += a[i] * a[i];
118
+ nb += b[i] * b[i];
119
+ }
120
+ if (na === 0 || nb === 0)
121
+ return 0;
122
+ return dot / (Math.sqrt(na) * Math.sqrt(nb));
123
+ }
@@ -0,0 +1,27 @@
1
+ import type { KnowledgeAuthorizer, KnowledgeScope } from '../types';
2
+ /**
3
+ * Default authorizer: require tenantId and enforce tenant equality.
4
+ */
5
+ export declare class TenantKnowledgeAuthorizer implements KnowledgeAuthorizer {
6
+ private readonly expectedTenantId?;
7
+ constructor(expectedTenantId?: string | undefined);
8
+ canAccess(scope: KnowledgeScope, _action: 'read' | 'write', _resource: {
9
+ kind: 'entity' | 'relation' | 'fact' | 'source';
10
+ id?: string;
11
+ type?: string;
12
+ }): boolean;
13
+ }
14
+ /**
15
+ * Adapts a CASL-shaped can(action, subject) without depending on @hazeljs/casl.
16
+ */
17
+ export declare function createCaslKnowledgeAuthorizer(can: (action: string, subject: string | {
18
+ __typename?: string;
19
+ id?: string;
20
+ tenantId?: string;
21
+ }) => boolean): KnowledgeAuthorizer;
22
+ export declare function assertAuthorized(authorizer: KnowledgeAuthorizer | undefined, scope: KnowledgeScope, action: 'read' | 'write', resource: {
23
+ kind: 'entity' | 'relation' | 'fact' | 'source';
24
+ id?: string;
25
+ type?: string;
26
+ }): Promise<void>;
27
+ //# sourceMappingURL=authorizer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authorizer.d.ts","sourceRoot":"","sources":["../../src/security/authorizer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAGpE;;GAEG;AACH,qBAAa,yBAA0B,YAAW,mBAAmB;IACvD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC;gBAAjB,gBAAgB,CAAC,EAAE,MAAM,YAAA;IAEtD,SAAS,CACP,KAAK,EAAE,cAAc,EACrB,OAAO,EAAE,MAAM,GAAG,OAAO,EACzB,SAAS,EAAE;QAAE,IAAI,EAAE,QAAQ,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GACzF,OAAO;CAOX;AAED;;GAEG;AACH,wBAAgB,6BAA6B,CAC3C,GAAG,EAAE,CACH,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,GAAG;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,KACtE,OAAO,GACX,mBAAmB,CAoBrB;AAED,wBAAsB,gBAAgB,CACpC,UAAU,EAAE,mBAAmB,GAAG,SAAS,EAC3C,KAAK,EAAE,cAAc,EACrB,MAAM,EAAE,MAAM,GAAG,OAAO,EACxB,QAAQ,EAAE;IAAE,IAAI,EAAE,QAAQ,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GACxF,OAAO,CAAC,IAAI,CAAC,CASf"}
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TenantKnowledgeAuthorizer = void 0;
4
+ exports.createCaslKnowledgeAuthorizer = createCaslKnowledgeAuthorizer;
5
+ exports.assertAuthorized = assertAuthorized;
6
+ const kag_error_1 = require("../errors/kag.error");
7
+ /**
8
+ * Default authorizer: require tenantId and enforce tenant equality.
9
+ */
10
+ class TenantKnowledgeAuthorizer {
11
+ constructor(expectedTenantId) {
12
+ this.expectedTenantId = expectedTenantId;
13
+ }
14
+ canAccess(scope, _action, _resource) {
15
+ if (!scope.tenantId)
16
+ return false;
17
+ if (this.expectedTenantId && scope.tenantId !== this.expectedTenantId) {
18
+ return false;
19
+ }
20
+ return true;
21
+ }
22
+ }
23
+ exports.TenantKnowledgeAuthorizer = TenantKnowledgeAuthorizer;
24
+ /**
25
+ * Adapts a CASL-shaped can(action, subject) without depending on @hazeljs/casl.
26
+ */
27
+ function createCaslKnowledgeAuthorizer(can) {
28
+ return {
29
+ canAccess(scope, action, resource) {
30
+ if (!scope.tenantId)
31
+ return false;
32
+ const subject = resource.kind === 'source'
33
+ ? { __typename: 'KnowledgeSource', id: resource.id, tenantId: scope.tenantId }
34
+ : {
35
+ __typename: resource.kind === 'entity'
36
+ ? 'KnowledgeEntity'
37
+ : resource.kind === 'relation'
38
+ ? 'KnowledgeRelation'
39
+ : 'KnowledgeFact',
40
+ id: resource.id,
41
+ tenantId: scope.tenantId,
42
+ };
43
+ return can(action === 'read' ? 'read' : 'update', subject);
44
+ },
45
+ };
46
+ }
47
+ async function assertAuthorized(authorizer, scope, action, resource) {
48
+ if (!scope.tenantId) {
49
+ throw kag_error_1.KagError.tenantRequired();
50
+ }
51
+ if (!authorizer)
52
+ return;
53
+ const ok = await authorizer.canAccess(scope, action, resource);
54
+ if (!ok) {
55
+ throw kag_error_1.KagError.accessDenied(`${resource.kind}:${resource.id ?? resource.type ?? '*'}`);
56
+ }
57
+ }
@@ -0,0 +1,22 @@
1
+ import type { ContradictionPolicy, KnowledgeEntity, KnowledgeFact, KnowledgeQuery, KnowledgeRelation, KnowledgeResult, KnowledgeScope, KnowledgeStore, TraversalOptions, TraversalResult } from '../types';
2
+ /**
3
+ * Tenant-partitioned in-memory knowledge store for development and tests.
4
+ */
5
+ export declare class InMemoryKnowledgeStore implements KnowledgeStore {
6
+ private readonly contradictionPolicy;
7
+ private readonly partitions;
8
+ constructor(contradictionPolicy?: ContradictionPolicy);
9
+ private partition;
10
+ upsertEntity(scope: KnowledgeScope, entity: KnowledgeEntity): Promise<void>;
11
+ upsertRelation(scope: KnowledgeScope, relation: KnowledgeRelation): Promise<void>;
12
+ upsertFact(scope: KnowledgeScope, fact: KnowledgeFact): Promise<{
13
+ contradictions: KnowledgeFact[];
14
+ }>;
15
+ getEntity(scope: KnowledgeScope, id: string): Promise<KnowledgeEntity | null>;
16
+ query(scope: KnowledgeScope, query: KnowledgeQuery): Promise<KnowledgeResult>;
17
+ traverse(scope: KnowledgeScope, seedIds: string[], options?: TraversalOptions): Promise<TraversalResult>;
18
+ listEntities(scope: KnowledgeScope): Promise<KnowledgeEntity[]>;
19
+ clear(scope: KnowledgeScope): Promise<void>;
20
+ private groupContradictions;
21
+ }
22
+ //# sourceMappingURL=in-memory-knowledge.store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"in-memory-knowledge.store.d.ts","sourceRoot":"","sources":["../../src/stores/in-memory-knowledge.store.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,mBAAmB,EACnB,eAAe,EACf,aAAa,EACb,cAAc,EACd,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,cAAc,EACd,gBAAgB,EAChB,eAAe,EAChB,MAAM,UAAU,CAAC;AAmBlB;;GAEG;AACH,qBAAa,sBAAuB,YAAW,cAAc;IAG/C,OAAO,CAAC,QAAQ,CAAC,mBAAmB;IAFhD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAgC;gBAE9B,mBAAmB,GAAE,mBAA4B;IAE9E,OAAO,CAAC,SAAS;IAeX,YAAY,CAAC,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAK3E,cAAc,CAAC,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAWjF,UAAU,CACd,KAAK,EAAE,cAAc,EACrB,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC;QAAE,cAAc,EAAE,aAAa,EAAE,CAAA;KAAE,CAAC;IA4BzC,SAAS,CAAC,KAAK,EAAE,cAAc,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAI7E,KAAK,CAAC,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC;IAoC7E,QAAQ,CACZ,KAAK,EAAE,cAAc,EACrB,OAAO,EAAE,MAAM,EAAE,EACjB,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,eAAe,CAAC;IAqDrB,YAAY,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAI/D,KAAK,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAIjD,OAAO,CAAC,mBAAmB;CAqB5B"}
@@ -0,0 +1,186 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.InMemoryKnowledgeStore = void 0;
4
+ const crypto_1 = require("crypto");
5
+ const kag_error_1 = require("../errors/kag.error");
6
+ const temporal_1 = require("../temporal");
7
+ const confidence_1 = require("../confidence");
8
+ function scopeKey(scope) {
9
+ if (!scope.tenantId) {
10
+ throw kag_error_1.KagError.tenantRequired();
11
+ }
12
+ return `${scope.tenantId}::${scope.graph ?? 'default'}`;
13
+ }
14
+ /**
15
+ * Tenant-partitioned in-memory knowledge store for development and tests.
16
+ */
17
+ class InMemoryKnowledgeStore {
18
+ constructor(contradictionPolicy = 'rank') {
19
+ this.contradictionPolicy = contradictionPolicy;
20
+ this.partitions = new Map();
21
+ }
22
+ partition(scope) {
23
+ const key = scopeKey(scope);
24
+ let part = this.partitions.get(key);
25
+ if (!part) {
26
+ part = {
27
+ entities: new Map(),
28
+ relations: new Map(),
29
+ facts: [],
30
+ adjacency: new Map(),
31
+ };
32
+ this.partitions.set(key, part);
33
+ }
34
+ return part;
35
+ }
36
+ async upsertEntity(scope, entity) {
37
+ const part = this.partition(scope);
38
+ part.entities.set(entity.id, { ...entity, properties: { ...entity.properties } });
39
+ }
40
+ async upsertRelation(scope, relation) {
41
+ const part = this.partition(scope);
42
+ const id = relation.id || `${relation.from}:${relation.type}:${relation.to}`;
43
+ const stored = { ...relation, id };
44
+ part.relations.set(id, stored);
45
+ if (!part.adjacency.has(relation.from))
46
+ part.adjacency.set(relation.from, new Set());
47
+ if (!part.adjacency.has(relation.to))
48
+ part.adjacency.set(relation.to, new Set());
49
+ part.adjacency.get(relation.from).add(relation.to);
50
+ part.adjacency.get(relation.to).add(relation.from);
51
+ }
52
+ async upsertFact(scope, fact) {
53
+ const part = this.partition(scope);
54
+ const stored = {
55
+ ...fact,
56
+ id: fact.id ?? (0, crypto_1.randomUUID)(),
57
+ createdAt: fact.createdAt ?? new Date().toISOString(),
58
+ confidence: fact.confidence ?? 1,
59
+ };
60
+ const contradictions = part.facts.filter((existing) => existing.subject === stored.subject &&
61
+ existing.predicate === stored.predicate &&
62
+ (0, temporal_1.factsOverlap)(existing, stored) &&
63
+ !(0, temporal_1.objectsEqual)(existing.object, stored.object));
64
+ if (contradictions.length > 0 && this.contradictionPolicy === 'reject') {
65
+ throw kag_error_1.KagError.conflict('Contradicting fact rejected by policy', {
66
+ fact: stored,
67
+ contradictions,
68
+ });
69
+ }
70
+ part.facts.push(stored);
71
+ return { contradictions };
72
+ }
73
+ async getEntity(scope, id) {
74
+ return this.partition(scope).entities.get(id) ?? null;
75
+ }
76
+ async query(scope, query) {
77
+ const part = this.partition(scope);
78
+ let entities = [...part.entities.values()];
79
+ if (query.entityIds?.length) {
80
+ const set = new Set(query.entityIds);
81
+ entities = entities.filter((e) => set.has(e.id));
82
+ }
83
+ if (query.types?.length) {
84
+ const set = new Set(query.types);
85
+ entities = entities.filter((e) => set.has(e.type));
86
+ }
87
+ let facts = part.facts.filter((f) => {
88
+ if (query.entityIds?.length && !query.entityIds.includes(f.subject))
89
+ return false;
90
+ if (query.predicate && f.predicate !== query.predicate)
91
+ return false;
92
+ if (query.currentOnly || query.asOf) {
93
+ return (0, temporal_1.isFactValidAt)(f, query.asOf);
94
+ }
95
+ return true;
96
+ });
97
+ if (query.limit) {
98
+ facts = facts.slice(0, query.limit);
99
+ entities = entities.slice(0, query.limit);
100
+ }
101
+ const relations = [...part.relations.values()].filter((r) => {
102
+ if (!query.entityIds?.length)
103
+ return true;
104
+ return query.entityIds.includes(r.from) || query.entityIds.includes(r.to);
105
+ });
106
+ const contradictions = this.groupContradictions(facts);
107
+ return { entities, relations, facts, contradictions };
108
+ }
109
+ async traverse(scope, seedIds, options = {}) {
110
+ const part = this.partition(scope);
111
+ const depth = options.depth ?? 2;
112
+ const visited = new Set();
113
+ const queue = seedIds.map((id) => ({ id, d: 0 }));
114
+ const entityIds = new Set();
115
+ const relationIds = new Set();
116
+ while (queue.length > 0) {
117
+ const { id, d } = queue.shift();
118
+ if (visited.has(id))
119
+ continue;
120
+ visited.add(id);
121
+ entityIds.add(id);
122
+ if (d >= depth)
123
+ continue;
124
+ for (const rel of part.relations.values()) {
125
+ if (options.relationTypes && !options.relationTypes.includes(rel.type)) {
126
+ continue;
127
+ }
128
+ let neighbor = null;
129
+ if (rel.from === id)
130
+ neighbor = rel.to;
131
+ else if (rel.to === id)
132
+ neighbor = rel.from;
133
+ if (!neighbor)
134
+ continue;
135
+ relationIds.add(rel.id);
136
+ if (!visited.has(neighbor)) {
137
+ queue.push({ id: neighbor, d: d + 1 });
138
+ }
139
+ }
140
+ }
141
+ const entities = [...entityIds]
142
+ .map((id) => part.entities.get(id))
143
+ .filter((e) => Boolean(e));
144
+ const relations = [...relationIds]
145
+ .map((id) => part.relations.get(id))
146
+ .filter((r) => Boolean(r));
147
+ const facts = part.facts.filter((f) => entityIds.has(f.subject) && (0, temporal_1.isFactValidAt)(f, options.asOf));
148
+ const confidences = [
149
+ ...relations.map((r) => r.confidence ?? 1),
150
+ ...facts.map((f) => f.confidence ?? 1),
151
+ ];
152
+ return {
153
+ entities,
154
+ relations,
155
+ facts,
156
+ confidence: (0, confidence_1.aggregateConfidence)(confidences, 'min'),
157
+ };
158
+ }
159
+ async listEntities(scope) {
160
+ return [...this.partition(scope).entities.values()];
161
+ }
162
+ async clear(scope) {
163
+ this.partitions.delete(scopeKey(scope));
164
+ }
165
+ groupContradictions(facts) {
166
+ const groups = [];
167
+ const used = new Set();
168
+ for (const fact of facts) {
169
+ if (!fact.id || used.has(fact.id))
170
+ continue;
171
+ const sameSlot = facts.filter((other) => other.subject === fact.subject &&
172
+ other.predicate === fact.predicate &&
173
+ (0, temporal_1.factsOverlap)(fact, other));
174
+ const distinctObjects = new Set(sameSlot.map((f) => JSON.stringify(f.object)));
175
+ if (sameSlot.length > 1 && distinctObjects.size > 1) {
176
+ for (const g of sameSlot) {
177
+ if (g.id)
178
+ used.add(g.id);
179
+ }
180
+ groups.push(sameSlot);
181
+ }
182
+ }
183
+ return groups;
184
+ }
185
+ }
186
+ exports.InMemoryKnowledgeStore = InMemoryKnowledgeStore;
@@ -0,0 +1,7 @@
1
+ import type { KnowledgeFact } from './types';
2
+ export declare function toDate(value: Date | string | undefined): Date | undefined;
3
+ export declare function isFactValidAt(fact: KnowledgeFact, asOf?: Date | string): boolean;
4
+ export declare function factsOverlap(a: KnowledgeFact, b: KnowledgeFact): boolean;
5
+ export declare function objectsEqual(a: unknown, b: unknown): boolean;
6
+ export declare function changesBetween(facts: KnowledgeFact[], from: Date | string, to: Date | string): KnowledgeFact[];
7
+ //# sourceMappingURL=temporal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"temporal.d.ts","sourceRoot":"","sources":["../src/temporal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C,wBAAgB,MAAM,CAAC,KAAK,EAAE,IAAI,GAAG,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,SAAS,CAKzE;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,aAAa,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,OAAO,CAgBhF;AAED,wBAAgB,YAAY,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,EAAE,aAAa,GAAG,OAAO,CAMxE;AAED,wBAAgB,YAAY,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,GAAG,OAAO,CAE5D;AAED,wBAAgB,cAAc,CAC5B,KAAK,EAAE,aAAa,EAAE,EACtB,IAAI,EAAE,IAAI,GAAG,MAAM,EACnB,EAAE,EAAE,IAAI,GAAG,MAAM,GAChB,aAAa,EAAE,CAYjB"}
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toDate = toDate;
4
+ exports.isFactValidAt = isFactValidAt;
5
+ exports.factsOverlap = factsOverlap;
6
+ exports.objectsEqual = objectsEqual;
7
+ exports.changesBetween = changesBetween;
8
+ function toDate(value) {
9
+ if (value === undefined)
10
+ return undefined;
11
+ if (value instanceof Date)
12
+ return value;
13
+ const d = new Date(value);
14
+ return Number.isNaN(d.getTime()) ? undefined : d;
15
+ }
16
+ function isFactValidAt(fact, asOf) {
17
+ if (asOf === undefined) {
18
+ // current: valid if no validUntil or validUntil in the future
19
+ const until = toDate(fact.validUntil);
20
+ if (until && until.getTime() < Date.now())
21
+ return false;
22
+ const from = toDate(fact.validFrom);
23
+ if (from && from.getTime() > Date.now())
24
+ return false;
25
+ return true;
26
+ }
27
+ const at = toDate(asOf);
28
+ if (!at)
29
+ return true;
30
+ const from = toDate(fact.validFrom);
31
+ const until = toDate(fact.validUntil);
32
+ if (from && at.getTime() < from.getTime())
33
+ return false;
34
+ if (until && at.getTime() > until.getTime())
35
+ return false;
36
+ return true;
37
+ }
38
+ function factsOverlap(a, b) {
39
+ const aFrom = toDate(a.validFrom)?.getTime() ?? Number.NEGATIVE_INFINITY;
40
+ const aUntil = toDate(a.validUntil)?.getTime() ?? Number.POSITIVE_INFINITY;
41
+ const bFrom = toDate(b.validFrom)?.getTime() ?? Number.NEGATIVE_INFINITY;
42
+ const bUntil = toDate(b.validUntil)?.getTime() ?? Number.POSITIVE_INFINITY;
43
+ return aFrom <= bUntil && bFrom <= aUntil;
44
+ }
45
+ function objectsEqual(a, b) {
46
+ return JSON.stringify(a) === JSON.stringify(b);
47
+ }
48
+ function changesBetween(facts, from, to) {
49
+ const start = toDate(from)?.getTime() ?? 0;
50
+ const end = toDate(to)?.getTime() ?? Date.now();
51
+ return facts.filter((f) => {
52
+ const vf = toDate(f.validFrom)?.getTime();
53
+ const vu = toDate(f.validUntil)?.getTime();
54
+ const created = toDate(f.createdAt)?.getTime();
55
+ if (vf !== undefined && vf >= start && vf <= end)
56
+ return true;
57
+ if (vu !== undefined && vu >= start && vu <= end)
58
+ return true;
59
+ if (created !== undefined && created >= start && created <= end)
60
+ return true;
61
+ return false;
62
+ });
63
+ }