@lucern/graph-primitives 1.0.50 → 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 +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
|
@@ -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, queryGeneric, internalMutationGeneric, internalQueryGeneric } from 'convex/server';
|
|
6
|
-
import { generateGlobalId, assertUuidV7Identity } from '@lucern/contracts/ids';
|
|
6
|
+
import { generateGlobalId, 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';
|
|
@@ -428,6 +428,8 @@ function resolveGraphPrimitivesAppResolvers(_ctx) {
|
|
|
428
428
|
...resolverOverrides
|
|
429
429
|
};
|
|
430
430
|
}
|
|
431
|
+
|
|
432
|
+
// src/topicScope.ts
|
|
431
433
|
var LEGACY_SCOPE_FIELD2 = "graphScopeProjectId";
|
|
432
434
|
async function resolveTopicNodeScopeOrNull(ctx, ref) {
|
|
433
435
|
if (!ctx?.db || typeof ctx.db.query !== "function") {
|
|
@@ -448,16 +450,36 @@ async function resolveTopicNodeScopeOrNull(ctx, ref) {
|
|
|
448
450
|
if (!node) {
|
|
449
451
|
return null;
|
|
450
452
|
}
|
|
451
|
-
const scopeKey =
|
|
453
|
+
const scopeKey = canonicalTopicGlobalId(node);
|
|
452
454
|
if (!scopeKey) {
|
|
453
|
-
|
|
455
|
+
throw new Error(
|
|
456
|
+
`topic.uuidv7_identity_required: topic node ${ref} is missing a UUIDv7 globalId.`
|
|
457
|
+
);
|
|
454
458
|
}
|
|
459
|
+
const metadata = node.metadata ?? {};
|
|
455
460
|
return {
|
|
456
461
|
topicId: scopeKey,
|
|
457
462
|
projectId: asMappedProjectId(node),
|
|
458
|
-
source: "topic_node"
|
|
463
|
+
source: "topic_node",
|
|
464
|
+
tenantId: normalizeScopeValue(node.tenantId) ?? normalizeScopeValue(metadata.tenantId),
|
|
465
|
+
workspaceId: normalizeScopeValue(node.workspaceId) ?? normalizeScopeValue(metadata.workspaceId)
|
|
459
466
|
};
|
|
460
467
|
}
|
|
468
|
+
function canonicalTopicGlobalId(node) {
|
|
469
|
+
const globalId = normalizeScopeValue(node.globalId);
|
|
470
|
+
if (globalId && isUuidV7(globalId)) {
|
|
471
|
+
return globalId;
|
|
472
|
+
}
|
|
473
|
+
const topicId = normalizeScopeValue(node.topicId);
|
|
474
|
+
return topicId && isUuidV7(topicId) ? topicId : null;
|
|
475
|
+
}
|
|
476
|
+
function requireUuidV7TopicScope(field, value) {
|
|
477
|
+
if (!isUuidV7(value)) {
|
|
478
|
+
throw new Error(
|
|
479
|
+
`topic.uuidv7_required: ${field} must be a UUIDv7 public topic identifier.`
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
461
483
|
function asMappedProjectId(topic) {
|
|
462
484
|
if (!topic) {
|
|
463
485
|
return;
|
|
@@ -479,200 +501,25 @@ function normalizeScopeValue(value) {
|
|
|
479
501
|
const normalized = value.trim();
|
|
480
502
|
return normalized.length > 0 ? normalized : void 0;
|
|
481
503
|
}
|
|
482
|
-
function pickPrimaryTopic(candidates) {
|
|
483
|
-
return [...candidates].sort((a, b) => {
|
|
484
|
-
const depthA = a.depth ?? 9999;
|
|
485
|
-
const depthB = b.depth ?? 9999;
|
|
486
|
-
if (depthA !== depthB) {
|
|
487
|
-
return depthA - depthB;
|
|
488
|
-
}
|
|
489
|
-
const createdA = a.createdAt ?? Number.MAX_SAFE_INTEGER;
|
|
490
|
-
const createdB = b.createdAt ?? Number.MAX_SAFE_INTEGER;
|
|
491
|
-
if (createdA !== createdB) {
|
|
492
|
-
return createdA - createdB;
|
|
493
|
-
}
|
|
494
|
-
return String(a.name || "").localeCompare(String(b.name || ""));
|
|
495
|
-
})[0];
|
|
496
|
-
}
|
|
497
|
-
async function findTopicsByScopeAlias(ctx, scopeId) {
|
|
498
|
-
const query2 = ctx.db.query("topics");
|
|
499
|
-
try {
|
|
500
|
-
return await query2.withIndex(
|
|
501
|
-
"by_graph_scope_project",
|
|
502
|
-
(q) => q.eq(LEGACY_SCOPE_FIELD2, scopeId)
|
|
503
|
-
).collect();
|
|
504
|
-
} catch (error) {
|
|
505
|
-
debugGraphPrimitiveFallback(
|
|
506
|
-
"[topicScope] Failed to resolve scope alias via index",
|
|
507
|
-
{
|
|
508
|
-
error,
|
|
509
|
-
scopeId
|
|
510
|
-
}
|
|
511
|
-
);
|
|
512
|
-
const topics = await query2.collect();
|
|
513
|
-
return topics.filter((topic) => {
|
|
514
|
-
const normalizedGlobalId = normalizeScopeValue(topic.globalId);
|
|
515
|
-
const mappedProjectId = asMappedProjectId(topic);
|
|
516
|
-
return String(topic._id) === scopeId || normalizedGlobalId === scopeId || mappedProjectId === scopeId;
|
|
517
|
-
});
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
async function tryResolveHostTopicById(ctx, topicId) {
|
|
521
|
-
if (typeof ctx.runQuery !== "function") {
|
|
522
|
-
return null;
|
|
523
|
-
}
|
|
524
|
-
try {
|
|
525
|
-
return await ctx.runQuery(api.topics.get, {
|
|
526
|
-
id: topicId
|
|
527
|
-
}) ?? null;
|
|
528
|
-
} catch (error) {
|
|
529
|
-
debugGraphPrimitiveFallback(
|
|
530
|
-
"[topicScope] Failed to resolve topic by host query",
|
|
531
|
-
{
|
|
532
|
-
error,
|
|
533
|
-
topicId
|
|
534
|
-
}
|
|
535
|
-
);
|
|
536
|
-
return null;
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
async function tryResolveHostTopicByLegacyScope(ctx, legacyScopeId) {
|
|
540
|
-
if (typeof ctx.runQuery !== "function") {
|
|
541
|
-
return null;
|
|
542
|
-
}
|
|
543
|
-
try {
|
|
544
|
-
return await ctx.runQuery(api.topics.getByLegacyScopeId, {
|
|
545
|
-
projectId: legacyScopeId
|
|
546
|
-
}) ?? null;
|
|
547
|
-
} catch (error) {
|
|
548
|
-
debugGraphPrimitiveFallback(
|
|
549
|
-
"[topicScope] Failed to resolve topic by legacy scope",
|
|
550
|
-
{
|
|
551
|
-
error,
|
|
552
|
-
legacyScopeId
|
|
553
|
-
}
|
|
554
|
-
);
|
|
555
|
-
return null;
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
async function resolveInheritedWorkspaceScope(ctx, topic) {
|
|
559
|
-
const MAX_DEPTH = 10;
|
|
560
|
-
let tenantId = normalizeScopeValue(topic.tenantId);
|
|
561
|
-
let workspaceId = normalizeScopeValue(topic.workspaceId);
|
|
562
|
-
if (tenantId && workspaceId) {
|
|
563
|
-
return { tenantId, workspaceId };
|
|
564
|
-
}
|
|
565
|
-
let current = topic;
|
|
566
|
-
for (let i = 0; i < MAX_DEPTH && current?.parentTopicId; i++) {
|
|
567
|
-
current = await ctx.db.get(current.parentTopicId);
|
|
568
|
-
if (!current) {
|
|
569
|
-
break;
|
|
570
|
-
}
|
|
571
|
-
if (!tenantId) {
|
|
572
|
-
tenantId = normalizeScopeValue(current.tenantId);
|
|
573
|
-
}
|
|
574
|
-
if (!workspaceId) {
|
|
575
|
-
workspaceId = normalizeScopeValue(current.workspaceId);
|
|
576
|
-
}
|
|
577
|
-
if (tenantId && workspaceId) {
|
|
578
|
-
break;
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
return { tenantId, workspaceId };
|
|
582
|
-
}
|
|
583
504
|
async function resolveTopicProjectScope(ctx, args) {
|
|
584
505
|
if (args.topicId) {
|
|
585
506
|
return await resolveScopeFromTopicId(ctx, args.topicId);
|
|
586
507
|
}
|
|
587
508
|
if (args.projectId) {
|
|
588
|
-
|
|
509
|
+
throw new Error(
|
|
510
|
+
"topic.uuidv7_required: projectId scope aliases are retired; pass topicId as the UUIDv7 topic globalId."
|
|
511
|
+
);
|
|
589
512
|
}
|
|
590
|
-
throw new Error(
|
|
591
|
-
"Missing scope: provide topicId (preferred) or legacy projectId alias."
|
|
592
|
-
);
|
|
513
|
+
throw new Error("topic.uuidv7_required: Missing scope: provide topicId.");
|
|
593
514
|
}
|
|
594
515
|
async function resolveScopeFromTopicId(ctx, topicId) {
|
|
595
|
-
const
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
}
|
|
599
|
-
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, String(topicId));
|
|
516
|
+
const topicGlobalId = String(topicId);
|
|
517
|
+
requireUuidV7TopicScope("topicId", topicGlobalId);
|
|
518
|
+
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, topicGlobalId);
|
|
600
519
|
if (nodeScope) {
|
|
601
520
|
return nodeScope;
|
|
602
521
|
}
|
|
603
|
-
throw new Error(`Topic not found: ${
|
|
604
|
-
}
|
|
605
|
-
async function resolveTopicDocFromTopicId(ctx, topicId) {
|
|
606
|
-
const direct = await tryReadTopicDoc(ctx, topicId, {
|
|
607
|
-
failureLog: "[topicScope] Failed to load topic by direct id",
|
|
608
|
-
idLogKey: "topicId"
|
|
609
|
-
});
|
|
610
|
-
if (direct) {
|
|
611
|
-
return direct;
|
|
612
|
-
}
|
|
613
|
-
const hostTopic = await tryResolveHostTopicById(ctx, String(topicId));
|
|
614
|
-
if (hostTopic) {
|
|
615
|
-
return hostTopic;
|
|
616
|
-
}
|
|
617
|
-
return pickPrimaryTopic(await findTopicsByScopeAlias(ctx, String(topicId))) ?? null;
|
|
618
|
-
}
|
|
619
|
-
async function resolveScopeFromLegacyProjectId(ctx, legacyProjectId) {
|
|
620
|
-
const directTopic = await resolveDirectLegacyProjectTopic(
|
|
621
|
-
ctx,
|
|
622
|
-
legacyProjectId
|
|
623
|
-
);
|
|
624
|
-
if (directTopic) {
|
|
625
|
-
return await buildTopicScope(ctx, directTopic, "topic_inferred", {
|
|
626
|
-
fallbackProjectId: legacyProjectId
|
|
627
|
-
});
|
|
628
|
-
}
|
|
629
|
-
const primary = pickPrimaryTopic(
|
|
630
|
-
await findTopicsByScopeAlias(ctx, legacyProjectId)
|
|
631
|
-
);
|
|
632
|
-
if (primary) {
|
|
633
|
-
return await buildTopicScope(ctx, primary, "project_mapped_topic", {
|
|
634
|
-
fallbackProjectId: legacyProjectId
|
|
635
|
-
});
|
|
636
|
-
}
|
|
637
|
-
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, legacyProjectId);
|
|
638
|
-
if (nodeScope) {
|
|
639
|
-
return {
|
|
640
|
-
...nodeScope,
|
|
641
|
-
projectId: nodeScope.projectId ?? legacyProjectId
|
|
642
|
-
};
|
|
643
|
-
}
|
|
644
|
-
throw new Error(
|
|
645
|
-
`Legacy project scope ${legacyProjectId} has no mapped topic.`
|
|
646
|
-
);
|
|
647
|
-
}
|
|
648
|
-
async function resolveDirectLegacyProjectTopic(ctx, legacyProjectId) {
|
|
649
|
-
const directTopic = await tryReadTopicDoc(ctx, legacyProjectId, {
|
|
650
|
-
failureLog: "[topicScope] Failed to load direct project topic",
|
|
651
|
-
idLogKey: "projectId"
|
|
652
|
-
});
|
|
653
|
-
return directTopic ?? tryResolveHostTopicByLegacyScope(ctx, legacyProjectId);
|
|
654
|
-
}
|
|
655
|
-
async function tryReadTopicDoc(ctx, id, log) {
|
|
656
|
-
try {
|
|
657
|
-
return await ctx.db.get(id);
|
|
658
|
-
} catch (error) {
|
|
659
|
-
debugGraphPrimitiveFallback(log.failureLog, {
|
|
660
|
-
error,
|
|
661
|
-
[log.idLogKey]: id
|
|
662
|
-
});
|
|
663
|
-
return null;
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
async function buildTopicScope(ctx, topic, source, options = {}) {
|
|
667
|
-
const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
|
|
668
|
-
const mapped = asMappedProjectId(topic);
|
|
669
|
-
return {
|
|
670
|
-
topicId: topic._id,
|
|
671
|
-
...mapped || options.fallbackProjectId ? { projectId: mapped ?? options.fallbackProjectId } : {},
|
|
672
|
-
tenantId: inherited.tenantId,
|
|
673
|
-
workspaceId: inherited.workspaceId,
|
|
674
|
-
source
|
|
675
|
-
};
|
|
522
|
+
throw new Error(`Topic not found: ${topicGlobalId}`);
|
|
676
523
|
}
|
|
677
524
|
var optionalScopeArgs = {
|
|
678
525
|
projectId: v.optional(v.string()),
|
|
@@ -1770,6 +1617,9 @@ async function scheduleEmbeddingGeneration(args) {
|
|
|
1770
1617
|
}
|
|
1771
1618
|
function insertEpistemicNode(ctx, doc) {
|
|
1772
1619
|
assertUuidV7Identity("epistemicNodes", doc.globalId);
|
|
1620
|
+
if (doc.topicId !== void 0 && doc.topicId !== null) {
|
|
1621
|
+
assertUuidV7Reference("epistemicNodes.topicId", doc.topicId);
|
|
1622
|
+
}
|
|
1773
1623
|
return ctx.db.insert("epistemicNodes", doc);
|
|
1774
1624
|
}
|
|
1775
1625
|
function normalizeScopeValue2(value) {
|
|
@@ -3,6 +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 { isUuidV7 } from '@lucern/contracts/ids';
|
|
6
7
|
import '@lucern/contracts/schema-helpers/spine/tables/epistemicNodes';
|
|
7
8
|
|
|
8
9
|
// src/epistemicQuestions.lifecycle.ts
|
|
@@ -400,6 +401,8 @@ function resolveGraphPrimitivesAppResolvers(_ctx) {
|
|
|
400
401
|
...resolverOverrides
|
|
401
402
|
};
|
|
402
403
|
}
|
|
404
|
+
|
|
405
|
+
// src/topicScope.ts
|
|
403
406
|
var LEGACY_SCOPE_FIELD2 = "graphScopeProjectId";
|
|
404
407
|
async function resolveTopicNodeScopeOrNull(ctx, ref) {
|
|
405
408
|
if (!ctx?.db || typeof ctx.db.query !== "function") {
|
|
@@ -420,16 +423,36 @@ async function resolveTopicNodeScopeOrNull(ctx, ref) {
|
|
|
420
423
|
if (!node) {
|
|
421
424
|
return null;
|
|
422
425
|
}
|
|
423
|
-
const scopeKey =
|
|
426
|
+
const scopeKey = canonicalTopicGlobalId(node);
|
|
424
427
|
if (!scopeKey) {
|
|
425
|
-
|
|
428
|
+
throw new Error(
|
|
429
|
+
`topic.uuidv7_identity_required: topic node ${ref} is missing a UUIDv7 globalId.`
|
|
430
|
+
);
|
|
426
431
|
}
|
|
432
|
+
const metadata = node.metadata ?? {};
|
|
427
433
|
return {
|
|
428
434
|
topicId: scopeKey,
|
|
429
435
|
projectId: asMappedProjectId(node),
|
|
430
|
-
source: "topic_node"
|
|
436
|
+
source: "topic_node",
|
|
437
|
+
tenantId: normalizeScopeValue(node.tenantId) ?? normalizeScopeValue(metadata.tenantId),
|
|
438
|
+
workspaceId: normalizeScopeValue(node.workspaceId) ?? normalizeScopeValue(metadata.workspaceId)
|
|
431
439
|
};
|
|
432
440
|
}
|
|
441
|
+
function canonicalTopicGlobalId(node) {
|
|
442
|
+
const globalId = normalizeScopeValue(node.globalId);
|
|
443
|
+
if (globalId && isUuidV7(globalId)) {
|
|
444
|
+
return globalId;
|
|
445
|
+
}
|
|
446
|
+
const topicId = normalizeScopeValue(node.topicId);
|
|
447
|
+
return topicId && isUuidV7(topicId) ? topicId : null;
|
|
448
|
+
}
|
|
449
|
+
function requireUuidV7TopicScope(field, value) {
|
|
450
|
+
if (!isUuidV7(value)) {
|
|
451
|
+
throw new Error(
|
|
452
|
+
`topic.uuidv7_required: ${field} must be a UUIDv7 public topic identifier.`
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
433
456
|
function asMappedProjectId(topic) {
|
|
434
457
|
if (!topic) {
|
|
435
458
|
return;
|
|
@@ -451,200 +474,25 @@ function normalizeScopeValue(value) {
|
|
|
451
474
|
const normalized = value.trim();
|
|
452
475
|
return normalized.length > 0 ? normalized : void 0;
|
|
453
476
|
}
|
|
454
|
-
function pickPrimaryTopic(candidates) {
|
|
455
|
-
return [...candidates].sort((a, b) => {
|
|
456
|
-
const depthA = a.depth ?? 9999;
|
|
457
|
-
const depthB = b.depth ?? 9999;
|
|
458
|
-
if (depthA !== depthB) {
|
|
459
|
-
return depthA - depthB;
|
|
460
|
-
}
|
|
461
|
-
const createdA = a.createdAt ?? Number.MAX_SAFE_INTEGER;
|
|
462
|
-
const createdB = b.createdAt ?? Number.MAX_SAFE_INTEGER;
|
|
463
|
-
if (createdA !== createdB) {
|
|
464
|
-
return createdA - createdB;
|
|
465
|
-
}
|
|
466
|
-
return String(a.name || "").localeCompare(String(b.name || ""));
|
|
467
|
-
})[0];
|
|
468
|
-
}
|
|
469
|
-
async function findTopicsByScopeAlias(ctx, scopeId) {
|
|
470
|
-
const query2 = ctx.db.query("topics");
|
|
471
|
-
try {
|
|
472
|
-
return await query2.withIndex(
|
|
473
|
-
"by_graph_scope_project",
|
|
474
|
-
(q) => q.eq(LEGACY_SCOPE_FIELD2, scopeId)
|
|
475
|
-
).collect();
|
|
476
|
-
} catch (error) {
|
|
477
|
-
debugGraphPrimitiveFallback(
|
|
478
|
-
"[topicScope] Failed to resolve scope alias via index",
|
|
479
|
-
{
|
|
480
|
-
error,
|
|
481
|
-
scopeId
|
|
482
|
-
}
|
|
483
|
-
);
|
|
484
|
-
const topics = await query2.collect();
|
|
485
|
-
return topics.filter((topic) => {
|
|
486
|
-
const normalizedGlobalId = normalizeScopeValue(topic.globalId);
|
|
487
|
-
const mappedProjectId = asMappedProjectId(topic);
|
|
488
|
-
return String(topic._id) === scopeId || normalizedGlobalId === scopeId || mappedProjectId === scopeId;
|
|
489
|
-
});
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
async function tryResolveHostTopicById(ctx, topicId) {
|
|
493
|
-
if (typeof ctx.runQuery !== "function") {
|
|
494
|
-
return null;
|
|
495
|
-
}
|
|
496
|
-
try {
|
|
497
|
-
return await ctx.runQuery(api.topics.get, {
|
|
498
|
-
id: topicId
|
|
499
|
-
}) ?? null;
|
|
500
|
-
} catch (error) {
|
|
501
|
-
debugGraphPrimitiveFallback(
|
|
502
|
-
"[topicScope] Failed to resolve topic by host query",
|
|
503
|
-
{
|
|
504
|
-
error,
|
|
505
|
-
topicId
|
|
506
|
-
}
|
|
507
|
-
);
|
|
508
|
-
return null;
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
async function tryResolveHostTopicByLegacyScope(ctx, legacyScopeId) {
|
|
512
|
-
if (typeof ctx.runQuery !== "function") {
|
|
513
|
-
return null;
|
|
514
|
-
}
|
|
515
|
-
try {
|
|
516
|
-
return await ctx.runQuery(api.topics.getByLegacyScopeId, {
|
|
517
|
-
projectId: legacyScopeId
|
|
518
|
-
}) ?? null;
|
|
519
|
-
} catch (error) {
|
|
520
|
-
debugGraphPrimitiveFallback(
|
|
521
|
-
"[topicScope] Failed to resolve topic by legacy scope",
|
|
522
|
-
{
|
|
523
|
-
error,
|
|
524
|
-
legacyScopeId
|
|
525
|
-
}
|
|
526
|
-
);
|
|
527
|
-
return null;
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
async function resolveInheritedWorkspaceScope(ctx, topic) {
|
|
531
|
-
const MAX_DEPTH = 10;
|
|
532
|
-
let tenantId = normalizeScopeValue(topic.tenantId);
|
|
533
|
-
let workspaceId = normalizeScopeValue(topic.workspaceId);
|
|
534
|
-
if (tenantId && workspaceId) {
|
|
535
|
-
return { tenantId, workspaceId };
|
|
536
|
-
}
|
|
537
|
-
let current = topic;
|
|
538
|
-
for (let i = 0; i < MAX_DEPTH && current?.parentTopicId; i++) {
|
|
539
|
-
current = await ctx.db.get(current.parentTopicId);
|
|
540
|
-
if (!current) {
|
|
541
|
-
break;
|
|
542
|
-
}
|
|
543
|
-
if (!tenantId) {
|
|
544
|
-
tenantId = normalizeScopeValue(current.tenantId);
|
|
545
|
-
}
|
|
546
|
-
if (!workspaceId) {
|
|
547
|
-
workspaceId = normalizeScopeValue(current.workspaceId);
|
|
548
|
-
}
|
|
549
|
-
if (tenantId && workspaceId) {
|
|
550
|
-
break;
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
return { tenantId, workspaceId };
|
|
554
|
-
}
|
|
555
477
|
async function resolveTopicProjectScope(ctx, args) {
|
|
556
478
|
if (args.topicId) {
|
|
557
479
|
return await resolveScopeFromTopicId(ctx, args.topicId);
|
|
558
480
|
}
|
|
559
481
|
if (args.projectId) {
|
|
560
|
-
|
|
482
|
+
throw new Error(
|
|
483
|
+
"topic.uuidv7_required: projectId scope aliases are retired; pass topicId as the UUIDv7 topic globalId."
|
|
484
|
+
);
|
|
561
485
|
}
|
|
562
|
-
throw new Error(
|
|
563
|
-
"Missing scope: provide topicId (preferred) or legacy projectId alias."
|
|
564
|
-
);
|
|
486
|
+
throw new Error("topic.uuidv7_required: Missing scope: provide topicId.");
|
|
565
487
|
}
|
|
566
488
|
async function resolveScopeFromTopicId(ctx, topicId) {
|
|
567
|
-
const
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
}
|
|
571
|
-
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, String(topicId));
|
|
489
|
+
const topicGlobalId = String(topicId);
|
|
490
|
+
requireUuidV7TopicScope("topicId", topicGlobalId);
|
|
491
|
+
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, topicGlobalId);
|
|
572
492
|
if (nodeScope) {
|
|
573
493
|
return nodeScope;
|
|
574
494
|
}
|
|
575
|
-
throw new Error(`Topic not found: ${
|
|
576
|
-
}
|
|
577
|
-
async function resolveTopicDocFromTopicId(ctx, topicId) {
|
|
578
|
-
const direct = await tryReadTopicDoc(ctx, topicId, {
|
|
579
|
-
failureLog: "[topicScope] Failed to load topic by direct id",
|
|
580
|
-
idLogKey: "topicId"
|
|
581
|
-
});
|
|
582
|
-
if (direct) {
|
|
583
|
-
return direct;
|
|
584
|
-
}
|
|
585
|
-
const hostTopic = await tryResolveHostTopicById(ctx, String(topicId));
|
|
586
|
-
if (hostTopic) {
|
|
587
|
-
return hostTopic;
|
|
588
|
-
}
|
|
589
|
-
return pickPrimaryTopic(await findTopicsByScopeAlias(ctx, String(topicId))) ?? null;
|
|
590
|
-
}
|
|
591
|
-
async function resolveScopeFromLegacyProjectId(ctx, legacyProjectId) {
|
|
592
|
-
const directTopic = await resolveDirectLegacyProjectTopic(
|
|
593
|
-
ctx,
|
|
594
|
-
legacyProjectId
|
|
595
|
-
);
|
|
596
|
-
if (directTopic) {
|
|
597
|
-
return await buildTopicScope(ctx, directTopic, "topic_inferred", {
|
|
598
|
-
fallbackProjectId: legacyProjectId
|
|
599
|
-
});
|
|
600
|
-
}
|
|
601
|
-
const primary = pickPrimaryTopic(
|
|
602
|
-
await findTopicsByScopeAlias(ctx, legacyProjectId)
|
|
603
|
-
);
|
|
604
|
-
if (primary) {
|
|
605
|
-
return await buildTopicScope(ctx, primary, "project_mapped_topic", {
|
|
606
|
-
fallbackProjectId: legacyProjectId
|
|
607
|
-
});
|
|
608
|
-
}
|
|
609
|
-
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, legacyProjectId);
|
|
610
|
-
if (nodeScope) {
|
|
611
|
-
return {
|
|
612
|
-
...nodeScope,
|
|
613
|
-
projectId: nodeScope.projectId ?? legacyProjectId
|
|
614
|
-
};
|
|
615
|
-
}
|
|
616
|
-
throw new Error(
|
|
617
|
-
`Legacy project scope ${legacyProjectId} has no mapped topic.`
|
|
618
|
-
);
|
|
619
|
-
}
|
|
620
|
-
async function resolveDirectLegacyProjectTopic(ctx, legacyProjectId) {
|
|
621
|
-
const directTopic = await tryReadTopicDoc(ctx, legacyProjectId, {
|
|
622
|
-
failureLog: "[topicScope] Failed to load direct project topic",
|
|
623
|
-
idLogKey: "projectId"
|
|
624
|
-
});
|
|
625
|
-
return directTopic ?? tryResolveHostTopicByLegacyScope(ctx, legacyProjectId);
|
|
626
|
-
}
|
|
627
|
-
async function tryReadTopicDoc(ctx, id, log) {
|
|
628
|
-
try {
|
|
629
|
-
return await ctx.db.get(id);
|
|
630
|
-
} catch (error) {
|
|
631
|
-
debugGraphPrimitiveFallback(log.failureLog, {
|
|
632
|
-
error,
|
|
633
|
-
[log.idLogKey]: id
|
|
634
|
-
});
|
|
635
|
-
return null;
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
async function buildTopicScope(ctx, topic, source, options = {}) {
|
|
639
|
-
const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
|
|
640
|
-
const mapped = asMappedProjectId(topic);
|
|
641
|
-
return {
|
|
642
|
-
topicId: topic._id,
|
|
643
|
-
...mapped || options.fallbackProjectId ? { projectId: mapped ?? options.fallbackProjectId } : {},
|
|
644
|
-
tenantId: inherited.tenantId,
|
|
645
|
-
workspaceId: inherited.workspaceId,
|
|
646
|
-
source
|
|
647
|
-
};
|
|
495
|
+
throw new Error(`Topic not found: ${topicGlobalId}`);
|
|
648
496
|
}
|
|
649
497
|
({
|
|
650
498
|
projectId: v.optional(v.string()),
|