@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,943 +1,32 @@
|
|
|
1
1
|
import { v } from 'convex/values';
|
|
2
|
-
import {
|
|
2
|
+
import { requireProjectAccess, checkScopeAccess, checkProjectAccess } from '@lucern/access-control/access';
|
|
3
|
+
import { canAudienceClassAccess, normalizeAudienceKey, classFromAudienceKey } from '@lucern/access-control/audience';
|
|
4
|
+
import { listAudienceRegistryRows } from '@lucern/access-control/audienceRegistry';
|
|
5
|
+
import { permissiveReturn } from '@lucern/contracts/schema-helpers/validators';
|
|
6
|
+
import { componentsGeneric, anyApi, mutationGeneric, queryGeneric, internalQueryGeneric, internalMutationGeneric } from 'convex/server';
|
|
7
|
+
import { isNodeType, getLayerForNodeType } from '@lucern/contracts/schema-helpers/spine/tables/epistemicNodes';
|
|
3
8
|
|
|
4
9
|
// src/epistemicEvidence.ts
|
|
5
10
|
var api = anyApi;
|
|
6
11
|
componentsGeneric();
|
|
7
|
-
|
|
8
|
-
// ../access-control/src/topicProjectOverlay.ts
|
|
9
|
-
var LEGACY_SCOPE_FIELD = "graphScopeProjectId";
|
|
10
|
-
function readNonEmptyString(value) {
|
|
11
|
-
if (typeof value !== "string") {
|
|
12
|
-
return;
|
|
13
|
-
}
|
|
14
|
-
const normalized = value.trim();
|
|
15
|
-
return normalized.length > 0 ? normalized : void 0;
|
|
16
|
-
}
|
|
17
|
-
function readStringArray(value) {
|
|
18
|
-
if (!Array.isArray(value)) {
|
|
19
|
-
return [];
|
|
20
|
-
}
|
|
21
|
-
return value.map((entry) => readNonEmptyString(entry)).filter((entry) => Boolean(entry));
|
|
22
|
-
}
|
|
23
|
-
function readMetadata(topic) {
|
|
24
|
-
return topic.metadata && typeof topic.metadata === "object" ? topic.metadata : {};
|
|
25
|
-
}
|
|
26
|
-
function readLegacyProjectId(value) {
|
|
27
|
-
if (!value) {
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
return readNonEmptyString(value[LEGACY_SCOPE_FIELD]);
|
|
31
|
-
}
|
|
32
|
-
function coerceVisibility(value) {
|
|
33
|
-
return value === "private" || value === "team" || value === "firm" || value === "external" || value === "public" ? value : void 0;
|
|
34
|
-
}
|
|
35
|
-
function coerceStatus(value) {
|
|
36
|
-
return value === "active" || value === "archived" || value === "watching" ? value : void 0;
|
|
37
|
-
}
|
|
38
|
-
function mapProjectType(topic, metadata) {
|
|
39
|
-
const explicit = readNonEmptyString(metadata.projectType);
|
|
40
|
-
if (explicit) {
|
|
41
|
-
return explicit;
|
|
42
|
-
}
|
|
43
|
-
if (topic.type === "theme") {
|
|
44
|
-
return "thematic";
|
|
45
|
-
}
|
|
46
|
-
return readNonEmptyString(topic.type) || "general";
|
|
47
|
-
}
|
|
48
|
-
function isProjectLikeTopic(topic) {
|
|
49
|
-
const metadata = readMetadata(topic);
|
|
50
|
-
return topic.type === "theme" || topic.type === "thematic" || topic.type === "deal" || topic.type === "monitoring" || readLegacyProjectId(topic) !== void 0 || readNonEmptyString(metadata.projectType) !== void 0;
|
|
51
|
-
}
|
|
52
|
-
async function resolveTopicDoc(ctx, scopeId) {
|
|
53
|
-
if (ctx?.db && typeof ctx.db.get === "function") {
|
|
54
|
-
try {
|
|
55
|
-
const directTopic = await ctx.db.get(scopeId);
|
|
56
|
-
if (directTopic) {
|
|
57
|
-
return directTopic;
|
|
58
|
-
}
|
|
59
|
-
} catch {
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
if (typeof ctx.runQuery !== "function") {
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
try {
|
|
66
|
-
const topic = await ctx.runQuery(api.topics.get, {
|
|
67
|
-
id: String(scopeId)
|
|
68
|
-
});
|
|
69
|
-
if (topic?.name !== void 0 && topic?.type !== void 0) {
|
|
70
|
-
return topic;
|
|
71
|
-
}
|
|
72
|
-
} catch {
|
|
73
|
-
}
|
|
74
|
-
try {
|
|
75
|
-
const topic = await ctx.runQuery(api.topics.getByLegacyScopeId, {
|
|
76
|
-
projectId: String(scopeId)
|
|
77
|
-
});
|
|
78
|
-
if (topic?.name !== void 0 && topic?.type !== void 0) {
|
|
79
|
-
return topic;
|
|
80
|
-
}
|
|
81
|
-
} catch {
|
|
82
|
-
}
|
|
83
|
-
return null;
|
|
84
|
-
}
|
|
85
|
-
function materializeTopicProjectOverlay(topic, idMode = "legacy") {
|
|
86
|
-
const metadata = readMetadata(topic);
|
|
87
|
-
const topicId = String(topic._id);
|
|
88
|
-
const legacyProjectId = readLegacyProjectId(topic) || readLegacyProjectId(metadata) || readNonEmptyString(metadata.legacyProjectId);
|
|
89
|
-
const storageProjectId = legacyProjectId || topicId;
|
|
90
|
-
const outwardId = idMode === "topic" ? topicId : storageProjectId;
|
|
91
|
-
const visibility = coerceVisibility(topic.visibility) || coerceVisibility(metadata.visibility) || "private";
|
|
92
|
-
const status = coerceStatus(topic.status) || coerceStatus(metadata.status) || "active";
|
|
93
|
-
const createdAt = typeof topic.createdAt === "number" ? topic.createdAt : typeof topic._creationTime === "number" ? topic._creationTime : 0;
|
|
94
|
-
const updatedAt = typeof topic.updatedAt === "number" ? topic.updatedAt : typeof metadata.updatedAt === "number" ? metadata.updatedAt : createdAt;
|
|
95
|
-
return {
|
|
96
|
-
...metadata,
|
|
97
|
-
_id: outwardId,
|
|
98
|
-
projectId: outwardId,
|
|
99
|
-
topicId,
|
|
100
|
-
storageProjectId,
|
|
101
|
-
legacyProjectId,
|
|
102
|
-
name: readNonEmptyString(topic.name) || "Untitled Theme",
|
|
103
|
-
type: mapProjectType(topic, metadata),
|
|
104
|
-
description: readNonEmptyString(topic.description),
|
|
105
|
-
ownerId: readNonEmptyString(metadata.ownerId) || readNonEmptyString(topic.createdBy) || "system",
|
|
106
|
-
sharedWith: readStringArray(metadata.sharedWith),
|
|
107
|
-
visibility,
|
|
108
|
-
tenantId: readNonEmptyString(topic.tenantId) || readNonEmptyString(metadata.tenantId),
|
|
109
|
-
workspaceId: readNonEmptyString(topic.workspaceId) || readNonEmptyString(metadata.workspaceId),
|
|
110
|
-
status,
|
|
111
|
-
tags: readStringArray(metadata.tags),
|
|
112
|
-
chatCount: typeof metadata.chatCount === "number" ? metadata.chatCount : 0,
|
|
113
|
-
artifactCount: typeof metadata.artifactCount === "number" ? metadata.artifactCount : 0,
|
|
114
|
-
lastActivityAt: typeof metadata.lastActivityAt === "number" ? metadata.lastActivityAt : updatedAt,
|
|
115
|
-
_creationTime: typeof topic._creationTime === "number" ? topic._creationTime : createdAt,
|
|
116
|
-
createdAt,
|
|
117
|
-
updatedAt
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
async function resolveTopicProjectOverlay(ctx, scopeId, options = {}) {
|
|
121
|
-
const topic = await resolveTopicDoc(ctx, scopeId);
|
|
122
|
-
if (!topic) {
|
|
123
|
-
return null;
|
|
124
|
-
}
|
|
125
|
-
if (options.projectLikeOnly !== false && !isProjectLikeTopic(topic)) {
|
|
126
|
-
return null;
|
|
127
|
-
}
|
|
128
|
-
return materializeTopicProjectOverlay(topic, options.idMode);
|
|
129
|
-
}
|
|
130
|
-
async function listTopicProjectOverlays(ctx, options = {}) {
|
|
131
|
-
let allTopics = [];
|
|
132
|
-
if (ctx?.db?.query && typeof ctx.db.query === "function") {
|
|
133
|
-
try {
|
|
134
|
-
allTopics = await ctx.db.query("topics").collect();
|
|
135
|
-
} catch {
|
|
136
|
-
allTopics = [];
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
if (allTopics.length === 0 && typeof ctx.runQuery === "function") {
|
|
140
|
-
allTopics = (await ctx.runQuery(api.topics.list, {}) ?? []) || [];
|
|
141
|
-
}
|
|
142
|
-
return allTopics.filter(
|
|
143
|
-
(topic) => options.projectLikeOnly === false || isProjectLikeTopic(topic)
|
|
144
|
-
).map((topic) => materializeTopicProjectOverlay(topic, options.idMode));
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// ../access-control/src/projectGrantsBridge.ts
|
|
148
|
-
var PROJECT_GRANT_STATUSES = ["active", "revoked", "expired"];
|
|
149
|
-
function normalizeString(value) {
|
|
150
|
-
if (typeof value !== "string") {
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
const trimmed = value.trim();
|
|
154
|
-
return trimmed.length > 0 ? trimmed : void 0;
|
|
155
|
-
}
|
|
156
|
-
async function resolveGrantScopeIds(ctx, args) {
|
|
157
|
-
const topicId = normalizeString(args.topicId);
|
|
158
|
-
const projectId = normalizeString(args.projectId);
|
|
159
|
-
for (const scopeId of [topicId, projectId]) {
|
|
160
|
-
if (!scopeId) {
|
|
161
|
-
continue;
|
|
162
|
-
}
|
|
163
|
-
try {
|
|
164
|
-
const overlay = await resolveTopicProjectOverlay(ctx, scopeId, {
|
|
165
|
-
idMode: "legacy",
|
|
166
|
-
projectLikeOnly: false
|
|
167
|
-
});
|
|
168
|
-
if (overlay) {
|
|
169
|
-
return {
|
|
170
|
-
topicId: normalizeString(overlay.topicId) ?? topicId,
|
|
171
|
-
projectId: normalizeString(overlay.projectId) ?? projectId ?? scopeId
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
} catch {
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
return { topicId, projectId };
|
|
178
|
-
}
|
|
179
|
-
async function normalizeProjectGrantRow(ctx, row) {
|
|
180
|
-
const scope = await resolveGrantScopeIds(ctx, {
|
|
181
|
-
topicId: row.topicId,
|
|
182
|
-
projectId: row.projectId
|
|
183
|
-
});
|
|
184
|
-
return {
|
|
185
|
-
...row,
|
|
186
|
-
...scope.topicId ? { topicId: scope.topicId } : {},
|
|
187
|
-
...scope.projectId ?? scope.topicId ? { projectId: scope.projectId ?? scope.topicId } : {}
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
async function normalizeProjectGrantRows(ctx, rows) {
|
|
191
|
-
return await Promise.all(rows.map((row) => normalizeProjectGrantRow(ctx, row)));
|
|
192
|
-
}
|
|
193
|
-
async function listProjectGrantsByPrincipal(ctx, principalId) {
|
|
194
|
-
const rows = await Promise.all(
|
|
195
|
-
PROJECT_GRANT_STATUSES.map(
|
|
196
|
-
(status) => ctx.db.query("projectGrants").withIndex(
|
|
197
|
-
"by_principal_status",
|
|
198
|
-
(q) => q.eq("principalId", principalId).eq("status", status)
|
|
199
|
-
).collect()
|
|
200
|
-
)
|
|
201
|
-
);
|
|
202
|
-
return await normalizeProjectGrantRows(ctx, rows.flat());
|
|
203
|
-
}
|
|
204
|
-
async function listProjectGrantsByGroup(ctx, groupId) {
|
|
205
|
-
const rows = await Promise.all(
|
|
206
|
-
PROJECT_GRANT_STATUSES.map(
|
|
207
|
-
(status) => ctx.db.query("projectGrants").withIndex(
|
|
208
|
-
"by_group_status",
|
|
209
|
-
(q) => q.eq("groupId", groupId).eq("status", status)
|
|
210
|
-
).collect()
|
|
211
|
-
)
|
|
212
|
-
);
|
|
213
|
-
return await normalizeProjectGrantRows(ctx, rows.flat());
|
|
214
|
-
}
|
|
215
|
-
function buildScopeMatchers(inputScopeId, resolved) {
|
|
216
|
-
return new Set(
|
|
217
|
-
[inputScopeId, resolved.topicId, resolved.projectId].map((value) => normalizeString(value)).filter((value) => Boolean(value))
|
|
218
|
-
);
|
|
219
|
-
}
|
|
220
|
-
function matchesResolvedScope(row, scopeIds) {
|
|
221
|
-
const rowTopicId = normalizeString(row.topicId);
|
|
222
|
-
const rowProjectId = normalizeString(row.projectId);
|
|
223
|
-
return rowTopicId !== void 0 && scopeIds.has(rowTopicId) || rowProjectId !== void 0 && scopeIds.has(rowProjectId);
|
|
224
|
-
}
|
|
225
|
-
async function bridgeListProjectGrantsByTopicAndPrincipal(ctx, topicId, principalId) {
|
|
226
|
-
const resolved = await resolveGrantScopeIds(ctx, { topicId });
|
|
227
|
-
const scopeIds = buildScopeMatchers(topicId, resolved);
|
|
228
|
-
const rows = await listProjectGrantsByPrincipal(ctx, principalId);
|
|
229
|
-
return rows.filter((row) => matchesResolvedScope(row, scopeIds));
|
|
230
|
-
}
|
|
231
|
-
async function bridgeListProjectGrantsByTopicAndGroup(ctx, topicId, groupId) {
|
|
232
|
-
const resolved = await resolveGrantScopeIds(ctx, { topicId });
|
|
233
|
-
const scopeIds = buildScopeMatchers(topicId, resolved);
|
|
234
|
-
const rows = await listProjectGrantsByGroup(ctx, groupId);
|
|
235
|
-
return rows.filter((row) => matchesResolvedScope(row, scopeIds));
|
|
236
|
-
}
|
|
237
|
-
async function bridgeListProjectGrantsByPrincipalStatus(ctx, principalId, status) {
|
|
238
|
-
const rows = await listProjectGrantsByPrincipal(ctx, principalId);
|
|
239
|
-
return rows.filter((row) => row.status === status);
|
|
240
|
-
}
|
|
241
|
-
async function bridgeListProjectGrantsByGroupStatus(ctx, groupId, status) {
|
|
242
|
-
const rows = await listProjectGrantsByGroup(ctx, groupId);
|
|
243
|
-
return rows.filter((row) => row.status === status);
|
|
244
|
-
}
|
|
245
|
-
async function bridgeInsertProjectGrant(ctx, value) {
|
|
246
|
-
const resolved = await resolveGrantScopeIds(ctx, value);
|
|
247
|
-
return await ctx.db.insert("projectGrants", {
|
|
248
|
-
...value,
|
|
249
|
-
...resolved.topicId ? { topicId: resolved.topicId } : {},
|
|
250
|
-
...resolved.projectId ?? resolved.topicId ? { projectId: resolved.projectId ?? resolved.topicId } : {}
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// ../access-control/src/resolvers.ts
|
|
255
|
-
async function findUserByClerkId(ctx, clerkId) {
|
|
256
|
-
const normalizedClerkId = clerkId.trim();
|
|
257
|
-
if (!normalizedClerkId) {
|
|
258
|
-
return null;
|
|
259
|
-
}
|
|
260
|
-
if (typeof ctx.runQuery === "function") {
|
|
261
|
-
try {
|
|
262
|
-
const bridgedUser = await ctx.runQuery(api.users.getUserByClerkId, {
|
|
263
|
-
clerkId: normalizedClerkId
|
|
264
|
-
});
|
|
265
|
-
if (bridgedUser) {
|
|
266
|
-
return bridgedUser;
|
|
267
|
-
}
|
|
268
|
-
} catch {
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
try {
|
|
272
|
-
const users = await ctx.db.query("users").collect();
|
|
273
|
-
return users.find((user) => String(user.clerkId ?? "") === normalizedClerkId) ?? null;
|
|
274
|
-
} catch {
|
|
275
|
-
return null;
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
async function findUserByPrincipalId(ctx, principalId) {
|
|
279
|
-
const normalizedPrincipalId = principalId.trim();
|
|
280
|
-
if (!normalizedPrincipalId) {
|
|
281
|
-
return null;
|
|
282
|
-
}
|
|
283
|
-
try {
|
|
284
|
-
const users = await ctx.db.query("users").collect();
|
|
285
|
-
return users.find(
|
|
286
|
-
(user) => String(user.defaultPrincipalId ?? "") === normalizedPrincipalId
|
|
287
|
-
) ?? null;
|
|
288
|
-
} catch {
|
|
289
|
-
return null;
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
async function findAgentByPrincipalId(ctx, principalId) {
|
|
293
|
-
const normalizedPrincipalId = principalId.trim();
|
|
294
|
-
if (!normalizedPrincipalId) {
|
|
295
|
-
return null;
|
|
296
|
-
}
|
|
297
|
-
if (typeof ctx.runQuery === "function") {
|
|
298
|
-
try {
|
|
299
|
-
const bridgedAgent = await ctx.runQuery(
|
|
300
|
-
api.agents.getAgentByPrincipalId,
|
|
301
|
-
{
|
|
302
|
-
principalId: normalizedPrincipalId
|
|
303
|
-
}
|
|
304
|
-
);
|
|
305
|
-
if (bridgedAgent) {
|
|
306
|
-
return bridgedAgent;
|
|
307
|
-
}
|
|
308
|
-
} catch {
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
try {
|
|
312
|
-
const agents = await ctx.db.query("agents").collect();
|
|
313
|
-
return agents.find(
|
|
314
|
-
(agent) => String(agent.principalId ?? "") === normalizedPrincipalId
|
|
315
|
-
) ?? null;
|
|
316
|
-
} catch {
|
|
317
|
-
return null;
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
function defaultResolvers() {
|
|
321
|
-
return {
|
|
322
|
-
async getProject(ctx, topicId) {
|
|
323
|
-
return await resolveTopicProjectOverlay(ctx, topicId, {
|
|
324
|
-
idMode: "legacy",
|
|
325
|
-
projectLikeOnly: false
|
|
326
|
-
});
|
|
327
|
-
},
|
|
328
|
-
async listTopics(ctx) {
|
|
329
|
-
return await listTopicProjectOverlays(ctx, { idMode: "legacy" });
|
|
330
|
-
},
|
|
331
|
-
async listTopicsByOwner(ctx, ownerId) {
|
|
332
|
-
const topics = await listTopicProjectOverlays(ctx, { idMode: "legacy" });
|
|
333
|
-
return topics.filter((topic) => topic.ownerId === ownerId);
|
|
334
|
-
},
|
|
335
|
-
async listTopicsByVisibility(ctx, visibility) {
|
|
336
|
-
const topics = await listTopicProjectOverlays(ctx, { idMode: "legacy" });
|
|
337
|
-
return topics.filter((topic) => topic.visibility === visibility);
|
|
338
|
-
},
|
|
339
|
-
async listProjectGrantsByProjectAndPrincipal(ctx, topicId, principalId) {
|
|
340
|
-
return await bridgeListProjectGrantsByTopicAndPrincipal(
|
|
341
|
-
ctx,
|
|
342
|
-
topicId,
|
|
343
|
-
principalId
|
|
344
|
-
);
|
|
345
|
-
},
|
|
346
|
-
async listProjectGrantsByProjectAndGroup(ctx, topicId, groupId) {
|
|
347
|
-
return await bridgeListProjectGrantsByTopicAndGroup(ctx, topicId, groupId);
|
|
348
|
-
},
|
|
349
|
-
async listProjectGrantsByPrincipalStatus(ctx, principalId, status) {
|
|
350
|
-
return await bridgeListProjectGrantsByPrincipalStatus(
|
|
351
|
-
ctx,
|
|
352
|
-
principalId,
|
|
353
|
-
status
|
|
354
|
-
);
|
|
355
|
-
},
|
|
356
|
-
async listProjectGrantsByGroupStatus(ctx, groupId, status) {
|
|
357
|
-
return await bridgeListProjectGrantsByGroupStatus(ctx, groupId, status);
|
|
358
|
-
},
|
|
359
|
-
async insertProjectGrant(ctx, value) {
|
|
360
|
-
return await bridgeInsertProjectGrant(ctx, value);
|
|
361
|
-
},
|
|
362
|
-
async getAgentByPrincipalId(ctx, principalId) {
|
|
363
|
-
return await findAgentByPrincipalId(ctx, principalId);
|
|
364
|
-
},
|
|
365
|
-
async getUserByClerkId(ctx, clerkId) {
|
|
366
|
-
return await findUserByClerkId(ctx, clerkId);
|
|
367
|
-
},
|
|
368
|
-
async getUserByPrincipalId(ctx, principalId) {
|
|
369
|
-
return await findUserByPrincipalId(ctx, principalId);
|
|
370
|
-
}
|
|
371
|
-
};
|
|
372
|
-
}
|
|
373
|
-
var resolverOverrides = {};
|
|
374
|
-
function resolveAccessControlAppResolvers(_ctx) {
|
|
375
|
-
return {
|
|
376
|
-
...defaultResolvers(),
|
|
377
|
-
...resolverOverrides
|
|
378
|
-
};
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
// ../access-control/src/principalContext.ts
|
|
382
|
-
function requireCanonicalResolvedUser(user, clerkId) {
|
|
383
|
-
const resolved = user;
|
|
384
|
-
if (!resolved) {
|
|
385
|
-
throw new Error(
|
|
386
|
-
`[AccessControl] Canonical user identity required for ${clerkId}. Sync users.upsertUser before user-bound access checks.`
|
|
387
|
-
);
|
|
388
|
-
}
|
|
389
|
-
const { mcRole, defaultTenantId, defaultWorkspaceId, defaultPrincipalId } = resolved;
|
|
390
|
-
if (mcRole !== "platform_admin" && mcRole !== "tenant_admin" && mcRole !== "workspace_admin" && mcRole !== "editor" && mcRole !== "viewer" && mcRole !== "auditor" && mcRole !== "service_agent") {
|
|
391
|
-
throw new Error(
|
|
392
|
-
`[AccessControl] Canonical MC role required for ${clerkId}. Re-sync Master Control identity before user-bound access checks.`
|
|
393
|
-
);
|
|
394
|
-
}
|
|
395
|
-
if (typeof defaultTenantId !== "string" || defaultTenantId.trim().length === 0) {
|
|
396
|
-
throw new Error(
|
|
397
|
-
`[AccessControl] Canonical home tenant required for ${clerkId}. Re-sync Master Control identity before user-bound access checks.`
|
|
398
|
-
);
|
|
399
|
-
}
|
|
400
|
-
if (typeof defaultWorkspaceId !== "string" || defaultWorkspaceId.trim().length === 0) {
|
|
401
|
-
throw new Error(
|
|
402
|
-
`[AccessControl] Canonical home workspace required for ${clerkId}. Re-sync Master Control identity before user-bound access checks.`
|
|
403
|
-
);
|
|
404
|
-
}
|
|
405
|
-
if (typeof defaultPrincipalId !== "string" || defaultPrincipalId.trim().length === 0) {
|
|
406
|
-
throw new Error(
|
|
407
|
-
`[AccessControl] Canonical federated principal required for ${clerkId}. Re-sync Master Control identity before user-bound access checks.`
|
|
408
|
-
);
|
|
409
|
-
}
|
|
410
|
-
return {
|
|
411
|
-
mcRole,
|
|
412
|
-
defaultTenantId: defaultTenantId.trim(),
|
|
413
|
-
defaultWorkspaceId: defaultWorkspaceId.trim(),
|
|
414
|
-
defaultPrincipalId: defaultPrincipalId.trim()
|
|
415
|
-
};
|
|
416
|
-
}
|
|
417
|
-
function isPrincipalIdInput(value) {
|
|
418
|
-
return value.startsWith("user:") || value.startsWith("group:") || value.startsWith("service:") || value.startsWith("agent:") || value.startsWith("external_viewer:");
|
|
419
|
-
}
|
|
420
|
-
async function resolveCanonicalUserRecord(ctx, actorId) {
|
|
421
|
-
const normalizedActorId = actorId.trim();
|
|
422
|
-
const clerkId = isPrincipalIdInput(normalizedActorId) && normalizedActorId.startsWith("user:") ? normalizedActorId.slice("user:".length) : normalizedActorId;
|
|
423
|
-
const resolvers = resolveAccessControlAppResolvers();
|
|
424
|
-
const resolvedByClerkId = await resolvers.getUserByClerkId(ctx, clerkId);
|
|
425
|
-
if (resolvedByClerkId) {
|
|
426
|
-
return {
|
|
427
|
-
resolvedUser: resolvedByClerkId,
|
|
428
|
-
clerkId,
|
|
429
|
-
contextClerkId: clerkId
|
|
430
|
-
};
|
|
431
|
-
}
|
|
432
|
-
const resolvedByPrincipalId = await resolvers.getUserByPrincipalId(
|
|
433
|
-
ctx,
|
|
434
|
-
normalizedActorId
|
|
435
|
-
);
|
|
436
|
-
return {
|
|
437
|
-
resolvedUser: resolvedByPrincipalId ?? null,
|
|
438
|
-
clerkId,
|
|
439
|
-
contextClerkId: normalizedActorId.startsWith("user:") && clerkId.length > 0 ? clerkId : normalizedActorId
|
|
440
|
-
};
|
|
441
|
-
}
|
|
442
|
-
function uniqRoles(roles) {
|
|
443
|
-
const roleSet = /* @__PURE__ */ new Set();
|
|
444
|
-
for (const role of roles) {
|
|
445
|
-
if (role === "platform_admin" || role === "tenant_admin" || role === "workspace_admin" || role === "editor" || role === "viewer" || role === "auditor" || role === "service_agent") {
|
|
446
|
-
roleSet.add(role);
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
return [...roleSet];
|
|
450
|
-
}
|
|
451
|
-
function normalizeGroupIds(value) {
|
|
452
|
-
if (!Array.isArray(value)) {
|
|
453
|
-
return [];
|
|
454
|
-
}
|
|
455
|
-
return [...new Set(
|
|
456
|
-
value.filter((entry) => typeof entry === "string").map((entry) => entry.trim()).filter(Boolean)
|
|
457
|
-
)];
|
|
458
|
-
}
|
|
459
|
-
function requireServiceAgentUser(user, actorId) {
|
|
460
|
-
const canonicalUser = requireCanonicalResolvedUser(user, actorId);
|
|
461
|
-
if (canonicalUser.mcRole !== "service_agent") {
|
|
462
|
-
throw new Error(
|
|
463
|
-
`[AccessControl] Canonical service_agent identity required for ${actorId}. Sync users.upsertUser before agent-bound access checks.`
|
|
464
|
-
);
|
|
465
|
-
}
|
|
466
|
-
return canonicalUser;
|
|
467
|
-
}
|
|
468
|
-
function requireCanonicalResolvedAgent(agent, actorId) {
|
|
469
|
-
const resolved = agent;
|
|
470
|
-
if (!resolved) {
|
|
471
|
-
throw new Error(
|
|
472
|
-
`[AccessControl] Agent "${actorId}" not found in agents or users table.`
|
|
473
|
-
);
|
|
474
|
-
}
|
|
475
|
-
if (typeof resolved.principalId !== "string" || resolved.principalId.trim().length === 0) {
|
|
476
|
-
throw new Error(
|
|
477
|
-
`[AccessControl] Canonical agent principalId required for ${actorId}.`
|
|
478
|
-
);
|
|
479
|
-
}
|
|
480
|
-
if (typeof resolved.tenantId !== "string" || resolved.tenantId.trim().length === 0) {
|
|
481
|
-
throw new Error(
|
|
482
|
-
`[AccessControl] Canonical home tenant required for ${actorId}.`
|
|
483
|
-
);
|
|
484
|
-
}
|
|
485
|
-
if (typeof resolved.workspaceId !== "string" || resolved.workspaceId.trim().length === 0) {
|
|
486
|
-
throw new Error(
|
|
487
|
-
`[AccessControl] Canonical home workspace required for ${actorId}.`
|
|
488
|
-
);
|
|
489
|
-
}
|
|
490
|
-
return {
|
|
491
|
-
principalId: resolved.principalId.trim(),
|
|
492
|
-
tenantId: resolved.tenantId.trim(),
|
|
493
|
-
workspaceId: resolved.workspaceId.trim(),
|
|
494
|
-
roles: uniqRoles(Array.isArray(resolved.roles) ? resolved.roles : []) ?? ["service_agent"],
|
|
495
|
-
groupIds: normalizeGroupIds(resolved.groupIds)
|
|
496
|
-
};
|
|
497
|
-
}
|
|
498
|
-
async function resolvePrincipalContext(ctx, actorId) {
|
|
499
|
-
if (actorId.startsWith("agent:")) {
|
|
500
|
-
const resolvers = resolveAccessControlAppResolvers();
|
|
501
|
-
const resolvedAgent = await resolvers.getAgentByPrincipalId(ctx, actorId);
|
|
502
|
-
if (resolvedAgent) {
|
|
503
|
-
const agent = requireCanonicalResolvedAgent(
|
|
504
|
-
resolvedAgent,
|
|
505
|
-
actorId
|
|
506
|
-
);
|
|
507
|
-
return {
|
|
508
|
-
principalId: agent.principalId,
|
|
509
|
-
principalType: "service",
|
|
510
|
-
clerkId: actorId,
|
|
511
|
-
tenantId: agent.tenantId,
|
|
512
|
-
workspaceId: agent.workspaceId,
|
|
513
|
-
roles: agent.roles.length > 0 ? agent.roles : ["service_agent"],
|
|
514
|
-
groupIds: agent.groupIds,
|
|
515
|
-
isPlatformAdmin: false,
|
|
516
|
-
isTenantAdmin: false,
|
|
517
|
-
isWorkspaceAdmin: false,
|
|
518
|
-
isSystemFallback: false
|
|
519
|
-
};
|
|
520
|
-
}
|
|
521
|
-
const resolvedUser2 = await resolvers.getUserByClerkId(
|
|
522
|
-
ctx,
|
|
523
|
-
actorId
|
|
524
|
-
);
|
|
525
|
-
if (!resolvedUser2) {
|
|
526
|
-
throw new Error(
|
|
527
|
-
`[AccessControl] Agent "${actorId}" not found in agents or users table.`
|
|
528
|
-
);
|
|
529
|
-
}
|
|
530
|
-
const user2 = requireServiceAgentUser(
|
|
531
|
-
resolvedUser2,
|
|
532
|
-
actorId
|
|
533
|
-
);
|
|
534
|
-
console.warn(
|
|
535
|
-
`[AccessControl] Deprecated legacy service-agent fallback for ${actorId}; migrate this principal into identity.agents.`
|
|
536
|
-
);
|
|
537
|
-
return {
|
|
538
|
-
principalId: user2.defaultPrincipalId,
|
|
539
|
-
principalType: "service",
|
|
540
|
-
clerkId: actorId,
|
|
541
|
-
tenantId: user2.defaultTenantId,
|
|
542
|
-
workspaceId: user2.defaultWorkspaceId,
|
|
543
|
-
roles: ["service_agent"],
|
|
544
|
-
groupIds: normalizeGroupIds(resolvedUser2?.principalGroupIds),
|
|
545
|
-
isPlatformAdmin: false,
|
|
546
|
-
isTenantAdmin: false,
|
|
547
|
-
isWorkspaceAdmin: false,
|
|
548
|
-
isSystemFallback: false
|
|
549
|
-
};
|
|
550
|
-
}
|
|
551
|
-
const {
|
|
552
|
-
resolvedUser,
|
|
553
|
-
contextClerkId
|
|
554
|
-
} = await resolveCanonicalUserRecord(ctx, actorId);
|
|
555
|
-
const user = requireCanonicalResolvedUser(
|
|
556
|
-
resolvedUser,
|
|
557
|
-
contextClerkId
|
|
558
|
-
);
|
|
559
|
-
if (!user.defaultPrincipalId) {
|
|
560
|
-
throw new Error(
|
|
561
|
-
`[AccessControl] Canonical federated principal required for ${contextClerkId}. Re-sync Master Control identity before user-bound access checks.`
|
|
562
|
-
);
|
|
563
|
-
}
|
|
564
|
-
if (user.mcRole === "service_agent") {
|
|
565
|
-
return {
|
|
566
|
-
principalId: user.defaultPrincipalId,
|
|
567
|
-
principalType: "service",
|
|
568
|
-
clerkId: contextClerkId,
|
|
569
|
-
tenantId: user.defaultTenantId,
|
|
570
|
-
workspaceId: user.defaultWorkspaceId,
|
|
571
|
-
roles: ["service_agent"],
|
|
572
|
-
groupIds: normalizeGroupIds(resolvedUser?.principalGroupIds),
|
|
573
|
-
isPlatformAdmin: false,
|
|
574
|
-
isTenantAdmin: false,
|
|
575
|
-
isWorkspaceAdmin: false,
|
|
576
|
-
isSystemFallback: false
|
|
577
|
-
};
|
|
578
|
-
}
|
|
579
|
-
const principalId = user.defaultPrincipalId;
|
|
580
|
-
const effectiveRole = user.mcRole;
|
|
581
|
-
const roles = effectiveRole === "platform_admin" ? ["platform_admin", "tenant_admin"] : effectiveRole === "tenant_admin" ? ["tenant_admin"] : [effectiveRole];
|
|
582
|
-
const tenantId = user.defaultTenantId;
|
|
583
|
-
const workspaceId = user.defaultWorkspaceId;
|
|
584
|
-
const isPlatformAdmin = effectiveRole === "platform_admin";
|
|
585
|
-
return {
|
|
586
|
-
principalId,
|
|
587
|
-
principalType: "user",
|
|
588
|
-
clerkId: contextClerkId,
|
|
589
|
-
tenantId,
|
|
590
|
-
workspaceId,
|
|
591
|
-
roles: uniqRoles(roles),
|
|
592
|
-
groupIds: normalizeGroupIds(resolvedUser?.principalGroupIds),
|
|
593
|
-
isPlatformAdmin,
|
|
594
|
-
isTenantAdmin: isPlatformAdmin || effectiveRole === "tenant_admin",
|
|
595
|
-
isWorkspaceAdmin: isPlatformAdmin || effectiveRole === "tenant_admin" || effectiveRole === "workspace_admin",
|
|
596
|
-
isSystemFallback: false
|
|
597
|
-
};
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
// ../access-control/src/access.ts
|
|
601
|
-
function isTopicInPrincipalTenant(topic, principalTenantId) {
|
|
602
|
-
if (!topic.tenantId) {
|
|
603
|
-
return false;
|
|
604
|
-
}
|
|
605
|
-
if (!principalTenantId) {
|
|
606
|
-
return false;
|
|
607
|
-
}
|
|
608
|
-
return String(topic.tenantId) === String(principalTenantId);
|
|
609
|
-
}
|
|
610
|
-
function isTopicInPrincipalWorkspace(topic, principalWorkspaceId) {
|
|
611
|
-
if (!topic.workspaceId) {
|
|
612
|
-
return false;
|
|
613
|
-
}
|
|
614
|
-
if (!principalWorkspaceId) {
|
|
615
|
-
return false;
|
|
616
|
-
}
|
|
617
|
-
return String(topic.workspaceId) === String(principalWorkspaceId);
|
|
618
|
-
}
|
|
619
|
-
function isLegacyUnscopedTopic(topic) {
|
|
620
|
-
return !topic.tenantId || !topic.workspaceId;
|
|
621
|
-
}
|
|
622
|
-
function isGrantScopeAlignedToTopic(topic, grant) {
|
|
623
|
-
if (topic.tenantId && grant.tenantId && String(topic.tenantId) !== String(grant.tenantId)) {
|
|
624
|
-
return false;
|
|
625
|
-
}
|
|
626
|
-
if (topic.workspaceId && grant.workspaceId && String(topic.workspaceId) !== String(grant.workspaceId)) {
|
|
627
|
-
return false;
|
|
628
|
-
}
|
|
629
|
-
return true;
|
|
630
|
-
}
|
|
631
|
-
function isGrantSourceAllowedForVisibility(visibility, source) {
|
|
632
|
-
if (source !== "external_share") {
|
|
633
|
-
return true;
|
|
634
|
-
}
|
|
635
|
-
return visibility === "external" || visibility === "public";
|
|
636
|
-
}
|
|
637
|
-
function isGrantActive(grant) {
|
|
638
|
-
if (grant.status !== "active") {
|
|
639
|
-
return false;
|
|
640
|
-
}
|
|
641
|
-
if (grant.expiresAt !== void 0 && grant.expiresAt <= Date.now()) {
|
|
642
|
-
return false;
|
|
643
|
-
}
|
|
644
|
-
return true;
|
|
645
|
-
}
|
|
646
|
-
async function hasPrincipalGrant(ctx, args) {
|
|
647
|
-
const grants = await resolveAccessControlAppResolvers().listProjectGrantsByProjectAndPrincipal(
|
|
648
|
-
ctx,
|
|
649
|
-
args.topic._id,
|
|
650
|
-
args.principalId
|
|
651
|
-
);
|
|
652
|
-
if (grants.some(
|
|
653
|
-
(grant) => isGrantActive(grant) && isGrantScopeAlignedToTopic(args.topic, grant) && isGrantSourceAllowedForVisibility(
|
|
654
|
-
args.topic.visibility,
|
|
655
|
-
grant.source
|
|
656
|
-
) && (!args.principalIsExternal || args.topic.visibility === "public" || grant.source === "external_share")
|
|
657
|
-
)) {
|
|
658
|
-
return true;
|
|
659
|
-
}
|
|
660
|
-
return false;
|
|
661
|
-
}
|
|
662
|
-
async function hasGroupGrant(ctx, args) {
|
|
663
|
-
if (args.groupIds.length === 0) {
|
|
664
|
-
return false;
|
|
665
|
-
}
|
|
666
|
-
for (const groupId of args.groupIds) {
|
|
667
|
-
const grants = await resolveAccessControlAppResolvers().listProjectGrantsByProjectAndGroup(ctx, args.topic._id, groupId);
|
|
668
|
-
if (grants.some(
|
|
669
|
-
(grant) => isGrantActive(grant) && isGrantScopeAlignedToTopic(args.topic, grant) && isGrantSourceAllowedForVisibility(
|
|
670
|
-
args.topic.visibility,
|
|
671
|
-
grant.source
|
|
672
|
-
)
|
|
673
|
-
)) {
|
|
674
|
-
return true;
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
return false;
|
|
678
|
-
}
|
|
679
|
-
function isExternalPrincipal(_ctx, _args) {
|
|
680
|
-
return false;
|
|
681
|
-
}
|
|
682
|
-
async function evaluateTopicAccessDetailed(ctx, args) {
|
|
683
|
-
if (args.legacyUserId) {
|
|
684
|
-
return {
|
|
685
|
-
hasAccess: true,
|
|
686
|
-
isAdmin: false,
|
|
687
|
-
isOwner: false,
|
|
688
|
-
isShared: false,
|
|
689
|
-
hasGrant: true,
|
|
690
|
-
isFirmVisible: true,
|
|
691
|
-
isExternalVisible: false,
|
|
692
|
-
isPublicVisible: false,
|
|
693
|
-
isTenantScopeMatch: true,
|
|
694
|
-
isWorkspaceScopeMatch: true,
|
|
695
|
-
isPrincipalExternal: false
|
|
696
|
-
};
|
|
697
|
-
}
|
|
698
|
-
const topic = await resolveAccessControlAppResolvers().getProject(
|
|
699
|
-
ctx,
|
|
700
|
-
args.topicId
|
|
701
|
-
);
|
|
702
|
-
if (!topic) {
|
|
703
|
-
return {
|
|
704
|
-
hasAccess: false,
|
|
705
|
-
isAdmin: false,
|
|
706
|
-
isOwner: false,
|
|
707
|
-
isShared: false,
|
|
708
|
-
hasGrant: false,
|
|
709
|
-
isFirmVisible: false,
|
|
710
|
-
isExternalVisible: false,
|
|
711
|
-
isPublicVisible: false,
|
|
712
|
-
isTenantScopeMatch: false,
|
|
713
|
-
isWorkspaceScopeMatch: false,
|
|
714
|
-
isPrincipalExternal: false
|
|
715
|
-
};
|
|
716
|
-
}
|
|
717
|
-
const { principalContext, legacyUserId } = args;
|
|
718
|
-
const userIsAdmin = principalContext.isPlatformAdmin;
|
|
719
|
-
const isOwner = topic.ownerId === legacyUserId;
|
|
720
|
-
const isShared = (topic.sharedWith ?? []).includes(legacyUserId);
|
|
721
|
-
const principalIsExternal = await isExternalPrincipal(ctx, {
|
|
722
|
-
groupIds: principalContext.groupIds,
|
|
723
|
-
topicTenantId: topic.tenantId,
|
|
724
|
-
topicWorkspaceId: topic.workspaceId
|
|
725
|
-
});
|
|
726
|
-
const hasPrincipalGrantResult = await hasPrincipalGrant(ctx, {
|
|
727
|
-
topic,
|
|
728
|
-
principalId: principalContext.principalId,
|
|
729
|
-
principalIsExternal
|
|
730
|
-
});
|
|
731
|
-
const hasGroupGrantResult = await hasGroupGrant(ctx, {
|
|
732
|
-
topic,
|
|
733
|
-
groupIds: principalContext.groupIds
|
|
734
|
-
});
|
|
735
|
-
const hasGrant = isShared || hasPrincipalGrantResult || hasGroupGrantResult;
|
|
736
|
-
const legacyUnscoped = isLegacyUnscopedTopic(topic);
|
|
737
|
-
const tenantScopeMatch = isTopicInPrincipalTenant(
|
|
738
|
-
topic,
|
|
739
|
-
principalContext.tenantId
|
|
740
|
-
);
|
|
741
|
-
const workspaceScopeMatch = isTopicInPrincipalWorkspace(
|
|
742
|
-
topic,
|
|
743
|
-
principalContext.workspaceId
|
|
744
|
-
);
|
|
745
|
-
const isPublicVisible = topic.visibility === "public";
|
|
746
|
-
const isFirmVisible = topic.visibility === "firm" && !legacyUnscoped && tenantScopeMatch && workspaceScopeMatch && !principalIsExternal;
|
|
747
|
-
const hasScopedGrant = hasGrant && (legacyUnscoped || tenantScopeMatch && workspaceScopeMatch);
|
|
748
|
-
const isExternalVisible = topic.visibility === "external" && hasScopedGrant;
|
|
749
|
-
const hasAccess = userIsAdmin || isOwner || hasScopedGrant || isPublicVisible || isFirmVisible;
|
|
750
|
-
return {
|
|
751
|
-
hasAccess,
|
|
752
|
-
isAdmin: userIsAdmin,
|
|
753
|
-
isOwner,
|
|
754
|
-
isShared,
|
|
755
|
-
hasGrant,
|
|
756
|
-
isFirmVisible,
|
|
757
|
-
isExternalVisible,
|
|
758
|
-
isPublicVisible,
|
|
759
|
-
isTenantScopeMatch: tenantScopeMatch,
|
|
760
|
-
isWorkspaceScopeMatch: workspaceScopeMatch,
|
|
761
|
-
isPrincipalExternal: principalIsExternal
|
|
762
|
-
};
|
|
763
|
-
}
|
|
764
|
-
async function checkTopicAccessDetailed(ctx, topicId, userId) {
|
|
765
|
-
const principalContext = await resolvePrincipalContext(ctx, userId);
|
|
766
|
-
return evaluateTopicAccessDetailed(ctx, {
|
|
767
|
-
topicId,
|
|
768
|
-
legacyUserId: userId,
|
|
769
|
-
principalContext
|
|
770
|
-
});
|
|
771
|
-
}
|
|
772
|
-
async function checkTopicAccess(ctx, topicId, userId) {
|
|
773
|
-
const result = await checkTopicAccessDetailed(ctx, topicId, userId);
|
|
774
|
-
return result.hasAccess;
|
|
775
|
-
}
|
|
776
|
-
async function checkScopeAccess(ctx, scopeId, userId) {
|
|
777
|
-
try {
|
|
778
|
-
const topic = await ctx.db.get(scopeId);
|
|
779
|
-
if (topic && topic.name !== void 0 && topic.type !== void 0) {
|
|
780
|
-
return true;
|
|
781
|
-
}
|
|
782
|
-
} catch {
|
|
783
|
-
}
|
|
784
|
-
try {
|
|
785
|
-
return await checkTopicAccess(ctx, scopeId, userId);
|
|
786
|
-
} catch {
|
|
787
|
-
return false;
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
async function requireTopicAccess(ctx, topicId, userId) {
|
|
791
|
-
const hasAccess = await checkTopicAccess(ctx, topicId, userId);
|
|
792
|
-
if (!hasAccess) {
|
|
793
|
-
throw new Error(
|
|
794
|
-
"Access denied: You don't have permission to access this topic"
|
|
795
|
-
);
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
var checkProjectAccess = checkTopicAccess;
|
|
799
|
-
var requireProjectAccess = requireTopicAccess;
|
|
800
|
-
|
|
801
|
-
// ../access-control/src/audience.ts
|
|
802
|
-
var AUDIENCE_CLASS_RANK = {
|
|
803
|
-
public: 0,
|
|
804
|
-
restricted_external: 1,
|
|
805
|
-
internal: 2
|
|
806
|
-
};
|
|
807
|
-
function normalizeKey(key) {
|
|
808
|
-
return (key ?? "").trim().toLowerCase().replace(/[^a-z0-9:_-]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "");
|
|
809
|
-
}
|
|
810
|
-
function normalizeAudienceKey(key) {
|
|
811
|
-
return normalizeKey(key);
|
|
812
|
-
}
|
|
813
|
-
function classFromAudienceKey(audienceKey, fallback = "internal") {
|
|
814
|
-
const key = normalizeKey(audienceKey);
|
|
815
|
-
if (!key) {
|
|
816
|
-
return fallback;
|
|
817
|
-
}
|
|
818
|
-
if (key === "internal") {
|
|
819
|
-
return "internal";
|
|
820
|
-
}
|
|
821
|
-
if (key === "public") {
|
|
822
|
-
return "public";
|
|
823
|
-
}
|
|
824
|
-
if (key === "lp" || key === "external" || key === "client" || key === "partner" || key === "portfolio" || key === "network" || key === "restricted_external") {
|
|
825
|
-
return "restricted_external";
|
|
826
|
-
}
|
|
827
|
-
return fallback;
|
|
828
|
-
}
|
|
829
|
-
function canAudienceClassAccess(viewerClass, resourceClass) {
|
|
830
|
-
return AUDIENCE_CLASS_RANK[viewerClass] >= AUDIENCE_CLASS_RANK[resourceClass];
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
// ../access-control/src/audienceRegistry.ts
|
|
834
|
-
var DEFAULT_AUDIENCES = [
|
|
835
|
-
{
|
|
836
|
-
audienceKey: "internal",
|
|
837
|
-
audienceLabel: "Internal",
|
|
838
|
-
audienceClass: "internal"
|
|
839
|
-
},
|
|
840
|
-
{
|
|
841
|
-
audienceKey: "lp",
|
|
842
|
-
audienceLabel: "Limited Partners",
|
|
843
|
-
audienceClass: "restricted_external"
|
|
844
|
-
},
|
|
845
|
-
{
|
|
846
|
-
audienceKey: "public",
|
|
847
|
-
audienceLabel: "Public",
|
|
848
|
-
audienceClass: "public"
|
|
849
|
-
}
|
|
850
|
-
];
|
|
851
|
-
var AUDIENCE_CLASS_PRIORITY = {
|
|
852
|
-
internal: 0,
|
|
853
|
-
restricted_external: 1,
|
|
854
|
-
public: 2
|
|
855
|
-
};
|
|
856
|
-
function normalizeRegistryRow(row) {
|
|
857
|
-
return {
|
|
858
|
-
audienceKey: normalizeAudienceKey(row.audienceKey),
|
|
859
|
-
audienceLabel: row.audienceLabel,
|
|
860
|
-
audienceClass: row.audienceClass,
|
|
861
|
-
workspaceId: row.workspaceId
|
|
862
|
-
};
|
|
863
|
-
}
|
|
864
|
-
function dedupeRegistryRows(rows) {
|
|
865
|
-
const byKey = /* @__PURE__ */ new Map();
|
|
866
|
-
for (const row of rows) {
|
|
867
|
-
const key = normalizeAudienceKey(row.audienceKey);
|
|
868
|
-
if (!key) {
|
|
869
|
-
continue;
|
|
870
|
-
}
|
|
871
|
-
const existing = byKey.get(key);
|
|
872
|
-
const isWorkspaceScoped = row.workspaceId !== void 0;
|
|
873
|
-
const existingWorkspaceScoped = existing?.workspaceId !== void 0;
|
|
874
|
-
if (!existing || isWorkspaceScoped && !existingWorkspaceScoped) {
|
|
875
|
-
byKey.set(key, {
|
|
876
|
-
...row,
|
|
877
|
-
audienceKey: key
|
|
878
|
-
});
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
const normalized = [...byKey.values()];
|
|
882
|
-
normalized.sort((a, b) => {
|
|
883
|
-
const classDelta = AUDIENCE_CLASS_PRIORITY[a.audienceClass] - AUDIENCE_CLASS_PRIORITY[b.audienceClass];
|
|
884
|
-
if (classDelta !== 0) {
|
|
885
|
-
return classDelta;
|
|
886
|
-
}
|
|
887
|
-
return a.audienceKey.localeCompare(b.audienceKey);
|
|
888
|
-
});
|
|
889
|
-
return normalized;
|
|
890
|
-
}
|
|
891
|
-
async function queryRegistryRows(ctx, args) {
|
|
892
|
-
if (!args.tenantId) {
|
|
893
|
-
return [...DEFAULT_AUDIENCES];
|
|
894
|
-
}
|
|
895
|
-
const rows = await ctx.db.query("platformAudiences").withIndex("by_tenantId", (q) => q.eq("tenantId", args.tenantId)).collect();
|
|
896
|
-
const workspaceIdString = args.workspaceId ? String(args.workspaceId) : null;
|
|
897
|
-
const tenantScoped = rows.filter((row) => row.status === "active");
|
|
898
|
-
const applicable = tenantScoped.filter((row) => {
|
|
899
|
-
if (!row.workspaceId) {
|
|
900
|
-
return true;
|
|
901
|
-
}
|
|
902
|
-
if (!workspaceIdString) {
|
|
903
|
-
return false;
|
|
904
|
-
}
|
|
905
|
-
return String(row.workspaceId) === workspaceIdString;
|
|
906
|
-
});
|
|
907
|
-
return dedupeRegistryRows([
|
|
908
|
-
...DEFAULT_AUDIENCES,
|
|
909
|
-
...applicable.map(
|
|
910
|
-
(row) => normalizeRegistryRow({
|
|
911
|
-
audienceKey: row.audienceKey,
|
|
912
|
-
audienceLabel: row.audienceLabel,
|
|
913
|
-
audienceClass: row.audienceClass,
|
|
914
|
-
workspaceId: row.workspaceId
|
|
915
|
-
})
|
|
916
|
-
)
|
|
917
|
-
]);
|
|
918
|
-
}
|
|
919
|
-
async function listAudienceRegistryRows(ctx, args) {
|
|
920
|
-
return queryRegistryRows(ctx, args);
|
|
921
|
-
}
|
|
922
|
-
var permissiveReturn = v.optional(v.any());
|
|
923
|
-
var looseJsonObject = v.record(v.string(), v.any());
|
|
924
|
-
var looseJsonArray = v.array(v.any());
|
|
925
|
-
v.union(
|
|
926
|
-
v.string(),
|
|
927
|
-
v.number(),
|
|
928
|
-
v.boolean(),
|
|
929
|
-
v.null(),
|
|
930
|
-
looseJsonObject,
|
|
931
|
-
looseJsonArray
|
|
932
|
-
);
|
|
933
|
-
var api2 = anyApi;
|
|
934
|
-
componentsGeneric();
|
|
935
12
|
var internal = anyApi;
|
|
936
13
|
var internalMutation = internalMutationGeneric;
|
|
937
14
|
var internalQuery = internalQueryGeneric;
|
|
938
15
|
var mutation = mutationGeneric;
|
|
939
16
|
var query = queryGeneric;
|
|
940
17
|
|
|
18
|
+
// src/debug.ts
|
|
19
|
+
function isGraphPrimitiveDebugEnabled() {
|
|
20
|
+
const env = globalThis.process?.env;
|
|
21
|
+
return env?.LUCERN_COMPAT_FALLBACK_DEBUG === "1" || env?.LUCERN_GRAPH_DEBUG === "1";
|
|
22
|
+
}
|
|
23
|
+
function debugGraphPrimitiveFallback(message, context) {
|
|
24
|
+
if (!isGraphPrimitiveDebugEnabled()) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
console.debug(message, context ?? {});
|
|
28
|
+
}
|
|
29
|
+
|
|
941
30
|
// src/embeddingTrigger.ts
|
|
942
31
|
async function scheduleEmbeddingGeneration(args) {
|
|
943
32
|
try {
|
|
@@ -955,7 +44,15 @@ async function scheduleEmbeddingGeneration(args) {
|
|
|
955
44
|
confidence: args.confidence
|
|
956
45
|
}
|
|
957
46
|
);
|
|
958
|
-
} catch {
|
|
47
|
+
} catch (error) {
|
|
48
|
+
debugGraphPrimitiveFallback(
|
|
49
|
+
"[embeddingTrigger] Failed to schedule embedding generation",
|
|
50
|
+
{
|
|
51
|
+
error,
|
|
52
|
+
nodeId: String(args.nodeId),
|
|
53
|
+
nodeType: args.nodeType
|
|
54
|
+
}
|
|
55
|
+
);
|
|
959
56
|
}
|
|
960
57
|
}
|
|
961
58
|
|
|
@@ -972,96 +69,125 @@ function generateGlobalId() {
|
|
|
972
69
|
}
|
|
973
70
|
|
|
974
71
|
// src/topicProjectOverlay.ts
|
|
975
|
-
var
|
|
976
|
-
function
|
|
72
|
+
var LEGACY_SCOPE_FIELD = "graphScopeProjectId";
|
|
73
|
+
function readNonEmptyString(value) {
|
|
977
74
|
if (typeof value !== "string") {
|
|
978
75
|
return;
|
|
979
76
|
}
|
|
980
77
|
const normalized = value.trim();
|
|
981
78
|
return normalized.length > 0 ? normalized : void 0;
|
|
982
79
|
}
|
|
983
|
-
function
|
|
80
|
+
function readStringArray(value) {
|
|
984
81
|
if (!Array.isArray(value)) {
|
|
985
82
|
return [];
|
|
986
83
|
}
|
|
987
|
-
return value.map((entry) =>
|
|
84
|
+
return value.map((entry) => readNonEmptyString(entry)).filter((entry) => Boolean(entry));
|
|
988
85
|
}
|
|
989
|
-
function
|
|
86
|
+
function readMetadata(topic) {
|
|
990
87
|
return topic.metadata && typeof topic.metadata === "object" ? topic.metadata : {};
|
|
991
88
|
}
|
|
992
|
-
function
|
|
89
|
+
function readLegacyProjectId(value) {
|
|
993
90
|
if (!value) {
|
|
994
91
|
return;
|
|
995
92
|
}
|
|
996
|
-
return
|
|
93
|
+
return readNonEmptyString(value[LEGACY_SCOPE_FIELD]);
|
|
997
94
|
}
|
|
998
|
-
function
|
|
95
|
+
function coerceVisibility(value) {
|
|
999
96
|
return value === "private" || value === "team" || value === "firm" || value === "external" || value === "public" ? value : void 0;
|
|
1000
97
|
}
|
|
1001
|
-
function
|
|
98
|
+
function coerceStatus(value) {
|
|
1002
99
|
return value === "active" || value === "archived" || value === "watching" ? value : void 0;
|
|
1003
100
|
}
|
|
1004
|
-
function
|
|
1005
|
-
const explicit =
|
|
101
|
+
function mapProjectType(topic, metadata) {
|
|
102
|
+
const explicit = readNonEmptyString(metadata.projectType);
|
|
1006
103
|
if (explicit) {
|
|
1007
104
|
return explicit;
|
|
1008
105
|
}
|
|
1009
106
|
if (topic.type === "theme") {
|
|
1010
107
|
return "thematic";
|
|
1011
108
|
}
|
|
1012
|
-
return
|
|
109
|
+
return readNonEmptyString(topic.type) || "general";
|
|
1013
110
|
}
|
|
1014
|
-
function
|
|
1015
|
-
const metadata =
|
|
1016
|
-
return topic.type === "theme" || topic.type === "thematic" || topic.type === "deal" || topic.type === "monitoring" ||
|
|
111
|
+
function isProjectLikeTopic(topic) {
|
|
112
|
+
const metadata = readMetadata(topic);
|
|
113
|
+
return topic.type === "theme" || topic.type === "thematic" || topic.type === "deal" || topic.type === "monitoring" || readLegacyProjectId(topic) !== void 0 || readNonEmptyString(metadata.projectType) !== void 0;
|
|
1017
114
|
}
|
|
1018
115
|
function isMissingLucernChildComponentError(error) {
|
|
1019
|
-
const message =
|
|
116
|
+
const message = getErrorMessage(error);
|
|
1020
117
|
return message.includes(
|
|
1021
118
|
'Child component ComponentName(Identifier("lucern")) not found'
|
|
1022
119
|
) || message.includes("Child component") && message.includes("lucern") && message.includes("not found");
|
|
1023
120
|
}
|
|
1024
|
-
|
|
121
|
+
function getErrorMessage(error) {
|
|
122
|
+
if (error instanceof Error) {
|
|
123
|
+
return error.message;
|
|
124
|
+
}
|
|
125
|
+
if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") {
|
|
126
|
+
return error.message;
|
|
127
|
+
}
|
|
128
|
+
return "unknown error";
|
|
129
|
+
}
|
|
130
|
+
async function resolveTopicDoc(ctx, scopeId) {
|
|
1025
131
|
if (ctx?.db && typeof ctx.db.get === "function") {
|
|
1026
132
|
try {
|
|
1027
|
-
const directTopic = await ctx.db.get(
|
|
133
|
+
const directTopic = await ctx.db.get(
|
|
134
|
+
scopeId
|
|
135
|
+
);
|
|
1028
136
|
if (directTopic) {
|
|
1029
137
|
return directTopic;
|
|
1030
138
|
}
|
|
1031
|
-
} catch {
|
|
139
|
+
} catch (error) {
|
|
140
|
+
debugGraphPrimitiveFallback(
|
|
141
|
+
"[topicProjectOverlay] Failed to resolve topic by direct ID",
|
|
142
|
+
{
|
|
143
|
+
error,
|
|
144
|
+
scopeId
|
|
145
|
+
}
|
|
146
|
+
);
|
|
1032
147
|
}
|
|
1033
148
|
}
|
|
1034
149
|
if (typeof ctx.runQuery !== "function") {
|
|
1035
150
|
return null;
|
|
1036
151
|
}
|
|
1037
152
|
try {
|
|
1038
|
-
const topic = await ctx.runQuery(
|
|
153
|
+
const topic = await ctx.runQuery(api.topics.get, {
|
|
1039
154
|
id: String(scopeId)
|
|
1040
155
|
});
|
|
1041
156
|
if (topic?.name !== void 0 && topic?.type !== void 0) {
|
|
1042
157
|
return topic;
|
|
1043
158
|
}
|
|
1044
|
-
} catch {
|
|
159
|
+
} catch (error) {
|
|
160
|
+
debugGraphPrimitiveFallback(
|
|
161
|
+
"[topicProjectOverlay] Failed to resolve topic by ID query",
|
|
162
|
+
{
|
|
163
|
+
error,
|
|
164
|
+
scopeId
|
|
165
|
+
}
|
|
166
|
+
);
|
|
1045
167
|
}
|
|
1046
168
|
try {
|
|
1047
|
-
const topic = await ctx.runQuery(
|
|
169
|
+
const topic = await ctx.runQuery(api.topics.getByLegacyScopeId, {
|
|
1048
170
|
projectId: String(scopeId)
|
|
1049
171
|
});
|
|
1050
172
|
if (topic?.name !== void 0 && topic?.type !== void 0) {
|
|
1051
173
|
return topic;
|
|
1052
174
|
}
|
|
1053
|
-
} catch {
|
|
175
|
+
} catch (error) {
|
|
176
|
+
debugGraphPrimitiveFallback(
|
|
177
|
+
"[topicProjectOverlay] Failed to resolve topic by legacy scope ID",
|
|
178
|
+
{ error, scopeId }
|
|
179
|
+
);
|
|
1054
180
|
}
|
|
1055
181
|
return null;
|
|
1056
182
|
}
|
|
1057
|
-
function
|
|
1058
|
-
const metadata =
|
|
183
|
+
function materializeTopicProjectOverlay(topic, idMode = "legacy") {
|
|
184
|
+
const metadata = readMetadata(topic);
|
|
1059
185
|
const topicId = String(topic._id);
|
|
1060
|
-
const legacyProjectId =
|
|
186
|
+
const legacyProjectId = readLegacyProjectId(topic) || readLegacyProjectId(metadata) || readNonEmptyString(metadata.legacyProjectId);
|
|
1061
187
|
const storageProjectId = legacyProjectId || topicId;
|
|
1062
188
|
const outwardId = idMode === "topic" ? topicId : storageProjectId;
|
|
1063
|
-
const visibility =
|
|
1064
|
-
const status =
|
|
189
|
+
const visibility = coerceVisibility(topic.visibility) || coerceVisibility(metadata.visibility) || "private";
|
|
190
|
+
const status = coerceStatus(topic.status) || coerceStatus(metadata.status) || "active";
|
|
1065
191
|
const createdAt = typeof topic.createdAt === "number" ? topic.createdAt : typeof topic._creationTime === "number" ? topic._creationTime : 0;
|
|
1066
192
|
const updatedAt = typeof topic.updatedAt === "number" ? topic.updatedAt : typeof metadata.updatedAt === "number" ? metadata.updatedAt : createdAt;
|
|
1067
193
|
return {
|
|
@@ -1071,16 +197,16 @@ function materializeTopicProjectOverlay2(topic, idMode = "legacy") {
|
|
|
1071
197
|
topicId,
|
|
1072
198
|
storageProjectId,
|
|
1073
199
|
legacyProjectId,
|
|
1074
|
-
name:
|
|
1075
|
-
type:
|
|
1076
|
-
description:
|
|
1077
|
-
ownerId:
|
|
1078
|
-
sharedWith:
|
|
200
|
+
name: readNonEmptyString(topic.name) || "Untitled Theme",
|
|
201
|
+
type: mapProjectType(topic, metadata),
|
|
202
|
+
description: readNonEmptyString(topic.description),
|
|
203
|
+
ownerId: readNonEmptyString(metadata.ownerId) || readNonEmptyString(topic.createdBy) || "system",
|
|
204
|
+
sharedWith: readStringArray(metadata.sharedWith),
|
|
1079
205
|
visibility,
|
|
1080
|
-
tenantId:
|
|
1081
|
-
workspaceId:
|
|
206
|
+
tenantId: readNonEmptyString(topic.tenantId) || readNonEmptyString(metadata.tenantId),
|
|
207
|
+
workspaceId: readNonEmptyString(topic.workspaceId) || readNonEmptyString(metadata.workspaceId),
|
|
1082
208
|
status,
|
|
1083
|
-
tags:
|
|
209
|
+
tags: readStringArray(metadata.tags),
|
|
1084
210
|
chatCount: typeof metadata.chatCount === "number" ? metadata.chatCount : 0,
|
|
1085
211
|
artifactCount: typeof metadata.artifactCount === "number" ? metadata.artifactCount : 0,
|
|
1086
212
|
lastActivityAt: typeof metadata.lastActivityAt === "number" ? metadata.lastActivityAt : updatedAt,
|
|
@@ -1089,38 +215,42 @@ function materializeTopicProjectOverlay2(topic, idMode = "legacy") {
|
|
|
1089
215
|
updatedAt
|
|
1090
216
|
};
|
|
1091
217
|
}
|
|
1092
|
-
async function
|
|
1093
|
-
const topic = await
|
|
218
|
+
async function resolveTopicProjectOverlay(ctx, scopeId, options = {}) {
|
|
219
|
+
const topic = await resolveTopicDoc(ctx, scopeId);
|
|
1094
220
|
if (!topic) {
|
|
1095
221
|
return null;
|
|
1096
222
|
}
|
|
1097
|
-
if (options.projectLikeOnly !== false && !
|
|
223
|
+
if (options.projectLikeOnly !== false && !isProjectLikeTopic(topic)) {
|
|
1098
224
|
return null;
|
|
1099
225
|
}
|
|
1100
|
-
return
|
|
226
|
+
return materializeTopicProjectOverlay(topic, options.idMode);
|
|
1101
227
|
}
|
|
1102
|
-
async function
|
|
228
|
+
async function listTopicProjectOverlays(ctx, options = {}) {
|
|
1103
229
|
let allTopics = [];
|
|
1104
230
|
if (ctx?.db?.query && typeof ctx.db.query === "function") {
|
|
1105
231
|
try {
|
|
1106
232
|
allTopics = await ctx.db.query("topics").collect();
|
|
1107
|
-
} catch {
|
|
233
|
+
} catch (error) {
|
|
234
|
+
debugGraphPrimitiveFallback(
|
|
235
|
+
"[topicProjectOverlay] Failed to read topics table; falling back to API",
|
|
236
|
+
{ error }
|
|
237
|
+
);
|
|
1108
238
|
allTopics = [];
|
|
1109
239
|
}
|
|
1110
240
|
}
|
|
1111
241
|
if (allTopics.length === 0 && typeof ctx.runQuery === "function") {
|
|
1112
|
-
allTopics = (await ctx.runQuery(
|
|
242
|
+
allTopics = (await ctx.runQuery(api.topics.list, {}) ?? []) || [];
|
|
1113
243
|
}
|
|
1114
244
|
return allTopics.filter(
|
|
1115
|
-
(topic) => options.projectLikeOnly === false ||
|
|
1116
|
-
).map((topic) =>
|
|
245
|
+
(topic) => options.projectLikeOnly === false || isProjectLikeTopic(topic)
|
|
246
|
+
).map((topic) => materializeTopicProjectOverlay(topic, options.idMode));
|
|
1117
247
|
}
|
|
1118
248
|
async function patchTopicProjectOverlay(ctx, scopeId, value) {
|
|
1119
|
-
const topic = await
|
|
249
|
+
const topic = await resolveTopicDoc(ctx, scopeId);
|
|
1120
250
|
if (!topic) {
|
|
1121
251
|
return null;
|
|
1122
252
|
}
|
|
1123
|
-
const nextMetadata = { ...
|
|
253
|
+
const nextMetadata = { ...readMetadata(topic) };
|
|
1124
254
|
const patch = {};
|
|
1125
255
|
const topicUpdateArgs = {
|
|
1126
256
|
id: String(topic._id)
|
|
@@ -1145,7 +275,7 @@ async function patchTopicProjectOverlay(ctx, scopeId, value) {
|
|
|
1145
275
|
`patchTopicProjectOverlay cannot mutate ${key} via component-owned topics`
|
|
1146
276
|
);
|
|
1147
277
|
case "status": {
|
|
1148
|
-
const status =
|
|
278
|
+
const status = coerceStatus(rawValue);
|
|
1149
279
|
if (status) {
|
|
1150
280
|
patch.status = status;
|
|
1151
281
|
topicUpdateArgs.status = status;
|
|
@@ -1153,7 +283,7 @@ async function patchTopicProjectOverlay(ctx, scopeId, value) {
|
|
|
1153
283
|
break;
|
|
1154
284
|
}
|
|
1155
285
|
case "visibility": {
|
|
1156
|
-
const visibility =
|
|
286
|
+
const visibility = coerceVisibility(rawValue);
|
|
1157
287
|
if (visibility) {
|
|
1158
288
|
patch.visibility = visibility;
|
|
1159
289
|
topicUpdateArgs.visibility = visibility;
|
|
@@ -1161,7 +291,7 @@ async function patchTopicProjectOverlay(ctx, scopeId, value) {
|
|
|
1161
291
|
break;
|
|
1162
292
|
}
|
|
1163
293
|
case "type": {
|
|
1164
|
-
const projectType =
|
|
294
|
+
const projectType = readNonEmptyString(rawValue);
|
|
1165
295
|
if (projectType) {
|
|
1166
296
|
nextMetadata.projectType = projectType;
|
|
1167
297
|
} else {
|
|
@@ -1185,7 +315,7 @@ async function patchTopicProjectOverlay(ctx, scopeId, value) {
|
|
|
1185
315
|
topicUpdateArgs.metadata = nextMetadata;
|
|
1186
316
|
if (typeof ctx.runMutation === "function") {
|
|
1187
317
|
try {
|
|
1188
|
-
await ctx.runMutation(
|
|
318
|
+
await ctx.runMutation(api.topics.update, topicUpdateArgs);
|
|
1189
319
|
} catch (error) {
|
|
1190
320
|
if (!isMissingLucernChildComponentError(error) || !ctx?.db || typeof ctx.db.patch !== "function") {
|
|
1191
321
|
throw error;
|
|
@@ -1199,19 +329,28 @@ async function patchTopicProjectOverlay(ctx, scopeId, value) {
|
|
|
1199
329
|
"Cannot patch topic without component adapter (ctx.runMutation unavailable)"
|
|
1200
330
|
);
|
|
1201
331
|
}
|
|
1202
|
-
return
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
}
|
|
1208
|
-
);
|
|
332
|
+
return materializeTopicProjectOverlay({
|
|
333
|
+
...topic,
|
|
334
|
+
...patch,
|
|
335
|
+
metadata: nextMetadata
|
|
336
|
+
});
|
|
1209
337
|
}
|
|
1210
338
|
|
|
1211
339
|
// src/resolvers.ts
|
|
1212
340
|
function isMissingLucernChildComponentError2(error) {
|
|
1213
|
-
const message =
|
|
1214
|
-
return message.includes(
|
|
341
|
+
const message = getErrorMessage2(error);
|
|
342
|
+
return message.includes(
|
|
343
|
+
'Child component ComponentName(Identifier("lucern")) not found'
|
|
344
|
+
) || message.includes("Child component") && message.includes("lucern") && message.includes("not found");
|
|
345
|
+
}
|
|
346
|
+
function getErrorMessage2(error) {
|
|
347
|
+
if (error instanceof Error) {
|
|
348
|
+
return error.message;
|
|
349
|
+
}
|
|
350
|
+
if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") {
|
|
351
|
+
return error.message;
|
|
352
|
+
}
|
|
353
|
+
return "unknown error";
|
|
1215
354
|
}
|
|
1216
355
|
function isAdvisoryTopicPatch(value) {
|
|
1217
356
|
const advisoryKeys = /* @__PURE__ */ new Set(["lastActivityAt", "updatedAt"]);
|
|
@@ -1225,52 +364,47 @@ async function patchProjectWithTolerance(ctx, projectId, value) {
|
|
|
1225
364
|
if (!isAdvisoryTopicPatch(value) || !isMissingLucernChildComponentError2(error)) {
|
|
1226
365
|
throw error;
|
|
1227
366
|
}
|
|
1228
|
-
console.warn(
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
367
|
+
console.warn(
|
|
368
|
+
"[lucern graph-primitives] Non-fatal advisory topic patch failure",
|
|
369
|
+
{
|
|
370
|
+
projectId,
|
|
371
|
+
keys: Object.keys(value),
|
|
372
|
+
error: getErrorMessage2(error)
|
|
373
|
+
}
|
|
374
|
+
);
|
|
1233
375
|
}
|
|
1234
376
|
}
|
|
1235
|
-
function
|
|
377
|
+
function defaultResolvers() {
|
|
1236
378
|
return {
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
async listTopics(ctx) {
|
|
1247
|
-
return await listTopicProjectOverlays2(ctx, {
|
|
1248
|
-
idMode: "legacy"
|
|
1249
|
-
});
|
|
1250
|
-
},
|
|
1251
|
-
async getFinalArtifact(ctx, artifactId) {
|
|
1252
|
-
return await ctx.db.get(artifactId);
|
|
1253
|
-
}
|
|
379
|
+
getProject: (ctx, projectId) => resolveTopicProjectOverlay(ctx, projectId, {
|
|
380
|
+
idMode: "legacy",
|
|
381
|
+
projectLikeOnly: false
|
|
382
|
+
}),
|
|
383
|
+
patchProject: (ctx, projectId, value) => patchProjectWithTolerance(ctx, projectId, value),
|
|
384
|
+
listTopics: (ctx) => listTopicProjectOverlays(ctx, {
|
|
385
|
+
idMode: "legacy"
|
|
386
|
+
}),
|
|
387
|
+
getFinalArtifact: (ctx, artifactId) => ctx.db.get(artifactId)
|
|
1254
388
|
};
|
|
1255
389
|
}
|
|
1256
|
-
var
|
|
390
|
+
var resolverOverrides = {};
|
|
1257
391
|
function resolveGraphPrimitivesAppResolvers(_ctx) {
|
|
1258
392
|
return {
|
|
1259
|
-
...
|
|
1260
|
-
...
|
|
393
|
+
...defaultResolvers(),
|
|
394
|
+
...resolverOverrides
|
|
1261
395
|
};
|
|
1262
396
|
}
|
|
1263
|
-
var
|
|
397
|
+
var LEGACY_SCOPE_FIELD2 = "graphScopeProjectId";
|
|
1264
398
|
function asMappedProjectId(topic) {
|
|
1265
399
|
if (!topic) {
|
|
1266
400
|
return;
|
|
1267
401
|
}
|
|
1268
|
-
const directLegacyProjectId = normalizeScopeValue(topic[
|
|
402
|
+
const directLegacyProjectId = normalizeScopeValue(topic[LEGACY_SCOPE_FIELD2]);
|
|
1269
403
|
if (directLegacyProjectId) {
|
|
1270
404
|
return directLegacyProjectId;
|
|
1271
405
|
}
|
|
1272
406
|
const metadata = topic.metadata || {};
|
|
1273
|
-
const candidate = metadata[
|
|
407
|
+
const candidate = metadata[LEGACY_SCOPE_FIELD2] || metadata.legacyProjectId || metadata.projectId || metadata.scopeProjectId;
|
|
1274
408
|
return candidate ? candidate : void 0;
|
|
1275
409
|
}
|
|
1276
410
|
function normalizeScopeValue(value) {
|
|
@@ -1299,9 +433,16 @@ async function findTopicsByScopeAlias(ctx, scopeId) {
|
|
|
1299
433
|
try {
|
|
1300
434
|
return await ctx.db.query("topics").withIndex(
|
|
1301
435
|
"by_graph_scope_project",
|
|
1302
|
-
(q) => q.eq(
|
|
436
|
+
(q) => q.eq(LEGACY_SCOPE_FIELD2, scopeId)
|
|
1303
437
|
).collect();
|
|
1304
|
-
} catch {
|
|
438
|
+
} catch (error) {
|
|
439
|
+
debugGraphPrimitiveFallback(
|
|
440
|
+
"[topicScope] Failed to resolve scope alias via index",
|
|
441
|
+
{
|
|
442
|
+
error,
|
|
443
|
+
scopeId
|
|
444
|
+
}
|
|
445
|
+
);
|
|
1305
446
|
const topics = await ctx.db.query("topics").collect();
|
|
1306
447
|
return topics.filter((topic) => {
|
|
1307
448
|
const normalizedGlobalId = normalizeScopeValue(topic.globalId);
|
|
@@ -1315,10 +456,17 @@ async function tryResolveHostTopicById(ctx, topicId) {
|
|
|
1315
456
|
return null;
|
|
1316
457
|
}
|
|
1317
458
|
try {
|
|
1318
|
-
return await ctx.runQuery(
|
|
459
|
+
return await ctx.runQuery(api.topics.get, {
|
|
1319
460
|
id: topicId
|
|
1320
461
|
}) ?? null;
|
|
1321
|
-
} catch {
|
|
462
|
+
} catch (error) {
|
|
463
|
+
debugGraphPrimitiveFallback(
|
|
464
|
+
"[topicScope] Failed to resolve topic by host query",
|
|
465
|
+
{
|
|
466
|
+
error,
|
|
467
|
+
topicId
|
|
468
|
+
}
|
|
469
|
+
);
|
|
1322
470
|
return null;
|
|
1323
471
|
}
|
|
1324
472
|
}
|
|
@@ -1327,10 +475,17 @@ async function tryResolveHostTopicByLegacyScope(ctx, legacyScopeId) {
|
|
|
1327
475
|
return null;
|
|
1328
476
|
}
|
|
1329
477
|
try {
|
|
1330
|
-
return await ctx.runQuery(
|
|
478
|
+
return await ctx.runQuery(api.topics.getByLegacyScopeId, {
|
|
1331
479
|
projectId: legacyScopeId
|
|
1332
480
|
}) ?? null;
|
|
1333
|
-
} catch {
|
|
481
|
+
} catch (error) {
|
|
482
|
+
debugGraphPrimitiveFallback(
|
|
483
|
+
"[topicScope] Failed to resolve topic by legacy scope",
|
|
484
|
+
{
|
|
485
|
+
error,
|
|
486
|
+
legacyScopeId
|
|
487
|
+
}
|
|
488
|
+
);
|
|
1334
489
|
return null;
|
|
1335
490
|
}
|
|
1336
491
|
}
|
|
@@ -1359,8 +514,17 @@ async function resolveTopicProjectScope(ctx, args) {
|
|
|
1359
514
|
if (args.topicId) {
|
|
1360
515
|
let topic = null;
|
|
1361
516
|
try {
|
|
1362
|
-
topic = await ctx.db.get(
|
|
1363
|
-
|
|
517
|
+
topic = await ctx.db.get(
|
|
518
|
+
args.topicId
|
|
519
|
+
);
|
|
520
|
+
} catch (error) {
|
|
521
|
+
debugGraphPrimitiveFallback(
|
|
522
|
+
"[topicScope] Failed to load topic by direct id",
|
|
523
|
+
{
|
|
524
|
+
error,
|
|
525
|
+
topicId: args.topicId
|
|
526
|
+
}
|
|
527
|
+
);
|
|
1364
528
|
}
|
|
1365
529
|
if (!topic) {
|
|
1366
530
|
topic = await tryResolveHostTopicById(ctx, String(args.topicId));
|
|
@@ -1397,7 +561,14 @@ async function resolveTopicProjectScope(ctx, args) {
|
|
|
1397
561
|
directTopic = await ctx.db.get(
|
|
1398
562
|
args.projectId
|
|
1399
563
|
);
|
|
1400
|
-
} catch {
|
|
564
|
+
} catch (error) {
|
|
565
|
+
debugGraphPrimitiveFallback(
|
|
566
|
+
"[topicScope] Failed to load direct project topic",
|
|
567
|
+
{
|
|
568
|
+
error,
|
|
569
|
+
projectId: args.projectId
|
|
570
|
+
}
|
|
571
|
+
);
|
|
1401
572
|
}
|
|
1402
573
|
if (directTopic) {
|
|
1403
574
|
const inherited = await resolveInheritedWorkspaceScope(ctx, directTopic);
|
|
@@ -1446,61 +617,6 @@ var optionalScopeArgs = {
|
|
|
1446
617
|
projectId: v.optional(v.string()),
|
|
1447
618
|
topicId: v.optional(v.string())
|
|
1448
619
|
};
|
|
1449
|
-
|
|
1450
|
-
// ../../packages/contracts/src/schema-helpers/spine/tables/epistemicNodes.ts
|
|
1451
|
-
var NODE_TYPES = [
|
|
1452
|
-
"decision",
|
|
1453
|
-
"belief",
|
|
1454
|
-
"question",
|
|
1455
|
-
"theme",
|
|
1456
|
-
"deal",
|
|
1457
|
-
"topic",
|
|
1458
|
-
"claim",
|
|
1459
|
-
"evidence",
|
|
1460
|
-
"synthesis",
|
|
1461
|
-
"answer",
|
|
1462
|
-
"atomic_fact",
|
|
1463
|
-
"excerpt",
|
|
1464
|
-
"source",
|
|
1465
|
-
"company",
|
|
1466
|
-
"person",
|
|
1467
|
-
"investor",
|
|
1468
|
-
"function",
|
|
1469
|
-
"value_chain"
|
|
1470
|
-
];
|
|
1471
|
-
function isNodeType(value) {
|
|
1472
|
-
return NODE_TYPES.includes(value);
|
|
1473
|
-
}
|
|
1474
|
-
function getLayerForNodeType(type) {
|
|
1475
|
-
switch (type) {
|
|
1476
|
-
case "decision":
|
|
1477
|
-
return "L4";
|
|
1478
|
-
case "belief":
|
|
1479
|
-
case "question":
|
|
1480
|
-
case "theme":
|
|
1481
|
-
case "deal":
|
|
1482
|
-
return "L3";
|
|
1483
|
-
case "claim":
|
|
1484
|
-
case "evidence":
|
|
1485
|
-
case "synthesis":
|
|
1486
|
-
case "answer":
|
|
1487
|
-
return "L2";
|
|
1488
|
-
case "atomic_fact":
|
|
1489
|
-
case "excerpt":
|
|
1490
|
-
case "source":
|
|
1491
|
-
return "L1";
|
|
1492
|
-
case "topic":
|
|
1493
|
-
return "organizational";
|
|
1494
|
-
case "company":
|
|
1495
|
-
case "person":
|
|
1496
|
-
case "investor":
|
|
1497
|
-
case "function":
|
|
1498
|
-
case "value_chain":
|
|
1499
|
-
return "ontological";
|
|
1500
|
-
}
|
|
1501
|
-
}
|
|
1502
|
-
|
|
1503
|
-
// src/workspaceIsolation.ts
|
|
1504
620
|
function normalizeScopeValue2(value) {
|
|
1505
621
|
if (typeof value !== "string") {
|
|
1506
622
|
return;
|
|
@@ -1703,7 +819,15 @@ async function resolveEvidenceScopeOrNull(ctx, args) {
|
|
|
1703
819
|
projectId: args.projectId ?? void 0,
|
|
1704
820
|
topicId: args.topicId ?? void 0
|
|
1705
821
|
});
|
|
1706
|
-
} catch {
|
|
822
|
+
} catch (error) {
|
|
823
|
+
debugGraphPrimitiveFallback(
|
|
824
|
+
"[epistemicEvidence] Failed to resolve evidence scope",
|
|
825
|
+
{
|
|
826
|
+
error,
|
|
827
|
+
projectId: args.projectId,
|
|
828
|
+
topicId: args.topicId
|
|
829
|
+
}
|
|
830
|
+
);
|
|
1707
831
|
return null;
|
|
1708
832
|
}
|
|
1709
833
|
}
|
|
@@ -1767,6 +891,7 @@ var create = mutation({
|
|
|
1767
891
|
sourceUrl: v.optional(v.string()),
|
|
1768
892
|
sourceQuestionId: v.optional(v.string()),
|
|
1769
893
|
userId: v.string(),
|
|
894
|
+
rationale: v.string(),
|
|
1770
895
|
// Classification fields (from AI tools)
|
|
1771
896
|
methodology: v.optional(v.string()),
|
|
1772
897
|
informationAsymmetry: v.optional(v.string()),
|
|
@@ -1824,6 +949,7 @@ var create = mutation({
|
|
|
1824
949
|
externalSourceType: args.externalSourceType,
|
|
1825
950
|
sourceUrl: args.sourceUrl,
|
|
1826
951
|
sourceQuestionId: args.sourceQuestionId,
|
|
952
|
+
rationale: args.rationale,
|
|
1827
953
|
linkedBeliefNodeId: args.linkedBeliefNodeId,
|
|
1828
954
|
evidenceRelation: args.evidenceRelation,
|
|
1829
955
|
confidence: args.confidence,
|
|
@@ -1877,6 +1003,7 @@ var create = mutation({
|
|
|
1877
1003
|
changedBy: args.userId,
|
|
1878
1004
|
isAgent: false,
|
|
1879
1005
|
projectId: scope.projectId,
|
|
1006
|
+
rationale: args.rationale,
|
|
1880
1007
|
newState: {
|
|
1881
1008
|
text: args.text.slice(0, 200),
|
|
1882
1009
|
kind,
|
|
@@ -2076,7 +1203,15 @@ var getByProject = query({
|
|
|
2076
1203
|
projectId: args.projectId,
|
|
2077
1204
|
topicId: args.topicId
|
|
2078
1205
|
});
|
|
2079
|
-
} catch {
|
|
1206
|
+
} catch (error) {
|
|
1207
|
+
debugGraphPrimitiveFallback(
|
|
1208
|
+
"[epistemicEvidence] Failed to resolve getByProject scope",
|
|
1209
|
+
{
|
|
1210
|
+
error,
|
|
1211
|
+
projectId: args.projectId,
|
|
1212
|
+
topicId: args.topicId
|
|
1213
|
+
}
|
|
1214
|
+
);
|
|
2080
1215
|
return [];
|
|
2081
1216
|
}
|
|
2082
1217
|
if (args.userId) {
|
|
@@ -2111,7 +1246,9 @@ var getByTopic = query({
|
|
|
2111
1246
|
handler: async (ctx, args) => {
|
|
2112
1247
|
const pageSize = clampEvidenceLimit(args.limit);
|
|
2113
1248
|
const scanLimit = Math.min(pageSize * 3, MAX_EVIDENCE_PAGE_SIZE);
|
|
2114
|
-
const scope = await resolveTopicProjectScope(ctx, {
|
|
1249
|
+
const scope = await resolveTopicProjectScope(ctx, {
|
|
1250
|
+
topicId: args.topicId
|
|
1251
|
+
});
|
|
2115
1252
|
const topicNodes = await ctx.db.query("epistemicNodes").withIndex(
|
|
2116
1253
|
"by_topic_type",
|
|
2117
1254
|
(q) => q.eq("topicId", scope.topicId).eq("nodeType", "evidence")
|
|
@@ -2221,7 +1358,9 @@ var internalGetByTopic = internalQuery({
|
|
|
2221
1358
|
const pageSize = clampEvidenceLimit(args.limit, 500);
|
|
2222
1359
|
const scanLimit = Math.min(pageSize * 3, MAX_EVIDENCE_PAGE_SIZE);
|
|
2223
1360
|
const audienceMode = args.audienceMode ?? "internal";
|
|
2224
|
-
const scope = await resolveTopicProjectScope(ctx, {
|
|
1361
|
+
const scope = await resolveTopicProjectScope(ctx, {
|
|
1362
|
+
topicId: args.topicId
|
|
1363
|
+
});
|
|
2225
1364
|
const registryRows = await listAudienceRegistryRows(ctx, {
|
|
2226
1365
|
tenantId: scope.tenantId,
|
|
2227
1366
|
workspaceId: scope.workspaceId
|
|
@@ -2283,6 +1422,7 @@ var internalCreate = internalMutation({
|
|
|
2283
1422
|
sourceUrl: v.optional(v.string()),
|
|
2284
1423
|
sourceQuestionId: v.optional(v.string()),
|
|
2285
1424
|
userId: v.string(),
|
|
1425
|
+
rationale: v.string(),
|
|
2286
1426
|
linkedBeliefNodeId: v.optional(v.id("epistemicNodes")),
|
|
2287
1427
|
evidenceRelation: v.optional(v.string()),
|
|
2288
1428
|
confidence: v.optional(v.number()),
|
|
@@ -2346,6 +1486,7 @@ var internalCreate = internalMutation({
|
|
|
2346
1486
|
externalSourceType: args.externalSourceType,
|
|
2347
1487
|
sourceUrl: args.sourceUrl,
|
|
2348
1488
|
sourceQuestionId: args.sourceQuestionId,
|
|
1489
|
+
rationale: args.rationale,
|
|
2349
1490
|
linkedBeliefNodeId: args.linkedBeliefNodeId,
|
|
2350
1491
|
evidenceRelation: args.evidenceRelation,
|
|
2351
1492
|
confidence: args.confidence,
|
|
@@ -2360,6 +1501,7 @@ var internalCreate = internalMutation({
|
|
|
2360
1501
|
changedBy: args.userId,
|
|
2361
1502
|
isAgent: false,
|
|
2362
1503
|
projectId: scope.projectId,
|
|
1504
|
+
rationale: args.rationale,
|
|
2363
1505
|
newState: {
|
|
2364
1506
|
text: args.text.slice(0, 200),
|
|
2365
1507
|
kind,
|