@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,796 +1,27 @@
|
|
|
1
1
|
import { v } from 'convex/values';
|
|
2
|
-
import {
|
|
2
|
+
import { checkProjectAccess } from '@lucern/access-control/access';
|
|
3
|
+
import { permissiveReturn } from '@lucern/contracts/schema-helpers/validators';
|
|
4
|
+
import { componentsGeneric, mutationGeneric, anyApi, queryGeneric } from 'convex/server';
|
|
3
5
|
|
|
4
6
|
// src/questionEvidenceLinks.ts
|
|
5
7
|
var api = anyApi;
|
|
6
8
|
componentsGeneric();
|
|
9
|
+
var internal = anyApi;
|
|
10
|
+
var mutation = mutationGeneric;
|
|
11
|
+
var query = queryGeneric;
|
|
7
12
|
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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));
|
|
13
|
+
// src/debug.ts
|
|
14
|
+
function isGraphPrimitiveDebugEnabled() {
|
|
15
|
+
const env = globalThis.process?.env;
|
|
16
|
+
return env?.LUCERN_COMPAT_FALLBACK_DEBUG === "1" || env?.LUCERN_GRAPH_DEBUG === "1";
|
|
145
17
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
var PROJECT_GRANT_STATUSES = ["active", "revoked", "expired"];
|
|
149
|
-
function normalizeString(value) {
|
|
150
|
-
if (typeof value !== "string") {
|
|
18
|
+
function debugGraphPrimitiveFallback(message, context) {
|
|
19
|
+
if (!isGraphPrimitiveDebugEnabled()) {
|
|
151
20
|
return;
|
|
152
21
|
}
|
|
153
|
-
|
|
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
|
-
};
|
|
22
|
+
console.debug(message, context ?? {});
|
|
379
23
|
}
|
|
380
24
|
|
|
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
|
-
var permissiveReturn = v.optional(v.any());
|
|
778
|
-
var looseJsonObject = v.record(v.string(), v.any());
|
|
779
|
-
var looseJsonArray = v.array(v.any());
|
|
780
|
-
v.union(
|
|
781
|
-
v.string(),
|
|
782
|
-
v.number(),
|
|
783
|
-
v.boolean(),
|
|
784
|
-
v.null(),
|
|
785
|
-
looseJsonObject,
|
|
786
|
-
looseJsonArray
|
|
787
|
-
);
|
|
788
|
-
var api2 = anyApi;
|
|
789
|
-
componentsGeneric();
|
|
790
|
-
var internal = anyApi;
|
|
791
|
-
var mutation = mutationGeneric;
|
|
792
|
-
var query = queryGeneric;
|
|
793
|
-
|
|
794
25
|
// src/matcherFeedbackUtils.ts
|
|
795
26
|
function isOperationalLinkStatus(status) {
|
|
796
27
|
return status !== "suggested" && status !== "dismissed";
|
|
@@ -824,96 +55,125 @@ function deriveMatcherReviewStatus(input) {
|
|
|
824
55
|
}
|
|
825
56
|
|
|
826
57
|
// src/topicProjectOverlay.ts
|
|
827
|
-
var
|
|
828
|
-
function
|
|
58
|
+
var LEGACY_SCOPE_FIELD = "graphScopeProjectId";
|
|
59
|
+
function readNonEmptyString(value) {
|
|
829
60
|
if (typeof value !== "string") {
|
|
830
61
|
return;
|
|
831
62
|
}
|
|
832
63
|
const normalized = value.trim();
|
|
833
64
|
return normalized.length > 0 ? normalized : void 0;
|
|
834
65
|
}
|
|
835
|
-
function
|
|
66
|
+
function readStringArray(value) {
|
|
836
67
|
if (!Array.isArray(value)) {
|
|
837
68
|
return [];
|
|
838
69
|
}
|
|
839
|
-
return value.map((entry) =>
|
|
70
|
+
return value.map((entry) => readNonEmptyString(entry)).filter((entry) => Boolean(entry));
|
|
840
71
|
}
|
|
841
|
-
function
|
|
72
|
+
function readMetadata(topic) {
|
|
842
73
|
return topic.metadata && typeof topic.metadata === "object" ? topic.metadata : {};
|
|
843
74
|
}
|
|
844
|
-
function
|
|
75
|
+
function readLegacyProjectId(value) {
|
|
845
76
|
if (!value) {
|
|
846
77
|
return;
|
|
847
78
|
}
|
|
848
|
-
return
|
|
79
|
+
return readNonEmptyString(value[LEGACY_SCOPE_FIELD]);
|
|
849
80
|
}
|
|
850
|
-
function
|
|
81
|
+
function coerceVisibility(value) {
|
|
851
82
|
return value === "private" || value === "team" || value === "firm" || value === "external" || value === "public" ? value : void 0;
|
|
852
83
|
}
|
|
853
|
-
function
|
|
84
|
+
function coerceStatus(value) {
|
|
854
85
|
return value === "active" || value === "archived" || value === "watching" ? value : void 0;
|
|
855
86
|
}
|
|
856
|
-
function
|
|
857
|
-
const explicit =
|
|
87
|
+
function mapProjectType(topic, metadata) {
|
|
88
|
+
const explicit = readNonEmptyString(metadata.projectType);
|
|
858
89
|
if (explicit) {
|
|
859
90
|
return explicit;
|
|
860
91
|
}
|
|
861
92
|
if (topic.type === "theme") {
|
|
862
93
|
return "thematic";
|
|
863
94
|
}
|
|
864
|
-
return
|
|
95
|
+
return readNonEmptyString(topic.type) || "general";
|
|
865
96
|
}
|
|
866
|
-
function
|
|
867
|
-
const metadata =
|
|
868
|
-
return topic.type === "theme" || topic.type === "thematic" || topic.type === "deal" || topic.type === "monitoring" ||
|
|
97
|
+
function isProjectLikeTopic(topic) {
|
|
98
|
+
const metadata = readMetadata(topic);
|
|
99
|
+
return topic.type === "theme" || topic.type === "thematic" || topic.type === "deal" || topic.type === "monitoring" || readLegacyProjectId(topic) !== void 0 || readNonEmptyString(metadata.projectType) !== void 0;
|
|
869
100
|
}
|
|
870
101
|
function isMissingLucernChildComponentError(error) {
|
|
871
|
-
const message =
|
|
102
|
+
const message = getErrorMessage(error);
|
|
872
103
|
return message.includes(
|
|
873
104
|
'Child component ComponentName(Identifier("lucern")) not found'
|
|
874
105
|
) || message.includes("Child component") && message.includes("lucern") && message.includes("not found");
|
|
875
106
|
}
|
|
876
|
-
|
|
107
|
+
function getErrorMessage(error) {
|
|
108
|
+
if (error instanceof Error) {
|
|
109
|
+
return error.message;
|
|
110
|
+
}
|
|
111
|
+
if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") {
|
|
112
|
+
return error.message;
|
|
113
|
+
}
|
|
114
|
+
return "unknown error";
|
|
115
|
+
}
|
|
116
|
+
async function resolveTopicDoc(ctx, scopeId) {
|
|
877
117
|
if (ctx?.db && typeof ctx.db.get === "function") {
|
|
878
118
|
try {
|
|
879
|
-
const directTopic = await ctx.db.get(
|
|
119
|
+
const directTopic = await ctx.db.get(
|
|
120
|
+
scopeId
|
|
121
|
+
);
|
|
880
122
|
if (directTopic) {
|
|
881
123
|
return directTopic;
|
|
882
124
|
}
|
|
883
|
-
} catch {
|
|
125
|
+
} catch (error) {
|
|
126
|
+
debugGraphPrimitiveFallback(
|
|
127
|
+
"[topicProjectOverlay] Failed to resolve topic by direct ID",
|
|
128
|
+
{
|
|
129
|
+
error,
|
|
130
|
+
scopeId
|
|
131
|
+
}
|
|
132
|
+
);
|
|
884
133
|
}
|
|
885
134
|
}
|
|
886
135
|
if (typeof ctx.runQuery !== "function") {
|
|
887
136
|
return null;
|
|
888
137
|
}
|
|
889
138
|
try {
|
|
890
|
-
const topic = await ctx.runQuery(
|
|
139
|
+
const topic = await ctx.runQuery(api.topics.get, {
|
|
891
140
|
id: String(scopeId)
|
|
892
141
|
});
|
|
893
142
|
if (topic?.name !== void 0 && topic?.type !== void 0) {
|
|
894
143
|
return topic;
|
|
895
144
|
}
|
|
896
|
-
} catch {
|
|
145
|
+
} catch (error) {
|
|
146
|
+
debugGraphPrimitiveFallback(
|
|
147
|
+
"[topicProjectOverlay] Failed to resolve topic by ID query",
|
|
148
|
+
{
|
|
149
|
+
error,
|
|
150
|
+
scopeId
|
|
151
|
+
}
|
|
152
|
+
);
|
|
897
153
|
}
|
|
898
154
|
try {
|
|
899
|
-
const topic = await ctx.runQuery(
|
|
155
|
+
const topic = await ctx.runQuery(api.topics.getByLegacyScopeId, {
|
|
900
156
|
projectId: String(scopeId)
|
|
901
157
|
});
|
|
902
158
|
if (topic?.name !== void 0 && topic?.type !== void 0) {
|
|
903
159
|
return topic;
|
|
904
160
|
}
|
|
905
|
-
} catch {
|
|
161
|
+
} catch (error) {
|
|
162
|
+
debugGraphPrimitiveFallback(
|
|
163
|
+
"[topicProjectOverlay] Failed to resolve topic by legacy scope ID",
|
|
164
|
+
{ error, scopeId }
|
|
165
|
+
);
|
|
906
166
|
}
|
|
907
167
|
return null;
|
|
908
168
|
}
|
|
909
|
-
function
|
|
910
|
-
const metadata =
|
|
169
|
+
function materializeTopicProjectOverlay(topic, idMode = "legacy") {
|
|
170
|
+
const metadata = readMetadata(topic);
|
|
911
171
|
const topicId = String(topic._id);
|
|
912
|
-
const legacyProjectId =
|
|
172
|
+
const legacyProjectId = readLegacyProjectId(topic) || readLegacyProjectId(metadata) || readNonEmptyString(metadata.legacyProjectId);
|
|
913
173
|
const storageProjectId = legacyProjectId || topicId;
|
|
914
174
|
const outwardId = idMode === "topic" ? topicId : storageProjectId;
|
|
915
|
-
const visibility =
|
|
916
|
-
const status =
|
|
175
|
+
const visibility = coerceVisibility(topic.visibility) || coerceVisibility(metadata.visibility) || "private";
|
|
176
|
+
const status = coerceStatus(topic.status) || coerceStatus(metadata.status) || "active";
|
|
917
177
|
const createdAt = typeof topic.createdAt === "number" ? topic.createdAt : typeof topic._creationTime === "number" ? topic._creationTime : 0;
|
|
918
178
|
const updatedAt = typeof topic.updatedAt === "number" ? topic.updatedAt : typeof metadata.updatedAt === "number" ? metadata.updatedAt : createdAt;
|
|
919
179
|
return {
|
|
@@ -923,16 +183,16 @@ function materializeTopicProjectOverlay2(topic, idMode = "legacy") {
|
|
|
923
183
|
topicId,
|
|
924
184
|
storageProjectId,
|
|
925
185
|
legacyProjectId,
|
|
926
|
-
name:
|
|
927
|
-
type:
|
|
928
|
-
description:
|
|
929
|
-
ownerId:
|
|
930
|
-
sharedWith:
|
|
186
|
+
name: readNonEmptyString(topic.name) || "Untitled Theme",
|
|
187
|
+
type: mapProjectType(topic, metadata),
|
|
188
|
+
description: readNonEmptyString(topic.description),
|
|
189
|
+
ownerId: readNonEmptyString(metadata.ownerId) || readNonEmptyString(topic.createdBy) || "system",
|
|
190
|
+
sharedWith: readStringArray(metadata.sharedWith),
|
|
931
191
|
visibility,
|
|
932
|
-
tenantId:
|
|
933
|
-
workspaceId:
|
|
192
|
+
tenantId: readNonEmptyString(topic.tenantId) || readNonEmptyString(metadata.tenantId),
|
|
193
|
+
workspaceId: readNonEmptyString(topic.workspaceId) || readNonEmptyString(metadata.workspaceId),
|
|
934
194
|
status,
|
|
935
|
-
tags:
|
|
195
|
+
tags: readStringArray(metadata.tags),
|
|
936
196
|
chatCount: typeof metadata.chatCount === "number" ? metadata.chatCount : 0,
|
|
937
197
|
artifactCount: typeof metadata.artifactCount === "number" ? metadata.artifactCount : 0,
|
|
938
198
|
lastActivityAt: typeof metadata.lastActivityAt === "number" ? metadata.lastActivityAt : updatedAt,
|
|
@@ -941,38 +201,42 @@ function materializeTopicProjectOverlay2(topic, idMode = "legacy") {
|
|
|
941
201
|
updatedAt
|
|
942
202
|
};
|
|
943
203
|
}
|
|
944
|
-
async function
|
|
945
|
-
const topic = await
|
|
204
|
+
async function resolveTopicProjectOverlay(ctx, scopeId, options = {}) {
|
|
205
|
+
const topic = await resolveTopicDoc(ctx, scopeId);
|
|
946
206
|
if (!topic) {
|
|
947
207
|
return null;
|
|
948
208
|
}
|
|
949
|
-
if (options.projectLikeOnly !== false && !
|
|
209
|
+
if (options.projectLikeOnly !== false && !isProjectLikeTopic(topic)) {
|
|
950
210
|
return null;
|
|
951
211
|
}
|
|
952
|
-
return
|
|
212
|
+
return materializeTopicProjectOverlay(topic, options.idMode);
|
|
953
213
|
}
|
|
954
|
-
async function
|
|
214
|
+
async function listTopicProjectOverlays(ctx, options = {}) {
|
|
955
215
|
let allTopics = [];
|
|
956
216
|
if (ctx?.db?.query && typeof ctx.db.query === "function") {
|
|
957
217
|
try {
|
|
958
218
|
allTopics = await ctx.db.query("topics").collect();
|
|
959
|
-
} catch {
|
|
219
|
+
} catch (error) {
|
|
220
|
+
debugGraphPrimitiveFallback(
|
|
221
|
+
"[topicProjectOverlay] Failed to read topics table; falling back to API",
|
|
222
|
+
{ error }
|
|
223
|
+
);
|
|
960
224
|
allTopics = [];
|
|
961
225
|
}
|
|
962
226
|
}
|
|
963
227
|
if (allTopics.length === 0 && typeof ctx.runQuery === "function") {
|
|
964
|
-
allTopics = (await ctx.runQuery(
|
|
228
|
+
allTopics = (await ctx.runQuery(api.topics.list, {}) ?? []) || [];
|
|
965
229
|
}
|
|
966
230
|
return allTopics.filter(
|
|
967
|
-
(topic) => options.projectLikeOnly === false ||
|
|
968
|
-
).map((topic) =>
|
|
231
|
+
(topic) => options.projectLikeOnly === false || isProjectLikeTopic(topic)
|
|
232
|
+
).map((topic) => materializeTopicProjectOverlay(topic, options.idMode));
|
|
969
233
|
}
|
|
970
234
|
async function patchTopicProjectOverlay(ctx, scopeId, value) {
|
|
971
|
-
const topic = await
|
|
235
|
+
const topic = await resolveTopicDoc(ctx, scopeId);
|
|
972
236
|
if (!topic) {
|
|
973
237
|
return null;
|
|
974
238
|
}
|
|
975
|
-
const nextMetadata = { ...
|
|
239
|
+
const nextMetadata = { ...readMetadata(topic) };
|
|
976
240
|
const patch = {};
|
|
977
241
|
const topicUpdateArgs = {
|
|
978
242
|
id: String(topic._id)
|
|
@@ -997,7 +261,7 @@ async function patchTopicProjectOverlay(ctx, scopeId, value) {
|
|
|
997
261
|
`patchTopicProjectOverlay cannot mutate ${key} via component-owned topics`
|
|
998
262
|
);
|
|
999
263
|
case "status": {
|
|
1000
|
-
const status =
|
|
264
|
+
const status = coerceStatus(rawValue);
|
|
1001
265
|
if (status) {
|
|
1002
266
|
patch.status = status;
|
|
1003
267
|
topicUpdateArgs.status = status;
|
|
@@ -1005,7 +269,7 @@ async function patchTopicProjectOverlay(ctx, scopeId, value) {
|
|
|
1005
269
|
break;
|
|
1006
270
|
}
|
|
1007
271
|
case "visibility": {
|
|
1008
|
-
const visibility =
|
|
272
|
+
const visibility = coerceVisibility(rawValue);
|
|
1009
273
|
if (visibility) {
|
|
1010
274
|
patch.visibility = visibility;
|
|
1011
275
|
topicUpdateArgs.visibility = visibility;
|
|
@@ -1013,7 +277,7 @@ async function patchTopicProjectOverlay(ctx, scopeId, value) {
|
|
|
1013
277
|
break;
|
|
1014
278
|
}
|
|
1015
279
|
case "type": {
|
|
1016
|
-
const projectType =
|
|
280
|
+
const projectType = readNonEmptyString(rawValue);
|
|
1017
281
|
if (projectType) {
|
|
1018
282
|
nextMetadata.projectType = projectType;
|
|
1019
283
|
} else {
|
|
@@ -1037,7 +301,7 @@ async function patchTopicProjectOverlay(ctx, scopeId, value) {
|
|
|
1037
301
|
topicUpdateArgs.metadata = nextMetadata;
|
|
1038
302
|
if (typeof ctx.runMutation === "function") {
|
|
1039
303
|
try {
|
|
1040
|
-
await ctx.runMutation(
|
|
304
|
+
await ctx.runMutation(api.topics.update, topicUpdateArgs);
|
|
1041
305
|
} catch (error) {
|
|
1042
306
|
if (!isMissingLucernChildComponentError(error) || !ctx?.db || typeof ctx.db.patch !== "function") {
|
|
1043
307
|
throw error;
|
|
@@ -1051,19 +315,28 @@ async function patchTopicProjectOverlay(ctx, scopeId, value) {
|
|
|
1051
315
|
"Cannot patch topic without component adapter (ctx.runMutation unavailable)"
|
|
1052
316
|
);
|
|
1053
317
|
}
|
|
1054
|
-
return
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
}
|
|
1060
|
-
);
|
|
318
|
+
return materializeTopicProjectOverlay({
|
|
319
|
+
...topic,
|
|
320
|
+
...patch,
|
|
321
|
+
metadata: nextMetadata
|
|
322
|
+
});
|
|
1061
323
|
}
|
|
1062
324
|
|
|
1063
325
|
// src/resolvers.ts
|
|
1064
326
|
function isMissingLucernChildComponentError2(error) {
|
|
1065
|
-
const message =
|
|
1066
|
-
return message.includes(
|
|
327
|
+
const message = getErrorMessage2(error);
|
|
328
|
+
return message.includes(
|
|
329
|
+
'Child component ComponentName(Identifier("lucern")) not found'
|
|
330
|
+
) || message.includes("Child component") && message.includes("lucern") && message.includes("not found");
|
|
331
|
+
}
|
|
332
|
+
function getErrorMessage2(error) {
|
|
333
|
+
if (error instanceof Error) {
|
|
334
|
+
return error.message;
|
|
335
|
+
}
|
|
336
|
+
if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") {
|
|
337
|
+
return error.message;
|
|
338
|
+
}
|
|
339
|
+
return "unknown error";
|
|
1067
340
|
}
|
|
1068
341
|
function isAdvisoryTopicPatch(value) {
|
|
1069
342
|
const advisoryKeys = /* @__PURE__ */ new Set(["lastActivityAt", "updatedAt"]);
|
|
@@ -1077,52 +350,47 @@ async function patchProjectWithTolerance(ctx, projectId, value) {
|
|
|
1077
350
|
if (!isAdvisoryTopicPatch(value) || !isMissingLucernChildComponentError2(error)) {
|
|
1078
351
|
throw error;
|
|
1079
352
|
}
|
|
1080
|
-
console.warn(
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
353
|
+
console.warn(
|
|
354
|
+
"[lucern graph-primitives] Non-fatal advisory topic patch failure",
|
|
355
|
+
{
|
|
356
|
+
projectId,
|
|
357
|
+
keys: Object.keys(value),
|
|
358
|
+
error: getErrorMessage2(error)
|
|
359
|
+
}
|
|
360
|
+
);
|
|
1085
361
|
}
|
|
1086
362
|
}
|
|
1087
|
-
function
|
|
363
|
+
function defaultResolvers() {
|
|
1088
364
|
return {
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
async listTopics(ctx) {
|
|
1099
|
-
return await listTopicProjectOverlays2(ctx, {
|
|
1100
|
-
idMode: "legacy"
|
|
1101
|
-
});
|
|
1102
|
-
},
|
|
1103
|
-
async getFinalArtifact(ctx, artifactId) {
|
|
1104
|
-
return await ctx.db.get(artifactId);
|
|
1105
|
-
}
|
|
365
|
+
getProject: (ctx, projectId) => resolveTopicProjectOverlay(ctx, projectId, {
|
|
366
|
+
idMode: "legacy",
|
|
367
|
+
projectLikeOnly: false
|
|
368
|
+
}),
|
|
369
|
+
patchProject: (ctx, projectId, value) => patchProjectWithTolerance(ctx, projectId, value),
|
|
370
|
+
listTopics: (ctx) => listTopicProjectOverlays(ctx, {
|
|
371
|
+
idMode: "legacy"
|
|
372
|
+
}),
|
|
373
|
+
getFinalArtifact: (ctx, artifactId) => ctx.db.get(artifactId)
|
|
1106
374
|
};
|
|
1107
375
|
}
|
|
1108
|
-
var
|
|
376
|
+
var resolverOverrides = {};
|
|
1109
377
|
function resolveGraphPrimitivesAppResolvers(_ctx) {
|
|
1110
378
|
return {
|
|
1111
|
-
...
|
|
1112
|
-
...
|
|
379
|
+
...defaultResolvers(),
|
|
380
|
+
...resolverOverrides
|
|
1113
381
|
};
|
|
1114
382
|
}
|
|
1115
|
-
var
|
|
383
|
+
var LEGACY_SCOPE_FIELD2 = "graphScopeProjectId";
|
|
1116
384
|
function asMappedProjectId(topic) {
|
|
1117
385
|
if (!topic) {
|
|
1118
386
|
return;
|
|
1119
387
|
}
|
|
1120
|
-
const directLegacyProjectId = normalizeScopeValue(topic[
|
|
388
|
+
const directLegacyProjectId = normalizeScopeValue(topic[LEGACY_SCOPE_FIELD2]);
|
|
1121
389
|
if (directLegacyProjectId) {
|
|
1122
390
|
return directLegacyProjectId;
|
|
1123
391
|
}
|
|
1124
392
|
const metadata = topic.metadata || {};
|
|
1125
|
-
const candidate = metadata[
|
|
393
|
+
const candidate = metadata[LEGACY_SCOPE_FIELD2] || metadata.legacyProjectId || metadata.projectId || metadata.scopeProjectId;
|
|
1126
394
|
return candidate ? candidate : void 0;
|
|
1127
395
|
}
|
|
1128
396
|
function normalizeScopeValue(value) {
|
|
@@ -1151,9 +419,16 @@ async function findTopicsByScopeAlias(ctx, scopeId) {
|
|
|
1151
419
|
try {
|
|
1152
420
|
return await ctx.db.query("topics").withIndex(
|
|
1153
421
|
"by_graph_scope_project",
|
|
1154
|
-
(q) => q.eq(
|
|
422
|
+
(q) => q.eq(LEGACY_SCOPE_FIELD2, scopeId)
|
|
1155
423
|
).collect();
|
|
1156
|
-
} catch {
|
|
424
|
+
} catch (error) {
|
|
425
|
+
debugGraphPrimitiveFallback(
|
|
426
|
+
"[topicScope] Failed to resolve scope alias via index",
|
|
427
|
+
{
|
|
428
|
+
error,
|
|
429
|
+
scopeId
|
|
430
|
+
}
|
|
431
|
+
);
|
|
1157
432
|
const topics = await ctx.db.query("topics").collect();
|
|
1158
433
|
return topics.filter((topic) => {
|
|
1159
434
|
const normalizedGlobalId = normalizeScopeValue(topic.globalId);
|
|
@@ -1167,10 +442,17 @@ async function tryResolveHostTopicById(ctx, topicId) {
|
|
|
1167
442
|
return null;
|
|
1168
443
|
}
|
|
1169
444
|
try {
|
|
1170
|
-
return await ctx.runQuery(
|
|
445
|
+
return await ctx.runQuery(api.topics.get, {
|
|
1171
446
|
id: topicId
|
|
1172
447
|
}) ?? null;
|
|
1173
|
-
} catch {
|
|
448
|
+
} catch (error) {
|
|
449
|
+
debugGraphPrimitiveFallback(
|
|
450
|
+
"[topicScope] Failed to resolve topic by host query",
|
|
451
|
+
{
|
|
452
|
+
error,
|
|
453
|
+
topicId
|
|
454
|
+
}
|
|
455
|
+
);
|
|
1174
456
|
return null;
|
|
1175
457
|
}
|
|
1176
458
|
}
|
|
@@ -1179,10 +461,17 @@ async function tryResolveHostTopicByLegacyScope(ctx, legacyScopeId) {
|
|
|
1179
461
|
return null;
|
|
1180
462
|
}
|
|
1181
463
|
try {
|
|
1182
|
-
return await ctx.runQuery(
|
|
464
|
+
return await ctx.runQuery(api.topics.getByLegacyScopeId, {
|
|
1183
465
|
projectId: legacyScopeId
|
|
1184
466
|
}) ?? null;
|
|
1185
|
-
} catch {
|
|
467
|
+
} catch (error) {
|
|
468
|
+
debugGraphPrimitiveFallback(
|
|
469
|
+
"[topicScope] Failed to resolve topic by legacy scope",
|
|
470
|
+
{
|
|
471
|
+
error,
|
|
472
|
+
legacyScopeId
|
|
473
|
+
}
|
|
474
|
+
);
|
|
1186
475
|
return null;
|
|
1187
476
|
}
|
|
1188
477
|
}
|
|
@@ -1211,8 +500,17 @@ async function resolveTopicProjectScope(ctx, args) {
|
|
|
1211
500
|
if (args.topicId) {
|
|
1212
501
|
let topic = null;
|
|
1213
502
|
try {
|
|
1214
|
-
topic = await ctx.db.get(
|
|
1215
|
-
|
|
503
|
+
topic = await ctx.db.get(
|
|
504
|
+
args.topicId
|
|
505
|
+
);
|
|
506
|
+
} catch (error) {
|
|
507
|
+
debugGraphPrimitiveFallback(
|
|
508
|
+
"[topicScope] Failed to load topic by direct id",
|
|
509
|
+
{
|
|
510
|
+
error,
|
|
511
|
+
topicId: args.topicId
|
|
512
|
+
}
|
|
513
|
+
);
|
|
1216
514
|
}
|
|
1217
515
|
if (!topic) {
|
|
1218
516
|
topic = await tryResolveHostTopicById(ctx, String(args.topicId));
|
|
@@ -1249,7 +547,14 @@ async function resolveTopicProjectScope(ctx, args) {
|
|
|
1249
547
|
directTopic = await ctx.db.get(
|
|
1250
548
|
args.projectId
|
|
1251
549
|
);
|
|
1252
|
-
} catch {
|
|
550
|
+
} catch (error) {
|
|
551
|
+
debugGraphPrimitiveFallback(
|
|
552
|
+
"[topicScope] Failed to load direct project topic",
|
|
553
|
+
{
|
|
554
|
+
error,
|
|
555
|
+
projectId: args.projectId
|
|
556
|
+
}
|
|
557
|
+
);
|
|
1253
558
|
}
|
|
1254
559
|
if (directTopic) {
|
|
1255
560
|
const inherited = await resolveInheritedWorkspaceScope(ctx, directTopic);
|
|
@@ -1800,7 +1105,20 @@ var getByProject = query({
|
|
|
1800
1105
|
if (!hasAccess) {
|
|
1801
1106
|
return [];
|
|
1802
1107
|
}
|
|
1803
|
-
|
|
1108
|
+
let scope;
|
|
1109
|
+
try {
|
|
1110
|
+
scope = await resolveTopicProjectScope(ctx, args);
|
|
1111
|
+
} catch (error) {
|
|
1112
|
+
debugGraphPrimitiveFallback(
|
|
1113
|
+
"[questionEvidenceLinks] Failed to resolve topic scope",
|
|
1114
|
+
{
|
|
1115
|
+
error,
|
|
1116
|
+
projectId: args.projectId,
|
|
1117
|
+
topicId: args.topicId
|
|
1118
|
+
}
|
|
1119
|
+
);
|
|
1120
|
+
scope = null;
|
|
1121
|
+
}
|
|
1804
1122
|
if (!scope) {
|
|
1805
1123
|
return [];
|
|
1806
1124
|
}
|