@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, queryGeneric, mutationGeneric } 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 '@lucern/contracts/schema-helpers/spine/tables/epistemicNodes';
|
|
@@ -50,6 +50,9 @@ function debugGraphPrimitiveFallback(message, context) {
|
|
|
50
50
|
}
|
|
51
51
|
function insertEpistemicNode(ctx, doc) {
|
|
52
52
|
assertUuidV7Identity("epistemicNodes", doc.globalId);
|
|
53
|
+
if (doc.topicId !== void 0 && doc.topicId !== null) {
|
|
54
|
+
assertUuidV7Reference("epistemicNodes.topicId", doc.topicId);
|
|
55
|
+
}
|
|
53
56
|
return ctx.db.insert("epistemicNodes", doc);
|
|
54
57
|
}
|
|
55
58
|
|
|
@@ -427,6 +430,8 @@ function resolveGraphPrimitivesAppResolvers(_ctx) {
|
|
|
427
430
|
...resolverOverrides
|
|
428
431
|
};
|
|
429
432
|
}
|
|
433
|
+
|
|
434
|
+
// src/topicScope.ts
|
|
430
435
|
var LEGACY_SCOPE_FIELD2 = "graphScopeProjectId";
|
|
431
436
|
async function resolveTopicNodeScopeOrNull(ctx, ref) {
|
|
432
437
|
if (!ctx?.db || typeof ctx.db.query !== "function") {
|
|
@@ -447,16 +452,36 @@ async function resolveTopicNodeScopeOrNull(ctx, ref) {
|
|
|
447
452
|
if (!node) {
|
|
448
453
|
return null;
|
|
449
454
|
}
|
|
450
|
-
const scopeKey =
|
|
455
|
+
const scopeKey = canonicalTopicGlobalId(node);
|
|
451
456
|
if (!scopeKey) {
|
|
452
|
-
|
|
457
|
+
throw new Error(
|
|
458
|
+
`topic.uuidv7_identity_required: topic node ${ref} is missing a UUIDv7 globalId.`
|
|
459
|
+
);
|
|
453
460
|
}
|
|
461
|
+
const metadata = node.metadata ?? {};
|
|
454
462
|
return {
|
|
455
463
|
topicId: scopeKey,
|
|
456
464
|
projectId: asMappedProjectId(node),
|
|
457
|
-
source: "topic_node"
|
|
465
|
+
source: "topic_node",
|
|
466
|
+
tenantId: normalizeScopeValue(node.tenantId) ?? normalizeScopeValue(metadata.tenantId),
|
|
467
|
+
workspaceId: normalizeScopeValue(node.workspaceId) ?? normalizeScopeValue(metadata.workspaceId)
|
|
458
468
|
};
|
|
459
469
|
}
|
|
470
|
+
function canonicalTopicGlobalId(node) {
|
|
471
|
+
const globalId = normalizeScopeValue(node.globalId);
|
|
472
|
+
if (globalId && isUuidV7(globalId)) {
|
|
473
|
+
return globalId;
|
|
474
|
+
}
|
|
475
|
+
const topicId = normalizeScopeValue(node.topicId);
|
|
476
|
+
return topicId && isUuidV7(topicId) ? topicId : null;
|
|
477
|
+
}
|
|
478
|
+
function requireUuidV7TopicScope(field, value) {
|
|
479
|
+
if (!isUuidV7(value)) {
|
|
480
|
+
throw new Error(
|
|
481
|
+
`topic.uuidv7_required: ${field} must be a UUIDv7 public topic identifier.`
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
460
485
|
function asMappedProjectId(topic) {
|
|
461
486
|
if (!topic) {
|
|
462
487
|
return;
|
|
@@ -478,200 +503,25 @@ function normalizeScopeValue(value) {
|
|
|
478
503
|
const normalized = value.trim();
|
|
479
504
|
return normalized.length > 0 ? normalized : void 0;
|
|
480
505
|
}
|
|
481
|
-
function pickPrimaryTopic(candidates) {
|
|
482
|
-
return [...candidates].sort((a, b) => {
|
|
483
|
-
const depthA = a.depth ?? 9999;
|
|
484
|
-
const depthB = b.depth ?? 9999;
|
|
485
|
-
if (depthA !== depthB) {
|
|
486
|
-
return depthA - depthB;
|
|
487
|
-
}
|
|
488
|
-
const createdA = a.createdAt ?? Number.MAX_SAFE_INTEGER;
|
|
489
|
-
const createdB = b.createdAt ?? Number.MAX_SAFE_INTEGER;
|
|
490
|
-
if (createdA !== createdB) {
|
|
491
|
-
return createdA - createdB;
|
|
492
|
-
}
|
|
493
|
-
return String(a.name || "").localeCompare(String(b.name || ""));
|
|
494
|
-
})[0];
|
|
495
|
-
}
|
|
496
|
-
async function findTopicsByScopeAlias(ctx, scopeId) {
|
|
497
|
-
const query2 = ctx.db.query("topics");
|
|
498
|
-
try {
|
|
499
|
-
return await query2.withIndex(
|
|
500
|
-
"by_graph_scope_project",
|
|
501
|
-
(q) => q.eq(LEGACY_SCOPE_FIELD2, scopeId)
|
|
502
|
-
).collect();
|
|
503
|
-
} catch (error) {
|
|
504
|
-
debugGraphPrimitiveFallback(
|
|
505
|
-
"[topicScope] Failed to resolve scope alias via index",
|
|
506
|
-
{
|
|
507
|
-
error,
|
|
508
|
-
scopeId
|
|
509
|
-
}
|
|
510
|
-
);
|
|
511
|
-
const topics = await query2.collect();
|
|
512
|
-
return topics.filter((topic) => {
|
|
513
|
-
const normalizedGlobalId = normalizeScopeValue(topic.globalId);
|
|
514
|
-
const mappedProjectId = asMappedProjectId(topic);
|
|
515
|
-
return String(topic._id) === scopeId || normalizedGlobalId === scopeId || mappedProjectId === scopeId;
|
|
516
|
-
});
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
async function tryResolveHostTopicById(ctx, topicId) {
|
|
520
|
-
if (typeof ctx.runQuery !== "function") {
|
|
521
|
-
return null;
|
|
522
|
-
}
|
|
523
|
-
try {
|
|
524
|
-
return await ctx.runQuery(api.topics.get, {
|
|
525
|
-
id: topicId
|
|
526
|
-
}) ?? null;
|
|
527
|
-
} catch (error) {
|
|
528
|
-
debugGraphPrimitiveFallback(
|
|
529
|
-
"[topicScope] Failed to resolve topic by host query",
|
|
530
|
-
{
|
|
531
|
-
error,
|
|
532
|
-
topicId
|
|
533
|
-
}
|
|
534
|
-
);
|
|
535
|
-
return null;
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
async function tryResolveHostTopicByLegacyScope(ctx, legacyScopeId) {
|
|
539
|
-
if (typeof ctx.runQuery !== "function") {
|
|
540
|
-
return null;
|
|
541
|
-
}
|
|
542
|
-
try {
|
|
543
|
-
return await ctx.runQuery(api.topics.getByLegacyScopeId, {
|
|
544
|
-
projectId: legacyScopeId
|
|
545
|
-
}) ?? null;
|
|
546
|
-
} catch (error) {
|
|
547
|
-
debugGraphPrimitiveFallback(
|
|
548
|
-
"[topicScope] Failed to resolve topic by legacy scope",
|
|
549
|
-
{
|
|
550
|
-
error,
|
|
551
|
-
legacyScopeId
|
|
552
|
-
}
|
|
553
|
-
);
|
|
554
|
-
return null;
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
async function resolveInheritedWorkspaceScope(ctx, topic) {
|
|
558
|
-
const MAX_DEPTH = 10;
|
|
559
|
-
let tenantId = normalizeScopeValue(topic.tenantId);
|
|
560
|
-
let workspaceId = normalizeScopeValue(topic.workspaceId);
|
|
561
|
-
if (tenantId && workspaceId) {
|
|
562
|
-
return { tenantId, workspaceId };
|
|
563
|
-
}
|
|
564
|
-
let current = topic;
|
|
565
|
-
for (let i = 0; i < MAX_DEPTH && current?.parentTopicId; i++) {
|
|
566
|
-
current = await ctx.db.get(current.parentTopicId);
|
|
567
|
-
if (!current) {
|
|
568
|
-
break;
|
|
569
|
-
}
|
|
570
|
-
if (!tenantId) {
|
|
571
|
-
tenantId = normalizeScopeValue(current.tenantId);
|
|
572
|
-
}
|
|
573
|
-
if (!workspaceId) {
|
|
574
|
-
workspaceId = normalizeScopeValue(current.workspaceId);
|
|
575
|
-
}
|
|
576
|
-
if (tenantId && workspaceId) {
|
|
577
|
-
break;
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
return { tenantId, workspaceId };
|
|
581
|
-
}
|
|
582
506
|
async function resolveTopicProjectScope(ctx, args) {
|
|
583
507
|
if (args.topicId) {
|
|
584
508
|
return await resolveScopeFromTopicId(ctx, args.topicId);
|
|
585
509
|
}
|
|
586
510
|
if (args.projectId) {
|
|
587
|
-
|
|
511
|
+
throw new Error(
|
|
512
|
+
"topic.uuidv7_required: projectId scope aliases are retired; pass topicId as the UUIDv7 topic globalId."
|
|
513
|
+
);
|
|
588
514
|
}
|
|
589
|
-
throw new Error(
|
|
590
|
-
"Missing scope: provide topicId (preferred) or legacy projectId alias."
|
|
591
|
-
);
|
|
515
|
+
throw new Error("topic.uuidv7_required: Missing scope: provide topicId.");
|
|
592
516
|
}
|
|
593
517
|
async function resolveScopeFromTopicId(ctx, topicId) {
|
|
594
|
-
const
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
}
|
|
598
|
-
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, String(topicId));
|
|
518
|
+
const topicGlobalId = String(topicId);
|
|
519
|
+
requireUuidV7TopicScope("topicId", topicGlobalId);
|
|
520
|
+
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, topicGlobalId);
|
|
599
521
|
if (nodeScope) {
|
|
600
522
|
return nodeScope;
|
|
601
523
|
}
|
|
602
|
-
throw new Error(`Topic not found: ${
|
|
603
|
-
}
|
|
604
|
-
async function resolveTopicDocFromTopicId(ctx, topicId) {
|
|
605
|
-
const direct = await tryReadTopicDoc(ctx, topicId, {
|
|
606
|
-
failureLog: "[topicScope] Failed to load topic by direct id",
|
|
607
|
-
idLogKey: "topicId"
|
|
608
|
-
});
|
|
609
|
-
if (direct) {
|
|
610
|
-
return direct;
|
|
611
|
-
}
|
|
612
|
-
const hostTopic = await tryResolveHostTopicById(ctx, String(topicId));
|
|
613
|
-
if (hostTopic) {
|
|
614
|
-
return hostTopic;
|
|
615
|
-
}
|
|
616
|
-
return pickPrimaryTopic(await findTopicsByScopeAlias(ctx, String(topicId))) ?? null;
|
|
617
|
-
}
|
|
618
|
-
async function resolveScopeFromLegacyProjectId(ctx, legacyProjectId) {
|
|
619
|
-
const directTopic = await resolveDirectLegacyProjectTopic(
|
|
620
|
-
ctx,
|
|
621
|
-
legacyProjectId
|
|
622
|
-
);
|
|
623
|
-
if (directTopic) {
|
|
624
|
-
return await buildTopicScope(ctx, directTopic, "topic_inferred", {
|
|
625
|
-
fallbackProjectId: legacyProjectId
|
|
626
|
-
});
|
|
627
|
-
}
|
|
628
|
-
const primary = pickPrimaryTopic(
|
|
629
|
-
await findTopicsByScopeAlias(ctx, legacyProjectId)
|
|
630
|
-
);
|
|
631
|
-
if (primary) {
|
|
632
|
-
return await buildTopicScope(ctx, primary, "project_mapped_topic", {
|
|
633
|
-
fallbackProjectId: legacyProjectId
|
|
634
|
-
});
|
|
635
|
-
}
|
|
636
|
-
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, legacyProjectId);
|
|
637
|
-
if (nodeScope) {
|
|
638
|
-
return {
|
|
639
|
-
...nodeScope,
|
|
640
|
-
projectId: nodeScope.projectId ?? legacyProjectId
|
|
641
|
-
};
|
|
642
|
-
}
|
|
643
|
-
throw new Error(
|
|
644
|
-
`Legacy project scope ${legacyProjectId} has no mapped topic.`
|
|
645
|
-
);
|
|
646
|
-
}
|
|
647
|
-
async function resolveDirectLegacyProjectTopic(ctx, legacyProjectId) {
|
|
648
|
-
const directTopic = await tryReadTopicDoc(ctx, legacyProjectId, {
|
|
649
|
-
failureLog: "[topicScope] Failed to load direct project topic",
|
|
650
|
-
idLogKey: "projectId"
|
|
651
|
-
});
|
|
652
|
-
return directTopic ?? tryResolveHostTopicByLegacyScope(ctx, legacyProjectId);
|
|
653
|
-
}
|
|
654
|
-
async function tryReadTopicDoc(ctx, id, log) {
|
|
655
|
-
try {
|
|
656
|
-
return await ctx.db.get(id);
|
|
657
|
-
} catch (error) {
|
|
658
|
-
debugGraphPrimitiveFallback(log.failureLog, {
|
|
659
|
-
error,
|
|
660
|
-
[log.idLogKey]: id
|
|
661
|
-
});
|
|
662
|
-
return null;
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
async function buildTopicScope(ctx, topic, source, options = {}) {
|
|
666
|
-
const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
|
|
667
|
-
const mapped = asMappedProjectId(topic);
|
|
668
|
-
return {
|
|
669
|
-
topicId: topic._id,
|
|
670
|
-
...mapped || options.fallbackProjectId ? { projectId: mapped ?? options.fallbackProjectId } : {},
|
|
671
|
-
tenantId: inherited.tenantId,
|
|
672
|
-
workspaceId: inherited.workspaceId,
|
|
673
|
-
source
|
|
674
|
-
};
|
|
524
|
+
throw new Error(`Topic not found: ${topicGlobalId}`);
|
|
675
525
|
}
|
|
676
526
|
var optionalScopeArgs = {
|
|
677
527
|
projectId: v.optional(v.string()),
|
package/dist/epistemicSources.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, mutationGeneric, queryGeneric } 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';
|
|
@@ -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;
|
|
@@ -57,6 +56,9 @@ async function scheduleEmbeddingGeneration(args) {
|
|
|
57
56
|
}
|
|
58
57
|
function insertEpistemicNode(ctx, doc) {
|
|
59
58
|
assertUuidV7Identity("epistemicNodes", doc.globalId);
|
|
59
|
+
if (doc.topicId !== void 0 && doc.topicId !== null) {
|
|
60
|
+
assertUuidV7Reference("epistemicNodes.topicId", doc.topicId);
|
|
61
|
+
}
|
|
60
62
|
return ctx.db.insert("epistemicNodes", doc);
|
|
61
63
|
}
|
|
62
64
|
var LEGACY_SCOPE_FIELD = "graphScopeProjectId";
|
|
@@ -79,16 +81,36 @@ async function resolveTopicNodeScopeOrNull(ctx, ref) {
|
|
|
79
81
|
if (!node) {
|
|
80
82
|
return null;
|
|
81
83
|
}
|
|
82
|
-
const scopeKey =
|
|
84
|
+
const scopeKey = canonicalTopicGlobalId(node);
|
|
83
85
|
if (!scopeKey) {
|
|
84
|
-
|
|
86
|
+
throw new Error(
|
|
87
|
+
`topic.uuidv7_identity_required: topic node ${ref} is missing a UUIDv7 globalId.`
|
|
88
|
+
);
|
|
85
89
|
}
|
|
90
|
+
const metadata = node.metadata ?? {};
|
|
86
91
|
return {
|
|
87
92
|
topicId: scopeKey,
|
|
88
93
|
projectId: asMappedProjectId(node),
|
|
89
|
-
source: "topic_node"
|
|
94
|
+
source: "topic_node",
|
|
95
|
+
tenantId: normalizeScopeValue(node.tenantId) ?? normalizeScopeValue(metadata.tenantId),
|
|
96
|
+
workspaceId: normalizeScopeValue(node.workspaceId) ?? normalizeScopeValue(metadata.workspaceId)
|
|
90
97
|
};
|
|
91
98
|
}
|
|
99
|
+
function canonicalTopicGlobalId(node) {
|
|
100
|
+
const globalId = normalizeScopeValue(node.globalId);
|
|
101
|
+
if (globalId && isUuidV7(globalId)) {
|
|
102
|
+
return globalId;
|
|
103
|
+
}
|
|
104
|
+
const topicId = normalizeScopeValue(node.topicId);
|
|
105
|
+
return topicId && isUuidV7(topicId) ? topicId : null;
|
|
106
|
+
}
|
|
107
|
+
function requireUuidV7TopicScope(field, value) {
|
|
108
|
+
if (!isUuidV7(value)) {
|
|
109
|
+
throw new Error(
|
|
110
|
+
`topic.uuidv7_required: ${field} must be a UUIDv7 public topic identifier.`
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
92
114
|
function asMappedProjectId(topic) {
|
|
93
115
|
if (!topic) {
|
|
94
116
|
return;
|
|
@@ -110,200 +132,25 @@ function normalizeScopeValue(value) {
|
|
|
110
132
|
const normalized = value.trim();
|
|
111
133
|
return normalized.length > 0 ? normalized : void 0;
|
|
112
134
|
}
|
|
113
|
-
function pickPrimaryTopic(candidates) {
|
|
114
|
-
return [...candidates].sort((a, b) => {
|
|
115
|
-
const depthA = a.depth ?? 9999;
|
|
116
|
-
const depthB = b.depth ?? 9999;
|
|
117
|
-
if (depthA !== depthB) {
|
|
118
|
-
return depthA - depthB;
|
|
119
|
-
}
|
|
120
|
-
const createdA = a.createdAt ?? Number.MAX_SAFE_INTEGER;
|
|
121
|
-
const createdB = b.createdAt ?? Number.MAX_SAFE_INTEGER;
|
|
122
|
-
if (createdA !== createdB) {
|
|
123
|
-
return createdA - createdB;
|
|
124
|
-
}
|
|
125
|
-
return String(a.name || "").localeCompare(String(b.name || ""));
|
|
126
|
-
})[0];
|
|
127
|
-
}
|
|
128
|
-
async function findTopicsByScopeAlias(ctx, scopeId) {
|
|
129
|
-
const query2 = ctx.db.query("topics");
|
|
130
|
-
try {
|
|
131
|
-
return await query2.withIndex(
|
|
132
|
-
"by_graph_scope_project",
|
|
133
|
-
(q) => q.eq(LEGACY_SCOPE_FIELD, scopeId)
|
|
134
|
-
).collect();
|
|
135
|
-
} catch (error) {
|
|
136
|
-
debugGraphPrimitiveFallback(
|
|
137
|
-
"[topicScope] Failed to resolve scope alias via index",
|
|
138
|
-
{
|
|
139
|
-
error,
|
|
140
|
-
scopeId
|
|
141
|
-
}
|
|
142
|
-
);
|
|
143
|
-
const topics = await query2.collect();
|
|
144
|
-
return topics.filter((topic) => {
|
|
145
|
-
const normalizedGlobalId = normalizeScopeValue(topic.globalId);
|
|
146
|
-
const mappedProjectId = asMappedProjectId(topic);
|
|
147
|
-
return String(topic._id) === scopeId || normalizedGlobalId === scopeId || mappedProjectId === scopeId;
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
async function tryResolveHostTopicById(ctx, topicId) {
|
|
152
|
-
if (typeof ctx.runQuery !== "function") {
|
|
153
|
-
return null;
|
|
154
|
-
}
|
|
155
|
-
try {
|
|
156
|
-
return await ctx.runQuery(api.topics.get, {
|
|
157
|
-
id: topicId
|
|
158
|
-
}) ?? null;
|
|
159
|
-
} catch (error) {
|
|
160
|
-
debugGraphPrimitiveFallback(
|
|
161
|
-
"[topicScope] Failed to resolve topic by host query",
|
|
162
|
-
{
|
|
163
|
-
error,
|
|
164
|
-
topicId
|
|
165
|
-
}
|
|
166
|
-
);
|
|
167
|
-
return null;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
async function tryResolveHostTopicByLegacyScope(ctx, legacyScopeId) {
|
|
171
|
-
if (typeof ctx.runQuery !== "function") {
|
|
172
|
-
return null;
|
|
173
|
-
}
|
|
174
|
-
try {
|
|
175
|
-
return await ctx.runQuery(api.topics.getByLegacyScopeId, {
|
|
176
|
-
projectId: legacyScopeId
|
|
177
|
-
}) ?? null;
|
|
178
|
-
} catch (error) {
|
|
179
|
-
debugGraphPrimitiveFallback(
|
|
180
|
-
"[topicScope] Failed to resolve topic by legacy scope",
|
|
181
|
-
{
|
|
182
|
-
error,
|
|
183
|
-
legacyScopeId
|
|
184
|
-
}
|
|
185
|
-
);
|
|
186
|
-
return null;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
async function resolveInheritedWorkspaceScope(ctx, topic) {
|
|
190
|
-
const MAX_DEPTH = 10;
|
|
191
|
-
let tenantId = normalizeScopeValue(topic.tenantId);
|
|
192
|
-
let workspaceId = normalizeScopeValue(topic.workspaceId);
|
|
193
|
-
if (tenantId && workspaceId) {
|
|
194
|
-
return { tenantId, workspaceId };
|
|
195
|
-
}
|
|
196
|
-
let current = topic;
|
|
197
|
-
for (let i = 0; i < MAX_DEPTH && current?.parentTopicId; i++) {
|
|
198
|
-
current = await ctx.db.get(current.parentTopicId);
|
|
199
|
-
if (!current) {
|
|
200
|
-
break;
|
|
201
|
-
}
|
|
202
|
-
if (!tenantId) {
|
|
203
|
-
tenantId = normalizeScopeValue(current.tenantId);
|
|
204
|
-
}
|
|
205
|
-
if (!workspaceId) {
|
|
206
|
-
workspaceId = normalizeScopeValue(current.workspaceId);
|
|
207
|
-
}
|
|
208
|
-
if (tenantId && workspaceId) {
|
|
209
|
-
break;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
return { tenantId, workspaceId };
|
|
213
|
-
}
|
|
214
135
|
async function resolveTopicProjectScope(ctx, args) {
|
|
215
136
|
if (args.topicId) {
|
|
216
137
|
return await resolveScopeFromTopicId(ctx, args.topicId);
|
|
217
138
|
}
|
|
218
139
|
if (args.projectId) {
|
|
219
|
-
|
|
140
|
+
throw new Error(
|
|
141
|
+
"topic.uuidv7_required: projectId scope aliases are retired; pass topicId as the UUIDv7 topic globalId."
|
|
142
|
+
);
|
|
220
143
|
}
|
|
221
|
-
throw new Error(
|
|
222
|
-
"Missing scope: provide topicId (preferred) or legacy projectId alias."
|
|
223
|
-
);
|
|
144
|
+
throw new Error("topic.uuidv7_required: Missing scope: provide topicId.");
|
|
224
145
|
}
|
|
225
146
|
async function resolveScopeFromTopicId(ctx, topicId) {
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
}
|
|
230
|
-
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, String(topicId));
|
|
147
|
+
const topicGlobalId = String(topicId);
|
|
148
|
+
requireUuidV7TopicScope("topicId", topicGlobalId);
|
|
149
|
+
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, topicGlobalId);
|
|
231
150
|
if (nodeScope) {
|
|
232
151
|
return nodeScope;
|
|
233
152
|
}
|
|
234
|
-
throw new Error(`Topic not found: ${
|
|
235
|
-
}
|
|
236
|
-
async function resolveTopicDocFromTopicId(ctx, topicId) {
|
|
237
|
-
const direct = await tryReadTopicDoc(ctx, topicId, {
|
|
238
|
-
failureLog: "[topicScope] Failed to load topic by direct id",
|
|
239
|
-
idLogKey: "topicId"
|
|
240
|
-
});
|
|
241
|
-
if (direct) {
|
|
242
|
-
return direct;
|
|
243
|
-
}
|
|
244
|
-
const hostTopic = await tryResolveHostTopicById(ctx, String(topicId));
|
|
245
|
-
if (hostTopic) {
|
|
246
|
-
return hostTopic;
|
|
247
|
-
}
|
|
248
|
-
return pickPrimaryTopic(await findTopicsByScopeAlias(ctx, String(topicId))) ?? null;
|
|
249
|
-
}
|
|
250
|
-
async function resolveScopeFromLegacyProjectId(ctx, legacyProjectId) {
|
|
251
|
-
const directTopic = await resolveDirectLegacyProjectTopic(
|
|
252
|
-
ctx,
|
|
253
|
-
legacyProjectId
|
|
254
|
-
);
|
|
255
|
-
if (directTopic) {
|
|
256
|
-
return await buildTopicScope(ctx, directTopic, "topic_inferred", {
|
|
257
|
-
fallbackProjectId: legacyProjectId
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
const primary = pickPrimaryTopic(
|
|
261
|
-
await findTopicsByScopeAlias(ctx, legacyProjectId)
|
|
262
|
-
);
|
|
263
|
-
if (primary) {
|
|
264
|
-
return await buildTopicScope(ctx, primary, "project_mapped_topic", {
|
|
265
|
-
fallbackProjectId: legacyProjectId
|
|
266
|
-
});
|
|
267
|
-
}
|
|
268
|
-
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, legacyProjectId);
|
|
269
|
-
if (nodeScope) {
|
|
270
|
-
return {
|
|
271
|
-
...nodeScope,
|
|
272
|
-
projectId: nodeScope.projectId ?? legacyProjectId
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
throw new Error(
|
|
276
|
-
`Legacy project scope ${legacyProjectId} has no mapped topic.`
|
|
277
|
-
);
|
|
278
|
-
}
|
|
279
|
-
async function resolveDirectLegacyProjectTopic(ctx, legacyProjectId) {
|
|
280
|
-
const directTopic = await tryReadTopicDoc(ctx, legacyProjectId, {
|
|
281
|
-
failureLog: "[topicScope] Failed to load direct project topic",
|
|
282
|
-
idLogKey: "projectId"
|
|
283
|
-
});
|
|
284
|
-
return directTopic ?? tryResolveHostTopicByLegacyScope(ctx, legacyProjectId);
|
|
285
|
-
}
|
|
286
|
-
async function tryReadTopicDoc(ctx, id, log) {
|
|
287
|
-
try {
|
|
288
|
-
return await ctx.db.get(id);
|
|
289
|
-
} catch (error) {
|
|
290
|
-
debugGraphPrimitiveFallback(log.failureLog, {
|
|
291
|
-
error,
|
|
292
|
-
[log.idLogKey]: id
|
|
293
|
-
});
|
|
294
|
-
return null;
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
async function buildTopicScope(ctx, topic, source, options = {}) {
|
|
298
|
-
const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
|
|
299
|
-
const mapped = asMappedProjectId(topic);
|
|
300
|
-
return {
|
|
301
|
-
topicId: topic._id,
|
|
302
|
-
...mapped || options.fallbackProjectId ? { projectId: mapped ?? options.fallbackProjectId } : {},
|
|
303
|
-
tenantId: inherited.tenantId,
|
|
304
|
-
workspaceId: inherited.workspaceId,
|
|
305
|
-
source
|
|
306
|
-
};
|
|
153
|
+
throw new Error(`Topic not found: ${topicGlobalId}`);
|
|
307
154
|
}
|
|
308
155
|
({
|
|
309
156
|
projectId: v.optional(v.string()),
|
package/dist/index.d.ts
CHANGED
|
@@ -35,7 +35,7 @@ export { q as questionEvidenceLinks } from './questionEvidenceLinks-_nPRa-LY.js'
|
|
|
35
35
|
export { r as resolvers } from './resolvers-B1TIBmRO.js';
|
|
36
36
|
export { r as resolverTypes } from './resolverTypes-BOXPxLET.js';
|
|
37
37
|
export { t as textMatching } from './text-matching-DzFooju6.js';
|
|
38
|
-
export { t as topicScope } from './topicScope-
|
|
38
|
+
export { t as topicScope } from './topicScope-CL1IVOmv.js';
|
|
39
39
|
import './convex.js';
|
|
40
40
|
import '@lucern/access-control/convex';
|
|
41
41
|
import '@lucern/contracts/convex/unsafeAnyApi';
|