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