@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/entityLifecycle.js
CHANGED
|
@@ -1,798 +1,12 @@
|
|
|
1
1
|
import { v } from 'convex/values';
|
|
2
|
-
import {
|
|
2
|
+
import { checkProjectAccess } from '@lucern/access-control/access';
|
|
3
|
+
import { getCurrentUserId } from '@lucern/access-control/auth';
|
|
4
|
+
import { permissiveReturn } from '@lucern/contracts/schema-helpers/validators';
|
|
5
|
+
import { componentsGeneric, anyApi, queryGeneric, mutationGeneric } from 'convex/server';
|
|
3
6
|
|
|
4
7
|
// src/entityLifecycle.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
|
-
var checkProjectAccess = checkTopicAccess;
|
|
777
|
-
|
|
778
|
-
// ../access-control/src/auth.ts
|
|
779
|
-
async function getCurrentUserId(ctx) {
|
|
780
|
-
const identity = await ctx.auth.getUserIdentity();
|
|
781
|
-
return identity?.subject ?? null;
|
|
782
|
-
}
|
|
783
|
-
var permissiveReturn = v.optional(v.any());
|
|
784
|
-
var looseJsonObject = v.record(v.string(), v.any());
|
|
785
|
-
var looseJsonArray = v.array(v.any());
|
|
786
|
-
v.union(
|
|
787
|
-
v.string(),
|
|
788
|
-
v.number(),
|
|
789
|
-
v.boolean(),
|
|
790
|
-
v.null(),
|
|
791
|
-
looseJsonObject,
|
|
792
|
-
looseJsonArray
|
|
793
|
-
);
|
|
794
|
-
var api2 = anyApi;
|
|
795
|
-
componentsGeneric();
|
|
796
10
|
var internal = anyApi;
|
|
797
11
|
var mutation = mutationGeneric;
|
|
798
12
|
var query = queryGeneric;
|
|
@@ -899,7 +113,7 @@ async function resolveTopicOntologyInternal(ctx, topicId) {
|
|
|
899
113
|
"by_ontologyId",
|
|
900
114
|
(q) => q.eq("ontologyId", current?.ontologyId)
|
|
901
115
|
).collect();
|
|
902
|
-
const published = versions.filter((
|
|
116
|
+
const published = versions.filter((v3) => v3.status === "published").sort((a, b) => (b.publishedAt ?? 0) - (a.publishedAt ?? 0));
|
|
903
117
|
const latestPublished = published[0] ?? null;
|
|
904
118
|
return {
|
|
905
119
|
ontologyId: ontologyDef._id,
|
|
@@ -937,97 +151,138 @@ async function validateEntityTypeForTopic(ctx, topicId, nodeType) {
|
|
|
937
151
|
};
|
|
938
152
|
}
|
|
939
153
|
|
|
154
|
+
// src/debug.ts
|
|
155
|
+
function isGraphPrimitiveDebugEnabled() {
|
|
156
|
+
const env = globalThis.process?.env;
|
|
157
|
+
return env?.LUCERN_COMPAT_FALLBACK_DEBUG === "1" || env?.LUCERN_GRAPH_DEBUG === "1";
|
|
158
|
+
}
|
|
159
|
+
function debugGraphPrimitiveFallback(message, context) {
|
|
160
|
+
if (!isGraphPrimitiveDebugEnabled()) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
console.debug(message, context ?? {});
|
|
164
|
+
}
|
|
165
|
+
|
|
940
166
|
// src/topicProjectOverlay.ts
|
|
941
|
-
var
|
|
942
|
-
function
|
|
167
|
+
var LEGACY_SCOPE_FIELD = "graphScopeProjectId";
|
|
168
|
+
function readNonEmptyString(value) {
|
|
943
169
|
if (typeof value !== "string") {
|
|
944
170
|
return;
|
|
945
171
|
}
|
|
946
172
|
const normalized = value.trim();
|
|
947
173
|
return normalized.length > 0 ? normalized : void 0;
|
|
948
174
|
}
|
|
949
|
-
function
|
|
175
|
+
function readStringArray(value) {
|
|
950
176
|
if (!Array.isArray(value)) {
|
|
951
177
|
return [];
|
|
952
178
|
}
|
|
953
|
-
return value.map((entry) =>
|
|
179
|
+
return value.map((entry) => readNonEmptyString(entry)).filter((entry) => Boolean(entry));
|
|
954
180
|
}
|
|
955
|
-
function
|
|
181
|
+
function readMetadata(topic) {
|
|
956
182
|
return topic.metadata && typeof topic.metadata === "object" ? topic.metadata : {};
|
|
957
183
|
}
|
|
958
|
-
function
|
|
184
|
+
function readLegacyProjectId(value) {
|
|
959
185
|
if (!value) {
|
|
960
186
|
return;
|
|
961
187
|
}
|
|
962
|
-
return
|
|
188
|
+
return readNonEmptyString(value[LEGACY_SCOPE_FIELD]);
|
|
963
189
|
}
|
|
964
|
-
function
|
|
190
|
+
function coerceVisibility(value) {
|
|
965
191
|
return value === "private" || value === "team" || value === "firm" || value === "external" || value === "public" ? value : void 0;
|
|
966
192
|
}
|
|
967
|
-
function
|
|
193
|
+
function coerceStatus(value) {
|
|
968
194
|
return value === "active" || value === "archived" || value === "watching" ? value : void 0;
|
|
969
195
|
}
|
|
970
|
-
function
|
|
971
|
-
const explicit =
|
|
196
|
+
function mapProjectType(topic, metadata) {
|
|
197
|
+
const explicit = readNonEmptyString(metadata.projectType);
|
|
972
198
|
if (explicit) {
|
|
973
199
|
return explicit;
|
|
974
200
|
}
|
|
975
201
|
if (topic.type === "theme") {
|
|
976
202
|
return "thematic";
|
|
977
203
|
}
|
|
978
|
-
return
|
|
204
|
+
return readNonEmptyString(topic.type) || "general";
|
|
979
205
|
}
|
|
980
|
-
function
|
|
981
|
-
const metadata =
|
|
982
|
-
return topic.type === "theme" || topic.type === "thematic" || topic.type === "deal" || topic.type === "monitoring" ||
|
|
206
|
+
function isProjectLikeTopic(topic) {
|
|
207
|
+
const metadata = readMetadata(topic);
|
|
208
|
+
return topic.type === "theme" || topic.type === "thematic" || topic.type === "deal" || topic.type === "monitoring" || readLegacyProjectId(topic) !== void 0 || readNonEmptyString(metadata.projectType) !== void 0;
|
|
983
209
|
}
|
|
984
210
|
function isMissingLucernChildComponentError(error) {
|
|
985
|
-
const message =
|
|
211
|
+
const message = getErrorMessage(error);
|
|
986
212
|
return message.includes(
|
|
987
213
|
'Child component ComponentName(Identifier("lucern")) not found'
|
|
988
214
|
) || message.includes("Child component") && message.includes("lucern") && message.includes("not found");
|
|
989
215
|
}
|
|
990
|
-
|
|
216
|
+
function getErrorMessage(error) {
|
|
217
|
+
if (error instanceof Error) {
|
|
218
|
+
return error.message;
|
|
219
|
+
}
|
|
220
|
+
if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") {
|
|
221
|
+
return error.message;
|
|
222
|
+
}
|
|
223
|
+
return "unknown error";
|
|
224
|
+
}
|
|
225
|
+
async function resolveTopicDoc(ctx, scopeId) {
|
|
991
226
|
if (ctx?.db && typeof ctx.db.get === "function") {
|
|
992
227
|
try {
|
|
993
|
-
const directTopic = await ctx.db.get(
|
|
228
|
+
const directTopic = await ctx.db.get(
|
|
229
|
+
scopeId
|
|
230
|
+
);
|
|
994
231
|
if (directTopic) {
|
|
995
232
|
return directTopic;
|
|
996
233
|
}
|
|
997
|
-
} catch {
|
|
234
|
+
} catch (error) {
|
|
235
|
+
debugGraphPrimitiveFallback(
|
|
236
|
+
"[topicProjectOverlay] Failed to resolve topic by direct ID",
|
|
237
|
+
{
|
|
238
|
+
error,
|
|
239
|
+
scopeId
|
|
240
|
+
}
|
|
241
|
+
);
|
|
998
242
|
}
|
|
999
243
|
}
|
|
1000
244
|
if (typeof ctx.runQuery !== "function") {
|
|
1001
245
|
return null;
|
|
1002
246
|
}
|
|
1003
247
|
try {
|
|
1004
|
-
const topic = await ctx.runQuery(
|
|
248
|
+
const topic = await ctx.runQuery(api.topics.get, {
|
|
1005
249
|
id: String(scopeId)
|
|
1006
250
|
});
|
|
1007
251
|
if (topic?.name !== void 0 && topic?.type !== void 0) {
|
|
1008
252
|
return topic;
|
|
1009
253
|
}
|
|
1010
|
-
} catch {
|
|
254
|
+
} catch (error) {
|
|
255
|
+
debugGraphPrimitiveFallback(
|
|
256
|
+
"[topicProjectOverlay] Failed to resolve topic by ID query",
|
|
257
|
+
{
|
|
258
|
+
error,
|
|
259
|
+
scopeId
|
|
260
|
+
}
|
|
261
|
+
);
|
|
1011
262
|
}
|
|
1012
263
|
try {
|
|
1013
|
-
const topic = await ctx.runQuery(
|
|
264
|
+
const topic = await ctx.runQuery(api.topics.getByLegacyScopeId, {
|
|
1014
265
|
projectId: String(scopeId)
|
|
1015
266
|
});
|
|
1016
267
|
if (topic?.name !== void 0 && topic?.type !== void 0) {
|
|
1017
268
|
return topic;
|
|
1018
269
|
}
|
|
1019
|
-
} catch {
|
|
270
|
+
} catch (error) {
|
|
271
|
+
debugGraphPrimitiveFallback(
|
|
272
|
+
"[topicProjectOverlay] Failed to resolve topic by legacy scope ID",
|
|
273
|
+
{ error, scopeId }
|
|
274
|
+
);
|
|
1020
275
|
}
|
|
1021
276
|
return null;
|
|
1022
277
|
}
|
|
1023
|
-
function
|
|
1024
|
-
const metadata =
|
|
278
|
+
function materializeTopicProjectOverlay(topic, idMode = "legacy") {
|
|
279
|
+
const metadata = readMetadata(topic);
|
|
1025
280
|
const topicId = String(topic._id);
|
|
1026
|
-
const legacyProjectId =
|
|
281
|
+
const legacyProjectId = readLegacyProjectId(topic) || readLegacyProjectId(metadata) || readNonEmptyString(metadata.legacyProjectId);
|
|
1027
282
|
const storageProjectId = legacyProjectId || topicId;
|
|
1028
283
|
const outwardId = idMode === "topic" ? topicId : storageProjectId;
|
|
1029
|
-
const visibility =
|
|
1030
|
-
const status =
|
|
284
|
+
const visibility = coerceVisibility(topic.visibility) || coerceVisibility(metadata.visibility) || "private";
|
|
285
|
+
const status = coerceStatus(topic.status) || coerceStatus(metadata.status) || "active";
|
|
1031
286
|
const createdAt = typeof topic.createdAt === "number" ? topic.createdAt : typeof topic._creationTime === "number" ? topic._creationTime : 0;
|
|
1032
287
|
const updatedAt = typeof topic.updatedAt === "number" ? topic.updatedAt : typeof metadata.updatedAt === "number" ? metadata.updatedAt : createdAt;
|
|
1033
288
|
return {
|
|
@@ -1037,16 +292,16 @@ function materializeTopicProjectOverlay2(topic, idMode = "legacy") {
|
|
|
1037
292
|
topicId,
|
|
1038
293
|
storageProjectId,
|
|
1039
294
|
legacyProjectId,
|
|
1040
|
-
name:
|
|
1041
|
-
type:
|
|
1042
|
-
description:
|
|
1043
|
-
ownerId:
|
|
1044
|
-
sharedWith:
|
|
295
|
+
name: readNonEmptyString(topic.name) || "Untitled Theme",
|
|
296
|
+
type: mapProjectType(topic, metadata),
|
|
297
|
+
description: readNonEmptyString(topic.description),
|
|
298
|
+
ownerId: readNonEmptyString(metadata.ownerId) || readNonEmptyString(topic.createdBy) || "system",
|
|
299
|
+
sharedWith: readStringArray(metadata.sharedWith),
|
|
1045
300
|
visibility,
|
|
1046
|
-
tenantId:
|
|
1047
|
-
workspaceId:
|
|
301
|
+
tenantId: readNonEmptyString(topic.tenantId) || readNonEmptyString(metadata.tenantId),
|
|
302
|
+
workspaceId: readNonEmptyString(topic.workspaceId) || readNonEmptyString(metadata.workspaceId),
|
|
1048
303
|
status,
|
|
1049
|
-
tags:
|
|
304
|
+
tags: readStringArray(metadata.tags),
|
|
1050
305
|
chatCount: typeof metadata.chatCount === "number" ? metadata.chatCount : 0,
|
|
1051
306
|
artifactCount: typeof metadata.artifactCount === "number" ? metadata.artifactCount : 0,
|
|
1052
307
|
lastActivityAt: typeof metadata.lastActivityAt === "number" ? metadata.lastActivityAt : updatedAt,
|
|
@@ -1055,38 +310,42 @@ function materializeTopicProjectOverlay2(topic, idMode = "legacy") {
|
|
|
1055
310
|
updatedAt
|
|
1056
311
|
};
|
|
1057
312
|
}
|
|
1058
|
-
async function
|
|
1059
|
-
const topic = await
|
|
313
|
+
async function resolveTopicProjectOverlay(ctx, scopeId, options = {}) {
|
|
314
|
+
const topic = await resolveTopicDoc(ctx, scopeId);
|
|
1060
315
|
if (!topic) {
|
|
1061
316
|
return null;
|
|
1062
317
|
}
|
|
1063
|
-
if (options.projectLikeOnly !== false && !
|
|
318
|
+
if (options.projectLikeOnly !== false && !isProjectLikeTopic(topic)) {
|
|
1064
319
|
return null;
|
|
1065
320
|
}
|
|
1066
|
-
return
|
|
321
|
+
return materializeTopicProjectOverlay(topic, options.idMode);
|
|
1067
322
|
}
|
|
1068
|
-
async function
|
|
323
|
+
async function listTopicProjectOverlays(ctx, options = {}) {
|
|
1069
324
|
let allTopics = [];
|
|
1070
325
|
if (ctx?.db?.query && typeof ctx.db.query === "function") {
|
|
1071
326
|
try {
|
|
1072
327
|
allTopics = await ctx.db.query("topics").collect();
|
|
1073
|
-
} catch {
|
|
328
|
+
} catch (error) {
|
|
329
|
+
debugGraphPrimitiveFallback(
|
|
330
|
+
"[topicProjectOverlay] Failed to read topics table; falling back to API",
|
|
331
|
+
{ error }
|
|
332
|
+
);
|
|
1074
333
|
allTopics = [];
|
|
1075
334
|
}
|
|
1076
335
|
}
|
|
1077
336
|
if (allTopics.length === 0 && typeof ctx.runQuery === "function") {
|
|
1078
|
-
allTopics = (await ctx.runQuery(
|
|
337
|
+
allTopics = (await ctx.runQuery(api.topics.list, {}) ?? []) || [];
|
|
1079
338
|
}
|
|
1080
339
|
return allTopics.filter(
|
|
1081
|
-
(topic) => options.projectLikeOnly === false ||
|
|
1082
|
-
).map((topic) =>
|
|
340
|
+
(topic) => options.projectLikeOnly === false || isProjectLikeTopic(topic)
|
|
341
|
+
).map((topic) => materializeTopicProjectOverlay(topic, options.idMode));
|
|
1083
342
|
}
|
|
1084
343
|
async function patchTopicProjectOverlay(ctx, scopeId, value) {
|
|
1085
|
-
const topic = await
|
|
344
|
+
const topic = await resolveTopicDoc(ctx, scopeId);
|
|
1086
345
|
if (!topic) {
|
|
1087
346
|
return null;
|
|
1088
347
|
}
|
|
1089
|
-
const nextMetadata = { ...
|
|
348
|
+
const nextMetadata = { ...readMetadata(topic) };
|
|
1090
349
|
const patch = {};
|
|
1091
350
|
const topicUpdateArgs = {
|
|
1092
351
|
id: String(topic._id)
|
|
@@ -1111,7 +370,7 @@ async function patchTopicProjectOverlay(ctx, scopeId, value) {
|
|
|
1111
370
|
`patchTopicProjectOverlay cannot mutate ${key} via component-owned topics`
|
|
1112
371
|
);
|
|
1113
372
|
case "status": {
|
|
1114
|
-
const status =
|
|
373
|
+
const status = coerceStatus(rawValue);
|
|
1115
374
|
if (status) {
|
|
1116
375
|
patch.status = status;
|
|
1117
376
|
topicUpdateArgs.status = status;
|
|
@@ -1119,7 +378,7 @@ async function patchTopicProjectOverlay(ctx, scopeId, value) {
|
|
|
1119
378
|
break;
|
|
1120
379
|
}
|
|
1121
380
|
case "visibility": {
|
|
1122
|
-
const visibility =
|
|
381
|
+
const visibility = coerceVisibility(rawValue);
|
|
1123
382
|
if (visibility) {
|
|
1124
383
|
patch.visibility = visibility;
|
|
1125
384
|
topicUpdateArgs.visibility = visibility;
|
|
@@ -1127,7 +386,7 @@ async function patchTopicProjectOverlay(ctx, scopeId, value) {
|
|
|
1127
386
|
break;
|
|
1128
387
|
}
|
|
1129
388
|
case "type": {
|
|
1130
|
-
const projectType =
|
|
389
|
+
const projectType = readNonEmptyString(rawValue);
|
|
1131
390
|
if (projectType) {
|
|
1132
391
|
nextMetadata.projectType = projectType;
|
|
1133
392
|
} else {
|
|
@@ -1151,7 +410,7 @@ async function patchTopicProjectOverlay(ctx, scopeId, value) {
|
|
|
1151
410
|
topicUpdateArgs.metadata = nextMetadata;
|
|
1152
411
|
if (typeof ctx.runMutation === "function") {
|
|
1153
412
|
try {
|
|
1154
|
-
await ctx.runMutation(
|
|
413
|
+
await ctx.runMutation(api.topics.update, topicUpdateArgs);
|
|
1155
414
|
} catch (error) {
|
|
1156
415
|
if (!isMissingLucernChildComponentError(error) || !ctx?.db || typeof ctx.db.patch !== "function") {
|
|
1157
416
|
throw error;
|
|
@@ -1165,19 +424,28 @@ async function patchTopicProjectOverlay(ctx, scopeId, value) {
|
|
|
1165
424
|
"Cannot patch topic without component adapter (ctx.runMutation unavailable)"
|
|
1166
425
|
);
|
|
1167
426
|
}
|
|
1168
|
-
return
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
}
|
|
1174
|
-
);
|
|
427
|
+
return materializeTopicProjectOverlay({
|
|
428
|
+
...topic,
|
|
429
|
+
...patch,
|
|
430
|
+
metadata: nextMetadata
|
|
431
|
+
});
|
|
1175
432
|
}
|
|
1176
433
|
|
|
1177
434
|
// src/resolvers.ts
|
|
1178
435
|
function isMissingLucernChildComponentError2(error) {
|
|
1179
|
-
const message =
|
|
1180
|
-
return message.includes(
|
|
436
|
+
const message = getErrorMessage2(error);
|
|
437
|
+
return message.includes(
|
|
438
|
+
'Child component ComponentName(Identifier("lucern")) not found'
|
|
439
|
+
) || message.includes("Child component") && message.includes("lucern") && message.includes("not found");
|
|
440
|
+
}
|
|
441
|
+
function getErrorMessage2(error) {
|
|
442
|
+
if (error instanceof Error) {
|
|
443
|
+
return error.message;
|
|
444
|
+
}
|
|
445
|
+
if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") {
|
|
446
|
+
return error.message;
|
|
447
|
+
}
|
|
448
|
+
return "unknown error";
|
|
1181
449
|
}
|
|
1182
450
|
function isAdvisoryTopicPatch(value) {
|
|
1183
451
|
const advisoryKeys = /* @__PURE__ */ new Set(["lastActivityAt", "updatedAt"]);
|
|
@@ -1191,52 +459,47 @@ async function patchProjectWithTolerance(ctx, projectId, value) {
|
|
|
1191
459
|
if (!isAdvisoryTopicPatch(value) || !isMissingLucernChildComponentError2(error)) {
|
|
1192
460
|
throw error;
|
|
1193
461
|
}
|
|
1194
|
-
console.warn(
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
462
|
+
console.warn(
|
|
463
|
+
"[lucern graph-primitives] Non-fatal advisory topic patch failure",
|
|
464
|
+
{
|
|
465
|
+
projectId,
|
|
466
|
+
keys: Object.keys(value),
|
|
467
|
+
error: getErrorMessage2(error)
|
|
468
|
+
}
|
|
469
|
+
);
|
|
1199
470
|
}
|
|
1200
471
|
}
|
|
1201
|
-
function
|
|
472
|
+
function defaultResolvers() {
|
|
1202
473
|
return {
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
async listTopics(ctx) {
|
|
1213
|
-
return await listTopicProjectOverlays2(ctx, {
|
|
1214
|
-
idMode: "legacy"
|
|
1215
|
-
});
|
|
1216
|
-
},
|
|
1217
|
-
async getFinalArtifact(ctx, artifactId) {
|
|
1218
|
-
return await ctx.db.get(artifactId);
|
|
1219
|
-
}
|
|
474
|
+
getProject: (ctx, projectId) => resolveTopicProjectOverlay(ctx, projectId, {
|
|
475
|
+
idMode: "legacy",
|
|
476
|
+
projectLikeOnly: false
|
|
477
|
+
}),
|
|
478
|
+
patchProject: (ctx, projectId, value) => patchProjectWithTolerance(ctx, projectId, value),
|
|
479
|
+
listTopics: (ctx) => listTopicProjectOverlays(ctx, {
|
|
480
|
+
idMode: "legacy"
|
|
481
|
+
}),
|
|
482
|
+
getFinalArtifact: (ctx, artifactId) => ctx.db.get(artifactId)
|
|
1220
483
|
};
|
|
1221
484
|
}
|
|
1222
|
-
var
|
|
485
|
+
var resolverOverrides = {};
|
|
1223
486
|
function resolveGraphPrimitivesAppResolvers(_ctx) {
|
|
1224
487
|
return {
|
|
1225
|
-
...
|
|
1226
|
-
...
|
|
488
|
+
...defaultResolvers(),
|
|
489
|
+
...resolverOverrides
|
|
1227
490
|
};
|
|
1228
491
|
}
|
|
1229
|
-
var
|
|
492
|
+
var LEGACY_SCOPE_FIELD2 = "graphScopeProjectId";
|
|
1230
493
|
function asMappedProjectId(topic) {
|
|
1231
494
|
if (!topic) {
|
|
1232
495
|
return;
|
|
1233
496
|
}
|
|
1234
|
-
const directLegacyProjectId = normalizeScopeValue(topic[
|
|
497
|
+
const directLegacyProjectId = normalizeScopeValue(topic[LEGACY_SCOPE_FIELD2]);
|
|
1235
498
|
if (directLegacyProjectId) {
|
|
1236
499
|
return directLegacyProjectId;
|
|
1237
500
|
}
|
|
1238
501
|
const metadata = topic.metadata || {};
|
|
1239
|
-
const candidate = metadata[
|
|
502
|
+
const candidate = metadata[LEGACY_SCOPE_FIELD2] || metadata.legacyProjectId || metadata.projectId || metadata.scopeProjectId;
|
|
1240
503
|
return candidate ? candidate : void 0;
|
|
1241
504
|
}
|
|
1242
505
|
function normalizeScopeValue(value) {
|
|
@@ -1265,9 +528,16 @@ async function findTopicsByScopeAlias(ctx, scopeId) {
|
|
|
1265
528
|
try {
|
|
1266
529
|
return await ctx.db.query("topics").withIndex(
|
|
1267
530
|
"by_graph_scope_project",
|
|
1268
|
-
(q) => q.eq(
|
|
531
|
+
(q) => q.eq(LEGACY_SCOPE_FIELD2, scopeId)
|
|
1269
532
|
).collect();
|
|
1270
|
-
} catch {
|
|
533
|
+
} catch (error) {
|
|
534
|
+
debugGraphPrimitiveFallback(
|
|
535
|
+
"[topicScope] Failed to resolve scope alias via index",
|
|
536
|
+
{
|
|
537
|
+
error,
|
|
538
|
+
scopeId
|
|
539
|
+
}
|
|
540
|
+
);
|
|
1271
541
|
const topics = await ctx.db.query("topics").collect();
|
|
1272
542
|
return topics.filter((topic) => {
|
|
1273
543
|
const normalizedGlobalId = normalizeScopeValue(topic.globalId);
|
|
@@ -1281,10 +551,17 @@ async function tryResolveHostTopicById(ctx, topicId) {
|
|
|
1281
551
|
return null;
|
|
1282
552
|
}
|
|
1283
553
|
try {
|
|
1284
|
-
return await ctx.runQuery(
|
|
554
|
+
return await ctx.runQuery(api.topics.get, {
|
|
1285
555
|
id: topicId
|
|
1286
556
|
}) ?? null;
|
|
1287
|
-
} catch {
|
|
557
|
+
} catch (error) {
|
|
558
|
+
debugGraphPrimitiveFallback(
|
|
559
|
+
"[topicScope] Failed to resolve topic by host query",
|
|
560
|
+
{
|
|
561
|
+
error,
|
|
562
|
+
topicId
|
|
563
|
+
}
|
|
564
|
+
);
|
|
1288
565
|
return null;
|
|
1289
566
|
}
|
|
1290
567
|
}
|
|
@@ -1293,10 +570,17 @@ async function tryResolveHostTopicByLegacyScope(ctx, legacyScopeId) {
|
|
|
1293
570
|
return null;
|
|
1294
571
|
}
|
|
1295
572
|
try {
|
|
1296
|
-
return await ctx.runQuery(
|
|
573
|
+
return await ctx.runQuery(api.topics.getByLegacyScopeId, {
|
|
1297
574
|
projectId: legacyScopeId
|
|
1298
575
|
}) ?? null;
|
|
1299
|
-
} catch {
|
|
576
|
+
} catch (error) {
|
|
577
|
+
debugGraphPrimitiveFallback(
|
|
578
|
+
"[topicScope] Failed to resolve topic by legacy scope",
|
|
579
|
+
{
|
|
580
|
+
error,
|
|
581
|
+
legacyScopeId
|
|
582
|
+
}
|
|
583
|
+
);
|
|
1300
584
|
return null;
|
|
1301
585
|
}
|
|
1302
586
|
}
|
|
@@ -1325,8 +609,17 @@ async function resolveTopicProjectScope(ctx, args) {
|
|
|
1325
609
|
if (args.topicId) {
|
|
1326
610
|
let topic = null;
|
|
1327
611
|
try {
|
|
1328
|
-
topic = await ctx.db.get(
|
|
1329
|
-
|
|
612
|
+
topic = await ctx.db.get(
|
|
613
|
+
args.topicId
|
|
614
|
+
);
|
|
615
|
+
} catch (error) {
|
|
616
|
+
debugGraphPrimitiveFallback(
|
|
617
|
+
"[topicScope] Failed to load topic by direct id",
|
|
618
|
+
{
|
|
619
|
+
error,
|
|
620
|
+
topicId: args.topicId
|
|
621
|
+
}
|
|
622
|
+
);
|
|
1330
623
|
}
|
|
1331
624
|
if (!topic) {
|
|
1332
625
|
topic = await tryResolveHostTopicById(ctx, String(args.topicId));
|
|
@@ -1363,7 +656,14 @@ async function resolveTopicProjectScope(ctx, args) {
|
|
|
1363
656
|
directTopic = await ctx.db.get(
|
|
1364
657
|
args.projectId
|
|
1365
658
|
);
|
|
1366
|
-
} catch {
|
|
659
|
+
} catch (error) {
|
|
660
|
+
debugGraphPrimitiveFallback(
|
|
661
|
+
"[topicScope] Failed to load direct project topic",
|
|
662
|
+
{
|
|
663
|
+
error,
|
|
664
|
+
projectId: args.projectId
|
|
665
|
+
}
|
|
666
|
+
);
|
|
1367
667
|
}
|
|
1368
668
|
if (directTopic) {
|
|
1369
669
|
const inherited = await resolveInheritedWorkspaceScope(ctx, directTopic);
|
|
@@ -1465,9 +765,10 @@ var ONTOLOGICAL_NODE_TYPES = [
|
|
|
1465
765
|
"function",
|
|
1466
766
|
"value_chain"
|
|
1467
767
|
];
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
768
|
+
var ONTOLOGICAL_NODE_TYPE_SET = new Set(ONTOLOGICAL_NODE_TYPES);
|
|
769
|
+
var isOntologicalNodeType = ONTOLOGICAL_NODE_TYPE_SET.has.bind(
|
|
770
|
+
ONTOLOGICAL_NODE_TYPE_SET
|
|
771
|
+
);
|
|
1471
772
|
function normalizeCanonicalEntityText(value) {
|
|
1472
773
|
return value.trim().toLowerCase().replace(/\s+/g, " ");
|
|
1473
774
|
}
|