@opengeni/db 0.17.1 → 0.19.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.
- package/dist/{chunk-TLAC622R.js → chunk-SZP6KRLC.js} +2128 -1927
- package/dist/chunk-SZP6KRLC.js.map +1 -0
- package/dist/{chunk-T6RSJT6C.js → chunk-XOXEQ7HG.js} +29 -1
- package/dist/chunk-XOXEQ7HG.js.map +1 -0
- package/dist/index.d.ts +71 -2
- package/dist/index.js +1194 -562
- package/dist/index.js.map +1 -1
- package/dist/memory-domain.d.ts +110 -1
- package/dist/memory-governance-schema.d.ts +562 -0
- package/dist/memory-governance.d.ts +41 -0
- package/dist/provision-roles.js +1 -1
- package/dist/runtime-posture.d.ts +2 -2
- package/dist/schema.d.ts +286 -2
- package/dist/schema.js +5 -1
- package/drizzle/0141_social_connection_credentials.sql +8 -0
- package/drizzle/0151_slack_delivery_backoff.sql +134 -0
- package/drizzle/0152_hierarchical_memory_foundation.sql +1589 -0
- package/package.json +3 -3
- package/src/index.ts +304 -11
- package/src/memory-domain.ts +360 -1
- package/src/memory-governance-schema.ts +162 -0
- package/src/memory-governance.ts +317 -0
- package/src/provision-roles.ts +24 -0
- package/src/runtime-posture.ts +4 -0
- package/src/schema.ts +50 -2
- package/dist/chunk-T6RSJT6C.js.map +0 -1
- package/dist/chunk-TLAC622R.js.map +0 -1
package/src/memory-domain.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import type
|
|
2
|
+
import { stableJson, type KnowledgeMemoryKind } from "@opengeni/contracts";
|
|
3
3
|
|
|
4
4
|
// Workspace Memory V1 — pure domain logic (gates + render + canonical prompt
|
|
5
5
|
// text). No database access: everything here is unit-testable in isolation and
|
|
@@ -32,6 +32,365 @@ export const MEMORY_SEARCH_MAX_LIMIT = 20;
|
|
|
32
32
|
/** Statuses an agent may see: active (agent-written) ∪ approved (curated). */
|
|
33
33
|
export const AGENT_VISIBLE_MEMORY_STATUSES = ["active", "approved"] as const;
|
|
34
34
|
|
|
35
|
+
/** Maximum normalized label length. Labels are relevance hints, never authority. */
|
|
36
|
+
export const MEMORY_LABEL_MAX_CHARS = 64;
|
|
37
|
+
/** Maximum labels stored on one memory. */
|
|
38
|
+
export const MEMORY_LABEL_MAX_COUNT = 16;
|
|
39
|
+
/** Maximum normalized namespace length, including hierarchy separators. */
|
|
40
|
+
export const MEMORY_NAMESPACE_MAX_CHARS = 128;
|
|
41
|
+
/** Maximum normalized durable-role key length. */
|
|
42
|
+
export const MEMORY_ROLE_KEY_MAX_CHARS = 64;
|
|
43
|
+
/** Maximum subject identifier length stored in typed scope/authority columns. */
|
|
44
|
+
export const MEMORY_SUBJECT_ID_MAX_CHARS = 1024;
|
|
45
|
+
|
|
46
|
+
const MEMORY_SELECTOR_SEGMENT_PATTERN = /^[a-z0-9](?:[a-z0-9._-]{0,62}[a-z0-9])?$/;
|
|
47
|
+
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
48
|
+
|
|
49
|
+
export type MemoryScopeType = "workspace" | "user" | "role" | "session" | "ephemeral" | "legacy";
|
|
50
|
+
|
|
51
|
+
export type MemoryScopeSpec =
|
|
52
|
+
| { type: "workspace" }
|
|
53
|
+
| { type: "user"; subjectId: string }
|
|
54
|
+
| { type: "role"; roleKey: string }
|
|
55
|
+
| { type: "session"; sessionId: string }
|
|
56
|
+
| { type: "ephemeral"; sessionId: string; validUntil: string }
|
|
57
|
+
| { type: "legacy"; legacyScope: string };
|
|
58
|
+
|
|
59
|
+
export type MemoryRelationshipType =
|
|
60
|
+
| "derived_from"
|
|
61
|
+
| "supersedes"
|
|
62
|
+
| "corrects"
|
|
63
|
+
| "conflicts_with"
|
|
64
|
+
| "related_to"
|
|
65
|
+
| "depends_on"
|
|
66
|
+
| "applies_to";
|
|
67
|
+
|
|
68
|
+
export const MEMORY_RELATIONSHIP_TYPES = [
|
|
69
|
+
"derived_from",
|
|
70
|
+
"supersedes",
|
|
71
|
+
"corrects",
|
|
72
|
+
"conflicts_with",
|
|
73
|
+
"related_to",
|
|
74
|
+
"depends_on",
|
|
75
|
+
"applies_to",
|
|
76
|
+
] as const satisfies readonly MemoryRelationshipType[];
|
|
77
|
+
|
|
78
|
+
const SYMMETRIC_MEMORY_RELATIONSHIPS = new Set<MemoryRelationshipType>([
|
|
79
|
+
"conflicts_with",
|
|
80
|
+
"related_to",
|
|
81
|
+
]);
|
|
82
|
+
|
|
83
|
+
export type MemoryOperationType =
|
|
84
|
+
| "reclassify"
|
|
85
|
+
| "archive"
|
|
86
|
+
| "relationship_add"
|
|
87
|
+
| "relationship_remove"
|
|
88
|
+
| "supersede"
|
|
89
|
+
| "correct";
|
|
90
|
+
|
|
91
|
+
type MemoryOperationPlanBase = {
|
|
92
|
+
operationId: string;
|
|
93
|
+
operationType: MemoryOperationType;
|
|
94
|
+
targetMemoryId: string;
|
|
95
|
+
expectedTargetVersion: number;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export type MemoryOperationPlan =
|
|
99
|
+
| (MemoryOperationPlanBase & {
|
|
100
|
+
operationType: "reclassify";
|
|
101
|
+
scope: MemoryScopeSpec;
|
|
102
|
+
namespace: string;
|
|
103
|
+
labels: string[];
|
|
104
|
+
})
|
|
105
|
+
| (MemoryOperationPlanBase & { operationType: "archive" })
|
|
106
|
+
| (MemoryOperationPlanBase & {
|
|
107
|
+
operationType: "relationship_add" | "relationship_remove";
|
|
108
|
+
relatedMemoryId: string;
|
|
109
|
+
expectedRelatedVersion: number;
|
|
110
|
+
relationshipType: MemoryRelationshipType;
|
|
111
|
+
})
|
|
112
|
+
| (MemoryOperationPlanBase & {
|
|
113
|
+
operationType: "supersede" | "correct";
|
|
114
|
+
relatedMemoryId: string;
|
|
115
|
+
expectedRelatedVersion: number;
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
export type MemoryOperationPlanInput =
|
|
119
|
+
| (Omit<MemoryOperationPlanBase, "operationType"> & {
|
|
120
|
+
operationType: "reclassify";
|
|
121
|
+
scope: MemoryScopeSpec;
|
|
122
|
+
namespace?: string | null;
|
|
123
|
+
labels?: readonly string[] | null;
|
|
124
|
+
})
|
|
125
|
+
| (Omit<MemoryOperationPlanBase, "operationType"> & { operationType: "archive" })
|
|
126
|
+
| (Omit<MemoryOperationPlanBase, "operationType"> & {
|
|
127
|
+
operationType: "relationship_add" | "relationship_remove";
|
|
128
|
+
relatedMemoryId: string;
|
|
129
|
+
expectedRelatedVersion: number;
|
|
130
|
+
relationshipType: MemoryRelationshipType;
|
|
131
|
+
})
|
|
132
|
+
| (Omit<MemoryOperationPlanBase, "operationType"> & {
|
|
133
|
+
operationType: "supersede" | "correct";
|
|
134
|
+
relatedMemoryId: string;
|
|
135
|
+
expectedRelatedVersion: number;
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
export type MemoryRevertPlan = {
|
|
139
|
+
operationId: string;
|
|
140
|
+
appliedOperationId: string;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
export type MemoryRevertPlanInput = MemoryRevertPlan;
|
|
144
|
+
|
|
145
|
+
function normalizeSelectorSegment(value: string, label: string): string {
|
|
146
|
+
const normalized = value.trim().toLowerCase().replace(/\s+/g, "-");
|
|
147
|
+
if (!MEMORY_SELECTOR_SEGMENT_PATTERN.test(normalized)) {
|
|
148
|
+
throw new Error(
|
|
149
|
+
`${label} must be a lowercase slug using letters, numbers, dot, underscore, or dash`,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
return normalized;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function normalizeUuid(value: string, label: string): string {
|
|
156
|
+
const normalized = value.trim().toLowerCase();
|
|
157
|
+
if (!UUID_PATTERN.test(normalized)) {
|
|
158
|
+
throw new Error(`${label} must be a UUID`);
|
|
159
|
+
}
|
|
160
|
+
return normalized;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function normalizePositiveVersion(value: number, label: string): number {
|
|
164
|
+
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
165
|
+
throw new Error(`${label} must be a positive safe integer`);
|
|
166
|
+
}
|
|
167
|
+
return value;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Normalize one bounded memory label. Invalid selectors fail closed. */
|
|
171
|
+
export function normalizeMemoryLabel(label: string): string {
|
|
172
|
+
const normalized = normalizeSelectorSegment(label, "memory label");
|
|
173
|
+
if (normalized.length > MEMORY_LABEL_MAX_CHARS) {
|
|
174
|
+
throw new Error(`memory label exceeds ${MEMORY_LABEL_MAX_CHARS} characters`);
|
|
175
|
+
}
|
|
176
|
+
return normalized;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Normalize, de-duplicate, sort, and bound memory labels deterministically. */
|
|
180
|
+
export function normalizeMemoryLabels(labels: readonly string[] | null | undefined): string[] {
|
|
181
|
+
const normalized = new Set((labels ?? []).map(normalizeMemoryLabel));
|
|
182
|
+
if (normalized.size > MEMORY_LABEL_MAX_COUNT) {
|
|
183
|
+
throw new Error(`memory labels exceed the ${MEMORY_LABEL_MAX_COUNT}-label limit`);
|
|
184
|
+
}
|
|
185
|
+
return [...normalized].sort((left, right) => (left < right ? -1 : left > right ? 1 : 0));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Normalize a hierarchical namespace such as `engineering/backend`. */
|
|
189
|
+
export function normalizeMemoryNamespace(namespace: string | null | undefined): string {
|
|
190
|
+
const raw = namespace?.trim() || "general";
|
|
191
|
+
const normalized = raw
|
|
192
|
+
.split("/")
|
|
193
|
+
.map((segment) => normalizeSelectorSegment(segment, "memory namespace segment"))
|
|
194
|
+
.join("/");
|
|
195
|
+
if (normalized.length > MEMORY_NAMESPACE_MAX_CHARS) {
|
|
196
|
+
throw new Error(`memory namespace exceeds ${MEMORY_NAMESPACE_MAX_CHARS} characters`);
|
|
197
|
+
}
|
|
198
|
+
return normalized;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function normalizeMemoryRoleKey(roleKey: string): string {
|
|
202
|
+
const normalized = normalizeSelectorSegment(roleKey, "memory role key");
|
|
203
|
+
if (normalized.length > MEMORY_ROLE_KEY_MAX_CHARS) {
|
|
204
|
+
throw new Error(`memory role key exceeds ${MEMORY_ROLE_KEY_MAX_CHARS} characters`);
|
|
205
|
+
}
|
|
206
|
+
return normalized;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function normalizeMemoryScope(scope: MemoryScopeSpec): MemoryScopeSpec {
|
|
210
|
+
switch (scope.type) {
|
|
211
|
+
case "workspace":
|
|
212
|
+
return { type: "workspace" };
|
|
213
|
+
case "user": {
|
|
214
|
+
const subjectId = scope.subjectId.trim();
|
|
215
|
+
if (!subjectId || subjectId.length > MEMORY_SUBJECT_ID_MAX_CHARS) {
|
|
216
|
+
throw new Error(
|
|
217
|
+
`memory user scope requires a subject id of at most ${MEMORY_SUBJECT_ID_MAX_CHARS} characters`,
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
return { type: "user", subjectId };
|
|
221
|
+
}
|
|
222
|
+
case "role":
|
|
223
|
+
return { type: "role", roleKey: normalizeMemoryRoleKey(scope.roleKey) };
|
|
224
|
+
case "session":
|
|
225
|
+
return { type: "session", sessionId: normalizeUuid(scope.sessionId, "memory session id") };
|
|
226
|
+
case "ephemeral": {
|
|
227
|
+
const validUntil = new Date(scope.validUntil);
|
|
228
|
+
if (!Number.isFinite(validUntil.getTime())) {
|
|
229
|
+
throw new Error("ephemeral memory scope requires a valid expiry timestamp");
|
|
230
|
+
}
|
|
231
|
+
return {
|
|
232
|
+
type: "ephemeral",
|
|
233
|
+
sessionId: normalizeUuid(scope.sessionId, "ephemeral memory session id"),
|
|
234
|
+
validUntil: validUntil.toISOString(),
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
case "legacy": {
|
|
238
|
+
const legacyScope = scope.legacyScope.trim();
|
|
239
|
+
if (!legacyScope || legacyScope.length > MEMORY_NAMESPACE_MAX_CHARS) {
|
|
240
|
+
throw new Error("legacy memory scope must be a non-empty bounded string");
|
|
241
|
+
}
|
|
242
|
+
return { type: "legacy", legacyScope };
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function isMemoryScopeApplicable(
|
|
248
|
+
scope: MemoryScopeSpec,
|
|
249
|
+
context: {
|
|
250
|
+
subjectId?: string | null;
|
|
251
|
+
roleKey?: string | null;
|
|
252
|
+
sessionId?: string | null;
|
|
253
|
+
now?: Date | string;
|
|
254
|
+
},
|
|
255
|
+
): boolean {
|
|
256
|
+
const normalized = normalizeMemoryScope(scope);
|
|
257
|
+
switch (normalized.type) {
|
|
258
|
+
case "workspace":
|
|
259
|
+
return true;
|
|
260
|
+
case "user":
|
|
261
|
+
return Boolean(context.subjectId) && normalized.subjectId === context.subjectId;
|
|
262
|
+
case "role":
|
|
263
|
+
return (
|
|
264
|
+
Boolean(context.roleKey) && normalized.roleKey === normalizeMemoryRoleKey(context.roleKey!)
|
|
265
|
+
);
|
|
266
|
+
case "session":
|
|
267
|
+
return Boolean(context.sessionId) && normalized.sessionId === context.sessionId;
|
|
268
|
+
case "ephemeral": {
|
|
269
|
+
if (!context.sessionId || normalized.sessionId !== context.sessionId) return false;
|
|
270
|
+
const now = context.now instanceof Date ? context.now : new Date(context.now ?? Date.now());
|
|
271
|
+
return Number.isFinite(now.getTime()) && now.getTime() < Date.parse(normalized.validUntil);
|
|
272
|
+
}
|
|
273
|
+
case "legacy":
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export function canonicalMemoryRelationship(input: {
|
|
279
|
+
sourceMemoryId: string;
|
|
280
|
+
targetMemoryId: string;
|
|
281
|
+
relationshipType: MemoryRelationshipType;
|
|
282
|
+
}): {
|
|
283
|
+
sourceMemoryId: string;
|
|
284
|
+
targetMemoryId: string;
|
|
285
|
+
relationshipType: MemoryRelationshipType;
|
|
286
|
+
} {
|
|
287
|
+
const sourceMemoryId = normalizeUuid(input.sourceMemoryId, "source memory id");
|
|
288
|
+
const targetMemoryId = normalizeUuid(input.targetMemoryId, "target memory id");
|
|
289
|
+
if (sourceMemoryId === targetMemoryId) {
|
|
290
|
+
throw new Error("a memory relationship must connect two distinct memories");
|
|
291
|
+
}
|
|
292
|
+
if (!(MEMORY_RELATIONSHIP_TYPES as readonly string[]).includes(input.relationshipType)) {
|
|
293
|
+
throw new Error(`unsupported memory relationship type: ${input.relationshipType}`);
|
|
294
|
+
}
|
|
295
|
+
if (
|
|
296
|
+
SYMMETRIC_MEMORY_RELATIONSHIPS.has(input.relationshipType) &&
|
|
297
|
+
targetMemoryId < sourceMemoryId
|
|
298
|
+
) {
|
|
299
|
+
return {
|
|
300
|
+
sourceMemoryId: targetMemoryId,
|
|
301
|
+
targetMemoryId: sourceMemoryId,
|
|
302
|
+
relationshipType: input.relationshipType,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
return { sourceMemoryId, targetMemoryId, relationshipType: input.relationshipType };
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/** Build the canonical, bounded operation plan consumed by the database lifecycle function. */
|
|
309
|
+
export function normalizeMemoryOperationPlan(input: MemoryOperationPlanInput): MemoryOperationPlan {
|
|
310
|
+
const base = {
|
|
311
|
+
operationId: normalizeUuid(input.operationId, "memory operation id"),
|
|
312
|
+
operationType: input.operationType,
|
|
313
|
+
targetMemoryId: normalizeUuid(input.targetMemoryId, "target memory id"),
|
|
314
|
+
expectedTargetVersion: normalizePositiveVersion(
|
|
315
|
+
input.expectedTargetVersion,
|
|
316
|
+
"expected target memory version",
|
|
317
|
+
),
|
|
318
|
+
} as const;
|
|
319
|
+
|
|
320
|
+
switch (input.operationType) {
|
|
321
|
+
case "reclassify":
|
|
322
|
+
return {
|
|
323
|
+
...base,
|
|
324
|
+
operationType: "reclassify",
|
|
325
|
+
scope: normalizeMemoryScope(input.scope),
|
|
326
|
+
namespace: normalizeMemoryNamespace(input.namespace),
|
|
327
|
+
labels: normalizeMemoryLabels(input.labels),
|
|
328
|
+
};
|
|
329
|
+
case "archive":
|
|
330
|
+
return { ...base, operationType: "archive" };
|
|
331
|
+
case "relationship_add":
|
|
332
|
+
case "relationship_remove": {
|
|
333
|
+
const relatedMemoryId = normalizeUuid(input.relatedMemoryId, "related memory id");
|
|
334
|
+
const expectedRelatedVersion = normalizePositiveVersion(
|
|
335
|
+
input.expectedRelatedVersion,
|
|
336
|
+
"expected related memory version",
|
|
337
|
+
);
|
|
338
|
+
const relationship = canonicalMemoryRelationship({
|
|
339
|
+
sourceMemoryId: base.targetMemoryId,
|
|
340
|
+
targetMemoryId: relatedMemoryId,
|
|
341
|
+
relationshipType: input.relationshipType,
|
|
342
|
+
});
|
|
343
|
+
const endpointsWereSwapped = relationship.sourceMemoryId !== base.targetMemoryId;
|
|
344
|
+
return {
|
|
345
|
+
...base,
|
|
346
|
+
operationType: input.operationType,
|
|
347
|
+
targetMemoryId: relationship.sourceMemoryId,
|
|
348
|
+
relatedMemoryId: relationship.targetMemoryId,
|
|
349
|
+
expectedTargetVersion: endpointsWereSwapped
|
|
350
|
+
? expectedRelatedVersion
|
|
351
|
+
: base.expectedTargetVersion,
|
|
352
|
+
expectedRelatedVersion: endpointsWereSwapped
|
|
353
|
+
? base.expectedTargetVersion
|
|
354
|
+
: expectedRelatedVersion,
|
|
355
|
+
relationshipType: relationship.relationshipType,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
case "supersede":
|
|
359
|
+
case "correct": {
|
|
360
|
+
const relatedMemoryId = normalizeUuid(input.relatedMemoryId, "replacement memory id");
|
|
361
|
+
if (relatedMemoryId === base.targetMemoryId) {
|
|
362
|
+
throw new Error("a memory cannot supersede or correct itself");
|
|
363
|
+
}
|
|
364
|
+
return {
|
|
365
|
+
...base,
|
|
366
|
+
operationType: input.operationType,
|
|
367
|
+
relatedMemoryId,
|
|
368
|
+
expectedRelatedVersion: normalizePositiveVersion(
|
|
369
|
+
input.expectedRelatedVersion,
|
|
370
|
+
"expected replacement memory version",
|
|
371
|
+
),
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/** Stable SHA-256 identity for one already-normalized operation plan. */
|
|
378
|
+
export function hashMemoryOperationPlan(plan: MemoryOperationPlan): string {
|
|
379
|
+
return createHash("sha256").update(stableJson(plan), "utf8").digest("hex");
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
export function normalizeMemoryRevertPlan(input: MemoryRevertPlanInput): MemoryRevertPlan {
|
|
383
|
+
return {
|
|
384
|
+
operationId: normalizeUuid(input.operationId, "memory revert operation id"),
|
|
385
|
+
appliedOperationId: normalizeUuid(input.appliedOperationId, "applied memory operation id"),
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/** Stable SHA-256 identity for one already-normalized revert plan. */
|
|
390
|
+
export function hashMemoryRevertPlan(plan: MemoryRevertPlan): string {
|
|
391
|
+
return createHash("sha256").update(stableJson(plan), "utf8").digest("hex");
|
|
392
|
+
}
|
|
393
|
+
|
|
35
394
|
// ---------------------------------------------------------------------------
|
|
36
395
|
// Kinds → block sections
|
|
37
396
|
// ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { sql } from "drizzle-orm";
|
|
2
|
+
import {
|
|
3
|
+
check,
|
|
4
|
+
index,
|
|
5
|
+
integer,
|
|
6
|
+
jsonb,
|
|
7
|
+
pgTable,
|
|
8
|
+
text,
|
|
9
|
+
timestamp,
|
|
10
|
+
uniqueIndex,
|
|
11
|
+
uuid,
|
|
12
|
+
} from "drizzle-orm/pg-core";
|
|
13
|
+
|
|
14
|
+
// Foreign keys, immutable-history triggers, and lifecycle-only mutation guards
|
|
15
|
+
// live in migration 0152. Keeping this leaf cycle-free lets schema.ts expose the
|
|
16
|
+
// additive memory governance tables without importing knowledgeMemories here.
|
|
17
|
+
export const knowledgeMemoryRelationships = pgTable(
|
|
18
|
+
"knowledge_memory_relationships",
|
|
19
|
+
{
|
|
20
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
21
|
+
accountId: uuid("account_id").notNull(),
|
|
22
|
+
workspaceId: uuid("workspace_id").notNull(),
|
|
23
|
+
sourceMemoryId: uuid("source_memory_id").notNull(),
|
|
24
|
+
targetMemoryId: uuid("target_memory_id").notNull(),
|
|
25
|
+
relationshipType: text("relationship_type").notNull(),
|
|
26
|
+
version: integer("version").notNull().default(1),
|
|
27
|
+
createdByEventId: uuid("created_by_event_id").notNull(),
|
|
28
|
+
removedByEventId: uuid("removed_by_event_id"),
|
|
29
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
30
|
+
removedAt: timestamp("removed_at", { withTimezone: true }),
|
|
31
|
+
},
|
|
32
|
+
(table) => ({
|
|
33
|
+
activeDirectedEdge: uniqueIndex("knowledge_memory_relationships_active_edge_uq")
|
|
34
|
+
.on(table.workspaceId, table.sourceMemoryId, table.targetMemoryId, table.relationshipType)
|
|
35
|
+
.where(sql`${table.removedByEventId} is null`),
|
|
36
|
+
activeSymmetricEdge: uniqueIndex("knowledge_memory_relationships_active_symmetric_edge_uq")
|
|
37
|
+
.on(
|
|
38
|
+
table.workspaceId,
|
|
39
|
+
table.relationshipType,
|
|
40
|
+
sql`least(${table.sourceMemoryId}, ${table.targetMemoryId})`,
|
|
41
|
+
sql`greatest(${table.sourceMemoryId}, ${table.targetMemoryId})`,
|
|
42
|
+
)
|
|
43
|
+
.where(
|
|
44
|
+
sql`${table.removedByEventId} is null and ${table.relationshipType} in ('conflicts_with', 'related_to')`,
|
|
45
|
+
),
|
|
46
|
+
source: index("knowledge_memory_relationships_source_idx").on(
|
|
47
|
+
table.workspaceId,
|
|
48
|
+
table.sourceMemoryId,
|
|
49
|
+
table.createdAt.desc(),
|
|
50
|
+
table.id,
|
|
51
|
+
),
|
|
52
|
+
target: index("knowledge_memory_relationships_target_idx").on(
|
|
53
|
+
table.workspaceId,
|
|
54
|
+
table.targetMemoryId,
|
|
55
|
+
table.createdAt.desc(),
|
|
56
|
+
table.id,
|
|
57
|
+
),
|
|
58
|
+
typeValid: check(
|
|
59
|
+
"knowledge_memory_relationships_type_chk",
|
|
60
|
+
sql`${table.relationshipType} in ('derived_from', 'supersedes', 'corrects', 'conflicts_with', 'related_to', 'depends_on', 'applies_to')`,
|
|
61
|
+
),
|
|
62
|
+
distinctEndpoints: check(
|
|
63
|
+
"knowledge_memory_relationships_distinct_chk",
|
|
64
|
+
sql`${table.sourceMemoryId} <> ${table.targetMemoryId}`,
|
|
65
|
+
),
|
|
66
|
+
versionPositive: check("knowledge_memory_relationships_version_chk", sql`${table.version} > 0`),
|
|
67
|
+
removedShape: check(
|
|
68
|
+
"knowledge_memory_relationships_removed_shape_chk",
|
|
69
|
+
sql`(${table.removedByEventId} is null and ${table.removedAt} is null) or (${table.removedByEventId} is not null and ${table.removedAt} is not null)`,
|
|
70
|
+
),
|
|
71
|
+
workspaceIdentity: uniqueIndex("knowledge_memory_relationships_workspace_id_uq").on(
|
|
72
|
+
table.workspaceId,
|
|
73
|
+
table.id,
|
|
74
|
+
),
|
|
75
|
+
}),
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
export const knowledgeMemoryLifecycleEvents = pgTable(
|
|
79
|
+
"knowledge_memory_lifecycle_events",
|
|
80
|
+
{
|
|
81
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
82
|
+
accountId: uuid("account_id").notNull(),
|
|
83
|
+
workspaceId: uuid("workspace_id").notNull(),
|
|
84
|
+
operationId: uuid("operation_id").notNull(),
|
|
85
|
+
action: text("action").notNull(),
|
|
86
|
+
operationType: text("operation_type").notNull(),
|
|
87
|
+
targetMemoryId: uuid("target_memory_id").notNull(),
|
|
88
|
+
relatedMemoryId: uuid("related_memory_id"),
|
|
89
|
+
relationshipId: uuid("relationship_id"),
|
|
90
|
+
relationshipType: text("relationship_type"),
|
|
91
|
+
actorKind: text("actor_kind").notNull(),
|
|
92
|
+
actorSubjectId: text("actor_subject_id").notNull(),
|
|
93
|
+
actorSessionId: uuid("actor_session_id"),
|
|
94
|
+
actorTurnId: uuid("actor_turn_id"),
|
|
95
|
+
actorAttemptId: uuid("actor_attempt_id"),
|
|
96
|
+
actorExecutionGeneration: integer("actor_execution_generation"),
|
|
97
|
+
planHash: text("plan_hash").notNull(),
|
|
98
|
+
beforeState: jsonb("before_state").$type<Record<string, unknown>>().notNull(),
|
|
99
|
+
afterState: jsonb("after_state").$type<Record<string, unknown>>().notNull(),
|
|
100
|
+
revertsEventId: uuid("reverts_event_id"),
|
|
101
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
102
|
+
},
|
|
103
|
+
(table) => ({
|
|
104
|
+
operationAction: uniqueIndex("knowledge_memory_lifecycle_events_operation_action_uq").on(
|
|
105
|
+
table.workspaceId,
|
|
106
|
+
table.operationId,
|
|
107
|
+
table.action,
|
|
108
|
+
),
|
|
109
|
+
oneRevert: uniqueIndex("knowledge_memory_lifecycle_events_one_revert_uq")
|
|
110
|
+
.on(table.revertsEventId)
|
|
111
|
+
.where(sql`${table.revertsEventId} is not null`),
|
|
112
|
+
targetTimeline: index("knowledge_memory_lifecycle_events_target_timeline_idx").on(
|
|
113
|
+
table.workspaceId,
|
|
114
|
+
table.targetMemoryId,
|
|
115
|
+
table.createdAt.desc(),
|
|
116
|
+
table.id,
|
|
117
|
+
),
|
|
118
|
+
actorTimeline: index("knowledge_memory_lifecycle_events_actor_timeline_idx").on(
|
|
119
|
+
table.workspaceId,
|
|
120
|
+
table.actorKind,
|
|
121
|
+
table.actorSubjectId,
|
|
122
|
+
table.createdAt.desc(),
|
|
123
|
+
table.id,
|
|
124
|
+
),
|
|
125
|
+
actionValid: check(
|
|
126
|
+
"knowledge_memory_lifecycle_events_action_chk",
|
|
127
|
+
sql`${table.action} in ('apply', 'revert')`,
|
|
128
|
+
),
|
|
129
|
+
operationValid: check(
|
|
130
|
+
"knowledge_memory_lifecycle_events_operation_chk",
|
|
131
|
+
sql`${table.operationType} in ('reclassify', 'archive', 'relationship_add', 'relationship_remove', 'supersede', 'correct')`,
|
|
132
|
+
),
|
|
133
|
+
relationshipShape: check(
|
|
134
|
+
"knowledge_memory_lifecycle_events_relationship_chk",
|
|
135
|
+
sql`(${table.operationType} in ('relationship_add', 'relationship_remove', 'supersede', 'correct') and ${table.relatedMemoryId} is not null and ${table.relationshipId} is not null and ${table.relationshipType} is not null) or (${table.operationType} in ('reclassify', 'archive') and ${table.relatedMemoryId} is null and ${table.relationshipId} is null and ${table.relationshipType} is null)`,
|
|
136
|
+
),
|
|
137
|
+
actorValid: check(
|
|
138
|
+
"knowledge_memory_lifecycle_events_actor_chk",
|
|
139
|
+
sql`${table.actorKind} in ('subject', 'service') and length(btrim(${table.actorSubjectId})) between 1 and 1024`,
|
|
140
|
+
),
|
|
141
|
+
attemptShape: check(
|
|
142
|
+
"knowledge_memory_lifecycle_events_attempt_shape_chk",
|
|
143
|
+
sql`(${table.actorSessionId} is null and ${table.actorTurnId} is null and ${table.actorAttemptId} is null and ${table.actorExecutionGeneration} is null) or (${table.actorSessionId} is not null and ${table.actorTurnId} is not null and ${table.actorAttemptId} is not null and ${table.actorExecutionGeneration} > 0)`,
|
|
144
|
+
),
|
|
145
|
+
planHashShape: check(
|
|
146
|
+
"knowledge_memory_lifecycle_events_plan_hash_chk",
|
|
147
|
+
sql`${table.planHash} ~ '^[a-f0-9]{64}$'`,
|
|
148
|
+
),
|
|
149
|
+
stateShape: check(
|
|
150
|
+
"knowledge_memory_lifecycle_events_state_chk",
|
|
151
|
+
sql`jsonb_typeof(${table.beforeState}) = 'object' and jsonb_typeof(${table.afterState}) = 'object' and not (${table.beforeState} ?| array['text', 'sourceRefs', 'source_refs', 'metadata', 'embedding']) and not (${table.afterState} ?| array['text', 'sourceRefs', 'source_refs', 'metadata', 'embedding'])`,
|
|
152
|
+
),
|
|
153
|
+
revertShape: check(
|
|
154
|
+
"knowledge_memory_lifecycle_events_revert_shape_chk",
|
|
155
|
+
sql`(${table.action} = 'apply' and ${table.revertsEventId} is null) or (${table.action} = 'revert' and ${table.revertsEventId} is not null)`,
|
|
156
|
+
),
|
|
157
|
+
workspaceIdentity: uniqueIndex("knowledge_memory_lifecycle_events_workspace_id_uq").on(
|
|
158
|
+
table.workspaceId,
|
|
159
|
+
table.id,
|
|
160
|
+
),
|
|
161
|
+
}),
|
|
162
|
+
);
|