dsh-memory-gate 0.2.0

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 (55) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/LICENSE +21 -0
  3. package/README.md +126 -0
  4. package/cordis.patch.yml +14 -0
  5. package/docs/architecture.md +142 -0
  6. package/docs/benchmark.md +41 -0
  7. package/lib/authority.d.ts +9 -0
  8. package/lib/authority.d.ts.map +1 -0
  9. package/lib/authority.js +71 -0
  10. package/lib/authority.js.map +1 -0
  11. package/lib/commands.d.ts +7 -0
  12. package/lib/commands.d.ts.map +1 -0
  13. package/lib/commands.js +225 -0
  14. package/lib/commands.js.map +1 -0
  15. package/lib/config.d.ts +19 -0
  16. package/lib/config.d.ts.map +1 -0
  17. package/lib/config.js +33 -0
  18. package/lib/config.js.map +1 -0
  19. package/lib/contracts.d.ts +94 -0
  20. package/lib/contracts.d.ts.map +1 -0
  21. package/lib/contracts.js +2 -0
  22. package/lib/contracts.js.map +1 -0
  23. package/lib/extractor.d.ts +8 -0
  24. package/lib/extractor.d.ts.map +1 -0
  25. package/lib/extractor.js +44 -0
  26. package/lib/extractor.js.map +1 -0
  27. package/lib/harness.d.ts +19 -0
  28. package/lib/harness.d.ts.map +1 -0
  29. package/lib/harness.js +85 -0
  30. package/lib/harness.js.map +1 -0
  31. package/lib/index.d.ts +15 -0
  32. package/lib/index.d.ts.map +1 -0
  33. package/lib/index.js +44 -0
  34. package/lib/index.js.map +1 -0
  35. package/lib/message-text.d.ts +3 -0
  36. package/lib/message-text.d.ts.map +1 -0
  37. package/lib/message-text.js +21 -0
  38. package/lib/message-text.js.map +1 -0
  39. package/lib/redaction.d.ts +7 -0
  40. package/lib/redaction.d.ts.map +1 -0
  41. package/lib/redaction.js +23 -0
  42. package/lib/redaction.js.map +1 -0
  43. package/lib/repository.d.ts +39 -0
  44. package/lib/repository.d.ts.map +1 -0
  45. package/lib/repository.js +492 -0
  46. package/lib/repository.js.map +1 -0
  47. package/lib/scope.d.ts +4 -0
  48. package/lib/scope.d.ts.map +1 -0
  49. package/lib/scope.js +26 -0
  50. package/lib/scope.js.map +1 -0
  51. package/lib/service.d.ts +42 -0
  52. package/lib/service.d.ts.map +1 -0
  53. package/lib/service.js +157 -0
  54. package/lib/service.js.map +1 -0
  55. package/package.json +74 -0
package/lib/index.js ADDED
@@ -0,0 +1,44 @@
1
+ import { registerUnavailableMemoryCommand } from './commands.js';
2
+ import { Config, validateConfig } from './config.js';
3
+ import { attachHarness } from './harness.js';
4
+ import { MemoryRepository } from './repository.js';
5
+ import { MemoryService } from './service.js';
6
+ export const name = 'dsh-memory-gate';
7
+ export const inject = ['commands'];
8
+ export { Config };
9
+ export function apply(ctx, config) {
10
+ const resolved = validateConfig(config);
11
+ let repository;
12
+ try {
13
+ repository = new MemoryRepository(resolved.databasePath);
14
+ }
15
+ catch (cause) {
16
+ ctx.logger('memory-gate').error('memory store unavailable; continuing without memory: %s', cause instanceof Error ? cause.message : String(cause));
17
+ ctx.effect(() => registerUnavailableMemoryCommand(ctx.commands), 'memory-gate: unavailable command');
18
+ return;
19
+ }
20
+ try {
21
+ repository.pruneAudit(resolved.auditRetentionRuns);
22
+ }
23
+ catch (cause) {
24
+ ctx.logger('memory-gate').warn('memory audit pruning omitted: %s', cause instanceof Error ? cause.message : String(cause));
25
+ }
26
+ const service = new MemoryService(repository, resolved);
27
+ ctx.effect(() => () => {
28
+ try {
29
+ repository.close();
30
+ }
31
+ catch (cause) {
32
+ ctx.logger('memory-gate').warn('memory database close failed: %s', cause instanceof Error ? cause.message : String(cause));
33
+ }
34
+ }, 'memory-gate: database');
35
+ attachHarness(ctx, service);
36
+ }
37
+ export { decideAuthority } from './authority.js';
38
+ export { extractDurableClaims } from './extractor.js';
39
+ export { injectRecall, observeSessionEvent } from './harness.js';
40
+ export { inspectForSecrets, redactForLog } from './redaction.js';
41
+ export { MemoryRepository } from './repository.js';
42
+ export { sessionScopeKey, workspaceScopeKey } from './scope.js';
43
+ export { MemoryService } from './service.js';
44
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gCAAgC,EAAE,MAAM,eAAe,CAAA;AAChE,OAAO,EAAE,MAAM,EAA+B,cAAc,EAAE,MAAM,aAAa,CAAA;AACjF,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAE5C,MAAM,CAAC,MAAM,IAAI,GAAG,iBAAiB,CAAA;AACrC,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,UAAU,CAAC,CAAA;AAClC,OAAO,EAAE,MAAM,EAAE,CAAA;AAEjB,MAAM,UAAU,KAAK,CAAC,GAAY,EAAE,MAAoB;IACtD,MAAM,QAAQ,GAAG,cAAc,CAAC,MAAM,CAAC,CAAA;IACvC,IAAI,UAA4B,CAAA;IAChC,IAAI,CAAC;QACH,UAAU,GAAG,IAAI,gBAAgB,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAA;IAC1D,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,KAAK,CAC7B,yDAAyD,EACzD,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CACvD,CAAA;QACD,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,gCAAgC,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,kCAAkC,CAAC,CAAA;QACpG,OAAM;IACR,CAAC;IACD,IAAI,CAAC;QACH,UAAU,CAAC,UAAU,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAA;IACpD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,IAAI,CAC5B,kCAAkC,EAClC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CACvD,CAAA;IACH,CAAC;IACD,MAAM,OAAO,GAAG,IAAI,aAAa,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAA;IACvD,GAAG,CAAC,MAAM,CACR,GAAG,EAAE,CAAC,GAAG,EAAE;QACT,IAAI,CAAC;YACH,UAAU,CAAC,KAAK,EAAE,CAAA;QACpB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,kCAAkC,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;QAC5H,CAAC;IACH,CAAC,EACD,uBAAuB,CACxB,CAAA;IACD,aAAa,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;AAC7B,CAAC;AAGD,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAChD,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AACrD,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AAChE,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAChE,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAClD,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA"}
@@ -0,0 +1,3 @@
1
+ export declare function messageText(message: unknown): string;
2
+ export declare function isHumanUserMessage(message: unknown): boolean;
3
+ //# sourceMappingURL=message-text.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-text.d.ts","sourceRoot":"","sources":["../src/message-text.ts"],"names":[],"mappings":"AAUA,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAUpD;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAI5D"}
@@ -0,0 +1,21 @@
1
+ export function messageText(message) {
2
+ if (!message || typeof message !== 'object')
3
+ return '';
4
+ const content = message.content;
5
+ if (typeof content === 'string')
6
+ return content.trim();
7
+ if (!Array.isArray(content))
8
+ return '';
9
+ return content
10
+ .map((part) => (part?.type === 'text' && typeof part.text === 'string' ? part.text : ''))
11
+ .filter(Boolean)
12
+ .join('\n')
13
+ .trim();
14
+ }
15
+ export function isHumanUserMessage(message) {
16
+ if (!message || typeof message !== 'object')
17
+ return false;
18
+ const source = message.source;
19
+ return source?.kind === 'user';
20
+ }
21
+ //# sourceMappingURL=message-text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-text.js","sourceRoot":"","sources":["../src/message-text.ts"],"names":[],"mappings":"AAUA,MAAM,UAAU,WAAW,CAAC,OAAgB;IAC1C,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,EAAE,CAAA;IACtD,MAAM,OAAO,GAAI,OAAuB,CAAC,OAAO,CAAA;IAChD,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,OAAO,CAAC,IAAI,EAAE,CAAA;IACtD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;QAAE,OAAO,EAAE,CAAA;IACtC,OAAO,OAAO;SACX,GAAG,CAAC,CAAC,IAAc,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,KAAK,MAAM,IAAI,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;SAClG,MAAM,CAAC,OAAO,CAAC;SACf,IAAI,CAAC,IAAI,CAAC;SACV,IAAI,EAAE,CAAA;AACX,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,OAAgB;IACjD,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAA;IACzD,MAAM,MAAM,GAAI,OAAuB,CAAC,MAAM,CAAA;IAC9C,OAAO,MAAM,EAAE,IAAI,KAAK,MAAM,CAAA;AAChC,CAAC"}
@@ -0,0 +1,7 @@
1
+ export interface SecretInspection {
2
+ secret: boolean;
3
+ labels: string[];
4
+ }
5
+ export declare function inspectForSecrets(text: string): SecretInspection;
6
+ export declare function redactForLog(text: string, maxLength?: number): string;
7
+ //# sourceMappingURL=redaction.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redaction.d.ts","sourceRoot":"","sources":["../src/redaction.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,OAAO,CAAA;IACf,MAAM,EAAE,MAAM,EAAE,CAAA;CACjB;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,CAGhE;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,SAAM,GAAG,MAAM,CAMlE"}
@@ -0,0 +1,23 @@
1
+ const SECRET_PATTERNS = [
2
+ { label: 'OpenAI-style API key', pattern: /\bsk-[A-Za-z0-9_-]{16,}\b/ },
3
+ { label: 'GitHub token', pattern: /\bgh[opsu]_[A-Za-z0-9]{20,}\b/ },
4
+ { label: 'AWS access key', pattern: /\bAKIA[0-9A-Z]{16}\b/ },
5
+ { label: 'Bearer token', pattern: /\bBearer\s+[A-Za-z0-9._~+/=-]{16,}/i },
6
+ {
7
+ label: 'credential assignment',
8
+ pattern: /\b(?:api[_-]?key|access[_-]?token|secret|password|passwd)\s*[:=]\s*["']?[^\s"']{8,}/i,
9
+ },
10
+ { label: 'private key', pattern: /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/ },
11
+ ];
12
+ export function inspectForSecrets(text) {
13
+ const labels = SECRET_PATTERNS.filter(({ pattern }) => pattern.test(text)).map(({ label }) => label);
14
+ return { secret: labels.length > 0, labels };
15
+ }
16
+ export function redactForLog(text, maxLength = 160) {
17
+ let value = text;
18
+ for (const { pattern } of SECRET_PATTERNS) {
19
+ value = value.replace(new RegExp(pattern.source, pattern.flags.includes('g') ? pattern.flags : `${pattern.flags}g`), '[REDACTED]');
20
+ }
21
+ return value.length > maxLength ? `${value.slice(0, maxLength - 1)}…` : value;
22
+ }
23
+ //# sourceMappingURL=redaction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redaction.js","sourceRoot":"","sources":["../src/redaction.ts"],"names":[],"mappings":"AAAA,MAAM,eAAe,GAA8C;IACjE,EAAE,KAAK,EAAE,sBAAsB,EAAE,OAAO,EAAE,2BAA2B,EAAE;IACvE,EAAE,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,+BAA+B,EAAE;IACnE,EAAE,KAAK,EAAE,gBAAgB,EAAE,OAAO,EAAE,sBAAsB,EAAE;IAC5D,EAAE,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,qCAAqC,EAAE;IACzE;QACE,KAAK,EAAE,uBAAuB;QAC9B,OAAO,EAAE,sFAAsF;KAChG;IACD,EAAE,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,mDAAmD,EAAE;CACvF,CAAA;AAOD,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,MAAM,MAAM,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,CAAA;IACpG,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,MAAM,EAAE,CAAA;AAC9C,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,SAAS,GAAG,GAAG;IACxD,IAAI,KAAK,GAAG,IAAI,CAAA;IAChB,KAAK,MAAM,EAAE,OAAO,EAAE,IAAI,eAAe,EAAE,CAAC;QAC1C,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,KAAK,GAAG,CAAC,EAAE,YAAY,CAAC,CAAA;IACpI,CAAC;IACD,OAAO,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAA;AAC/E,CAAC"}
@@ -0,0 +1,39 @@
1
+ import type { AuthorityDecision, Belief, Claim, ConsumptionOutcome, Evidence, MemoryStats, NewClaim, SearchCandidate } from './contracts.js';
2
+ export declare class MemoryRepository {
3
+ readonly databasePath: string;
4
+ private readonly db;
5
+ private ftsAvailable;
6
+ constructor(databasePath: string);
7
+ close(): void;
8
+ health(): {
9
+ ok: boolean;
10
+ schemaVersion: number;
11
+ ftsAvailable: boolean;
12
+ };
13
+ remember(input: NewClaim): {
14
+ claim: Claim;
15
+ created: boolean;
16
+ };
17
+ tombstone(claimId: string, scopeKeys: string[]): boolean;
18
+ getClaim(claimId: string, scopeKeys?: string[]): Claim | undefined;
19
+ getBelief(claimId: string): Belief | undefined;
20
+ getEvidence(claimId: string): Evidence[];
21
+ search(query: string, scopeKeys: string[], limit: number): SearchCandidate[];
22
+ capsule(scopeKeys: string[], limit: number): SearchCandidate[];
23
+ listActive(scopeKeys: string[], limit: number): SearchCandidate[];
24
+ recordRetrieval(query: string, sessionId: string, workspaceKey: string, candidateCount: number): string;
25
+ recordDecisions(runId: string, decisions: AuthorityDecision[]): void;
26
+ recordInjection(runId: string, sessionId: string, mode: string, claimIds: string[], messageId: string): string;
27
+ recordConsumption(claimId: string, outcome: ConsumptionOutcome, sessionId: string, detail?: string): Belief;
28
+ pruneAudit(maxRuns: number): number;
29
+ stats(): MemoryStats;
30
+ private count;
31
+ private countRows;
32
+ private searchFtsIds;
33
+ private indexClaim;
34
+ private migrate;
35
+ private transaction;
36
+ }
37
+ export declare function normalizeContent(value: string): string;
38
+ export declare function hashContent(value: string): string;
39
+ //# sourceMappingURL=repository.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repository.d.ts","sourceRoot":"","sources":["../src/repository.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,iBAAiB,EACjB,MAAM,EACN,KAAK,EACL,kBAAkB,EAClB,QAAQ,EACR,WAAW,EACX,QAAQ,EACR,eAAe,EAChB,MAAM,gBAAgB,CAAA;AAOvB,qBAAa,gBAAgB;IAC3B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAc;IACjC,OAAO,CAAC,YAAY,CAAQ;gBAEhB,YAAY,EAAE,MAAM;IAShC,KAAK,IAAI,IAAI;IAIb,MAAM,IAAI;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,OAAO,CAAA;KAAE;IASvE,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG;QAAE,KAAK,EAAE,KAAK,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE;IA0E7D,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO;IAcxD,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,KAAK,GAAG,SAAS;IAalE,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAK9C,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,EAAE;IAOxC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,eAAe,EAAE;IA4B5E,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,eAAe,EAAE;IAwB9D,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,eAAe,EAAE;IAIjE,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,MAAM;IAUvG,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,iBAAiB,EAAE,GAAG,IAAI;IAyBpE,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM;IAU9G,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM;IAgC3G,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAoBnC,KAAK,IAAI,WAAW;IAYpB,OAAO,CAAC,KAAK;IAIb,OAAO,CAAC,SAAS;IAKjB,OAAO,CAAC,YAAY;IAmBpB,OAAO,CAAC,UAAU;IAKlB,OAAO,CAAC,OAAO;IAkGf,OAAO,CAAC,WAAW;CAWpB;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEjD"}
@@ -0,0 +1,492 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { mkdirSync } from 'node:fs';
3
+ import { dirname, resolve } from 'node:path';
4
+ import { DatabaseSync } from 'node:sqlite';
5
+ const SCHEMA_VERSION = 1;
6
+ export class MemoryRepository {
7
+ databasePath;
8
+ db;
9
+ ftsAvailable = false;
10
+ constructor(databasePath) {
11
+ this.databasePath = databasePath === ':memory:' ? databasePath : resolve(databasePath);
12
+ if (this.databasePath !== ':memory:')
13
+ mkdirSync(dirname(this.databasePath), { recursive: true, mode: 0o700 });
14
+ this.db = new DatabaseSync(this.databasePath);
15
+ this.db.exec('PRAGMA foreign_keys = ON; PRAGMA busy_timeout = 3000;');
16
+ if (this.databasePath !== ':memory:')
17
+ this.db.exec('PRAGMA journal_mode = WAL;');
18
+ this.migrate();
19
+ }
20
+ close() {
21
+ this.db.close();
22
+ }
23
+ health() {
24
+ const row = this.db.prepare('PRAGMA user_version').get();
25
+ return {
26
+ ok: Number(row.user_version) === SCHEMA_VERSION,
27
+ schemaVersion: Number(row.user_version),
28
+ ftsAvailable: this.ftsAvailable,
29
+ };
30
+ }
31
+ remember(input) {
32
+ const content = normalizeContent(input.content);
33
+ if (!content)
34
+ throw new Error('Memory content cannot be empty');
35
+ const contentHash = hashContent(content);
36
+ const existing = this.db
37
+ .prepare(`SELECT * FROM claims
38
+ WHERE scope_key = ? AND content_hash = ? AND state = 'active'
39
+ ORDER BY updated_at DESC LIMIT 1`)
40
+ .get(input.scopeKey, contentHash);
41
+ if (existing)
42
+ return { claim: rowToClaim(existing), created: false };
43
+ const now = Date.now();
44
+ const claim = {
45
+ id: `mem_${randomUUID()}`,
46
+ scope: input.scope,
47
+ scopeKey: input.scopeKey,
48
+ kind: input.kind,
49
+ content,
50
+ tags: uniqueTags(input.tags ?? []),
51
+ state: 'active',
52
+ origin: input.origin,
53
+ sensitivity: input.sensitivity ?? 'normal',
54
+ contentHash,
55
+ ...(input.sourceSessionId === undefined ? {} : { sourceSessionId: input.sourceSessionId }),
56
+ ...(input.sourceEventSeq === undefined ? {} : { sourceEventSeq: input.sourceEventSeq }),
57
+ validFrom: input.validFrom ?? now,
58
+ ...(input.validUntil === undefined ? {} : { validUntil: input.validUntil }),
59
+ createdAt: now,
60
+ updatedAt: now,
61
+ };
62
+ this.transaction(() => {
63
+ this.db
64
+ .prepare(`INSERT INTO claims (
65
+ id, scope, scope_key, kind, content, tags_json, state, origin,
66
+ sensitivity, content_hash, source_session_id, source_event_seq,
67
+ valid_from, valid_until, created_at, updated_at
68
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
69
+ .run(claim.id, claim.scope, claim.scopeKey, claim.kind, claim.content, JSON.stringify(claim.tags), claim.state, claim.origin, claim.sensitivity, claim.contentHash, claim.sourceSessionId ?? null, claim.sourceEventSeq ?? null, claim.validFrom, claim.validUntil ?? null, claim.createdAt, claim.updatedAt);
70
+ const alpha = claim.origin === 'explicit' ? 6 : 4;
71
+ const beta = claim.origin === 'explicit' ? 1 : 2;
72
+ this.db
73
+ .prepare('INSERT INTO beliefs (claim_id, alpha, beta, harmful_count, updated_at) VALUES (?, ?, ?, 0, ?)')
74
+ .run(claim.id, alpha, beta, now);
75
+ this.db
76
+ .prepare('INSERT INTO evidence (id, claim_id, kind, weight, detail, session_id, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)')
77
+ .run(`ev_${randomUUID()}`, claim.id, 'asserted', 1, claim.origin, claim.sourceSessionId ?? null, now);
78
+ this.indexClaim(claim);
79
+ });
80
+ return { claim, created: true };
81
+ }
82
+ tombstone(claimId, scopeKeys) {
83
+ const placeholders = scopeKeys.map(() => '?').join(', ');
84
+ if (!placeholders)
85
+ return false;
86
+ let changed = false;
87
+ this.transaction(() => {
88
+ const result = this.db
89
+ .prepare(`UPDATE claims SET state = 'tombstoned', updated_at = ? WHERE id = ? AND scope_key IN (${placeholders}) AND state = 'active'`)
90
+ .run(Date.now(), claimId, ...scopeKeys);
91
+ changed = Number(result.changes) > 0;
92
+ if (changed && this.ftsAvailable)
93
+ this.db.prepare('DELETE FROM claim_fts WHERE claim_id = ?').run(claimId);
94
+ });
95
+ return changed;
96
+ }
97
+ getClaim(claimId, scopeKeys) {
98
+ let row;
99
+ if (scopeKeys?.length) {
100
+ const placeholders = scopeKeys.map(() => '?').join(', ');
101
+ row = this.db
102
+ .prepare(`SELECT * FROM claims WHERE id = ? AND scope_key IN (${placeholders}) LIMIT 1`)
103
+ .get(claimId, ...scopeKeys);
104
+ }
105
+ else {
106
+ row = this.db.prepare('SELECT * FROM claims WHERE id = ? LIMIT 1').get(claimId);
107
+ }
108
+ return row ? rowToClaim(row) : undefined;
109
+ }
110
+ getBelief(claimId) {
111
+ const row = this.db.prepare('SELECT * FROM beliefs WHERE claim_id = ?').get(claimId);
112
+ return row ? rowToBelief(row) : undefined;
113
+ }
114
+ getEvidence(claimId) {
115
+ const rows = this.db
116
+ .prepare('SELECT * FROM evidence WHERE claim_id = ? ORDER BY created_at DESC')
117
+ .all(claimId);
118
+ return rows.map(rowToEvidence);
119
+ }
120
+ search(query, scopeKeys, limit) {
121
+ if (!scopeKeys.length || limit < 1)
122
+ return [];
123
+ const placeholders = scopeKeys.map(() => '?').join(', ');
124
+ const scanLimit = Math.min(Math.max(limit * 20, 100), 500);
125
+ const rows = this.db
126
+ .prepare(`SELECT c.*, b.alpha, b.beta, b.harmful_count, b.updated_at AS belief_updated_at
127
+ FROM claims c JOIN beliefs b ON b.claim_id = c.id
128
+ WHERE c.state = 'active' AND c.scope_key IN (${placeholders})
129
+ AND c.valid_from <= ? AND (c.valid_until IS NULL OR c.valid_until > ?)
130
+ ORDER BY c.updated_at DESC LIMIT ?`)
131
+ .all(...scopeKeys, Date.now(), Date.now(), scanLimit);
132
+ const ftsIds = this.searchFtsIds(query, scopeKeys, scanLimit);
133
+ const queryTerms = terms(query);
134
+ return rows
135
+ .map((row) => {
136
+ const claim = rowToClaim(row);
137
+ const lexicalScore = lexicalSimilarity(queryTerms, claim.content, claim.tags, ftsIds.has(claim.id));
138
+ const belief = rowToBelief(row);
139
+ return { claim, belief, recallChannel: 'trigger', lexicalScore, freshnessScore: 0, rankScore: lexicalScore };
140
+ })
141
+ .filter((candidate) => queryTerms.length === 0 || candidate.lexicalScore > 0)
142
+ .sort((a, b) => b.rankScore - a.rankScore || b.claim.updatedAt - a.claim.updatedAt)
143
+ .slice(0, limit);
144
+ }
145
+ capsule(scopeKeys, limit) {
146
+ if (!scopeKeys.includes('global') || limit < 1)
147
+ return [];
148
+ const now = Date.now();
149
+ const rows = this.db
150
+ .prepare(`SELECT c.*, b.alpha, b.beta, b.harmful_count, b.updated_at AS belief_updated_at
151
+ FROM claims c JOIN beliefs b ON b.claim_id = c.id
152
+ WHERE c.state = 'active' AND c.scope = 'global' AND c.scope_key = 'global'
153
+ AND c.origin = 'explicit' AND c.kind IN ('preference', 'constraint')
154
+ AND c.valid_from <= ? AND (c.valid_until IS NULL OR c.valid_until > ?)
155
+ ORDER BY (b.alpha / (b.alpha + b.beta)) DESC, c.updated_at DESC
156
+ LIMIT ?`)
157
+ .all(now, now, limit);
158
+ return rows.map((row) => ({
159
+ claim: rowToClaim(row),
160
+ belief: rowToBelief(row),
161
+ recallChannel: 'capsule',
162
+ lexicalScore: 0.35,
163
+ freshnessScore: 0,
164
+ rankScore: 0.35,
165
+ }));
166
+ }
167
+ listActive(scopeKeys, limit) {
168
+ return this.search('', scopeKeys, limit);
169
+ }
170
+ recordRetrieval(query, sessionId, workspaceKey, candidateCount) {
171
+ const id = `run_${randomUUID()}`;
172
+ this.db
173
+ .prepare('INSERT INTO retrieval_runs (id, query_hash, session_id, workspace_key, candidate_count, created_at) VALUES (?, ?, ?, ?, ?, ?)')
174
+ .run(id, hashContent(query), sessionId, workspaceKey, candidateCount, Date.now());
175
+ return id;
176
+ }
177
+ recordDecisions(runId, decisions) {
178
+ this.transaction(() => {
179
+ const statement = this.db.prepare(`INSERT INTO authority_decisions
180
+ (id, run_id, claim_id, action, reason_codes_json, belief_score, relevance_score, freshness_score, risk_score, created_at)
181
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
182
+ const now = Date.now();
183
+ for (const decision of decisions) {
184
+ statement.run(`dec_${randomUUID()}`, runId, decision.claimId, decision.action, JSON.stringify(decision.reasonCodes), decision.beliefScore, decision.relevanceScore, decision.freshnessScore, decision.riskScore, now);
185
+ }
186
+ });
187
+ }
188
+ recordInjection(runId, sessionId, mode, claimIds, messageId) {
189
+ const id = `inj_${randomUUID()}`;
190
+ this.db
191
+ .prepare('INSERT INTO injections (id, run_id, session_id, mode, claim_ids_json, message_id, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)')
192
+ .run(id, runId, sessionId, mode, JSON.stringify(claimIds), messageId, Date.now());
193
+ return id;
194
+ }
195
+ recordConsumption(claimId, outcome, sessionId, detail) {
196
+ const claim = this.getClaim(claimId);
197
+ if (!claim || claim.state !== 'active')
198
+ throw new Error('Active memory claim not found');
199
+ const weights = {
200
+ helped: { alpha: 1, beta: 0, harmful: 0 },
201
+ harmful: { alpha: 0, beta: 5, harmful: 1 },
202
+ stale: { alpha: 0, beta: 2, harmful: 0 },
203
+ conflict: { alpha: 0, beta: 4, harmful: 0 },
204
+ unknown: { alpha: 0, beta: 0, harmful: 0 },
205
+ };
206
+ const delta = weights[outcome];
207
+ const now = Date.now();
208
+ this.transaction(() => {
209
+ this.db
210
+ .prepare('INSERT INTO consumption (id, claim_id, session_id, outcome, detail, created_at) VALUES (?, ?, ?, ?, ?, ?)')
211
+ .run(`con_${randomUUID()}`, claimId, sessionId, outcome, detail ?? null, now);
212
+ if (outcome !== 'unknown') {
213
+ this.db
214
+ .prepare('UPDATE beliefs SET alpha = alpha + ?, beta = beta + ?, harmful_count = harmful_count + ?, updated_at = ? WHERE claim_id = ?')
215
+ .run(delta.alpha, delta.beta, delta.harmful, now, claimId);
216
+ this.db
217
+ .prepare('INSERT INTO evidence (id, claim_id, kind, weight, detail, session_id, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)')
218
+ .run(`ev_${randomUUID()}`, claimId, outcome, delta.alpha || delta.beta, detail ?? null, sessionId, now);
219
+ }
220
+ });
221
+ const belief = this.getBelief(claimId);
222
+ if (!belief)
223
+ throw new Error('Memory belief update failed');
224
+ return belief;
225
+ }
226
+ pruneAudit(maxRuns) {
227
+ const excess = Math.max(0, this.countRows('retrieval_runs') - Math.max(1, Math.floor(maxRuns)));
228
+ if (!excess)
229
+ return 0;
230
+ this.transaction(() => {
231
+ this.db.exec('CREATE TEMP TABLE IF NOT EXISTS memory_prune_runs (id TEXT PRIMARY KEY)');
232
+ this.db.exec('DELETE FROM memory_prune_runs');
233
+ this.db
234
+ .prepare(`INSERT INTO memory_prune_runs (id)
235
+ SELECT id FROM retrieval_runs ORDER BY created_at ASC, id ASC LIMIT ?`)
236
+ .run(excess);
237
+ this.db.exec('DELETE FROM injections WHERE run_id IN (SELECT id FROM memory_prune_runs)');
238
+ this.db.exec('DELETE FROM authority_decisions WHERE run_id IN (SELECT id FROM memory_prune_runs)');
239
+ this.db.exec('DELETE FROM retrieval_runs WHERE id IN (SELECT id FROM memory_prune_runs)');
240
+ this.db.exec('DELETE FROM memory_prune_runs');
241
+ });
242
+ return excess;
243
+ }
244
+ stats() {
245
+ const claimRows = this.db.prepare('SELECT state, COUNT(*) AS count FROM claims GROUP BY state').all();
246
+ const byState = new Map(claimRows.map((row) => [String(row.state), Number(row.count)]));
247
+ return {
248
+ activeClaims: byState.get('active') ?? 0,
249
+ tombstonedClaims: byState.get('tombstoned') ?? 0,
250
+ decisions: this.count('authority_decisions'),
251
+ injections: this.count('injections'),
252
+ consumptions: this.count('consumption'),
253
+ };
254
+ }
255
+ count(table) {
256
+ return this.countRows(table);
257
+ }
258
+ countRows(table) {
259
+ const row = this.db.prepare(`SELECT COUNT(*) AS count FROM ${table}`).get();
260
+ return Number(row.count);
261
+ }
262
+ searchFtsIds(query, scopeKeys, limit) {
263
+ if (!this.ftsAvailable)
264
+ return new Set();
265
+ const ftsQuery = buildFtsQuery(query);
266
+ if (!ftsQuery)
267
+ return new Set();
268
+ try {
269
+ const placeholders = scopeKeys.map(() => '?').join(', ');
270
+ const rows = this.db
271
+ .prepare(`SELECT f.claim_id FROM claim_fts f JOIN claims c ON c.id = f.claim_id
272
+ WHERE claim_fts MATCH ? AND c.scope_key IN (${placeholders}) AND c.state = 'active'
273
+ ORDER BY bm25(claim_fts) LIMIT ?`)
274
+ .all(ftsQuery, ...scopeKeys, limit);
275
+ return new Set(rows.map((row) => String(row.claim_id)));
276
+ }
277
+ catch {
278
+ return new Set();
279
+ }
280
+ }
281
+ indexClaim(claim) {
282
+ if (!this.ftsAvailable)
283
+ return;
284
+ this.db.prepare('INSERT INTO claim_fts (claim_id, content, tags) VALUES (?, ?, ?)').run(claim.id, claim.content, claim.tags.join(' '));
285
+ }
286
+ migrate() {
287
+ const versionRow = this.db.prepare('PRAGMA user_version').get();
288
+ const version = Number(versionRow.user_version);
289
+ if (version > SCHEMA_VERSION)
290
+ throw new Error(`Unsupported memory schema version ${version}`);
291
+ if (version < 1) {
292
+ this.transaction(() => {
293
+ this.db.exec(`
294
+ CREATE TABLE claims (
295
+ id TEXT PRIMARY KEY,
296
+ scope TEXT NOT NULL CHECK (scope IN ('session', 'workspace', 'global')),
297
+ scope_key TEXT NOT NULL,
298
+ kind TEXT NOT NULL CHECK (kind IN ('preference', 'constraint', 'fact', 'procedure', 'warning')),
299
+ content TEXT NOT NULL,
300
+ tags_json TEXT NOT NULL DEFAULT '[]',
301
+ state TEXT NOT NULL CHECK (state IN ('active', 'superseded', 'tombstoned')),
302
+ origin TEXT NOT NULL CHECK (origin IN ('explicit', 'heuristic')),
303
+ sensitivity TEXT NOT NULL CHECK (sensitivity IN ('normal', 'private')),
304
+ content_hash TEXT NOT NULL,
305
+ source_session_id TEXT,
306
+ source_event_seq INTEGER,
307
+ valid_from INTEGER NOT NULL,
308
+ valid_until INTEGER,
309
+ created_at INTEGER NOT NULL,
310
+ updated_at INTEGER NOT NULL
311
+ );
312
+ CREATE INDEX claims_scope_state_updated ON claims(scope_key, state, updated_at DESC);
313
+ CREATE INDEX claims_dedupe ON claims(scope_key, content_hash, state);
314
+ CREATE TABLE beliefs (
315
+ claim_id TEXT PRIMARY KEY REFERENCES claims(id) ON DELETE CASCADE,
316
+ alpha REAL NOT NULL,
317
+ beta REAL NOT NULL,
318
+ harmful_count INTEGER NOT NULL DEFAULT 0,
319
+ updated_at INTEGER NOT NULL
320
+ );
321
+ CREATE TABLE evidence (
322
+ id TEXT PRIMARY KEY,
323
+ claim_id TEXT NOT NULL REFERENCES claims(id) ON DELETE CASCADE,
324
+ kind TEXT NOT NULL,
325
+ weight REAL NOT NULL,
326
+ detail TEXT,
327
+ session_id TEXT,
328
+ created_at INTEGER NOT NULL
329
+ );
330
+ CREATE INDEX evidence_claim_created ON evidence(claim_id, created_at DESC);
331
+ CREATE TABLE retrieval_runs (
332
+ id TEXT PRIMARY KEY,
333
+ query_hash TEXT NOT NULL,
334
+ session_id TEXT NOT NULL,
335
+ workspace_key TEXT NOT NULL,
336
+ candidate_count INTEGER NOT NULL,
337
+ created_at INTEGER NOT NULL
338
+ );
339
+ CREATE TABLE authority_decisions (
340
+ id TEXT PRIMARY KEY,
341
+ run_id TEXT NOT NULL REFERENCES retrieval_runs(id) ON DELETE CASCADE,
342
+ claim_id TEXT NOT NULL REFERENCES claims(id),
343
+ action TEXT NOT NULL CHECK (action IN ('use', 'verify', 'ignore')),
344
+ reason_codes_json TEXT NOT NULL,
345
+ belief_score REAL NOT NULL,
346
+ relevance_score REAL NOT NULL,
347
+ freshness_score REAL NOT NULL,
348
+ risk_score REAL NOT NULL,
349
+ created_at INTEGER NOT NULL
350
+ );
351
+ CREATE INDEX decisions_run ON authority_decisions(run_id);
352
+ CREATE TABLE injections (
353
+ id TEXT PRIMARY KEY,
354
+ run_id TEXT NOT NULL REFERENCES retrieval_runs(id),
355
+ session_id TEXT NOT NULL,
356
+ mode TEXT NOT NULL,
357
+ claim_ids_json TEXT NOT NULL,
358
+ message_id TEXT NOT NULL,
359
+ created_at INTEGER NOT NULL
360
+ );
361
+ CREATE TABLE consumption (
362
+ id TEXT PRIMARY KEY,
363
+ claim_id TEXT NOT NULL REFERENCES claims(id),
364
+ session_id TEXT NOT NULL,
365
+ outcome TEXT NOT NULL,
366
+ detail TEXT,
367
+ created_at INTEGER NOT NULL
368
+ );
369
+ PRAGMA user_version = 1;
370
+ `);
371
+ });
372
+ }
373
+ try {
374
+ this.db.exec("CREATE VIRTUAL TABLE IF NOT EXISTS claim_fts USING fts5(claim_id UNINDEXED, content, tags, tokenize='unicode61')");
375
+ this.ftsAvailable = true;
376
+ const row = this.db.prepare('SELECT COUNT(*) AS count FROM claim_fts').get();
377
+ if (Number(row.count) === 0) {
378
+ this.db.exec("INSERT INTO claim_fts (claim_id, content, tags) SELECT id, content, replace(replace(tags_json, '[', ''), ']', '') FROM claims WHERE state = 'active'");
379
+ }
380
+ }
381
+ catch {
382
+ this.ftsAvailable = false;
383
+ }
384
+ }
385
+ transaction(operation) {
386
+ this.db.exec('BEGIN IMMEDIATE');
387
+ try {
388
+ const result = operation();
389
+ this.db.exec('COMMIT');
390
+ return result;
391
+ }
392
+ catch (error) {
393
+ this.db.exec('ROLLBACK');
394
+ throw error;
395
+ }
396
+ }
397
+ }
398
+ export function normalizeContent(value) {
399
+ return value.replace(/\s+/g, ' ').trim();
400
+ }
401
+ export function hashContent(value) {
402
+ return createHash('sha256').update(normalizeContent(value).toLocaleLowerCase()).digest('hex');
403
+ }
404
+ function uniqueTags(tags) {
405
+ return [...new Set(tags.map((tag) => tag.trim().toLocaleLowerCase()).filter(Boolean))].slice(0, 20);
406
+ }
407
+ function terms(value) {
408
+ const normalized = value.toLocaleLowerCase().normalize('NFKC');
409
+ const latin = normalized.match(/[\p{Script=Latin}\p{N}_-]{2,}/gu) ?? [];
410
+ const hanRuns = normalized.match(/[\p{Script=Han}]+/gu) ?? [];
411
+ const han = hanRuns.flatMap((run) => {
412
+ if (run.length <= 2)
413
+ return [run];
414
+ return Array.from({ length: run.length - 1 }, (_, index) => run.slice(index, index + 2));
415
+ });
416
+ const raw = [...latin, ...han];
417
+ const aliases = raw.flatMap((term) => recallAliases(term));
418
+ return [...new Set([...raw, ...aliases])];
419
+ }
420
+ function lexicalSimilarity(queryTerms, content, tags, ftsHit) {
421
+ if (!queryTerms.length)
422
+ return 0.5;
423
+ const candidateTerms = new Set(terms(`${content} ${tags.join(' ')}`));
424
+ const matched = queryTerms.filter((term) => candidateTerms.has(term)).length;
425
+ const queryCoverage = matched / queryTerms.length;
426
+ const candidateCoverage = matched / Math.max(1, candidateTerms.size);
427
+ const overlap = Math.max(queryCoverage, candidateCoverage);
428
+ return Math.min(1, overlap * 0.9 + (ftsHit ? 0.1 : 0));
429
+ }
430
+ const RECALL_ALIAS_GROUPS = [
431
+ ['简洁', '简短', '精炼', '扼要', 'concise', 'brief'],
432
+ ['中文', '汉语', 'chinese'],
433
+ ['回答', '回复', '答复', 'answer', 'response'],
434
+ ['偏好', '喜欢', 'prefer', 'preference'],
435
+ ['项目', '工程', 'project'],
436
+ ['测试', '验证', 'test', 'verify'],
437
+ ];
438
+ function recallAliases(term) {
439
+ const aliases = [];
440
+ for (let index = 0; index < RECALL_ALIAS_GROUPS.length; index += 1) {
441
+ if (RECALL_ALIAS_GROUPS[index]?.includes(term))
442
+ aliases.push(`recall_alias_${index}`);
443
+ }
444
+ return aliases;
445
+ }
446
+ function buildFtsQuery(value) {
447
+ return terms(value)
448
+ .slice(0, 12)
449
+ .map((term) => `"${term.replaceAll('"', '""')}"`)
450
+ .join(' OR ');
451
+ }
452
+ function rowToClaim(row) {
453
+ return {
454
+ id: String(row.id),
455
+ scope: String(row.scope),
456
+ scopeKey: String(row.scope_key),
457
+ kind: String(row.kind),
458
+ content: String(row.content),
459
+ tags: JSON.parse(String(row.tags_json)),
460
+ state: String(row.state),
461
+ origin: String(row.origin),
462
+ sensitivity: String(row.sensitivity),
463
+ contentHash: String(row.content_hash),
464
+ ...(row.source_session_id === null || row.source_session_id === undefined ? {} : { sourceSessionId: String(row.source_session_id) }),
465
+ ...(row.source_event_seq === null || row.source_event_seq === undefined ? {} : { sourceEventSeq: Number(row.source_event_seq) }),
466
+ validFrom: Number(row.valid_from),
467
+ ...(row.valid_until === null || row.valid_until === undefined ? {} : { validUntil: Number(row.valid_until) }),
468
+ createdAt: Number(row.created_at),
469
+ updatedAt: Number(row.updated_at),
470
+ };
471
+ }
472
+ function rowToBelief(row) {
473
+ return {
474
+ claimId: String(row.claim_id ?? row.id),
475
+ alpha: Number(row.alpha),
476
+ beta: Number(row.beta),
477
+ harmfulCount: Number(row.harmful_count),
478
+ updatedAt: Number(row.belief_updated_at ?? row.updated_at),
479
+ };
480
+ }
481
+ function rowToEvidence(row) {
482
+ return {
483
+ id: String(row.id),
484
+ claimId: String(row.claim_id),
485
+ kind: String(row.kind),
486
+ weight: Number(row.weight),
487
+ ...(row.detail === null || row.detail === undefined ? {} : { detail: String(row.detail) }),
488
+ ...(row.session_id === null || row.session_id === undefined ? {} : { sessionId: String(row.session_id) }),
489
+ createdAt: Number(row.created_at),
490
+ };
491
+ }
492
+ //# sourceMappingURL=repository.js.map