@lucern/graph-primitives 1.0.50 → 1.0.53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/beliefDecay.js +34 -186
- package/dist/beliefEvidenceLinks.js +32 -187
- package/dist/contradictions.js +34 -187
- package/dist/entityLifecycle.js +88 -205
- package/dist/epistemicAnswers.js +35 -187
- package/dist/epistemicBeliefs.admin.js +34 -187
- package/dist/epistemicBeliefs.backfills.js +34 -188
- package/dist/epistemicBeliefs.confidence.d.ts +1 -1
- package/dist/epistemicBeliefs.confidence.js +32 -188
- package/dist/epistemicBeliefs.core.js +37 -187
- package/dist/epistemicBeliefs.d.ts +1 -1
- package/dist/epistemicBeliefs.helpers.d.ts +1 -1
- package/dist/epistemicBeliefs.helpers.js +34 -186
- package/dist/epistemicBeliefs.internal.js +37 -187
- package/dist/epistemicBeliefs.js +37 -187
- package/dist/epistemicBeliefs.lifecycle.js +32 -188
- package/dist/epistemicBeliefs.links.js +34 -188
- package/dist/epistemicContracts.evaluators.js +34 -188
- package/dist/epistemicContracts.handlers.js +34 -188
- package/dist/epistemicContracts.js +34 -188
- package/dist/epistemicEdges.d.ts +1 -1
- package/dist/epistemicEdges.helpers.d.ts +1 -1
- package/dist/epistemicEdges.js +32 -187
- package/dist/epistemicEdges.mutations.js +34 -186
- package/dist/epistemicEdges.queries.js +35 -188
- package/dist/epistemicEdges.types.d.ts +1 -1
- package/dist/epistemicEvidence.d.ts +1 -1
- package/dist/epistemicEvidence.js +37 -187
- package/dist/epistemicEvidenceHelpers.d.ts +1 -1
- package/dist/epistemicEvidenceHelpers.js +34 -186
- package/dist/epistemicEvidenceMutations.js +37 -187
- package/dist/epistemicEvidenceQueries.js +34 -186
- package/dist/epistemicHelpers.js +4 -1
- package/dist/epistemicInsert.js +4 -1
- package/dist/epistemicNodeCreation.js +4 -1
- package/dist/epistemicNodes.helpers.d.ts +1 -1
- package/dist/epistemicNodes.internal.js +35 -188
- package/dist/epistemicNodes.js +37 -188
- package/dist/epistemicNodes.mutations.js +35 -188
- package/dist/epistemicNodes.queries.js +35 -188
- package/dist/epistemicQuestions.conviction.js +34 -186
- package/dist/epistemicQuestions.create.js +37 -187
- package/dist/epistemicQuestions.d.ts +1 -1
- package/dist/epistemicQuestions.evidence.js +37 -187
- package/dist/epistemicQuestions.helpers.d.ts +1 -1
- package/dist/epistemicQuestions.helpers.js +34 -186
- package/dist/epistemicQuestions.js +37 -187
- package/dist/epistemicQuestions.lifecycle.js +34 -186
- package/dist/epistemicQuestions.queries.js +34 -186
- package/dist/epistemicQuestions.sprint.js +35 -188
- package/dist/epistemicQuestions.tail.js +37 -187
- package/dist/epistemicSources.js +35 -188
- package/dist/index.d.ts +1 -1
- package/dist/index.js +98 -213
- package/dist/proof-attestation.json +1 -1
- package/dist/questionEvidenceLinks.js +34 -187
- package/dist/scopeResolverCompat.d.ts +1 -1
- package/dist/scopeResolverCompat.js +32 -193
- package/dist/topicOntologyResolver.d.ts +3 -3
- package/dist/topicOntologyResolver.js +57 -18
- package/dist/{topicScope-DJVa0mLa.d.ts → topicScope-CL1IVOmv.d.ts} +2 -2
- package/dist/topicScope.d.ts +1 -1
- package/dist/topicScope.js +32 -193
- package/dist/workflowBridge.js +32 -193
- package/dist/workspaceIsolation.d.ts +1 -1
- package/dist/workspaceIsolation.js +32 -193
- package/package.json +4 -4
package/dist/beliefDecay.js
CHANGED
|
@@ -5,6 +5,7 @@ import { permissiveReturn } from '@lucern/contracts/schema-helpers/validators';
|
|
|
5
5
|
import { v } from 'convex/values';
|
|
6
6
|
import { unsafeConvexAnyApi } from '@lucern/contracts/convex/unsafeAnyApi';
|
|
7
7
|
import { componentsGeneric, queryGeneric } from 'convex/server';
|
|
8
|
+
import { isUuidV7 } from '@lucern/contracts/ids';
|
|
8
9
|
|
|
9
10
|
// src/beliefDecay.ts
|
|
10
11
|
var unsafeApi = unsafeConvexAnyApi(
|
|
@@ -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
|
var optionalScopeArgs = {
|
|
650
498
|
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, queryGeneric } from 'convex/server';
|
|
6
|
-
import { generateGlobalId, assertUuidV7Identity, assertStorageEdgeVocabulary, assertUuidShapedEdgeEndpoint } from '@lucern/contracts/ids';
|
|
6
|
+
import { generateGlobalId, assertUuidV7Identity, assertStorageEdgeVocabulary, assertUuidShapedEdgeEndpoint, isUuidV7 } from '@lucern/contracts/ids';
|
|
7
7
|
import { assertEdgePolicyAllowed } from '@lucern/contracts/manifests/edge-policy-manifest';
|
|
8
8
|
import { edgePolicyManifest } from '@lucern/contracts/manifests/edge-policy-manifest.data';
|
|
9
9
|
|
|
@@ -715,16 +715,36 @@ async function resolveTopicNodeScopeOrNull(ctx, ref) {
|
|
|
715
715
|
if (!node) {
|
|
716
716
|
return null;
|
|
717
717
|
}
|
|
718
|
-
const scopeKey =
|
|
718
|
+
const scopeKey = canonicalTopicGlobalId(node);
|
|
719
719
|
if (!scopeKey) {
|
|
720
|
-
|
|
720
|
+
throw new Error(
|
|
721
|
+
`topic.uuidv7_identity_required: topic node ${ref} is missing a UUIDv7 globalId.`
|
|
722
|
+
);
|
|
721
723
|
}
|
|
724
|
+
const metadata = node.metadata ?? {};
|
|
722
725
|
return {
|
|
723
726
|
topicId: scopeKey,
|
|
724
727
|
projectId: asMappedProjectId(node),
|
|
725
|
-
source: "topic_node"
|
|
728
|
+
source: "topic_node",
|
|
729
|
+
tenantId: normalizeScopeValue(node.tenantId) ?? normalizeScopeValue(metadata.tenantId),
|
|
730
|
+
workspaceId: normalizeScopeValue(node.workspaceId) ?? normalizeScopeValue(metadata.workspaceId)
|
|
726
731
|
};
|
|
727
732
|
}
|
|
733
|
+
function canonicalTopicGlobalId(node) {
|
|
734
|
+
const globalId = normalizeScopeValue(node.globalId);
|
|
735
|
+
if (globalId && isUuidV7(globalId)) {
|
|
736
|
+
return globalId;
|
|
737
|
+
}
|
|
738
|
+
const topicId = normalizeScopeValue(node.topicId);
|
|
739
|
+
return topicId && isUuidV7(topicId) ? topicId : null;
|
|
740
|
+
}
|
|
741
|
+
function requireUuidV7TopicScope(field, value) {
|
|
742
|
+
if (!isUuidV7(value)) {
|
|
743
|
+
throw new Error(
|
|
744
|
+
`topic.uuidv7_required: ${field} must be a UUIDv7 public topic identifier.`
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
728
748
|
function asMappedProjectId(topic) {
|
|
729
749
|
if (!topic) {
|
|
730
750
|
return;
|
|
@@ -746,200 +766,25 @@ function normalizeScopeValue(value) {
|
|
|
746
766
|
const normalized = value.trim();
|
|
747
767
|
return normalized.length > 0 ? normalized : void 0;
|
|
748
768
|
}
|
|
749
|
-
function pickPrimaryTopic(candidates) {
|
|
750
|
-
return [...candidates].sort((a, b) => {
|
|
751
|
-
const depthA = a.depth ?? 9999;
|
|
752
|
-
const depthB = b.depth ?? 9999;
|
|
753
|
-
if (depthA !== depthB) {
|
|
754
|
-
return depthA - depthB;
|
|
755
|
-
}
|
|
756
|
-
const createdA = a.createdAt ?? Number.MAX_SAFE_INTEGER;
|
|
757
|
-
const createdB = b.createdAt ?? Number.MAX_SAFE_INTEGER;
|
|
758
|
-
if (createdA !== createdB) {
|
|
759
|
-
return createdA - createdB;
|
|
760
|
-
}
|
|
761
|
-
return String(a.name || "").localeCompare(String(b.name || ""));
|
|
762
|
-
})[0];
|
|
763
|
-
}
|
|
764
|
-
async function findTopicsByScopeAlias(ctx, scopeId) {
|
|
765
|
-
const query2 = ctx.db.query("topics");
|
|
766
|
-
try {
|
|
767
|
-
return await query2.withIndex(
|
|
768
|
-
"by_graph_scope_project",
|
|
769
|
-
(q) => q.eq(LEGACY_SCOPE_FIELD2, scopeId)
|
|
770
|
-
).collect();
|
|
771
|
-
} catch (error) {
|
|
772
|
-
debugGraphPrimitiveFallback(
|
|
773
|
-
"[topicScope] Failed to resolve scope alias via index",
|
|
774
|
-
{
|
|
775
|
-
error,
|
|
776
|
-
scopeId
|
|
777
|
-
}
|
|
778
|
-
);
|
|
779
|
-
const topics = await query2.collect();
|
|
780
|
-
return topics.filter((topic) => {
|
|
781
|
-
const normalizedGlobalId = normalizeScopeValue(topic.globalId);
|
|
782
|
-
const mappedProjectId = asMappedProjectId(topic);
|
|
783
|
-
return String(topic._id) === scopeId || normalizedGlobalId === scopeId || mappedProjectId === scopeId;
|
|
784
|
-
});
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
async function tryResolveHostTopicById(ctx, topicId) {
|
|
788
|
-
if (typeof ctx.runQuery !== "function") {
|
|
789
|
-
return null;
|
|
790
|
-
}
|
|
791
|
-
try {
|
|
792
|
-
return await ctx.runQuery(api.topics.get, {
|
|
793
|
-
id: topicId
|
|
794
|
-
}) ?? null;
|
|
795
|
-
} catch (error) {
|
|
796
|
-
debugGraphPrimitiveFallback(
|
|
797
|
-
"[topicScope] Failed to resolve topic by host query",
|
|
798
|
-
{
|
|
799
|
-
error,
|
|
800
|
-
topicId
|
|
801
|
-
}
|
|
802
|
-
);
|
|
803
|
-
return null;
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
async function tryResolveHostTopicByLegacyScope(ctx, legacyScopeId) {
|
|
807
|
-
if (typeof ctx.runQuery !== "function") {
|
|
808
|
-
return null;
|
|
809
|
-
}
|
|
810
|
-
try {
|
|
811
|
-
return await ctx.runQuery(api.topics.getByLegacyScopeId, {
|
|
812
|
-
projectId: legacyScopeId
|
|
813
|
-
}) ?? null;
|
|
814
|
-
} catch (error) {
|
|
815
|
-
debugGraphPrimitiveFallback(
|
|
816
|
-
"[topicScope] Failed to resolve topic by legacy scope",
|
|
817
|
-
{
|
|
818
|
-
error,
|
|
819
|
-
legacyScopeId
|
|
820
|
-
}
|
|
821
|
-
);
|
|
822
|
-
return null;
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
async function resolveInheritedWorkspaceScope(ctx, topic) {
|
|
826
|
-
const MAX_DEPTH = 10;
|
|
827
|
-
let tenantId = normalizeScopeValue(topic.tenantId);
|
|
828
|
-
let workspaceId = normalizeScopeValue(topic.workspaceId);
|
|
829
|
-
if (tenantId && workspaceId) {
|
|
830
|
-
return { tenantId, workspaceId };
|
|
831
|
-
}
|
|
832
|
-
let current = topic;
|
|
833
|
-
for (let i = 0; i < MAX_DEPTH && current?.parentTopicId; i++) {
|
|
834
|
-
current = await ctx.db.get(current.parentTopicId);
|
|
835
|
-
if (!current) {
|
|
836
|
-
break;
|
|
837
|
-
}
|
|
838
|
-
if (!tenantId) {
|
|
839
|
-
tenantId = normalizeScopeValue(current.tenantId);
|
|
840
|
-
}
|
|
841
|
-
if (!workspaceId) {
|
|
842
|
-
workspaceId = normalizeScopeValue(current.workspaceId);
|
|
843
|
-
}
|
|
844
|
-
if (tenantId && workspaceId) {
|
|
845
|
-
break;
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
return { tenantId, workspaceId };
|
|
849
|
-
}
|
|
850
769
|
async function resolveTopicProjectScope(ctx, args) {
|
|
851
770
|
if (args.topicId) {
|
|
852
771
|
return await resolveScopeFromTopicId(ctx, args.topicId);
|
|
853
772
|
}
|
|
854
773
|
if (args.projectId) {
|
|
855
|
-
|
|
774
|
+
throw new Error(
|
|
775
|
+
"topic.uuidv7_required: projectId scope aliases are retired; pass topicId as the UUIDv7 topic globalId."
|
|
776
|
+
);
|
|
856
777
|
}
|
|
857
|
-
throw new Error(
|
|
858
|
-
"Missing scope: provide topicId (preferred) or legacy projectId alias."
|
|
859
|
-
);
|
|
778
|
+
throw new Error("topic.uuidv7_required: Missing scope: provide topicId.");
|
|
860
779
|
}
|
|
861
780
|
async function resolveScopeFromTopicId(ctx, topicId) {
|
|
862
|
-
const
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
}
|
|
866
|
-
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, String(topicId));
|
|
781
|
+
const topicGlobalId = String(topicId);
|
|
782
|
+
requireUuidV7TopicScope("topicId", topicGlobalId);
|
|
783
|
+
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, topicGlobalId);
|
|
867
784
|
if (nodeScope) {
|
|
868
785
|
return nodeScope;
|
|
869
786
|
}
|
|
870
|
-
throw new Error(`Topic not found: ${
|
|
871
|
-
}
|
|
872
|
-
async function resolveTopicDocFromTopicId(ctx, topicId) {
|
|
873
|
-
const direct = await tryReadTopicDoc(ctx, topicId, {
|
|
874
|
-
failureLog: "[topicScope] Failed to load topic by direct id",
|
|
875
|
-
idLogKey: "topicId"
|
|
876
|
-
});
|
|
877
|
-
if (direct) {
|
|
878
|
-
return direct;
|
|
879
|
-
}
|
|
880
|
-
const hostTopic = await tryResolveHostTopicById(ctx, String(topicId));
|
|
881
|
-
if (hostTopic) {
|
|
882
|
-
return hostTopic;
|
|
883
|
-
}
|
|
884
|
-
return pickPrimaryTopic(await findTopicsByScopeAlias(ctx, String(topicId))) ?? null;
|
|
885
|
-
}
|
|
886
|
-
async function resolveScopeFromLegacyProjectId(ctx, legacyProjectId) {
|
|
887
|
-
const directTopic = await resolveDirectLegacyProjectTopic(
|
|
888
|
-
ctx,
|
|
889
|
-
legacyProjectId
|
|
890
|
-
);
|
|
891
|
-
if (directTopic) {
|
|
892
|
-
return await buildTopicScope(ctx, directTopic, "topic_inferred", {
|
|
893
|
-
fallbackProjectId: legacyProjectId
|
|
894
|
-
});
|
|
895
|
-
}
|
|
896
|
-
const primary = pickPrimaryTopic(
|
|
897
|
-
await findTopicsByScopeAlias(ctx, legacyProjectId)
|
|
898
|
-
);
|
|
899
|
-
if (primary) {
|
|
900
|
-
return await buildTopicScope(ctx, primary, "project_mapped_topic", {
|
|
901
|
-
fallbackProjectId: legacyProjectId
|
|
902
|
-
});
|
|
903
|
-
}
|
|
904
|
-
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, legacyProjectId);
|
|
905
|
-
if (nodeScope) {
|
|
906
|
-
return {
|
|
907
|
-
...nodeScope,
|
|
908
|
-
projectId: nodeScope.projectId ?? legacyProjectId
|
|
909
|
-
};
|
|
910
|
-
}
|
|
911
|
-
throw new Error(
|
|
912
|
-
`Legacy project scope ${legacyProjectId} has no mapped topic.`
|
|
913
|
-
);
|
|
914
|
-
}
|
|
915
|
-
async function resolveDirectLegacyProjectTopic(ctx, legacyProjectId) {
|
|
916
|
-
const directTopic = await tryReadTopicDoc(ctx, legacyProjectId, {
|
|
917
|
-
failureLog: "[topicScope] Failed to load direct project topic",
|
|
918
|
-
idLogKey: "projectId"
|
|
919
|
-
});
|
|
920
|
-
return directTopic ?? tryResolveHostTopicByLegacyScope(ctx, legacyProjectId);
|
|
921
|
-
}
|
|
922
|
-
async function tryReadTopicDoc(ctx, id, log) {
|
|
923
|
-
try {
|
|
924
|
-
return await ctx.db.get(id);
|
|
925
|
-
} catch (error) {
|
|
926
|
-
debugGraphPrimitiveFallback(log.failureLog, {
|
|
927
|
-
error,
|
|
928
|
-
[log.idLogKey]: id
|
|
929
|
-
});
|
|
930
|
-
return null;
|
|
931
|
-
}
|
|
932
|
-
}
|
|
933
|
-
async function buildTopicScope(ctx, topic, source, options = {}) {
|
|
934
|
-
const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
|
|
935
|
-
const mapped = asMappedProjectId(topic);
|
|
936
|
-
return {
|
|
937
|
-
topicId: topic._id,
|
|
938
|
-
...mapped || options.fallbackProjectId ? { projectId: mapped ?? options.fallbackProjectId } : {},
|
|
939
|
-
tenantId: inherited.tenantId,
|
|
940
|
-
workspaceId: inherited.workspaceId,
|
|
941
|
-
source
|
|
942
|
-
};
|
|
787
|
+
throw new Error(`Topic not found: ${topicGlobalId}`);
|
|
943
788
|
}
|
|
944
789
|
var optionalScopeArgs = {
|
|
945
790
|
projectId: v.optional(v.string()),
|