@lucern/graph-primitives 1.0.48 → 1.0.52
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 +2 -2
- 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
|
@@ -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, 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 mutation = mutationGeneric;
|
|
@@ -30,6 +29,9 @@ function debugGraphPrimitiveFallback(message, context) {
|
|
|
30
29
|
}
|
|
31
30
|
function insertEpistemicNode(ctx, doc) {
|
|
32
31
|
assertUuidV7Identity("epistemicNodes", doc.globalId);
|
|
32
|
+
if (doc.topicId !== void 0 && doc.topicId !== null) {
|
|
33
|
+
assertUuidV7Reference("epistemicNodes.topicId", doc.topicId);
|
|
34
|
+
}
|
|
33
35
|
return ctx.db.insert("epistemicNodes", doc);
|
|
34
36
|
}
|
|
35
37
|
|
|
@@ -400,16 +402,36 @@ async function resolveTopicNodeScopeOrNull(ctx, ref) {
|
|
|
400
402
|
if (!node) {
|
|
401
403
|
return null;
|
|
402
404
|
}
|
|
403
|
-
const scopeKey =
|
|
405
|
+
const scopeKey = canonicalTopicGlobalId(node);
|
|
404
406
|
if (!scopeKey) {
|
|
405
|
-
|
|
407
|
+
throw new Error(
|
|
408
|
+
`topic.uuidv7_identity_required: topic node ${ref} is missing a UUIDv7 globalId.`
|
|
409
|
+
);
|
|
406
410
|
}
|
|
411
|
+
const metadata = node.metadata ?? {};
|
|
407
412
|
return {
|
|
408
413
|
topicId: scopeKey,
|
|
409
414
|
projectId: asMappedProjectId(node),
|
|
410
|
-
source: "topic_node"
|
|
415
|
+
source: "topic_node",
|
|
416
|
+
tenantId: normalizeScopeValue(node.tenantId) ?? normalizeScopeValue(metadata.tenantId),
|
|
417
|
+
workspaceId: normalizeScopeValue(node.workspaceId) ?? normalizeScopeValue(metadata.workspaceId)
|
|
411
418
|
};
|
|
412
419
|
}
|
|
420
|
+
function canonicalTopicGlobalId(node) {
|
|
421
|
+
const globalId = normalizeScopeValue(node.globalId);
|
|
422
|
+
if (globalId && isUuidV7(globalId)) {
|
|
423
|
+
return globalId;
|
|
424
|
+
}
|
|
425
|
+
const topicId = normalizeScopeValue(node.topicId);
|
|
426
|
+
return topicId && isUuidV7(topicId) ? topicId : null;
|
|
427
|
+
}
|
|
428
|
+
function requireUuidV7TopicScope(field, value) {
|
|
429
|
+
if (!isUuidV7(value)) {
|
|
430
|
+
throw new Error(
|
|
431
|
+
`topic.uuidv7_required: ${field} must be a UUIDv7 public topic identifier.`
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
413
435
|
function asMappedProjectId(topic) {
|
|
414
436
|
if (!topic) {
|
|
415
437
|
return;
|
|
@@ -431,200 +453,25 @@ function normalizeScopeValue(value) {
|
|
|
431
453
|
const normalized = value.trim();
|
|
432
454
|
return normalized.length > 0 ? normalized : void 0;
|
|
433
455
|
}
|
|
434
|
-
function pickPrimaryTopic(candidates) {
|
|
435
|
-
return [...candidates].sort((a, b) => {
|
|
436
|
-
const depthA = a.depth ?? 9999;
|
|
437
|
-
const depthB = b.depth ?? 9999;
|
|
438
|
-
if (depthA !== depthB) {
|
|
439
|
-
return depthA - depthB;
|
|
440
|
-
}
|
|
441
|
-
const createdA = a.createdAt ?? Number.MAX_SAFE_INTEGER;
|
|
442
|
-
const createdB = b.createdAt ?? Number.MAX_SAFE_INTEGER;
|
|
443
|
-
if (createdA !== createdB) {
|
|
444
|
-
return createdA - createdB;
|
|
445
|
-
}
|
|
446
|
-
return String(a.name || "").localeCompare(String(b.name || ""));
|
|
447
|
-
})[0];
|
|
448
|
-
}
|
|
449
|
-
async function findTopicsByScopeAlias(ctx, scopeId) {
|
|
450
|
-
const query = ctx.db.query("topics");
|
|
451
|
-
try {
|
|
452
|
-
return await query.withIndex(
|
|
453
|
-
"by_graph_scope_project",
|
|
454
|
-
(q) => q.eq(LEGACY_SCOPE_FIELD, scopeId)
|
|
455
|
-
).collect();
|
|
456
|
-
} catch (error) {
|
|
457
|
-
debugGraphPrimitiveFallback(
|
|
458
|
-
"[topicScope] Failed to resolve scope alias via index",
|
|
459
|
-
{
|
|
460
|
-
error,
|
|
461
|
-
scopeId
|
|
462
|
-
}
|
|
463
|
-
);
|
|
464
|
-
const topics = await query.collect();
|
|
465
|
-
return topics.filter((topic) => {
|
|
466
|
-
const normalizedGlobalId = normalizeScopeValue(topic.globalId);
|
|
467
|
-
const mappedProjectId = asMappedProjectId(topic);
|
|
468
|
-
return String(topic._id) === scopeId || normalizedGlobalId === scopeId || mappedProjectId === scopeId;
|
|
469
|
-
});
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
async function tryResolveHostTopicById(ctx, topicId) {
|
|
473
|
-
if (typeof ctx.runQuery !== "function") {
|
|
474
|
-
return null;
|
|
475
|
-
}
|
|
476
|
-
try {
|
|
477
|
-
return await ctx.runQuery(api.topics.get, {
|
|
478
|
-
id: topicId
|
|
479
|
-
}) ?? null;
|
|
480
|
-
} catch (error) {
|
|
481
|
-
debugGraphPrimitiveFallback(
|
|
482
|
-
"[topicScope] Failed to resolve topic by host query",
|
|
483
|
-
{
|
|
484
|
-
error,
|
|
485
|
-
topicId
|
|
486
|
-
}
|
|
487
|
-
);
|
|
488
|
-
return null;
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
async function tryResolveHostTopicByLegacyScope(ctx, legacyScopeId) {
|
|
492
|
-
if (typeof ctx.runQuery !== "function") {
|
|
493
|
-
return null;
|
|
494
|
-
}
|
|
495
|
-
try {
|
|
496
|
-
return await ctx.runQuery(api.topics.getByLegacyScopeId, {
|
|
497
|
-
projectId: legacyScopeId
|
|
498
|
-
}) ?? null;
|
|
499
|
-
} catch (error) {
|
|
500
|
-
debugGraphPrimitiveFallback(
|
|
501
|
-
"[topicScope] Failed to resolve topic by legacy scope",
|
|
502
|
-
{
|
|
503
|
-
error,
|
|
504
|
-
legacyScopeId
|
|
505
|
-
}
|
|
506
|
-
);
|
|
507
|
-
return null;
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
async function resolveInheritedWorkspaceScope(ctx, topic) {
|
|
511
|
-
const MAX_DEPTH = 10;
|
|
512
|
-
let tenantId = normalizeScopeValue(topic.tenantId);
|
|
513
|
-
let workspaceId = normalizeScopeValue(topic.workspaceId);
|
|
514
|
-
if (tenantId && workspaceId) {
|
|
515
|
-
return { tenantId, workspaceId };
|
|
516
|
-
}
|
|
517
|
-
let current = topic;
|
|
518
|
-
for (let i = 0; i < MAX_DEPTH && current?.parentTopicId; i++) {
|
|
519
|
-
current = await ctx.db.get(current.parentTopicId);
|
|
520
|
-
if (!current) {
|
|
521
|
-
break;
|
|
522
|
-
}
|
|
523
|
-
if (!tenantId) {
|
|
524
|
-
tenantId = normalizeScopeValue(current.tenantId);
|
|
525
|
-
}
|
|
526
|
-
if (!workspaceId) {
|
|
527
|
-
workspaceId = normalizeScopeValue(current.workspaceId);
|
|
528
|
-
}
|
|
529
|
-
if (tenantId && workspaceId) {
|
|
530
|
-
break;
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
return { tenantId, workspaceId };
|
|
534
|
-
}
|
|
535
456
|
async function resolveTopicProjectScope(ctx, args) {
|
|
536
457
|
if (args.topicId) {
|
|
537
458
|
return await resolveScopeFromTopicId(ctx, args.topicId);
|
|
538
459
|
}
|
|
539
460
|
if (args.projectId) {
|
|
540
|
-
|
|
461
|
+
throw new Error(
|
|
462
|
+
"topic.uuidv7_required: projectId scope aliases are retired; pass topicId as the UUIDv7 topic globalId."
|
|
463
|
+
);
|
|
541
464
|
}
|
|
542
|
-
throw new Error(
|
|
543
|
-
"Missing scope: provide topicId (preferred) or legacy projectId alias."
|
|
544
|
-
);
|
|
465
|
+
throw new Error("topic.uuidv7_required: Missing scope: provide topicId.");
|
|
545
466
|
}
|
|
546
467
|
async function resolveScopeFromTopicId(ctx, topicId) {
|
|
547
|
-
const
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
}
|
|
551
|
-
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, String(topicId));
|
|
468
|
+
const topicGlobalId = String(topicId);
|
|
469
|
+
requireUuidV7TopicScope("topicId", topicGlobalId);
|
|
470
|
+
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, topicGlobalId);
|
|
552
471
|
if (nodeScope) {
|
|
553
472
|
return nodeScope;
|
|
554
473
|
}
|
|
555
|
-
throw new Error(`Topic not found: ${
|
|
556
|
-
}
|
|
557
|
-
async function resolveTopicDocFromTopicId(ctx, topicId) {
|
|
558
|
-
const direct = await tryReadTopicDoc(ctx, topicId, {
|
|
559
|
-
failureLog: "[topicScope] Failed to load topic by direct id",
|
|
560
|
-
idLogKey: "topicId"
|
|
561
|
-
});
|
|
562
|
-
if (direct) {
|
|
563
|
-
return direct;
|
|
564
|
-
}
|
|
565
|
-
const hostTopic = await tryResolveHostTopicById(ctx, String(topicId));
|
|
566
|
-
if (hostTopic) {
|
|
567
|
-
return hostTopic;
|
|
568
|
-
}
|
|
569
|
-
return pickPrimaryTopic(await findTopicsByScopeAlias(ctx, String(topicId))) ?? null;
|
|
570
|
-
}
|
|
571
|
-
async function resolveScopeFromLegacyProjectId(ctx, legacyProjectId) {
|
|
572
|
-
const directTopic = await resolveDirectLegacyProjectTopic(
|
|
573
|
-
ctx,
|
|
574
|
-
legacyProjectId
|
|
575
|
-
);
|
|
576
|
-
if (directTopic) {
|
|
577
|
-
return await buildTopicScope(ctx, directTopic, "topic_inferred", {
|
|
578
|
-
fallbackProjectId: legacyProjectId
|
|
579
|
-
});
|
|
580
|
-
}
|
|
581
|
-
const primary = pickPrimaryTopic(
|
|
582
|
-
await findTopicsByScopeAlias(ctx, legacyProjectId)
|
|
583
|
-
);
|
|
584
|
-
if (primary) {
|
|
585
|
-
return await buildTopicScope(ctx, primary, "project_mapped_topic", {
|
|
586
|
-
fallbackProjectId: legacyProjectId
|
|
587
|
-
});
|
|
588
|
-
}
|
|
589
|
-
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, legacyProjectId);
|
|
590
|
-
if (nodeScope) {
|
|
591
|
-
return {
|
|
592
|
-
...nodeScope,
|
|
593
|
-
projectId: nodeScope.projectId ?? legacyProjectId
|
|
594
|
-
};
|
|
595
|
-
}
|
|
596
|
-
throw new Error(
|
|
597
|
-
`Legacy project scope ${legacyProjectId} has no mapped topic.`
|
|
598
|
-
);
|
|
599
|
-
}
|
|
600
|
-
async function resolveDirectLegacyProjectTopic(ctx, legacyProjectId) {
|
|
601
|
-
const directTopic = await tryReadTopicDoc(ctx, legacyProjectId, {
|
|
602
|
-
failureLog: "[topicScope] Failed to load direct project topic",
|
|
603
|
-
idLogKey: "projectId"
|
|
604
|
-
});
|
|
605
|
-
return directTopic ?? tryResolveHostTopicByLegacyScope(ctx, legacyProjectId);
|
|
606
|
-
}
|
|
607
|
-
async function tryReadTopicDoc(ctx, id, log) {
|
|
608
|
-
try {
|
|
609
|
-
return await ctx.db.get(id);
|
|
610
|
-
} catch (error) {
|
|
611
|
-
debugGraphPrimitiveFallback(log.failureLog, {
|
|
612
|
-
error,
|
|
613
|
-
[log.idLogKey]: id
|
|
614
|
-
});
|
|
615
|
-
return null;
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
async function buildTopicScope(ctx, topic, source, options = {}) {
|
|
619
|
-
const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
|
|
620
|
-
const mapped = asMappedProjectId(topic);
|
|
621
|
-
return {
|
|
622
|
-
topicId: topic._id,
|
|
623
|
-
...mapped || options.fallbackProjectId ? { projectId: mapped ?? options.fallbackProjectId } : {},
|
|
624
|
-
tenantId: inherited.tenantId,
|
|
625
|
-
workspaceId: inherited.workspaceId,
|
|
626
|
-
source
|
|
627
|
-
};
|
|
474
|
+
throw new Error(`Topic not found: ${topicGlobalId}`);
|
|
628
475
|
}
|
|
629
476
|
var optionalScopeArgs = {
|
|
630
477
|
projectId: v.optional(v.string()),
|
|
@@ -3,12 +3,12 @@ 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 } from 'convex/server';
|
|
6
|
+
import { isUuidV7 } from '@lucern/contracts/ids';
|
|
6
7
|
|
|
7
8
|
// src/epistemicNodes.queries.ts
|
|
8
|
-
|
|
9
|
+
unsafeConvexAnyApi(
|
|
9
10
|
"graph-primitives top-level module bundle lacks a committed Convex _generated/api surface"
|
|
10
11
|
);
|
|
11
|
-
var api = unsafeApi;
|
|
12
12
|
componentsGeneric();
|
|
13
13
|
var query = queryGeneric;
|
|
14
14
|
|
|
@@ -170,6 +170,8 @@ v.union(
|
|
|
170
170
|
v.literal("contradicted"),
|
|
171
171
|
v.literal("outdated")
|
|
172
172
|
);
|
|
173
|
+
|
|
174
|
+
// src/topicScope.ts
|
|
173
175
|
var LEGACY_SCOPE_FIELD = "graphScopeProjectId";
|
|
174
176
|
async function resolveTopicNodeScopeOrNull(ctx, ref) {
|
|
175
177
|
if (!ctx?.db || typeof ctx.db.query !== "function") {
|
|
@@ -190,16 +192,36 @@ async function resolveTopicNodeScopeOrNull(ctx, ref) {
|
|
|
190
192
|
if (!node) {
|
|
191
193
|
return null;
|
|
192
194
|
}
|
|
193
|
-
const scopeKey =
|
|
195
|
+
const scopeKey = canonicalTopicGlobalId(node);
|
|
194
196
|
if (!scopeKey) {
|
|
195
|
-
|
|
197
|
+
throw new Error(
|
|
198
|
+
`topic.uuidv7_identity_required: topic node ${ref} is missing a UUIDv7 globalId.`
|
|
199
|
+
);
|
|
196
200
|
}
|
|
201
|
+
const metadata = node.metadata ?? {};
|
|
197
202
|
return {
|
|
198
203
|
topicId: scopeKey,
|
|
199
204
|
projectId: asMappedProjectId(node),
|
|
200
|
-
source: "topic_node"
|
|
205
|
+
source: "topic_node",
|
|
206
|
+
tenantId: normalizeScopeValue(node.tenantId) ?? normalizeScopeValue(metadata.tenantId),
|
|
207
|
+
workspaceId: normalizeScopeValue(node.workspaceId) ?? normalizeScopeValue(metadata.workspaceId)
|
|
201
208
|
};
|
|
202
209
|
}
|
|
210
|
+
function canonicalTopicGlobalId(node) {
|
|
211
|
+
const globalId = normalizeScopeValue(node.globalId);
|
|
212
|
+
if (globalId && isUuidV7(globalId)) {
|
|
213
|
+
return globalId;
|
|
214
|
+
}
|
|
215
|
+
const topicId = normalizeScopeValue(node.topicId);
|
|
216
|
+
return topicId && isUuidV7(topicId) ? topicId : null;
|
|
217
|
+
}
|
|
218
|
+
function requireUuidV7TopicScope(field, value) {
|
|
219
|
+
if (!isUuidV7(value)) {
|
|
220
|
+
throw new Error(
|
|
221
|
+
`topic.uuidv7_required: ${field} must be a UUIDv7 public topic identifier.`
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
203
225
|
function asMappedProjectId(topic) {
|
|
204
226
|
if (!topic) {
|
|
205
227
|
return;
|
|
@@ -221,200 +243,25 @@ function normalizeScopeValue(value) {
|
|
|
221
243
|
const normalized = value.trim();
|
|
222
244
|
return normalized.length > 0 ? normalized : void 0;
|
|
223
245
|
}
|
|
224
|
-
function pickPrimaryTopic(candidates) {
|
|
225
|
-
return [...candidates].sort((a, b) => {
|
|
226
|
-
const depthA = a.depth ?? 9999;
|
|
227
|
-
const depthB = b.depth ?? 9999;
|
|
228
|
-
if (depthA !== depthB) {
|
|
229
|
-
return depthA - depthB;
|
|
230
|
-
}
|
|
231
|
-
const createdA = a.createdAt ?? Number.MAX_SAFE_INTEGER;
|
|
232
|
-
const createdB = b.createdAt ?? Number.MAX_SAFE_INTEGER;
|
|
233
|
-
if (createdA !== createdB) {
|
|
234
|
-
return createdA - createdB;
|
|
235
|
-
}
|
|
236
|
-
return String(a.name || "").localeCompare(String(b.name || ""));
|
|
237
|
-
})[0];
|
|
238
|
-
}
|
|
239
|
-
async function findTopicsByScopeAlias(ctx, scopeId) {
|
|
240
|
-
const query2 = ctx.db.query("topics");
|
|
241
|
-
try {
|
|
242
|
-
return await query2.withIndex(
|
|
243
|
-
"by_graph_scope_project",
|
|
244
|
-
(q) => q.eq(LEGACY_SCOPE_FIELD, scopeId)
|
|
245
|
-
).collect();
|
|
246
|
-
} catch (error) {
|
|
247
|
-
debugGraphPrimitiveFallback(
|
|
248
|
-
"[topicScope] Failed to resolve scope alias via index",
|
|
249
|
-
{
|
|
250
|
-
error,
|
|
251
|
-
scopeId
|
|
252
|
-
}
|
|
253
|
-
);
|
|
254
|
-
const topics = await query2.collect();
|
|
255
|
-
return topics.filter((topic) => {
|
|
256
|
-
const normalizedGlobalId = normalizeScopeValue(topic.globalId);
|
|
257
|
-
const mappedProjectId = asMappedProjectId(topic);
|
|
258
|
-
return String(topic._id) === scopeId || normalizedGlobalId === scopeId || mappedProjectId === scopeId;
|
|
259
|
-
});
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
async function tryResolveHostTopicById(ctx, topicId) {
|
|
263
|
-
if (typeof ctx.runQuery !== "function") {
|
|
264
|
-
return null;
|
|
265
|
-
}
|
|
266
|
-
try {
|
|
267
|
-
return await ctx.runQuery(api.topics.get, {
|
|
268
|
-
id: topicId
|
|
269
|
-
}) ?? null;
|
|
270
|
-
} catch (error) {
|
|
271
|
-
debugGraphPrimitiveFallback(
|
|
272
|
-
"[topicScope] Failed to resolve topic by host query",
|
|
273
|
-
{
|
|
274
|
-
error,
|
|
275
|
-
topicId
|
|
276
|
-
}
|
|
277
|
-
);
|
|
278
|
-
return null;
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
async function tryResolveHostTopicByLegacyScope(ctx, legacyScopeId) {
|
|
282
|
-
if (typeof ctx.runQuery !== "function") {
|
|
283
|
-
return null;
|
|
284
|
-
}
|
|
285
|
-
try {
|
|
286
|
-
return await ctx.runQuery(api.topics.getByLegacyScopeId, {
|
|
287
|
-
projectId: legacyScopeId
|
|
288
|
-
}) ?? null;
|
|
289
|
-
} catch (error) {
|
|
290
|
-
debugGraphPrimitiveFallback(
|
|
291
|
-
"[topicScope] Failed to resolve topic by legacy scope",
|
|
292
|
-
{
|
|
293
|
-
error,
|
|
294
|
-
legacyScopeId
|
|
295
|
-
}
|
|
296
|
-
);
|
|
297
|
-
return null;
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
async function resolveInheritedWorkspaceScope(ctx, topic) {
|
|
301
|
-
const MAX_DEPTH = 10;
|
|
302
|
-
let tenantId = normalizeScopeValue(topic.tenantId);
|
|
303
|
-
let workspaceId = normalizeScopeValue(topic.workspaceId);
|
|
304
|
-
if (tenantId && workspaceId) {
|
|
305
|
-
return { tenantId, workspaceId };
|
|
306
|
-
}
|
|
307
|
-
let current = topic;
|
|
308
|
-
for (let i = 0; i < MAX_DEPTH && current?.parentTopicId; i++) {
|
|
309
|
-
current = await ctx.db.get(current.parentTopicId);
|
|
310
|
-
if (!current) {
|
|
311
|
-
break;
|
|
312
|
-
}
|
|
313
|
-
if (!tenantId) {
|
|
314
|
-
tenantId = normalizeScopeValue(current.tenantId);
|
|
315
|
-
}
|
|
316
|
-
if (!workspaceId) {
|
|
317
|
-
workspaceId = normalizeScopeValue(current.workspaceId);
|
|
318
|
-
}
|
|
319
|
-
if (tenantId && workspaceId) {
|
|
320
|
-
break;
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
return { tenantId, workspaceId };
|
|
324
|
-
}
|
|
325
246
|
async function resolveTopicProjectScope(ctx, args) {
|
|
326
247
|
if (args.topicId) {
|
|
327
248
|
return await resolveScopeFromTopicId(ctx, args.topicId);
|
|
328
249
|
}
|
|
329
250
|
if (args.projectId) {
|
|
330
|
-
|
|
251
|
+
throw new Error(
|
|
252
|
+
"topic.uuidv7_required: projectId scope aliases are retired; pass topicId as the UUIDv7 topic globalId."
|
|
253
|
+
);
|
|
331
254
|
}
|
|
332
|
-
throw new Error(
|
|
333
|
-
"Missing scope: provide topicId (preferred) or legacy projectId alias."
|
|
334
|
-
);
|
|
255
|
+
throw new Error("topic.uuidv7_required: Missing scope: provide topicId.");
|
|
335
256
|
}
|
|
336
257
|
async function resolveScopeFromTopicId(ctx, topicId) {
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
}
|
|
341
|
-
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, String(topicId));
|
|
258
|
+
const topicGlobalId = String(topicId);
|
|
259
|
+
requireUuidV7TopicScope("topicId", topicGlobalId);
|
|
260
|
+
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, topicGlobalId);
|
|
342
261
|
if (nodeScope) {
|
|
343
262
|
return nodeScope;
|
|
344
263
|
}
|
|
345
|
-
throw new Error(`Topic not found: ${
|
|
346
|
-
}
|
|
347
|
-
async function resolveTopicDocFromTopicId(ctx, topicId) {
|
|
348
|
-
const direct = await tryReadTopicDoc(ctx, topicId, {
|
|
349
|
-
failureLog: "[topicScope] Failed to load topic by direct id",
|
|
350
|
-
idLogKey: "topicId"
|
|
351
|
-
});
|
|
352
|
-
if (direct) {
|
|
353
|
-
return direct;
|
|
354
|
-
}
|
|
355
|
-
const hostTopic = await tryResolveHostTopicById(ctx, String(topicId));
|
|
356
|
-
if (hostTopic) {
|
|
357
|
-
return hostTopic;
|
|
358
|
-
}
|
|
359
|
-
return pickPrimaryTopic(await findTopicsByScopeAlias(ctx, String(topicId))) ?? null;
|
|
360
|
-
}
|
|
361
|
-
async function resolveScopeFromLegacyProjectId(ctx, legacyProjectId) {
|
|
362
|
-
const directTopic = await resolveDirectLegacyProjectTopic(
|
|
363
|
-
ctx,
|
|
364
|
-
legacyProjectId
|
|
365
|
-
);
|
|
366
|
-
if (directTopic) {
|
|
367
|
-
return await buildTopicScope(ctx, directTopic, "topic_inferred", {
|
|
368
|
-
fallbackProjectId: legacyProjectId
|
|
369
|
-
});
|
|
370
|
-
}
|
|
371
|
-
const primary = pickPrimaryTopic(
|
|
372
|
-
await findTopicsByScopeAlias(ctx, legacyProjectId)
|
|
373
|
-
);
|
|
374
|
-
if (primary) {
|
|
375
|
-
return await buildTopicScope(ctx, primary, "project_mapped_topic", {
|
|
376
|
-
fallbackProjectId: legacyProjectId
|
|
377
|
-
});
|
|
378
|
-
}
|
|
379
|
-
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, legacyProjectId);
|
|
380
|
-
if (nodeScope) {
|
|
381
|
-
return {
|
|
382
|
-
...nodeScope,
|
|
383
|
-
projectId: nodeScope.projectId ?? legacyProjectId
|
|
384
|
-
};
|
|
385
|
-
}
|
|
386
|
-
throw new Error(
|
|
387
|
-
`Legacy project scope ${legacyProjectId} has no mapped topic.`
|
|
388
|
-
);
|
|
389
|
-
}
|
|
390
|
-
async function resolveDirectLegacyProjectTopic(ctx, legacyProjectId) {
|
|
391
|
-
const directTopic = await tryReadTopicDoc(ctx, legacyProjectId, {
|
|
392
|
-
failureLog: "[topicScope] Failed to load direct project topic",
|
|
393
|
-
idLogKey: "projectId"
|
|
394
|
-
});
|
|
395
|
-
return directTopic ?? tryResolveHostTopicByLegacyScope(ctx, legacyProjectId);
|
|
396
|
-
}
|
|
397
|
-
async function tryReadTopicDoc(ctx, id, log) {
|
|
398
|
-
try {
|
|
399
|
-
return await ctx.db.get(id);
|
|
400
|
-
} catch (error) {
|
|
401
|
-
debugGraphPrimitiveFallback(log.failureLog, {
|
|
402
|
-
error,
|
|
403
|
-
[log.idLogKey]: id
|
|
404
|
-
});
|
|
405
|
-
return null;
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
async function buildTopicScope(ctx, topic, source, options = {}) {
|
|
409
|
-
const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
|
|
410
|
-
const mapped = asMappedProjectId(topic);
|
|
411
|
-
return {
|
|
412
|
-
topicId: topic._id,
|
|
413
|
-
...mapped || options.fallbackProjectId ? { projectId: mapped ?? options.fallbackProjectId } : {},
|
|
414
|
-
tenantId: inherited.tenantId,
|
|
415
|
-
workspaceId: inherited.workspaceId,
|
|
416
|
-
source
|
|
417
|
-
};
|
|
264
|
+
throw new Error(`Topic not found: ${topicGlobalId}`);
|
|
418
265
|
}
|
|
419
266
|
var optionalScopeArgs = {
|
|
420
267
|
projectId: v.optional(v.string()),
|