@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.
Files changed (67) hide show
  1. package/dist/beliefDecay.js +34 -186
  2. package/dist/beliefEvidenceLinks.js +32 -187
  3. package/dist/contradictions.js +34 -187
  4. package/dist/entityLifecycle.js +88 -205
  5. package/dist/epistemicAnswers.js +35 -187
  6. package/dist/epistemicBeliefs.admin.js +34 -187
  7. package/dist/epistemicBeliefs.backfills.js +34 -188
  8. package/dist/epistemicBeliefs.confidence.d.ts +1 -1
  9. package/dist/epistemicBeliefs.confidence.js +32 -188
  10. package/dist/epistemicBeliefs.core.js +37 -187
  11. package/dist/epistemicBeliefs.d.ts +1 -1
  12. package/dist/epistemicBeliefs.helpers.d.ts +1 -1
  13. package/dist/epistemicBeliefs.helpers.js +34 -186
  14. package/dist/epistemicBeliefs.internal.js +37 -187
  15. package/dist/epistemicBeliefs.js +37 -187
  16. package/dist/epistemicBeliefs.lifecycle.js +32 -188
  17. package/dist/epistemicBeliefs.links.js +34 -188
  18. package/dist/epistemicContracts.evaluators.js +34 -188
  19. package/dist/epistemicContracts.handlers.js +34 -188
  20. package/dist/epistemicContracts.js +34 -188
  21. package/dist/epistemicEdges.d.ts +1 -1
  22. package/dist/epistemicEdges.helpers.d.ts +1 -1
  23. package/dist/epistemicEdges.js +32 -187
  24. package/dist/epistemicEdges.mutations.js +34 -186
  25. package/dist/epistemicEdges.queries.js +35 -188
  26. package/dist/epistemicEdges.types.d.ts +1 -1
  27. package/dist/epistemicEvidence.d.ts +1 -1
  28. package/dist/epistemicEvidence.js +37 -187
  29. package/dist/epistemicEvidenceHelpers.d.ts +1 -1
  30. package/dist/epistemicEvidenceHelpers.js +34 -186
  31. package/dist/epistemicEvidenceMutations.js +37 -187
  32. package/dist/epistemicEvidenceQueries.js +34 -186
  33. package/dist/epistemicHelpers.js +4 -1
  34. package/dist/epistemicInsert.js +4 -1
  35. package/dist/epistemicNodeCreation.js +4 -1
  36. package/dist/epistemicNodes.helpers.d.ts +1 -1
  37. package/dist/epistemicNodes.internal.js +35 -188
  38. package/dist/epistemicNodes.js +37 -188
  39. package/dist/epistemicNodes.mutations.js +35 -188
  40. package/dist/epistemicNodes.queries.js +35 -188
  41. package/dist/epistemicQuestions.conviction.js +34 -186
  42. package/dist/epistemicQuestions.create.js +37 -187
  43. package/dist/epistemicQuestions.d.ts +1 -1
  44. package/dist/epistemicQuestions.evidence.js +37 -187
  45. package/dist/epistemicQuestions.helpers.d.ts +1 -1
  46. package/dist/epistemicQuestions.helpers.js +34 -186
  47. package/dist/epistemicQuestions.js +37 -187
  48. package/dist/epistemicQuestions.lifecycle.js +34 -186
  49. package/dist/epistemicQuestions.queries.js +34 -186
  50. package/dist/epistemicQuestions.sprint.js +35 -188
  51. package/dist/epistemicQuestions.tail.js +37 -187
  52. package/dist/epistemicSources.js +35 -188
  53. package/dist/index.d.ts +1 -1
  54. package/dist/index.js +98 -213
  55. package/dist/proof-attestation.json +1 -1
  56. package/dist/questionEvidenceLinks.js +34 -187
  57. package/dist/scopeResolverCompat.d.ts +1 -1
  58. package/dist/scopeResolverCompat.js +32 -193
  59. package/dist/topicOntologyResolver.d.ts +3 -3
  60. package/dist/topicOntologyResolver.js +57 -18
  61. package/dist/{topicScope-DJVa0mLa.d.ts → topicScope-CL1IVOmv.d.ts} +2 -2
  62. package/dist/topicScope.d.ts +1 -1
  63. package/dist/topicScope.js +32 -193
  64. package/dist/workflowBridge.js +32 -193
  65. package/dist/workspaceIsolation.d.ts +1 -1
  66. package/dist/workspaceIsolation.js +32 -193
  67. 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 = normalizeScopeValue(node.topicId) ?? normalizeScopeValue(node.globalId);
1602
+ const scopeKey = canonicalTopicGlobalId(node);
1593
1603
  if (!scopeKey) {
1594
- return null;
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
- return await resolveScopeFromLegacyProjectId(ctx, args.projectId);
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 topic = await resolveTopicDocFromTopicId(ctx, topicId2);
1745
- if (topic) {
1746
- return await buildTopicScope(ctx, topic, "topic");
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: ${String(topicId2)}`);
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, topicId2) {
11086
- return await ctx.db.get(topicId2);
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
- if (!args.current.ontologyId) {
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.parentTopicId ? "continue" : null;
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._id === args.startTopicId ? "direct" : "inherited",
11127
- sourceTopicId: args.current._id
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 startTopicId = topicId2;
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
- startTopicId
11024
+ startTopicGlobalId
11141
11025
  });
11142
11026
  if (resolved !== "continue") {
11143
11027
  return resolved;
11144
11028
  }
11145
- if (!current.parentTopicId) {
11029
+ const parent = parentTopicGlobalId(current);
11030
+ if (!parent) {
11146
11031
  break;
11147
11032
  }
11148
- current = await loadTopic(ctx, current.parentTopicId);
11033
+ current = await loadTopic(ctx, parent);
11149
11034
  }
11150
11035
  return null;
11151
11036
  }
@@ -41,5 +41,5 @@
41
41
  "convex-validators",
42
42
  "proof-attestation"
43
43
  ],
44
- "signedAt": 1782651828312
44
+ "signedAt": 1782800591189
45
45
  }
@@ -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 = normalizeScopeValue(node.topicId) ?? normalizeScopeValue(node.globalId);
458
+ const scopeKey = canonicalTopicGlobalId(node);
457
459
  if (!scopeKey) {
458
- return null;
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
- return await resolveScopeFromLegacyProjectId(ctx, args.projectId);
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 topic = await resolveTopicDocFromTopicId(ctx, topicId);
601
- if (topic) {
602
- return await buildTopicScope(ctx, topic, "topic");
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: ${String(topicId)}`);
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-DJVa0mLa.js';
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';