@lucern/graph-primitives 1.0.50 → 1.0.53
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/beliefDecay.js +34 -186
- package/dist/beliefEvidenceLinks.js +32 -187
- package/dist/contradictions.js +34 -187
- package/dist/entityLifecycle.js +88 -205
- package/dist/epistemicAnswers.js +35 -187
- package/dist/epistemicBeliefs.admin.js +34 -187
- package/dist/epistemicBeliefs.backfills.js +34 -188
- package/dist/epistemicBeliefs.confidence.d.ts +1 -1
- package/dist/epistemicBeliefs.confidence.js +32 -188
- package/dist/epistemicBeliefs.core.js +37 -187
- package/dist/epistemicBeliefs.d.ts +1 -1
- package/dist/epistemicBeliefs.helpers.d.ts +1 -1
- package/dist/epistemicBeliefs.helpers.js +34 -186
- package/dist/epistemicBeliefs.internal.js +37 -187
- package/dist/epistemicBeliefs.js +37 -187
- package/dist/epistemicBeliefs.lifecycle.js +32 -188
- package/dist/epistemicBeliefs.links.js +34 -188
- package/dist/epistemicContracts.evaluators.js +34 -188
- package/dist/epistemicContracts.handlers.js +34 -188
- package/dist/epistemicContracts.js +34 -188
- package/dist/epistemicEdges.d.ts +1 -1
- package/dist/epistemicEdges.helpers.d.ts +1 -1
- package/dist/epistemicEdges.js +32 -187
- package/dist/epistemicEdges.mutations.js +34 -186
- package/dist/epistemicEdges.queries.js +35 -188
- package/dist/epistemicEdges.types.d.ts +1 -1
- package/dist/epistemicEvidence.d.ts +1 -1
- package/dist/epistemicEvidence.js +37 -187
- package/dist/epistemicEvidenceHelpers.d.ts +1 -1
- package/dist/epistemicEvidenceHelpers.js +34 -186
- package/dist/epistemicEvidenceMutations.js +37 -187
- package/dist/epistemicEvidenceQueries.js +34 -186
- package/dist/epistemicHelpers.js +4 -1
- package/dist/epistemicInsert.js +4 -1
- package/dist/epistemicNodeCreation.js +4 -1
- package/dist/epistemicNodes.helpers.d.ts +1 -1
- package/dist/epistemicNodes.internal.js +35 -188
- package/dist/epistemicNodes.js +37 -188
- package/dist/epistemicNodes.mutations.js +35 -188
- package/dist/epistemicNodes.queries.js +35 -188
- package/dist/epistemicQuestions.conviction.js +34 -186
- package/dist/epistemicQuestions.create.js +37 -187
- package/dist/epistemicQuestions.d.ts +1 -1
- package/dist/epistemicQuestions.evidence.js +37 -187
- package/dist/epistemicQuestions.helpers.d.ts +1 -1
- package/dist/epistemicQuestions.helpers.js +34 -186
- package/dist/epistemicQuestions.js +37 -187
- package/dist/epistemicQuestions.lifecycle.js +34 -186
- package/dist/epistemicQuestions.queries.js +34 -186
- package/dist/epistemicQuestions.sprint.js +35 -188
- package/dist/epistemicQuestions.tail.js +37 -187
- package/dist/epistemicSources.js +35 -188
- package/dist/index.d.ts +1 -1
- package/dist/index.js +98 -213
- package/dist/proof-attestation.json +1 -1
- package/dist/questionEvidenceLinks.js +34 -187
- package/dist/scopeResolverCompat.d.ts +1 -1
- package/dist/scopeResolverCompat.js +32 -193
- package/dist/topicOntologyResolver.d.ts +3 -3
- package/dist/topicOntologyResolver.js +57 -18
- package/dist/{topicScope-DJVa0mLa.d.ts → topicScope-CL1IVOmv.d.ts} +2 -2
- package/dist/topicScope.d.ts +1 -1
- package/dist/topicScope.js +32 -193
- package/dist/workflowBridge.js +32 -193
- package/dist/workspaceIsolation.d.ts +1 -1
- package/dist/workspaceIsolation.js +32 -193
- package/package.json +4 -4
|
@@ -2,7 +2,7 @@ import { permissiveReturn } from '@lucern/contracts/schema-helpers/validators';
|
|
|
2
2
|
import { v } from 'convex/values';
|
|
3
3
|
import { unsafeConvexAnyApi } from '@lucern/contracts/convex/unsafeAnyApi';
|
|
4
4
|
import { componentsGeneric, internalMutationGeneric, internalQueryGeneric } from 'convex/server';
|
|
5
|
-
import { assertUuidV7Identity } from '@lucern/contracts/ids';
|
|
5
|
+
import { assertUuidV7Identity, assertUuidV7Reference, isUuidV7 } from '@lucern/contracts/ids';
|
|
6
6
|
import '@lucern/contracts/manifests/edge-policy-manifest';
|
|
7
7
|
import '@lucern/contracts/manifests/edge-policy-manifest.data';
|
|
8
8
|
|
|
@@ -10,7 +10,6 @@ import '@lucern/contracts/manifests/edge-policy-manifest.data';
|
|
|
10
10
|
var unsafeApi = unsafeConvexAnyApi(
|
|
11
11
|
"graph-primitives top-level module bundle lacks a committed Convex _generated/api surface"
|
|
12
12
|
);
|
|
13
|
-
var api = unsafeApi;
|
|
14
13
|
componentsGeneric();
|
|
15
14
|
var internal = unsafeApi;
|
|
16
15
|
var internalMutation = internalMutationGeneric;
|
|
@@ -29,6 +28,9 @@ function debugGraphPrimitiveFallback(message, context) {
|
|
|
29
28
|
}
|
|
30
29
|
function insertEpistemicNode(ctx, doc) {
|
|
31
30
|
assertUuidV7Identity("epistemicNodes", doc.globalId);
|
|
31
|
+
if (doc.topicId !== void 0 && doc.topicId !== null) {
|
|
32
|
+
assertUuidV7Reference("epistemicNodes.topicId", doc.topicId);
|
|
33
|
+
}
|
|
32
34
|
return ctx.db.insert("epistemicNodes", doc);
|
|
33
35
|
}
|
|
34
36
|
|
|
@@ -241,16 +243,36 @@ async function resolveTopicNodeScopeOrNull(ctx, ref) {
|
|
|
241
243
|
if (!node) {
|
|
242
244
|
return null;
|
|
243
245
|
}
|
|
244
|
-
const scopeKey =
|
|
246
|
+
const scopeKey = canonicalTopicGlobalId(node);
|
|
245
247
|
if (!scopeKey) {
|
|
246
|
-
|
|
248
|
+
throw new Error(
|
|
249
|
+
`topic.uuidv7_identity_required: topic node ${ref} is missing a UUIDv7 globalId.`
|
|
250
|
+
);
|
|
247
251
|
}
|
|
252
|
+
const metadata = node.metadata ?? {};
|
|
248
253
|
return {
|
|
249
254
|
topicId: scopeKey,
|
|
250
255
|
projectId: asMappedProjectId(node),
|
|
251
|
-
source: "topic_node"
|
|
256
|
+
source: "topic_node",
|
|
257
|
+
tenantId: normalizeScopeValue(node.tenantId) ?? normalizeScopeValue(metadata.tenantId),
|
|
258
|
+
workspaceId: normalizeScopeValue(node.workspaceId) ?? normalizeScopeValue(metadata.workspaceId)
|
|
252
259
|
};
|
|
253
260
|
}
|
|
261
|
+
function canonicalTopicGlobalId(node) {
|
|
262
|
+
const globalId = normalizeScopeValue(node.globalId);
|
|
263
|
+
if (globalId && isUuidV7(globalId)) {
|
|
264
|
+
return globalId;
|
|
265
|
+
}
|
|
266
|
+
const topicId = normalizeScopeValue(node.topicId);
|
|
267
|
+
return topicId && isUuidV7(topicId) ? topicId : null;
|
|
268
|
+
}
|
|
269
|
+
function requireUuidV7TopicScope(field, value) {
|
|
270
|
+
if (!isUuidV7(value)) {
|
|
271
|
+
throw new Error(
|
|
272
|
+
`topic.uuidv7_required: ${field} must be a UUIDv7 public topic identifier.`
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
254
276
|
function asMappedProjectId(topic) {
|
|
255
277
|
if (!topic) {
|
|
256
278
|
return;
|
|
@@ -272,200 +294,25 @@ function normalizeScopeValue(value) {
|
|
|
272
294
|
const normalized = value.trim();
|
|
273
295
|
return normalized.length > 0 ? normalized : void 0;
|
|
274
296
|
}
|
|
275
|
-
function pickPrimaryTopic(candidates) {
|
|
276
|
-
return [...candidates].sort((a, b) => {
|
|
277
|
-
const depthA = a.depth ?? 9999;
|
|
278
|
-
const depthB = b.depth ?? 9999;
|
|
279
|
-
if (depthA !== depthB) {
|
|
280
|
-
return depthA - depthB;
|
|
281
|
-
}
|
|
282
|
-
const createdA = a.createdAt ?? Number.MAX_SAFE_INTEGER;
|
|
283
|
-
const createdB = b.createdAt ?? Number.MAX_SAFE_INTEGER;
|
|
284
|
-
if (createdA !== createdB) {
|
|
285
|
-
return createdA - createdB;
|
|
286
|
-
}
|
|
287
|
-
return String(a.name || "").localeCompare(String(b.name || ""));
|
|
288
|
-
})[0];
|
|
289
|
-
}
|
|
290
|
-
async function findTopicsByScopeAlias(ctx, scopeId) {
|
|
291
|
-
const query = ctx.db.query("topics");
|
|
292
|
-
try {
|
|
293
|
-
return await query.withIndex(
|
|
294
|
-
"by_graph_scope_project",
|
|
295
|
-
(q) => q.eq(LEGACY_SCOPE_FIELD, scopeId)
|
|
296
|
-
).collect();
|
|
297
|
-
} catch (error) {
|
|
298
|
-
debugGraphPrimitiveFallback(
|
|
299
|
-
"[topicScope] Failed to resolve scope alias via index",
|
|
300
|
-
{
|
|
301
|
-
error,
|
|
302
|
-
scopeId
|
|
303
|
-
}
|
|
304
|
-
);
|
|
305
|
-
const topics = await query.collect();
|
|
306
|
-
return topics.filter((topic) => {
|
|
307
|
-
const normalizedGlobalId = normalizeScopeValue(topic.globalId);
|
|
308
|
-
const mappedProjectId = asMappedProjectId(topic);
|
|
309
|
-
return String(topic._id) === scopeId || normalizedGlobalId === scopeId || mappedProjectId === scopeId;
|
|
310
|
-
});
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
async function tryResolveHostTopicById(ctx, topicId) {
|
|
314
|
-
if (typeof ctx.runQuery !== "function") {
|
|
315
|
-
return null;
|
|
316
|
-
}
|
|
317
|
-
try {
|
|
318
|
-
return await ctx.runQuery(api.topics.get, {
|
|
319
|
-
id: topicId
|
|
320
|
-
}) ?? null;
|
|
321
|
-
} catch (error) {
|
|
322
|
-
debugGraphPrimitiveFallback(
|
|
323
|
-
"[topicScope] Failed to resolve topic by host query",
|
|
324
|
-
{
|
|
325
|
-
error,
|
|
326
|
-
topicId
|
|
327
|
-
}
|
|
328
|
-
);
|
|
329
|
-
return null;
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
async function tryResolveHostTopicByLegacyScope(ctx, legacyScopeId) {
|
|
333
|
-
if (typeof ctx.runQuery !== "function") {
|
|
334
|
-
return null;
|
|
335
|
-
}
|
|
336
|
-
try {
|
|
337
|
-
return await ctx.runQuery(api.topics.getByLegacyScopeId, {
|
|
338
|
-
projectId: legacyScopeId
|
|
339
|
-
}) ?? null;
|
|
340
|
-
} catch (error) {
|
|
341
|
-
debugGraphPrimitiveFallback(
|
|
342
|
-
"[topicScope] Failed to resolve topic by legacy scope",
|
|
343
|
-
{
|
|
344
|
-
error,
|
|
345
|
-
legacyScopeId
|
|
346
|
-
}
|
|
347
|
-
);
|
|
348
|
-
return null;
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
async function resolveInheritedWorkspaceScope(ctx, topic) {
|
|
352
|
-
const MAX_DEPTH = 10;
|
|
353
|
-
let tenantId = normalizeScopeValue(topic.tenantId);
|
|
354
|
-
let workspaceId = normalizeScopeValue(topic.workspaceId);
|
|
355
|
-
if (tenantId && workspaceId) {
|
|
356
|
-
return { tenantId, workspaceId };
|
|
357
|
-
}
|
|
358
|
-
let current = topic;
|
|
359
|
-
for (let i = 0; i < MAX_DEPTH && current?.parentTopicId; i++) {
|
|
360
|
-
current = await ctx.db.get(current.parentTopicId);
|
|
361
|
-
if (!current) {
|
|
362
|
-
break;
|
|
363
|
-
}
|
|
364
|
-
if (!tenantId) {
|
|
365
|
-
tenantId = normalizeScopeValue(current.tenantId);
|
|
366
|
-
}
|
|
367
|
-
if (!workspaceId) {
|
|
368
|
-
workspaceId = normalizeScopeValue(current.workspaceId);
|
|
369
|
-
}
|
|
370
|
-
if (tenantId && workspaceId) {
|
|
371
|
-
break;
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
return { tenantId, workspaceId };
|
|
375
|
-
}
|
|
376
297
|
async function resolveTopicProjectScope(ctx, args) {
|
|
377
298
|
if (args.topicId) {
|
|
378
299
|
return await resolveScopeFromTopicId(ctx, args.topicId);
|
|
379
300
|
}
|
|
380
301
|
if (args.projectId) {
|
|
381
|
-
|
|
302
|
+
throw new Error(
|
|
303
|
+
"topic.uuidv7_required: projectId scope aliases are retired; pass topicId as the UUIDv7 topic globalId."
|
|
304
|
+
);
|
|
382
305
|
}
|
|
383
|
-
throw new Error(
|
|
384
|
-
"Missing scope: provide topicId (preferred) or legacy projectId alias."
|
|
385
|
-
);
|
|
306
|
+
throw new Error("topic.uuidv7_required: Missing scope: provide topicId.");
|
|
386
307
|
}
|
|
387
308
|
async function resolveScopeFromTopicId(ctx, topicId) {
|
|
388
|
-
const
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
}
|
|
392
|
-
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, String(topicId));
|
|
309
|
+
const topicGlobalId = String(topicId);
|
|
310
|
+
requireUuidV7TopicScope("topicId", topicGlobalId);
|
|
311
|
+
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, topicGlobalId);
|
|
393
312
|
if (nodeScope) {
|
|
394
313
|
return nodeScope;
|
|
395
314
|
}
|
|
396
|
-
throw new Error(`Topic not found: ${
|
|
397
|
-
}
|
|
398
|
-
async function resolveTopicDocFromTopicId(ctx, topicId) {
|
|
399
|
-
const direct = await tryReadTopicDoc(ctx, topicId, {
|
|
400
|
-
failureLog: "[topicScope] Failed to load topic by direct id",
|
|
401
|
-
idLogKey: "topicId"
|
|
402
|
-
});
|
|
403
|
-
if (direct) {
|
|
404
|
-
return direct;
|
|
405
|
-
}
|
|
406
|
-
const hostTopic = await tryResolveHostTopicById(ctx, String(topicId));
|
|
407
|
-
if (hostTopic) {
|
|
408
|
-
return hostTopic;
|
|
409
|
-
}
|
|
410
|
-
return pickPrimaryTopic(await findTopicsByScopeAlias(ctx, String(topicId))) ?? null;
|
|
411
|
-
}
|
|
412
|
-
async function resolveScopeFromLegacyProjectId(ctx, legacyProjectId) {
|
|
413
|
-
const directTopic = await resolveDirectLegacyProjectTopic(
|
|
414
|
-
ctx,
|
|
415
|
-
legacyProjectId
|
|
416
|
-
);
|
|
417
|
-
if (directTopic) {
|
|
418
|
-
return await buildTopicScope(ctx, directTopic, "topic_inferred", {
|
|
419
|
-
fallbackProjectId: legacyProjectId
|
|
420
|
-
});
|
|
421
|
-
}
|
|
422
|
-
const primary = pickPrimaryTopic(
|
|
423
|
-
await findTopicsByScopeAlias(ctx, legacyProjectId)
|
|
424
|
-
);
|
|
425
|
-
if (primary) {
|
|
426
|
-
return await buildTopicScope(ctx, primary, "project_mapped_topic", {
|
|
427
|
-
fallbackProjectId: legacyProjectId
|
|
428
|
-
});
|
|
429
|
-
}
|
|
430
|
-
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, legacyProjectId);
|
|
431
|
-
if (nodeScope) {
|
|
432
|
-
return {
|
|
433
|
-
...nodeScope,
|
|
434
|
-
projectId: nodeScope.projectId ?? legacyProjectId
|
|
435
|
-
};
|
|
436
|
-
}
|
|
437
|
-
throw new Error(
|
|
438
|
-
`Legacy project scope ${legacyProjectId} has no mapped topic.`
|
|
439
|
-
);
|
|
440
|
-
}
|
|
441
|
-
async function resolveDirectLegacyProjectTopic(ctx, legacyProjectId) {
|
|
442
|
-
const directTopic = await tryReadTopicDoc(ctx, legacyProjectId, {
|
|
443
|
-
failureLog: "[topicScope] Failed to load direct project topic",
|
|
444
|
-
idLogKey: "projectId"
|
|
445
|
-
});
|
|
446
|
-
return directTopic ?? tryResolveHostTopicByLegacyScope(ctx, legacyProjectId);
|
|
447
|
-
}
|
|
448
|
-
async function tryReadTopicDoc(ctx, id, log) {
|
|
449
|
-
try {
|
|
450
|
-
return await ctx.db.get(id);
|
|
451
|
-
} catch (error) {
|
|
452
|
-
debugGraphPrimitiveFallback(log.failureLog, {
|
|
453
|
-
error,
|
|
454
|
-
[log.idLogKey]: id
|
|
455
|
-
});
|
|
456
|
-
return null;
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
async function buildTopicScope(ctx, topic, source, options = {}) {
|
|
460
|
-
const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
|
|
461
|
-
const mapped = asMappedProjectId(topic);
|
|
462
|
-
return {
|
|
463
|
-
topicId: topic._id,
|
|
464
|
-
...mapped || options.fallbackProjectId ? { projectId: mapped ?? options.fallbackProjectId } : {},
|
|
465
|
-
tenantId: inherited.tenantId,
|
|
466
|
-
workspaceId: inherited.workspaceId,
|
|
467
|
-
source
|
|
468
|
-
};
|
|
315
|
+
throw new Error(`Topic not found: ${topicGlobalId}`);
|
|
469
316
|
}
|
|
470
317
|
var optionalScopeArgs = {
|
|
471
318
|
projectId: v.optional(v.string()),
|
package/dist/epistemicNodes.js
CHANGED
|
@@ -3,7 +3,7 @@ import { permissiveReturn } from '@lucern/contracts/schema-helpers/validators';
|
|
|
3
3
|
import { v } from 'convex/values';
|
|
4
4
|
import { unsafeConvexAnyApi } from '@lucern/contracts/convex/unsafeAnyApi';
|
|
5
5
|
import { componentsGeneric, queryGeneric, internalMutationGeneric, internalQueryGeneric, mutationGeneric } from 'convex/server';
|
|
6
|
-
import { assertUuidV7Identity } from '@lucern/contracts/ids';
|
|
6
|
+
import { assertUuidV7Identity, assertUuidV7Reference, isUuidV7 } from '@lucern/contracts/ids';
|
|
7
7
|
import '@lucern/contracts/manifests/edge-policy-manifest';
|
|
8
8
|
import '@lucern/contracts/manifests/edge-policy-manifest.data';
|
|
9
9
|
import { isNodeType, getLayerForNodeType } from '@lucern/contracts/schema-helpers/spine/tables/epistemicNodes';
|
|
@@ -12,7 +12,6 @@ import { isNodeType, getLayerForNodeType } from '@lucern/contracts/schema-helper
|
|
|
12
12
|
var unsafeApi = unsafeConvexAnyApi(
|
|
13
13
|
"graph-primitives top-level module bundle lacks a committed Convex _generated/api surface"
|
|
14
14
|
);
|
|
15
|
-
var api = unsafeApi;
|
|
16
15
|
componentsGeneric();
|
|
17
16
|
var internal = unsafeApi;
|
|
18
17
|
var internalMutation = internalMutationGeneric;
|
|
@@ -199,6 +198,8 @@ var verificationStatusValidator = v.union(
|
|
|
199
198
|
v.literal("contradicted"),
|
|
200
199
|
v.literal("outdated")
|
|
201
200
|
);
|
|
201
|
+
|
|
202
|
+
// src/topicScope.ts
|
|
202
203
|
var LEGACY_SCOPE_FIELD = "graphScopeProjectId";
|
|
203
204
|
async function resolveTopicNodeScopeOrNull(ctx, ref) {
|
|
204
205
|
if (!ctx?.db || typeof ctx.db.query !== "function") {
|
|
@@ -219,16 +220,36 @@ async function resolveTopicNodeScopeOrNull(ctx, ref) {
|
|
|
219
220
|
if (!node) {
|
|
220
221
|
return null;
|
|
221
222
|
}
|
|
222
|
-
const scopeKey =
|
|
223
|
+
const scopeKey = canonicalTopicGlobalId(node);
|
|
223
224
|
if (!scopeKey) {
|
|
224
|
-
|
|
225
|
+
throw new Error(
|
|
226
|
+
`topic.uuidv7_identity_required: topic node ${ref} is missing a UUIDv7 globalId.`
|
|
227
|
+
);
|
|
225
228
|
}
|
|
229
|
+
const metadata = node.metadata ?? {};
|
|
226
230
|
return {
|
|
227
231
|
topicId: scopeKey,
|
|
228
232
|
projectId: asMappedProjectId(node),
|
|
229
|
-
source: "topic_node"
|
|
233
|
+
source: "topic_node",
|
|
234
|
+
tenantId: normalizeScopeValue(node.tenantId) ?? normalizeScopeValue(metadata.tenantId),
|
|
235
|
+
workspaceId: normalizeScopeValue(node.workspaceId) ?? normalizeScopeValue(metadata.workspaceId)
|
|
230
236
|
};
|
|
231
237
|
}
|
|
238
|
+
function canonicalTopicGlobalId(node) {
|
|
239
|
+
const globalId = normalizeScopeValue(node.globalId);
|
|
240
|
+
if (globalId && isUuidV7(globalId)) {
|
|
241
|
+
return globalId;
|
|
242
|
+
}
|
|
243
|
+
const topicId = normalizeScopeValue(node.topicId);
|
|
244
|
+
return topicId && isUuidV7(topicId) ? topicId : null;
|
|
245
|
+
}
|
|
246
|
+
function requireUuidV7TopicScope(field, value) {
|
|
247
|
+
if (!isUuidV7(value)) {
|
|
248
|
+
throw new Error(
|
|
249
|
+
`topic.uuidv7_required: ${field} must be a UUIDv7 public topic identifier.`
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
232
253
|
function asMappedProjectId(topic) {
|
|
233
254
|
if (!topic) {
|
|
234
255
|
return;
|
|
@@ -250,200 +271,25 @@ function normalizeScopeValue(value) {
|
|
|
250
271
|
const normalized = value.trim();
|
|
251
272
|
return normalized.length > 0 ? normalized : void 0;
|
|
252
273
|
}
|
|
253
|
-
function pickPrimaryTopic(candidates) {
|
|
254
|
-
return [...candidates].sort((a, b) => {
|
|
255
|
-
const depthA = a.depth ?? 9999;
|
|
256
|
-
const depthB = b.depth ?? 9999;
|
|
257
|
-
if (depthA !== depthB) {
|
|
258
|
-
return depthA - depthB;
|
|
259
|
-
}
|
|
260
|
-
const createdA = a.createdAt ?? Number.MAX_SAFE_INTEGER;
|
|
261
|
-
const createdB = b.createdAt ?? Number.MAX_SAFE_INTEGER;
|
|
262
|
-
if (createdA !== createdB) {
|
|
263
|
-
return createdA - createdB;
|
|
264
|
-
}
|
|
265
|
-
return String(a.name || "").localeCompare(String(b.name || ""));
|
|
266
|
-
})[0];
|
|
267
|
-
}
|
|
268
|
-
async function findTopicsByScopeAlias(ctx, scopeId) {
|
|
269
|
-
const query2 = ctx.db.query("topics");
|
|
270
|
-
try {
|
|
271
|
-
return await query2.withIndex(
|
|
272
|
-
"by_graph_scope_project",
|
|
273
|
-
(q) => q.eq(LEGACY_SCOPE_FIELD, scopeId)
|
|
274
|
-
).collect();
|
|
275
|
-
} catch (error) {
|
|
276
|
-
debugGraphPrimitiveFallback(
|
|
277
|
-
"[topicScope] Failed to resolve scope alias via index",
|
|
278
|
-
{
|
|
279
|
-
error,
|
|
280
|
-
scopeId
|
|
281
|
-
}
|
|
282
|
-
);
|
|
283
|
-
const topics = await query2.collect();
|
|
284
|
-
return topics.filter((topic) => {
|
|
285
|
-
const normalizedGlobalId = normalizeScopeValue(topic.globalId);
|
|
286
|
-
const mappedProjectId = asMappedProjectId(topic);
|
|
287
|
-
return String(topic._id) === scopeId || normalizedGlobalId === scopeId || mappedProjectId === scopeId;
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
async function tryResolveHostTopicById(ctx, topicId) {
|
|
292
|
-
if (typeof ctx.runQuery !== "function") {
|
|
293
|
-
return null;
|
|
294
|
-
}
|
|
295
|
-
try {
|
|
296
|
-
return await ctx.runQuery(api.topics.get, {
|
|
297
|
-
id: topicId
|
|
298
|
-
}) ?? null;
|
|
299
|
-
} catch (error) {
|
|
300
|
-
debugGraphPrimitiveFallback(
|
|
301
|
-
"[topicScope] Failed to resolve topic by host query",
|
|
302
|
-
{
|
|
303
|
-
error,
|
|
304
|
-
topicId
|
|
305
|
-
}
|
|
306
|
-
);
|
|
307
|
-
return null;
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
async function tryResolveHostTopicByLegacyScope(ctx, legacyScopeId) {
|
|
311
|
-
if (typeof ctx.runQuery !== "function") {
|
|
312
|
-
return null;
|
|
313
|
-
}
|
|
314
|
-
try {
|
|
315
|
-
return await ctx.runQuery(api.topics.getByLegacyScopeId, {
|
|
316
|
-
projectId: legacyScopeId
|
|
317
|
-
}) ?? null;
|
|
318
|
-
} catch (error) {
|
|
319
|
-
debugGraphPrimitiveFallback(
|
|
320
|
-
"[topicScope] Failed to resolve topic by legacy scope",
|
|
321
|
-
{
|
|
322
|
-
error,
|
|
323
|
-
legacyScopeId
|
|
324
|
-
}
|
|
325
|
-
);
|
|
326
|
-
return null;
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
async function resolveInheritedWorkspaceScope(ctx, topic) {
|
|
330
|
-
const MAX_DEPTH = 10;
|
|
331
|
-
let tenantId = normalizeScopeValue(topic.tenantId);
|
|
332
|
-
let workspaceId = normalizeScopeValue(topic.workspaceId);
|
|
333
|
-
if (tenantId && workspaceId) {
|
|
334
|
-
return { tenantId, workspaceId };
|
|
335
|
-
}
|
|
336
|
-
let current = topic;
|
|
337
|
-
for (let i = 0; i < MAX_DEPTH && current?.parentTopicId; i++) {
|
|
338
|
-
current = await ctx.db.get(current.parentTopicId);
|
|
339
|
-
if (!current) {
|
|
340
|
-
break;
|
|
341
|
-
}
|
|
342
|
-
if (!tenantId) {
|
|
343
|
-
tenantId = normalizeScopeValue(current.tenantId);
|
|
344
|
-
}
|
|
345
|
-
if (!workspaceId) {
|
|
346
|
-
workspaceId = normalizeScopeValue(current.workspaceId);
|
|
347
|
-
}
|
|
348
|
-
if (tenantId && workspaceId) {
|
|
349
|
-
break;
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
return { tenantId, workspaceId };
|
|
353
|
-
}
|
|
354
274
|
async function resolveTopicProjectScope(ctx, args) {
|
|
355
275
|
if (args.topicId) {
|
|
356
276
|
return await resolveScopeFromTopicId(ctx, args.topicId);
|
|
357
277
|
}
|
|
358
278
|
if (args.projectId) {
|
|
359
|
-
|
|
279
|
+
throw new Error(
|
|
280
|
+
"topic.uuidv7_required: projectId scope aliases are retired; pass topicId as the UUIDv7 topic globalId."
|
|
281
|
+
);
|
|
360
282
|
}
|
|
361
|
-
throw new Error(
|
|
362
|
-
"Missing scope: provide topicId (preferred) or legacy projectId alias."
|
|
363
|
-
);
|
|
283
|
+
throw new Error("topic.uuidv7_required: Missing scope: provide topicId.");
|
|
364
284
|
}
|
|
365
285
|
async function resolveScopeFromTopicId(ctx, topicId) {
|
|
366
|
-
const
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
}
|
|
370
|
-
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, String(topicId));
|
|
286
|
+
const topicGlobalId = String(topicId);
|
|
287
|
+
requireUuidV7TopicScope("topicId", topicGlobalId);
|
|
288
|
+
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, topicGlobalId);
|
|
371
289
|
if (nodeScope) {
|
|
372
290
|
return nodeScope;
|
|
373
291
|
}
|
|
374
|
-
throw new Error(`Topic not found: ${
|
|
375
|
-
}
|
|
376
|
-
async function resolveTopicDocFromTopicId(ctx, topicId) {
|
|
377
|
-
const direct = await tryReadTopicDoc(ctx, topicId, {
|
|
378
|
-
failureLog: "[topicScope] Failed to load topic by direct id",
|
|
379
|
-
idLogKey: "topicId"
|
|
380
|
-
});
|
|
381
|
-
if (direct) {
|
|
382
|
-
return direct;
|
|
383
|
-
}
|
|
384
|
-
const hostTopic = await tryResolveHostTopicById(ctx, String(topicId));
|
|
385
|
-
if (hostTopic) {
|
|
386
|
-
return hostTopic;
|
|
387
|
-
}
|
|
388
|
-
return pickPrimaryTopic(await findTopicsByScopeAlias(ctx, String(topicId))) ?? null;
|
|
389
|
-
}
|
|
390
|
-
async function resolveScopeFromLegacyProjectId(ctx, legacyProjectId) {
|
|
391
|
-
const directTopic = await resolveDirectLegacyProjectTopic(
|
|
392
|
-
ctx,
|
|
393
|
-
legacyProjectId
|
|
394
|
-
);
|
|
395
|
-
if (directTopic) {
|
|
396
|
-
return await buildTopicScope(ctx, directTopic, "topic_inferred", {
|
|
397
|
-
fallbackProjectId: legacyProjectId
|
|
398
|
-
});
|
|
399
|
-
}
|
|
400
|
-
const primary = pickPrimaryTopic(
|
|
401
|
-
await findTopicsByScopeAlias(ctx, legacyProjectId)
|
|
402
|
-
);
|
|
403
|
-
if (primary) {
|
|
404
|
-
return await buildTopicScope(ctx, primary, "project_mapped_topic", {
|
|
405
|
-
fallbackProjectId: legacyProjectId
|
|
406
|
-
});
|
|
407
|
-
}
|
|
408
|
-
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, legacyProjectId);
|
|
409
|
-
if (nodeScope) {
|
|
410
|
-
return {
|
|
411
|
-
...nodeScope,
|
|
412
|
-
projectId: nodeScope.projectId ?? legacyProjectId
|
|
413
|
-
};
|
|
414
|
-
}
|
|
415
|
-
throw new Error(
|
|
416
|
-
`Legacy project scope ${legacyProjectId} has no mapped topic.`
|
|
417
|
-
);
|
|
418
|
-
}
|
|
419
|
-
async function resolveDirectLegacyProjectTopic(ctx, legacyProjectId) {
|
|
420
|
-
const directTopic = await tryReadTopicDoc(ctx, legacyProjectId, {
|
|
421
|
-
failureLog: "[topicScope] Failed to load direct project topic",
|
|
422
|
-
idLogKey: "projectId"
|
|
423
|
-
});
|
|
424
|
-
return directTopic ?? tryResolveHostTopicByLegacyScope(ctx, legacyProjectId);
|
|
425
|
-
}
|
|
426
|
-
async function tryReadTopicDoc(ctx, id, log) {
|
|
427
|
-
try {
|
|
428
|
-
return await ctx.db.get(id);
|
|
429
|
-
} catch (error) {
|
|
430
|
-
debugGraphPrimitiveFallback(log.failureLog, {
|
|
431
|
-
error,
|
|
432
|
-
[log.idLogKey]: id
|
|
433
|
-
});
|
|
434
|
-
return null;
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
async function buildTopicScope(ctx, topic, source, options = {}) {
|
|
438
|
-
const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
|
|
439
|
-
const mapped = asMappedProjectId(topic);
|
|
440
|
-
return {
|
|
441
|
-
topicId: topic._id,
|
|
442
|
-
...mapped || options.fallbackProjectId ? { projectId: mapped ?? options.fallbackProjectId } : {},
|
|
443
|
-
tenantId: inherited.tenantId,
|
|
444
|
-
workspaceId: inherited.workspaceId,
|
|
445
|
-
source
|
|
446
|
-
};
|
|
292
|
+
throw new Error(`Topic not found: ${topicGlobalId}`);
|
|
447
293
|
}
|
|
448
294
|
var optionalScopeArgs = {
|
|
449
295
|
projectId: v.optional(v.string()),
|
|
@@ -687,6 +533,9 @@ var search = query({
|
|
|
687
533
|
});
|
|
688
534
|
function insertEpistemicNode(ctx, doc) {
|
|
689
535
|
assertUuidV7Identity("epistemicNodes", doc.globalId);
|
|
536
|
+
if (doc.topicId !== void 0 && doc.topicId !== null) {
|
|
537
|
+
assertUuidV7Reference("epistemicNodes.topicId", doc.topicId);
|
|
538
|
+
}
|
|
690
539
|
return ctx.db.insert("epistemicNodes", doc);
|
|
691
540
|
}
|
|
692
541
|
|