@jungjaehoon/mama-core 1.6.0 → 1.7.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 (35) hide show
  1. package/db/migrations/037-create-context-packets.sql +32 -0
  2. package/dist/cases/wiki-page-index.js +6 -2
  3. package/dist/context-compile/boundary-defaults.d.ts +12 -0
  4. package/dist/context-compile/boundary-defaults.js +105 -0
  5. package/dist/context-compile/compiler-policy.d.ts +39 -0
  6. package/dist/context-compile/compiler-policy.js +147 -0
  7. package/dist/context-compile/compiler.d.ts +18 -0
  8. package/dist/context-compile/compiler.js +401 -0
  9. package/dist/context-compile/index.d.ts +9 -0
  10. package/dist/context-compile/index.js +25 -0
  11. package/dist/context-compile/packet-store.d.ts +9 -0
  12. package/dist/context-compile/packet-store.js +255 -0
  13. package/dist/context-compile/ref.d.ts +7 -0
  14. package/dist/context-compile/ref.js +102 -0
  15. package/dist/context-compile/source-readers.d.ts +69 -0
  16. package/dist/context-compile/source-readers.js +707 -0
  17. package/dist/context-compile/types.d.ts +138 -0
  18. package/dist/context-compile/types.js +11 -0
  19. package/dist/context-compile/visibility.d.ts +21 -0
  20. package/dist/context-compile/visibility.js +200 -0
  21. package/dist/db-adapter/node-sqlite-adapter.d.ts +3 -0
  22. package/dist/db-adapter/node-sqlite-adapter.js +76 -0
  23. package/dist/edges/ref-validation.js +55 -16
  24. package/dist/edges/types.d.ts +2 -0
  25. package/dist/embeddings.d.ts +3 -0
  26. package/dist/embeddings.js +37 -10
  27. package/dist/entities/store.d.ts +4 -1
  28. package/dist/entities/store.js +206 -78
  29. package/dist/entities/types.d.ts +5 -1
  30. package/dist/entities/types.js +1 -1
  31. package/dist/index.d.ts +1 -0
  32. package/dist/index.js +1 -0
  33. package/dist/memory/provenance.js +1 -0
  34. package/dist/memory/types.d.ts +1 -0
  35. package/package.json +3 -2
@@ -0,0 +1,138 @@
1
+ import type { MemoryScopeRef } from '../memory/types.js';
2
+ export declare const CONTEXT_REF_KINDS: readonly ["memory", "raw", "entity", "case"];
3
+ export type ContextRefKind = (typeof CONTEXT_REF_KINDS)[number];
4
+ export declare const UNSUPPORTED_CONTEXT_REF_KINDS: readonly ["decision", "wiki_page", "context_packet", "report"];
5
+ export type UnsupportedContextRefKind = (typeof UNSUPPORTED_CONTEXT_REF_KINDS)[number];
6
+ export type ContextRef = {
7
+ kind: 'memory';
8
+ id: string;
9
+ } | {
10
+ kind: 'raw';
11
+ raw_id: string;
12
+ connector: string;
13
+ source_id?: string;
14
+ channel_id?: string | null;
15
+ } | {
16
+ kind: 'entity';
17
+ id: string;
18
+ } | {
19
+ kind: 'case';
20
+ id: string;
21
+ };
22
+ export interface ContextRange {
23
+ start_ms?: number;
24
+ end_ms?: number;
25
+ }
26
+ export interface ContextProjectRef {
27
+ kind: 'project';
28
+ id: string;
29
+ }
30
+ export interface ContextCompileInput {
31
+ task: string;
32
+ scopes?: MemoryScopeRef[];
33
+ seed_refs?: ContextRef[];
34
+ connectors?: string[];
35
+ project_refs?: ContextProjectRef[];
36
+ tenant_id?: string | null;
37
+ range?: ContextRange;
38
+ as_of?: string | number | null;
39
+ limit?: number;
40
+ max_tool_calls?: number;
41
+ max_ms?: number;
42
+ max_tokens?: number;
43
+ strictness?: 'recall' | 'balanced' | 'strict' | 'low' | 'medium' | 'high';
44
+ }
45
+ export interface ContextBoundary {
46
+ scopes?: MemoryScopeRef[];
47
+ connectors?: string[];
48
+ project_refs?: ContextProjectRef[];
49
+ tenant_id?: string | null;
50
+ as_of?: string | number | null;
51
+ range?: ContextRange;
52
+ }
53
+ export interface CanonicalContextScopes {
54
+ scopes: MemoryScopeRef[];
55
+ scopeJson: string;
56
+ scopeHash: string;
57
+ }
58
+ export interface ContextVisibility {
59
+ scopes: MemoryScopeRef[];
60
+ scope_json: string;
61
+ scope_hash: string;
62
+ connectors: string[];
63
+ project_refs: ContextProjectRef[];
64
+ tenant_id: string | null;
65
+ as_of: string | number | null;
66
+ range: ContextRange | null;
67
+ }
68
+ export interface ContextEvidence {
69
+ ref: ContextRef;
70
+ title?: string;
71
+ excerpt?: string;
72
+ score?: number;
73
+ reasons?: string[];
74
+ retrieval_diagnostics?: unknown;
75
+ }
76
+ export interface ContextPacket {
77
+ packet_id: string;
78
+ mode: 'general';
79
+ task: string;
80
+ scopes: MemoryScopeRef[];
81
+ scope_hash: string;
82
+ generated_at: string;
83
+ range: ContextRange | null;
84
+ as_of: string | number | null;
85
+ compiler_version: string;
86
+ source_refs: ContextRef[];
87
+ selected_evidence: ContextEvidence[];
88
+ evidence_clusters: unknown[];
89
+ related_decisions: unknown[];
90
+ rejected_refs: ContextRef[];
91
+ rejected_refs_truncated: boolean;
92
+ rejected_summary: string[];
93
+ missing_context: string[];
94
+ caveats: string[];
95
+ expansion_trace: unknown[];
96
+ retrieval_diagnostics: unknown;
97
+ budget: {
98
+ max_tool_calls?: number;
99
+ used_tool_calls: number;
100
+ max_ms?: number;
101
+ elapsed_ms: number;
102
+ max_tokens?: number;
103
+ estimated_tokens: number;
104
+ budget_exhausted: boolean;
105
+ };
106
+ budget_manifest: {
107
+ budget_exhausted: boolean;
108
+ skipped_operators: string[];
109
+ };
110
+ }
111
+ export interface ContextPacketRecord {
112
+ packet_id: string;
113
+ task: string;
114
+ packet_json: string;
115
+ packet: ContextPacket;
116
+ scope_json: string;
117
+ scopes: MemoryScopeRef[];
118
+ scope_hash: string;
119
+ envelope_hash: string;
120
+ model_run_id: string;
121
+ agent_id: string;
122
+ input_snapshot_ref: string;
123
+ source_refs_json: string;
124
+ source_refs: ContextRef[];
125
+ tenant_id: string;
126
+ project_id: string;
127
+ memory_scope_kind: MemoryScopeRef['kind'];
128
+ memory_scope_id: string;
129
+ created_at: number;
130
+ }
131
+ export interface TrustedContextPacketLookupInput {
132
+ packetId: string;
133
+ envelopeHash: string;
134
+ modelRunId?: string;
135
+ callerModelRunId?: string;
136
+ includeFailed?: boolean;
137
+ }
138
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UNSUPPORTED_CONTEXT_REF_KINDS = exports.CONTEXT_REF_KINDS = void 0;
4
+ exports.CONTEXT_REF_KINDS = ['memory', 'raw', 'entity', 'case'];
5
+ exports.UNSUPPORTED_CONTEXT_REF_KINDS = [
6
+ 'decision',
7
+ 'wiki_page',
8
+ 'context_packet',
9
+ 'report',
10
+ ];
11
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,21 @@
1
+ import { type MemoryScopeRef } from '../memory/types.js';
2
+ import type { ContextBoundary, ContextProjectRef, ContextRef } from './types.js';
3
+ type BoundaryCheckInput = {
4
+ boundary: ContextBoundary;
5
+ requestedScopes?: readonly MemoryScopeRef[];
6
+ requestedConnectors?: readonly string[];
7
+ requestedProjectRefs?: readonly ContextProjectRef[];
8
+ requestedTenantId?: string | null;
9
+ seedRefs?: readonly unknown[];
10
+ };
11
+ export declare function canonicalizeContextScopes(scopes: readonly MemoryScopeRef[] | undefined): {
12
+ scopes: MemoryScopeRef[];
13
+ scopeJson: string;
14
+ scopeHash: string;
15
+ };
16
+ export declare function derivePrimaryContextScope(scopes: readonly MemoryScopeRef[]): MemoryScopeRef;
17
+ export declare function assertContextBoundaryAllowsInput(input: BoundaryCheckInput): void;
18
+ export declare function sanitizeContextPacketForVisibility<T>(packet: T): T;
19
+ export declare function normalizeSeedRefs(seedRefs: readonly unknown[] | undefined): ContextRef[];
20
+ export {};
21
+ //# sourceMappingURL=visibility.d.ts.map
@@ -0,0 +1,200 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.canonicalizeContextScopes = canonicalizeContextScopes;
4
+ exports.derivePrimaryContextScope = derivePrimaryContextScope;
5
+ exports.assertContextBoundaryAllowsInput = assertContextBoundaryAllowsInput;
6
+ exports.sanitizeContextPacketForVisibility = sanitizeContextPacketForVisibility;
7
+ exports.normalizeSeedRefs = normalizeSeedRefs;
8
+ const node_crypto_1 = require("node:crypto");
9
+ const types_js_1 = require("../memory/types.js");
10
+ const ref_js_1 = require("./ref.js");
11
+ const SCOPE_ORDER = {
12
+ project: 0,
13
+ channel: 1,
14
+ user: 2,
15
+ global: 3,
16
+ };
17
+ function assertScope(scope) {
18
+ if (!types_js_1.MEMORY_SCOPE_KINDS.includes(scope.kind)) {
19
+ throw new Error(`Unsupported memory scope kind: ${scope.kind}`);
20
+ }
21
+ const id = scope.id.trim();
22
+ if (id.length === 0) {
23
+ throw new Error('Memory scope id must not be empty');
24
+ }
25
+ return { kind: scope.kind, id };
26
+ }
27
+ function scopeKey(scope) {
28
+ return `${scope.kind}\0${scope.id}`;
29
+ }
30
+ function connectorKey(connector) {
31
+ const trimmed = connector.trim();
32
+ if (trimmed.length === 0) {
33
+ throw new Error('Connector must not be empty');
34
+ }
35
+ return trimmed;
36
+ }
37
+ function projectKey(project) {
38
+ const kind = project.kind.trim();
39
+ const id = project.id.trim();
40
+ if (kind.length === 0 || id.length === 0) {
41
+ throw new Error('Project ref kind and id must not be empty');
42
+ }
43
+ return `${kind}\0${id}`;
44
+ }
45
+ function sortScopes(scopes) {
46
+ return [...scopes].sort((left, right) => {
47
+ const orderDiff = SCOPE_ORDER[left.kind] - SCOPE_ORDER[right.kind];
48
+ if (orderDiff !== 0) {
49
+ return orderDiff;
50
+ }
51
+ return left.id.localeCompare(right.id);
52
+ });
53
+ }
54
+ function canonicalizeContextScopes(scopes) {
55
+ const unique = new Map();
56
+ for (const scope of scopes ?? []) {
57
+ const normalized = assertScope(scope);
58
+ unique.set(scopeKey(normalized), normalized);
59
+ }
60
+ const canonicalScopes = sortScopes([...unique.values()]);
61
+ const scopeJson = JSON.stringify(canonicalScopes);
62
+ const scopeHash = (0, node_crypto_1.createHash)('sha256').update(scopeJson).digest('hex');
63
+ return {
64
+ scopes: canonicalScopes,
65
+ scopeJson,
66
+ scopeHash,
67
+ };
68
+ }
69
+ function derivePrimaryContextScope(scopes) {
70
+ const canonical = canonicalizeContextScopes(scopes).scopes;
71
+ const primary = canonical.find((scope) => scope.kind === 'project') ?? canonical[0];
72
+ return primary ?? { kind: 'global', id: 'default' };
73
+ }
74
+ function assertContextBoundaryAllowsInput(input) {
75
+ const hasScopeBoundary = Array.isArray(input.boundary.scopes);
76
+ const boundaryScopes = canonicalizeContextScopes(input.boundary.scopes).scopes;
77
+ const requestedScopes = canonicalizeContextScopes(input.requestedScopes).scopes;
78
+ if (hasScopeBoundary && requestedScopes.length > 0) {
79
+ const allowed = new Set(boundaryScopes.map(scopeKey));
80
+ const disallowed = requestedScopes.find((scope) => !allowed.has(scopeKey(scope)));
81
+ if (disallowed) {
82
+ throw new Error(`Requested scope is outside the context boundary: ${disallowed.kind}:${disallowed.id}`);
83
+ }
84
+ }
85
+ const hasConnectorBoundary = Array.isArray(input.boundary.connectors);
86
+ const boundaryConnectors = new Set((input.boundary.connectors ?? []).map(connectorKey));
87
+ const requestedConnectors = (input.requestedConnectors ?? []).map(connectorKey);
88
+ if (hasConnectorBoundary) {
89
+ const disallowedConnector = requestedConnectors.find((connector) => !boundaryConnectors.has(connector));
90
+ if (disallowedConnector) {
91
+ throw new Error(`Requested connector is outside the context boundary: ${disallowedConnector}`);
92
+ }
93
+ }
94
+ const hasProjectBoundary = Array.isArray(input.boundary.project_refs);
95
+ const boundaryProjects = new Set((input.boundary.project_refs ?? []).map(projectKey));
96
+ const requestedProjects = (input.requestedProjectRefs ?? []).map(projectKey);
97
+ if (hasProjectBoundary && requestedProjects.length > 0) {
98
+ const disallowedProject = requestedProjects.find((project) => !boundaryProjects.has(project));
99
+ if (disallowedProject) {
100
+ const [kind, id] = disallowedProject.split('\0');
101
+ throw new Error(`Requested project ref is outside the context boundary: ${kind}:${id}`);
102
+ }
103
+ }
104
+ const boundaryTenantId = input.boundary.tenant_id;
105
+ if (typeof boundaryTenantId === 'string' &&
106
+ boundaryTenantId.length > 0 &&
107
+ input.requestedTenantId !== undefined &&
108
+ input.requestedTenantId !== null &&
109
+ input.requestedTenantId !== boundaryTenantId) {
110
+ throw new Error('Requested tenant is outside the context boundary');
111
+ }
112
+ const seedRefs = (0, ref_js_1.normalizeContextRefs)(input.seedRefs);
113
+ assertSeedRefsAllowed(seedRefs, boundaryConnectors, hasConnectorBoundary);
114
+ }
115
+ function assertSeedRefsAllowed(seedRefs, boundaryConnectors, hasConnectorBoundary) {
116
+ for (const ref of seedRefs) {
117
+ if (ref.kind === 'raw' && hasConnectorBoundary && !boundaryConnectors.has(ref.connector)) {
118
+ throw new Error(`Seed raw ref connector is outside the context boundary: ${ref.connector}`);
119
+ }
120
+ }
121
+ }
122
+ function isRecord(value) {
123
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
124
+ }
125
+ function collectHiddenIdentifiers(value, hidden) {
126
+ if (Array.isArray(value)) {
127
+ for (const item of value) {
128
+ collectHiddenIdentifiers(item, hidden);
129
+ }
130
+ return;
131
+ }
132
+ if (!isRecord(value)) {
133
+ return;
134
+ }
135
+ const maybeHidden = value;
136
+ if (maybeHidden.visible === false) {
137
+ for (const field of ['id', 'raw_id', 'source_id', 'channel_id']) {
138
+ const fieldValue = value[field];
139
+ if (typeof fieldValue === 'string' && fieldValue.length > 0) {
140
+ hidden.add(fieldValue);
141
+ }
142
+ }
143
+ }
144
+ for (const child of Object.values(value)) {
145
+ collectHiddenIdentifiers(child, hidden);
146
+ }
147
+ }
148
+ function isIdentifierBoundary(character) {
149
+ return character === undefined || !/[A-Za-z0-9_]/.test(character);
150
+ }
151
+ function containsHiddenIdentifier(value, identifier) {
152
+ if (identifier.length === 0) {
153
+ return false;
154
+ }
155
+ let index = value.indexOf(identifier);
156
+ while (index !== -1) {
157
+ const before = value[index - 1];
158
+ const after = value[index + identifier.length];
159
+ if (isIdentifierBoundary(before) && isIdentifierBoundary(after)) {
160
+ return true;
161
+ }
162
+ index = value.indexOf(identifier, index + 1);
163
+ }
164
+ return false;
165
+ }
166
+ function sanitizeValue(value, hidden) {
167
+ if (typeof value === 'string') {
168
+ return [...hidden].some((identifier) => containsHiddenIdentifier(value, identifier))
169
+ ? undefined
170
+ : value;
171
+ }
172
+ if (Array.isArray(value)) {
173
+ return value
174
+ .map((item) => sanitizeValue(item, hidden))
175
+ .filter((item) => item !== undefined);
176
+ }
177
+ if (!isRecord(value)) {
178
+ return value;
179
+ }
180
+ if (value.visible === false) {
181
+ return undefined;
182
+ }
183
+ const sanitized = {};
184
+ for (const [key, child] of Object.entries(value)) {
185
+ const next = sanitizeValue(child, hidden);
186
+ if (next !== undefined) {
187
+ sanitized[key] = next;
188
+ }
189
+ }
190
+ return sanitized;
191
+ }
192
+ function sanitizeContextPacketForVisibility(packet) {
193
+ const hidden = new Set();
194
+ collectHiddenIdentifiers(packet, hidden);
195
+ return sanitizeValue(packet, hidden);
196
+ }
197
+ function normalizeSeedRefs(seedRefs) {
198
+ return (0, ref_js_1.normalizeContextRefs)(seedRefs).map((ref) => (0, ref_js_1.normalizeContextRef)(ref));
199
+ }
200
+ //# sourceMappingURL=visibility.js.map
@@ -55,11 +55,14 @@ export declare class NodeSQLiteAdapter extends DatabaseAdapter {
55
55
  insertEmbedding(rowid: number, embedding: Float32Array | number[]): RunResult | null;
56
56
  getLastInsertRowid(): number;
57
57
  runMigrations(migrationsDir: string): void;
58
+ private repairSkippedFeatureMigrations;
59
+ private applyRepairMigration;
58
60
  private recoverMemoryProvenanceMigration032;
59
61
  private assertMigration032Complete;
60
62
  private recoverConnectorEventScopeMigration034;
61
63
  private assertMigration034Complete;
62
64
  private tableColumns;
65
+ private tableExists;
63
66
  private indexExists;
64
67
  private migrateFromVssMemories;
65
68
  }
@@ -350,12 +350,81 @@ class NodeSQLiteAdapter extends base_adapter_js_1.DatabaseAdapter {
350
350
  throw new Error(`Migration ${file} failed: ${message}`);
351
351
  }
352
352
  }
353
+ this.repairSkippedFeatureMigrations(migrationsDir);
353
354
  const embeddingsTables = this.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='embeddings'`).all();
354
355
  if (embeddingsTables.length === 0) {
355
356
  throw new Error('Embeddings table is missing after migrations. Add the required embeddings migration.');
356
357
  }
357
358
  this.migrateFromVssMemories();
358
359
  }
360
+ repairSkippedFeatureMigrations(migrationsDir) {
361
+ if (this.tableExists('decisions')) {
362
+ const decisionColumns = this.tableColumns('decisions');
363
+ const hasMissingMemoryProvenanceColumn = [
364
+ 'agent_id',
365
+ 'model_run_id',
366
+ 'envelope_hash',
367
+ 'gateway_call_id',
368
+ 'source_refs_json',
369
+ 'provenance_json',
370
+ ].some((column) => !decisionColumns.has(column));
371
+ const hasMissingMemoryProvenanceIndex = [
372
+ 'idx_decisions_envelope_hash',
373
+ 'idx_decisions_model_run_id',
374
+ 'idx_decisions_gateway_call_id',
375
+ 'idx_memory_events_memory_created',
376
+ ].some((indexName) => !this.indexExists(indexName));
377
+ if (hasMissingMemoryProvenanceColumn || hasMissingMemoryProvenanceIndex) {
378
+ this.recoverMemoryProvenanceMigration032();
379
+ (0, debug_logger_js_1.info)('[node-sqlite-adapter] Repaired skipped memory provenance migration');
380
+ }
381
+ }
382
+ if (!this.tableExists('model_runs') || !this.tableExists('tool_traces')) {
383
+ this.applyRepairMigration(migrationsDir, '033-create-model-runs-and-tool-traces.sql', 'model run provenance');
384
+ }
385
+ if (this.tableExists('connector_event_index')) {
386
+ const connectorColumns = this.tableColumns('connector_event_index');
387
+ const hasMissingConnectorScopeColumn = [
388
+ 'source_cursor',
389
+ 'tenant_id',
390
+ 'project_id',
391
+ 'memory_scope_kind',
392
+ 'memory_scope_id',
393
+ ].some((column) => !connectorColumns.has(column));
394
+ if (hasMissingConnectorScopeColumn) {
395
+ this.recoverConnectorEventScopeMigration034();
396
+ (0, debug_logger_js_1.info)('[node-sqlite-adapter] Repaired skipped connector event scope migration');
397
+ }
398
+ }
399
+ if (!this.tableExists('twin_edges')) {
400
+ this.applyRepairMigration(migrationsDir, '035-create-twin-edges.sql', 'twin edge ledger');
401
+ }
402
+ if (!this.tableExists('agent_situation_packets') ||
403
+ !this.tableExists('agent_situation_refresh_leases')) {
404
+ this.applyRepairMigration(migrationsDir, '036-create-agent-situation-packets.sql', 'agent situation packet cache');
405
+ }
406
+ if (!this.tableExists('context_packets')) {
407
+ this.applyRepairMigration(migrationsDir, '037-create-context-packets.sql', 'context packet store');
408
+ }
409
+ }
410
+ applyRepairMigration(migrationsDir, fileName, label) {
411
+ const migrationPath = path_1.default.join(migrationsDir, fileName);
412
+ if (!fs_1.default.existsSync(migrationPath)) {
413
+ throw new Error(`Missing repair migration ${fileName} for ${label}`);
414
+ }
415
+ const migrationSQL = fs_1.default.readFileSync(migrationPath, 'utf8');
416
+ (0, debug_logger_js_1.info)(`[node-sqlite-adapter] Repairing skipped ${label} migration: ${fileName}`);
417
+ try {
418
+ this.transaction(() => {
419
+ this.exec(migrationSQL);
420
+ });
421
+ (0, debug_logger_js_1.info)(`[node-sqlite-adapter] Repair migration ${fileName} applied successfully`);
422
+ }
423
+ catch (err) {
424
+ const message = err instanceof Error ? err.message : String(err);
425
+ throw new Error(`Repair migration ${fileName} failed: ${message}`);
426
+ }
427
+ }
359
428
  recoverMemoryProvenanceMigration032() {
360
429
  this.transaction(() => {
361
430
  const expectedDecisionColumns = [
@@ -472,6 +541,13 @@ class NodeSQLiteAdapter extends base_adapter_js_1.DatabaseAdapter {
472
541
  const rows = this.prepare(`PRAGMA table_info(${tableName})`).all();
473
542
  return new Set(rows.map((row) => row.name));
474
543
  }
544
+ tableExists(tableName) {
545
+ if (!SQLITE_IDENTIFIER_PATTERN.test(tableName)) {
546
+ throw new Error('Invalid SQLite table identifier');
547
+ }
548
+ const row = this.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name = ?").get(tableName);
549
+ return row?.name === tableName;
550
+ }
475
551
  indexExists(indexName) {
476
552
  const row = this.prepare("SELECT name FROM sqlite_master WHERE type='index' AND name = ?").get(indexName);
477
553
  return row?.name === indexName;
@@ -9,15 +9,15 @@ const TABLE_COLUMN_PRAGMAS = {
9
9
  case_truth: 'PRAGMA table_info(case_truth)',
10
10
  };
11
11
  const tableColumnCache = new WeakMap();
12
+ // Keep memory-truth quarantine semantics excluded for legacy/backcompat rows even though the
13
+ // current decisions.status CHECK only admits superseded/contradicted/stale terminal states.
14
+ const EXCLUDED_MEMORY_STATUSES = new Set(['superseded', 'quarantined', 'contradicted', 'stale']);
12
15
  function scopeKey(scope) {
13
16
  return `${scope.kind}\0${scope.id}`;
14
17
  }
15
18
  function hasScopes(scopes) {
16
19
  return Array.isArray(scopes) && scopes.length > 0;
17
20
  }
18
- function hasConnectors(connectors) {
19
- return Array.isArray(connectors) && connectors.length > 0;
20
- }
21
21
  function hasProjectRefs(projectRefs) {
22
22
  return Array.isArray(projectRefs) && projectRefs.length > 0;
23
23
  }
@@ -27,16 +27,33 @@ function visibilityFromScopes(scopes) {
27
27
  function asOfMs(visibility) {
28
28
  return typeof visibility.asOfMs === 'number' ? visibility.asOfMs : null;
29
29
  }
30
- function isAtOrBeforeAsOf(value, visibility) {
30
+ function startMs(visibility) {
31
+ return typeof visibility.startMs === 'number' ? visibility.startMs : null;
32
+ }
33
+ function isWithinVisibilityTime(value, visibility) {
34
+ const start = startMs(visibility);
31
35
  const asOf = asOfMs(visibility);
32
- return asOf === null || (typeof value === 'number' && value <= asOf);
36
+ if (start === null && asOf === null) {
37
+ return true;
38
+ }
39
+ return (typeof value === 'number' &&
40
+ (start === null || value >= start) &&
41
+ (asOf === null || value <= asOf));
33
42
  }
34
43
  function parseTimestamp(value) {
35
44
  if (typeof value === 'number' && Number.isFinite(value)) {
36
45
  return Math.floor(value);
37
46
  }
38
47
  if (typeof value === 'string') {
39
- const parsed = Date.parse(value);
48
+ const trimmed = value.trim();
49
+ if (trimmed.length === 0) {
50
+ return null;
51
+ }
52
+ const numeric = Number(trimmed);
53
+ if (Number.isFinite(numeric)) {
54
+ return Math.floor(numeric);
55
+ }
56
+ const parsed = Date.parse(trimmed);
40
57
  return Number.isFinite(parsed) ? parsed : null;
41
58
  }
42
59
  return null;
@@ -61,9 +78,14 @@ function tableColumns(adapter, table) {
61
78
  return columns;
62
79
  }
63
80
  function isMemoryVisible(adapter, id, visibility) {
81
+ const columns = tableColumns(adapter, 'decisions');
82
+ const statusSelect = columns.has('status') ? 'status' : 'NULL AS status';
83
+ const supersededBySelect = columns.has('superseded_by')
84
+ ? 'superseded_by'
85
+ : 'NULL AS superseded_by';
64
86
  const row = adapter
65
87
  .prepare(`
66
- SELECT created_at, event_datetime
88
+ SELECT created_at, event_datetime, ${statusSelect}, ${supersededBySelect}
67
89
  FROM decisions
68
90
  WHERE id = ?
69
91
  LIMIT 1
@@ -72,7 +94,12 @@ function isMemoryVisible(adapter, id, visibility) {
72
94
  if (!row) {
73
95
  return false;
74
96
  }
75
- if (!isAtOrBeforeAsOf(row.event_datetime ?? row.created_at, visibility)) {
97
+ const status = typeof row.status === 'string' ? row.status : 'active';
98
+ if (EXCLUDED_MEMORY_STATUSES.has(status) ||
99
+ (typeof row.superseded_by === 'string' && row.superseded_by.length > 0)) {
100
+ return false;
101
+ }
102
+ if (!isWithinVisibilityTime(row.event_datetime ?? row.created_at, visibility)) {
76
103
  return false;
77
104
  }
78
105
  const scopes = visibility.scopes;
@@ -94,7 +121,6 @@ function isMemoryVisible(adapter, id, visibility) {
94
121
  if (bindingRow?.ok) {
95
122
  return true;
96
123
  }
97
- const columns = tableColumns(adapter, 'decisions');
98
124
  if (columns.has('memory_scope_kind') && columns.has('memory_scope_id')) {
99
125
  const row = adapter
100
126
  .prepare(`
@@ -133,7 +159,7 @@ function isCaseVisible(adapter, id, visibility) {
133
159
  return false;
134
160
  }
135
161
  const timestamp = parseTimestamp(row.last_activity_at) ?? parseTimestamp(row.updated_at);
136
- if (!isAtOrBeforeAsOf(timestamp, visibility)) {
162
+ if (!isWithinVisibilityTime(timestamp, visibility)) {
137
163
  return false;
138
164
  }
139
165
  const scopes = visibility.scopes;
@@ -158,7 +184,7 @@ function isRawVisible(adapter, id, visibility) {
158
184
  if (!row) {
159
185
  return false;
160
186
  }
161
- if (hasConnectors(visibility.connectors) &&
187
+ if (Array.isArray(visibility.connectors) &&
162
188
  !visibility.connectors.includes(String(row.source_connector))) {
163
189
  return false;
164
190
  }
@@ -171,7 +197,12 @@ function isRawVisible(adapter, id, visibility) {
171
197
  row.tenant_id !== visibility.tenantId) {
172
198
  return false;
173
199
  }
174
- if (!isAtOrBeforeAsOf(Number(row.event_datetime ?? row.source_timestamp_ms), visibility)) {
200
+ const rawTs = row.event_datetime ?? row.source_timestamp_ms;
201
+ if (rawTs === null || rawTs === undefined || (typeof rawTs === 'string' && rawTs.trim() === '')) {
202
+ return false;
203
+ }
204
+ const eventTsMs = parseTimestamp(rawTs);
205
+ if (eventTsMs === null || eventTsMs < 0 || !isWithinVisibilityTime(eventTsMs, visibility)) {
175
206
  return false;
176
207
  }
177
208
  if (!hasScopes(visibility.scopes)) {
@@ -186,7 +217,7 @@ function isEntityVisible(adapter, id, visibility) {
186
217
  if (!row) {
187
218
  return false;
188
219
  }
189
- if (!isAtOrBeforeAsOf(row.created_at, visibility)) {
220
+ if (!isWithinVisibilityTime(row.created_at, visibility)) {
190
221
  return false;
191
222
  }
192
223
  const scopes = visibility.scopes;
@@ -222,7 +253,7 @@ function isTwinRefVisible(adapter, ref, visibility, visitedEdges, edgeCache) {
222
253
  if (!edge) {
223
254
  return false;
224
255
  }
225
- if (!isAtOrBeforeAsOf(edge.created_at, visibility)) {
256
+ if (!isWithinVisibilityTime(edge.created_at, visibility)) {
226
257
  return false;
227
258
  }
228
259
  const pathWithCurrent = new Set(visitedEdges);
@@ -245,13 +276,21 @@ function listVisibleTwinEdgesForRefs(adapter, refs, options = {}) {
245
276
  const edgeCache = new Map();
246
277
  const edgeTypes = normalizeEdgeTypes(options.edgeTypes);
247
278
  const edges = (0, store_js_1.listTwinEdgesForRefs)(adapter, refs).filter((edge) => (edgeTypes.size === 0 || edgeTypes.has(edge.edge_type)) &&
279
+ (typeof options.startMs !== 'number' || edge.created_at >= options.startMs) &&
248
280
  (typeof options.asOfMs !== 'number' || edge.created_at <= options.asOfMs) &&
249
281
  isTwinRefVisible(adapter, edge.subject_ref, options, new Set(), edgeCache) &&
250
282
  isTwinRefVisible(adapter, edge.object_ref, options, new Set(), edgeCache));
251
283
  const limit = typeof options.limit === 'number' && Number.isFinite(options.limit)
252
284
  ? Math.max(0, Math.floor(options.limit))
253
- : edges.length;
254
- return edges.slice(0, limit);
285
+ : null;
286
+ const sortedEdges = [...edges].sort((left, right) => {
287
+ const createdDiff = right.created_at - left.created_at;
288
+ return createdDiff !== 0 ? createdDiff : left.edge_id.localeCompare(right.edge_id);
289
+ });
290
+ if (limit === null) {
291
+ return sortedEdges;
292
+ }
293
+ return sortedEdges.slice(0, limit);
255
294
  }
256
295
  function normalizeEdgeTypes(edgeTypes) {
257
296
  if (!Array.isArray(edgeTypes) || edgeTypes.length === 0) {
@@ -27,6 +27,7 @@ export interface TwinVisibility {
27
27
  connectors?: string[];
28
28
  projectRefs?: TwinProjectRef[];
29
29
  tenantId?: string | null;
30
+ startMs?: number | null;
30
31
  asOfMs?: number | null;
31
32
  }
32
33
  export interface InsertTwinEdgeInput {
@@ -80,6 +81,7 @@ export interface ListVisibleTwinEdgesOptions {
80
81
  projectRefs?: TwinProjectRef[];
81
82
  tenantId?: string | null;
82
83
  edgeTypes?: TwinEdgeType[];
84
+ startMs?: number | null;
83
85
  asOfMs?: number | null;
84
86
  limit?: number;
85
87
  }
@@ -11,6 +11,9 @@
11
11
  */
12
12
  import { embeddingCache } from './embedding-cache.js';
13
13
  import { loadConfig, getModelName, getEmbeddingDim, getQuantized } from './config-loader.js';
14
+ export declare const EMBEDDING_MODEL_MAX_LENGTH = 512;
15
+ export declare const EMBEDDING_MAX_TOKENISH_SEGMENTS = 480;
16
+ export declare function prepareEmbeddingText(text: string): string;
14
17
  /**
15
18
  * Decision object for enhanced embedding generation
16
19
  */