@lucern/graph-primitives 0.1.0-alpha.2

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 (224) hide show
  1. package/README.md +29 -0
  2. package/dist/beliefDecay-Q_26RTc-.d.ts +72 -0
  3. package/dist/beliefDecay.d.ts +2 -0
  4. package/dist/beliefDecay.js +1628 -0
  5. package/dist/beliefDecay.js.map +1 -0
  6. package/dist/beliefEvidenceLinks-42FlR48t.d.ts +77 -0
  7. package/dist/beliefEvidenceLinks.d.ts +1 -0
  8. package/dist/beliefEvidenceLinks.js +1978 -0
  9. package/dist/beliefEvidenceLinks.js.map +1 -0
  10. package/dist/beliefLifecycle-C-AehZgF.d.ts +43 -0
  11. package/dist/beliefLifecycle.d.ts +1 -0
  12. package/dist/beliefLifecycle.js +98 -0
  13. package/dist/beliefLifecycle.js.map +1 -0
  14. package/dist/confidencePropagationDispatch.d.ts +46 -0
  15. package/dist/confidencePropagationDispatch.js +744 -0
  16. package/dist/confidencePropagationDispatch.js.map +1 -0
  17. package/dist/contradictions-Hdwl7zid.d.ts +71 -0
  18. package/dist/contradictions.d.ts +1 -0
  19. package/dist/contradictions.js +1557 -0
  20. package/dist/contradictions.js.map +1 -0
  21. package/dist/convex.d.ts +23 -0
  22. package/dist/convex.js +17 -0
  23. package/dist/convex.js.map +1 -0
  24. package/dist/edgeValidation-CeI0wc0r.d.ts +35 -0
  25. package/dist/edgeValidation.d.ts +2 -0
  26. package/dist/edgeValidation.js +307 -0
  27. package/dist/edgeValidation.js.map +1 -0
  28. package/dist/edges/contains.d.ts +6 -0
  29. package/dist/edges/contains.js +14 -0
  30. package/dist/edges/contains.js.map +1 -0
  31. package/dist/edges/contradicts.d.ts +6 -0
  32. package/dist/edges/contradicts.js +183 -0
  33. package/dist/edges/contradicts.js.map +1 -0
  34. package/dist/edges/dependsOn.d.ts +6 -0
  35. package/dist/edges/dependsOn.js +240 -0
  36. package/dist/edges/dependsOn.js.map +1 -0
  37. package/dist/edges/derivedFrom.d.ts +6 -0
  38. package/dist/edges/derivedFrom.js +14 -0
  39. package/dist/edges/derivedFrom.js.map +1 -0
  40. package/dist/edges/elaborates.d.ts +6 -0
  41. package/dist/edges/elaborates.js +100 -0
  42. package/dist/edges/elaborates.js.map +1 -0
  43. package/dist/edges/index.d.ts +3 -0
  44. package/dist/edges/index.js +556 -0
  45. package/dist/edges/index.js.map +1 -0
  46. package/dist/edges/informs.d.ts +6 -0
  47. package/dist/edges/informs.js +112 -0
  48. package/dist/edges/informs.js.map +1 -0
  49. package/dist/edges/propagationTypes.d.ts +39 -0
  50. package/dist/edges/propagationTypes.js +17 -0
  51. package/dist/edges/propagationTypes.js.map +1 -0
  52. package/dist/edges/refutes.d.ts +6 -0
  53. package/dist/edges/refutes.js +108 -0
  54. package/dist/edges/refutes.js.map +1 -0
  55. package/dist/edges/supports.d.ts +6 -0
  56. package/dist/edges/supports.js +193 -0
  57. package/dist/edges/supports.js.map +1 -0
  58. package/dist/edges/tests.d.ts +6 -0
  59. package/dist/edges/tests.js +14 -0
  60. package/dist/edges/tests.js.map +1 -0
  61. package/dist/edges/utils.d.ts +12 -0
  62. package/dist/edges/utils.js +188 -0
  63. package/dist/edges/utils.js.map +1 -0
  64. package/dist/embeddingTrigger.d.ts +24 -0
  65. package/dist/embeddingTrigger.js +24 -0
  66. package/dist/embeddingTrigger.js.map +1 -0
  67. package/dist/entityBridge-DMaKooYn.d.ts +59 -0
  68. package/dist/entityBridge.d.ts +1 -0
  69. package/dist/entityBridge.js +663 -0
  70. package/dist/entityBridge.js.map +1 -0
  71. package/dist/entityLifecycle-BkhRJ-XI.d.ts +69 -0
  72. package/dist/entityLifecycle.d.ts +1 -0
  73. package/dist/entityLifecycle.js +2083 -0
  74. package/dist/entityLifecycle.js.map +1 -0
  75. package/dist/entityValidation-KLZ_Xl2D.d.ts +50 -0
  76. package/dist/entityValidation.d.ts +3 -0
  77. package/dist/entityValidation.js +71 -0
  78. package/dist/entityValidation.js.map +1 -0
  79. package/dist/epistemicAnswers-DSP1slZ9.d.ts +67 -0
  80. package/dist/epistemicAnswers.d.ts +1 -0
  81. package/dist/epistemicAnswers.js +1650 -0
  82. package/dist/epistemicAnswers.js.map +1 -0
  83. package/dist/epistemicBeliefs-DtFVTp-k.d.ts +377 -0
  84. package/dist/epistemicBeliefs.d.ts +5 -0
  85. package/dist/epistemicBeliefs.js +6386 -0
  86. package/dist/epistemicBeliefs.js.map +1 -0
  87. package/dist/epistemicContractHelpers.d.ts +1 -0
  88. package/dist/epistemicContractHelpers.js +320 -0
  89. package/dist/epistemicContractHelpers.js.map +1 -0
  90. package/dist/epistemicContracts.d.ts +77 -0
  91. package/dist/epistemicContracts.js +8436 -0
  92. package/dist/epistemicContracts.js.map +1 -0
  93. package/dist/epistemicEdges-DcA8ErUG.d.ts +191 -0
  94. package/dist/epistemicEdges.d.ts +2 -0
  95. package/dist/epistemicEdges.js +2749 -0
  96. package/dist/epistemicEdges.js.map +1 -0
  97. package/dist/epistemicEvidence-Bo638XDP.d.ts +128 -0
  98. package/dist/epistemicEvidence.d.ts +3 -0
  99. package/dist/epistemicEvidence.js +3282 -0
  100. package/dist/epistemicEvidence.js.map +1 -0
  101. package/dist/epistemicHelpers-Bd9xbaib.d.ts +329 -0
  102. package/dist/epistemicHelpers.d.ts +4 -0
  103. package/dist/epistemicHelpers.js +999 -0
  104. package/dist/epistemicHelpers.js.map +1 -0
  105. package/dist/epistemicLinking-CyeLOIzN.d.ts +35 -0
  106. package/dist/epistemicLinking.d.ts +1 -0
  107. package/dist/epistemicLinking.js +1391 -0
  108. package/dist/epistemicLinking.js.map +1 -0
  109. package/dist/epistemicNodes-BpD6Koud.d.ts +167 -0
  110. package/dist/epistemicNodes.d.ts +2 -0
  111. package/dist/epistemicNodes.js +2942 -0
  112. package/dist/epistemicNodes.js.map +1 -0
  113. package/dist/epistemicQuestions-CmEeY6zQ.d.ts +214 -0
  114. package/dist/epistemicQuestions.d.ts +3 -0
  115. package/dist/epistemicQuestions.js +4993 -0
  116. package/dist/epistemicQuestions.js.map +1 -0
  117. package/dist/epistemicSources-ZazxHOK1.d.ts +25 -0
  118. package/dist/epistemicSources.d.ts +1 -0
  119. package/dist/epistemicSources.js +2025 -0
  120. package/dist/epistemicSources.js.map +1 -0
  121. package/dist/evaluators/index.d.ts +9 -0
  122. package/dist/evaluators/index.js +8440 -0
  123. package/dist/evaluators/index.js.map +1 -0
  124. package/dist/evaluators/lintCheckerEvaluator.d.ts +11 -0
  125. package/dist/evaluators/lintCheckerEvaluator.js +155 -0
  126. package/dist/evaluators/lintCheckerEvaluator.js.map +1 -0
  127. package/dist/evaluators/sentryCheckerEvaluator.d.ts +11 -0
  128. package/dist/evaluators/sentryCheckerEvaluator.js +126 -0
  129. package/dist/evaluators/sentryCheckerEvaluator.js.map +1 -0
  130. package/dist/evaluators/shared.d.ts +27 -0
  131. package/dist/evaluators/shared.js +92 -0
  132. package/dist/evaluators/shared.js.map +1 -0
  133. package/dist/evaluators/testRunnerEvaluator.d.ts +17 -0
  134. package/dist/evaluators/testRunnerEvaluator.js +232 -0
  135. package/dist/evaluators/testRunnerEvaluator.js.map +1 -0
  136. package/dist/evaluators/tscCheckerEvaluator.d.ts +11 -0
  137. package/dist/evaluators/tscCheckerEvaluator.js +189 -0
  138. package/dist/evaluators/tscCheckerEvaluator.js.map +1 -0
  139. package/dist/globalId-DKh9d_uD.d.ts +20 -0
  140. package/dist/globalId.d.ts +1 -0
  141. package/dist/globalId.js +15 -0
  142. package/dist/globalId.js.map +1 -0
  143. package/dist/graphTypes-CpgIuCdo.d.ts +52 -0
  144. package/dist/graphTypes.d.ts +1 -0
  145. package/dist/graphTypes.js +120 -0
  146. package/dist/graphTypes.js.map +1 -0
  147. package/dist/helpers-BYHIk5vU.d.ts +27 -0
  148. package/dist/helpers.d.ts +4 -0
  149. package/dist/helpers.js +313 -0
  150. package/dist/helpers.js.map +1 -0
  151. package/dist/index-Dct1T70K.d.ts +25 -0
  152. package/dist/index-Dq-7R-gi.d.ts +31 -0
  153. package/dist/index.d.ts +45 -0
  154. package/dist/index.js +22294 -0
  155. package/dist/index.js.map +1 -0
  156. package/dist/invariantEnforcement.d.ts +52 -0
  157. package/dist/invariantEnforcement.js +231 -0
  158. package/dist/invariantEnforcement.js.map +1 -0
  159. package/dist/logicalRoleInference-CJxqWi3u.d.ts +16 -0
  160. package/dist/logicalRoleInference.d.ts +3 -0
  161. package/dist/logicalRoleInference.js +64 -0
  162. package/dist/logicalRoleInference.js.map +1 -0
  163. package/dist/matcherFeedbackUtils.d.ts +33 -0
  164. package/dist/matcherFeedbackUtils.js +95 -0
  165. package/dist/matcherFeedbackUtils.js.map +1 -0
  166. package/dist/ontology-matching-Buhu23ss.d.ts +48 -0
  167. package/dist/ontology-matching.d.ts +2 -0
  168. package/dist/ontology-matching.js +346 -0
  169. package/dist/ontology-matching.js.map +1 -0
  170. package/dist/ontologyApproval-Ba0Jjk1k.d.ts +26 -0
  171. package/dist/ontologyApproval.d.ts +1 -0
  172. package/dist/ontologyApproval.js +78 -0
  173. package/dist/ontologyApproval.js.map +1 -0
  174. package/dist/ontologyDefinitions.d.ts +72 -0
  175. package/dist/ontologyDefinitions.js +635 -0
  176. package/dist/ontologyDefinitions.js.map +1 -0
  177. package/dist/ontologyHelpers.d.ts +79 -0
  178. package/dist/ontologyHelpers.js +81 -0
  179. package/dist/ontologyHelpers.js.map +1 -0
  180. package/dist/ontologyRegistry-B67rPJ16.d.ts +31 -0
  181. package/dist/ontologyRegistry.d.ts +1 -0
  182. package/dist/ontologyRegistry.js +296 -0
  183. package/dist/ontologyRegistry.js.map +1 -0
  184. package/dist/projectionReconciliation-CxrXYGaB.d.ts +20 -0
  185. package/dist/projectionReconciliation.d.ts +1 -0
  186. package/dist/projectionReconciliation.js +261 -0
  187. package/dist/projectionReconciliation.js.map +1 -0
  188. package/dist/projectionStaleness-CAdpIsaW.d.ts +51 -0
  189. package/dist/projectionStaleness.d.ts +1 -0
  190. package/dist/projectionStaleness.js +57 -0
  191. package/dist/projectionStaleness.js.map +1 -0
  192. package/dist/questionEvidenceLinks-BdQD0TkM.d.ts +34 -0
  193. package/dist/questionEvidenceLinks.d.ts +1 -0
  194. package/dist/questionEvidenceLinks.js +1690 -0
  195. package/dist/questionEvidenceLinks.js.map +1 -0
  196. package/dist/resolverTypes-CC8Ea2E2.d.ts +20 -0
  197. package/dist/resolverTypes.d.ts +4 -0
  198. package/dist/resolverTypes.js +3 -0
  199. package/dist/resolverTypes.js.map +1 -0
  200. package/dist/resolvers-Br1a6eLV.d.ts +14 -0
  201. package/dist/resolvers.d.ts +5 -0
  202. package/dist/resolvers.js +308 -0
  203. package/dist/resolvers.js.map +1 -0
  204. package/dist/scopeResolverCompat.d.ts +26 -0
  205. package/dist/scopeResolverCompat.js +242 -0
  206. package/dist/scopeResolverCompat.js.map +1 -0
  207. package/dist/text-matching-CMn2WnVD.d.ts +40 -0
  208. package/dist/text-matching.d.ts +2 -0
  209. package/dist/text-matching.js +246 -0
  210. package/dist/text-matching.js.map +1 -0
  211. package/dist/topicOntologyResolver.d.ts +80 -0
  212. package/dist/topicOntologyResolver.js +67 -0
  213. package/dist/topicOntologyResolver.js.map +1 -0
  214. package/dist/topicProjectOverlay.d.ts +92 -0
  215. package/dist/topicProjectOverlay.js +249 -0
  216. package/dist/topicProjectOverlay.js.map +1 -0
  217. package/dist/topicScope-By_zp4tt.d.ts +34 -0
  218. package/dist/topicScope.d.ts +3 -0
  219. package/dist/topicScope.js +206 -0
  220. package/dist/topicScope.js.map +1 -0
  221. package/dist/workspaceIsolation.d.ts +44 -0
  222. package/dist/workspaceIsolation.js +950 -0
  223. package/dist/workspaceIsolation.js.map +1 -0
  224. package/package.json +46 -0
@@ -0,0 +1,2942 @@
1
+ import { v } from 'convex/values';
2
+ import { componentsGeneric, defineTable, queryGeneric, mutationGeneric, anyApi, internalMutationGeneric, internalQueryGeneric } from 'convex/server';
3
+
4
+ // src/epistemicNodes.ts
5
+ var api = anyApi;
6
+ componentsGeneric();
7
+
8
+ // ../access-control/src/topicProjectOverlay.ts
9
+ var LEGACY_SCOPE_FIELD = "graphScopeProjectId";
10
+ function readNonEmptyString(value) {
11
+ if (typeof value !== "string") {
12
+ return;
13
+ }
14
+ const normalized = value.trim();
15
+ return normalized.length > 0 ? normalized : void 0;
16
+ }
17
+ function readStringArray(value) {
18
+ if (!Array.isArray(value)) {
19
+ return [];
20
+ }
21
+ return value.map((entry) => readNonEmptyString(entry)).filter((entry) => Boolean(entry));
22
+ }
23
+ function readMetadata(topic) {
24
+ return topic.metadata && typeof topic.metadata === "object" ? topic.metadata : {};
25
+ }
26
+ function readLegacyProjectId(value) {
27
+ if (!value) {
28
+ return;
29
+ }
30
+ return readNonEmptyString(value[LEGACY_SCOPE_FIELD]);
31
+ }
32
+ function coerceVisibility(value) {
33
+ return value === "private" || value === "team" || value === "firm" || value === "external" || value === "public" ? value : void 0;
34
+ }
35
+ function coerceStatus(value) {
36
+ return value === "active" || value === "archived" || value === "watching" ? value : void 0;
37
+ }
38
+ function mapProjectType(topic, metadata) {
39
+ const explicit = readNonEmptyString(metadata.projectType);
40
+ if (explicit) {
41
+ return explicit;
42
+ }
43
+ if (topic.type === "theme") {
44
+ return "thematic";
45
+ }
46
+ return readNonEmptyString(topic.type) || "general";
47
+ }
48
+ function isProjectLikeTopic(topic) {
49
+ const metadata = readMetadata(topic);
50
+ return topic.type === "theme" || topic.type === "thematic" || topic.type === "deal" || topic.type === "monitoring" || readLegacyProjectId(topic) !== void 0 || readNonEmptyString(metadata.projectType) !== void 0;
51
+ }
52
+ async function resolveTopicDoc(ctx, scopeId) {
53
+ if (ctx?.db && typeof ctx.db.get === "function") {
54
+ try {
55
+ const directTopic = await ctx.db.get(scopeId);
56
+ if (directTopic) {
57
+ return directTopic;
58
+ }
59
+ } catch {
60
+ }
61
+ }
62
+ if (typeof ctx.runQuery !== "function") {
63
+ return null;
64
+ }
65
+ try {
66
+ const topic = await ctx.runQuery(api.topics.get, {
67
+ id: String(scopeId)
68
+ });
69
+ if (topic?.name !== void 0 && topic?.type !== void 0) {
70
+ return topic;
71
+ }
72
+ } catch {
73
+ }
74
+ try {
75
+ const topic = await ctx.runQuery(api.topics.getByLegacyScopeId, {
76
+ projectId: String(scopeId)
77
+ });
78
+ if (topic?.name !== void 0 && topic?.type !== void 0) {
79
+ return topic;
80
+ }
81
+ } catch {
82
+ }
83
+ return null;
84
+ }
85
+ function materializeTopicProjectOverlay(topic, idMode = "legacy") {
86
+ const metadata = readMetadata(topic);
87
+ const topicId = String(topic._id);
88
+ const legacyProjectId = readLegacyProjectId(topic) || readLegacyProjectId(metadata) || readNonEmptyString(metadata.legacyProjectId);
89
+ const storageProjectId = legacyProjectId || topicId;
90
+ const outwardId = idMode === "topic" ? topicId : storageProjectId;
91
+ const visibility = coerceVisibility(topic.visibility) || coerceVisibility(metadata.visibility) || "private";
92
+ const status = coerceStatus(topic.status) || coerceStatus(metadata.status) || "active";
93
+ const createdAt = typeof topic.createdAt === "number" ? topic.createdAt : typeof topic._creationTime === "number" ? topic._creationTime : 0;
94
+ const updatedAt = typeof topic.updatedAt === "number" ? topic.updatedAt : typeof metadata.updatedAt === "number" ? metadata.updatedAt : createdAt;
95
+ return {
96
+ ...metadata,
97
+ _id: outwardId,
98
+ projectId: outwardId,
99
+ topicId,
100
+ storageProjectId,
101
+ legacyProjectId,
102
+ name: readNonEmptyString(topic.name) || "Untitled Theme",
103
+ type: mapProjectType(topic, metadata),
104
+ description: readNonEmptyString(topic.description),
105
+ ownerId: readNonEmptyString(metadata.ownerId) || readNonEmptyString(topic.createdBy) || "system",
106
+ sharedWith: readStringArray(metadata.sharedWith),
107
+ visibility,
108
+ tenantId: readNonEmptyString(topic.tenantId) || readNonEmptyString(metadata.tenantId),
109
+ workspaceId: readNonEmptyString(topic.workspaceId) || readNonEmptyString(metadata.workspaceId),
110
+ status,
111
+ tags: readStringArray(metadata.tags),
112
+ chatCount: typeof metadata.chatCount === "number" ? metadata.chatCount : 0,
113
+ artifactCount: typeof metadata.artifactCount === "number" ? metadata.artifactCount : 0,
114
+ lastActivityAt: typeof metadata.lastActivityAt === "number" ? metadata.lastActivityAt : updatedAt,
115
+ _creationTime: typeof topic._creationTime === "number" ? topic._creationTime : createdAt,
116
+ createdAt,
117
+ updatedAt
118
+ };
119
+ }
120
+ async function resolveTopicProjectOverlay(ctx, scopeId, options = {}) {
121
+ const topic = await resolveTopicDoc(ctx, scopeId);
122
+ if (!topic) {
123
+ return null;
124
+ }
125
+ if (options.projectLikeOnly !== false && !isProjectLikeTopic(topic)) {
126
+ return null;
127
+ }
128
+ return materializeTopicProjectOverlay(topic, options.idMode);
129
+ }
130
+ async function listTopicProjectOverlays(ctx, options = {}) {
131
+ let allTopics = [];
132
+ if (ctx?.db?.query && typeof ctx.db.query === "function") {
133
+ try {
134
+ allTopics = await ctx.db.query("topics").collect();
135
+ } catch {
136
+ allTopics = [];
137
+ }
138
+ }
139
+ if (allTopics.length === 0 && typeof ctx.runQuery === "function") {
140
+ allTopics = (await ctx.runQuery(api.topics.list, {}) ?? []) || [];
141
+ }
142
+ return allTopics.filter(
143
+ (topic) => options.projectLikeOnly === false || isProjectLikeTopic(topic)
144
+ ).map((topic) => materializeTopicProjectOverlay(topic, options.idMode));
145
+ }
146
+
147
+ // ../access-control/src/projectGrantsBridge.ts
148
+ var PROJECT_GRANT_STATUSES = ["active", "revoked", "expired"];
149
+ function normalizeString(value) {
150
+ if (typeof value !== "string") {
151
+ return;
152
+ }
153
+ const trimmed = value.trim();
154
+ return trimmed.length > 0 ? trimmed : void 0;
155
+ }
156
+ async function resolveGrantScopeIds(ctx, args) {
157
+ const topicId = normalizeString(args.topicId);
158
+ const projectId = normalizeString(args.projectId);
159
+ for (const scopeId of [topicId, projectId]) {
160
+ if (!scopeId) {
161
+ continue;
162
+ }
163
+ try {
164
+ const overlay = await resolveTopicProjectOverlay(ctx, scopeId, {
165
+ idMode: "legacy",
166
+ projectLikeOnly: false
167
+ });
168
+ if (overlay) {
169
+ return {
170
+ topicId: normalizeString(overlay.topicId) ?? topicId,
171
+ projectId: normalizeString(overlay.projectId) ?? projectId ?? scopeId
172
+ };
173
+ }
174
+ } catch {
175
+ }
176
+ }
177
+ return { topicId, projectId };
178
+ }
179
+ async function normalizeProjectGrantRow(ctx, row) {
180
+ const scope = await resolveGrantScopeIds(ctx, {
181
+ topicId: row.topicId,
182
+ projectId: row.projectId
183
+ });
184
+ return {
185
+ ...row,
186
+ ...scope.topicId ? { topicId: scope.topicId } : {},
187
+ ...scope.projectId ?? scope.topicId ? { projectId: scope.projectId ?? scope.topicId } : {}
188
+ };
189
+ }
190
+ async function normalizeProjectGrantRows(ctx, rows) {
191
+ return await Promise.all(rows.map((row) => normalizeProjectGrantRow(ctx, row)));
192
+ }
193
+ async function listProjectGrantsByPrincipal(ctx, principalId) {
194
+ const rows = await Promise.all(
195
+ PROJECT_GRANT_STATUSES.map(
196
+ (status) => ctx.db.query("projectGrants").withIndex(
197
+ "by_principal_status",
198
+ (q) => q.eq("principalId", principalId).eq("status", status)
199
+ ).collect()
200
+ )
201
+ );
202
+ return await normalizeProjectGrantRows(ctx, rows.flat());
203
+ }
204
+ async function listProjectGrantsByGroup(ctx, groupId) {
205
+ const rows = await Promise.all(
206
+ PROJECT_GRANT_STATUSES.map(
207
+ (status) => ctx.db.query("projectGrants").withIndex(
208
+ "by_group_status",
209
+ (q) => q.eq("groupId", groupId).eq("status", status)
210
+ ).collect()
211
+ )
212
+ );
213
+ return await normalizeProjectGrantRows(ctx, rows.flat());
214
+ }
215
+ function buildScopeMatchers(inputScopeId, resolved) {
216
+ return new Set(
217
+ [inputScopeId, resolved.topicId, resolved.projectId].map((value) => normalizeString(value)).filter((value) => Boolean(value))
218
+ );
219
+ }
220
+ function matchesResolvedScope(row, scopeIds) {
221
+ const rowTopicId = normalizeString(row.topicId);
222
+ const rowProjectId = normalizeString(row.projectId);
223
+ return rowTopicId !== void 0 && scopeIds.has(rowTopicId) || rowProjectId !== void 0 && scopeIds.has(rowProjectId);
224
+ }
225
+ async function bridgeListProjectGrantsByTopicAndPrincipal(ctx, topicId, principalId) {
226
+ const resolved = await resolveGrantScopeIds(ctx, { topicId });
227
+ const scopeIds = buildScopeMatchers(topicId, resolved);
228
+ const rows = await listProjectGrantsByPrincipal(ctx, principalId);
229
+ return rows.filter((row) => matchesResolvedScope(row, scopeIds));
230
+ }
231
+ async function bridgeListProjectGrantsByTopicAndGroup(ctx, topicId, groupId) {
232
+ const resolved = await resolveGrantScopeIds(ctx, { topicId });
233
+ const scopeIds = buildScopeMatchers(topicId, resolved);
234
+ const rows = await listProjectGrantsByGroup(ctx, groupId);
235
+ return rows.filter((row) => matchesResolvedScope(row, scopeIds));
236
+ }
237
+ async function bridgeListProjectGrantsByPrincipalStatus(ctx, principalId, status) {
238
+ const rows = await listProjectGrantsByPrincipal(ctx, principalId);
239
+ return rows.filter((row) => row.status === status);
240
+ }
241
+ async function bridgeListProjectGrantsByGroupStatus(ctx, groupId, status) {
242
+ const rows = await listProjectGrantsByGroup(ctx, groupId);
243
+ return rows.filter((row) => row.status === status);
244
+ }
245
+ async function bridgeInsertProjectGrant(ctx, value) {
246
+ const resolved = await resolveGrantScopeIds(ctx, value);
247
+ return await ctx.db.insert("projectGrants", {
248
+ ...value,
249
+ ...resolved.topicId ? { topicId: resolved.topicId } : {},
250
+ ...resolved.projectId ?? resolved.topicId ? { projectId: resolved.projectId ?? resolved.topicId } : {}
251
+ });
252
+ }
253
+
254
+ // ../access-control/src/resolvers.ts
255
+ async function findUserByClerkId(ctx, clerkId) {
256
+ const normalizedClerkId = clerkId.trim();
257
+ if (!normalizedClerkId) {
258
+ return null;
259
+ }
260
+ if (typeof ctx.runQuery === "function") {
261
+ try {
262
+ const bridgedUser = await ctx.runQuery(api.users.getUserByClerkId, {
263
+ clerkId: normalizedClerkId
264
+ });
265
+ if (bridgedUser) {
266
+ return bridgedUser;
267
+ }
268
+ } catch {
269
+ }
270
+ }
271
+ try {
272
+ const users = await ctx.db.query("users").collect();
273
+ return users.find((user) => String(user.clerkId ?? "") === normalizedClerkId) ?? null;
274
+ } catch {
275
+ return null;
276
+ }
277
+ }
278
+ async function findUserByPrincipalId(ctx, principalId) {
279
+ const normalizedPrincipalId = principalId.trim();
280
+ if (!normalizedPrincipalId) {
281
+ return null;
282
+ }
283
+ try {
284
+ const users = await ctx.db.query("users").collect();
285
+ return users.find(
286
+ (user) => String(user.defaultPrincipalId ?? "") === normalizedPrincipalId
287
+ ) ?? null;
288
+ } catch {
289
+ return null;
290
+ }
291
+ }
292
+ async function findAgentByPrincipalId(ctx, principalId) {
293
+ const normalizedPrincipalId = principalId.trim();
294
+ if (!normalizedPrincipalId) {
295
+ return null;
296
+ }
297
+ if (typeof ctx.runQuery === "function") {
298
+ try {
299
+ const bridgedAgent = await ctx.runQuery(
300
+ api.agents.getAgentByPrincipalId,
301
+ {
302
+ principalId: normalizedPrincipalId
303
+ }
304
+ );
305
+ if (bridgedAgent) {
306
+ return bridgedAgent;
307
+ }
308
+ } catch {
309
+ }
310
+ }
311
+ try {
312
+ const agents = await ctx.db.query("agents").collect();
313
+ return agents.find(
314
+ (agent) => String(agent.principalId ?? "") === normalizedPrincipalId
315
+ ) ?? null;
316
+ } catch {
317
+ return null;
318
+ }
319
+ }
320
+ function defaultResolvers() {
321
+ return {
322
+ async getProject(ctx, topicId) {
323
+ return await resolveTopicProjectOverlay(ctx, topicId, {
324
+ idMode: "legacy",
325
+ projectLikeOnly: false
326
+ });
327
+ },
328
+ async listTopics(ctx) {
329
+ return await listTopicProjectOverlays(ctx, { idMode: "legacy" });
330
+ },
331
+ async listTopicsByOwner(ctx, ownerId) {
332
+ const topics = await listTopicProjectOverlays(ctx, { idMode: "legacy" });
333
+ return topics.filter((topic) => topic.ownerId === ownerId);
334
+ },
335
+ async listTopicsByVisibility(ctx, visibility) {
336
+ const topics = await listTopicProjectOverlays(ctx, { idMode: "legacy" });
337
+ return topics.filter((topic) => topic.visibility === visibility);
338
+ },
339
+ async listProjectGrantsByProjectAndPrincipal(ctx, topicId, principalId) {
340
+ return await bridgeListProjectGrantsByTopicAndPrincipal(
341
+ ctx,
342
+ topicId,
343
+ principalId
344
+ );
345
+ },
346
+ async listProjectGrantsByProjectAndGroup(ctx, topicId, groupId) {
347
+ return await bridgeListProjectGrantsByTopicAndGroup(ctx, topicId, groupId);
348
+ },
349
+ async listProjectGrantsByPrincipalStatus(ctx, principalId, status) {
350
+ return await bridgeListProjectGrantsByPrincipalStatus(
351
+ ctx,
352
+ principalId,
353
+ status
354
+ );
355
+ },
356
+ async listProjectGrantsByGroupStatus(ctx, groupId, status) {
357
+ return await bridgeListProjectGrantsByGroupStatus(ctx, groupId, status);
358
+ },
359
+ async insertProjectGrant(ctx, value) {
360
+ return await bridgeInsertProjectGrant(ctx, value);
361
+ },
362
+ async getAgentByPrincipalId(ctx, principalId) {
363
+ return await findAgentByPrincipalId(ctx, principalId);
364
+ },
365
+ async getUserByClerkId(ctx, clerkId) {
366
+ return await findUserByClerkId(ctx, clerkId);
367
+ },
368
+ async getUserByPrincipalId(ctx, principalId) {
369
+ return await findUserByPrincipalId(ctx, principalId);
370
+ }
371
+ };
372
+ }
373
+ var resolverOverrides = {};
374
+ function resolveAccessControlAppResolvers(_ctx) {
375
+ return {
376
+ ...defaultResolvers(),
377
+ ...resolverOverrides
378
+ };
379
+ }
380
+
381
+ // ../access-control/src/principalContext.ts
382
+ function requireCanonicalResolvedUser(user, clerkId) {
383
+ const resolved = user;
384
+ if (!resolved) {
385
+ throw new Error(
386
+ `[AccessControl] Canonical user identity required for ${clerkId}. Sync users.upsertUser before user-bound access checks.`
387
+ );
388
+ }
389
+ const { mcRole, defaultTenantId, defaultWorkspaceId, defaultPrincipalId } = resolved;
390
+ if (mcRole !== "platform_admin" && mcRole !== "tenant_admin" && mcRole !== "workspace_admin" && mcRole !== "editor" && mcRole !== "viewer" && mcRole !== "auditor" && mcRole !== "service_agent") {
391
+ throw new Error(
392
+ `[AccessControl] Canonical MC role required for ${clerkId}. Re-sync Master Control identity before user-bound access checks.`
393
+ );
394
+ }
395
+ if (typeof defaultTenantId !== "string" || defaultTenantId.trim().length === 0) {
396
+ throw new Error(
397
+ `[AccessControl] Canonical home tenant required for ${clerkId}. Re-sync Master Control identity before user-bound access checks.`
398
+ );
399
+ }
400
+ if (typeof defaultWorkspaceId !== "string" || defaultWorkspaceId.trim().length === 0) {
401
+ throw new Error(
402
+ `[AccessControl] Canonical home workspace required for ${clerkId}. Re-sync Master Control identity before user-bound access checks.`
403
+ );
404
+ }
405
+ if (typeof defaultPrincipalId !== "string" || defaultPrincipalId.trim().length === 0) {
406
+ throw new Error(
407
+ `[AccessControl] Canonical federated principal required for ${clerkId}. Re-sync Master Control identity before user-bound access checks.`
408
+ );
409
+ }
410
+ return {
411
+ mcRole,
412
+ defaultTenantId: defaultTenantId.trim(),
413
+ defaultWorkspaceId: defaultWorkspaceId.trim(),
414
+ defaultPrincipalId: defaultPrincipalId.trim()
415
+ };
416
+ }
417
+ function isPrincipalIdInput(value) {
418
+ return value.startsWith("user:") || value.startsWith("group:") || value.startsWith("service:") || value.startsWith("agent:") || value.startsWith("external_viewer:");
419
+ }
420
+ async function resolveCanonicalUserRecord(ctx, actorId) {
421
+ const normalizedActorId = actorId.trim();
422
+ const clerkId = isPrincipalIdInput(normalizedActorId) && normalizedActorId.startsWith("user:") ? normalizedActorId.slice("user:".length) : normalizedActorId;
423
+ const resolvers = resolveAccessControlAppResolvers();
424
+ const resolvedByClerkId = await resolvers.getUserByClerkId(ctx, clerkId);
425
+ if (resolvedByClerkId) {
426
+ return {
427
+ resolvedUser: resolvedByClerkId,
428
+ clerkId,
429
+ contextClerkId: clerkId
430
+ };
431
+ }
432
+ const resolvedByPrincipalId = await resolvers.getUserByPrincipalId(
433
+ ctx,
434
+ normalizedActorId
435
+ );
436
+ return {
437
+ resolvedUser: resolvedByPrincipalId ?? null,
438
+ clerkId,
439
+ contextClerkId: normalizedActorId.startsWith("user:") && clerkId.length > 0 ? clerkId : normalizedActorId
440
+ };
441
+ }
442
+ function uniqRoles(roles) {
443
+ const roleSet = /* @__PURE__ */ new Set();
444
+ for (const role of roles) {
445
+ if (role === "platform_admin" || role === "tenant_admin" || role === "workspace_admin" || role === "editor" || role === "viewer" || role === "auditor" || role === "service_agent") {
446
+ roleSet.add(role);
447
+ }
448
+ }
449
+ return [...roleSet];
450
+ }
451
+ function normalizeGroupIds(value) {
452
+ if (!Array.isArray(value)) {
453
+ return [];
454
+ }
455
+ return [...new Set(
456
+ value.filter((entry) => typeof entry === "string").map((entry) => entry.trim()).filter(Boolean)
457
+ )];
458
+ }
459
+ function requireServiceAgentUser(user, actorId) {
460
+ const canonicalUser = requireCanonicalResolvedUser(user, actorId);
461
+ if (canonicalUser.mcRole !== "service_agent") {
462
+ throw new Error(
463
+ `[AccessControl] Canonical service_agent identity required for ${actorId}. Sync users.upsertUser before agent-bound access checks.`
464
+ );
465
+ }
466
+ return canonicalUser;
467
+ }
468
+ function requireCanonicalResolvedAgent(agent, actorId) {
469
+ const resolved = agent;
470
+ if (!resolved) {
471
+ throw new Error(
472
+ `[AccessControl] Agent "${actorId}" not found in agents or users table.`
473
+ );
474
+ }
475
+ if (typeof resolved.principalId !== "string" || resolved.principalId.trim().length === 0) {
476
+ throw new Error(
477
+ `[AccessControl] Canonical agent principalId required for ${actorId}.`
478
+ );
479
+ }
480
+ if (typeof resolved.tenantId !== "string" || resolved.tenantId.trim().length === 0) {
481
+ throw new Error(
482
+ `[AccessControl] Canonical home tenant required for ${actorId}.`
483
+ );
484
+ }
485
+ if (typeof resolved.workspaceId !== "string" || resolved.workspaceId.trim().length === 0) {
486
+ throw new Error(
487
+ `[AccessControl] Canonical home workspace required for ${actorId}.`
488
+ );
489
+ }
490
+ return {
491
+ principalId: resolved.principalId.trim(),
492
+ tenantId: resolved.tenantId.trim(),
493
+ workspaceId: resolved.workspaceId.trim(),
494
+ roles: uniqRoles(Array.isArray(resolved.roles) ? resolved.roles : []) ?? ["service_agent"],
495
+ groupIds: normalizeGroupIds(resolved.groupIds)
496
+ };
497
+ }
498
+ async function resolvePrincipalContext(ctx, actorId) {
499
+ if (actorId.startsWith("agent:")) {
500
+ const resolvers = resolveAccessControlAppResolvers();
501
+ const resolvedAgent = await resolvers.getAgentByPrincipalId(ctx, actorId);
502
+ if (resolvedAgent) {
503
+ const agent = requireCanonicalResolvedAgent(
504
+ resolvedAgent,
505
+ actorId
506
+ );
507
+ return {
508
+ principalId: agent.principalId,
509
+ principalType: "service",
510
+ clerkId: actorId,
511
+ tenantId: agent.tenantId,
512
+ workspaceId: agent.workspaceId,
513
+ roles: agent.roles.length > 0 ? agent.roles : ["service_agent"],
514
+ groupIds: agent.groupIds,
515
+ isPlatformAdmin: false,
516
+ isTenantAdmin: false,
517
+ isWorkspaceAdmin: false,
518
+ isSystemFallback: false
519
+ };
520
+ }
521
+ const resolvedUser2 = await resolvers.getUserByClerkId(
522
+ ctx,
523
+ actorId
524
+ );
525
+ if (!resolvedUser2) {
526
+ throw new Error(
527
+ `[AccessControl] Agent "${actorId}" not found in agents or users table.`
528
+ );
529
+ }
530
+ const user2 = requireServiceAgentUser(
531
+ resolvedUser2,
532
+ actorId
533
+ );
534
+ console.warn(
535
+ `[AccessControl] Deprecated legacy service-agent fallback for ${actorId}; migrate this principal into identity.agents.`
536
+ );
537
+ return {
538
+ principalId: user2.defaultPrincipalId,
539
+ principalType: "service",
540
+ clerkId: actorId,
541
+ tenantId: user2.defaultTenantId,
542
+ workspaceId: user2.defaultWorkspaceId,
543
+ roles: ["service_agent"],
544
+ groupIds: normalizeGroupIds(resolvedUser2?.principalGroupIds),
545
+ isPlatformAdmin: false,
546
+ isTenantAdmin: false,
547
+ isWorkspaceAdmin: false,
548
+ isSystemFallback: false
549
+ };
550
+ }
551
+ const {
552
+ resolvedUser,
553
+ contextClerkId
554
+ } = await resolveCanonicalUserRecord(ctx, actorId);
555
+ const user = requireCanonicalResolvedUser(
556
+ resolvedUser,
557
+ contextClerkId
558
+ );
559
+ if (!user.defaultPrincipalId) {
560
+ throw new Error(
561
+ `[AccessControl] Canonical federated principal required for ${contextClerkId}. Re-sync Master Control identity before user-bound access checks.`
562
+ );
563
+ }
564
+ if (user.mcRole === "service_agent") {
565
+ return {
566
+ principalId: user.defaultPrincipalId,
567
+ principalType: "service",
568
+ clerkId: contextClerkId,
569
+ tenantId: user.defaultTenantId,
570
+ workspaceId: user.defaultWorkspaceId,
571
+ roles: ["service_agent"],
572
+ groupIds: normalizeGroupIds(resolvedUser?.principalGroupIds),
573
+ isPlatformAdmin: false,
574
+ isTenantAdmin: false,
575
+ isWorkspaceAdmin: false,
576
+ isSystemFallback: false
577
+ };
578
+ }
579
+ const principalId = user.defaultPrincipalId;
580
+ const effectiveRole = user.mcRole;
581
+ const roles = effectiveRole === "platform_admin" ? ["platform_admin", "tenant_admin"] : effectiveRole === "tenant_admin" ? ["tenant_admin"] : [effectiveRole];
582
+ const tenantId = user.defaultTenantId;
583
+ const workspaceId = user.defaultWorkspaceId;
584
+ const isPlatformAdmin = effectiveRole === "platform_admin";
585
+ return {
586
+ principalId,
587
+ principalType: "user",
588
+ clerkId: contextClerkId,
589
+ tenantId,
590
+ workspaceId,
591
+ roles: uniqRoles(roles),
592
+ groupIds: normalizeGroupIds(resolvedUser?.principalGroupIds),
593
+ isPlatformAdmin,
594
+ isTenantAdmin: isPlatformAdmin || effectiveRole === "tenant_admin",
595
+ isWorkspaceAdmin: isPlatformAdmin || effectiveRole === "tenant_admin" || effectiveRole === "workspace_admin",
596
+ isSystemFallback: false
597
+ };
598
+ }
599
+
600
+ // ../access-control/src/access.ts
601
+ function isTopicInPrincipalTenant(topic, principalTenantId) {
602
+ if (!topic.tenantId) {
603
+ return false;
604
+ }
605
+ if (!principalTenantId) {
606
+ return false;
607
+ }
608
+ return String(topic.tenantId) === String(principalTenantId);
609
+ }
610
+ function isTopicInPrincipalWorkspace(topic, principalWorkspaceId) {
611
+ if (!topic.workspaceId) {
612
+ return false;
613
+ }
614
+ if (!principalWorkspaceId) {
615
+ return false;
616
+ }
617
+ return String(topic.workspaceId) === String(principalWorkspaceId);
618
+ }
619
+ function isLegacyUnscopedTopic(topic) {
620
+ return !topic.tenantId || !topic.workspaceId;
621
+ }
622
+ function isGrantScopeAlignedToTopic(topic, grant) {
623
+ if (topic.tenantId && grant.tenantId && String(topic.tenantId) !== String(grant.tenantId)) {
624
+ return false;
625
+ }
626
+ if (topic.workspaceId && grant.workspaceId && String(topic.workspaceId) !== String(grant.workspaceId)) {
627
+ return false;
628
+ }
629
+ return true;
630
+ }
631
+ function isGrantSourceAllowedForVisibility(visibility, source) {
632
+ if (source !== "external_share") {
633
+ return true;
634
+ }
635
+ return visibility === "external" || visibility === "public";
636
+ }
637
+ function isGrantActive(grant) {
638
+ if (grant.status !== "active") {
639
+ return false;
640
+ }
641
+ if (grant.expiresAt !== void 0 && grant.expiresAt <= Date.now()) {
642
+ return false;
643
+ }
644
+ return true;
645
+ }
646
+ async function hasPrincipalGrant(ctx, args) {
647
+ const grants = await resolveAccessControlAppResolvers().listProjectGrantsByProjectAndPrincipal(
648
+ ctx,
649
+ args.topic._id,
650
+ args.principalId
651
+ );
652
+ if (grants.some(
653
+ (grant) => isGrantActive(grant) && isGrantScopeAlignedToTopic(args.topic, grant) && isGrantSourceAllowedForVisibility(
654
+ args.topic.visibility,
655
+ grant.source
656
+ ) && (!args.principalIsExternal || args.topic.visibility === "public" || grant.source === "external_share")
657
+ )) {
658
+ return true;
659
+ }
660
+ return false;
661
+ }
662
+ async function hasGroupGrant(ctx, args) {
663
+ if (args.groupIds.length === 0) {
664
+ return false;
665
+ }
666
+ for (const groupId of args.groupIds) {
667
+ const grants = await resolveAccessControlAppResolvers().listProjectGrantsByProjectAndGroup(ctx, args.topic._id, groupId);
668
+ if (grants.some(
669
+ (grant) => isGrantActive(grant) && isGrantScopeAlignedToTopic(args.topic, grant) && isGrantSourceAllowedForVisibility(
670
+ args.topic.visibility,
671
+ grant.source
672
+ )
673
+ )) {
674
+ return true;
675
+ }
676
+ }
677
+ return false;
678
+ }
679
+ function isExternalPrincipal(_ctx, _args) {
680
+ return false;
681
+ }
682
+ async function evaluateTopicAccessDetailed(ctx, args) {
683
+ if (args.legacyUserId) {
684
+ return {
685
+ hasAccess: true,
686
+ isAdmin: false,
687
+ isOwner: false,
688
+ isShared: false,
689
+ hasGrant: true,
690
+ isFirmVisible: true,
691
+ isExternalVisible: false,
692
+ isPublicVisible: false,
693
+ isTenantScopeMatch: true,
694
+ isWorkspaceScopeMatch: true,
695
+ isPrincipalExternal: false
696
+ };
697
+ }
698
+ const topic = await resolveAccessControlAppResolvers().getProject(
699
+ ctx,
700
+ args.topicId
701
+ );
702
+ if (!topic) {
703
+ return {
704
+ hasAccess: false,
705
+ isAdmin: false,
706
+ isOwner: false,
707
+ isShared: false,
708
+ hasGrant: false,
709
+ isFirmVisible: false,
710
+ isExternalVisible: false,
711
+ isPublicVisible: false,
712
+ isTenantScopeMatch: false,
713
+ isWorkspaceScopeMatch: false,
714
+ isPrincipalExternal: false
715
+ };
716
+ }
717
+ const { principalContext, legacyUserId } = args;
718
+ const userIsAdmin = principalContext.isPlatformAdmin;
719
+ const isOwner = topic.ownerId === legacyUserId;
720
+ const isShared = (topic.sharedWith ?? []).includes(legacyUserId);
721
+ const principalIsExternal = await isExternalPrincipal(ctx, {
722
+ groupIds: principalContext.groupIds,
723
+ topicTenantId: topic.tenantId,
724
+ topicWorkspaceId: topic.workspaceId
725
+ });
726
+ const hasPrincipalGrantResult = await hasPrincipalGrant(ctx, {
727
+ topic,
728
+ principalId: principalContext.principalId,
729
+ principalIsExternal
730
+ });
731
+ const hasGroupGrantResult = await hasGroupGrant(ctx, {
732
+ topic,
733
+ groupIds: principalContext.groupIds
734
+ });
735
+ const hasGrant = isShared || hasPrincipalGrantResult || hasGroupGrantResult;
736
+ const legacyUnscoped = isLegacyUnscopedTopic(topic);
737
+ const tenantScopeMatch = isTopicInPrincipalTenant(
738
+ topic,
739
+ principalContext.tenantId
740
+ );
741
+ const workspaceScopeMatch = isTopicInPrincipalWorkspace(
742
+ topic,
743
+ principalContext.workspaceId
744
+ );
745
+ const isPublicVisible = topic.visibility === "public";
746
+ const isFirmVisible = topic.visibility === "firm" && !legacyUnscoped && tenantScopeMatch && workspaceScopeMatch && !principalIsExternal;
747
+ const hasScopedGrant = hasGrant && (legacyUnscoped || tenantScopeMatch && workspaceScopeMatch);
748
+ const isExternalVisible = topic.visibility === "external" && hasScopedGrant;
749
+ const hasAccess = userIsAdmin || isOwner || hasScopedGrant || isPublicVisible || isFirmVisible;
750
+ return {
751
+ hasAccess,
752
+ isAdmin: userIsAdmin,
753
+ isOwner,
754
+ isShared,
755
+ hasGrant,
756
+ isFirmVisible,
757
+ isExternalVisible,
758
+ isPublicVisible,
759
+ isTenantScopeMatch: tenantScopeMatch,
760
+ isWorkspaceScopeMatch: workspaceScopeMatch,
761
+ isPrincipalExternal: principalIsExternal
762
+ };
763
+ }
764
+ async function checkTopicAccessDetailed(ctx, topicId, userId) {
765
+ const principalContext = await resolvePrincipalContext(ctx, userId);
766
+ return evaluateTopicAccessDetailed(ctx, {
767
+ topicId,
768
+ legacyUserId: userId,
769
+ principalContext
770
+ });
771
+ }
772
+ async function checkTopicAccess(ctx, topicId, userId) {
773
+ const result = await checkTopicAccessDetailed(ctx, topicId, userId);
774
+ return result.hasAccess;
775
+ }
776
+ async function checkScopeAccess(ctx, scopeId, userId) {
777
+ try {
778
+ const topic = await ctx.db.get(scopeId);
779
+ if (topic && topic.name !== void 0 && topic.type !== void 0) {
780
+ return true;
781
+ }
782
+ } catch {
783
+ }
784
+ try {
785
+ return await checkTopicAccess(ctx, scopeId, userId);
786
+ } catch {
787
+ return false;
788
+ }
789
+ }
790
+ async function requireTopicAccess(ctx, topicId, userId) {
791
+ const hasAccess = await checkTopicAccess(ctx, topicId, userId);
792
+ if (!hasAccess) {
793
+ throw new Error(
794
+ "Access denied: You don't have permission to access this topic"
795
+ );
796
+ }
797
+ }
798
+ var requireProjectAccess = requireTopicAccess;
799
+ var permissiveReturn = v.optional(v.any());
800
+ var looseJsonObject = v.record(v.string(), v.any());
801
+ var looseJsonArray = v.array(v.any());
802
+ v.union(
803
+ v.string(),
804
+ v.number(),
805
+ v.boolean(),
806
+ v.null(),
807
+ looseJsonObject,
808
+ looseJsonArray
809
+ );
810
+ var api2 = anyApi;
811
+ componentsGeneric();
812
+ var internal = anyApi;
813
+ var internalMutation = internalMutationGeneric;
814
+ var internalQuery = internalQueryGeneric;
815
+ var mutation = mutationGeneric;
816
+ var query = queryGeneric;
817
+
818
+ // src/graphTypes.ts
819
+ function getNodeLayer(nodeType2) {
820
+ const L4_TYPES = ["decision"];
821
+ const L3_TYPES = ["belief", "question", "theme", "deal"];
822
+ const L2_TYPES = ["claim", "evidence", "synthesis", "answer"];
823
+ const L1_TYPES = ["atomic_fact", "excerpt", "source"];
824
+ const ONTOLOGICAL_TYPES = [
825
+ "company",
826
+ "person",
827
+ "investor",
828
+ "function",
829
+ "value_chain"
830
+ ];
831
+ const ORGANIZATIONAL_TYPES = ["topic"];
832
+ if (L4_TYPES.includes(nodeType2)) {
833
+ return "L4";
834
+ }
835
+ if (L3_TYPES.includes(nodeType2)) {
836
+ return "L3";
837
+ }
838
+ if (L2_TYPES.includes(nodeType2)) {
839
+ return "L2";
840
+ }
841
+ if (L1_TYPES.includes(nodeType2)) {
842
+ return "L1";
843
+ }
844
+ if (ONTOLOGICAL_TYPES.includes(nodeType2)) {
845
+ return "ontological";
846
+ }
847
+ if (ORGANIZATIONAL_TYPES.includes(nodeType2)) {
848
+ return "organizational";
849
+ }
850
+ console.warn(`[GraphTypes] Unknown nodeType "${nodeType2}", defaulting to L2`);
851
+ return "L2";
852
+ }
853
+
854
+ // src/beliefLifecycle.ts
855
+ var RESOLVED_PREDICTION_OUTCOMES = [
856
+ "confirmed",
857
+ "disconfirmed",
858
+ "partial",
859
+ "expired"
860
+ ];
861
+ function hasResolvedPredictionOutcome(predictionMeta2) {
862
+ if (!predictionMeta2 || typeof predictionMeta2 !== "object") {
863
+ return false;
864
+ }
865
+ const outcome = predictionMeta2.outcome;
866
+ return typeof outcome === "string" && RESOLVED_PREDICTION_OUTCOMES.includes(outcome);
867
+ }
868
+
869
+ // src/invariantEnforcement.ts
870
+ var FORBIDDEN_GENERIC_BELIEF_METADATA_KEYS = /* @__PURE__ */ new Set([
871
+ "beliefStatus",
872
+ "epistemicStatus",
873
+ "forkedBy",
874
+ "forkedFrom",
875
+ "forkReason",
876
+ "forkTimestamp",
877
+ "status",
878
+ "supersededBy",
879
+ "supersedes"
880
+ ]);
881
+ var ONTOLOGICAL_NODE_TYPES = /* @__PURE__ */ new Set([
882
+ "company",
883
+ "person",
884
+ "investor",
885
+ "function",
886
+ "value_chain"
887
+ ]);
888
+ function throwInvariantError(args) {
889
+ const error = new Error(args.message);
890
+ error.status = args.status ?? 409;
891
+ error.code = args.code ?? "INVARIANT_VIOLATION";
892
+ error.invariantCode = args.invariantCode;
893
+ error.suggestion = args.suggestion;
894
+ error.details = args.details;
895
+ throw error;
896
+ }
897
+ function isBeliefNode(node) {
898
+ return node?.nodeType === "belief";
899
+ }
900
+ function isOntologicalNode(node) {
901
+ return typeof node?.nodeType === "string" && ONTOLOGICAL_NODE_TYPES.has(node.nodeType);
902
+ }
903
+ function isScoredBeliefNode(node) {
904
+ if (!isBeliefNode(node)) {
905
+ return false;
906
+ }
907
+ const metadata = node.metadata && typeof node.metadata === "object" ? node.metadata : void 0;
908
+ const numericConfidence = typeof node.confidence === "number" && Number.isFinite(node.confidence);
909
+ if (numericConfidence) {
910
+ return true;
911
+ }
912
+ return hasResolvedPredictionOutcome(node.predictionMeta) || hasResolvedPredictionOutcome(metadata?.predictionMeta);
913
+ }
914
+ function getForbiddenMetadataKeys(metadata) {
915
+ if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) {
916
+ return [];
917
+ }
918
+ return Object.keys(metadata).filter(
919
+ (key) => FORBIDDEN_GENERIC_BELIEF_METADATA_KEYS.has(key)
920
+ );
921
+ }
922
+ function assertBeliefNodeGenericUpdateAllowed(args) {
923
+ if (!isBeliefNode(args.node)) {
924
+ return;
925
+ }
926
+ if (Object.hasOwn(args.updates, "confidence")) {
927
+ throwInvariantError({
928
+ message: "Belief confidence is append-only. Generic node updates cannot set confidence directly.",
929
+ invariantCode: "belief.confidence_append_only",
930
+ suggestion: "Use epistemicBeliefs.modulateConfidence() so the beliefConfidence ledger and audit trail are updated together.",
931
+ details: { mutationName: args.mutationName, nodeId: args.node._id }
932
+ });
933
+ }
934
+ if (Object.hasOwn(args.updates, "status")) {
935
+ throwInvariantError({
936
+ message: "Belief status transitions must use the dedicated belief lifecycle APIs.",
937
+ invariantCode: "belief.status_transition_requires_belief_api",
938
+ suggestion: "Use epistemicBeliefs.updateStatus() or epistemicBeliefs.archive() so status transitions emit the correct audit event.",
939
+ details: { mutationName: args.mutationName, nodeId: args.node._id }
940
+ });
941
+ }
942
+ const forbiddenMetadataKeys = getForbiddenMetadataKeys(args.updates.metadata);
943
+ if (forbiddenMetadataKeys.length > 0) {
944
+ throwInvariantError({
945
+ message: "Belief lineage and lifecycle metadata cannot be rewritten through generic node updates.",
946
+ invariantCode: "belief.lineage_requires_fork_belief",
947
+ suggestion: "Use epistemicBeliefs.forkBelief() for lineage changes and dedicated belief lifecycle mutations for status changes.",
948
+ details: {
949
+ mutationName: args.mutationName,
950
+ nodeId: args.node._id,
951
+ forbiddenMetadataKeys
952
+ }
953
+ });
954
+ }
955
+ if (isScoredBeliefNode(args.node) && (Object.hasOwn(args.updates, "canonicalText") || Object.hasOwn(args.updates, "contentHash"))) {
956
+ throwInvariantError({
957
+ message: "Cannot refine a scored belief in place. Scored formulations are immutable.",
958
+ invariantCode: "belief.formulation_immutable_after_scoring",
959
+ suggestion: "Use epistemicBeliefs.forkBelief() to evolve the formulation while preserving lineage.",
960
+ details: { mutationName: args.mutationName, nodeId: args.node._id }
961
+ });
962
+ }
963
+ }
964
+ function assertBeliefNodeArchiveAllowed(args) {
965
+ if (!isBeliefNode(args.node)) {
966
+ return;
967
+ }
968
+ throwInvariantError({
969
+ message: "Belief archiving must go through the dedicated belief lifecycle API.",
970
+ invariantCode: "belief.status_transition_requires_belief_api",
971
+ suggestion: "Use epistemicBeliefs.archive() so the belief lifecycle audit trail stays consistent.",
972
+ details: { mutationName: args.mutationName, nodeId: args.node._id }
973
+ });
974
+ }
975
+ function assertBeliefNodeVerifyAllowed(args) {
976
+ if (!isBeliefNode(args.node) || args.confidence === void 0) {
977
+ return;
978
+ }
979
+ throwInvariantError({
980
+ message: "Belief verification cannot set confidence directly. Confidence changes must stay append-only.",
981
+ invariantCode: "belief.confidence_append_only",
982
+ suggestion: "Call epistemicBeliefs.modulateConfidence() after verification so the confidence history is preserved.",
983
+ details: { mutationName: args.mutationName, nodeId: args.node._id }
984
+ });
985
+ }
986
+ function assertBeliefNodeSupersedeAllowed(args) {
987
+ if (!isBeliefNode(args.node)) {
988
+ return;
989
+ }
990
+ throwInvariantError({
991
+ message: "Belief lineage changes must use forkBelief(), not the generic supersede path.",
992
+ invariantCode: "belief.lineage_requires_fork_belief",
993
+ suggestion: "Use epistemicBeliefs.forkBelief() so the child belief, supersedes edge, and audit trail are created together.",
994
+ details: { mutationName: args.mutationName, nodeId: args.node._id }
995
+ });
996
+ }
997
+ function assertBeliefNodeHardDeleteAllowed(args) {
998
+ if (!isBeliefNode(args.node)) {
999
+ return;
1000
+ }
1001
+ if (!args.allowBeliefHardDelete) {
1002
+ throwInvariantError({
1003
+ message: "Belief hard delete is forbidden by default. Beliefs must retain lineage and audit history.",
1004
+ invariantCode: "belief.hard_delete_forbidden",
1005
+ suggestion: "Use epistemicBeliefs.archive() or epistemicBeliefs.forkBelief() instead. Only migration repair flows may opt into hard delete explicitly.",
1006
+ details: { mutationName: args.mutationName, nodeId: args.node._id }
1007
+ });
1008
+ }
1009
+ if (!args.reason.trim().toLowerCase().startsWith("migration:")) {
1010
+ throwInvariantError({
1011
+ message: "Belief hard delete bypasses require a migration-scoped rationale.",
1012
+ invariantCode: "belief.hard_delete_forbidden",
1013
+ suggestion: 'Retry with allowBeliefHardDelete: true and a reason starting with "migration:" only for one-off data repair flows.',
1014
+ details: {
1015
+ mutationName: args.mutationName,
1016
+ nodeId: args.node._id,
1017
+ reason: args.reason
1018
+ }
1019
+ });
1020
+ }
1021
+ }
1022
+ function assertOntologicalNodeGenericCreateAllowed(args) {
1023
+ if (!ONTOLOGICAL_NODE_TYPES.has(args.nodeType)) {
1024
+ return;
1025
+ }
1026
+ throwInvariantError({
1027
+ message: "Ontological entities must be created through the dedicated entity lifecycle API.",
1028
+ invariantCode: "entity.create_requires_entity_lifecycle",
1029
+ suggestion: "Use entityLifecycle.createEntity() so tenant-global canonical scope and deduplication are enforced.",
1030
+ details: {
1031
+ mutationName: args.mutationName,
1032
+ nodeType: args.nodeType
1033
+ }
1034
+ });
1035
+ }
1036
+ function assertOntologicalNodeGenericUpdateAllowed(args) {
1037
+ if (!isOntologicalNode(args.node)) {
1038
+ return;
1039
+ }
1040
+ throwInvariantError({
1041
+ message: "Ontological entities must be updated through the dedicated entity lifecycle API.",
1042
+ invariantCode: "entity.update_requires_entity_lifecycle",
1043
+ suggestion: "Use entityLifecycle.updateEntityAttributes() so canonical entity mutations stay type-safe and audited.",
1044
+ details: {
1045
+ mutationName: args.mutationName,
1046
+ nodeId: args.node._id,
1047
+ nodeType: args.node.nodeType
1048
+ }
1049
+ });
1050
+ }
1051
+ function assertOntologicalNodeArchiveAllowed(args) {
1052
+ if (!isOntologicalNode(args.node)) {
1053
+ return;
1054
+ }
1055
+ throwInvariantError({
1056
+ message: "Ontological entities must be archived through the dedicated entity lifecycle API.",
1057
+ invariantCode: "entity.archive_requires_entity_lifecycle",
1058
+ suggestion: "Use entityLifecycle.archiveEntity() so entity archival emits the correct audit trail and review hooks.",
1059
+ details: {
1060
+ mutationName: args.mutationName,
1061
+ nodeId: args.node._id,
1062
+ nodeType: args.node.nodeType
1063
+ }
1064
+ });
1065
+ }
1066
+ function assertOntologicalNodeSupersedeAllowed(args) {
1067
+ if (!isOntologicalNode(args.node)) {
1068
+ return;
1069
+ }
1070
+ throwInvariantError({
1071
+ message: "Ontological entities do not use the generic supersede path.",
1072
+ invariantCode: "entity.supersede_requires_entity_lifecycle",
1073
+ suggestion: "Use entityLifecycle.updateEntityAttributes() to edit an entity in place or entityLifecycle.mergeEntities() to collapse duplicates.",
1074
+ details: {
1075
+ mutationName: args.mutationName,
1076
+ nodeId: args.node._id,
1077
+ nodeType: args.node.nodeType
1078
+ }
1079
+ });
1080
+ }
1081
+ var LEGACY_SCOPE_FIELD2 = "graphScopeProjectId";
1082
+ function asMappedProjectId(topic) {
1083
+ if (!topic) {
1084
+ return;
1085
+ }
1086
+ const directLegacyProjectId = normalizeScopeValue(topic[LEGACY_SCOPE_FIELD2]);
1087
+ if (directLegacyProjectId) {
1088
+ return directLegacyProjectId;
1089
+ }
1090
+ const metadata = topic.metadata || {};
1091
+ const candidate = metadata[LEGACY_SCOPE_FIELD2] || metadata.legacyProjectId || metadata.projectId || metadata.scopeProjectId;
1092
+ return candidate ? candidate : void 0;
1093
+ }
1094
+ function normalizeScopeValue(value) {
1095
+ if (typeof value !== "string") {
1096
+ return;
1097
+ }
1098
+ const normalized = value.trim();
1099
+ return normalized.length > 0 ? normalized : void 0;
1100
+ }
1101
+ function pickPrimaryTopic(candidates) {
1102
+ return [...candidates].sort((a, b) => {
1103
+ const depthA = a.depth ?? 9999;
1104
+ const depthB = b.depth ?? 9999;
1105
+ if (depthA !== depthB) {
1106
+ return depthA - depthB;
1107
+ }
1108
+ const createdA = a.createdAt ?? Number.MAX_SAFE_INTEGER;
1109
+ const createdB = b.createdAt ?? Number.MAX_SAFE_INTEGER;
1110
+ if (createdA !== createdB) {
1111
+ return createdA - createdB;
1112
+ }
1113
+ return String(a.name || "").localeCompare(String(b.name || ""));
1114
+ })[0];
1115
+ }
1116
+ async function findTopicsByScopeAlias(ctx, scopeId) {
1117
+ try {
1118
+ return await ctx.db.query("topics").withIndex(
1119
+ "by_graph_scope_project",
1120
+ (q) => q.eq(LEGACY_SCOPE_FIELD2, scopeId)
1121
+ ).collect();
1122
+ } catch {
1123
+ const topics = await ctx.db.query("topics").collect();
1124
+ return topics.filter((topic) => {
1125
+ const normalizedGlobalId = normalizeScopeValue(topic.globalId);
1126
+ const mappedProjectId = asMappedProjectId(topic);
1127
+ return String(topic._id) === scopeId || normalizedGlobalId === scopeId || mappedProjectId === scopeId;
1128
+ });
1129
+ }
1130
+ }
1131
+ async function tryResolveHostTopicById(ctx, topicId) {
1132
+ if (typeof ctx.runQuery !== "function") {
1133
+ return null;
1134
+ }
1135
+ try {
1136
+ return await ctx.runQuery(api2.topics.get, {
1137
+ id: topicId
1138
+ }) ?? null;
1139
+ } catch {
1140
+ return null;
1141
+ }
1142
+ }
1143
+ async function tryResolveHostTopicByLegacyScope(ctx, legacyScopeId) {
1144
+ if (typeof ctx.runQuery !== "function") {
1145
+ return null;
1146
+ }
1147
+ try {
1148
+ return await ctx.runQuery(api2.topics.getByLegacyScopeId, {
1149
+ projectId: legacyScopeId
1150
+ }) ?? null;
1151
+ } catch {
1152
+ return null;
1153
+ }
1154
+ }
1155
+ async function resolveInheritedWorkspaceScope(ctx, topic) {
1156
+ const MAX_DEPTH = 10;
1157
+ let tenantId = normalizeScopeValue(topic.tenantId);
1158
+ let workspaceId = normalizeScopeValue(topic.workspaceId);
1159
+ if (tenantId && workspaceId) {
1160
+ return { tenantId, workspaceId };
1161
+ }
1162
+ let current = topic;
1163
+ for (let i = 0; i < MAX_DEPTH && current?.parentTopicId; i++) {
1164
+ current = await ctx.db.get(current.parentTopicId);
1165
+ if (!current) break;
1166
+ if (!tenantId) {
1167
+ tenantId = normalizeScopeValue(current.tenantId);
1168
+ }
1169
+ if (!workspaceId) {
1170
+ workspaceId = normalizeScopeValue(current.workspaceId);
1171
+ }
1172
+ if (tenantId && workspaceId) break;
1173
+ }
1174
+ return { tenantId, workspaceId };
1175
+ }
1176
+ async function resolveTopicProjectScope(ctx, args) {
1177
+ if (args.topicId) {
1178
+ let topic = null;
1179
+ try {
1180
+ topic = await ctx.db.get(args.topicId);
1181
+ } catch {
1182
+ }
1183
+ if (!topic) {
1184
+ topic = await tryResolveHostTopicById(ctx, String(args.topicId));
1185
+ }
1186
+ if (!topic) {
1187
+ topic = pickPrimaryTopic(
1188
+ await findTopicsByScopeAlias(ctx, String(args.topicId))
1189
+ ) ?? null;
1190
+ }
1191
+ if (!topic) {
1192
+ throw new Error(`Topic not found: ${String(args.topicId)}`);
1193
+ }
1194
+ const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
1195
+ const mapped = asMappedProjectId(topic);
1196
+ if (mapped) {
1197
+ return {
1198
+ topicId: topic._id,
1199
+ projectId: mapped,
1200
+ tenantId: inherited.tenantId,
1201
+ workspaceId: inherited.workspaceId,
1202
+ source: "topic"
1203
+ };
1204
+ }
1205
+ return {
1206
+ topicId: topic._id,
1207
+ tenantId: inherited.tenantId,
1208
+ workspaceId: inherited.workspaceId,
1209
+ source: "topic"
1210
+ };
1211
+ }
1212
+ if (args.projectId) {
1213
+ let directTopic = null;
1214
+ try {
1215
+ directTopic = await ctx.db.get(
1216
+ args.projectId
1217
+ );
1218
+ } catch {
1219
+ }
1220
+ if (directTopic) {
1221
+ const inherited = await resolveInheritedWorkspaceScope(ctx, directTopic);
1222
+ const mapped = asMappedProjectId(directTopic);
1223
+ return {
1224
+ topicId: directTopic._id,
1225
+ projectId: mapped ?? args.projectId,
1226
+ tenantId: inherited.tenantId,
1227
+ workspaceId: inherited.workspaceId,
1228
+ source: "topic_inferred"
1229
+ };
1230
+ }
1231
+ directTopic = await tryResolveHostTopicByLegacyScope(ctx, args.projectId);
1232
+ if (directTopic) {
1233
+ const inherited = await resolveInheritedWorkspaceScope(ctx, directTopic);
1234
+ const mapped = asMappedProjectId(directTopic);
1235
+ return {
1236
+ topicId: directTopic._id,
1237
+ projectId: mapped ?? args.projectId,
1238
+ tenantId: inherited.tenantId,
1239
+ workspaceId: inherited.workspaceId,
1240
+ source: "topic_inferred"
1241
+ };
1242
+ }
1243
+ const topics = await findTopicsByScopeAlias(ctx, args.projectId);
1244
+ const primary = pickPrimaryTopic(topics);
1245
+ if (primary) {
1246
+ const inherited = await resolveInheritedWorkspaceScope(ctx, primary);
1247
+ return {
1248
+ topicId: primary._id,
1249
+ projectId: args.projectId,
1250
+ tenantId: inherited.tenantId,
1251
+ workspaceId: inherited.workspaceId,
1252
+ source: "project_mapped_topic"
1253
+ };
1254
+ }
1255
+ throw new Error(
1256
+ `Legacy project scope ${String(args.projectId)} has no mapped topic.`
1257
+ );
1258
+ }
1259
+ throw new Error(
1260
+ "Missing scope: provide topicId (preferred) or legacy projectId alias."
1261
+ );
1262
+ }
1263
+ var optionalScopeArgs = {
1264
+ projectId: v.optional(v.string()),
1265
+ topicId: v.optional(v.string())
1266
+ };
1267
+ v.number();
1268
+ v.union(
1269
+ v.literal("very_high"),
1270
+ // 0.9+
1271
+ v.literal("high"),
1272
+ // 0.7-0.9
1273
+ v.literal("medium"),
1274
+ // 0.4-0.7
1275
+ v.literal("low"),
1276
+ // 0.2-0.4
1277
+ v.literal("very_low")
1278
+ // 0-0.2
1279
+ );
1280
+ v.union(
1281
+ v.literal(1),
1282
+ // Critical
1283
+ v.literal(2),
1284
+ // High
1285
+ v.literal(3),
1286
+ // Medium
1287
+ v.literal(4),
1288
+ // Low
1289
+ v.literal(5)
1290
+ // Backlog
1291
+ );
1292
+ v.union(
1293
+ v.literal("critical"),
1294
+ v.literal("high"),
1295
+ v.literal("medium"),
1296
+ v.literal("low"),
1297
+ v.literal("backlog")
1298
+ );
1299
+ v.union(
1300
+ v.literal("active"),
1301
+ v.literal("paused"),
1302
+ v.literal("completed"),
1303
+ v.literal("archived")
1304
+ );
1305
+ v.union(
1306
+ v.literal("pending"),
1307
+ v.literal("processing"),
1308
+ v.literal("completed"),
1309
+ v.literal("failed")
1310
+ );
1311
+ v.object({
1312
+ crunchbaseId: v.optional(v.string()),
1313
+ linkedinUrl: v.optional(v.string()),
1314
+ pitchbookId: v.optional(v.string()),
1315
+ twitterUrl: v.optional(v.string()),
1316
+ domain: v.optional(v.string())
1317
+ });
1318
+ var sourceType = v.union(
1319
+ v.literal("proprietary"),
1320
+ // Internal Stack research
1321
+ v.literal("primary"),
1322
+ // Direct interviews, calls
1323
+ v.literal("secondary"),
1324
+ // Published sources
1325
+ v.literal("ai_generated"),
1326
+ // AI-synthesized
1327
+ v.literal("user_input"),
1328
+ // Manual user entry
1329
+ v.literal("inferred")
1330
+ // System inference
1331
+ );
1332
+ v.object({
1333
+ sourceType: v.optional(sourceType),
1334
+ sourceId: v.optional(v.string()),
1335
+ // Reference to source entity
1336
+ sourceUrl: v.optional(v.string()),
1337
+ sourceDate: v.optional(v.number()),
1338
+ sourceName: v.optional(v.string())
1339
+ });
1340
+ v.object({
1341
+ cursor: v.optional(v.string()),
1342
+ limit: v.optional(v.number())
1343
+ });
1344
+ v.object({
1345
+ hasMore: v.boolean(),
1346
+ nextCursor: v.optional(v.string()),
1347
+ totalCount: v.optional(v.number())
1348
+ });
1349
+ var richTextContent = v.object({
1350
+ type: v.literal("doc"),
1351
+ content: looseJsonArray
1352
+ });
1353
+ v.union(v.string(), richTextContent);
1354
+ v.object({
1355
+ promptTokens: v.optional(v.number()),
1356
+ completionTokens: v.optional(v.number()),
1357
+ totalTokens: v.optional(v.number())
1358
+ });
1359
+ v.object({
1360
+ fileName: v.optional(v.string()),
1361
+ fileSize: v.optional(v.number()),
1362
+ mimeType: v.optional(v.string()),
1363
+ storageId: v.optional(v.id("_storage")),
1364
+ externalUrl: v.optional(v.string())
1365
+ });
1366
+
1367
+ // ../schema-management/src/spine/tables/epistemicNodes.ts
1368
+ var nodeType = v.union(
1369
+ // --- L4: Audit Targets (decisions, outcomes) ---
1370
+ v.literal("decision"),
1371
+ // Investment decision with knowledge horizon snapshot
1372
+ // --- L3: Traversal Anchors (epistemic structure) ---
1373
+ v.literal("belief"),
1374
+ // Structured conviction (immutable formulation)
1375
+ v.literal("question"),
1376
+ // Unit of uncertainty
1377
+ v.literal("theme"),
1378
+ // Investment thesis / conviction cluster
1379
+ v.literal("deal"),
1380
+ // Investment evaluation process
1381
+ v.literal("topic"),
1382
+ // Hierarchical knowledge container
1383
+ // --- L2: Compression Boundary (minimum reasoning unit) ---
1384
+ v.literal("claim"),
1385
+ // Atomic assertion that can be true/false
1386
+ v.literal("evidence"),
1387
+ // Interpreted signal linked to beliefs
1388
+ v.literal("synthesis"),
1389
+ // Primers, deep research
1390
+ v.literal("answer"),
1391
+ // Immutable answer snapshot for a question
1392
+ // --- L1: Terminal Leaves (non-traversable, grounding) ---
1393
+ v.literal("atomic_fact"),
1394
+ // Raw fact from source (not interpreted)
1395
+ v.literal("excerpt"),
1396
+ // Direct quote from source document
1397
+ v.literal("source"),
1398
+ // News, documents, transcripts
1399
+ // --- Ontological Entities (things in the world) ---
1400
+ v.literal("company"),
1401
+ // Organization (subtype: private, corporate, portfolio)
1402
+ v.literal("person"),
1403
+ // Individual (founder, expert, LP, contact)
1404
+ v.literal("investor"),
1405
+ // Investment entity (subtype: vc, lp, cvc, pe, family_office, angel)
1406
+ v.literal("function"),
1407
+ // What a company does (from classifier)
1408
+ v.literal("value_chain")
1409
+ // Market structure / value flow
1410
+ );
1411
+ var epistemicLayer = v.union(
1412
+ v.literal("L4"),
1413
+ // Decisions, outcomes - audit targets
1414
+ v.literal("L3"),
1415
+ // Beliefs, questions, themes - traversal anchors
1416
+ v.literal("L2"),
1417
+ // Claims, evidence, synthesis - compression boundary
1418
+ v.literal("L1"),
1419
+ // Atomic facts, excerpts, sources - terminal leaves
1420
+ v.literal("ontological"),
1421
+ // Companies, people, etc - not epistemic
1422
+ v.literal("organizational")
1423
+ // Topics, lenses, worktrees — structural containers
1424
+ );
1425
+ var nodeStatus = v.union(
1426
+ v.literal("active"),
1427
+ v.literal("superseded"),
1428
+ // Replaced by newer version
1429
+ v.literal("archived"),
1430
+ v.literal("deleted")
1431
+ );
1432
+ var sourceType2 = v.union(
1433
+ v.literal("human"),
1434
+ // User created directly
1435
+ v.literal("ai_extracted"),
1436
+ // LLM extracted from a source
1437
+ v.literal("ai_generated"),
1438
+ // LLM synthesized/created
1439
+ v.literal("imported"),
1440
+ // External system import
1441
+ v.literal("system"),
1442
+ // System-generated (migrations, classifiers)
1443
+ v.literal("verified"),
1444
+ // Human-verified source
1445
+ v.literal("proprietary")
1446
+ // Proprietary/internal data
1447
+ );
1448
+ var verificationStatus = v.union(
1449
+ v.literal("unverified"),
1450
+ v.literal("human_verified"),
1451
+ v.literal("ai_verified"),
1452
+ v.literal("contradicted"),
1453
+ v.literal("outdated")
1454
+ );
1455
+ var syncStatus = v.union(
1456
+ v.literal("synced"),
1457
+ // Node and edges fully synced to Neo4j
1458
+ v.literal("pending_edges"),
1459
+ // Node created, edges being created
1460
+ v.literal("edge_creation_failed")
1461
+ // Edge creation failed, needs retry
1462
+ );
1463
+ var audienceLabel = v.string();
1464
+ var sensitivityTier = v.union(
1465
+ v.literal("low"),
1466
+ v.literal("medium"),
1467
+ v.literal("high"),
1468
+ v.literal("restricted")
1469
+ );
1470
+ var exportClass = v.union(
1471
+ v.literal("internal_only"),
1472
+ v.literal("client_safe"),
1473
+ v.literal("public_safe"),
1474
+ v.literal("restricted")
1475
+ );
1476
+ var anonymizationClass = v.union(
1477
+ v.literal("none"),
1478
+ v.literal("standard"),
1479
+ v.literal("strict")
1480
+ );
1481
+ var epistemicStatus = v.union(
1482
+ v.literal("hypothesis"),
1483
+ // Initial conjecture, low evidence
1484
+ v.literal("emerging"),
1485
+ // Building evidence, gaining traction
1486
+ v.literal("established"),
1487
+ // Well-evidenced, core to thesis
1488
+ v.literal("challenged"),
1489
+ // Contradicting evidence appeared
1490
+ v.literal("assumption"),
1491
+ // Taken as given, not actively tested
1492
+ v.literal("deprecated")
1493
+ // Superseded or abandoned
1494
+ );
1495
+ var beliefStatus = v.union(
1496
+ v.literal("assumption"),
1497
+ v.literal("hypothesis"),
1498
+ v.literal("belief"),
1499
+ v.literal("fact")
1500
+ );
1501
+ var reversibility = v.union(
1502
+ v.literal("irreversible"),
1503
+ // One-way door decision
1504
+ v.literal("hard_to_reverse"),
1505
+ // Significant cost to undo
1506
+ v.literal("reversible"),
1507
+ // Can change course with moderate effort
1508
+ v.literal("trivial")
1509
+ // Easy to adjust
1510
+ );
1511
+ var predictionOutcome = v.union(
1512
+ v.literal("pending"),
1513
+ v.literal("confirmed"),
1514
+ v.literal("disconfirmed"),
1515
+ v.literal("partial"),
1516
+ v.literal("expired")
1517
+ );
1518
+ var predictionMeta = v.object({
1519
+ isPrediction: v.boolean(),
1520
+ registeredAt: v.number(),
1521
+ // When prediction was made
1522
+ expectedBy: v.optional(v.number()),
1523
+ // When we expect resolution
1524
+ outcome: v.optional(predictionOutcome),
1525
+ outcomeRecordedAt: v.optional(v.number()),
1526
+ outcomeEvidenceId: v.optional(v.string()),
1527
+ // globalId of confirming evidence
1528
+ confidenceAtPrediction: v.optional(v.number()),
1529
+ // 0-1
1530
+ actualVsPredicted: v.optional(v.string())
1531
+ // Notes on how outcome compared
1532
+ });
1533
+ var methodology = v.union(
1534
+ // Primary Research (high value)
1535
+ v.literal("primary_research"),
1536
+ // Direct investigation
1537
+ v.literal("expert_interview"),
1538
+ // Expert call/interview
1539
+ v.literal("customer_interview"),
1540
+ // Customer research
1541
+ v.literal("field_observation"),
1542
+ // On-site observation
1543
+ v.literal("proprietary_data"),
1544
+ // Internal data analysis
1545
+ // Secondary Research
1546
+ v.literal("desk_research"),
1547
+ // Public sources
1548
+ v.literal("regulatory_filing"),
1549
+ // SEC, regulatory docs
1550
+ v.literal("news_article"),
1551
+ // News/press
1552
+ v.literal("academic_paper"),
1553
+ // Academic research
1554
+ // AI-Assisted
1555
+ v.literal("ai_synthesis"),
1556
+ // AI-generated synthesis
1557
+ v.literal("ai_extraction")
1558
+ // AI-extracted from source
1559
+ );
1560
+ var informationAsymmetry = v.union(
1561
+ v.literal("proprietary"),
1562
+ // Only we have this
1563
+ v.literal("early"),
1564
+ // We're early but others will get it
1565
+ v.literal("common")
1566
+ // Everyone has access
1567
+ );
1568
+ var temporalNature = v.union(
1569
+ v.literal("factual"),
1570
+ // Resolved outcome. Grounded in reality.
1571
+ v.literal("forecast"),
1572
+ // Prediction. Will resolve. Discounted weight.
1573
+ v.literal("unknown")
1574
+ // Not yet classified.
1575
+ );
1576
+ var questionType = v.union(
1577
+ v.literal("validation"),
1578
+ // Does evidence support this belief?
1579
+ v.literal("falsification"),
1580
+ // What would prove this belief wrong?
1581
+ v.literal("assumption_probe"),
1582
+ // Is this unstated assumption true?
1583
+ v.literal("prediction_test"),
1584
+ // Will this predicted outcome occur?
1585
+ v.literal("counterfactual"),
1586
+ // What would we expect if X were false?
1587
+ v.literal("discovery"),
1588
+ // What don't we know yet?
1589
+ v.literal("clarification"),
1590
+ // What does X actually mean?
1591
+ v.literal("comparison"),
1592
+ // How does X compare to Y?
1593
+ v.literal("causal"),
1594
+ // What caused X?
1595
+ v.literal("mechanism"),
1596
+ // How does X work?
1597
+ v.literal("general")
1598
+ // Unclassified
1599
+ );
1600
+ var questionPriority = v.union(
1601
+ v.literal("critical"),
1602
+ // Blocks decision-making
1603
+ v.literal("high"),
1604
+ // Important for thesis
1605
+ v.literal("medium"),
1606
+ // Would be nice to know
1607
+ v.literal("low")
1608
+ // Background/curiosity
1609
+ );
1610
+ var answerQuality = v.union(
1611
+ v.literal("definitive"),
1612
+ // Clear, well-supported
1613
+ v.literal("strong"),
1614
+ // Good evidence, high confidence
1615
+ v.literal("moderate"),
1616
+ // Some evidence
1617
+ v.literal("weak"),
1618
+ // Limited evidence
1619
+ v.literal("speculative"),
1620
+ // Mostly conjecture
1621
+ v.literal("unanswered")
1622
+ // No answer yet
1623
+ );
1624
+ var consensusView = v.union(
1625
+ v.literal("aligned"),
1626
+ // We agree with market consensus
1627
+ v.literal("ahead_of"),
1628
+ // We see this before consensus does
1629
+ v.literal("contrarian"),
1630
+ // We actively disagree with consensus
1631
+ v.literal("orthogonal"),
1632
+ // We're looking at something consensus isn't discussing
1633
+ v.literal("unknown")
1634
+ // We don't know what consensus thinks
1635
+ );
1636
+ var themeConviction = v.union(
1637
+ v.literal("high"),
1638
+ // Strong conviction, actively deploying
1639
+ v.literal("medium"),
1640
+ // Building conviction
1641
+ v.literal("low"),
1642
+ // Exploring, not convicted
1643
+ v.literal("negative")
1644
+ // Actively avoiding
1645
+ );
1646
+ var decisionType = v.union(
1647
+ v.literal("invest"),
1648
+ v.literal("pass"),
1649
+ v.literal("follow_on"),
1650
+ v.literal("exit"),
1651
+ v.literal("deep_dive"),
1652
+ v.literal("monitor"),
1653
+ v.literal("deprioritize"),
1654
+ v.literal("thesis_adopt"),
1655
+ v.literal("thesis_revise"),
1656
+ v.literal("thesis_abandon")
1657
+ );
1658
+ var decisionOutcome = v.union(
1659
+ v.literal("pending"),
1660
+ v.literal("successful"),
1661
+ v.literal("unsuccessful"),
1662
+ v.literal("mixed"),
1663
+ v.literal("unknown")
1664
+ );
1665
+ var externalIds2 = v.object({
1666
+ crunchbase: v.optional(v.string()),
1667
+ linkedin: v.optional(v.string()),
1668
+ pitchbook: v.optional(v.string()),
1669
+ twitter: v.optional(v.string()),
1670
+ website: v.optional(v.string())
1671
+ });
1672
+ defineTable({
1673
+ // === IDENTITY ===
1674
+ globalId: v.string(),
1675
+ // UUID - survives migration to Neo4j
1676
+ // === TYPE ===
1677
+ nodeType,
1678
+ // === EPISTEMIC LAYER ===
1679
+ epistemicLayer: v.optional(epistemicLayer),
1680
+ // === SUBTYPE (for typed entities) ===
1681
+ subtype: v.optional(v.string()),
1682
+ // company: private|corporate|portfolio, investor: vc|lp|cvc|pe|family_office|angel
1683
+ // === CONTENT ===
1684
+ canonicalText: v.string(),
1685
+ // The core content (belief statement, company name, etc.)
1686
+ contentHash: v.string(),
1687
+ // SHA256(nodeType + canonicalText) for deduplication
1688
+ // Extended content (for sources/syntheses)
1689
+ content: v.optional(v.string()),
1690
+ // Full text for documents/articles
1691
+ contentType: v.optional(v.string()),
1692
+ // "markdown", "html", "pdf", "text"
1693
+ // === METADATA ===
1694
+ title: v.optional(v.string()),
1695
+ // Display title
1696
+ tags: v.optional(v.array(v.string())),
1697
+ domain: v.optional(v.string()),
1698
+ // For companies: website domain
1699
+ // Type-specific metadata (flexible object - LEGACY)
1700
+ // New code should use the typed fields below when available
1701
+ metadata: v.optional(looseJsonObject),
1702
+ // === POLICY / ENTITLEMENT ===
1703
+ tenantId: v.optional(v.string()),
1704
+ workspaceId: v.optional(v.string()),
1705
+ ownerPrincipalId: v.optional(v.string()),
1706
+ audienceLabel: v.optional(audienceLabel),
1707
+ policyTags: v.optional(v.array(v.string())),
1708
+ sensitivityTier: v.optional(sensitivityTier),
1709
+ exportClass: v.optional(exportClass),
1710
+ anonymizationClass: v.optional(anonymizationClass),
1711
+ // === PUBLICATION (visibility-based, not copy-based) ===
1712
+ // Publication expands who can see a workspace-local node — the node stays
1713
+ // in its workspace, like a microservice exposing part of its API surface.
1714
+ // Rules-based: pack/tenant-level publicationRules auto-evaluate on
1715
+ // confidence changes and node creation. No manual click-by-click.
1716
+ publicationStatus: v.optional(
1717
+ v.union(
1718
+ v.literal("unpublished"),
1719
+ // Default: workspace-local only
1720
+ v.literal("published"),
1721
+ // Visible at tenant scope (rules matched)
1722
+ v.literal("suppressed")
1723
+ // Manually blocked even if rules match
1724
+ )
1725
+ ),
1726
+ publishedAt: v.optional(v.number()),
1727
+ // When publication status last changed to published
1728
+ publishedBy: v.optional(v.string()),
1729
+ // userId or "system:publication_rules" for auto-publish
1730
+ // === TYPED METADATA FIELDS ===
1731
+ // --- Belief ---
1732
+ // Belief type — validated against schemaEnumConfig category "belief_type"
1733
+ // Platform core: hypothesis, belief, principle, invariant, assumption,
1734
+ // tenet, prior, preference, goal, forecast
1735
+ beliefType: v.optional(v.string()),
1736
+ beliefStatus: v.optional(beliefStatus),
1737
+ epistemicStatus: v.optional(epistemicStatus),
1738
+ reversibility: v.optional(reversibility),
1739
+ predictionMeta: v.optional(predictionMeta),
1740
+ // Consensus tracking (for non-consensus detection)
1741
+ consensusView: v.optional(consensusView),
1742
+ consensusConfidence: v.optional(v.number()),
1743
+ // 0-1: What we think consensus confidence is
1744
+ consensusSource: v.optional(v.string()),
1745
+ // Where we got the consensus view (twitter, reports, etc.)
1746
+ // --- Evidence ---
1747
+ methodology: v.optional(methodology),
1748
+ informationAsymmetry: v.optional(informationAsymmetry),
1749
+ temporalNature: v.optional(temporalNature),
1750
+ // --- Question ---
1751
+ questionType: v.optional(questionType),
1752
+ questionPriority: v.optional(questionPriority),
1753
+ answerQuality: v.optional(answerQuality),
1754
+ // --- Theme ---
1755
+ themeConviction: v.optional(themeConviction),
1756
+ // Market timing (for "early on theme" detection)
1757
+ marketAwarenessDate: v.optional(v.number()),
1758
+ // When this theme became broadly discussed
1759
+ marketAwarenessSource: v.optional(v.string()),
1760
+ // How we know (first major report, twitter volume spike, etc.)
1761
+ earlySignalIds: v.optional(v.array(v.string())),
1762
+ // globalIds of evidence we had before market awareness
1763
+ // --- Decision ---
1764
+ decisionType: v.optional(decisionType),
1765
+ decisionOutcome: v.optional(decisionOutcome),
1766
+ // === EXTERNAL IDS (for ontological entities) ===
1767
+ externalIds: v.optional(externalIds2),
1768
+ // === PROVENANCE ===
1769
+ sourceType: sourceType2,
1770
+ aiProvider: v.optional(v.string()),
1771
+ // "claude", "gemini", "gpt-4", etc.
1772
+ extractedFromNodeId: v.optional(v.id("epistemicNodes")),
1773
+ // Quick reference to source
1774
+ // === EXTRACTION CONTEXT ===
1775
+ extractionModel: v.optional(v.string()),
1776
+ // "claude-sonnet-4-20250514"
1777
+ extractionPromptName: v.optional(v.string()),
1778
+ // "lucern/extract-evidence"
1779
+ extractionPromptVersion: v.optional(v.number()),
1780
+ extractionTemperature: v.optional(v.number()),
1781
+ extractionLangfuseTraceId: v.optional(v.string()),
1782
+ // === GROUNDING VERIFICATION ===
1783
+ groundingVerified: v.optional(v.boolean()),
1784
+ groundingConfidence: v.optional(v.number()),
1785
+ // 0-1 match quality
1786
+ groundingMatchedText: v.optional(v.string()),
1787
+ // Actual text from source
1788
+ groundingStartOffset: v.optional(v.number()),
1789
+ groundingEndOffset: v.optional(v.number()),
1790
+ groundingRejectionReason: v.optional(v.string()),
1791
+ // === CONFIDENCE & VERIFICATION ===
1792
+ confidence: v.optional(v.number()),
1793
+ // 0-1 projected probability P(x) = b + a*u
1794
+ verificationStatus: v.optional(verificationStatus),
1795
+ // === SL OPINION (Subjective Logic — Kernel v2) ===
1796
+ // Replaces scalar confidence with rich epistemic state.
1797
+ // b + d + u = 1. P(x) = b + a*u is stored in `confidence` for backward compat.
1798
+ opinion_b: v.optional(v.number()),
1799
+ // Belief: evidence FOR (0-1)
1800
+ opinion_d: v.optional(v.number()),
1801
+ // Disbelief: evidence AGAINST (0-1)
1802
+ opinion_u: v.optional(v.number()),
1803
+ // Uncertainty: absence of evidence (0-1)
1804
+ opinion_a: v.optional(v.number()),
1805
+ // Base rate / prior probability (0-1)
1806
+ tupleContradicted: v.optional(v.boolean()),
1807
+ // Single-belief tuple-space contradiction flag
1808
+ // === LIFECYCLE ===
1809
+ status: nodeStatus,
1810
+ supersededBy: v.optional(v.id("epistemicNodes")),
1811
+ // === OWNERSHIP ===
1812
+ topicId: v.optional(v.string()),
1813
+ // Canonical scope container (topic-first model)
1814
+ projectId: v.optional(v.string()),
1815
+ // DEPRECATED: Use belongs_to edges
1816
+ createdBy: v.string(),
1817
+ // Clerk user ID
1818
+ createdAt: v.number(),
1819
+ updatedAt: v.number(),
1820
+ // === NEO4J SYNC STATUS ===
1821
+ syncStatus: v.optional(syncStatus),
1822
+ syncError: v.optional(v.string())
1823
+ // Error message if sync failed
1824
+ }).index("by_globalId", ["globalId"]).index("by_contentHash", ["contentHash"]).index("by_nodeType", ["nodeType"]).index("by_subtype", ["nodeType", "subtype"]).index("by_domain", ["domain"]).index("by_project", ["projectId"]).index("by_project_type", ["projectId", "nodeType"]).index("by_topic", ["topicId"]).index("by_topic_type", ["topicId", "nodeType"]).index("by_tenantId", ["tenantId"]).index("by_workspaceId", ["workspaceId"]).index("by_tenant_workspace", ["tenantId", "workspaceId"]).index("by_audienceLabel", ["audienceLabel"]).index("by_sensitivityTier", ["sensitivityTier"]).index("by_exportClass", ["exportClass"]).index("by_status", ["status"]).index("by_sourceType", ["sourceType"]).index("by_verification", ["verificationStatus"]).index("by_layer", ["epistemicLayer"]).index("by_layer_type", ["epistemicLayer", "nodeType"]).index("by_syncStatus", ["syncStatus"]).index("by_publicationStatus", ["publicationStatus"]).index("by_tenant_publicationStatus", ["tenantId", "publicationStatus"]).index("by_belief_status", ["nodeType", "beliefStatus"]).index("by_epistemic_status", ["nodeType", "epistemicStatus"]).index("by_temporal_nature", ["nodeType", "temporalNature"]).index("by_methodology", ["nodeType", "methodology"]).index("by_reversibility", ["nodeType", "reversibility"]).index("by_questionType", ["nodeType", "questionType"]).index("by_questionPriority", ["nodeType", "questionPriority"]).searchIndex("search_canonicalText", {
1825
+ searchField: "canonicalText",
1826
+ filterFields: ["nodeType", "projectId", "topicId", "status"]
1827
+ });
1828
+ function getLayerForNodeType(type) {
1829
+ switch (type) {
1830
+ case "decision":
1831
+ return "L4";
1832
+ case "belief":
1833
+ case "question":
1834
+ case "theme":
1835
+ case "deal":
1836
+ return "L3";
1837
+ case "claim":
1838
+ case "evidence":
1839
+ case "synthesis":
1840
+ case "answer":
1841
+ return "L2";
1842
+ case "atomic_fact":
1843
+ case "excerpt":
1844
+ case "source":
1845
+ return "L1";
1846
+ case "topic":
1847
+ return "organizational";
1848
+ case "company":
1849
+ case "person":
1850
+ case "investor":
1851
+ case "function":
1852
+ case "value_chain":
1853
+ return "ontological";
1854
+ }
1855
+ }
1856
+
1857
+ // src/workspaceIsolation.ts
1858
+ function normalizeScopeValue2(value) {
1859
+ if (typeof value !== "string") {
1860
+ return;
1861
+ }
1862
+ const normalized = value.trim();
1863
+ return normalized.length > 0 ? normalized : void 0;
1864
+ }
1865
+ function throwWorkspaceIsolationError(args) {
1866
+ const error = new Error(args.message);
1867
+ error.status = 409;
1868
+ error.code = "INVARIANT_VIOLATION";
1869
+ error.invariantCode = args.invariantCode;
1870
+ error.suggestion = args.suggestion;
1871
+ error.details = args.details;
1872
+ throw error;
1873
+ }
1874
+ function assertWorkspaceScopedEpistemicNodeScope(args) {
1875
+ const layer = getLayerForNodeType(args.nodeType);
1876
+ if (layer === "ontological") {
1877
+ return;
1878
+ }
1879
+ const workspaceId = normalizeScopeValue2(args.scope.workspaceId);
1880
+ if (workspaceId) {
1881
+ return;
1882
+ }
1883
+ throwWorkspaceIsolationError({
1884
+ message: "Workspace-scoped reasoning isolation requires workspaceId on non-ontological node creation.",
1885
+ invariantCode: "workspace.scope_required_for_epistemic_nodes",
1886
+ suggestion: "Resolve the topic/project scope through a workspace-bound topic before creating epistemic nodes.",
1887
+ details: {
1888
+ mutationName: args.mutationName,
1889
+ nodeType: args.nodeType,
1890
+ topicId: args.scope.topicId,
1891
+ projectId: args.scope.projectId
1892
+ }
1893
+ });
1894
+ }
1895
+ function resolveRuntimePackMutationContext(args) {
1896
+ if (!args.runtimeToolName && !args.runtimePackKey && !args.runtimePackInstallScope) {
1897
+ return;
1898
+ }
1899
+ return {
1900
+ toolName: args.runtimeToolName,
1901
+ packKey: args.runtimePackKey,
1902
+ packInstallScope: args.runtimePackInstallScope
1903
+ };
1904
+ }
1905
+ function assertTenantPackWorkspaceMutationAllowed(args) {
1906
+ if (!args.runtime?.packKey || args.runtime.packInstallScope !== "tenant") {
1907
+ return;
1908
+ }
1909
+ const targetWorkspaceId = normalizeScopeValue2(args.target.workspaceId);
1910
+ const targetLayer = typeof args.target.epistemicLayer === "string" ? args.target.epistemicLayer : void 0;
1911
+ if (!targetWorkspaceId || targetLayer === "ontological") {
1912
+ return;
1913
+ }
1914
+ throwWorkspaceIsolationError({
1915
+ message: `Tenant-scoped pack "${args.runtime.packKey}" cannot mutate workspace-scoped reasoning state.`,
1916
+ invariantCode: "workspace.tenant_pack_reasoning_write_forbidden",
1917
+ suggestion: "Use a workspace-scoped pack for workspace-local graph mutations, or route the change through tenant-global canonical entity flows.",
1918
+ details: {
1919
+ mutationName: args.mutationName,
1920
+ toolName: args.runtime.toolName,
1921
+ packKey: args.runtime.packKey,
1922
+ targetWorkspaceId,
1923
+ targetNodeType: args.target.nodeType,
1924
+ targetLayer
1925
+ }
1926
+ });
1927
+ }
1928
+
1929
+ // src/epistemicNodes.ts
1930
+ var epistemicLayerValidator = v.union(
1931
+ v.literal("L4"),
1932
+ v.literal("L3"),
1933
+ v.literal("L2"),
1934
+ v.literal("L1"),
1935
+ v.literal("ontological"),
1936
+ v.literal("organizational")
1937
+ );
1938
+ var l4NodeTypeValidator = v.union(v.literal("decision"));
1939
+ var l3NodeTypeValidator = v.union(
1940
+ v.literal("belief"),
1941
+ v.literal("question"),
1942
+ v.literal("theme"),
1943
+ v.literal("deal")
1944
+ );
1945
+ var l2NodeTypeValidator = v.union(
1946
+ v.literal("claim"),
1947
+ v.literal("evidence"),
1948
+ v.literal("synthesis")
1949
+ );
1950
+ var l1NodeTypeValidator = v.union(
1951
+ v.literal("atomic_fact"),
1952
+ v.literal("excerpt"),
1953
+ v.literal("source")
1954
+ );
1955
+ var epistemicNodeTypeValidator = v.union(
1956
+ // L4: Audit targets
1957
+ v.literal("decision"),
1958
+ // L3: Traversal anchors
1959
+ v.literal("belief"),
1960
+ v.literal("question"),
1961
+ v.literal("theme"),
1962
+ v.literal("deal"),
1963
+ // L2: Compression boundary
1964
+ v.literal("claim"),
1965
+ v.literal("evidence"),
1966
+ v.literal("synthesis"),
1967
+ v.literal("answer"),
1968
+ // L1: Terminal leaves
1969
+ v.literal("atomic_fact"),
1970
+ v.literal("excerpt"),
1971
+ v.literal("source")
1972
+ );
1973
+ var ontologicalNodeTypeValidator = v.union(
1974
+ v.literal("company"),
1975
+ v.literal("person"),
1976
+ v.literal("investor"),
1977
+ v.literal("function"),
1978
+ v.literal("value_chain")
1979
+ );
1980
+ var organizationalNodeTypeValidator = v.union(v.literal("topic"));
1981
+ var nodeTypeValidator = v.union(
1982
+ // L4: Audit targets
1983
+ v.literal("decision"),
1984
+ // L3: Traversal anchors
1985
+ v.literal("belief"),
1986
+ v.literal("question"),
1987
+ v.literal("theme"),
1988
+ v.literal("deal"),
1989
+ // L2: Compression boundary
1990
+ v.literal("claim"),
1991
+ v.literal("evidence"),
1992
+ v.literal("synthesis"),
1993
+ v.literal("answer"),
1994
+ // L1: Terminal leaves
1995
+ v.literal("atomic_fact"),
1996
+ v.literal("excerpt"),
1997
+ v.literal("source"),
1998
+ // Ontological
1999
+ v.literal("company"),
2000
+ v.literal("person"),
2001
+ v.literal("investor"),
2002
+ v.literal("function"),
2003
+ v.literal("value_chain"),
2004
+ // Organizational
2005
+ v.literal("topic")
2006
+ );
2007
+ var sourceTypeValidator = v.union(
2008
+ v.literal("human"),
2009
+ v.literal("ai_extracted"),
2010
+ v.literal("ai_generated"),
2011
+ v.literal("imported"),
2012
+ v.literal("system")
2013
+ // System-generated (migrations, classifiers)
2014
+ );
2015
+ var statusValidator = v.union(
2016
+ v.literal("active"),
2017
+ v.literal("superseded"),
2018
+ v.literal("archived"),
2019
+ v.literal("deleted")
2020
+ );
2021
+ var verificationStatusValidator = v.union(
2022
+ v.literal("unverified"),
2023
+ v.literal("human_verified"),
2024
+ v.literal("ai_verified"),
2025
+ v.literal("contradicted"),
2026
+ v.literal("outdated")
2027
+ );
2028
+ var optionalNodeScopeArgs = optionalScopeArgs;
2029
+ var get = query({
2030
+ args: { nodeId: v.id("epistemicNodes") },
2031
+ returns: permissiveReturn,
2032
+ handler: async (ctx, args) => {
2033
+ return await ctx.db.get(args.nodeId);
2034
+ }
2035
+ });
2036
+ var getByGlobalId = query({
2037
+ args: { globalId: v.string() },
2038
+ returns: permissiveReturn,
2039
+ handler: async (ctx, args) => {
2040
+ return await ctx.db.query("epistemicNodes").withIndex("by_globalId", (q) => q.eq("globalId", args.globalId)).first();
2041
+ }
2042
+ });
2043
+ var getByContentHash = query({
2044
+ args: { contentHash: v.string() },
2045
+ returns: permissiveReturn,
2046
+ handler: async (ctx, args) => {
2047
+ return await ctx.db.query("epistemicNodes").withIndex("by_contentHash", (q) => q.eq("contentHash", args.contentHash)).collect();
2048
+ }
2049
+ });
2050
+ var getByProjectAndType = query({
2051
+ args: {
2052
+ ...optionalNodeScopeArgs,
2053
+ nodeType: nodeTypeValidator,
2054
+ status: v.optional(statusValidator),
2055
+ limit: v.optional(v.number())
2056
+ },
2057
+ returns: permissiveReturn,
2058
+ handler: async (ctx, args) => {
2059
+ if (!args.projectId && !args.topicId) {
2060
+ return [];
2061
+ }
2062
+ let scope;
2063
+ try {
2064
+ scope = await resolveTopicProjectScope(ctx, {
2065
+ projectId: args.projectId,
2066
+ topicId: args.topicId
2067
+ });
2068
+ } catch {
2069
+ return [];
2070
+ }
2071
+ const pageSize = clampNodeLimit(args.limit);
2072
+ const scanLimit = Math.min(pageSize * 3, MAX_NODE_PAGE_SIZE);
2073
+ const nodes = await collectScopedNodes(ctx, scope, {
2074
+ nodeType: args.nodeType,
2075
+ scanLimit
2076
+ });
2077
+ if (args.status) {
2078
+ return nodes.filter(
2079
+ (n) => n.status === args.status && nodeMatchesWorkspaceReasoningScope(n, scope)
2080
+ ).slice(0, pageSize);
2081
+ }
2082
+ return nodes.filter(
2083
+ (n) => n.status === "active" && nodeMatchesWorkspaceReasoningScope(n, scope)
2084
+ ).slice(0, pageSize);
2085
+ }
2086
+ });
2087
+ var getByProjectAndTypeLite = query({
2088
+ args: {
2089
+ ...optionalNodeScopeArgs,
2090
+ nodeType: nodeTypeValidator,
2091
+ status: v.optional(statusValidator),
2092
+ limit: v.optional(v.number())
2093
+ },
2094
+ returns: permissiveReturn,
2095
+ handler: async (ctx, args) => {
2096
+ if (!args.projectId && !args.topicId) {
2097
+ return [];
2098
+ }
2099
+ let scope;
2100
+ try {
2101
+ scope = await resolveTopicProjectScope(ctx, {
2102
+ projectId: args.projectId,
2103
+ topicId: args.topicId
2104
+ });
2105
+ } catch {
2106
+ return [];
2107
+ }
2108
+ const pageSize = clampNodeLimit(args.limit);
2109
+ const scanLimit = Math.min(pageSize * 3, MAX_NODE_PAGE_SIZE);
2110
+ const query2 = ctx.db.query("epistemicNodes").withIndex(
2111
+ scope.topicId ? "by_topic_type" : "by_project_type",
2112
+ (q) => scope.topicId ? q.eq("topicId", scope.topicId).eq("nodeType", args.nodeType) : q.eq("projectId", scope.projectId).eq("nodeType", args.nodeType)
2113
+ );
2114
+ const nodes = await query2.order("desc").take(scanLimit);
2115
+ const statusFiltered = args.status ? nodes.filter((n) => n.status === args.status) : nodes.filter((n) => n.status === "active");
2116
+ const capped = statusFiltered.slice(0, pageSize);
2117
+ return capped.map((n) => ({
2118
+ _id: n._id,
2119
+ globalId: n.globalId,
2120
+ nodeType: n.nodeType,
2121
+ createdBy: n.createdBy,
2122
+ topicId: n.topicId,
2123
+ projectId: n.projectId,
2124
+ status: n.status,
2125
+ epistemicLayer: n.epistemicLayer
2126
+ }));
2127
+ }
2128
+ });
2129
+ var DEFAULT_NODE_PAGE_SIZE = 250;
2130
+ var MAX_NODE_PAGE_SIZE = 8e3;
2131
+ function clampNodeLimit(limit, fallback = DEFAULT_NODE_PAGE_SIZE) {
2132
+ if (!Number.isFinite(limit)) {
2133
+ return fallback;
2134
+ }
2135
+ return Math.max(1, Math.min(Math.floor(limit), MAX_NODE_PAGE_SIZE));
2136
+ }
2137
+ function dedupeWorkspaceNodes(nodes) {
2138
+ const seen = /* @__PURE__ */ new Set();
2139
+ const deduped = [];
2140
+ for (const node of nodes) {
2141
+ const key = String(node._id);
2142
+ if (seen.has(key)) {
2143
+ continue;
2144
+ }
2145
+ seen.add(key);
2146
+ deduped.push(node);
2147
+ }
2148
+ return deduped;
2149
+ }
2150
+ function nodeMatchesWorkspaceReasoningScope(node, scope) {
2151
+ return scope.topicId !== void 0 && node.topicId === scope.topicId || scope.projectId !== void 0 && node.projectId === scope.projectId;
2152
+ }
2153
+ async function collectScopedNodes(ctx, scope, args) {
2154
+ const queries = [];
2155
+ if (scope.topicId) {
2156
+ queries.push(
2157
+ args.nodeType ? ctx.db.query("epistemicNodes").withIndex(
2158
+ "by_topic_type",
2159
+ (q) => q.eq("topicId", scope.topicId).eq("nodeType", args.nodeType)
2160
+ ).order("desc").take(args.scanLimit) : ctx.db.query("epistemicNodes").withIndex("by_topic", (q) => q.eq("topicId", scope.topicId)).order("desc").take(args.scanLimit)
2161
+ );
2162
+ }
2163
+ if (scope.projectId && !scope.topicId) {
2164
+ queries.push(
2165
+ args.nodeType ? ctx.db.query("epistemicNodes").withIndex(
2166
+ "by_topic_type",
2167
+ (q) => q.eq("topicId", scope.projectId).eq("nodeType", args.nodeType)
2168
+ ).order("desc").take(args.scanLimit) : ctx.db.query("epistemicNodes").withIndex(
2169
+ "by_topic",
2170
+ (q) => q.eq("topicId", scope.projectId)
2171
+ ).order("desc").take(args.scanLimit)
2172
+ );
2173
+ }
2174
+ const combined = dedupeWorkspaceNodes((await Promise.all(queries)).flat());
2175
+ return combined.filter((node) => nodeMatchesWorkspaceReasoningScope(node, scope));
2176
+ }
2177
+ var getByProject = query({
2178
+ args: {
2179
+ ...optionalNodeScopeArgs,
2180
+ userId: v.optional(v.string()),
2181
+ includeArchived: v.optional(v.boolean()),
2182
+ limit: v.optional(v.number())
2183
+ },
2184
+ returns: permissiveReturn,
2185
+ handler: async (ctx, args) => {
2186
+ if (!args.projectId && !args.topicId) {
2187
+ return [];
2188
+ }
2189
+ const pageSize = clampNodeLimit(args.limit);
2190
+ const scanLimit = Math.min(pageSize * 3, MAX_NODE_PAGE_SIZE);
2191
+ let scope;
2192
+ try {
2193
+ scope = await resolveTopicProjectScope(ctx, {
2194
+ projectId: args.projectId,
2195
+ topicId: args.topicId
2196
+ });
2197
+ } catch {
2198
+ return [];
2199
+ }
2200
+ if (args.userId) {
2201
+ const hasAccess = await checkScopeAccess(
2202
+ ctx,
2203
+ String(scope.topicId ?? scope.projectId),
2204
+ args.userId
2205
+ );
2206
+ if (!hasAccess) {
2207
+ return [];
2208
+ }
2209
+ }
2210
+ const nodes = await collectScopedNodes(ctx, scope, { scanLimit });
2211
+ if (args.includeArchived) {
2212
+ return nodes.filter(
2213
+ (n) => n.status !== "deleted" && nodeMatchesWorkspaceReasoningScope(n, scope)
2214
+ ).slice(0, pageSize);
2215
+ }
2216
+ return nodes.filter(
2217
+ (n) => n.status === "active" && nodeMatchesWorkspaceReasoningScope(n, scope)
2218
+ ).slice(0, pageSize);
2219
+ }
2220
+ });
2221
+ var getByTopic = getByProject;
2222
+ var listAll = query({
2223
+ args: {
2224
+ limit: v.optional(v.number())
2225
+ },
2226
+ returns: permissiveReturn,
2227
+ handler: async (ctx, args) => {
2228
+ const pageSize = clampNodeLimit(args.limit ?? 2e3);
2229
+ const scanLimit = Math.min(pageSize * 2, MAX_NODE_PAGE_SIZE * 2);
2230
+ const nodes = await ctx.db.query("epistemicNodes").order("desc").take(scanLimit);
2231
+ return nodes.filter((n) => n.status === "active").slice(0, pageSize);
2232
+ }
2233
+ });
2234
+ var search = query({
2235
+ args: {
2236
+ searchQuery: v.string(),
2237
+ ...optionalNodeScopeArgs,
2238
+ nodeType: v.optional(nodeTypeValidator),
2239
+ limit: v.optional(v.number())
2240
+ },
2241
+ returns: permissiveReturn,
2242
+ handler: async (ctx, args) => {
2243
+ const pageSize = clampNodeLimit(args.limit, 100);
2244
+ let scope;
2245
+ if (args.projectId || args.topicId) {
2246
+ try {
2247
+ scope = await resolveTopicProjectScope(ctx, {
2248
+ projectId: args.projectId,
2249
+ topicId: args.topicId
2250
+ });
2251
+ } catch {
2252
+ return [];
2253
+ }
2254
+ }
2255
+ const searchResults = ctx.db.query("epistemicNodes").withSearchIndex("search_canonicalText", (q) => {
2256
+ let search2 = q.search("canonicalText", args.searchQuery);
2257
+ if (scope?.topicId) {
2258
+ search2 = search2.eq("topicId", scope.topicId);
2259
+ } else if (scope?.projectId) {
2260
+ search2 = search2.eq("projectId", scope.projectId);
2261
+ }
2262
+ if (args.nodeType) {
2263
+ search2 = search2.eq("nodeType", args.nodeType);
2264
+ }
2265
+ return search2.eq("status", "active");
2266
+ });
2267
+ return await searchResults.take(pageSize);
2268
+ }
2269
+ });
2270
+ var create = mutation({
2271
+ args: {
2272
+ globalId: v.string(),
2273
+ nodeType: nodeTypeValidator,
2274
+ subtype: v.optional(v.string()),
2275
+ // company: private|corporate|portfolio, investor: vc|lp|cvc|pe|etc
2276
+ canonicalText: v.string(),
2277
+ contentHash: v.string(),
2278
+ content: v.optional(v.string()),
2279
+ contentType: v.optional(v.string()),
2280
+ title: v.optional(v.string()),
2281
+ tags: v.optional(v.array(v.string())),
2282
+ domain: v.optional(v.string()),
2283
+ // For companies: website domain
2284
+ metadata: v.optional(v.any()),
2285
+ externalIds: v.optional(
2286
+ v.object({
2287
+ crunchbase: v.optional(v.string()),
2288
+ linkedin: v.optional(v.string()),
2289
+ pitchbook: v.optional(v.string()),
2290
+ twitter: v.optional(v.string()),
2291
+ website: v.optional(v.string())
2292
+ })
2293
+ ),
2294
+ sourceType: sourceTypeValidator,
2295
+ aiProvider: v.optional(v.string()),
2296
+ extractedFromNodeId: v.optional(v.id("epistemicNodes")),
2297
+ confidence: v.optional(v.number()),
2298
+ verificationStatus: v.optional(verificationStatusValidator),
2299
+ ...optionalNodeScopeArgs,
2300
+ createdBy: v.string(),
2301
+ runtimeToolName: v.optional(v.string()),
2302
+ runtimePackKey: v.optional(v.string()),
2303
+ runtimePackInstallScope: v.optional(
2304
+ v.union(v.literal("tenant"), v.literal("workspace"))
2305
+ )
2306
+ },
2307
+ returns: permissiveReturn,
2308
+ handler: async (ctx, args) => {
2309
+ const now = Date.now();
2310
+ assertOntologicalNodeGenericCreateAllowed({
2311
+ nodeType: args.nodeType,
2312
+ mutationName: "epistemicNodes.create"
2313
+ });
2314
+ const existing = await ctx.db.query("epistemicNodes").withIndex("by_contentHash", (q) => q.eq("contentHash", args.contentHash)).first();
2315
+ if (existing && existing.status === "active") {
2316
+ return { nodeId: existing._id, isDuplicate: true };
2317
+ }
2318
+ const epistemicLayer2 = getNodeLayer(args.nodeType);
2319
+ const resolvedScope = args.topicId || args.projectId ? await resolveTopicProjectScope(ctx, {
2320
+ topicId: args.topicId,
2321
+ projectId: args.projectId
2322
+ }) : void 0;
2323
+ if (resolvedScope) {
2324
+ assertWorkspaceScopedEpistemicNodeScope({
2325
+ scope: resolvedScope,
2326
+ nodeType: args.nodeType,
2327
+ mutationName: "epistemicNodes.create"
2328
+ });
2329
+ assertTenantPackWorkspaceMutationAllowed({
2330
+ runtime: resolveRuntimePackMutationContext(args),
2331
+ target: {
2332
+ tenantId: resolvedScope.tenantId,
2333
+ workspaceId: resolvedScope.workspaceId,
2334
+ nodeType: args.nodeType,
2335
+ epistemicLayer: epistemicLayer2
2336
+ },
2337
+ mutationName: "epistemicNodes.create"
2338
+ });
2339
+ } else if (epistemicLayer2 !== "ontological") {
2340
+ throw new Error(
2341
+ "Workspace-scoped reasoning isolation requires topicId or projectId for non-ontological node creation."
2342
+ );
2343
+ }
2344
+ const nodeId = await ctx.db.insert("epistemicNodes", {
2345
+ globalId: args.globalId,
2346
+ nodeType: args.nodeType,
2347
+ epistemicLayer: epistemicLayer2,
2348
+ // Phase 2B: Auto-derived from nodeType
2349
+ subtype: args.subtype,
2350
+ canonicalText: args.canonicalText,
2351
+ contentHash: args.contentHash,
2352
+ content: args.content,
2353
+ contentType: args.contentType,
2354
+ title: args.title,
2355
+ tags: args.tags,
2356
+ domain: args.domain,
2357
+ metadata: args.metadata,
2358
+ externalIds: args.externalIds,
2359
+ sourceType: args.sourceType,
2360
+ aiProvider: args.aiProvider,
2361
+ extractedFromNodeId: args.extractedFromNodeId,
2362
+ confidence: args.confidence,
2363
+ verificationStatus: args.verificationStatus ?? "unverified",
2364
+ status: "active",
2365
+ topicId: resolvedScope?.topicId ?? args.topicId,
2366
+ projectId: resolvedScope?.projectId ?? args.projectId,
2367
+ tenantId: resolvedScope?.tenantId,
2368
+ workspaceId: resolvedScope?.workspaceId,
2369
+ createdBy: args.createdBy,
2370
+ createdAt: now,
2371
+ updatedAt: now
2372
+ });
2373
+ await ctx.db.insert("epistemicAudit", {
2374
+ entityType: "node",
2375
+ entityId: String(nodeId),
2376
+ changeType: "created",
2377
+ changedAt: now,
2378
+ changedBy: args.createdBy,
2379
+ isAgent: false,
2380
+ newState: {
2381
+ nodeType: args.nodeType,
2382
+ canonicalText: args.canonicalText.slice(0, 200),
2383
+ status: "active",
2384
+ verificationStatus: args.verificationStatus ?? "unverified",
2385
+ confidence: args.confidence
2386
+ },
2387
+ projectId: resolvedScope?.projectId ?? args.projectId,
2388
+ triggeringAction: "epistemicNodes.create"
2389
+ });
2390
+ await ctx.scheduler.runAfter(0, internal.neo4jSync.syncNodeToNeo4j, {
2391
+ nodeId,
2392
+ operation: "upsert"
2393
+ });
2394
+ return { nodeId, isDuplicate: false };
2395
+ }
2396
+ });
2397
+ var update = mutation({
2398
+ args: {
2399
+ nodeId: v.id("epistemicNodes"),
2400
+ subtype: v.optional(v.string()),
2401
+ canonicalText: v.optional(v.string()),
2402
+ contentHash: v.optional(v.string()),
2403
+ content: v.optional(v.string()),
2404
+ contentType: v.optional(v.string()),
2405
+ title: v.optional(v.string()),
2406
+ tags: v.optional(v.array(v.string())),
2407
+ domain: v.optional(v.string()),
2408
+ metadata: v.optional(v.any()),
2409
+ externalIds: v.optional(
2410
+ v.object({
2411
+ crunchbase: v.optional(v.string()),
2412
+ linkedin: v.optional(v.string()),
2413
+ pitchbook: v.optional(v.string()),
2414
+ twitter: v.optional(v.string()),
2415
+ website: v.optional(v.string())
2416
+ })
2417
+ ),
2418
+ confidence: v.optional(v.number()),
2419
+ verificationStatus: v.optional(verificationStatusValidator),
2420
+ status: v.optional(statusValidator),
2421
+ userId: v.optional(v.string()),
2422
+ // EK-4: SL opinion fields (Kernel v2)
2423
+ opinion_b: v.optional(v.number()),
2424
+ opinion_d: v.optional(v.number()),
2425
+ opinion_u: v.optional(v.number()),
2426
+ opinion_a: v.optional(v.number())
2427
+ },
2428
+ returns: permissiveReturn,
2429
+ handler: async (ctx, args) => {
2430
+ const { nodeId, userId, ...updates } = args;
2431
+ const node = await ctx.db.get(nodeId);
2432
+ if (!node) {
2433
+ throw new Error("Node not found");
2434
+ }
2435
+ if (node.projectId && userId) {
2436
+ await requireProjectAccess(ctx, node.projectId, userId);
2437
+ }
2438
+ const cleanUpdates = {};
2439
+ for (const [key, value] of Object.entries(updates)) {
2440
+ if (value !== void 0) {
2441
+ cleanUpdates[key] = value;
2442
+ }
2443
+ }
2444
+ if (node.nodeType === "belief") {
2445
+ assertBeliefNodeGenericUpdateAllowed({
2446
+ node,
2447
+ updates: cleanUpdates,
2448
+ mutationName: "epistemicNodes.update"
2449
+ });
2450
+ }
2451
+ assertOntologicalNodeGenericUpdateAllowed({
2452
+ node,
2453
+ mutationName: "epistemicNodes.update"
2454
+ });
2455
+ cleanUpdates.updatedAt = Date.now();
2456
+ await ctx.db.patch(nodeId, cleanUpdates);
2457
+ if (node.projectId) {
2458
+ await ctx.db.insert("epistemicAudit", {
2459
+ entityType: "node",
2460
+ entityId: String(nodeId),
2461
+ changeType: "updated",
2462
+ changedAt: Date.now(),
2463
+ changedBy: userId || node.createdBy,
2464
+ isAgent: false,
2465
+ previousState: {
2466
+ status: node.status,
2467
+ confidence: node.confidence,
2468
+ canonicalText: node.canonicalText?.slice(0, 200)
2469
+ },
2470
+ newState: cleanUpdates,
2471
+ projectId: node.projectId
2472
+ });
2473
+ }
2474
+ await ctx.scheduler.runAfter(0, internal.neo4jSync.syncNodeToNeo4j, {
2475
+ nodeId,
2476
+ operation: "upsert"
2477
+ });
2478
+ return { success: true };
2479
+ }
2480
+ });
2481
+ var supersede = mutation({
2482
+ args: {
2483
+ oldNodeId: v.id("epistemicNodes"),
2484
+ newGlobalId: v.string(),
2485
+ newCanonicalText: v.string(),
2486
+ newContentHash: v.string(),
2487
+ createdBy: v.string(),
2488
+ reason: v.optional(v.string())
2489
+ },
2490
+ returns: permissiveReturn,
2491
+ handler: async (ctx, args) => {
2492
+ const oldNode = await ctx.db.get(args.oldNodeId);
2493
+ if (!oldNode) {
2494
+ throw new Error("Node not found");
2495
+ }
2496
+ if (oldNode.projectId) {
2497
+ await requireProjectAccess(ctx, oldNode.projectId, args.createdBy);
2498
+ }
2499
+ assertBeliefNodeSupersedeAllowed({
2500
+ node: oldNode,
2501
+ mutationName: "epistemicNodes.supersede"
2502
+ });
2503
+ assertOntologicalNodeSupersedeAllowed({
2504
+ node: oldNode,
2505
+ mutationName: "epistemicNodes.supersede"
2506
+ });
2507
+ const now = Date.now();
2508
+ const epistemicLayer2 = oldNode.epistemicLayer || getNodeLayer(oldNode.nodeType);
2509
+ const newNodeId = await ctx.db.insert("epistemicNodes", {
2510
+ globalId: args.newGlobalId,
2511
+ nodeType: oldNode.nodeType,
2512
+ epistemicLayer: epistemicLayer2,
2513
+ // Phase 2B: Inherit layer (supersession is same-layer only)
2514
+ canonicalText: args.newCanonicalText,
2515
+ contentHash: args.newContentHash,
2516
+ content: oldNode.content,
2517
+ contentType: oldNode.contentType,
2518
+ title: oldNode.title,
2519
+ tags: oldNode.tags,
2520
+ metadata: oldNode.metadata,
2521
+ sourceType: "human",
2522
+ // Supersession is always human-initiated
2523
+ confidence: oldNode.confidence,
2524
+ verificationStatus: "unverified",
2525
+ // New version needs re-verification
2526
+ status: "active",
2527
+ projectId: oldNode.projectId,
2528
+ createdBy: args.createdBy,
2529
+ createdAt: now,
2530
+ updatedAt: now
2531
+ });
2532
+ await ctx.db.patch(args.oldNodeId, {
2533
+ status: "superseded",
2534
+ supersededBy: newNodeId,
2535
+ updatedAt: now
2536
+ });
2537
+ if (oldNode.projectId) {
2538
+ await ctx.db.insert("epistemicAudit", {
2539
+ entityType: "node",
2540
+ entityId: String(args.oldNodeId),
2541
+ changeType: "superseded",
2542
+ changedAt: now,
2543
+ changedBy: args.createdBy,
2544
+ isAgent: false,
2545
+ previousState: {
2546
+ status: oldNode.status,
2547
+ canonicalText: oldNode.canonicalText?.slice(0, 200)
2548
+ },
2549
+ newState: {
2550
+ status: "superseded",
2551
+ supersededBy: String(newNodeId),
2552
+ newCanonicalText: args.newCanonicalText.slice(0, 200)
2553
+ },
2554
+ rationale: args.reason,
2555
+ projectId: oldNode.projectId
2556
+ });
2557
+ }
2558
+ return { newNodeId, oldNodeId: args.oldNodeId };
2559
+ }
2560
+ });
2561
+ var archive = mutation({
2562
+ args: {
2563
+ nodeId: v.id("epistemicNodes"),
2564
+ userId: v.optional(v.string())
2565
+ },
2566
+ returns: permissiveReturn,
2567
+ handler: async (ctx, args) => {
2568
+ const node = await ctx.db.get(args.nodeId);
2569
+ if (!node) {
2570
+ throw new Error("Node not found");
2571
+ }
2572
+ if (node.projectId && args.userId) {
2573
+ await requireProjectAccess(ctx, node.projectId, args.userId);
2574
+ }
2575
+ assertBeliefNodeArchiveAllowed({
2576
+ node,
2577
+ mutationName: "epistemicNodes.archive"
2578
+ });
2579
+ assertOntologicalNodeArchiveAllowed({
2580
+ node,
2581
+ mutationName: "epistemicNodes.archive"
2582
+ });
2583
+ await ctx.db.patch(args.nodeId, {
2584
+ status: "archived",
2585
+ updatedAt: Date.now()
2586
+ });
2587
+ if (node.projectId) {
2588
+ await ctx.db.insert("epistemicAudit", {
2589
+ entityType: "node",
2590
+ entityId: String(args.nodeId),
2591
+ changeType: "archived",
2592
+ changedAt: Date.now(),
2593
+ changedBy: args.userId || node.createdBy,
2594
+ isAgent: false,
2595
+ previousState: { status: node.status },
2596
+ newState: { status: "archived" },
2597
+ projectId: node.projectId
2598
+ });
2599
+ }
2600
+ return { success: true, effectiveStatus: "archived" };
2601
+ }
2602
+ });
2603
+ var hardDelete = internalMutation({
2604
+ args: {
2605
+ nodeId: v.id("epistemicNodes"),
2606
+ reason: v.string(),
2607
+ // Required: document why this is being hard-deleted
2608
+ allowBeliefHardDelete: v.optional(v.boolean())
2609
+ },
2610
+ returns: permissiveReturn,
2611
+ handler: async (ctx, args) => {
2612
+ const node = await ctx.db.get(args.nodeId);
2613
+ if (!node) {
2614
+ return { success: false, error: "Node not found" };
2615
+ }
2616
+ assertBeliefNodeHardDeleteAllowed({
2617
+ node,
2618
+ allowBeliefHardDelete: args.allowBeliefHardDelete ?? false,
2619
+ reason: args.reason,
2620
+ mutationName: "epistemicNodes.hardDelete"
2621
+ });
2622
+ await ctx.db.insert("epistemicAudit", {
2623
+ entityType: "node",
2624
+ entityId: args.nodeId,
2625
+ changeType: "deleted",
2626
+ projectId: node.projectId,
2627
+ changedBy: "system:hard_delete",
2628
+ changedAt: Date.now(),
2629
+ isAgent: false,
2630
+ previousState: {
2631
+ nodeType: node.nodeType,
2632
+ title: node.title || node.canonicalText?.slice(0, 100),
2633
+ status: node.status
2634
+ },
2635
+ rationale: args.reason
2636
+ });
2637
+ const fromEdges = await ctx.db.query("epistemicEdges").withIndex("by_from", (q) => q.eq("fromNodeId", args.nodeId)).collect();
2638
+ const toEdges = await ctx.db.query("epistemicEdges").withIndex("by_to", (q) => q.eq("toNodeId", args.nodeId)).collect();
2639
+ const uniqueEdges = /* @__PURE__ */ new Map();
2640
+ for (const edge of [...fromEdges, ...toEdges]) {
2641
+ uniqueEdges.set(edge.globalId, edge);
2642
+ }
2643
+ for (const edge of uniqueEdges.values()) {
2644
+ await ctx.scheduler.runAfter(0, internal.neo4jEdgeAPI.deleteEdge, {
2645
+ globalId: edge.globalId
2646
+ });
2647
+ await ctx.db.delete(edge._id);
2648
+ }
2649
+ await ctx.db.delete(args.nodeId);
2650
+ return {
2651
+ success: true,
2652
+ deletedEdgeCount: uniqueEdges.size
2653
+ };
2654
+ }
2655
+ });
2656
+ var verify = mutation({
2657
+ args: {
2658
+ nodeId: v.id("epistemicNodes"),
2659
+ verificationStatus: verificationStatusValidator,
2660
+ confidence: v.optional(v.number()),
2661
+ userId: v.optional(v.string())
2662
+ },
2663
+ returns: permissiveReturn,
2664
+ handler: async (ctx, args) => {
2665
+ const node = await ctx.db.get(args.nodeId);
2666
+ if (!node) {
2667
+ throw new Error("Node not found");
2668
+ }
2669
+ assertBeliefNodeVerifyAllowed({
2670
+ node,
2671
+ confidence: args.confidence,
2672
+ mutationName: "epistemicNodes.verify"
2673
+ });
2674
+ const updates = {
2675
+ verificationStatus: args.verificationStatus,
2676
+ updatedAt: Date.now()
2677
+ };
2678
+ if (args.confidence !== void 0) {
2679
+ updates.confidence = args.confidence;
2680
+ }
2681
+ await ctx.db.patch(args.nodeId, updates);
2682
+ await ctx.db.insert("epistemicAudit", {
2683
+ entityType: "node",
2684
+ entityId: String(args.nodeId),
2685
+ changeType: "updated",
2686
+ changedAt: Date.now(),
2687
+ changedBy: args.userId ?? node.createdBy ?? "system:verification",
2688
+ isAgent: false,
2689
+ previousState: {
2690
+ verificationStatus: node.verificationStatus,
2691
+ confidence: node.confidence
2692
+ },
2693
+ newState: {
2694
+ verificationStatus: args.verificationStatus,
2695
+ confidence: args.confidence ?? node.confidence
2696
+ },
2697
+ projectId: node.projectId,
2698
+ triggeringAction: "epistemicNodes.verify"
2699
+ });
2700
+ return { success: true };
2701
+ }
2702
+ });
2703
+ var batchCreate = mutation({
2704
+ args: {
2705
+ nodes: v.array(
2706
+ v.object({
2707
+ ...optionalNodeScopeArgs,
2708
+ globalId: v.string(),
2709
+ nodeType: nodeTypeValidator,
2710
+ subtype: v.optional(v.string()),
2711
+ canonicalText: v.string(),
2712
+ contentHash: v.string(),
2713
+ content: v.optional(v.string()),
2714
+ contentType: v.optional(v.string()),
2715
+ title: v.optional(v.string()),
2716
+ tags: v.optional(v.array(v.string())),
2717
+ domain: v.optional(v.string()),
2718
+ metadata: v.optional(v.any()),
2719
+ externalIds: v.optional(
2720
+ v.object({
2721
+ crunchbase: v.optional(v.string()),
2722
+ linkedin: v.optional(v.string()),
2723
+ pitchbook: v.optional(v.string()),
2724
+ twitter: v.optional(v.string()),
2725
+ website: v.optional(v.string())
2726
+ })
2727
+ ),
2728
+ sourceType: sourceTypeValidator,
2729
+ aiProvider: v.optional(v.string()),
2730
+ confidence: v.optional(v.number()),
2731
+ verificationStatus: v.optional(verificationStatusValidator),
2732
+ createdBy: v.string()
2733
+ })
2734
+ )
2735
+ },
2736
+ returns: permissiveReturn,
2737
+ handler: async (ctx, args) => {
2738
+ const now = Date.now();
2739
+ const results = [];
2740
+ for (const node of args.nodes) {
2741
+ assertOntologicalNodeGenericCreateAllowed({
2742
+ nodeType: node.nodeType,
2743
+ mutationName: "epistemicNodes.batchCreate"
2744
+ });
2745
+ const epistemicLayer2 = getNodeLayer(node.nodeType);
2746
+ const resolvedScope = node.topicId || node.projectId ? await resolveTopicProjectScope(ctx, {
2747
+ topicId: node.topicId,
2748
+ projectId: node.projectId
2749
+ }).catch(() => void 0) : void 0;
2750
+ const nodeId = await ctx.db.insert("epistemicNodes", {
2751
+ ...node,
2752
+ epistemicLayer: epistemicLayer2,
2753
+ // Phase 2B: Auto-derived from nodeType
2754
+ verificationStatus: node.verificationStatus ?? "unverified",
2755
+ status: "active",
2756
+ topicId: resolvedScope?.topicId ?? node.topicId,
2757
+ projectId: resolvedScope?.projectId ?? node.projectId,
2758
+ createdAt: now,
2759
+ updatedAt: now
2760
+ });
2761
+ results.push({ globalId: node.globalId, nodeId });
2762
+ await ctx.db.insert("epistemicAudit", {
2763
+ entityType: "node",
2764
+ entityId: String(nodeId),
2765
+ changeType: "created",
2766
+ changedAt: now,
2767
+ changedBy: node.createdBy,
2768
+ isAgent: false,
2769
+ newState: {
2770
+ nodeType: node.nodeType,
2771
+ canonicalText: node.canonicalText.slice(0, 200),
2772
+ status: "active",
2773
+ verificationStatus: node.verificationStatus ?? "unverified",
2774
+ confidence: node.confidence
2775
+ },
2776
+ projectId: resolvedScope?.projectId ?? node.projectId,
2777
+ triggeringAction: "epistemicNodes.batchCreate"
2778
+ });
2779
+ await ctx.scheduler.runAfter(0, internal.neo4jSync.syncNodeToNeo4j, {
2780
+ nodeId,
2781
+ operation: "upsert"
2782
+ });
2783
+ }
2784
+ return { created: results.length, results };
2785
+ }
2786
+ });
2787
+ var createInternal = internalMutation({
2788
+ args: {
2789
+ ...optionalNodeScopeArgs,
2790
+ globalId: v.string(),
2791
+ nodeType: nodeTypeValidator,
2792
+ subtype: v.optional(v.string()),
2793
+ canonicalText: v.string(),
2794
+ contentHash: v.optional(v.string()),
2795
+ // Optional - will be generated if not provided
2796
+ content: v.optional(v.string()),
2797
+ contentType: v.optional(v.string()),
2798
+ title: v.optional(v.string()),
2799
+ tags: v.optional(v.array(v.string())),
2800
+ domain: v.optional(v.string()),
2801
+ metadata: v.optional(v.any()),
2802
+ externalIds: v.optional(
2803
+ v.object({
2804
+ crunchbase: v.optional(v.string()),
2805
+ linkedin: v.optional(v.string()),
2806
+ pitchbook: v.optional(v.string()),
2807
+ twitter: v.optional(v.string()),
2808
+ website: v.optional(v.string())
2809
+ })
2810
+ ),
2811
+ sourceType: v.optional(sourceTypeValidator),
2812
+ aiProvider: v.optional(v.string()),
2813
+ extractedFromNodeId: v.optional(v.id("epistemicNodes")),
2814
+ confidence: v.optional(v.number()),
2815
+ verificationStatus: v.optional(verificationStatusValidator),
2816
+ createdBy: v.string(),
2817
+ // Neo4j sync status tracking
2818
+ syncStatus: v.optional(
2819
+ v.union(
2820
+ v.literal("synced"),
2821
+ v.literal("pending_edges"),
2822
+ v.literal("edge_creation_failed")
2823
+ )
2824
+ ),
2825
+ epistemicLayer: v.optional(v.string()),
2826
+ status: v.optional(v.string())
2827
+ },
2828
+ returns: permissiveReturn,
2829
+ handler: async (ctx, args) => {
2830
+ const now = Date.now();
2831
+ const resolvedScope = args.topicId || args.projectId ? await resolveTopicProjectScope(ctx, {
2832
+ topicId: args.topicId,
2833
+ projectId: args.projectId
2834
+ }).catch(() => void 0) : void 0;
2835
+ const contentHash = args.contentHash || `${args.nodeType}:${args.canonicalText}`.slice(0, 64);
2836
+ const epistemicLayer2 = args.epistemicLayer || getNodeLayer(args.nodeType);
2837
+ const nodeId = await ctx.db.insert("epistemicNodes", {
2838
+ globalId: args.globalId,
2839
+ nodeType: args.nodeType,
2840
+ epistemicLayer: epistemicLayer2,
2841
+ subtype: args.subtype,
2842
+ canonicalText: args.canonicalText,
2843
+ contentHash,
2844
+ content: args.content,
2845
+ contentType: args.contentType,
2846
+ title: args.title,
2847
+ tags: args.tags,
2848
+ domain: args.domain,
2849
+ metadata: args.metadata,
2850
+ externalIds: args.externalIds,
2851
+ sourceType: args.sourceType ?? "human",
2852
+ aiProvider: args.aiProvider,
2853
+ extractedFromNodeId: args.extractedFromNodeId,
2854
+ confidence: args.confidence,
2855
+ verificationStatus: args.verificationStatus ?? "unverified",
2856
+ status: args.status ?? "active",
2857
+ topicId: resolvedScope?.topicId ?? args.topicId,
2858
+ projectId: resolvedScope?.projectId ?? args.projectId,
2859
+ createdBy: args.createdBy,
2860
+ createdAt: now,
2861
+ updatedAt: now,
2862
+ // Neo4j sync status
2863
+ syncStatus: args.syncStatus
2864
+ });
2865
+ await ctx.scheduler.runAfter(0, internal.neo4jSync.syncNodeToNeo4j, {
2866
+ nodeId,
2867
+ operation: "upsert"
2868
+ });
2869
+ return nodeId;
2870
+ }
2871
+ });
2872
+ var getInternal = internalQuery({
2873
+ args: {
2874
+ nodeId: v.id("epistemicNodes")
2875
+ },
2876
+ returns: permissiveReturn,
2877
+ handler: async (ctx, args) => {
2878
+ return await ctx.db.get(args.nodeId);
2879
+ }
2880
+ });
2881
+ var updateSyncStatus = internalMutation({
2882
+ args: {
2883
+ nodeId: v.id("epistemicNodes"),
2884
+ syncStatus: v.union(
2885
+ v.literal("synced"),
2886
+ v.literal("pending_edges"),
2887
+ v.literal("edge_creation_failed")
2888
+ ),
2889
+ syncError: v.optional(v.string())
2890
+ },
2891
+ returns: permissiveReturn,
2892
+ handler: async (ctx, args) => {
2893
+ const updates = {
2894
+ syncStatus: args.syncStatus,
2895
+ updatedAt: Date.now()
2896
+ };
2897
+ if (args.syncError !== void 0) {
2898
+ updates.syncError = args.syncError;
2899
+ }
2900
+ await ctx.db.patch(args.nodeId, updates);
2901
+ return { success: true };
2902
+ }
2903
+ });
2904
+ var backfillTopicId = internalMutation({
2905
+ args: {
2906
+ nodeIds: v.array(v.id("epistemicNodes")),
2907
+ newTopicId: v.string()
2908
+ },
2909
+ returns: permissiveReturn,
2910
+ handler: async (ctx, args) => {
2911
+ let patched = 0;
2912
+ for (const nodeId of args.nodeIds) {
2913
+ const node = await ctx.db.get(nodeId);
2914
+ if (!node) {
2915
+ continue;
2916
+ }
2917
+ await ctx.db.patch(nodeId, {
2918
+ topicId: args.newTopicId,
2919
+ updatedAt: Date.now()
2920
+ });
2921
+ patched++;
2922
+ }
2923
+ return { patched, total: args.nodeIds.length };
2924
+ }
2925
+ });
2926
+ var getNodesPendingEdgeSync = internalQuery({
2927
+ args: {
2928
+ limit: v.optional(v.number())
2929
+ },
2930
+ returns: permissiveReturn,
2931
+ handler: async (ctx, args) => {
2932
+ const limit = args.limit ?? 100;
2933
+ return await ctx.db.query("epistemicNodes").withIndex(
2934
+ "by_syncStatus",
2935
+ (q) => q.eq("syncStatus", "edge_creation_failed")
2936
+ ).take(limit);
2937
+ }
2938
+ });
2939
+
2940
+ export { archive, backfillTopicId, batchCreate, create, createInternal, epistemicLayerValidator, epistemicNodeTypeValidator, get, getByContentHash, getByGlobalId, getByProject, getByProjectAndType, getByProjectAndTypeLite, getByTopic, getInternal, getNodesPendingEdgeSync, hardDelete, l1NodeTypeValidator, l2NodeTypeValidator, l3NodeTypeValidator, l4NodeTypeValidator, listAll, nodeTypeValidator, ontologicalNodeTypeValidator, organizationalNodeTypeValidator, search, sourceTypeValidator, statusValidator, supersede, update, updateSyncStatus, verificationStatusValidator, verify };
2941
+ //# sourceMappingURL=epistemicNodes.js.map
2942
+ //# sourceMappingURL=epistemicNodes.js.map