@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
package/dist/index.js
CHANGED
|
@@ -7,8 +7,8 @@ import { componentsGeneric, mutationGeneric, queryGeneric, internalMutationGener
|
|
|
7
7
|
import { canAudienceClassAccess, normalizeAudienceKey, classFromAudienceKey } from '@lucern/access-control/audience';
|
|
8
8
|
import { getCurrentUserId } from '@lucern/access-control/auth';
|
|
9
9
|
import { throwStructuredMutationError } from '@lucern/access-control/structuredMutationError';
|
|
10
|
+
import { generateUuidV7, generateGlobalId, assertUuidV7Identity, assertUuidV7Reference, assertStorageEdgeVocabulary, isUuidV7, assertUuidShapedEdgeEndpoint } from '@lucern/contracts/ids';
|
|
10
11
|
import { isNodeType, getLayerForNodeType } from '@lucern/contracts/schema-helpers/spine/tables/epistemicNodes';
|
|
11
|
-
import { generateUuidV7, generateGlobalId, assertUuidV7Identity, assertStorageEdgeVocabulary, isUuidV7, assertUuidShapedEdgeEndpoint } from '@lucern/contracts/ids';
|
|
12
12
|
import { assertSchemaEnumValue } from '@lucern/contracts/schema-helpers/enumValidation';
|
|
13
13
|
import { assertEdgePolicyAllowed } from '@lucern/contracts/manifests/edge-policy-manifest';
|
|
14
14
|
import { edgePolicyManifest } from '@lucern/contracts/manifests/edge-policy-manifest.data';
|
|
@@ -1569,6 +1569,16 @@ __export(topicScope_exports, {
|
|
|
1569
1569
|
readMaterializedTopicTableId: () => readMaterializedTopicTableId,
|
|
1570
1570
|
resolveTopicProjectScope: () => resolveTopicProjectScope
|
|
1571
1571
|
});
|
|
1572
|
+
|
|
1573
|
+
// src/globalId.ts
|
|
1574
|
+
var globalId_exports = {};
|
|
1575
|
+
__export(globalId_exports, {
|
|
1576
|
+
generateGlobalId: () => generateGlobalId,
|
|
1577
|
+
generateUuidV7: () => generateUuidV7,
|
|
1578
|
+
isUuidV7: () => isUuidV7
|
|
1579
|
+
});
|
|
1580
|
+
|
|
1581
|
+
// src/topicScope.ts
|
|
1572
1582
|
var LEGACY_SCOPE_FIELD2 = "graphScopeProjectId";
|
|
1573
1583
|
async function resolveTopicNodeScopeOrNull(ctx, ref) {
|
|
1574
1584
|
if (!ctx?.db || typeof ctx.db.query !== "function") {
|
|
@@ -1589,16 +1599,36 @@ async function resolveTopicNodeScopeOrNull(ctx, ref) {
|
|
|
1589
1599
|
if (!node) {
|
|
1590
1600
|
return null;
|
|
1591
1601
|
}
|
|
1592
|
-
const scopeKey =
|
|
1602
|
+
const scopeKey = canonicalTopicGlobalId(node);
|
|
1593
1603
|
if (!scopeKey) {
|
|
1594
|
-
|
|
1604
|
+
throw new Error(
|
|
1605
|
+
`topic.uuidv7_identity_required: topic node ${ref} is missing a UUIDv7 globalId.`
|
|
1606
|
+
);
|
|
1595
1607
|
}
|
|
1608
|
+
const metadata = node.metadata ?? {};
|
|
1596
1609
|
return {
|
|
1597
1610
|
topicId: scopeKey,
|
|
1598
1611
|
projectId: asMappedProjectId(node),
|
|
1599
|
-
source: "topic_node"
|
|
1612
|
+
source: "topic_node",
|
|
1613
|
+
tenantId: normalizeScopeValue(node.tenantId) ?? normalizeScopeValue(metadata.tenantId),
|
|
1614
|
+
workspaceId: normalizeScopeValue(node.workspaceId) ?? normalizeScopeValue(metadata.workspaceId)
|
|
1600
1615
|
};
|
|
1601
1616
|
}
|
|
1617
|
+
function canonicalTopicGlobalId(node) {
|
|
1618
|
+
const globalId = normalizeScopeValue(node.globalId);
|
|
1619
|
+
if (globalId && isUuidV7(globalId)) {
|
|
1620
|
+
return globalId;
|
|
1621
|
+
}
|
|
1622
|
+
const topicId2 = normalizeScopeValue(node.topicId);
|
|
1623
|
+
return topicId2 && isUuidV7(topicId2) ? topicId2 : null;
|
|
1624
|
+
}
|
|
1625
|
+
function requireUuidV7TopicScope(field, value) {
|
|
1626
|
+
if (!isUuidV7(value)) {
|
|
1627
|
+
throw new Error(
|
|
1628
|
+
`topic.uuidv7_required: ${field} must be a UUIDv7 public topic identifier.`
|
|
1629
|
+
);
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1602
1632
|
function asMappedProjectId(topic) {
|
|
1603
1633
|
if (!topic) {
|
|
1604
1634
|
return;
|
|
@@ -1620,82 +1650,6 @@ function normalizeScopeValue(value) {
|
|
|
1620
1650
|
const normalized = value.trim();
|
|
1621
1651
|
return normalized.length > 0 ? normalized : void 0;
|
|
1622
1652
|
}
|
|
1623
|
-
function pickPrimaryTopic(candidates) {
|
|
1624
|
-
return [...candidates].sort((a, b) => {
|
|
1625
|
-
const depthA = a.depth ?? 9999;
|
|
1626
|
-
const depthB = b.depth ?? 9999;
|
|
1627
|
-
if (depthA !== depthB) {
|
|
1628
|
-
return depthA - depthB;
|
|
1629
|
-
}
|
|
1630
|
-
const createdA = a.createdAt ?? Number.MAX_SAFE_INTEGER;
|
|
1631
|
-
const createdB = b.createdAt ?? Number.MAX_SAFE_INTEGER;
|
|
1632
|
-
if (createdA !== createdB) {
|
|
1633
|
-
return createdA - createdB;
|
|
1634
|
-
}
|
|
1635
|
-
return String(a.name || "").localeCompare(String(b.name || ""));
|
|
1636
|
-
})[0];
|
|
1637
|
-
}
|
|
1638
|
-
async function findTopicsByScopeAlias(ctx, scopeId) {
|
|
1639
|
-
const query2 = ctx.db.query("topics");
|
|
1640
|
-
try {
|
|
1641
|
-
return await query2.withIndex(
|
|
1642
|
-
"by_graph_scope_project",
|
|
1643
|
-
(q) => q.eq(LEGACY_SCOPE_FIELD2, scopeId)
|
|
1644
|
-
).collect();
|
|
1645
|
-
} catch (error) {
|
|
1646
|
-
debugGraphPrimitiveFallback(
|
|
1647
|
-
"[topicScope] Failed to resolve scope alias via index",
|
|
1648
|
-
{
|
|
1649
|
-
error,
|
|
1650
|
-
scopeId
|
|
1651
|
-
}
|
|
1652
|
-
);
|
|
1653
|
-
const topics = await query2.collect();
|
|
1654
|
-
return topics.filter((topic) => {
|
|
1655
|
-
const normalizedGlobalId = normalizeScopeValue(topic.globalId);
|
|
1656
|
-
const mappedProjectId = asMappedProjectId(topic);
|
|
1657
|
-
return String(topic._id) === scopeId || normalizedGlobalId === scopeId || mappedProjectId === scopeId;
|
|
1658
|
-
});
|
|
1659
|
-
}
|
|
1660
|
-
}
|
|
1661
|
-
async function tryResolveHostTopicById(ctx, topicId2) {
|
|
1662
|
-
if (typeof ctx.runQuery !== "function") {
|
|
1663
|
-
return null;
|
|
1664
|
-
}
|
|
1665
|
-
try {
|
|
1666
|
-
return await ctx.runQuery(api.topics.get, {
|
|
1667
|
-
id: topicId2
|
|
1668
|
-
}) ?? null;
|
|
1669
|
-
} catch (error) {
|
|
1670
|
-
debugGraphPrimitiveFallback(
|
|
1671
|
-
"[topicScope] Failed to resolve topic by host query",
|
|
1672
|
-
{
|
|
1673
|
-
error,
|
|
1674
|
-
topicId: topicId2
|
|
1675
|
-
}
|
|
1676
|
-
);
|
|
1677
|
-
return null;
|
|
1678
|
-
}
|
|
1679
|
-
}
|
|
1680
|
-
async function tryResolveHostTopicByLegacyScope(ctx, legacyScopeId) {
|
|
1681
|
-
if (typeof ctx.runQuery !== "function") {
|
|
1682
|
-
return null;
|
|
1683
|
-
}
|
|
1684
|
-
try {
|
|
1685
|
-
return await ctx.runQuery(api.topics.getByLegacyScopeId, {
|
|
1686
|
-
projectId: legacyScopeId
|
|
1687
|
-
}) ?? null;
|
|
1688
|
-
} catch (error) {
|
|
1689
|
-
debugGraphPrimitiveFallback(
|
|
1690
|
-
"[topicScope] Failed to resolve topic by legacy scope",
|
|
1691
|
-
{
|
|
1692
|
-
error,
|
|
1693
|
-
legacyScopeId
|
|
1694
|
-
}
|
|
1695
|
-
);
|
|
1696
|
-
return null;
|
|
1697
|
-
}
|
|
1698
|
-
}
|
|
1699
1653
|
function readMaterializedTopicTableId(topicNode) {
|
|
1700
1654
|
if (!topicNode) {
|
|
1701
1655
|
return;
|
|
@@ -1704,124 +1658,25 @@ function readMaterializedTopicTableId(topicNode) {
|
|
|
1704
1658
|
const topicTableId = metadata.topicTableId || metadata.topicId;
|
|
1705
1659
|
return typeof topicTableId === "string" && topicTableId.trim().length > 0 ? topicTableId.trim() : void 0;
|
|
1706
1660
|
}
|
|
1707
|
-
async function resolveInheritedWorkspaceScope(ctx, topic) {
|
|
1708
|
-
const MAX_DEPTH = 10;
|
|
1709
|
-
let tenantId = normalizeScopeValue(topic.tenantId);
|
|
1710
|
-
let workspaceId = normalizeScopeValue(topic.workspaceId);
|
|
1711
|
-
if (tenantId && workspaceId) {
|
|
1712
|
-
return { tenantId, workspaceId };
|
|
1713
|
-
}
|
|
1714
|
-
let current = topic;
|
|
1715
|
-
for (let i = 0; i < MAX_DEPTH && current?.parentTopicId; i++) {
|
|
1716
|
-
current = await ctx.db.get(current.parentTopicId);
|
|
1717
|
-
if (!current) {
|
|
1718
|
-
break;
|
|
1719
|
-
}
|
|
1720
|
-
if (!tenantId) {
|
|
1721
|
-
tenantId = normalizeScopeValue(current.tenantId);
|
|
1722
|
-
}
|
|
1723
|
-
if (!workspaceId) {
|
|
1724
|
-
workspaceId = normalizeScopeValue(current.workspaceId);
|
|
1725
|
-
}
|
|
1726
|
-
if (tenantId && workspaceId) {
|
|
1727
|
-
break;
|
|
1728
|
-
}
|
|
1729
|
-
}
|
|
1730
|
-
return { tenantId, workspaceId };
|
|
1731
|
-
}
|
|
1732
1661
|
async function resolveTopicProjectScope(ctx, args) {
|
|
1733
1662
|
if (args.topicId) {
|
|
1734
1663
|
return await resolveScopeFromTopicId(ctx, args.topicId);
|
|
1735
1664
|
}
|
|
1736
1665
|
if (args.projectId) {
|
|
1737
|
-
|
|
1666
|
+
throw new Error(
|
|
1667
|
+
"topic.uuidv7_required: projectId scope aliases are retired; pass topicId as the UUIDv7 topic globalId."
|
|
1668
|
+
);
|
|
1738
1669
|
}
|
|
1739
|
-
throw new Error(
|
|
1740
|
-
"Missing scope: provide topicId (preferred) or legacy projectId alias."
|
|
1741
|
-
);
|
|
1670
|
+
throw new Error("topic.uuidv7_required: Missing scope: provide topicId.");
|
|
1742
1671
|
}
|
|
1743
1672
|
async function resolveScopeFromTopicId(ctx, topicId2) {
|
|
1744
|
-
const
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
}
|
|
1748
|
-
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, String(topicId2));
|
|
1673
|
+
const topicGlobalId = String(topicId2);
|
|
1674
|
+
requireUuidV7TopicScope("topicId", topicGlobalId);
|
|
1675
|
+
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, topicGlobalId);
|
|
1749
1676
|
if (nodeScope) {
|
|
1750
1677
|
return nodeScope;
|
|
1751
1678
|
}
|
|
1752
|
-
throw new Error(`Topic not found: ${
|
|
1753
|
-
}
|
|
1754
|
-
async function resolveTopicDocFromTopicId(ctx, topicId2) {
|
|
1755
|
-
const direct = await tryReadTopicDoc(ctx, topicId2, {
|
|
1756
|
-
failureLog: "[topicScope] Failed to load topic by direct id",
|
|
1757
|
-
idLogKey: "topicId"
|
|
1758
|
-
});
|
|
1759
|
-
if (direct) {
|
|
1760
|
-
return direct;
|
|
1761
|
-
}
|
|
1762
|
-
const hostTopic = await tryResolveHostTopicById(ctx, String(topicId2));
|
|
1763
|
-
if (hostTopic) {
|
|
1764
|
-
return hostTopic;
|
|
1765
|
-
}
|
|
1766
|
-
return pickPrimaryTopic(await findTopicsByScopeAlias(ctx, String(topicId2))) ?? null;
|
|
1767
|
-
}
|
|
1768
|
-
async function resolveScopeFromLegacyProjectId(ctx, legacyProjectId) {
|
|
1769
|
-
const directTopic = await resolveDirectLegacyProjectTopic(
|
|
1770
|
-
ctx,
|
|
1771
|
-
legacyProjectId
|
|
1772
|
-
);
|
|
1773
|
-
if (directTopic) {
|
|
1774
|
-
return await buildTopicScope(ctx, directTopic, "topic_inferred", {
|
|
1775
|
-
fallbackProjectId: legacyProjectId
|
|
1776
|
-
});
|
|
1777
|
-
}
|
|
1778
|
-
const primary = pickPrimaryTopic(
|
|
1779
|
-
await findTopicsByScopeAlias(ctx, legacyProjectId)
|
|
1780
|
-
);
|
|
1781
|
-
if (primary) {
|
|
1782
|
-
return await buildTopicScope(ctx, primary, "project_mapped_topic", {
|
|
1783
|
-
fallbackProjectId: legacyProjectId
|
|
1784
|
-
});
|
|
1785
|
-
}
|
|
1786
|
-
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, legacyProjectId);
|
|
1787
|
-
if (nodeScope) {
|
|
1788
|
-
return {
|
|
1789
|
-
...nodeScope,
|
|
1790
|
-
projectId: nodeScope.projectId ?? legacyProjectId
|
|
1791
|
-
};
|
|
1792
|
-
}
|
|
1793
|
-
throw new Error(
|
|
1794
|
-
`Legacy project scope ${legacyProjectId} has no mapped topic.`
|
|
1795
|
-
);
|
|
1796
|
-
}
|
|
1797
|
-
async function resolveDirectLegacyProjectTopic(ctx, legacyProjectId) {
|
|
1798
|
-
const directTopic = await tryReadTopicDoc(ctx, legacyProjectId, {
|
|
1799
|
-
failureLog: "[topicScope] Failed to load direct project topic",
|
|
1800
|
-
idLogKey: "projectId"
|
|
1801
|
-
});
|
|
1802
|
-
return directTopic ?? tryResolveHostTopicByLegacyScope(ctx, legacyProjectId);
|
|
1803
|
-
}
|
|
1804
|
-
async function tryReadTopicDoc(ctx, id, log) {
|
|
1805
|
-
try {
|
|
1806
|
-
return await ctx.db.get(id);
|
|
1807
|
-
} catch (error) {
|
|
1808
|
-
debugGraphPrimitiveFallback(log.failureLog, {
|
|
1809
|
-
error,
|
|
1810
|
-
[log.idLogKey]: id
|
|
1811
|
-
});
|
|
1812
|
-
return null;
|
|
1813
|
-
}
|
|
1814
|
-
}
|
|
1815
|
-
async function buildTopicScope(ctx, topic, source, options = {}) {
|
|
1816
|
-
const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
|
|
1817
|
-
const mapped = asMappedProjectId(topic);
|
|
1818
|
-
return {
|
|
1819
|
-
topicId: topic._id,
|
|
1820
|
-
...mapped || options.fallbackProjectId ? { projectId: mapped ?? options.fallbackProjectId } : {},
|
|
1821
|
-
tenantId: inherited.tenantId,
|
|
1822
|
-
workspaceId: inherited.workspaceId,
|
|
1823
|
-
source
|
|
1824
|
-
};
|
|
1679
|
+
throw new Error(`Topic not found: ${topicGlobalId}`);
|
|
1825
1680
|
}
|
|
1826
1681
|
var optionalScopeArgs = {
|
|
1827
1682
|
projectId: v.optional(v.string()),
|
|
@@ -4494,6 +4349,9 @@ async function resolveForkTriggerEvidence(ctx, args) {
|
|
|
4494
4349
|
}
|
|
4495
4350
|
function insertEpistemicNode(ctx, doc) {
|
|
4496
4351
|
assertUuidV7Identity("epistemicNodes", doc.globalId);
|
|
4352
|
+
if (doc.topicId !== void 0 && doc.topicId !== null) {
|
|
4353
|
+
assertUuidV7Reference("epistemicNodes.topicId", doc.topicId);
|
|
4354
|
+
}
|
|
4497
4355
|
return ctx.db.insert("epistemicNodes", doc);
|
|
4498
4356
|
}
|
|
4499
4357
|
async function assertExistingNodeEndpoint(ctx, endpointRole, endpoint) {
|
|
@@ -4542,14 +4400,6 @@ async function insertEpistemicEdge(ctx, doc) {
|
|
|
4542
4400
|
return ctx.db.insert("epistemicEdges", doc);
|
|
4543
4401
|
}
|
|
4544
4402
|
|
|
4545
|
-
// src/globalId.ts
|
|
4546
|
-
var globalId_exports = {};
|
|
4547
|
-
__export(globalId_exports, {
|
|
4548
|
-
generateGlobalId: () => generateGlobalId,
|
|
4549
|
-
generateUuidV7: () => generateUuidV7,
|
|
4550
|
-
isUuidV7: () => isUuidV7
|
|
4551
|
-
});
|
|
4552
|
-
|
|
4553
4403
|
// src/epistemicBeliefs.topicAnchor.ts
|
|
4554
4404
|
function cleanString(value) {
|
|
4555
4405
|
return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
|
|
@@ -11082,8 +10932,47 @@ async function isRegisteredEntityType(ctx, nodeType, tenantId) {
|
|
|
11082
10932
|
|
|
11083
10933
|
// src/topicOntologyResolver.ts
|
|
11084
10934
|
var MAX_RESOLUTION_DEPTH = 10;
|
|
11085
|
-
async function loadTopic(ctx,
|
|
11086
|
-
|
|
10935
|
+
async function loadTopic(ctx, topicGlobalId) {
|
|
10936
|
+
requireUuidV7TopicGlobalId(topicGlobalId);
|
|
10937
|
+
const topic = await ctx.db.query("epistemicNodes").withIndex("by_globalId", (q) => q.eq("globalId", topicGlobalId)).first();
|
|
10938
|
+
return requireTopicGlobalId2(topic);
|
|
10939
|
+
}
|
|
10940
|
+
function requireUuidV7TopicGlobalId(topicGlobalId) {
|
|
10941
|
+
if (!isUuidV7(topicGlobalId)) {
|
|
10942
|
+
throw new Error(
|
|
10943
|
+
`topic.uuidv7_required: topic ontology resolution requires a UUIDv7 topic globalId, received ${topicGlobalId}.`
|
|
10944
|
+
);
|
|
10945
|
+
}
|
|
10946
|
+
}
|
|
10947
|
+
function requireTopicGlobalId2(topic) {
|
|
10948
|
+
if (!topic) {
|
|
10949
|
+
return null;
|
|
10950
|
+
}
|
|
10951
|
+
if (topic.nodeType !== "topic") {
|
|
10952
|
+
return null;
|
|
10953
|
+
}
|
|
10954
|
+
if (!isUuidV7(topic.globalId)) {
|
|
10955
|
+
throw new Error(
|
|
10956
|
+
`topic.uuidv7_required: topic node ${String(topic._id)} is missing a UUIDv7 globalId.`
|
|
10957
|
+
);
|
|
10958
|
+
}
|
|
10959
|
+
return topic;
|
|
10960
|
+
}
|
|
10961
|
+
function metadataString(topic, field) {
|
|
10962
|
+
const value = topic.metadata?.[field];
|
|
10963
|
+
return typeof value === "string" && value.trim() ? value : null;
|
|
10964
|
+
}
|
|
10965
|
+
function topicOntologyId(topic) {
|
|
10966
|
+
const ontologyId = metadataString(topic, "ontologyId");
|
|
10967
|
+
return ontologyId ? ontologyId : null;
|
|
10968
|
+
}
|
|
10969
|
+
function parentTopicGlobalId(topic) {
|
|
10970
|
+
const parent = metadataString(topic, "parentTopicGlobalId");
|
|
10971
|
+
if (!parent) {
|
|
10972
|
+
return null;
|
|
10973
|
+
}
|
|
10974
|
+
requireUuidV7TopicGlobalId(parent);
|
|
10975
|
+
return parent;
|
|
11087
10976
|
}
|
|
11088
10977
|
async function loadOntologyDefinition(ctx, ontologyId) {
|
|
11089
10978
|
return await ctx.db.get(ontologyId);
|
|
@@ -11106,25 +10995,20 @@ function resolvedTopicOntology(args) {
|
|
|
11106
10995
|
};
|
|
11107
10996
|
}
|
|
11108
10997
|
async function resolveCurrentTopicOntology(args) {
|
|
11109
|
-
|
|
10998
|
+
const ontologyId = topicOntologyId(args.current);
|
|
10999
|
+
if (!ontologyId) {
|
|
11110
11000
|
return "continue";
|
|
11111
11001
|
}
|
|
11112
|
-
const ontologyDef = await loadOntologyDefinition(
|
|
11113
|
-
args.ctx,
|
|
11114
|
-
args.current.ontologyId
|
|
11115
|
-
);
|
|
11002
|
+
const ontologyDef = await loadOntologyDefinition(args.ctx, ontologyId);
|
|
11116
11003
|
if (!ontologyDef || ontologyDef.status === "archived") {
|
|
11117
|
-
return args.current
|
|
11004
|
+
return parentTopicGlobalId(args.current) ? "continue" : null;
|
|
11118
11005
|
}
|
|
11119
|
-
const published = await loadPublishedOntologyVersions(
|
|
11120
|
-
args.ctx,
|
|
11121
|
-
args.current.ontologyId
|
|
11122
|
-
);
|
|
11006
|
+
const published = await loadPublishedOntologyVersions(args.ctx, ontologyId);
|
|
11123
11007
|
return resolvedTopicOntology({
|
|
11124
11008
|
latestPublished: published[0] ?? null,
|
|
11125
11009
|
ontologyDef,
|
|
11126
|
-
source: args.current.
|
|
11127
|
-
sourceTopicId: args.current.
|
|
11010
|
+
source: args.current.globalId === args.startTopicGlobalId ? "direct" : "inherited",
|
|
11011
|
+
sourceTopicId: args.current.globalId
|
|
11128
11012
|
});
|
|
11129
11013
|
}
|
|
11130
11014
|
async function resolveTopicOntologyInternal(ctx, topicId2) {
|
|
@@ -11132,20 +11016,21 @@ async function resolveTopicOntologyInternal(ctx, topicId2) {
|
|
|
11132
11016
|
if (!current) {
|
|
11133
11017
|
return null;
|
|
11134
11018
|
}
|
|
11135
|
-
const
|
|
11019
|
+
const startTopicGlobalId = topicId2;
|
|
11136
11020
|
for (let i = 0; i < MAX_RESOLUTION_DEPTH && current; i++) {
|
|
11137
11021
|
const resolved = await resolveCurrentTopicOntology({
|
|
11138
11022
|
ctx,
|
|
11139
11023
|
current,
|
|
11140
|
-
|
|
11024
|
+
startTopicGlobalId
|
|
11141
11025
|
});
|
|
11142
11026
|
if (resolved !== "continue") {
|
|
11143
11027
|
return resolved;
|
|
11144
11028
|
}
|
|
11145
|
-
|
|
11029
|
+
const parent = parentTopicGlobalId(current);
|
|
11030
|
+
if (!parent) {
|
|
11146
11031
|
break;
|
|
11147
11032
|
}
|
|
11148
|
-
current = await loadTopic(ctx,
|
|
11033
|
+
current = await loadTopic(ctx, parent);
|
|
11149
11034
|
}
|
|
11150
11035
|
return null;
|
|
11151
11036
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { checkProjectAccess } from '@lucern/access-control/access';
|
|
2
|
-
import { generateUuidV7 } from '@lucern/contracts/ids';
|
|
2
|
+
import { generateUuidV7, isUuidV7 } from '@lucern/contracts/ids';
|
|
3
3
|
import { permissiveReturn } from '@lucern/contracts/schema-helpers/validators';
|
|
4
4
|
import { v } from 'convex/values';
|
|
5
5
|
import { unsafeConvexAnyApi } from '@lucern/contracts/convex/unsafeAnyApi';
|
|
@@ -433,6 +433,8 @@ function resolveGraphPrimitivesAppResolvers(_ctx) {
|
|
|
433
433
|
...resolverOverrides
|
|
434
434
|
};
|
|
435
435
|
}
|
|
436
|
+
|
|
437
|
+
// src/topicScope.ts
|
|
436
438
|
var LEGACY_SCOPE_FIELD2 = "graphScopeProjectId";
|
|
437
439
|
async function resolveTopicNodeScopeOrNull(ctx, ref) {
|
|
438
440
|
if (!ctx?.db || typeof ctx.db.query !== "function") {
|
|
@@ -453,16 +455,36 @@ async function resolveTopicNodeScopeOrNull(ctx, ref) {
|
|
|
453
455
|
if (!node) {
|
|
454
456
|
return null;
|
|
455
457
|
}
|
|
456
|
-
const scopeKey =
|
|
458
|
+
const scopeKey = canonicalTopicGlobalId(node);
|
|
457
459
|
if (!scopeKey) {
|
|
458
|
-
|
|
460
|
+
throw new Error(
|
|
461
|
+
`topic.uuidv7_identity_required: topic node ${ref} is missing a UUIDv7 globalId.`
|
|
462
|
+
);
|
|
459
463
|
}
|
|
464
|
+
const metadata = node.metadata ?? {};
|
|
460
465
|
return {
|
|
461
466
|
topicId: scopeKey,
|
|
462
467
|
projectId: asMappedProjectId(node),
|
|
463
|
-
source: "topic_node"
|
|
468
|
+
source: "topic_node",
|
|
469
|
+
tenantId: normalizeScopeValue(node.tenantId) ?? normalizeScopeValue(metadata.tenantId),
|
|
470
|
+
workspaceId: normalizeScopeValue(node.workspaceId) ?? normalizeScopeValue(metadata.workspaceId)
|
|
464
471
|
};
|
|
465
472
|
}
|
|
473
|
+
function canonicalTopicGlobalId(node) {
|
|
474
|
+
const globalId = normalizeScopeValue(node.globalId);
|
|
475
|
+
if (globalId && isUuidV7(globalId)) {
|
|
476
|
+
return globalId;
|
|
477
|
+
}
|
|
478
|
+
const topicId = normalizeScopeValue(node.topicId);
|
|
479
|
+
return topicId && isUuidV7(topicId) ? topicId : null;
|
|
480
|
+
}
|
|
481
|
+
function requireUuidV7TopicScope(field, value) {
|
|
482
|
+
if (!isUuidV7(value)) {
|
|
483
|
+
throw new Error(
|
|
484
|
+
`topic.uuidv7_required: ${field} must be a UUIDv7 public topic identifier.`
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
466
488
|
function asMappedProjectId(topic) {
|
|
467
489
|
if (!topic) {
|
|
468
490
|
return;
|
|
@@ -484,200 +506,25 @@ function normalizeScopeValue(value) {
|
|
|
484
506
|
const normalized = value.trim();
|
|
485
507
|
return normalized.length > 0 ? normalized : void 0;
|
|
486
508
|
}
|
|
487
|
-
function pickPrimaryTopic(candidates) {
|
|
488
|
-
return [...candidates].sort((a, b) => {
|
|
489
|
-
const depthA = a.depth ?? 9999;
|
|
490
|
-
const depthB = b.depth ?? 9999;
|
|
491
|
-
if (depthA !== depthB) {
|
|
492
|
-
return depthA - depthB;
|
|
493
|
-
}
|
|
494
|
-
const createdA = a.createdAt ?? Number.MAX_SAFE_INTEGER;
|
|
495
|
-
const createdB = b.createdAt ?? Number.MAX_SAFE_INTEGER;
|
|
496
|
-
if (createdA !== createdB) {
|
|
497
|
-
return createdA - createdB;
|
|
498
|
-
}
|
|
499
|
-
return String(a.name || "").localeCompare(String(b.name || ""));
|
|
500
|
-
})[0];
|
|
501
|
-
}
|
|
502
|
-
async function findTopicsByScopeAlias(ctx, scopeId) {
|
|
503
|
-
const query2 = ctx.db.query("topics");
|
|
504
|
-
try {
|
|
505
|
-
return await query2.withIndex(
|
|
506
|
-
"by_graph_scope_project",
|
|
507
|
-
(q) => q.eq(LEGACY_SCOPE_FIELD2, scopeId)
|
|
508
|
-
).collect();
|
|
509
|
-
} catch (error) {
|
|
510
|
-
debugGraphPrimitiveFallback(
|
|
511
|
-
"[topicScope] Failed to resolve scope alias via index",
|
|
512
|
-
{
|
|
513
|
-
error,
|
|
514
|
-
scopeId
|
|
515
|
-
}
|
|
516
|
-
);
|
|
517
|
-
const topics = await query2.collect();
|
|
518
|
-
return topics.filter((topic) => {
|
|
519
|
-
const normalizedGlobalId = normalizeScopeValue(topic.globalId);
|
|
520
|
-
const mappedProjectId = asMappedProjectId(topic);
|
|
521
|
-
return String(topic._id) === scopeId || normalizedGlobalId === scopeId || mappedProjectId === scopeId;
|
|
522
|
-
});
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
async function tryResolveHostTopicById(ctx, topicId) {
|
|
526
|
-
if (typeof ctx.runQuery !== "function") {
|
|
527
|
-
return null;
|
|
528
|
-
}
|
|
529
|
-
try {
|
|
530
|
-
return await ctx.runQuery(api.topics.get, {
|
|
531
|
-
id: topicId
|
|
532
|
-
}) ?? null;
|
|
533
|
-
} catch (error) {
|
|
534
|
-
debugGraphPrimitiveFallback(
|
|
535
|
-
"[topicScope] Failed to resolve topic by host query",
|
|
536
|
-
{
|
|
537
|
-
error,
|
|
538
|
-
topicId
|
|
539
|
-
}
|
|
540
|
-
);
|
|
541
|
-
return null;
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
async function tryResolveHostTopicByLegacyScope(ctx, legacyScopeId) {
|
|
545
|
-
if (typeof ctx.runQuery !== "function") {
|
|
546
|
-
return null;
|
|
547
|
-
}
|
|
548
|
-
try {
|
|
549
|
-
return await ctx.runQuery(api.topics.getByLegacyScopeId, {
|
|
550
|
-
projectId: legacyScopeId
|
|
551
|
-
}) ?? null;
|
|
552
|
-
} catch (error) {
|
|
553
|
-
debugGraphPrimitiveFallback(
|
|
554
|
-
"[topicScope] Failed to resolve topic by legacy scope",
|
|
555
|
-
{
|
|
556
|
-
error,
|
|
557
|
-
legacyScopeId
|
|
558
|
-
}
|
|
559
|
-
);
|
|
560
|
-
return null;
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
async function resolveInheritedWorkspaceScope(ctx, topic) {
|
|
564
|
-
const MAX_DEPTH = 10;
|
|
565
|
-
let tenantId = normalizeScopeValue(topic.tenantId);
|
|
566
|
-
let workspaceId = normalizeScopeValue(topic.workspaceId);
|
|
567
|
-
if (tenantId && workspaceId) {
|
|
568
|
-
return { tenantId, workspaceId };
|
|
569
|
-
}
|
|
570
|
-
let current = topic;
|
|
571
|
-
for (let i = 0; i < MAX_DEPTH && current?.parentTopicId; i++) {
|
|
572
|
-
current = await ctx.db.get(current.parentTopicId);
|
|
573
|
-
if (!current) {
|
|
574
|
-
break;
|
|
575
|
-
}
|
|
576
|
-
if (!tenantId) {
|
|
577
|
-
tenantId = normalizeScopeValue(current.tenantId);
|
|
578
|
-
}
|
|
579
|
-
if (!workspaceId) {
|
|
580
|
-
workspaceId = normalizeScopeValue(current.workspaceId);
|
|
581
|
-
}
|
|
582
|
-
if (tenantId && workspaceId) {
|
|
583
|
-
break;
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
return { tenantId, workspaceId };
|
|
587
|
-
}
|
|
588
509
|
async function resolveTopicProjectScope(ctx, args) {
|
|
589
510
|
if (args.topicId) {
|
|
590
511
|
return await resolveScopeFromTopicId(ctx, args.topicId);
|
|
591
512
|
}
|
|
592
513
|
if (args.projectId) {
|
|
593
|
-
|
|
514
|
+
throw new Error(
|
|
515
|
+
"topic.uuidv7_required: projectId scope aliases are retired; pass topicId as the UUIDv7 topic globalId."
|
|
516
|
+
);
|
|
594
517
|
}
|
|
595
|
-
throw new Error(
|
|
596
|
-
"Missing scope: provide topicId (preferred) or legacy projectId alias."
|
|
597
|
-
);
|
|
518
|
+
throw new Error("topic.uuidv7_required: Missing scope: provide topicId.");
|
|
598
519
|
}
|
|
599
520
|
async function resolveScopeFromTopicId(ctx, topicId) {
|
|
600
|
-
const
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
}
|
|
604
|
-
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, String(topicId));
|
|
521
|
+
const topicGlobalId = String(topicId);
|
|
522
|
+
requireUuidV7TopicScope("topicId", topicGlobalId);
|
|
523
|
+
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, topicGlobalId);
|
|
605
524
|
if (nodeScope) {
|
|
606
525
|
return nodeScope;
|
|
607
526
|
}
|
|
608
|
-
throw new Error(`Topic not found: ${
|
|
609
|
-
}
|
|
610
|
-
async function resolveTopicDocFromTopicId(ctx, topicId) {
|
|
611
|
-
const direct = await tryReadTopicDoc(ctx, topicId, {
|
|
612
|
-
failureLog: "[topicScope] Failed to load topic by direct id",
|
|
613
|
-
idLogKey: "topicId"
|
|
614
|
-
});
|
|
615
|
-
if (direct) {
|
|
616
|
-
return direct;
|
|
617
|
-
}
|
|
618
|
-
const hostTopic = await tryResolveHostTopicById(ctx, String(topicId));
|
|
619
|
-
if (hostTopic) {
|
|
620
|
-
return hostTopic;
|
|
621
|
-
}
|
|
622
|
-
return pickPrimaryTopic(await findTopicsByScopeAlias(ctx, String(topicId))) ?? null;
|
|
623
|
-
}
|
|
624
|
-
async function resolveScopeFromLegacyProjectId(ctx, legacyProjectId) {
|
|
625
|
-
const directTopic = await resolveDirectLegacyProjectTopic(
|
|
626
|
-
ctx,
|
|
627
|
-
legacyProjectId
|
|
628
|
-
);
|
|
629
|
-
if (directTopic) {
|
|
630
|
-
return await buildTopicScope(ctx, directTopic, "topic_inferred", {
|
|
631
|
-
fallbackProjectId: legacyProjectId
|
|
632
|
-
});
|
|
633
|
-
}
|
|
634
|
-
const primary = pickPrimaryTopic(
|
|
635
|
-
await findTopicsByScopeAlias(ctx, legacyProjectId)
|
|
636
|
-
);
|
|
637
|
-
if (primary) {
|
|
638
|
-
return await buildTopicScope(ctx, primary, "project_mapped_topic", {
|
|
639
|
-
fallbackProjectId: legacyProjectId
|
|
640
|
-
});
|
|
641
|
-
}
|
|
642
|
-
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, legacyProjectId);
|
|
643
|
-
if (nodeScope) {
|
|
644
|
-
return {
|
|
645
|
-
...nodeScope,
|
|
646
|
-
projectId: nodeScope.projectId ?? legacyProjectId
|
|
647
|
-
};
|
|
648
|
-
}
|
|
649
|
-
throw new Error(
|
|
650
|
-
`Legacy project scope ${legacyProjectId} has no mapped topic.`
|
|
651
|
-
);
|
|
652
|
-
}
|
|
653
|
-
async function resolveDirectLegacyProjectTopic(ctx, legacyProjectId) {
|
|
654
|
-
const directTopic = await tryReadTopicDoc(ctx, legacyProjectId, {
|
|
655
|
-
failureLog: "[topicScope] Failed to load direct project topic",
|
|
656
|
-
idLogKey: "projectId"
|
|
657
|
-
});
|
|
658
|
-
return directTopic ?? tryResolveHostTopicByLegacyScope(ctx, legacyProjectId);
|
|
659
|
-
}
|
|
660
|
-
async function tryReadTopicDoc(ctx, id, log) {
|
|
661
|
-
try {
|
|
662
|
-
return await ctx.db.get(id);
|
|
663
|
-
} catch (error) {
|
|
664
|
-
debugGraphPrimitiveFallback(log.failureLog, {
|
|
665
|
-
error,
|
|
666
|
-
[log.idLogKey]: id
|
|
667
|
-
});
|
|
668
|
-
return null;
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
async function buildTopicScope(ctx, topic, source, options = {}) {
|
|
672
|
-
const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
|
|
673
|
-
const mapped = asMappedProjectId(topic);
|
|
674
|
-
return {
|
|
675
|
-
topicId: topic._id,
|
|
676
|
-
...mapped || options.fallbackProjectId ? { projectId: mapped ?? options.fallbackProjectId } : {},
|
|
677
|
-
tenantId: inherited.tenantId,
|
|
678
|
-
workspaceId: inherited.workspaceId,
|
|
679
|
-
source
|
|
680
|
-
};
|
|
527
|
+
throw new Error(`Topic not found: ${topicGlobalId}`);
|
|
681
528
|
}
|
|
682
529
|
var optionalScopeArgs = {
|
|
683
530
|
projectId: v.optional(v.string()),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as convex_values from 'convex/values';
|
|
2
|
-
import { r as resolveTopicProjectScope } from './topicScope-
|
|
2
|
+
import { r as resolveTopicProjectScope } from './topicScope-CL1IVOmv.js';
|
|
3
3
|
import './convex.js';
|
|
4
4
|
import '@lucern/access-control/convex';
|
|
5
5
|
import '@lucern/contracts/convex/unsafeAnyApi';
|