@lucern/graph-primitives 0.3.0-alpha.0 → 0.3.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{beliefDecay-Q_26RTc-.d.ts → beliefDecay-DZ6tkLYq.d.ts} +1 -1
- package/dist/beliefDecay.d.ts +1 -1
- package/dist/beliefDecay.js +188 -1144
- package/dist/beliefDecay.js.map +1 -1
- package/dist/{beliefEvidenceLinks-42FlR48t.d.ts → beliefEvidenceLinks-CWOXxxJg.d.ts} +1 -1
- package/dist/beliefEvidenceLinks.d.ts +1 -1
- package/dist/beliefEvidenceLinks.js +186 -871
- package/dist/beliefEvidenceLinks.js.map +1 -1
- package/dist/{beliefLifecycle-C-AehZgF.d.ts → beliefLifecycle-y8WLXqQj.d.ts} +1 -1
- package/dist/beliefLifecycle.d.ts +1 -1
- package/dist/confidencePropagationDispatch.d.ts +4 -4
- package/dist/confidencePropagationDispatch.js +31 -311
- package/dist/confidencePropagationDispatch.js.map +1 -1
- package/dist/{contradictions-Hdwl7zid.d.ts → contradictions-51VLsESq.d.ts} +1 -1
- package/dist/contradictions.d.ts +1 -1
- package/dist/contradictions.js +67 -800
- package/dist/contradictions.js.map +1 -1
- package/dist/debug.d.ts +4 -0
- package/dist/debug.js +34 -0
- package/dist/debug.js.map +1 -0
- package/dist/edges/contradicts.js +1 -122
- package/dist/edges/contradicts.js.map +1 -1
- package/dist/edges/dependsOn.js +14 -172
- package/dist/edges/dependsOn.js.map +1 -1
- package/dist/edges/elaborates.js +1 -49
- package/dist/edges/elaborates.js.map +1 -1
- package/dist/edges/index.js +15 -280
- package/dist/edges/index.js.map +1 -1
- package/dist/edges/informs.js +2 -65
- package/dist/edges/informs.js.map +1 -1
- package/dist/edges/propagationTypes.d.ts +2 -2
- package/dist/edges/propagationTypes.js.map +1 -1
- package/dist/edges/refutes.js +2 -65
- package/dist/edges/refutes.js.map +1 -1
- package/dist/edges/supports.js +1 -122
- package/dist/edges/supports.js.map +1 -1
- package/dist/edges/utils.d.ts +7 -7
- package/dist/edges/utils.js +2 -133
- package/dist/edges/utils.js.map +1 -1
- package/dist/embeddingTrigger.js +21 -1
- package/dist/embeddingTrigger.js.map +1 -1
- package/dist/entityBridge.js +3 -18
- package/dist/entityBridge.js.map +1 -1
- package/dist/{entityLifecycle-BkhRJ-XI.d.ts → entityLifecycle-CvgSK5FV.d.ts} +1 -1
- package/dist/entityLifecycle.d.ts +1 -1
- package/dist/entityLifecycle.js +193 -892
- package/dist/entityLifecycle.js.map +1 -1
- package/dist/{epistemicAnswers-DSP1slZ9.d.ts → epistemicAnswers-C5ib4z6_.d.ts} +1 -1
- package/dist/epistemicAnswers.d.ts +1 -1
- package/dist/epistemicAnswers.js +73 -810
- package/dist/epistemicAnswers.js.map +1 -1
- package/dist/{epistemicBeliefs-DtFVTp-k.d.ts → epistemicBeliefs-DzKjZAeC.d.ts} +3 -3
- package/dist/epistemicBeliefs.d.ts +2 -2
- package/dist/epistemicBeliefs.js +404 -1698
- package/dist/epistemicBeliefs.js.map +1 -1
- package/dist/epistemicContractHelpers.js +1 -318
- package/dist/epistemicContractHelpers.js.map +1 -1
- package/dist/epistemicContracts.d.ts +1 -1
- package/dist/epistemicContracts.js +417 -1980
- package/dist/epistemicContracts.js.map +1 -1
- package/dist/{epistemicEdges-DcA8ErUG.d.ts → epistemicEdges-CD5vxmlH.d.ts} +3 -3
- package/dist/epistemicEdges.d.ts +1 -1
- package/dist/epistemicEdges.js +248 -919
- package/dist/epistemicEdges.js.map +1 -1
- package/dist/{epistemicEvidence-Bo638XDP.d.ts → epistemicEvidence-xw6UUrwh.d.ts} +1 -1
- package/dist/epistemicEvidence.d.ts +1 -1
- package/dist/epistemicEvidence.js +229 -1087
- package/dist/epistemicEvidence.js.map +1 -1
- package/dist/{epistemicHelpers-Bd9xbaib.d.ts → epistemicHelpers-DevrYgPN.d.ts} +1 -1
- package/dist/epistemicHelpers.d.ts +1 -1
- package/dist/{epistemicLinking-CyeLOIzN.d.ts → epistemicLinking-CfE00tHJ.d.ts} +1 -1
- package/dist/epistemicLinking.d.ts +1 -1
- package/dist/epistemicLinking.js +3 -786
- package/dist/epistemicLinking.js.map +1 -1
- package/dist/{epistemicNodes-BpD6Koud.d.ts → epistemicNodes-NBrPW7fk.d.ts} +2 -2
- package/dist/epistemicNodes.d.ts +1 -1
- package/dist/epistemicNodes.js +172 -899
- package/dist/epistemicNodes.js.map +1 -1
- package/dist/{epistemicQuestions-CmEeY6zQ.d.ts → epistemicQuestions-B_nUclrH.d.ts} +1 -1
- package/dist/epistemicQuestions.d.ts +1 -1
- package/dist/epistemicQuestions.js +369 -1125
- package/dist/epistemicQuestions.js.map +1 -1
- package/dist/{epistemicSources-ZazxHOK1.d.ts → epistemicSources-dlKj58Jp.d.ts} +1 -1
- package/dist/epistemicSources.d.ts +1 -1
- package/dist/epistemicSources.js +86 -886
- package/dist/epistemicSources.js.map +1 -1
- package/dist/evaluators/index.js +417 -1980
- package/dist/evaluators/index.js.map +1 -1
- package/dist/evaluators/lintCheckerEvaluator.js.map +1 -1
- package/dist/evaluators/sentryCheckerEvaluator.js.map +1 -1
- package/dist/evaluators/shared.js +20 -1
- package/dist/evaluators/shared.js.map +1 -1
- package/dist/evaluators/testRunnerEvaluator.js +20 -1
- package/dist/evaluators/testRunnerEvaluator.js.map +1 -1
- package/dist/evaluators/tscCheckerEvaluator.js.map +1 -1
- package/dist/index.d.ts +20 -20
- package/dist/index.js +965 -3004
- package/dist/index.js.map +1 -1
- package/dist/{ontology-matching-Buhu23ss.d.ts → ontology-matching-C6rrz2VP.d.ts} +1 -1
- package/dist/ontology-matching.d.ts +1 -1
- package/dist/ontology-matching.js +1 -344
- package/dist/ontology-matching.js.map +1 -1
- package/dist/{ontologyApproval-Ba0Jjk1k.d.ts → ontologyApproval-CFYmqKmk.d.ts} +1 -1
- package/dist/ontologyApproval.d.ts +1 -1
- package/dist/ontologyApproval.js +1 -13
- package/dist/ontologyApproval.js.map +1 -1
- package/dist/ontologyDefinitions.js +6 -20
- package/dist/ontologyDefinitions.js.map +1 -1
- package/dist/ontologyHelpers.d.ts +1 -1
- package/dist/ontologyHelpers.js +4 -3
- package/dist/ontologyHelpers.js.map +1 -1
- package/dist/ontologyRegistry.js +2 -17
- package/dist/ontologyRegistry.js.map +1 -1
- package/dist/{projectionReconciliation-CxrXYGaB.d.ts → projectionReconciliation-jww2fBI0.d.ts} +1 -1
- package/dist/projectionReconciliation.d.ts +1 -1
- package/dist/projectionReconciliation.js +16 -37
- package/dist/projectionReconciliation.js.map +1 -1
- package/dist/{projectionStaleness-CAdpIsaW.d.ts → projectionStaleness-CmdbpjVK.d.ts} +1 -1
- package/dist/projectionStaleness.d.ts +1 -1
- package/dist/{questionEvidenceLinks-BdQD0TkM.d.ts → questionEvidenceLinks-DFlyPpAj.d.ts} +1 -1
- package/dist/questionEvidenceLinks.d.ts +1 -1
- package/dist/questionEvidenceLinks.js +199 -881
- package/dist/questionEvidenceLinks.js.map +1 -1
- package/dist/resolvers.js +86 -37
- package/dist/resolvers.js.map +1 -1
- package/dist/scopeResolverCompat.js +64 -7
- package/dist/scopeResolverCompat.js.map +1 -1
- package/dist/{text-matching-CMn2WnVD.d.ts → text-matching-DNg4M5Wd.d.ts} +1 -1
- package/dist/text-matching.d.ts +1 -1
- package/dist/text-matching.js +1 -244
- package/dist/text-matching.js.map +1 -1
- package/dist/topicProjectOverlay.js +56 -13
- package/dist/topicProjectOverlay.js.map +1 -1
- package/dist/topicScope.js +55 -6
- package/dist/topicScope.js.map +1 -1
- package/dist/workflowBridge.d.ts +27 -0
- package/dist/workflowBridge.js +352 -0
- package/dist/workflowBridge.js.map +1 -0
- package/dist/workspaceIsolation.js +56 -57
- package/dist/workspaceIsolation.js.map +1 -1
- package/package.json +6 -5
package/dist/epistemicNodes.js
CHANGED
|
@@ -1,820 +1,30 @@
|
|
|
1
1
|
import { v } from 'convex/values';
|
|
2
|
-
import {
|
|
2
|
+
import { checkScopeAccess, requireProjectAccess } from '@lucern/access-control/access';
|
|
3
|
+
import { permissiveReturn } from '@lucern/contracts/schema-helpers/validators';
|
|
4
|
+
import { componentsGeneric, anyApi, queryGeneric, mutationGeneric, internalMutationGeneric, internalQueryGeneric } from 'convex/server';
|
|
5
|
+
import { isNodeType, getLayerForNodeType } from '@lucern/contracts/schema-helpers/spine/tables/epistemicNodes';
|
|
3
6
|
|
|
4
7
|
// src/epistemicNodes.ts
|
|
5
8
|
var api = anyApi;
|
|
6
9
|
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
10
|
var internal = anyApi;
|
|
813
11
|
var internalMutation = internalMutationGeneric;
|
|
814
12
|
var internalQuery = internalQueryGeneric;
|
|
815
13
|
var mutation = mutationGeneric;
|
|
816
14
|
var query = queryGeneric;
|
|
817
15
|
|
|
16
|
+
// src/debug.ts
|
|
17
|
+
function isGraphPrimitiveDebugEnabled() {
|
|
18
|
+
const env = globalThis.process?.env;
|
|
19
|
+
return env?.LUCERN_COMPAT_FALLBACK_DEBUG === "1" || env?.LUCERN_GRAPH_DEBUG === "1";
|
|
20
|
+
}
|
|
21
|
+
function debugGraphPrimitiveFallback(message, context) {
|
|
22
|
+
if (!isGraphPrimitiveDebugEnabled()) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
console.debug(message, context ?? {});
|
|
26
|
+
}
|
|
27
|
+
|
|
818
28
|
// src/graphTypes.ts
|
|
819
29
|
function getNodeLayer(nodeType) {
|
|
820
30
|
const L4_TYPES = ["decision"];
|
|
@@ -1078,17 +288,17 @@ function assertOntologicalNodeSupersedeAllowed(args) {
|
|
|
1078
288
|
}
|
|
1079
289
|
});
|
|
1080
290
|
}
|
|
1081
|
-
var
|
|
291
|
+
var LEGACY_SCOPE_FIELD = "graphScopeProjectId";
|
|
1082
292
|
function asMappedProjectId(topic) {
|
|
1083
293
|
if (!topic) {
|
|
1084
294
|
return;
|
|
1085
295
|
}
|
|
1086
|
-
const directLegacyProjectId = normalizeScopeValue(topic[
|
|
296
|
+
const directLegacyProjectId = normalizeScopeValue(topic[LEGACY_SCOPE_FIELD]);
|
|
1087
297
|
if (directLegacyProjectId) {
|
|
1088
298
|
return directLegacyProjectId;
|
|
1089
299
|
}
|
|
1090
300
|
const metadata = topic.metadata || {};
|
|
1091
|
-
const candidate = metadata[
|
|
301
|
+
const candidate = metadata[LEGACY_SCOPE_FIELD] || metadata.legacyProjectId || metadata.projectId || metadata.scopeProjectId;
|
|
1092
302
|
return candidate ? candidate : void 0;
|
|
1093
303
|
}
|
|
1094
304
|
function normalizeScopeValue(value) {
|
|
@@ -1117,9 +327,16 @@ async function findTopicsByScopeAlias(ctx, scopeId) {
|
|
|
1117
327
|
try {
|
|
1118
328
|
return await ctx.db.query("topics").withIndex(
|
|
1119
329
|
"by_graph_scope_project",
|
|
1120
|
-
(q) => q.eq(
|
|
330
|
+
(q) => q.eq(LEGACY_SCOPE_FIELD, scopeId)
|
|
1121
331
|
).collect();
|
|
1122
|
-
} catch {
|
|
332
|
+
} catch (error) {
|
|
333
|
+
debugGraphPrimitiveFallback(
|
|
334
|
+
"[topicScope] Failed to resolve scope alias via index",
|
|
335
|
+
{
|
|
336
|
+
error,
|
|
337
|
+
scopeId
|
|
338
|
+
}
|
|
339
|
+
);
|
|
1123
340
|
const topics = await ctx.db.query("topics").collect();
|
|
1124
341
|
return topics.filter((topic) => {
|
|
1125
342
|
const normalizedGlobalId = normalizeScopeValue(topic.globalId);
|
|
@@ -1133,10 +350,17 @@ async function tryResolveHostTopicById(ctx, topicId) {
|
|
|
1133
350
|
return null;
|
|
1134
351
|
}
|
|
1135
352
|
try {
|
|
1136
|
-
return await ctx.runQuery(
|
|
353
|
+
return await ctx.runQuery(api.topics.get, {
|
|
1137
354
|
id: topicId
|
|
1138
355
|
}) ?? null;
|
|
1139
|
-
} catch {
|
|
356
|
+
} catch (error) {
|
|
357
|
+
debugGraphPrimitiveFallback(
|
|
358
|
+
"[topicScope] Failed to resolve topic by host query",
|
|
359
|
+
{
|
|
360
|
+
error,
|
|
361
|
+
topicId
|
|
362
|
+
}
|
|
363
|
+
);
|
|
1140
364
|
return null;
|
|
1141
365
|
}
|
|
1142
366
|
}
|
|
@@ -1145,10 +369,17 @@ async function tryResolveHostTopicByLegacyScope(ctx, legacyScopeId) {
|
|
|
1145
369
|
return null;
|
|
1146
370
|
}
|
|
1147
371
|
try {
|
|
1148
|
-
return await ctx.runQuery(
|
|
372
|
+
return await ctx.runQuery(api.topics.getByLegacyScopeId, {
|
|
1149
373
|
projectId: legacyScopeId
|
|
1150
374
|
}) ?? null;
|
|
1151
|
-
} catch {
|
|
375
|
+
} catch (error) {
|
|
376
|
+
debugGraphPrimitiveFallback(
|
|
377
|
+
"[topicScope] Failed to resolve topic by legacy scope",
|
|
378
|
+
{
|
|
379
|
+
error,
|
|
380
|
+
legacyScopeId
|
|
381
|
+
}
|
|
382
|
+
);
|
|
1152
383
|
return null;
|
|
1153
384
|
}
|
|
1154
385
|
}
|
|
@@ -1177,8 +408,17 @@ async function resolveTopicProjectScope(ctx, args) {
|
|
|
1177
408
|
if (args.topicId) {
|
|
1178
409
|
let topic = null;
|
|
1179
410
|
try {
|
|
1180
|
-
topic = await ctx.db.get(
|
|
1181
|
-
|
|
411
|
+
topic = await ctx.db.get(
|
|
412
|
+
args.topicId
|
|
413
|
+
);
|
|
414
|
+
} catch (error) {
|
|
415
|
+
debugGraphPrimitiveFallback(
|
|
416
|
+
"[topicScope] Failed to load topic by direct id",
|
|
417
|
+
{
|
|
418
|
+
error,
|
|
419
|
+
topicId: args.topicId
|
|
420
|
+
}
|
|
421
|
+
);
|
|
1182
422
|
}
|
|
1183
423
|
if (!topic) {
|
|
1184
424
|
topic = await tryResolveHostTopicById(ctx, String(args.topicId));
|
|
@@ -1215,7 +455,14 @@ async function resolveTopicProjectScope(ctx, args) {
|
|
|
1215
455
|
directTopic = await ctx.db.get(
|
|
1216
456
|
args.projectId
|
|
1217
457
|
);
|
|
1218
|
-
} catch {
|
|
458
|
+
} catch (error) {
|
|
459
|
+
debugGraphPrimitiveFallback(
|
|
460
|
+
"[topicScope] Failed to load direct project topic",
|
|
461
|
+
{
|
|
462
|
+
error,
|
|
463
|
+
projectId: args.projectId
|
|
464
|
+
}
|
|
465
|
+
);
|
|
1219
466
|
}
|
|
1220
467
|
if (directTopic) {
|
|
1221
468
|
const inherited = await resolveInheritedWorkspaceScope(ctx, directTopic);
|
|
@@ -1264,61 +511,6 @@ var optionalScopeArgs = {
|
|
|
1264
511
|
projectId: v.optional(v.string()),
|
|
1265
512
|
topicId: v.optional(v.string())
|
|
1266
513
|
};
|
|
1267
|
-
|
|
1268
|
-
// ../../packages/contracts/src/schema-helpers/spine/tables/epistemicNodes.ts
|
|
1269
|
-
var NODE_TYPES = [
|
|
1270
|
-
"decision",
|
|
1271
|
-
"belief",
|
|
1272
|
-
"question",
|
|
1273
|
-
"theme",
|
|
1274
|
-
"deal",
|
|
1275
|
-
"topic",
|
|
1276
|
-
"claim",
|
|
1277
|
-
"evidence",
|
|
1278
|
-
"synthesis",
|
|
1279
|
-
"answer",
|
|
1280
|
-
"atomic_fact",
|
|
1281
|
-
"excerpt",
|
|
1282
|
-
"source",
|
|
1283
|
-
"company",
|
|
1284
|
-
"person",
|
|
1285
|
-
"investor",
|
|
1286
|
-
"function",
|
|
1287
|
-
"value_chain"
|
|
1288
|
-
];
|
|
1289
|
-
function isNodeType(value) {
|
|
1290
|
-
return NODE_TYPES.includes(value);
|
|
1291
|
-
}
|
|
1292
|
-
function getLayerForNodeType(type) {
|
|
1293
|
-
switch (type) {
|
|
1294
|
-
case "decision":
|
|
1295
|
-
return "L4";
|
|
1296
|
-
case "belief":
|
|
1297
|
-
case "question":
|
|
1298
|
-
case "theme":
|
|
1299
|
-
case "deal":
|
|
1300
|
-
return "L3";
|
|
1301
|
-
case "claim":
|
|
1302
|
-
case "evidence":
|
|
1303
|
-
case "synthesis":
|
|
1304
|
-
case "answer":
|
|
1305
|
-
return "L2";
|
|
1306
|
-
case "atomic_fact":
|
|
1307
|
-
case "excerpt":
|
|
1308
|
-
case "source":
|
|
1309
|
-
return "L1";
|
|
1310
|
-
case "topic":
|
|
1311
|
-
return "organizational";
|
|
1312
|
-
case "company":
|
|
1313
|
-
case "person":
|
|
1314
|
-
case "investor":
|
|
1315
|
-
case "function":
|
|
1316
|
-
case "value_chain":
|
|
1317
|
-
return "ontological";
|
|
1318
|
-
}
|
|
1319
|
-
}
|
|
1320
|
-
|
|
1321
|
-
// src/workspaceIsolation.ts
|
|
1322
514
|
function normalizeScopeValue2(value) {
|
|
1323
515
|
if (typeof value !== "string") {
|
|
1324
516
|
return;
|
|
@@ -1529,7 +721,15 @@ var getByProjectAndType = query({
|
|
|
1529
721
|
projectId: args.projectId,
|
|
1530
722
|
topicId: args.topicId
|
|
1531
723
|
});
|
|
1532
|
-
} catch {
|
|
724
|
+
} catch (error) {
|
|
725
|
+
debugGraphPrimitiveFallback(
|
|
726
|
+
"[epistemicNodes] Failed to resolve getByProjectAndType scope",
|
|
727
|
+
{
|
|
728
|
+
error,
|
|
729
|
+
projectId: args.projectId,
|
|
730
|
+
topicId: args.topicId
|
|
731
|
+
}
|
|
732
|
+
);
|
|
1533
733
|
return [];
|
|
1534
734
|
}
|
|
1535
735
|
const pageSize = clampNodeLimit(args.limit);
|
|
@@ -1566,7 +766,15 @@ var getByProjectAndTypeLite = query({
|
|
|
1566
766
|
projectId: args.projectId,
|
|
1567
767
|
topicId: args.topicId
|
|
1568
768
|
});
|
|
1569
|
-
} catch {
|
|
769
|
+
} catch (error) {
|
|
770
|
+
debugGraphPrimitiveFallback(
|
|
771
|
+
"[epistemicNodes] Failed to resolve getByNodeType scope",
|
|
772
|
+
{
|
|
773
|
+
error,
|
|
774
|
+
projectId: args.projectId,
|
|
775
|
+
topicId: args.topicId
|
|
776
|
+
}
|
|
777
|
+
);
|
|
1570
778
|
return [];
|
|
1571
779
|
}
|
|
1572
780
|
const pageSize = clampNodeLimit(args.limit);
|
|
@@ -1598,6 +806,27 @@ function clampNodeLimit(limit, fallback = DEFAULT_NODE_PAGE_SIZE) {
|
|
|
1598
806
|
}
|
|
1599
807
|
return Math.max(1, Math.min(Math.floor(limit), MAX_NODE_PAGE_SIZE));
|
|
1600
808
|
}
|
|
809
|
+
function buildNodeStatusSuccessResult() {
|
|
810
|
+
return { success: true };
|
|
811
|
+
}
|
|
812
|
+
function buildNodeArchivedResult() {
|
|
813
|
+
return {
|
|
814
|
+
success: true,
|
|
815
|
+
effectiveStatus: "archived"
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
function buildNodeNotFoundResult() {
|
|
819
|
+
const result = {};
|
|
820
|
+
result.success = false;
|
|
821
|
+
result.error = "Node not found";
|
|
822
|
+
return result;
|
|
823
|
+
}
|
|
824
|
+
function buildNodeDeletedResult(deletedEdgeCount) {
|
|
825
|
+
return {
|
|
826
|
+
success: true,
|
|
827
|
+
deletedEdgeCount
|
|
828
|
+
};
|
|
829
|
+
}
|
|
1601
830
|
function dedupeWorkspaceNodes(nodes) {
|
|
1602
831
|
const seen = /* @__PURE__ */ new Set();
|
|
1603
832
|
const deduped = [];
|
|
@@ -1629,14 +858,13 @@ async function collectScopedNodes(ctx, scope, args) {
|
|
|
1629
858
|
args.nodeType ? ctx.db.query("epistemicNodes").withIndex(
|
|
1630
859
|
"by_topic_type",
|
|
1631
860
|
(q) => q.eq("topicId", scope.projectId).eq("nodeType", args.nodeType)
|
|
1632
|
-
).order("desc").take(args.scanLimit) : ctx.db.query("epistemicNodes").withIndex(
|
|
1633
|
-
"by_topic",
|
|
1634
|
-
(q) => q.eq("topicId", scope.projectId)
|
|
1635
|
-
).order("desc").take(args.scanLimit)
|
|
861
|
+
).order("desc").take(args.scanLimit) : ctx.db.query("epistemicNodes").withIndex("by_topic", (q) => q.eq("topicId", scope.projectId)).order("desc").take(args.scanLimit)
|
|
1636
862
|
);
|
|
1637
863
|
}
|
|
1638
864
|
const combined = dedupeWorkspaceNodes((await Promise.all(queries)).flat());
|
|
1639
|
-
return combined.filter(
|
|
865
|
+
return combined.filter(
|
|
866
|
+
(node) => nodeMatchesWorkspaceReasoningScope(node, scope)
|
|
867
|
+
);
|
|
1640
868
|
}
|
|
1641
869
|
var getByProject = query({
|
|
1642
870
|
args: {
|
|
@@ -1658,7 +886,15 @@ var getByProject = query({
|
|
|
1658
886
|
projectId: args.projectId,
|
|
1659
887
|
topicId: args.topicId
|
|
1660
888
|
});
|
|
1661
|
-
} catch {
|
|
889
|
+
} catch (error) {
|
|
890
|
+
debugGraphPrimitiveFallback(
|
|
891
|
+
"[epistemicNodes] Failed to resolve list scope",
|
|
892
|
+
{
|
|
893
|
+
error,
|
|
894
|
+
projectId: args.projectId,
|
|
895
|
+
topicId: args.topicId
|
|
896
|
+
}
|
|
897
|
+
);
|
|
1662
898
|
return [];
|
|
1663
899
|
}
|
|
1664
900
|
if (args.userId) {
|
|
@@ -1712,7 +948,15 @@ var search = query({
|
|
|
1712
948
|
projectId: args.projectId,
|
|
1713
949
|
topicId: args.topicId
|
|
1714
950
|
});
|
|
1715
|
-
} catch {
|
|
951
|
+
} catch (error) {
|
|
952
|
+
debugGraphPrimitiveFallback(
|
|
953
|
+
"[epistemicNodes] Failed to resolve search scope",
|
|
954
|
+
{
|
|
955
|
+
error,
|
|
956
|
+
projectId: args.projectId,
|
|
957
|
+
topicId: args.topicId
|
|
958
|
+
}
|
|
959
|
+
);
|
|
1716
960
|
return [];
|
|
1717
961
|
}
|
|
1718
962
|
}
|
|
@@ -1939,7 +1183,7 @@ var update = mutation({
|
|
|
1939
1183
|
nodeId,
|
|
1940
1184
|
operation: "upsert"
|
|
1941
1185
|
});
|
|
1942
|
-
return
|
|
1186
|
+
return buildNodeStatusSuccessResult();
|
|
1943
1187
|
}
|
|
1944
1188
|
});
|
|
1945
1189
|
var supersede = mutation({
|
|
@@ -2061,7 +1305,7 @@ var archive = mutation({
|
|
|
2061
1305
|
projectId: node.projectId
|
|
2062
1306
|
});
|
|
2063
1307
|
}
|
|
2064
|
-
return
|
|
1308
|
+
return buildNodeArchivedResult();
|
|
2065
1309
|
}
|
|
2066
1310
|
});
|
|
2067
1311
|
var hardDelete = internalMutation({
|
|
@@ -2075,7 +1319,7 @@ var hardDelete = internalMutation({
|
|
|
2075
1319
|
handler: async (ctx, args) => {
|
|
2076
1320
|
const node = await ctx.db.get(args.nodeId);
|
|
2077
1321
|
if (!node) {
|
|
2078
|
-
return
|
|
1322
|
+
return buildNodeNotFoundResult();
|
|
2079
1323
|
}
|
|
2080
1324
|
assertBeliefNodeHardDeleteAllowed({
|
|
2081
1325
|
node,
|
|
@@ -2111,10 +1355,7 @@ var hardDelete = internalMutation({
|
|
|
2111
1355
|
await ctx.db.delete(edge._id);
|
|
2112
1356
|
}
|
|
2113
1357
|
await ctx.db.delete(args.nodeId);
|
|
2114
|
-
return
|
|
2115
|
-
success: true,
|
|
2116
|
-
deletedEdgeCount: uniqueEdges.size
|
|
2117
|
-
};
|
|
1358
|
+
return buildNodeDeletedResult(uniqueEdges.size);
|
|
2118
1359
|
}
|
|
2119
1360
|
});
|
|
2120
1361
|
var verify = mutation({
|
|
@@ -2161,7 +1402,7 @@ var verify = mutation({
|
|
|
2161
1402
|
projectId: node.projectId,
|
|
2162
1403
|
triggeringAction: "epistemicNodes.verify"
|
|
2163
1404
|
});
|
|
2164
|
-
return
|
|
1405
|
+
return buildNodeStatusSuccessResult();
|
|
2165
1406
|
}
|
|
2166
1407
|
});
|
|
2167
1408
|
var batchCreate = mutation({
|
|
@@ -2199,6 +1440,27 @@ var batchCreate = mutation({
|
|
|
2199
1440
|
},
|
|
2200
1441
|
returns: permissiveReturn,
|
|
2201
1442
|
handler: async (ctx, args) => {
|
|
1443
|
+
const resolveNodeScope = async (node) => {
|
|
1444
|
+
if (!(node.topicId || node.projectId)) {
|
|
1445
|
+
return void 0;
|
|
1446
|
+
}
|
|
1447
|
+
try {
|
|
1448
|
+
return await resolveTopicProjectScope(ctx, {
|
|
1449
|
+
topicId: node.topicId,
|
|
1450
|
+
projectId: node.projectId
|
|
1451
|
+
});
|
|
1452
|
+
} catch (error) {
|
|
1453
|
+
debugGraphPrimitiveFallback(
|
|
1454
|
+
"[epistemicNodes] Failed to resolve scope for batch create node",
|
|
1455
|
+
{
|
|
1456
|
+
error,
|
|
1457
|
+
topicId: node.topicId,
|
|
1458
|
+
projectId: node.projectId
|
|
1459
|
+
}
|
|
1460
|
+
);
|
|
1461
|
+
return void 0;
|
|
1462
|
+
}
|
|
1463
|
+
};
|
|
2202
1464
|
const now = Date.now();
|
|
2203
1465
|
const results = [];
|
|
2204
1466
|
for (const node of args.nodes) {
|
|
@@ -2207,10 +1469,7 @@ var batchCreate = mutation({
|
|
|
2207
1469
|
mutationName: "epistemicNodes.batchCreate"
|
|
2208
1470
|
});
|
|
2209
1471
|
const epistemicLayer = getNodeLayer(node.nodeType);
|
|
2210
|
-
const resolvedScope =
|
|
2211
|
-
topicId: node.topicId,
|
|
2212
|
-
projectId: node.projectId
|
|
2213
|
-
}).catch(() => void 0) : void 0;
|
|
1472
|
+
const resolvedScope = await resolveNodeScope(node);
|
|
2214
1473
|
const nodeId = await ctx.db.insert("epistemicNodes", {
|
|
2215
1474
|
...node,
|
|
2216
1475
|
epistemicLayer,
|
|
@@ -2292,10 +1551,24 @@ var createInternal = internalMutation({
|
|
|
2292
1551
|
returns: permissiveReturn,
|
|
2293
1552
|
handler: async (ctx, args) => {
|
|
2294
1553
|
const now = Date.now();
|
|
2295
|
-
const resolvedScope = args.topicId || args.projectId ? await
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
1554
|
+
const resolvedScope = args.topicId || args.projectId ? await (async () => {
|
|
1555
|
+
try {
|
|
1556
|
+
return await resolveTopicProjectScope(ctx, {
|
|
1557
|
+
topicId: args.topicId,
|
|
1558
|
+
projectId: args.projectId
|
|
1559
|
+
});
|
|
1560
|
+
} catch (error) {
|
|
1561
|
+
debugGraphPrimitiveFallback(
|
|
1562
|
+
"[epistemicNodes] Failed to resolve create scope",
|
|
1563
|
+
{
|
|
1564
|
+
error,
|
|
1565
|
+
topicId: args.topicId,
|
|
1566
|
+
projectId: args.projectId
|
|
1567
|
+
}
|
|
1568
|
+
);
|
|
1569
|
+
return void 0;
|
|
1570
|
+
}
|
|
1571
|
+
})() : void 0;
|
|
2299
1572
|
const contentHash = args.contentHash || `${args.nodeType}:${args.canonicalText}`.slice(0, 64);
|
|
2300
1573
|
const epistemicLayer = args.epistemicLayer || getNodeLayer(args.nodeType);
|
|
2301
1574
|
const nodeId = await ctx.db.insert("epistemicNodes", {
|
|
@@ -2362,7 +1635,7 @@ var updateSyncStatus = internalMutation({
|
|
|
2362
1635
|
updates.syncError = args.syncError;
|
|
2363
1636
|
}
|
|
2364
1637
|
await ctx.db.patch(args.nodeId, updates);
|
|
2365
|
-
return
|
|
1638
|
+
return buildNodeStatusSuccessResult();
|
|
2366
1639
|
}
|
|
2367
1640
|
});
|
|
2368
1641
|
var backfillTopicId = internalMutation({
|