@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,939 +1,51 @@
|
|
|
1
1
|
import { v } from 'convex/values';
|
|
2
|
-
import {
|
|
2
|
+
import { checkProjectAccess, checkScopeAccess } from '@lucern/access-control/access';
|
|
3
|
+
import { normalizeAudienceKey, canAudienceClassAccess, classFromAudienceKey } from '@lucern/access-control/audience';
|
|
4
|
+
import { listAudienceRegistryRows } from '@lucern/access-control/audienceRegistry';
|
|
5
|
+
import { getCurrentUserId } from '@lucern/access-control/auth';
|
|
6
|
+
import { permissiveReturn } from '@lucern/contracts/schema-helpers/validators';
|
|
7
|
+
import { componentsGeneric, anyApi, mutationGeneric, queryGeneric, internalQueryGeneric, internalMutationGeneric } from 'convex/server';
|
|
8
|
+
import { isNodeType, getLayerForNodeType } from '@lucern/contracts/schema-helpers/spine/tables/epistemicNodes';
|
|
3
9
|
|
|
4
10
|
// src/epistemicQuestions.ts
|
|
5
11
|
var api = anyApi;
|
|
6
12
|
componentsGeneric();
|
|
13
|
+
var internal = anyApi;
|
|
14
|
+
var internalMutation = internalMutationGeneric;
|
|
15
|
+
var internalQuery = internalQueryGeneric;
|
|
16
|
+
var mutation = mutationGeneric;
|
|
17
|
+
var query = queryGeneric;
|
|
7
18
|
|
|
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));
|
|
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
|
-
var checkProjectAccess = checkTopicAccess;
|
|
791
|
-
|
|
792
|
-
// ../access-control/src/audience.ts
|
|
793
|
-
var AUDIENCE_CLASS_RANK = {
|
|
794
|
-
public: 0,
|
|
795
|
-
restricted_external: 1,
|
|
796
|
-
internal: 2
|
|
797
|
-
};
|
|
798
|
-
function normalizeKey(key) {
|
|
799
|
-
return (key ?? "").trim().toLowerCase().replace(/[^a-z0-9:_-]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "");
|
|
19
|
+
// src/debug.ts
|
|
20
|
+
function isGraphPrimitiveDebugEnabled() {
|
|
21
|
+
const env = globalThis.process?.env;
|
|
22
|
+
return env?.LUCERN_COMPAT_FALLBACK_DEBUG === "1" || env?.LUCERN_GRAPH_DEBUG === "1";
|
|
800
23
|
}
|
|
801
|
-
function
|
|
802
|
-
|
|
803
|
-
}
|
|
804
|
-
function classFromAudienceKey(audienceKey, fallback = "internal") {
|
|
805
|
-
const key = normalizeKey(audienceKey);
|
|
806
|
-
if (!key) {
|
|
807
|
-
return fallback;
|
|
24
|
+
function formatGraphPrimitiveError(error) {
|
|
25
|
+
if (error instanceof Error) {
|
|
26
|
+
return `${error.name}: ${error.message}`;
|
|
808
27
|
}
|
|
809
|
-
if (
|
|
810
|
-
return
|
|
28
|
+
if (typeof error === "string") {
|
|
29
|
+
return error;
|
|
811
30
|
}
|
|
812
|
-
if (
|
|
813
|
-
return "
|
|
31
|
+
if (error === null) {
|
|
32
|
+
return "null";
|
|
814
33
|
}
|
|
815
|
-
if (
|
|
816
|
-
return "
|
|
817
|
-
}
|
|
818
|
-
return fallback;
|
|
819
|
-
}
|
|
820
|
-
function canAudienceClassAccess(viewerClass, resourceClass) {
|
|
821
|
-
return AUDIENCE_CLASS_RANK[viewerClass] >= AUDIENCE_CLASS_RANK[resourceClass];
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
// ../access-control/src/audienceRegistry.ts
|
|
825
|
-
var DEFAULT_AUDIENCES = [
|
|
826
|
-
{
|
|
827
|
-
audienceKey: "internal",
|
|
828
|
-
audienceLabel: "Internal",
|
|
829
|
-
audienceClass: "internal"
|
|
830
|
-
},
|
|
831
|
-
{
|
|
832
|
-
audienceKey: "lp",
|
|
833
|
-
audienceLabel: "Limited Partners",
|
|
834
|
-
audienceClass: "restricted_external"
|
|
835
|
-
},
|
|
836
|
-
{
|
|
837
|
-
audienceKey: "public",
|
|
838
|
-
audienceLabel: "Public",
|
|
839
|
-
audienceClass: "public"
|
|
34
|
+
if (error === void 0) {
|
|
35
|
+
return "undefined";
|
|
840
36
|
}
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
public: 2
|
|
846
|
-
};
|
|
847
|
-
function normalizeRegistryRow(row) {
|
|
848
|
-
return {
|
|
849
|
-
audienceKey: normalizeAudienceKey(row.audienceKey),
|
|
850
|
-
audienceLabel: row.audienceLabel,
|
|
851
|
-
audienceClass: row.audienceClass,
|
|
852
|
-
workspaceId: row.workspaceId
|
|
853
|
-
};
|
|
854
|
-
}
|
|
855
|
-
function dedupeRegistryRows(rows) {
|
|
856
|
-
const byKey = /* @__PURE__ */ new Map();
|
|
857
|
-
for (const row of rows) {
|
|
858
|
-
const key = normalizeAudienceKey(row.audienceKey);
|
|
859
|
-
if (!key) {
|
|
860
|
-
continue;
|
|
861
|
-
}
|
|
862
|
-
const existing = byKey.get(key);
|
|
863
|
-
const isWorkspaceScoped = row.workspaceId !== void 0;
|
|
864
|
-
const existingWorkspaceScoped = existing?.workspaceId !== void 0;
|
|
865
|
-
if (!existing || isWorkspaceScoped && !existingWorkspaceScoped) {
|
|
866
|
-
byKey.set(key, {
|
|
867
|
-
...row,
|
|
868
|
-
audienceKey: key
|
|
869
|
-
});
|
|
870
|
-
}
|
|
37
|
+
try {
|
|
38
|
+
return JSON.stringify(error);
|
|
39
|
+
} catch {
|
|
40
|
+
return Object.prototype.toString.call(error);
|
|
871
41
|
}
|
|
872
|
-
const normalized = [...byKey.values()];
|
|
873
|
-
normalized.sort((a, b) => {
|
|
874
|
-
const classDelta = AUDIENCE_CLASS_PRIORITY[a.audienceClass] - AUDIENCE_CLASS_PRIORITY[b.audienceClass];
|
|
875
|
-
if (classDelta !== 0) {
|
|
876
|
-
return classDelta;
|
|
877
|
-
}
|
|
878
|
-
return a.audienceKey.localeCompare(b.audienceKey);
|
|
879
|
-
});
|
|
880
|
-
return normalized;
|
|
881
42
|
}
|
|
882
|
-
|
|
883
|
-
if (!
|
|
884
|
-
return
|
|
43
|
+
function debugGraphPrimitiveFallback(message, context) {
|
|
44
|
+
if (!isGraphPrimitiveDebugEnabled()) {
|
|
45
|
+
return;
|
|
885
46
|
}
|
|
886
|
-
|
|
887
|
-
const workspaceIdString = args.workspaceId ? String(args.workspaceId) : null;
|
|
888
|
-
const tenantScoped = rows.filter((row) => row.status === "active");
|
|
889
|
-
const applicable = tenantScoped.filter((row) => {
|
|
890
|
-
if (!row.workspaceId) {
|
|
891
|
-
return true;
|
|
892
|
-
}
|
|
893
|
-
if (!workspaceIdString) {
|
|
894
|
-
return false;
|
|
895
|
-
}
|
|
896
|
-
return String(row.workspaceId) === workspaceIdString;
|
|
897
|
-
});
|
|
898
|
-
return dedupeRegistryRows([
|
|
899
|
-
...DEFAULT_AUDIENCES,
|
|
900
|
-
...applicable.map(
|
|
901
|
-
(row) => normalizeRegistryRow({
|
|
902
|
-
audienceKey: row.audienceKey,
|
|
903
|
-
audienceLabel: row.audienceLabel,
|
|
904
|
-
audienceClass: row.audienceClass,
|
|
905
|
-
workspaceId: row.workspaceId
|
|
906
|
-
})
|
|
907
|
-
)
|
|
908
|
-
]);
|
|
909
|
-
}
|
|
910
|
-
async function listAudienceRegistryRows(ctx, args) {
|
|
911
|
-
return queryRegistryRows(ctx, args);
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
// ../access-control/src/auth.ts
|
|
915
|
-
async function getCurrentUserId(ctx) {
|
|
916
|
-
const identity = await ctx.auth.getUserIdentity();
|
|
917
|
-
return identity?.subject ?? null;
|
|
47
|
+
console.debug(message, context ?? {});
|
|
918
48
|
}
|
|
919
|
-
var permissiveReturn = v.optional(v.any());
|
|
920
|
-
var looseJsonObject = v.record(v.string(), v.any());
|
|
921
|
-
var looseJsonArray = v.array(v.any());
|
|
922
|
-
v.union(
|
|
923
|
-
v.string(),
|
|
924
|
-
v.number(),
|
|
925
|
-
v.boolean(),
|
|
926
|
-
v.null(),
|
|
927
|
-
looseJsonObject,
|
|
928
|
-
looseJsonArray
|
|
929
|
-
);
|
|
930
|
-
var api2 = anyApi;
|
|
931
|
-
componentsGeneric();
|
|
932
|
-
var internal = anyApi;
|
|
933
|
-
var internalMutation = internalMutationGeneric;
|
|
934
|
-
var internalQuery = internalQueryGeneric;
|
|
935
|
-
var mutation = mutationGeneric;
|
|
936
|
-
var query = queryGeneric;
|
|
937
49
|
|
|
938
50
|
// src/embeddingTrigger.ts
|
|
939
51
|
async function scheduleEmbeddingGeneration(args) {
|
|
@@ -952,7 +64,15 @@ async function scheduleEmbeddingGeneration(args) {
|
|
|
952
64
|
confidence: args.confidence
|
|
953
65
|
}
|
|
954
66
|
);
|
|
955
|
-
} catch {
|
|
67
|
+
} catch (error) {
|
|
68
|
+
debugGraphPrimitiveFallback(
|
|
69
|
+
"[embeddingTrigger] Failed to schedule embedding generation",
|
|
70
|
+
{
|
|
71
|
+
error,
|
|
72
|
+
nodeId: String(args.nodeId),
|
|
73
|
+
nodeType: args.nodeType
|
|
74
|
+
}
|
|
75
|
+
);
|
|
956
76
|
}
|
|
957
77
|
}
|
|
958
78
|
|
|
@@ -969,96 +89,125 @@ function generateGlobalId() {
|
|
|
969
89
|
}
|
|
970
90
|
|
|
971
91
|
// src/topicProjectOverlay.ts
|
|
972
|
-
var
|
|
973
|
-
function
|
|
92
|
+
var LEGACY_SCOPE_FIELD = "graphScopeProjectId";
|
|
93
|
+
function readNonEmptyString(value) {
|
|
974
94
|
if (typeof value !== "string") {
|
|
975
95
|
return;
|
|
976
96
|
}
|
|
977
97
|
const normalized = value.trim();
|
|
978
98
|
return normalized.length > 0 ? normalized : void 0;
|
|
979
99
|
}
|
|
980
|
-
function
|
|
100
|
+
function readStringArray(value) {
|
|
981
101
|
if (!Array.isArray(value)) {
|
|
982
102
|
return [];
|
|
983
103
|
}
|
|
984
|
-
return value.map((entry) =>
|
|
104
|
+
return value.map((entry) => readNonEmptyString(entry)).filter((entry) => Boolean(entry));
|
|
985
105
|
}
|
|
986
|
-
function
|
|
106
|
+
function readMetadata(topic) {
|
|
987
107
|
return topic.metadata && typeof topic.metadata === "object" ? topic.metadata : {};
|
|
988
108
|
}
|
|
989
|
-
function
|
|
109
|
+
function readLegacyProjectId(value) {
|
|
990
110
|
if (!value) {
|
|
991
111
|
return;
|
|
992
112
|
}
|
|
993
|
-
return
|
|
113
|
+
return readNonEmptyString(value[LEGACY_SCOPE_FIELD]);
|
|
994
114
|
}
|
|
995
|
-
function
|
|
115
|
+
function coerceVisibility(value) {
|
|
996
116
|
return value === "private" || value === "team" || value === "firm" || value === "external" || value === "public" ? value : void 0;
|
|
997
117
|
}
|
|
998
|
-
function
|
|
118
|
+
function coerceStatus(value) {
|
|
999
119
|
return value === "active" || value === "archived" || value === "watching" ? value : void 0;
|
|
1000
120
|
}
|
|
1001
|
-
function
|
|
1002
|
-
const explicit =
|
|
121
|
+
function mapProjectType(topic, metadata) {
|
|
122
|
+
const explicit = readNonEmptyString(metadata.projectType);
|
|
1003
123
|
if (explicit) {
|
|
1004
124
|
return explicit;
|
|
1005
125
|
}
|
|
1006
126
|
if (topic.type === "theme") {
|
|
1007
127
|
return "thematic";
|
|
1008
128
|
}
|
|
1009
|
-
return
|
|
129
|
+
return readNonEmptyString(topic.type) || "general";
|
|
1010
130
|
}
|
|
1011
|
-
function
|
|
1012
|
-
const metadata =
|
|
1013
|
-
return topic.type === "theme" || topic.type === "thematic" || topic.type === "deal" || topic.type === "monitoring" ||
|
|
131
|
+
function isProjectLikeTopic(topic) {
|
|
132
|
+
const metadata = readMetadata(topic);
|
|
133
|
+
return topic.type === "theme" || topic.type === "thematic" || topic.type === "deal" || topic.type === "monitoring" || readLegacyProjectId(topic) !== void 0 || readNonEmptyString(metadata.projectType) !== void 0;
|
|
1014
134
|
}
|
|
1015
135
|
function isMissingLucernChildComponentError(error) {
|
|
1016
|
-
const message =
|
|
136
|
+
const message = getErrorMessage(error);
|
|
1017
137
|
return message.includes(
|
|
1018
138
|
'Child component ComponentName(Identifier("lucern")) not found'
|
|
1019
139
|
) || message.includes("Child component") && message.includes("lucern") && message.includes("not found");
|
|
1020
140
|
}
|
|
1021
|
-
|
|
141
|
+
function getErrorMessage(error) {
|
|
142
|
+
if (error instanceof Error) {
|
|
143
|
+
return error.message;
|
|
144
|
+
}
|
|
145
|
+
if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") {
|
|
146
|
+
return error.message;
|
|
147
|
+
}
|
|
148
|
+
return "unknown error";
|
|
149
|
+
}
|
|
150
|
+
async function resolveTopicDoc(ctx, scopeId) {
|
|
1022
151
|
if (ctx?.db && typeof ctx.db.get === "function") {
|
|
1023
152
|
try {
|
|
1024
|
-
const directTopic = await ctx.db.get(
|
|
153
|
+
const directTopic = await ctx.db.get(
|
|
154
|
+
scopeId
|
|
155
|
+
);
|
|
1025
156
|
if (directTopic) {
|
|
1026
157
|
return directTopic;
|
|
1027
158
|
}
|
|
1028
|
-
} catch {
|
|
159
|
+
} catch (error) {
|
|
160
|
+
debugGraphPrimitiveFallback(
|
|
161
|
+
"[topicProjectOverlay] Failed to resolve topic by direct ID",
|
|
162
|
+
{
|
|
163
|
+
error,
|
|
164
|
+
scopeId
|
|
165
|
+
}
|
|
166
|
+
);
|
|
1029
167
|
}
|
|
1030
168
|
}
|
|
1031
169
|
if (typeof ctx.runQuery !== "function") {
|
|
1032
170
|
return null;
|
|
1033
171
|
}
|
|
1034
172
|
try {
|
|
1035
|
-
const topic = await ctx.runQuery(
|
|
173
|
+
const topic = await ctx.runQuery(api.topics.get, {
|
|
1036
174
|
id: String(scopeId)
|
|
1037
175
|
});
|
|
1038
176
|
if (topic?.name !== void 0 && topic?.type !== void 0) {
|
|
1039
177
|
return topic;
|
|
1040
178
|
}
|
|
1041
|
-
} catch {
|
|
179
|
+
} catch (error) {
|
|
180
|
+
debugGraphPrimitiveFallback(
|
|
181
|
+
"[topicProjectOverlay] Failed to resolve topic by ID query",
|
|
182
|
+
{
|
|
183
|
+
error,
|
|
184
|
+
scopeId
|
|
185
|
+
}
|
|
186
|
+
);
|
|
1042
187
|
}
|
|
1043
188
|
try {
|
|
1044
|
-
const topic = await ctx.runQuery(
|
|
189
|
+
const topic = await ctx.runQuery(api.topics.getByLegacyScopeId, {
|
|
1045
190
|
projectId: String(scopeId)
|
|
1046
191
|
});
|
|
1047
192
|
if (topic?.name !== void 0 && topic?.type !== void 0) {
|
|
1048
193
|
return topic;
|
|
1049
194
|
}
|
|
1050
|
-
} catch {
|
|
195
|
+
} catch (error) {
|
|
196
|
+
debugGraphPrimitiveFallback(
|
|
197
|
+
"[topicProjectOverlay] Failed to resolve topic by legacy scope ID",
|
|
198
|
+
{ error, scopeId }
|
|
199
|
+
);
|
|
1051
200
|
}
|
|
1052
201
|
return null;
|
|
1053
202
|
}
|
|
1054
|
-
function
|
|
1055
|
-
const metadata =
|
|
203
|
+
function materializeTopicProjectOverlay(topic, idMode = "legacy") {
|
|
204
|
+
const metadata = readMetadata(topic);
|
|
1056
205
|
const topicId = String(topic._id);
|
|
1057
|
-
const legacyProjectId =
|
|
206
|
+
const legacyProjectId = readLegacyProjectId(topic) || readLegacyProjectId(metadata) || readNonEmptyString(metadata.legacyProjectId);
|
|
1058
207
|
const storageProjectId = legacyProjectId || topicId;
|
|
1059
208
|
const outwardId = idMode === "topic" ? topicId : storageProjectId;
|
|
1060
|
-
const visibility =
|
|
1061
|
-
const status =
|
|
209
|
+
const visibility = coerceVisibility(topic.visibility) || coerceVisibility(metadata.visibility) || "private";
|
|
210
|
+
const status = coerceStatus(topic.status) || coerceStatus(metadata.status) || "active";
|
|
1062
211
|
const createdAt = typeof topic.createdAt === "number" ? topic.createdAt : typeof topic._creationTime === "number" ? topic._creationTime : 0;
|
|
1063
212
|
const updatedAt = typeof topic.updatedAt === "number" ? topic.updatedAt : typeof metadata.updatedAt === "number" ? metadata.updatedAt : createdAt;
|
|
1064
213
|
return {
|
|
@@ -1068,16 +217,16 @@ function materializeTopicProjectOverlay2(topic, idMode = "legacy") {
|
|
|
1068
217
|
topicId,
|
|
1069
218
|
storageProjectId,
|
|
1070
219
|
legacyProjectId,
|
|
1071
|
-
name:
|
|
1072
|
-
type:
|
|
1073
|
-
description:
|
|
1074
|
-
ownerId:
|
|
1075
|
-
sharedWith:
|
|
220
|
+
name: readNonEmptyString(topic.name) || "Untitled Theme",
|
|
221
|
+
type: mapProjectType(topic, metadata),
|
|
222
|
+
description: readNonEmptyString(topic.description),
|
|
223
|
+
ownerId: readNonEmptyString(metadata.ownerId) || readNonEmptyString(topic.createdBy) || "system",
|
|
224
|
+
sharedWith: readStringArray(metadata.sharedWith),
|
|
1076
225
|
visibility,
|
|
1077
|
-
tenantId:
|
|
1078
|
-
workspaceId:
|
|
226
|
+
tenantId: readNonEmptyString(topic.tenantId) || readNonEmptyString(metadata.tenantId),
|
|
227
|
+
workspaceId: readNonEmptyString(topic.workspaceId) || readNonEmptyString(metadata.workspaceId),
|
|
1079
228
|
status,
|
|
1080
|
-
tags:
|
|
229
|
+
tags: readStringArray(metadata.tags),
|
|
1081
230
|
chatCount: typeof metadata.chatCount === "number" ? metadata.chatCount : 0,
|
|
1082
231
|
artifactCount: typeof metadata.artifactCount === "number" ? metadata.artifactCount : 0,
|
|
1083
232
|
lastActivityAt: typeof metadata.lastActivityAt === "number" ? metadata.lastActivityAt : updatedAt,
|
|
@@ -1086,38 +235,42 @@ function materializeTopicProjectOverlay2(topic, idMode = "legacy") {
|
|
|
1086
235
|
updatedAt
|
|
1087
236
|
};
|
|
1088
237
|
}
|
|
1089
|
-
async function
|
|
1090
|
-
const topic = await
|
|
238
|
+
async function resolveTopicProjectOverlay(ctx, scopeId, options = {}) {
|
|
239
|
+
const topic = await resolveTopicDoc(ctx, scopeId);
|
|
1091
240
|
if (!topic) {
|
|
1092
241
|
return null;
|
|
1093
242
|
}
|
|
1094
|
-
if (options.projectLikeOnly !== false && !
|
|
243
|
+
if (options.projectLikeOnly !== false && !isProjectLikeTopic(topic)) {
|
|
1095
244
|
return null;
|
|
1096
245
|
}
|
|
1097
|
-
return
|
|
246
|
+
return materializeTopicProjectOverlay(topic, options.idMode);
|
|
1098
247
|
}
|
|
1099
|
-
async function
|
|
248
|
+
async function listTopicProjectOverlays(ctx, options = {}) {
|
|
1100
249
|
let allTopics = [];
|
|
1101
250
|
if (ctx?.db?.query && typeof ctx.db.query === "function") {
|
|
1102
251
|
try {
|
|
1103
252
|
allTopics = await ctx.db.query("topics").collect();
|
|
1104
|
-
} catch {
|
|
253
|
+
} catch (error) {
|
|
254
|
+
debugGraphPrimitiveFallback(
|
|
255
|
+
"[topicProjectOverlay] Failed to read topics table; falling back to API",
|
|
256
|
+
{ error }
|
|
257
|
+
);
|
|
1105
258
|
allTopics = [];
|
|
1106
259
|
}
|
|
1107
260
|
}
|
|
1108
261
|
if (allTopics.length === 0 && typeof ctx.runQuery === "function") {
|
|
1109
|
-
allTopics = (await ctx.runQuery(
|
|
262
|
+
allTopics = (await ctx.runQuery(api.topics.list, {}) ?? []) || [];
|
|
1110
263
|
}
|
|
1111
264
|
return allTopics.filter(
|
|
1112
|
-
(topic) => options.projectLikeOnly === false ||
|
|
1113
|
-
).map((topic) =>
|
|
265
|
+
(topic) => options.projectLikeOnly === false || isProjectLikeTopic(topic)
|
|
266
|
+
).map((topic) => materializeTopicProjectOverlay(topic, options.idMode));
|
|
1114
267
|
}
|
|
1115
268
|
async function patchTopicProjectOverlay(ctx, scopeId, value) {
|
|
1116
|
-
const topic = await
|
|
269
|
+
const topic = await resolveTopicDoc(ctx, scopeId);
|
|
1117
270
|
if (!topic) {
|
|
1118
271
|
return null;
|
|
1119
272
|
}
|
|
1120
|
-
const nextMetadata = { ...
|
|
273
|
+
const nextMetadata = { ...readMetadata(topic) };
|
|
1121
274
|
const patch = {};
|
|
1122
275
|
const topicUpdateArgs = {
|
|
1123
276
|
id: String(topic._id)
|
|
@@ -1142,7 +295,7 @@ async function patchTopicProjectOverlay(ctx, scopeId, value) {
|
|
|
1142
295
|
`patchTopicProjectOverlay cannot mutate ${key} via component-owned topics`
|
|
1143
296
|
);
|
|
1144
297
|
case "status": {
|
|
1145
|
-
const status =
|
|
298
|
+
const status = coerceStatus(rawValue);
|
|
1146
299
|
if (status) {
|
|
1147
300
|
patch.status = status;
|
|
1148
301
|
topicUpdateArgs.status = status;
|
|
@@ -1150,7 +303,7 @@ async function patchTopicProjectOverlay(ctx, scopeId, value) {
|
|
|
1150
303
|
break;
|
|
1151
304
|
}
|
|
1152
305
|
case "visibility": {
|
|
1153
|
-
const visibility =
|
|
306
|
+
const visibility = coerceVisibility(rawValue);
|
|
1154
307
|
if (visibility) {
|
|
1155
308
|
patch.visibility = visibility;
|
|
1156
309
|
topicUpdateArgs.visibility = visibility;
|
|
@@ -1158,7 +311,7 @@ async function patchTopicProjectOverlay(ctx, scopeId, value) {
|
|
|
1158
311
|
break;
|
|
1159
312
|
}
|
|
1160
313
|
case "type": {
|
|
1161
|
-
const projectType =
|
|
314
|
+
const projectType = readNonEmptyString(rawValue);
|
|
1162
315
|
if (projectType) {
|
|
1163
316
|
nextMetadata.projectType = projectType;
|
|
1164
317
|
} else {
|
|
@@ -1182,7 +335,7 @@ async function patchTopicProjectOverlay(ctx, scopeId, value) {
|
|
|
1182
335
|
topicUpdateArgs.metadata = nextMetadata;
|
|
1183
336
|
if (typeof ctx.runMutation === "function") {
|
|
1184
337
|
try {
|
|
1185
|
-
await ctx.runMutation(
|
|
338
|
+
await ctx.runMutation(api.topics.update, topicUpdateArgs);
|
|
1186
339
|
} catch (error) {
|
|
1187
340
|
if (!isMissingLucernChildComponentError(error) || !ctx?.db || typeof ctx.db.patch !== "function") {
|
|
1188
341
|
throw error;
|
|
@@ -1196,19 +349,28 @@ async function patchTopicProjectOverlay(ctx, scopeId, value) {
|
|
|
1196
349
|
"Cannot patch topic without component adapter (ctx.runMutation unavailable)"
|
|
1197
350
|
);
|
|
1198
351
|
}
|
|
1199
|
-
return
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
}
|
|
1205
|
-
);
|
|
352
|
+
return materializeTopicProjectOverlay({
|
|
353
|
+
...topic,
|
|
354
|
+
...patch,
|
|
355
|
+
metadata: nextMetadata
|
|
356
|
+
});
|
|
1206
357
|
}
|
|
1207
358
|
|
|
1208
359
|
// src/resolvers.ts
|
|
1209
360
|
function isMissingLucernChildComponentError2(error) {
|
|
1210
|
-
const message =
|
|
1211
|
-
return message.includes(
|
|
361
|
+
const message = getErrorMessage2(error);
|
|
362
|
+
return message.includes(
|
|
363
|
+
'Child component ComponentName(Identifier("lucern")) not found'
|
|
364
|
+
) || message.includes("Child component") && message.includes("lucern") && message.includes("not found");
|
|
365
|
+
}
|
|
366
|
+
function getErrorMessage2(error) {
|
|
367
|
+
if (error instanceof Error) {
|
|
368
|
+
return error.message;
|
|
369
|
+
}
|
|
370
|
+
if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") {
|
|
371
|
+
return error.message;
|
|
372
|
+
}
|
|
373
|
+
return "unknown error";
|
|
1212
374
|
}
|
|
1213
375
|
function isAdvisoryTopicPatch(value) {
|
|
1214
376
|
const advisoryKeys = /* @__PURE__ */ new Set(["lastActivityAt", "updatedAt"]);
|
|
@@ -1222,52 +384,47 @@ async function patchProjectWithTolerance(ctx, projectId, value) {
|
|
|
1222
384
|
if (!isAdvisoryTopicPatch(value) || !isMissingLucernChildComponentError2(error)) {
|
|
1223
385
|
throw error;
|
|
1224
386
|
}
|
|
1225
|
-
console.warn(
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
387
|
+
console.warn(
|
|
388
|
+
"[lucern graph-primitives] Non-fatal advisory topic patch failure",
|
|
389
|
+
{
|
|
390
|
+
projectId,
|
|
391
|
+
keys: Object.keys(value),
|
|
392
|
+
error: getErrorMessage2(error)
|
|
393
|
+
}
|
|
394
|
+
);
|
|
1230
395
|
}
|
|
1231
396
|
}
|
|
1232
|
-
function
|
|
397
|
+
function defaultResolvers() {
|
|
1233
398
|
return {
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
async listTopics(ctx) {
|
|
1244
|
-
return await listTopicProjectOverlays2(ctx, {
|
|
1245
|
-
idMode: "legacy"
|
|
1246
|
-
});
|
|
1247
|
-
},
|
|
1248
|
-
async getFinalArtifact(ctx, artifactId) {
|
|
1249
|
-
return await ctx.db.get(artifactId);
|
|
1250
|
-
}
|
|
399
|
+
getProject: (ctx, projectId) => resolveTopicProjectOverlay(ctx, projectId, {
|
|
400
|
+
idMode: "legacy",
|
|
401
|
+
projectLikeOnly: false
|
|
402
|
+
}),
|
|
403
|
+
patchProject: (ctx, projectId, value) => patchProjectWithTolerance(ctx, projectId, value),
|
|
404
|
+
listTopics: (ctx) => listTopicProjectOverlays(ctx, {
|
|
405
|
+
idMode: "legacy"
|
|
406
|
+
}),
|
|
407
|
+
getFinalArtifact: (ctx, artifactId) => ctx.db.get(artifactId)
|
|
1251
408
|
};
|
|
1252
409
|
}
|
|
1253
|
-
var
|
|
410
|
+
var resolverOverrides = {};
|
|
1254
411
|
function resolveGraphPrimitivesAppResolvers(_ctx) {
|
|
1255
412
|
return {
|
|
1256
|
-
...
|
|
1257
|
-
...
|
|
413
|
+
...defaultResolvers(),
|
|
414
|
+
...resolverOverrides
|
|
1258
415
|
};
|
|
1259
416
|
}
|
|
1260
|
-
var
|
|
417
|
+
var LEGACY_SCOPE_FIELD2 = "graphScopeProjectId";
|
|
1261
418
|
function asMappedProjectId(topic) {
|
|
1262
419
|
if (!topic) {
|
|
1263
420
|
return;
|
|
1264
421
|
}
|
|
1265
|
-
const directLegacyProjectId = normalizeScopeValue(topic[
|
|
422
|
+
const directLegacyProjectId = normalizeScopeValue(topic[LEGACY_SCOPE_FIELD2]);
|
|
1266
423
|
if (directLegacyProjectId) {
|
|
1267
424
|
return directLegacyProjectId;
|
|
1268
425
|
}
|
|
1269
426
|
const metadata = topic.metadata || {};
|
|
1270
|
-
const candidate = metadata[
|
|
427
|
+
const candidate = metadata[LEGACY_SCOPE_FIELD2] || metadata.legacyProjectId || metadata.projectId || metadata.scopeProjectId;
|
|
1271
428
|
return candidate ? candidate : void 0;
|
|
1272
429
|
}
|
|
1273
430
|
function normalizeScopeValue(value) {
|
|
@@ -1296,9 +453,16 @@ async function findTopicsByScopeAlias(ctx, scopeId) {
|
|
|
1296
453
|
try {
|
|
1297
454
|
return await ctx.db.query("topics").withIndex(
|
|
1298
455
|
"by_graph_scope_project",
|
|
1299
|
-
(q) => q.eq(
|
|
456
|
+
(q) => q.eq(LEGACY_SCOPE_FIELD2, scopeId)
|
|
1300
457
|
).collect();
|
|
1301
|
-
} catch {
|
|
458
|
+
} catch (error) {
|
|
459
|
+
debugGraphPrimitiveFallback(
|
|
460
|
+
"[topicScope] Failed to resolve scope alias via index",
|
|
461
|
+
{
|
|
462
|
+
error,
|
|
463
|
+
scopeId
|
|
464
|
+
}
|
|
465
|
+
);
|
|
1302
466
|
const topics = await ctx.db.query("topics").collect();
|
|
1303
467
|
return topics.filter((topic) => {
|
|
1304
468
|
const normalizedGlobalId = normalizeScopeValue(topic.globalId);
|
|
@@ -1312,10 +476,17 @@ async function tryResolveHostTopicById(ctx, topicId) {
|
|
|
1312
476
|
return null;
|
|
1313
477
|
}
|
|
1314
478
|
try {
|
|
1315
|
-
return await ctx.runQuery(
|
|
479
|
+
return await ctx.runQuery(api.topics.get, {
|
|
1316
480
|
id: topicId
|
|
1317
481
|
}) ?? null;
|
|
1318
|
-
} catch {
|
|
482
|
+
} catch (error) {
|
|
483
|
+
debugGraphPrimitiveFallback(
|
|
484
|
+
"[topicScope] Failed to resolve topic by host query",
|
|
485
|
+
{
|
|
486
|
+
error,
|
|
487
|
+
topicId
|
|
488
|
+
}
|
|
489
|
+
);
|
|
1319
490
|
return null;
|
|
1320
491
|
}
|
|
1321
492
|
}
|
|
@@ -1324,10 +495,17 @@ async function tryResolveHostTopicByLegacyScope(ctx, legacyScopeId) {
|
|
|
1324
495
|
return null;
|
|
1325
496
|
}
|
|
1326
497
|
try {
|
|
1327
|
-
return await ctx.runQuery(
|
|
498
|
+
return await ctx.runQuery(api.topics.getByLegacyScopeId, {
|
|
1328
499
|
projectId: legacyScopeId
|
|
1329
500
|
}) ?? null;
|
|
1330
|
-
} catch {
|
|
501
|
+
} catch (error) {
|
|
502
|
+
debugGraphPrimitiveFallback(
|
|
503
|
+
"[topicScope] Failed to resolve topic by legacy scope",
|
|
504
|
+
{
|
|
505
|
+
error,
|
|
506
|
+
legacyScopeId
|
|
507
|
+
}
|
|
508
|
+
);
|
|
1331
509
|
return null;
|
|
1332
510
|
}
|
|
1333
511
|
}
|
|
@@ -1356,8 +534,17 @@ async function resolveTopicProjectScope(ctx, args) {
|
|
|
1356
534
|
if (args.topicId) {
|
|
1357
535
|
let topic = null;
|
|
1358
536
|
try {
|
|
1359
|
-
topic = await ctx.db.get(
|
|
1360
|
-
|
|
537
|
+
topic = await ctx.db.get(
|
|
538
|
+
args.topicId
|
|
539
|
+
);
|
|
540
|
+
} catch (error) {
|
|
541
|
+
debugGraphPrimitiveFallback(
|
|
542
|
+
"[topicScope] Failed to load topic by direct id",
|
|
543
|
+
{
|
|
544
|
+
error,
|
|
545
|
+
topicId: args.topicId
|
|
546
|
+
}
|
|
547
|
+
);
|
|
1361
548
|
}
|
|
1362
549
|
if (!topic) {
|
|
1363
550
|
topic = await tryResolveHostTopicById(ctx, String(args.topicId));
|
|
@@ -1394,7 +581,14 @@ async function resolveTopicProjectScope(ctx, args) {
|
|
|
1394
581
|
directTopic = await ctx.db.get(
|
|
1395
582
|
args.projectId
|
|
1396
583
|
);
|
|
1397
|
-
} catch {
|
|
584
|
+
} catch (error) {
|
|
585
|
+
debugGraphPrimitiveFallback(
|
|
586
|
+
"[topicScope] Failed to load direct project topic",
|
|
587
|
+
{
|
|
588
|
+
error,
|
|
589
|
+
projectId: args.projectId
|
|
590
|
+
}
|
|
591
|
+
);
|
|
1398
592
|
}
|
|
1399
593
|
if (directTopic) {
|
|
1400
594
|
const inherited = await resolveInheritedWorkspaceScope(ctx, directTopic);
|
|
@@ -1443,61 +637,6 @@ var optionalScopeArgs = {
|
|
|
1443
637
|
projectId: v.optional(v.string()),
|
|
1444
638
|
topicId: v.optional(v.string())
|
|
1445
639
|
};
|
|
1446
|
-
|
|
1447
|
-
// ../../packages/contracts/src/schema-helpers/spine/tables/epistemicNodes.ts
|
|
1448
|
-
var NODE_TYPES = [
|
|
1449
|
-
"decision",
|
|
1450
|
-
"belief",
|
|
1451
|
-
"question",
|
|
1452
|
-
"theme",
|
|
1453
|
-
"deal",
|
|
1454
|
-
"topic",
|
|
1455
|
-
"claim",
|
|
1456
|
-
"evidence",
|
|
1457
|
-
"synthesis",
|
|
1458
|
-
"answer",
|
|
1459
|
-
"atomic_fact",
|
|
1460
|
-
"excerpt",
|
|
1461
|
-
"source",
|
|
1462
|
-
"company",
|
|
1463
|
-
"person",
|
|
1464
|
-
"investor",
|
|
1465
|
-
"function",
|
|
1466
|
-
"value_chain"
|
|
1467
|
-
];
|
|
1468
|
-
function isNodeType(value) {
|
|
1469
|
-
return NODE_TYPES.includes(value);
|
|
1470
|
-
}
|
|
1471
|
-
function getLayerForNodeType(type) {
|
|
1472
|
-
switch (type) {
|
|
1473
|
-
case "decision":
|
|
1474
|
-
return "L4";
|
|
1475
|
-
case "belief":
|
|
1476
|
-
case "question":
|
|
1477
|
-
case "theme":
|
|
1478
|
-
case "deal":
|
|
1479
|
-
return "L3";
|
|
1480
|
-
case "claim":
|
|
1481
|
-
case "evidence":
|
|
1482
|
-
case "synthesis":
|
|
1483
|
-
case "answer":
|
|
1484
|
-
return "L2";
|
|
1485
|
-
case "atomic_fact":
|
|
1486
|
-
case "excerpt":
|
|
1487
|
-
case "source":
|
|
1488
|
-
return "L1";
|
|
1489
|
-
case "topic":
|
|
1490
|
-
return "organizational";
|
|
1491
|
-
case "company":
|
|
1492
|
-
case "person":
|
|
1493
|
-
case "investor":
|
|
1494
|
-
case "function":
|
|
1495
|
-
case "value_chain":
|
|
1496
|
-
return "ontological";
|
|
1497
|
-
}
|
|
1498
|
-
}
|
|
1499
|
-
|
|
1500
|
-
// src/workspaceIsolation.ts
|
|
1501
640
|
function normalizeScopeValue2(value) {
|
|
1502
641
|
if (typeof value !== "string") {
|
|
1503
642
|
return;
|
|
@@ -1631,38 +770,7 @@ function assertTenantPackWorkspaceMutationAllowed(args) {
|
|
|
1631
770
|
});
|
|
1632
771
|
}
|
|
1633
772
|
|
|
1634
|
-
//
|
|
1635
|
-
function normalizeString2(value) {
|
|
1636
|
-
if (typeof value !== "string") {
|
|
1637
|
-
return void 0;
|
|
1638
|
-
}
|
|
1639
|
-
const trimmed = value.trim();
|
|
1640
|
-
return trimmed.length > 0 ? trimmed : void 0;
|
|
1641
|
-
}
|
|
1642
|
-
function requireScopeId(...ids) {
|
|
1643
|
-
for (const id of ids) {
|
|
1644
|
-
const normalized = normalizeString2(id);
|
|
1645
|
-
if (normalized) {
|
|
1646
|
-
return normalized;
|
|
1647
|
-
}
|
|
1648
|
-
}
|
|
1649
|
-
throw new Error("No scope identifier provided (topicId or projectId required)");
|
|
1650
|
-
}
|
|
1651
|
-
async function resolveTopicProjectScope2(ctx, args) {
|
|
1652
|
-
const resolved = await resolveTopicProjectScope(ctx, {
|
|
1653
|
-
topicId: normalizeString2(args.topicId),
|
|
1654
|
-
projectId: normalizeString2(args.projectId)
|
|
1655
|
-
});
|
|
1656
|
-
const topicId = normalizeString2(resolved.topicId);
|
|
1657
|
-
const projectId = requireScopeId(
|
|
1658
|
-
resolved.projectId,
|
|
1659
|
-
args.projectId,
|
|
1660
|
-
topicId
|
|
1661
|
-
);
|
|
1662
|
-
return { projectId, ...topicId ? { topicId } : {} };
|
|
1663
|
-
}
|
|
1664
|
-
|
|
1665
|
-
// ../worktrees/src/v1/engine/worktreeWorkflowBridge.ts
|
|
773
|
+
// src/workflowBridge.ts
|
|
1666
774
|
function isLegacySprintDoc(doc) {
|
|
1667
775
|
if (!doc || typeof doc !== "object") {
|
|
1668
776
|
return false;
|
|
@@ -1688,7 +796,7 @@ async function findPairedWorktreeForSprint(ctx, sprint) {
|
|
|
1688
796
|
let topicId = getStringField(sprint, "topicId");
|
|
1689
797
|
if (!topicId) {
|
|
1690
798
|
try {
|
|
1691
|
-
const scope = await
|
|
799
|
+
const scope = await resolveTopicProjectScope(ctx, {
|
|
1692
800
|
topicId: getStringField(sprint, "topicId"),
|
|
1693
801
|
projectId: getStringField(sprint, "projectId")
|
|
1694
802
|
});
|
|
@@ -1855,6 +963,12 @@ function normalizeQuestionTopicId(topicId) {
|
|
|
1855
963
|
function resolveQuestionScopeId(scope) {
|
|
1856
964
|
return normalizeQuestionTopicId(scope.topicId) ?? scope.projectId ?? void 0;
|
|
1857
965
|
}
|
|
966
|
+
function logQuestionFallback(message, error, context) {
|
|
967
|
+
debugGraphPrimitiveFallback(message, {
|
|
968
|
+
error: formatGraphPrimitiveError(error),
|
|
969
|
+
...context ?? {}
|
|
970
|
+
});
|
|
971
|
+
}
|
|
1858
972
|
async function resolveQuestionScopeOrNull(ctx, args) {
|
|
1859
973
|
if (!args.projectId && !args.topicId) {
|
|
1860
974
|
return null;
|
|
@@ -1864,7 +978,15 @@ async function resolveQuestionScopeOrNull(ctx, args) {
|
|
|
1864
978
|
projectId: args.projectId ?? void 0,
|
|
1865
979
|
topicId: args.topicId ?? void 0
|
|
1866
980
|
});
|
|
1867
|
-
} catch {
|
|
981
|
+
} catch (error) {
|
|
982
|
+
debugGraphPrimitiveFallback(
|
|
983
|
+
"[epistemicQuestions] Failed to resolve question scope",
|
|
984
|
+
{
|
|
985
|
+
error: formatGraphPrimitiveError(error),
|
|
986
|
+
projectId: args.projectId,
|
|
987
|
+
topicId: args.topicId
|
|
988
|
+
}
|
|
989
|
+
);
|
|
1868
990
|
return null;
|
|
1869
991
|
}
|
|
1870
992
|
}
|
|
@@ -2068,9 +1190,7 @@ var create = mutation({
|
|
|
2068
1190
|
source: args.source || "ai_suggested",
|
|
2069
1191
|
questionStatus: "open",
|
|
2070
1192
|
linkedBeliefNodeId: args.linkedBeliefNodeId,
|
|
2071
|
-
...buildLinkedWorktreeMetadata(
|
|
2072
|
-
args.linkedWorktreeId
|
|
2073
|
-
),
|
|
1193
|
+
...buildLinkedWorktreeMetadata(args.linkedWorktreeId),
|
|
2074
1194
|
testType: args.testType,
|
|
2075
1195
|
importance: args.importance,
|
|
2076
1196
|
epistemicUnlock: args.epistemicUnlock,
|
|
@@ -2481,7 +1601,9 @@ var getByTopic = query({
|
|
|
2481
1601
|
handler: async (ctx, args) => {
|
|
2482
1602
|
const pageSize = clampQuestionLimit(args.limit);
|
|
2483
1603
|
const scanLimit = Math.min(pageSize * 3, MAX_QUESTION_PAGE_SIZE);
|
|
2484
|
-
const scope = await resolveTopicProjectScope(ctx, {
|
|
1604
|
+
const scope = await resolveTopicProjectScope(ctx, {
|
|
1605
|
+
topicId: args.topicId
|
|
1606
|
+
});
|
|
2485
1607
|
const scopedNodes = await getQuestionNodesForScope(ctx, scope, {
|
|
2486
1608
|
scanLimit
|
|
2487
1609
|
});
|
|
@@ -2505,7 +1627,7 @@ var getByCategory = query({
|
|
|
2505
1627
|
const nodes = await getQuestionNodesForScope(ctx, scope);
|
|
2506
1628
|
return nodes.filter((n) => {
|
|
2507
1629
|
const metadata = n.metadata || {};
|
|
2508
|
-
return questionMatchesScope(n, scope) &&
|
|
1630
|
+
return questionMatchesScope(n, scope) && isActiveQuestionNode(n) && metadata.category === normalizeCategory(args.category);
|
|
2509
1631
|
});
|
|
2510
1632
|
}
|
|
2511
1633
|
});
|
|
@@ -2601,7 +1723,9 @@ var internalGetByTopic = internalQuery({
|
|
|
2601
1723
|
const pageSize = clampQuestionLimit(args.limit, 500);
|
|
2602
1724
|
const scanLimit = Math.min(pageSize * 3, MAX_QUESTION_PAGE_SIZE);
|
|
2603
1725
|
const audienceMode = args.audienceMode ?? "internal";
|
|
2604
|
-
const scope = await resolveTopicProjectScope(ctx, {
|
|
1726
|
+
const scope = await resolveTopicProjectScope(ctx, {
|
|
1727
|
+
topicId: args.topicId
|
|
1728
|
+
});
|
|
2605
1729
|
const registryRows = await listAudienceRegistryRows(ctx, {
|
|
2606
1730
|
tenantId: scope.tenantId,
|
|
2607
1731
|
workspaceId: scope.workspaceId
|
|
@@ -2931,7 +2055,14 @@ A: ${args.answerText}`;
|
|
|
2931
2055
|
if (node?.nodeType === "belief") {
|
|
2932
2056
|
return node;
|
|
2933
2057
|
}
|
|
2934
|
-
} catch {
|
|
2058
|
+
} catch (error) {
|
|
2059
|
+
debugGraphPrimitiveFallback(
|
|
2060
|
+
"[epistemicQuestions] Failed to resolve belief node",
|
|
2061
|
+
{
|
|
2062
|
+
error: formatGraphPrimitiveError(error),
|
|
2063
|
+
beliefId: bId
|
|
2064
|
+
}
|
|
2065
|
+
);
|
|
2935
2066
|
return null;
|
|
2936
2067
|
}
|
|
2937
2068
|
return null;
|
|
@@ -3337,7 +2468,14 @@ var getInConviction = query({
|
|
|
3337
2468
|
exclusiveWithCount: 0
|
|
3338
2469
|
};
|
|
3339
2470
|
}
|
|
3340
|
-
} catch {
|
|
2471
|
+
} catch (error) {
|
|
2472
|
+
debugGraphPrimitiveFallback(
|
|
2473
|
+
"[epistemicQuestions] Failed to hydrate linked belief",
|
|
2474
|
+
{
|
|
2475
|
+
error: formatGraphPrimitiveError(error),
|
|
2476
|
+
beliefId
|
|
2477
|
+
}
|
|
2478
|
+
);
|
|
3341
2479
|
}
|
|
3342
2480
|
return null;
|
|
3343
2481
|
})
|
|
@@ -3435,6 +2573,14 @@ var advanceToConviction = mutation({
|
|
|
3435
2573
|
triggeringAction: "question_advanced_to_conviction"
|
|
3436
2574
|
});
|
|
3437
2575
|
} catch (e) {
|
|
2576
|
+
logQuestionFallback(
|
|
2577
|
+
"[epistemicQuestions] Failed to log advanceToConviction audit",
|
|
2578
|
+
e,
|
|
2579
|
+
{
|
|
2580
|
+
questionId: args.questionId,
|
|
2581
|
+
projectId: node.projectId
|
|
2582
|
+
}
|
|
2583
|
+
);
|
|
3438
2584
|
console.error("[EpistemicAudit] Failed to log advanceToConviction:", e);
|
|
3439
2585
|
}
|
|
3440
2586
|
await markProjectGraphDirty(ctx, node.projectId, node.topicId);
|
|
@@ -3561,6 +2707,14 @@ var finalizeConviction = mutation({
|
|
|
3561
2707
|
triggeringAction: "question_conviction_finalized"
|
|
3562
2708
|
});
|
|
3563
2709
|
} catch (e) {
|
|
2710
|
+
logQuestionFallback(
|
|
2711
|
+
"[epistemicQuestions] Failed to log finalizeConviction audit",
|
|
2712
|
+
e,
|
|
2713
|
+
{
|
|
2714
|
+
questionId: args.questionId,
|
|
2715
|
+
projectId: node.projectId
|
|
2716
|
+
}
|
|
2717
|
+
);
|
|
3564
2718
|
console.error("[EpistemicAudit] Failed to log finalizeConviction:", e);
|
|
3565
2719
|
}
|
|
3566
2720
|
if (node.projectId || node.topicId) {
|
|
@@ -3584,6 +2738,15 @@ var finalizeConviction = mutation({
|
|
|
3584
2738
|
}
|
|
3585
2739
|
);
|
|
3586
2740
|
} catch (e) {
|
|
2741
|
+
logQuestionFallback(
|
|
2742
|
+
"[epistemicQuestions] Failed to schedule evidence creation from scored question",
|
|
2743
|
+
e,
|
|
2744
|
+
{
|
|
2745
|
+
questionId: args.questionId,
|
|
2746
|
+
beliefId,
|
|
2747
|
+
projectId: node.projectId
|
|
2748
|
+
}
|
|
2749
|
+
);
|
|
3587
2750
|
console.error(
|
|
3588
2751
|
"[finalizeConviction] Failed to schedule evidence creation:",
|
|
3589
2752
|
e
|
|
@@ -3613,10 +2776,24 @@ var getByBeliefWithAccess = query({
|
|
|
3613
2776
|
let beliefNode = null;
|
|
3614
2777
|
try {
|
|
3615
2778
|
beliefNode = await ctx.db.get(args.beliefId);
|
|
3616
|
-
} catch {
|
|
2779
|
+
} catch (error) {
|
|
2780
|
+
debugGraphPrimitiveFallback(
|
|
2781
|
+
"[epistemicQuestions] Failed to resolve belief node directly",
|
|
2782
|
+
{
|
|
2783
|
+
error: formatGraphPrimitiveError(error),
|
|
2784
|
+
beliefId: args.beliefId
|
|
2785
|
+
}
|
|
2786
|
+
);
|
|
3617
2787
|
try {
|
|
3618
2788
|
beliefNode = await ctx.db.get(args.beliefId);
|
|
3619
|
-
} catch {
|
|
2789
|
+
} catch (legacyError) {
|
|
2790
|
+
debugGraphPrimitiveFallback(
|
|
2791
|
+
"[epistemicQuestions] Failed to resolve legacy belief node",
|
|
2792
|
+
{
|
|
2793
|
+
error: formatGraphPrimitiveError(legacyError),
|
|
2794
|
+
beliefId: args.beliefId
|
|
2795
|
+
}
|
|
2796
|
+
);
|
|
3620
2797
|
return [];
|
|
3621
2798
|
}
|
|
3622
2799
|
}
|
|
@@ -3760,10 +2937,24 @@ var linkToBelief = mutation({
|
|
|
3760
2937
|
let beliefNode = null;
|
|
3761
2938
|
try {
|
|
3762
2939
|
beliefNode = await ctx.db.get(args.beliefId);
|
|
3763
|
-
} catch {
|
|
2940
|
+
} catch (error) {
|
|
2941
|
+
debugGraphPrimitiveFallback(
|
|
2942
|
+
"[epistemicQuestions] Failed to resolve belief node directly",
|
|
2943
|
+
{
|
|
2944
|
+
error: formatGraphPrimitiveError(error),
|
|
2945
|
+
beliefId: args.beliefId
|
|
2946
|
+
}
|
|
2947
|
+
);
|
|
3764
2948
|
try {
|
|
3765
2949
|
beliefNode = await ctx.db.get(args.beliefId);
|
|
3766
|
-
} catch {
|
|
2950
|
+
} catch (legacyError) {
|
|
2951
|
+
debugGraphPrimitiveFallback(
|
|
2952
|
+
"[epistemicQuestions] Failed to resolve legacy belief node",
|
|
2953
|
+
{
|
|
2954
|
+
error: formatGraphPrimitiveError(legacyError),
|
|
2955
|
+
beliefId: args.beliefId
|
|
2956
|
+
}
|
|
2957
|
+
);
|
|
3767
2958
|
throw new Error("Belief not found");
|
|
3768
2959
|
}
|
|
3769
2960
|
}
|
|
@@ -3824,6 +3015,14 @@ var linkToBelief = mutation({
|
|
|
3824
3015
|
});
|
|
3825
3016
|
}
|
|
3826
3017
|
} catch (e) {
|
|
3018
|
+
logQuestionFallback(
|
|
3019
|
+
"[epistemicQuestions] Failed to create tests edge",
|
|
3020
|
+
e,
|
|
3021
|
+
{
|
|
3022
|
+
questionId: args.questionId,
|
|
3023
|
+
beliefId: args.beliefId
|
|
3024
|
+
}
|
|
3025
|
+
);
|
|
3827
3026
|
console.error("[linkToBelief] Failed to create tests edge:", e);
|
|
3828
3027
|
}
|
|
3829
3028
|
await markProjectGraphDirty(
|
|
@@ -3962,6 +3161,14 @@ var unlinkInsight = mutation({
|
|
|
3962
3161
|
}
|
|
3963
3162
|
}
|
|
3964
3163
|
} catch (e) {
|
|
3164
|
+
logQuestionFallback(
|
|
3165
|
+
"[epistemicQuestions] Failed to remove questionEvidenceLink",
|
|
3166
|
+
e,
|
|
3167
|
+
{
|
|
3168
|
+
questionId: args.questionId,
|
|
3169
|
+
insightId: args.insightId
|
|
3170
|
+
}
|
|
3171
|
+
);
|
|
3965
3172
|
console.error(
|
|
3966
3173
|
"[unlinkInsight] Failed to remove questionEvidenceLink:",
|
|
3967
3174
|
e
|
|
@@ -3984,6 +3191,14 @@ var unlinkInsight = mutation({
|
|
|
3984
3191
|
}
|
|
3985
3192
|
}
|
|
3986
3193
|
} catch (e) {
|
|
3194
|
+
logQuestionFallback(
|
|
3195
|
+
"[epistemicQuestions] Failed to remove derived edge",
|
|
3196
|
+
e,
|
|
3197
|
+
{
|
|
3198
|
+
questionId: args.questionId,
|
|
3199
|
+
insightId: args.insightId
|
|
3200
|
+
}
|
|
3201
|
+
);
|
|
3987
3202
|
console.error("[unlinkInsight] Failed to remove edge:", e);
|
|
3988
3203
|
}
|
|
3989
3204
|
await markProjectGraphDirty(
|
|
@@ -4154,7 +3369,14 @@ var consolidate = mutation({
|
|
|
4154
3369
|
args.questionIds.map(async (qid) => {
|
|
4155
3370
|
try {
|
|
4156
3371
|
return await ctx.db.get(qid);
|
|
4157
|
-
} catch {
|
|
3372
|
+
} catch (error) {
|
|
3373
|
+
debugGraphPrimitiveFallback(
|
|
3374
|
+
"[epistemicQuestions] Failed to load original question",
|
|
3375
|
+
{
|
|
3376
|
+
error: formatGraphPrimitiveError(error),
|
|
3377
|
+
questionId: qid
|
|
3378
|
+
}
|
|
3379
|
+
);
|
|
4158
3380
|
return null;
|
|
4159
3381
|
}
|
|
4160
3382
|
})
|
|
@@ -4215,7 +3437,14 @@ var consolidate = mutation({
|
|
|
4215
3437
|
const bMeta = belief.metadata || {};
|
|
4216
3438
|
category = bMeta.category || bMeta.pillar || "other";
|
|
4217
3439
|
}
|
|
4218
|
-
} catch {
|
|
3440
|
+
} catch (error) {
|
|
3441
|
+
debugGraphPrimitiveFallback(
|
|
3442
|
+
"[epistemicQuestions] Failed to read first linked belief metadata",
|
|
3443
|
+
{
|
|
3444
|
+
error: formatGraphPrimitiveError(error),
|
|
3445
|
+
beliefId: firstBeliefId
|
|
3446
|
+
}
|
|
3447
|
+
);
|
|
4219
3448
|
}
|
|
4220
3449
|
}
|
|
4221
3450
|
const now = Date.now();
|
|
@@ -4265,7 +3494,14 @@ var consolidate = mutation({
|
|
|
4265
3494
|
}
|
|
4266
3495
|
});
|
|
4267
3496
|
}
|
|
4268
|
-
} catch {
|
|
3497
|
+
} catch (error) {
|
|
3498
|
+
debugGraphPrimitiveFallback(
|
|
3499
|
+
"[epistemicQuestions] Failed to archive linked question",
|
|
3500
|
+
{
|
|
3501
|
+
error: formatGraphPrimitiveError(error),
|
|
3502
|
+
questionId: qid
|
|
3503
|
+
}
|
|
3504
|
+
);
|
|
4269
3505
|
}
|
|
4270
3506
|
}
|
|
4271
3507
|
await ctx.scheduler.runAfter(0, internal.neo4jSync.syncNodeToNeo4j, {
|
|
@@ -4397,7 +3633,15 @@ var getQuestionClusterPositions = query({
|
|
|
4397
3633
|
projectId: args.projectId,
|
|
4398
3634
|
topicId: args.topicId
|
|
4399
3635
|
});
|
|
4400
|
-
} catch {
|
|
3636
|
+
} catch (error) {
|
|
3637
|
+
debugGraphPrimitiveFallback(
|
|
3638
|
+
"[epistemicQuestions] Failed to resolve question cluster scope",
|
|
3639
|
+
{
|
|
3640
|
+
error: formatGraphPrimitiveError(error),
|
|
3641
|
+
projectId: args.projectId,
|
|
3642
|
+
topicId: args.topicId
|
|
3643
|
+
}
|
|
3644
|
+
);
|
|
4401
3645
|
return {
|
|
4402
3646
|
positions: {},
|
|
4403
3647
|
counts: {
|