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