@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
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { v } from 'convex/values';
|
|
2
|
-
import { componentsGeneric, mutationGeneric, queryGeneric
|
|
2
|
+
import { componentsGeneric, anyApi, mutationGeneric, queryGeneric } from 'convex/server';
|
|
3
|
+
import { checkProjectAccess } from '@lucern/access-control/access';
|
|
4
|
+
import { permissiveReturn } from '@lucern/contracts/schema-helpers/validators';
|
|
3
5
|
|
|
4
6
|
// src/beliefEvidenceLinks.ts
|
|
5
7
|
|
|
@@ -39,789 +41,31 @@ componentsGeneric();
|
|
|
39
41
|
var internal = anyApi;
|
|
40
42
|
var mutation = mutationGeneric;
|
|
41
43
|
var query = queryGeneric;
|
|
42
|
-
var api2 = anyApi;
|
|
43
|
-
componentsGeneric();
|
|
44
44
|
|
|
45
|
-
//
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
const normalized = value.trim();
|
|
52
|
-
return normalized.length > 0 ? normalized : void 0;
|
|
53
|
-
}
|
|
54
|
-
function readStringArray(value) {
|
|
55
|
-
if (!Array.isArray(value)) {
|
|
56
|
-
return [];
|
|
57
|
-
}
|
|
58
|
-
return value.map((entry) => readNonEmptyString(entry)).filter((entry) => Boolean(entry));
|
|
59
|
-
}
|
|
60
|
-
function readMetadata(topic) {
|
|
61
|
-
return topic.metadata && typeof topic.metadata === "object" ? topic.metadata : {};
|
|
62
|
-
}
|
|
63
|
-
function readLegacyProjectId(value) {
|
|
64
|
-
if (!value) {
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
return readNonEmptyString(value[LEGACY_SCOPE_FIELD]);
|
|
68
|
-
}
|
|
69
|
-
function coerceVisibility(value) {
|
|
70
|
-
return value === "private" || value === "team" || value === "firm" || value === "external" || value === "public" ? value : void 0;
|
|
71
|
-
}
|
|
72
|
-
function coerceStatus(value) {
|
|
73
|
-
return value === "active" || value === "archived" || value === "watching" ? value : void 0;
|
|
74
|
-
}
|
|
75
|
-
function mapProjectType(topic, metadata) {
|
|
76
|
-
const explicit = readNonEmptyString(metadata.projectType);
|
|
77
|
-
if (explicit) {
|
|
78
|
-
return explicit;
|
|
79
|
-
}
|
|
80
|
-
if (topic.type === "theme") {
|
|
81
|
-
return "thematic";
|
|
82
|
-
}
|
|
83
|
-
return readNonEmptyString(topic.type) || "general";
|
|
84
|
-
}
|
|
85
|
-
function isProjectLikeTopic(topic) {
|
|
86
|
-
const metadata = readMetadata(topic);
|
|
87
|
-
return topic.type === "theme" || topic.type === "thematic" || topic.type === "deal" || topic.type === "monitoring" || readLegacyProjectId(topic) !== void 0 || readNonEmptyString(metadata.projectType) !== void 0;
|
|
88
|
-
}
|
|
89
|
-
async function resolveTopicDoc(ctx, scopeId) {
|
|
90
|
-
if (ctx?.db && typeof ctx.db.get === "function") {
|
|
91
|
-
try {
|
|
92
|
-
const directTopic = await ctx.db.get(scopeId);
|
|
93
|
-
if (directTopic) {
|
|
94
|
-
return directTopic;
|
|
95
|
-
}
|
|
96
|
-
} catch {
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
if (typeof ctx.runQuery !== "function") {
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
102
|
-
try {
|
|
103
|
-
const topic = await ctx.runQuery(api2.topics.get, {
|
|
104
|
-
id: String(scopeId)
|
|
105
|
-
});
|
|
106
|
-
if (topic?.name !== void 0 && topic?.type !== void 0) {
|
|
107
|
-
return topic;
|
|
108
|
-
}
|
|
109
|
-
} catch {
|
|
110
|
-
}
|
|
111
|
-
try {
|
|
112
|
-
const topic = await ctx.runQuery(api2.topics.getByLegacyScopeId, {
|
|
113
|
-
projectId: String(scopeId)
|
|
114
|
-
});
|
|
115
|
-
if (topic?.name !== void 0 && topic?.type !== void 0) {
|
|
116
|
-
return topic;
|
|
117
|
-
}
|
|
118
|
-
} catch {
|
|
119
|
-
}
|
|
120
|
-
return null;
|
|
121
|
-
}
|
|
122
|
-
function materializeTopicProjectOverlay(topic, idMode = "legacy") {
|
|
123
|
-
const metadata = readMetadata(topic);
|
|
124
|
-
const topicId = String(topic._id);
|
|
125
|
-
const legacyProjectId = readLegacyProjectId(topic) || readLegacyProjectId(metadata) || readNonEmptyString(metadata.legacyProjectId);
|
|
126
|
-
const storageProjectId = legacyProjectId || topicId;
|
|
127
|
-
const outwardId = idMode === "topic" ? topicId : storageProjectId;
|
|
128
|
-
const visibility = coerceVisibility(topic.visibility) || coerceVisibility(metadata.visibility) || "private";
|
|
129
|
-
const status = coerceStatus(topic.status) || coerceStatus(metadata.status) || "active";
|
|
130
|
-
const createdAt = typeof topic.createdAt === "number" ? topic.createdAt : typeof topic._creationTime === "number" ? topic._creationTime : 0;
|
|
131
|
-
const updatedAt = typeof topic.updatedAt === "number" ? topic.updatedAt : typeof metadata.updatedAt === "number" ? metadata.updatedAt : createdAt;
|
|
132
|
-
return {
|
|
133
|
-
...metadata,
|
|
134
|
-
_id: outwardId,
|
|
135
|
-
projectId: outwardId,
|
|
136
|
-
topicId,
|
|
137
|
-
storageProjectId,
|
|
138
|
-
legacyProjectId,
|
|
139
|
-
name: readNonEmptyString(topic.name) || "Untitled Theme",
|
|
140
|
-
type: mapProjectType(topic, metadata),
|
|
141
|
-
description: readNonEmptyString(topic.description),
|
|
142
|
-
ownerId: readNonEmptyString(metadata.ownerId) || readNonEmptyString(topic.createdBy) || "system",
|
|
143
|
-
sharedWith: readStringArray(metadata.sharedWith),
|
|
144
|
-
visibility,
|
|
145
|
-
tenantId: readNonEmptyString(topic.tenantId) || readNonEmptyString(metadata.tenantId),
|
|
146
|
-
workspaceId: readNonEmptyString(topic.workspaceId) || readNonEmptyString(metadata.workspaceId),
|
|
147
|
-
status,
|
|
148
|
-
tags: readStringArray(metadata.tags),
|
|
149
|
-
chatCount: typeof metadata.chatCount === "number" ? metadata.chatCount : 0,
|
|
150
|
-
artifactCount: typeof metadata.artifactCount === "number" ? metadata.artifactCount : 0,
|
|
151
|
-
lastActivityAt: typeof metadata.lastActivityAt === "number" ? metadata.lastActivityAt : updatedAt,
|
|
152
|
-
_creationTime: typeof topic._creationTime === "number" ? topic._creationTime : createdAt,
|
|
153
|
-
createdAt,
|
|
154
|
-
updatedAt
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
async function resolveTopicProjectOverlay(ctx, scopeId, options = {}) {
|
|
158
|
-
const topic = await resolveTopicDoc(ctx, scopeId);
|
|
159
|
-
if (!topic) {
|
|
160
|
-
return null;
|
|
161
|
-
}
|
|
162
|
-
if (options.projectLikeOnly !== false && !isProjectLikeTopic(topic)) {
|
|
163
|
-
return null;
|
|
164
|
-
}
|
|
165
|
-
return materializeTopicProjectOverlay(topic, options.idMode);
|
|
45
|
+
// src/debug.ts
|
|
46
|
+
function isGraphPrimitiveDebugEnabled() {
|
|
47
|
+
const env = globalThis.process?.env;
|
|
48
|
+
return env?.LUCERN_COMPAT_FALLBACK_DEBUG === "1" || env?.LUCERN_GRAPH_DEBUG === "1";
|
|
166
49
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
if (ctx?.db?.query && typeof ctx.db.query === "function") {
|
|
170
|
-
try {
|
|
171
|
-
allTopics = await ctx.db.query("topics").collect();
|
|
172
|
-
} catch {
|
|
173
|
-
allTopics = [];
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
if (allTopics.length === 0 && typeof ctx.runQuery === "function") {
|
|
177
|
-
allTopics = (await ctx.runQuery(api2.topics.list, {}) ?? []) || [];
|
|
178
|
-
}
|
|
179
|
-
return allTopics.filter(
|
|
180
|
-
(topic) => options.projectLikeOnly === false || isProjectLikeTopic(topic)
|
|
181
|
-
).map((topic) => materializeTopicProjectOverlay(topic, options.idMode));
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// ../access-control/src/projectGrantsBridge.ts
|
|
185
|
-
var PROJECT_GRANT_STATUSES = ["active", "revoked", "expired"];
|
|
186
|
-
function normalizeString(value) {
|
|
187
|
-
if (typeof value !== "string") {
|
|
50
|
+
function debugGraphPrimitiveFallback(message, context) {
|
|
51
|
+
if (!isGraphPrimitiveDebugEnabled()) {
|
|
188
52
|
return;
|
|
189
53
|
}
|
|
190
|
-
|
|
191
|
-
return trimmed.length > 0 ? trimmed : void 0;
|
|
192
|
-
}
|
|
193
|
-
async function resolveGrantScopeIds(ctx, args) {
|
|
194
|
-
const topicId = normalizeString(args.topicId);
|
|
195
|
-
const projectId = normalizeString(args.projectId);
|
|
196
|
-
for (const scopeId of [topicId, projectId]) {
|
|
197
|
-
if (!scopeId) {
|
|
198
|
-
continue;
|
|
199
|
-
}
|
|
200
|
-
try {
|
|
201
|
-
const overlay = await resolveTopicProjectOverlay(ctx, scopeId, {
|
|
202
|
-
idMode: "legacy",
|
|
203
|
-
projectLikeOnly: false
|
|
204
|
-
});
|
|
205
|
-
if (overlay) {
|
|
206
|
-
return {
|
|
207
|
-
topicId: normalizeString(overlay.topicId) ?? topicId,
|
|
208
|
-
projectId: normalizeString(overlay.projectId) ?? projectId ?? scopeId
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
} catch {
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
return { topicId, projectId };
|
|
215
|
-
}
|
|
216
|
-
async function normalizeProjectGrantRow(ctx, row) {
|
|
217
|
-
const scope = await resolveGrantScopeIds(ctx, {
|
|
218
|
-
topicId: row.topicId,
|
|
219
|
-
projectId: row.projectId
|
|
220
|
-
});
|
|
221
|
-
return {
|
|
222
|
-
...row,
|
|
223
|
-
...scope.topicId ? { topicId: scope.topicId } : {},
|
|
224
|
-
...scope.projectId ?? scope.topicId ? { projectId: scope.projectId ?? scope.topicId } : {}
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
async function normalizeProjectGrantRows(ctx, rows) {
|
|
228
|
-
return await Promise.all(rows.map((row) => normalizeProjectGrantRow(ctx, row)));
|
|
229
|
-
}
|
|
230
|
-
async function listProjectGrantsByPrincipal(ctx, principalId) {
|
|
231
|
-
const rows = await Promise.all(
|
|
232
|
-
PROJECT_GRANT_STATUSES.map(
|
|
233
|
-
(status) => ctx.db.query("projectGrants").withIndex(
|
|
234
|
-
"by_principal_status",
|
|
235
|
-
(q) => q.eq("principalId", principalId).eq("status", status)
|
|
236
|
-
).collect()
|
|
237
|
-
)
|
|
238
|
-
);
|
|
239
|
-
return await normalizeProjectGrantRows(ctx, rows.flat());
|
|
240
|
-
}
|
|
241
|
-
async function listProjectGrantsByGroup(ctx, groupId) {
|
|
242
|
-
const rows = await Promise.all(
|
|
243
|
-
PROJECT_GRANT_STATUSES.map(
|
|
244
|
-
(status) => ctx.db.query("projectGrants").withIndex(
|
|
245
|
-
"by_group_status",
|
|
246
|
-
(q) => q.eq("groupId", groupId).eq("status", status)
|
|
247
|
-
).collect()
|
|
248
|
-
)
|
|
249
|
-
);
|
|
250
|
-
return await normalizeProjectGrantRows(ctx, rows.flat());
|
|
251
|
-
}
|
|
252
|
-
function buildScopeMatchers(inputScopeId, resolved) {
|
|
253
|
-
return new Set(
|
|
254
|
-
[inputScopeId, resolved.topicId, resolved.projectId].map((value) => normalizeString(value)).filter((value) => Boolean(value))
|
|
255
|
-
);
|
|
256
|
-
}
|
|
257
|
-
function matchesResolvedScope(row, scopeIds) {
|
|
258
|
-
const rowTopicId = normalizeString(row.topicId);
|
|
259
|
-
const rowProjectId = normalizeString(row.projectId);
|
|
260
|
-
return rowTopicId !== void 0 && scopeIds.has(rowTopicId) || rowProjectId !== void 0 && scopeIds.has(rowProjectId);
|
|
261
|
-
}
|
|
262
|
-
async function bridgeListProjectGrantsByTopicAndPrincipal(ctx, topicId, principalId) {
|
|
263
|
-
const resolved = await resolveGrantScopeIds(ctx, { topicId });
|
|
264
|
-
const scopeIds = buildScopeMatchers(topicId, resolved);
|
|
265
|
-
const rows = await listProjectGrantsByPrincipal(ctx, principalId);
|
|
266
|
-
return rows.filter((row) => matchesResolvedScope(row, scopeIds));
|
|
267
|
-
}
|
|
268
|
-
async function bridgeListProjectGrantsByTopicAndGroup(ctx, topicId, groupId) {
|
|
269
|
-
const resolved = await resolveGrantScopeIds(ctx, { topicId });
|
|
270
|
-
const scopeIds = buildScopeMatchers(topicId, resolved);
|
|
271
|
-
const rows = await listProjectGrantsByGroup(ctx, groupId);
|
|
272
|
-
return rows.filter((row) => matchesResolvedScope(row, scopeIds));
|
|
273
|
-
}
|
|
274
|
-
async function bridgeListProjectGrantsByPrincipalStatus(ctx, principalId, status) {
|
|
275
|
-
const rows = await listProjectGrantsByPrincipal(ctx, principalId);
|
|
276
|
-
return rows.filter((row) => row.status === status);
|
|
277
|
-
}
|
|
278
|
-
async function bridgeListProjectGrantsByGroupStatus(ctx, groupId, status) {
|
|
279
|
-
const rows = await listProjectGrantsByGroup(ctx, groupId);
|
|
280
|
-
return rows.filter((row) => row.status === status);
|
|
281
|
-
}
|
|
282
|
-
async function bridgeInsertProjectGrant(ctx, value) {
|
|
283
|
-
const resolved = await resolveGrantScopeIds(ctx, value);
|
|
284
|
-
return await ctx.db.insert("projectGrants", {
|
|
285
|
-
...value,
|
|
286
|
-
...resolved.topicId ? { topicId: resolved.topicId } : {},
|
|
287
|
-
...resolved.projectId ?? resolved.topicId ? { projectId: resolved.projectId ?? resolved.topicId } : {}
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// ../access-control/src/resolvers.ts
|
|
292
|
-
async function findUserByClerkId(ctx, clerkId) {
|
|
293
|
-
const normalizedClerkId = clerkId.trim();
|
|
294
|
-
if (!normalizedClerkId) {
|
|
295
|
-
return null;
|
|
296
|
-
}
|
|
297
|
-
if (typeof ctx.runQuery === "function") {
|
|
298
|
-
try {
|
|
299
|
-
const bridgedUser = await ctx.runQuery(api2.users.getUserByClerkId, {
|
|
300
|
-
clerkId: normalizedClerkId
|
|
301
|
-
});
|
|
302
|
-
if (bridgedUser) {
|
|
303
|
-
return bridgedUser;
|
|
304
|
-
}
|
|
305
|
-
} catch {
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
try {
|
|
309
|
-
const users = await ctx.db.query("users").collect();
|
|
310
|
-
return users.find((user) => String(user.clerkId ?? "") === normalizedClerkId) ?? null;
|
|
311
|
-
} catch {
|
|
312
|
-
return null;
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
async function findUserByPrincipalId(ctx, principalId) {
|
|
316
|
-
const normalizedPrincipalId = principalId.trim();
|
|
317
|
-
if (!normalizedPrincipalId) {
|
|
318
|
-
return null;
|
|
319
|
-
}
|
|
320
|
-
try {
|
|
321
|
-
const users = await ctx.db.query("users").collect();
|
|
322
|
-
return users.find(
|
|
323
|
-
(user) => String(user.defaultPrincipalId ?? "") === normalizedPrincipalId
|
|
324
|
-
) ?? null;
|
|
325
|
-
} catch {
|
|
326
|
-
return null;
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
async function findAgentByPrincipalId(ctx, principalId) {
|
|
330
|
-
const normalizedPrincipalId = principalId.trim();
|
|
331
|
-
if (!normalizedPrincipalId) {
|
|
332
|
-
return null;
|
|
333
|
-
}
|
|
334
|
-
if (typeof ctx.runQuery === "function") {
|
|
335
|
-
try {
|
|
336
|
-
const bridgedAgent = await ctx.runQuery(
|
|
337
|
-
api2.agents.getAgentByPrincipalId,
|
|
338
|
-
{
|
|
339
|
-
principalId: normalizedPrincipalId
|
|
340
|
-
}
|
|
341
|
-
);
|
|
342
|
-
if (bridgedAgent) {
|
|
343
|
-
return bridgedAgent;
|
|
344
|
-
}
|
|
345
|
-
} catch {
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
try {
|
|
349
|
-
const agents = await ctx.db.query("agents").collect();
|
|
350
|
-
return agents.find(
|
|
351
|
-
(agent) => String(agent.principalId ?? "") === normalizedPrincipalId
|
|
352
|
-
) ?? null;
|
|
353
|
-
} catch {
|
|
354
|
-
return null;
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
function defaultResolvers() {
|
|
358
|
-
return {
|
|
359
|
-
async getProject(ctx, topicId) {
|
|
360
|
-
return await resolveTopicProjectOverlay(ctx, topicId, {
|
|
361
|
-
idMode: "legacy",
|
|
362
|
-
projectLikeOnly: false
|
|
363
|
-
});
|
|
364
|
-
},
|
|
365
|
-
async listTopics(ctx) {
|
|
366
|
-
return await listTopicProjectOverlays(ctx, { idMode: "legacy" });
|
|
367
|
-
},
|
|
368
|
-
async listTopicsByOwner(ctx, ownerId) {
|
|
369
|
-
const topics = await listTopicProjectOverlays(ctx, { idMode: "legacy" });
|
|
370
|
-
return topics.filter((topic) => topic.ownerId === ownerId);
|
|
371
|
-
},
|
|
372
|
-
async listTopicsByVisibility(ctx, visibility) {
|
|
373
|
-
const topics = await listTopicProjectOverlays(ctx, { idMode: "legacy" });
|
|
374
|
-
return topics.filter((topic) => topic.visibility === visibility);
|
|
375
|
-
},
|
|
376
|
-
async listProjectGrantsByProjectAndPrincipal(ctx, topicId, principalId) {
|
|
377
|
-
return await bridgeListProjectGrantsByTopicAndPrincipal(
|
|
378
|
-
ctx,
|
|
379
|
-
topicId,
|
|
380
|
-
principalId
|
|
381
|
-
);
|
|
382
|
-
},
|
|
383
|
-
async listProjectGrantsByProjectAndGroup(ctx, topicId, groupId) {
|
|
384
|
-
return await bridgeListProjectGrantsByTopicAndGroup(ctx, topicId, groupId);
|
|
385
|
-
},
|
|
386
|
-
async listProjectGrantsByPrincipalStatus(ctx, principalId, status) {
|
|
387
|
-
return await bridgeListProjectGrantsByPrincipalStatus(
|
|
388
|
-
ctx,
|
|
389
|
-
principalId,
|
|
390
|
-
status
|
|
391
|
-
);
|
|
392
|
-
},
|
|
393
|
-
async listProjectGrantsByGroupStatus(ctx, groupId, status) {
|
|
394
|
-
return await bridgeListProjectGrantsByGroupStatus(ctx, groupId, status);
|
|
395
|
-
},
|
|
396
|
-
async insertProjectGrant(ctx, value) {
|
|
397
|
-
return await bridgeInsertProjectGrant(ctx, value);
|
|
398
|
-
},
|
|
399
|
-
async getAgentByPrincipalId(ctx, principalId) {
|
|
400
|
-
return await findAgentByPrincipalId(ctx, principalId);
|
|
401
|
-
},
|
|
402
|
-
async getUserByClerkId(ctx, clerkId) {
|
|
403
|
-
return await findUserByClerkId(ctx, clerkId);
|
|
404
|
-
},
|
|
405
|
-
async getUserByPrincipalId(ctx, principalId) {
|
|
406
|
-
return await findUserByPrincipalId(ctx, principalId);
|
|
407
|
-
}
|
|
408
|
-
};
|
|
409
|
-
}
|
|
410
|
-
var resolverOverrides = {};
|
|
411
|
-
function resolveAccessControlAppResolvers(_ctx) {
|
|
412
|
-
return {
|
|
413
|
-
...defaultResolvers(),
|
|
414
|
-
...resolverOverrides
|
|
415
|
-
};
|
|
54
|
+
console.debug(message, context ?? {});
|
|
416
55
|
}
|
|
417
56
|
|
|
418
|
-
//
|
|
419
|
-
|
|
420
|
-
const resolved = user;
|
|
421
|
-
if (!resolved) {
|
|
422
|
-
throw new Error(
|
|
423
|
-
`[AccessControl] Canonical user identity required for ${clerkId}. Sync users.upsertUser before user-bound access checks.`
|
|
424
|
-
);
|
|
425
|
-
}
|
|
426
|
-
const { mcRole, defaultTenantId, defaultWorkspaceId, defaultPrincipalId } = resolved;
|
|
427
|
-
if (mcRole !== "platform_admin" && mcRole !== "tenant_admin" && mcRole !== "workspace_admin" && mcRole !== "editor" && mcRole !== "viewer" && mcRole !== "auditor" && mcRole !== "service_agent") {
|
|
428
|
-
throw new Error(
|
|
429
|
-
`[AccessControl] Canonical MC role required for ${clerkId}. Re-sync Master Control identity before user-bound access checks.`
|
|
430
|
-
);
|
|
431
|
-
}
|
|
432
|
-
if (typeof defaultTenantId !== "string" || defaultTenantId.trim().length === 0) {
|
|
433
|
-
throw new Error(
|
|
434
|
-
`[AccessControl] Canonical home tenant required for ${clerkId}. Re-sync Master Control identity before user-bound access checks.`
|
|
435
|
-
);
|
|
436
|
-
}
|
|
437
|
-
if (typeof defaultWorkspaceId !== "string" || defaultWorkspaceId.trim().length === 0) {
|
|
438
|
-
throw new Error(
|
|
439
|
-
`[AccessControl] Canonical home workspace required for ${clerkId}. Re-sync Master Control identity before user-bound access checks.`
|
|
440
|
-
);
|
|
441
|
-
}
|
|
442
|
-
if (typeof defaultPrincipalId !== "string" || defaultPrincipalId.trim().length === 0) {
|
|
443
|
-
throw new Error(
|
|
444
|
-
`[AccessControl] Canonical federated principal required for ${clerkId}. Re-sync Master Control identity before user-bound access checks.`
|
|
445
|
-
);
|
|
446
|
-
}
|
|
447
|
-
return {
|
|
448
|
-
mcRole,
|
|
449
|
-
defaultTenantId: defaultTenantId.trim(),
|
|
450
|
-
defaultWorkspaceId: defaultWorkspaceId.trim(),
|
|
451
|
-
defaultPrincipalId: defaultPrincipalId.trim()
|
|
452
|
-
};
|
|
453
|
-
}
|
|
454
|
-
function isPrincipalIdInput(value) {
|
|
455
|
-
return value.startsWith("user:") || value.startsWith("group:") || value.startsWith("service:") || value.startsWith("agent:") || value.startsWith("external_viewer:");
|
|
456
|
-
}
|
|
457
|
-
async function resolveCanonicalUserRecord(ctx, actorId) {
|
|
458
|
-
const normalizedActorId = actorId.trim();
|
|
459
|
-
const clerkId = isPrincipalIdInput(normalizedActorId) && normalizedActorId.startsWith("user:") ? normalizedActorId.slice("user:".length) : normalizedActorId;
|
|
460
|
-
const resolvers = resolveAccessControlAppResolvers();
|
|
461
|
-
const resolvedByClerkId = await resolvers.getUserByClerkId(ctx, clerkId);
|
|
462
|
-
if (resolvedByClerkId) {
|
|
463
|
-
return {
|
|
464
|
-
resolvedUser: resolvedByClerkId,
|
|
465
|
-
clerkId,
|
|
466
|
-
contextClerkId: clerkId
|
|
467
|
-
};
|
|
468
|
-
}
|
|
469
|
-
const resolvedByPrincipalId = await resolvers.getUserByPrincipalId(
|
|
470
|
-
ctx,
|
|
471
|
-
normalizedActorId
|
|
472
|
-
);
|
|
473
|
-
return {
|
|
474
|
-
resolvedUser: resolvedByPrincipalId ?? null,
|
|
475
|
-
clerkId,
|
|
476
|
-
contextClerkId: normalizedActorId.startsWith("user:") && clerkId.length > 0 ? clerkId : normalizedActorId
|
|
477
|
-
};
|
|
478
|
-
}
|
|
479
|
-
function uniqRoles(roles) {
|
|
480
|
-
const roleSet = /* @__PURE__ */ new Set();
|
|
481
|
-
for (const role of roles) {
|
|
482
|
-
if (role === "platform_admin" || role === "tenant_admin" || role === "workspace_admin" || role === "editor" || role === "viewer" || role === "auditor" || role === "service_agent") {
|
|
483
|
-
roleSet.add(role);
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
return [...roleSet];
|
|
487
|
-
}
|
|
488
|
-
function normalizeGroupIds(value) {
|
|
489
|
-
if (!Array.isArray(value)) {
|
|
490
|
-
return [];
|
|
491
|
-
}
|
|
492
|
-
return [...new Set(
|
|
493
|
-
value.filter((entry) => typeof entry === "string").map((entry) => entry.trim()).filter(Boolean)
|
|
494
|
-
)];
|
|
495
|
-
}
|
|
496
|
-
function requireServiceAgentUser(user, actorId) {
|
|
497
|
-
const canonicalUser = requireCanonicalResolvedUser(user, actorId);
|
|
498
|
-
if (canonicalUser.mcRole !== "service_agent") {
|
|
499
|
-
throw new Error(
|
|
500
|
-
`[AccessControl] Canonical service_agent identity required for ${actorId}. Sync users.upsertUser before agent-bound access checks.`
|
|
501
|
-
);
|
|
502
|
-
}
|
|
503
|
-
return canonicalUser;
|
|
504
|
-
}
|
|
505
|
-
function requireCanonicalResolvedAgent(agent, actorId) {
|
|
506
|
-
const resolved = agent;
|
|
507
|
-
if (!resolved) {
|
|
508
|
-
throw new Error(
|
|
509
|
-
`[AccessControl] Agent "${actorId}" not found in agents or users table.`
|
|
510
|
-
);
|
|
511
|
-
}
|
|
512
|
-
if (typeof resolved.principalId !== "string" || resolved.principalId.trim().length === 0) {
|
|
513
|
-
throw new Error(
|
|
514
|
-
`[AccessControl] Canonical agent principalId required for ${actorId}.`
|
|
515
|
-
);
|
|
516
|
-
}
|
|
517
|
-
if (typeof resolved.tenantId !== "string" || resolved.tenantId.trim().length === 0) {
|
|
518
|
-
throw new Error(
|
|
519
|
-
`[AccessControl] Canonical home tenant required for ${actorId}.`
|
|
520
|
-
);
|
|
521
|
-
}
|
|
522
|
-
if (typeof resolved.workspaceId !== "string" || resolved.workspaceId.trim().length === 0) {
|
|
523
|
-
throw new Error(
|
|
524
|
-
`[AccessControl] Canonical home workspace required for ${actorId}.`
|
|
525
|
-
);
|
|
526
|
-
}
|
|
527
|
-
return {
|
|
528
|
-
principalId: resolved.principalId.trim(),
|
|
529
|
-
tenantId: resolved.tenantId.trim(),
|
|
530
|
-
workspaceId: resolved.workspaceId.trim(),
|
|
531
|
-
roles: uniqRoles(Array.isArray(resolved.roles) ? resolved.roles : []) ?? ["service_agent"],
|
|
532
|
-
groupIds: normalizeGroupIds(resolved.groupIds)
|
|
533
|
-
};
|
|
534
|
-
}
|
|
535
|
-
async function resolvePrincipalContext(ctx, actorId) {
|
|
536
|
-
if (actorId.startsWith("agent:")) {
|
|
537
|
-
const resolvers = resolveAccessControlAppResolvers();
|
|
538
|
-
const resolvedAgent = await resolvers.getAgentByPrincipalId(ctx, actorId);
|
|
539
|
-
if (resolvedAgent) {
|
|
540
|
-
const agent = requireCanonicalResolvedAgent(
|
|
541
|
-
resolvedAgent,
|
|
542
|
-
actorId
|
|
543
|
-
);
|
|
544
|
-
return {
|
|
545
|
-
principalId: agent.principalId,
|
|
546
|
-
principalType: "service",
|
|
547
|
-
clerkId: actorId,
|
|
548
|
-
tenantId: agent.tenantId,
|
|
549
|
-
workspaceId: agent.workspaceId,
|
|
550
|
-
roles: agent.roles.length > 0 ? agent.roles : ["service_agent"],
|
|
551
|
-
groupIds: agent.groupIds,
|
|
552
|
-
isPlatformAdmin: false,
|
|
553
|
-
isTenantAdmin: false,
|
|
554
|
-
isWorkspaceAdmin: false,
|
|
555
|
-
isSystemFallback: false
|
|
556
|
-
};
|
|
557
|
-
}
|
|
558
|
-
const resolvedUser2 = await resolvers.getUserByClerkId(
|
|
559
|
-
ctx,
|
|
560
|
-
actorId
|
|
561
|
-
);
|
|
562
|
-
if (!resolvedUser2) {
|
|
563
|
-
throw new Error(
|
|
564
|
-
`[AccessControl] Agent "${actorId}" not found in agents or users table.`
|
|
565
|
-
);
|
|
566
|
-
}
|
|
567
|
-
const user2 = requireServiceAgentUser(
|
|
568
|
-
resolvedUser2,
|
|
569
|
-
actorId
|
|
570
|
-
);
|
|
571
|
-
console.warn(
|
|
572
|
-
`[AccessControl] Deprecated legacy service-agent fallback for ${actorId}; migrate this principal into identity.agents.`
|
|
573
|
-
);
|
|
574
|
-
return {
|
|
575
|
-
principalId: user2.defaultPrincipalId,
|
|
576
|
-
principalType: "service",
|
|
577
|
-
clerkId: actorId,
|
|
578
|
-
tenantId: user2.defaultTenantId,
|
|
579
|
-
workspaceId: user2.defaultWorkspaceId,
|
|
580
|
-
roles: ["service_agent"],
|
|
581
|
-
groupIds: normalizeGroupIds(resolvedUser2?.principalGroupIds),
|
|
582
|
-
isPlatformAdmin: false,
|
|
583
|
-
isTenantAdmin: false,
|
|
584
|
-
isWorkspaceAdmin: false,
|
|
585
|
-
isSystemFallback: false
|
|
586
|
-
};
|
|
587
|
-
}
|
|
588
|
-
const {
|
|
589
|
-
resolvedUser,
|
|
590
|
-
contextClerkId
|
|
591
|
-
} = await resolveCanonicalUserRecord(ctx, actorId);
|
|
592
|
-
const user = requireCanonicalResolvedUser(
|
|
593
|
-
resolvedUser,
|
|
594
|
-
contextClerkId
|
|
595
|
-
);
|
|
596
|
-
if (!user.defaultPrincipalId) {
|
|
597
|
-
throw new Error(
|
|
598
|
-
`[AccessControl] Canonical federated principal required for ${contextClerkId}. Re-sync Master Control identity before user-bound access checks.`
|
|
599
|
-
);
|
|
600
|
-
}
|
|
601
|
-
if (user.mcRole === "service_agent") {
|
|
602
|
-
return {
|
|
603
|
-
principalId: user.defaultPrincipalId,
|
|
604
|
-
principalType: "service",
|
|
605
|
-
clerkId: contextClerkId,
|
|
606
|
-
tenantId: user.defaultTenantId,
|
|
607
|
-
workspaceId: user.defaultWorkspaceId,
|
|
608
|
-
roles: ["service_agent"],
|
|
609
|
-
groupIds: normalizeGroupIds(resolvedUser?.principalGroupIds),
|
|
610
|
-
isPlatformAdmin: false,
|
|
611
|
-
isTenantAdmin: false,
|
|
612
|
-
isWorkspaceAdmin: false,
|
|
613
|
-
isSystemFallback: false
|
|
614
|
-
};
|
|
615
|
-
}
|
|
616
|
-
const principalId = user.defaultPrincipalId;
|
|
617
|
-
const effectiveRole = user.mcRole;
|
|
618
|
-
const roles = effectiveRole === "platform_admin" ? ["platform_admin", "tenant_admin"] : effectiveRole === "tenant_admin" ? ["tenant_admin"] : [effectiveRole];
|
|
619
|
-
const tenantId = user.defaultTenantId;
|
|
620
|
-
const workspaceId = user.defaultWorkspaceId;
|
|
621
|
-
const isPlatformAdmin = effectiveRole === "platform_admin";
|
|
622
|
-
return {
|
|
623
|
-
principalId,
|
|
624
|
-
principalType: "user",
|
|
625
|
-
clerkId: contextClerkId,
|
|
626
|
-
tenantId,
|
|
627
|
-
workspaceId,
|
|
628
|
-
roles: uniqRoles(roles),
|
|
629
|
-
groupIds: normalizeGroupIds(resolvedUser?.principalGroupIds),
|
|
630
|
-
isPlatformAdmin,
|
|
631
|
-
isTenantAdmin: isPlatformAdmin || effectiveRole === "tenant_admin",
|
|
632
|
-
isWorkspaceAdmin: isPlatformAdmin || effectiveRole === "tenant_admin" || effectiveRole === "workspace_admin",
|
|
633
|
-
isSystemFallback: false
|
|
634
|
-
};
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
// ../access-control/src/access.ts
|
|
638
|
-
function isTopicInPrincipalTenant(topic, principalTenantId) {
|
|
639
|
-
if (!topic.tenantId) {
|
|
640
|
-
return false;
|
|
641
|
-
}
|
|
642
|
-
if (!principalTenantId) {
|
|
643
|
-
return false;
|
|
644
|
-
}
|
|
645
|
-
return String(topic.tenantId) === String(principalTenantId);
|
|
646
|
-
}
|
|
647
|
-
function isTopicInPrincipalWorkspace(topic, principalWorkspaceId) {
|
|
648
|
-
if (!topic.workspaceId) {
|
|
649
|
-
return false;
|
|
650
|
-
}
|
|
651
|
-
if (!principalWorkspaceId) {
|
|
652
|
-
return false;
|
|
653
|
-
}
|
|
654
|
-
return String(topic.workspaceId) === String(principalWorkspaceId);
|
|
655
|
-
}
|
|
656
|
-
function isLegacyUnscopedTopic(topic) {
|
|
657
|
-
return !topic.tenantId || !topic.workspaceId;
|
|
658
|
-
}
|
|
659
|
-
function isGrantScopeAlignedToTopic(topic, grant) {
|
|
660
|
-
if (topic.tenantId && grant.tenantId && String(topic.tenantId) !== String(grant.tenantId)) {
|
|
661
|
-
return false;
|
|
662
|
-
}
|
|
663
|
-
if (topic.workspaceId && grant.workspaceId && String(topic.workspaceId) !== String(grant.workspaceId)) {
|
|
664
|
-
return false;
|
|
665
|
-
}
|
|
666
|
-
return true;
|
|
667
|
-
}
|
|
668
|
-
function isGrantSourceAllowedForVisibility(visibility, source) {
|
|
669
|
-
if (source !== "external_share") {
|
|
670
|
-
return true;
|
|
671
|
-
}
|
|
672
|
-
return visibility === "external" || visibility === "public";
|
|
673
|
-
}
|
|
674
|
-
function isGrantActive(grant) {
|
|
675
|
-
if (grant.status !== "active") {
|
|
676
|
-
return false;
|
|
677
|
-
}
|
|
678
|
-
if (grant.expiresAt !== void 0 && grant.expiresAt <= Date.now()) {
|
|
679
|
-
return false;
|
|
680
|
-
}
|
|
681
|
-
return true;
|
|
682
|
-
}
|
|
683
|
-
async function hasPrincipalGrant(ctx, args) {
|
|
684
|
-
const grants = await resolveAccessControlAppResolvers().listProjectGrantsByProjectAndPrincipal(
|
|
685
|
-
ctx,
|
|
686
|
-
args.topic._id,
|
|
687
|
-
args.principalId
|
|
688
|
-
);
|
|
689
|
-
if (grants.some(
|
|
690
|
-
(grant) => isGrantActive(grant) && isGrantScopeAlignedToTopic(args.topic, grant) && isGrantSourceAllowedForVisibility(
|
|
691
|
-
args.topic.visibility,
|
|
692
|
-
grant.source
|
|
693
|
-
) && (!args.principalIsExternal || args.topic.visibility === "public" || grant.source === "external_share")
|
|
694
|
-
)) {
|
|
695
|
-
return true;
|
|
696
|
-
}
|
|
697
|
-
return false;
|
|
698
|
-
}
|
|
699
|
-
async function hasGroupGrant(ctx, args) {
|
|
700
|
-
if (args.groupIds.length === 0) {
|
|
701
|
-
return false;
|
|
702
|
-
}
|
|
703
|
-
for (const groupId of args.groupIds) {
|
|
704
|
-
const grants = await resolveAccessControlAppResolvers().listProjectGrantsByProjectAndGroup(ctx, args.topic._id, groupId);
|
|
705
|
-
if (grants.some(
|
|
706
|
-
(grant) => isGrantActive(grant) && isGrantScopeAlignedToTopic(args.topic, grant) && isGrantSourceAllowedForVisibility(
|
|
707
|
-
args.topic.visibility,
|
|
708
|
-
grant.source
|
|
709
|
-
)
|
|
710
|
-
)) {
|
|
711
|
-
return true;
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
return false;
|
|
715
|
-
}
|
|
716
|
-
function isExternalPrincipal(_ctx, _args) {
|
|
717
|
-
return false;
|
|
718
|
-
}
|
|
719
|
-
async function evaluateTopicAccessDetailed(ctx, args) {
|
|
720
|
-
if (args.legacyUserId) {
|
|
721
|
-
return {
|
|
722
|
-
hasAccess: true,
|
|
723
|
-
isAdmin: false,
|
|
724
|
-
isOwner: false,
|
|
725
|
-
isShared: false,
|
|
726
|
-
hasGrant: true,
|
|
727
|
-
isFirmVisible: true,
|
|
728
|
-
isExternalVisible: false,
|
|
729
|
-
isPublicVisible: false,
|
|
730
|
-
isTenantScopeMatch: true,
|
|
731
|
-
isWorkspaceScopeMatch: true,
|
|
732
|
-
isPrincipalExternal: false
|
|
733
|
-
};
|
|
734
|
-
}
|
|
735
|
-
const topic = await resolveAccessControlAppResolvers().getProject(
|
|
736
|
-
ctx,
|
|
737
|
-
args.topicId
|
|
738
|
-
);
|
|
739
|
-
if (!topic) {
|
|
740
|
-
return {
|
|
741
|
-
hasAccess: false,
|
|
742
|
-
isAdmin: false,
|
|
743
|
-
isOwner: false,
|
|
744
|
-
isShared: false,
|
|
745
|
-
hasGrant: false,
|
|
746
|
-
isFirmVisible: false,
|
|
747
|
-
isExternalVisible: false,
|
|
748
|
-
isPublicVisible: false,
|
|
749
|
-
isTenantScopeMatch: false,
|
|
750
|
-
isWorkspaceScopeMatch: false,
|
|
751
|
-
isPrincipalExternal: false
|
|
752
|
-
};
|
|
753
|
-
}
|
|
754
|
-
const { principalContext, legacyUserId } = args;
|
|
755
|
-
const userIsAdmin = principalContext.isPlatformAdmin;
|
|
756
|
-
const isOwner = topic.ownerId === legacyUserId;
|
|
757
|
-
const isShared = (topic.sharedWith ?? []).includes(legacyUserId);
|
|
758
|
-
const principalIsExternal = await isExternalPrincipal(ctx, {
|
|
759
|
-
groupIds: principalContext.groupIds,
|
|
760
|
-
topicTenantId: topic.tenantId,
|
|
761
|
-
topicWorkspaceId: topic.workspaceId
|
|
762
|
-
});
|
|
763
|
-
const hasPrincipalGrantResult = await hasPrincipalGrant(ctx, {
|
|
764
|
-
topic,
|
|
765
|
-
principalId: principalContext.principalId,
|
|
766
|
-
principalIsExternal
|
|
767
|
-
});
|
|
768
|
-
const hasGroupGrantResult = await hasGroupGrant(ctx, {
|
|
769
|
-
topic,
|
|
770
|
-
groupIds: principalContext.groupIds
|
|
771
|
-
});
|
|
772
|
-
const hasGrant = isShared || hasPrincipalGrantResult || hasGroupGrantResult;
|
|
773
|
-
const legacyUnscoped = isLegacyUnscopedTopic(topic);
|
|
774
|
-
const tenantScopeMatch = isTopicInPrincipalTenant(
|
|
775
|
-
topic,
|
|
776
|
-
principalContext.tenantId
|
|
777
|
-
);
|
|
778
|
-
const workspaceScopeMatch = isTopicInPrincipalWorkspace(
|
|
779
|
-
topic,
|
|
780
|
-
principalContext.workspaceId
|
|
781
|
-
);
|
|
782
|
-
const isPublicVisible = topic.visibility === "public";
|
|
783
|
-
const isFirmVisible = topic.visibility === "firm" && !legacyUnscoped && tenantScopeMatch && workspaceScopeMatch && !principalIsExternal;
|
|
784
|
-
const hasScopedGrant = hasGrant && (legacyUnscoped || tenantScopeMatch && workspaceScopeMatch);
|
|
785
|
-
const isExternalVisible = topic.visibility === "external" && hasScopedGrant;
|
|
786
|
-
const hasAccess = userIsAdmin || isOwner || hasScopedGrant || isPublicVisible || isFirmVisible;
|
|
787
|
-
return {
|
|
788
|
-
hasAccess,
|
|
789
|
-
isAdmin: userIsAdmin,
|
|
790
|
-
isOwner,
|
|
791
|
-
isShared,
|
|
792
|
-
hasGrant,
|
|
793
|
-
isFirmVisible,
|
|
794
|
-
isExternalVisible,
|
|
795
|
-
isPublicVisible,
|
|
796
|
-
isTenantScopeMatch: tenantScopeMatch,
|
|
797
|
-
isWorkspaceScopeMatch: workspaceScopeMatch,
|
|
798
|
-
isPrincipalExternal: principalIsExternal
|
|
799
|
-
};
|
|
800
|
-
}
|
|
801
|
-
async function checkTopicAccessDetailed(ctx, topicId, userId) {
|
|
802
|
-
const principalContext = await resolvePrincipalContext(ctx, userId);
|
|
803
|
-
return evaluateTopicAccessDetailed(ctx, {
|
|
804
|
-
topicId,
|
|
805
|
-
legacyUserId: userId,
|
|
806
|
-
principalContext
|
|
807
|
-
});
|
|
808
|
-
}
|
|
809
|
-
async function checkTopicAccess(ctx, topicId, userId) {
|
|
810
|
-
const result = await checkTopicAccessDetailed(ctx, topicId, userId);
|
|
811
|
-
return result.hasAccess;
|
|
812
|
-
}
|
|
813
|
-
var checkProjectAccess = checkTopicAccess;
|
|
814
|
-
var LEGACY_SCOPE_FIELD2 = "graphScopeProjectId";
|
|
57
|
+
// src/topicScope.ts
|
|
58
|
+
var LEGACY_SCOPE_FIELD = "graphScopeProjectId";
|
|
815
59
|
function asMappedProjectId(topic) {
|
|
816
60
|
if (!topic) {
|
|
817
61
|
return;
|
|
818
62
|
}
|
|
819
|
-
const directLegacyProjectId = normalizeScopeValue(topic[
|
|
63
|
+
const directLegacyProjectId = normalizeScopeValue(topic[LEGACY_SCOPE_FIELD]);
|
|
820
64
|
if (directLegacyProjectId) {
|
|
821
65
|
return directLegacyProjectId;
|
|
822
66
|
}
|
|
823
67
|
const metadata = topic.metadata || {};
|
|
824
|
-
const candidate = metadata[
|
|
68
|
+
const candidate = metadata[LEGACY_SCOPE_FIELD] || metadata.legacyProjectId || metadata.projectId || metadata.scopeProjectId;
|
|
825
69
|
return candidate ? candidate : void 0;
|
|
826
70
|
}
|
|
827
71
|
function normalizeScopeValue(value) {
|
|
@@ -850,9 +94,16 @@ async function findTopicsByScopeAlias(ctx, scopeId) {
|
|
|
850
94
|
try {
|
|
851
95
|
return await ctx.db.query("topics").withIndex(
|
|
852
96
|
"by_graph_scope_project",
|
|
853
|
-
(q) => q.eq(
|
|
97
|
+
(q) => q.eq(LEGACY_SCOPE_FIELD, scopeId)
|
|
854
98
|
).collect();
|
|
855
|
-
} catch {
|
|
99
|
+
} catch (error) {
|
|
100
|
+
debugGraphPrimitiveFallback(
|
|
101
|
+
"[topicScope] Failed to resolve scope alias via index",
|
|
102
|
+
{
|
|
103
|
+
error,
|
|
104
|
+
scopeId
|
|
105
|
+
}
|
|
106
|
+
);
|
|
856
107
|
const topics = await ctx.db.query("topics").collect();
|
|
857
108
|
return topics.filter((topic) => {
|
|
858
109
|
const normalizedGlobalId = normalizeScopeValue(topic.globalId);
|
|
@@ -869,7 +120,14 @@ async function tryResolveHostTopicById(ctx, topicId) {
|
|
|
869
120
|
return await ctx.runQuery(api.topics.get, {
|
|
870
121
|
id: topicId
|
|
871
122
|
}) ?? null;
|
|
872
|
-
} catch {
|
|
123
|
+
} catch (error) {
|
|
124
|
+
debugGraphPrimitiveFallback(
|
|
125
|
+
"[topicScope] Failed to resolve topic by host query",
|
|
126
|
+
{
|
|
127
|
+
error,
|
|
128
|
+
topicId
|
|
129
|
+
}
|
|
130
|
+
);
|
|
873
131
|
return null;
|
|
874
132
|
}
|
|
875
133
|
}
|
|
@@ -881,7 +139,14 @@ async function tryResolveHostTopicByLegacyScope(ctx, legacyScopeId) {
|
|
|
881
139
|
return await ctx.runQuery(api.topics.getByLegacyScopeId, {
|
|
882
140
|
projectId: legacyScopeId
|
|
883
141
|
}) ?? null;
|
|
884
|
-
} catch {
|
|
142
|
+
} catch (error) {
|
|
143
|
+
debugGraphPrimitiveFallback(
|
|
144
|
+
"[topicScope] Failed to resolve topic by legacy scope",
|
|
145
|
+
{
|
|
146
|
+
error,
|
|
147
|
+
legacyScopeId
|
|
148
|
+
}
|
|
149
|
+
);
|
|
885
150
|
return null;
|
|
886
151
|
}
|
|
887
152
|
}
|
|
@@ -910,8 +175,17 @@ async function resolveTopicProjectScope(ctx, args) {
|
|
|
910
175
|
if (args.topicId) {
|
|
911
176
|
let topic = null;
|
|
912
177
|
try {
|
|
913
|
-
topic = await ctx.db.get(
|
|
914
|
-
|
|
178
|
+
topic = await ctx.db.get(
|
|
179
|
+
args.topicId
|
|
180
|
+
);
|
|
181
|
+
} catch (error) {
|
|
182
|
+
debugGraphPrimitiveFallback(
|
|
183
|
+
"[topicScope] Failed to load topic by direct id",
|
|
184
|
+
{
|
|
185
|
+
error,
|
|
186
|
+
topicId: args.topicId
|
|
187
|
+
}
|
|
188
|
+
);
|
|
915
189
|
}
|
|
916
190
|
if (!topic) {
|
|
917
191
|
topic = await tryResolveHostTopicById(ctx, String(args.topicId));
|
|
@@ -948,7 +222,14 @@ async function resolveTopicProjectScope(ctx, args) {
|
|
|
948
222
|
directTopic = await ctx.db.get(
|
|
949
223
|
args.projectId
|
|
950
224
|
);
|
|
951
|
-
} catch {
|
|
225
|
+
} catch (error) {
|
|
226
|
+
debugGraphPrimitiveFallback(
|
|
227
|
+
"[topicScope] Failed to load direct project topic",
|
|
228
|
+
{
|
|
229
|
+
error,
|
|
230
|
+
projectId: args.projectId
|
|
231
|
+
}
|
|
232
|
+
);
|
|
952
233
|
}
|
|
953
234
|
if (directTopic) {
|
|
954
235
|
const inherited = await resolveInheritedWorkspaceScope(ctx, directTopic);
|
|
@@ -1018,7 +299,15 @@ async function resolveScope(ctx, args) {
|
|
|
1018
299
|
topicId,
|
|
1019
300
|
projectId
|
|
1020
301
|
});
|
|
1021
|
-
} catch {
|
|
302
|
+
} catch (error) {
|
|
303
|
+
debugGraphPrimitiveFallback(
|
|
304
|
+
"[scopeResolverCompat] Failed to resolve scope",
|
|
305
|
+
{
|
|
306
|
+
error,
|
|
307
|
+
topicId,
|
|
308
|
+
projectId
|
|
309
|
+
}
|
|
310
|
+
);
|
|
1022
311
|
return null;
|
|
1023
312
|
}
|
|
1024
313
|
}
|
|
@@ -1041,76 +330,83 @@ async function resolveScopeSoft(ctx, args) {
|
|
|
1041
330
|
...projectId ? { projectId } : {}
|
|
1042
331
|
};
|
|
1043
332
|
}
|
|
1044
|
-
var permissiveReturn = v.optional(v.any());
|
|
1045
|
-
var looseJsonObject = v.record(v.string(), v.any());
|
|
1046
|
-
var looseJsonArray = v.array(v.any());
|
|
1047
|
-
v.union(
|
|
1048
|
-
v.string(),
|
|
1049
|
-
v.number(),
|
|
1050
|
-
v.boolean(),
|
|
1051
|
-
v.null(),
|
|
1052
|
-
looseJsonObject,
|
|
1053
|
-
looseJsonArray
|
|
1054
|
-
);
|
|
1055
333
|
|
|
1056
334
|
// src/topicProjectOverlay.ts
|
|
1057
|
-
var
|
|
1058
|
-
function
|
|
335
|
+
var LEGACY_SCOPE_FIELD2 = "graphScopeProjectId";
|
|
336
|
+
function readNonEmptyString(value) {
|
|
1059
337
|
if (typeof value !== "string") {
|
|
1060
338
|
return;
|
|
1061
339
|
}
|
|
1062
340
|
const normalized = value.trim();
|
|
1063
341
|
return normalized.length > 0 ? normalized : void 0;
|
|
1064
342
|
}
|
|
1065
|
-
function
|
|
343
|
+
function readStringArray(value) {
|
|
1066
344
|
if (!Array.isArray(value)) {
|
|
1067
345
|
return [];
|
|
1068
346
|
}
|
|
1069
|
-
return value.map((entry) =>
|
|
347
|
+
return value.map((entry) => readNonEmptyString(entry)).filter((entry) => Boolean(entry));
|
|
1070
348
|
}
|
|
1071
|
-
function
|
|
349
|
+
function readMetadata(topic) {
|
|
1072
350
|
return topic.metadata && typeof topic.metadata === "object" ? topic.metadata : {};
|
|
1073
351
|
}
|
|
1074
|
-
function
|
|
352
|
+
function readLegacyProjectId(value) {
|
|
1075
353
|
if (!value) {
|
|
1076
354
|
return;
|
|
1077
355
|
}
|
|
1078
|
-
return
|
|
356
|
+
return readNonEmptyString(value[LEGACY_SCOPE_FIELD2]);
|
|
1079
357
|
}
|
|
1080
|
-
function
|
|
358
|
+
function coerceVisibility(value) {
|
|
1081
359
|
return value === "private" || value === "team" || value === "firm" || value === "external" || value === "public" ? value : void 0;
|
|
1082
360
|
}
|
|
1083
|
-
function
|
|
361
|
+
function coerceStatus(value) {
|
|
1084
362
|
return value === "active" || value === "archived" || value === "watching" ? value : void 0;
|
|
1085
363
|
}
|
|
1086
|
-
function
|
|
1087
|
-
const explicit =
|
|
364
|
+
function mapProjectType(topic, metadata) {
|
|
365
|
+
const explicit = readNonEmptyString(metadata.projectType);
|
|
1088
366
|
if (explicit) {
|
|
1089
367
|
return explicit;
|
|
1090
368
|
}
|
|
1091
369
|
if (topic.type === "theme") {
|
|
1092
370
|
return "thematic";
|
|
1093
371
|
}
|
|
1094
|
-
return
|
|
372
|
+
return readNonEmptyString(topic.type) || "general";
|
|
1095
373
|
}
|
|
1096
|
-
function
|
|
1097
|
-
const metadata =
|
|
1098
|
-
return topic.type === "theme" || topic.type === "thematic" || topic.type === "deal" || topic.type === "monitoring" ||
|
|
374
|
+
function isProjectLikeTopic(topic) {
|
|
375
|
+
const metadata = readMetadata(topic);
|
|
376
|
+
return topic.type === "theme" || topic.type === "thematic" || topic.type === "deal" || topic.type === "monitoring" || readLegacyProjectId(topic) !== void 0 || readNonEmptyString(metadata.projectType) !== void 0;
|
|
1099
377
|
}
|
|
1100
378
|
function isMissingLucernChildComponentError(error) {
|
|
1101
|
-
const message =
|
|
379
|
+
const message = getErrorMessage(error);
|
|
1102
380
|
return message.includes(
|
|
1103
381
|
'Child component ComponentName(Identifier("lucern")) not found'
|
|
1104
382
|
) || message.includes("Child component") && message.includes("lucern") && message.includes("not found");
|
|
1105
383
|
}
|
|
1106
|
-
|
|
384
|
+
function getErrorMessage(error) {
|
|
385
|
+
if (error instanceof Error) {
|
|
386
|
+
return error.message;
|
|
387
|
+
}
|
|
388
|
+
if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") {
|
|
389
|
+
return error.message;
|
|
390
|
+
}
|
|
391
|
+
return "unknown error";
|
|
392
|
+
}
|
|
393
|
+
async function resolveTopicDoc(ctx, scopeId) {
|
|
1107
394
|
if (ctx?.db && typeof ctx.db.get === "function") {
|
|
1108
395
|
try {
|
|
1109
|
-
const directTopic = await ctx.db.get(
|
|
396
|
+
const directTopic = await ctx.db.get(
|
|
397
|
+
scopeId
|
|
398
|
+
);
|
|
1110
399
|
if (directTopic) {
|
|
1111
400
|
return directTopic;
|
|
1112
401
|
}
|
|
1113
|
-
} catch {
|
|
402
|
+
} catch (error) {
|
|
403
|
+
debugGraphPrimitiveFallback(
|
|
404
|
+
"[topicProjectOverlay] Failed to resolve topic by direct ID",
|
|
405
|
+
{
|
|
406
|
+
error,
|
|
407
|
+
scopeId
|
|
408
|
+
}
|
|
409
|
+
);
|
|
1114
410
|
}
|
|
1115
411
|
}
|
|
1116
412
|
if (typeof ctx.runQuery !== "function") {
|
|
@@ -1123,7 +419,14 @@ async function resolveTopicDoc2(ctx, scopeId) {
|
|
|
1123
419
|
if (topic?.name !== void 0 && topic?.type !== void 0) {
|
|
1124
420
|
return topic;
|
|
1125
421
|
}
|
|
1126
|
-
} catch {
|
|
422
|
+
} catch (error) {
|
|
423
|
+
debugGraphPrimitiveFallback(
|
|
424
|
+
"[topicProjectOverlay] Failed to resolve topic by ID query",
|
|
425
|
+
{
|
|
426
|
+
error,
|
|
427
|
+
scopeId
|
|
428
|
+
}
|
|
429
|
+
);
|
|
1127
430
|
}
|
|
1128
431
|
try {
|
|
1129
432
|
const topic = await ctx.runQuery(api.topics.getByLegacyScopeId, {
|
|
@@ -1132,18 +435,22 @@ async function resolveTopicDoc2(ctx, scopeId) {
|
|
|
1132
435
|
if (topic?.name !== void 0 && topic?.type !== void 0) {
|
|
1133
436
|
return topic;
|
|
1134
437
|
}
|
|
1135
|
-
} catch {
|
|
438
|
+
} catch (error) {
|
|
439
|
+
debugGraphPrimitiveFallback(
|
|
440
|
+
"[topicProjectOverlay] Failed to resolve topic by legacy scope ID",
|
|
441
|
+
{ error, scopeId }
|
|
442
|
+
);
|
|
1136
443
|
}
|
|
1137
444
|
return null;
|
|
1138
445
|
}
|
|
1139
|
-
function
|
|
1140
|
-
const metadata =
|
|
446
|
+
function materializeTopicProjectOverlay(topic, idMode = "legacy") {
|
|
447
|
+
const metadata = readMetadata(topic);
|
|
1141
448
|
const topicId = String(topic._id);
|
|
1142
|
-
const legacyProjectId =
|
|
449
|
+
const legacyProjectId = readLegacyProjectId(topic) || readLegacyProjectId(metadata) || readNonEmptyString(metadata.legacyProjectId);
|
|
1143
450
|
const storageProjectId = legacyProjectId || topicId;
|
|
1144
451
|
const outwardId = idMode === "topic" ? topicId : storageProjectId;
|
|
1145
|
-
const visibility =
|
|
1146
|
-
const status =
|
|
452
|
+
const visibility = coerceVisibility(topic.visibility) || coerceVisibility(metadata.visibility) || "private";
|
|
453
|
+
const status = coerceStatus(topic.status) || coerceStatus(metadata.status) || "active";
|
|
1147
454
|
const createdAt = typeof topic.createdAt === "number" ? topic.createdAt : typeof topic._creationTime === "number" ? topic._creationTime : 0;
|
|
1148
455
|
const updatedAt = typeof topic.updatedAt === "number" ? topic.updatedAt : typeof metadata.updatedAt === "number" ? metadata.updatedAt : createdAt;
|
|
1149
456
|
return {
|
|
@@ -1153,16 +460,16 @@ function materializeTopicProjectOverlay2(topic, idMode = "legacy") {
|
|
|
1153
460
|
topicId,
|
|
1154
461
|
storageProjectId,
|
|
1155
462
|
legacyProjectId,
|
|
1156
|
-
name:
|
|
1157
|
-
type:
|
|
1158
|
-
description:
|
|
1159
|
-
ownerId:
|
|
1160
|
-
sharedWith:
|
|
463
|
+
name: readNonEmptyString(topic.name) || "Untitled Theme",
|
|
464
|
+
type: mapProjectType(topic, metadata),
|
|
465
|
+
description: readNonEmptyString(topic.description),
|
|
466
|
+
ownerId: readNonEmptyString(metadata.ownerId) || readNonEmptyString(topic.createdBy) || "system",
|
|
467
|
+
sharedWith: readStringArray(metadata.sharedWith),
|
|
1161
468
|
visibility,
|
|
1162
|
-
tenantId:
|
|
1163
|
-
workspaceId:
|
|
469
|
+
tenantId: readNonEmptyString(topic.tenantId) || readNonEmptyString(metadata.tenantId),
|
|
470
|
+
workspaceId: readNonEmptyString(topic.workspaceId) || readNonEmptyString(metadata.workspaceId),
|
|
1164
471
|
status,
|
|
1165
|
-
tags:
|
|
472
|
+
tags: readStringArray(metadata.tags),
|
|
1166
473
|
chatCount: typeof metadata.chatCount === "number" ? metadata.chatCount : 0,
|
|
1167
474
|
artifactCount: typeof metadata.artifactCount === "number" ? metadata.artifactCount : 0,
|
|
1168
475
|
lastActivityAt: typeof metadata.lastActivityAt === "number" ? metadata.lastActivityAt : updatedAt,
|
|
@@ -1171,22 +478,26 @@ function materializeTopicProjectOverlay2(topic, idMode = "legacy") {
|
|
|
1171
478
|
updatedAt
|
|
1172
479
|
};
|
|
1173
480
|
}
|
|
1174
|
-
async function
|
|
1175
|
-
const topic = await
|
|
481
|
+
async function resolveTopicProjectOverlay(ctx, scopeId, options = {}) {
|
|
482
|
+
const topic = await resolveTopicDoc(ctx, scopeId);
|
|
1176
483
|
if (!topic) {
|
|
1177
484
|
return null;
|
|
1178
485
|
}
|
|
1179
|
-
if (options.projectLikeOnly !== false && !
|
|
486
|
+
if (options.projectLikeOnly !== false && !isProjectLikeTopic(topic)) {
|
|
1180
487
|
return null;
|
|
1181
488
|
}
|
|
1182
|
-
return
|
|
489
|
+
return materializeTopicProjectOverlay(topic, options.idMode);
|
|
1183
490
|
}
|
|
1184
|
-
async function
|
|
491
|
+
async function listTopicProjectOverlays(ctx, options = {}) {
|
|
1185
492
|
let allTopics = [];
|
|
1186
493
|
if (ctx?.db?.query && typeof ctx.db.query === "function") {
|
|
1187
494
|
try {
|
|
1188
495
|
allTopics = await ctx.db.query("topics").collect();
|
|
1189
|
-
} catch {
|
|
496
|
+
} catch (error) {
|
|
497
|
+
debugGraphPrimitiveFallback(
|
|
498
|
+
"[topicProjectOverlay] Failed to read topics table; falling back to API",
|
|
499
|
+
{ error }
|
|
500
|
+
);
|
|
1190
501
|
allTopics = [];
|
|
1191
502
|
}
|
|
1192
503
|
}
|
|
@@ -1194,15 +505,15 @@ async function listTopicProjectOverlays2(ctx, options = {}) {
|
|
|
1194
505
|
allTopics = (await ctx.runQuery(api.topics.list, {}) ?? []) || [];
|
|
1195
506
|
}
|
|
1196
507
|
return allTopics.filter(
|
|
1197
|
-
(topic) => options.projectLikeOnly === false ||
|
|
1198
|
-
).map((topic) =>
|
|
508
|
+
(topic) => options.projectLikeOnly === false || isProjectLikeTopic(topic)
|
|
509
|
+
).map((topic) => materializeTopicProjectOverlay(topic, options.idMode));
|
|
1199
510
|
}
|
|
1200
511
|
async function patchTopicProjectOverlay(ctx, scopeId, value) {
|
|
1201
|
-
const topic = await
|
|
512
|
+
const topic = await resolveTopicDoc(ctx, scopeId);
|
|
1202
513
|
if (!topic) {
|
|
1203
514
|
return null;
|
|
1204
515
|
}
|
|
1205
|
-
const nextMetadata = { ...
|
|
516
|
+
const nextMetadata = { ...readMetadata(topic) };
|
|
1206
517
|
const patch = {};
|
|
1207
518
|
const topicUpdateArgs = {
|
|
1208
519
|
id: String(topic._id)
|
|
@@ -1227,7 +538,7 @@ async function patchTopicProjectOverlay(ctx, scopeId, value) {
|
|
|
1227
538
|
`patchTopicProjectOverlay cannot mutate ${key} via component-owned topics`
|
|
1228
539
|
);
|
|
1229
540
|
case "status": {
|
|
1230
|
-
const status =
|
|
541
|
+
const status = coerceStatus(rawValue);
|
|
1231
542
|
if (status) {
|
|
1232
543
|
patch.status = status;
|
|
1233
544
|
topicUpdateArgs.status = status;
|
|
@@ -1235,7 +546,7 @@ async function patchTopicProjectOverlay(ctx, scopeId, value) {
|
|
|
1235
546
|
break;
|
|
1236
547
|
}
|
|
1237
548
|
case "visibility": {
|
|
1238
|
-
const visibility =
|
|
549
|
+
const visibility = coerceVisibility(rawValue);
|
|
1239
550
|
if (visibility) {
|
|
1240
551
|
patch.visibility = visibility;
|
|
1241
552
|
topicUpdateArgs.visibility = visibility;
|
|
@@ -1243,7 +554,7 @@ async function patchTopicProjectOverlay(ctx, scopeId, value) {
|
|
|
1243
554
|
break;
|
|
1244
555
|
}
|
|
1245
556
|
case "type": {
|
|
1246
|
-
const projectType =
|
|
557
|
+
const projectType = readNonEmptyString(rawValue);
|
|
1247
558
|
if (projectType) {
|
|
1248
559
|
nextMetadata.projectType = projectType;
|
|
1249
560
|
} else {
|
|
@@ -1281,19 +592,28 @@ async function patchTopicProjectOverlay(ctx, scopeId, value) {
|
|
|
1281
592
|
"Cannot patch topic without component adapter (ctx.runMutation unavailable)"
|
|
1282
593
|
);
|
|
1283
594
|
}
|
|
1284
|
-
return
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
}
|
|
1290
|
-
);
|
|
595
|
+
return materializeTopicProjectOverlay({
|
|
596
|
+
...topic,
|
|
597
|
+
...patch,
|
|
598
|
+
metadata: nextMetadata
|
|
599
|
+
});
|
|
1291
600
|
}
|
|
1292
601
|
|
|
1293
602
|
// src/resolvers.ts
|
|
1294
603
|
function isMissingLucernChildComponentError2(error) {
|
|
1295
|
-
const message =
|
|
1296
|
-
return message.includes(
|
|
604
|
+
const message = getErrorMessage2(error);
|
|
605
|
+
return message.includes(
|
|
606
|
+
'Child component ComponentName(Identifier("lucern")) not found'
|
|
607
|
+
) || message.includes("Child component") && message.includes("lucern") && message.includes("not found");
|
|
608
|
+
}
|
|
609
|
+
function getErrorMessage2(error) {
|
|
610
|
+
if (error instanceof Error) {
|
|
611
|
+
return error.message;
|
|
612
|
+
}
|
|
613
|
+
if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") {
|
|
614
|
+
return error.message;
|
|
615
|
+
}
|
|
616
|
+
return "unknown error";
|
|
1297
617
|
}
|
|
1298
618
|
function isAdvisoryTopicPatch(value) {
|
|
1299
619
|
const advisoryKeys = /* @__PURE__ */ new Set(["lastActivityAt", "updatedAt"]);
|
|
@@ -1307,39 +627,34 @@ async function patchProjectWithTolerance(ctx, projectId, value) {
|
|
|
1307
627
|
if (!isAdvisoryTopicPatch(value) || !isMissingLucernChildComponentError2(error)) {
|
|
1308
628
|
throw error;
|
|
1309
629
|
}
|
|
1310
|
-
console.warn(
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
630
|
+
console.warn(
|
|
631
|
+
"[lucern graph-primitives] Non-fatal advisory topic patch failure",
|
|
632
|
+
{
|
|
633
|
+
projectId,
|
|
634
|
+
keys: Object.keys(value),
|
|
635
|
+
error: getErrorMessage2(error)
|
|
636
|
+
}
|
|
637
|
+
);
|
|
1315
638
|
}
|
|
1316
639
|
}
|
|
1317
|
-
function
|
|
640
|
+
function defaultResolvers() {
|
|
1318
641
|
return {
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
async listTopics(ctx) {
|
|
1329
|
-
return await listTopicProjectOverlays2(ctx, {
|
|
1330
|
-
idMode: "legacy"
|
|
1331
|
-
});
|
|
1332
|
-
},
|
|
1333
|
-
async getFinalArtifact(ctx, artifactId) {
|
|
1334
|
-
return await ctx.db.get(artifactId);
|
|
1335
|
-
}
|
|
642
|
+
getProject: (ctx, projectId) => resolveTopicProjectOverlay(ctx, projectId, {
|
|
643
|
+
idMode: "legacy",
|
|
644
|
+
projectLikeOnly: false
|
|
645
|
+
}),
|
|
646
|
+
patchProject: (ctx, projectId, value) => patchProjectWithTolerance(ctx, projectId, value),
|
|
647
|
+
listTopics: (ctx) => listTopicProjectOverlays(ctx, {
|
|
648
|
+
idMode: "legacy"
|
|
649
|
+
}),
|
|
650
|
+
getFinalArtifact: (ctx, artifactId) => ctx.db.get(artifactId)
|
|
1336
651
|
};
|
|
1337
652
|
}
|
|
1338
|
-
var
|
|
653
|
+
var resolverOverrides = {};
|
|
1339
654
|
function resolveGraphPrimitivesAppResolvers(_ctx) {
|
|
1340
655
|
return {
|
|
1341
|
-
...
|
|
1342
|
-
...
|
|
656
|
+
...defaultResolvers(),
|
|
657
|
+
...resolverOverrides
|
|
1343
658
|
};
|
|
1344
659
|
}
|
|
1345
660
|
|