@lucern/graph-primitives 1.0.50 → 1.0.53
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.js +34 -186
- package/dist/beliefEvidenceLinks.js +32 -187
- package/dist/contradictions.js +34 -187
- package/dist/entityLifecycle.js +88 -205
- package/dist/epistemicAnswers.js +35 -187
- package/dist/epistemicBeliefs.admin.js +34 -187
- package/dist/epistemicBeliefs.backfills.js +34 -188
- package/dist/epistemicBeliefs.confidence.d.ts +1 -1
- package/dist/epistemicBeliefs.confidence.js +32 -188
- package/dist/epistemicBeliefs.core.js +37 -187
- package/dist/epistemicBeliefs.d.ts +1 -1
- package/dist/epistemicBeliefs.helpers.d.ts +1 -1
- package/dist/epistemicBeliefs.helpers.js +34 -186
- package/dist/epistemicBeliefs.internal.js +37 -187
- package/dist/epistemicBeliefs.js +37 -187
- package/dist/epistemicBeliefs.lifecycle.js +32 -188
- package/dist/epistemicBeliefs.links.js +34 -188
- package/dist/epistemicContracts.evaluators.js +34 -188
- package/dist/epistemicContracts.handlers.js +34 -188
- package/dist/epistemicContracts.js +34 -188
- package/dist/epistemicEdges.d.ts +1 -1
- package/dist/epistemicEdges.helpers.d.ts +1 -1
- package/dist/epistemicEdges.js +32 -187
- package/dist/epistemicEdges.mutations.js +34 -186
- package/dist/epistemicEdges.queries.js +35 -188
- package/dist/epistemicEdges.types.d.ts +1 -1
- package/dist/epistemicEvidence.d.ts +1 -1
- package/dist/epistemicEvidence.js +37 -187
- package/dist/epistemicEvidenceHelpers.d.ts +1 -1
- package/dist/epistemicEvidenceHelpers.js +34 -186
- package/dist/epistemicEvidenceMutations.js +37 -187
- package/dist/epistemicEvidenceQueries.js +34 -186
- package/dist/epistemicHelpers.js +4 -1
- package/dist/epistemicInsert.js +4 -1
- package/dist/epistemicNodeCreation.js +4 -1
- package/dist/epistemicNodes.helpers.d.ts +1 -1
- package/dist/epistemicNodes.internal.js +35 -188
- package/dist/epistemicNodes.js +37 -188
- package/dist/epistemicNodes.mutations.js +35 -188
- package/dist/epistemicNodes.queries.js +35 -188
- package/dist/epistemicQuestions.conviction.js +34 -186
- package/dist/epistemicQuestions.create.js +37 -187
- package/dist/epistemicQuestions.d.ts +1 -1
- package/dist/epistemicQuestions.evidence.js +37 -187
- package/dist/epistemicQuestions.helpers.d.ts +1 -1
- package/dist/epistemicQuestions.helpers.js +34 -186
- package/dist/epistemicQuestions.js +37 -187
- package/dist/epistemicQuestions.lifecycle.js +34 -186
- package/dist/epistemicQuestions.queries.js +34 -186
- package/dist/epistemicQuestions.sprint.js +35 -188
- package/dist/epistemicQuestions.tail.js +37 -187
- package/dist/epistemicSources.js +35 -188
- package/dist/index.d.ts +1 -1
- package/dist/index.js +98 -213
- package/dist/proof-attestation.json +1 -1
- package/dist/questionEvidenceLinks.js +34 -187
- package/dist/scopeResolverCompat.d.ts +1 -1
- package/dist/scopeResolverCompat.js +32 -193
- package/dist/topicOntologyResolver.d.ts +3 -3
- package/dist/topicOntologyResolver.js +57 -18
- package/dist/{topicScope-DJVa0mLa.d.ts → topicScope-CL1IVOmv.d.ts} +2 -2
- package/dist/topicScope.d.ts +1 -1
- package/dist/topicScope.js +32 -193
- package/dist/workflowBridge.js +32 -193
- package/dist/workspaceIsolation.d.ts +1 -1
- package/dist/workspaceIsolation.js +32 -193
- package/package.json +4 -4
|
@@ -3,6 +3,7 @@ import { permissiveReturn } from '@lucern/contracts/schema-helpers/validators';
|
|
|
3
3
|
import { v } from 'convex/values';
|
|
4
4
|
import { unsafeConvexAnyApi } from '@lucern/contracts/convex/unsafeAnyApi';
|
|
5
5
|
import { componentsGeneric, mutationGeneric } from 'convex/server';
|
|
6
|
+
import { isUuidV7 } from '@lucern/contracts/ids';
|
|
6
7
|
|
|
7
8
|
// src/epistemicEdges.mutations.ts
|
|
8
9
|
var unsafeApi = unsafeConvexAnyApi(
|
|
@@ -1076,6 +1077,8 @@ function resolveGraphPrimitivesAppResolvers(_ctx) {
|
|
|
1076
1077
|
...resolverOverrides
|
|
1077
1078
|
};
|
|
1078
1079
|
}
|
|
1080
|
+
|
|
1081
|
+
// src/topicScope.ts
|
|
1079
1082
|
var LEGACY_SCOPE_FIELD2 = "graphScopeProjectId";
|
|
1080
1083
|
async function resolveTopicNodeScopeOrNull(ctx, ref) {
|
|
1081
1084
|
if (!ctx?.db || typeof ctx.db.query !== "function") {
|
|
@@ -1096,16 +1099,36 @@ async function resolveTopicNodeScopeOrNull(ctx, ref) {
|
|
|
1096
1099
|
if (!node) {
|
|
1097
1100
|
return null;
|
|
1098
1101
|
}
|
|
1099
|
-
const scopeKey =
|
|
1102
|
+
const scopeKey = canonicalTopicGlobalId(node);
|
|
1100
1103
|
if (!scopeKey) {
|
|
1101
|
-
|
|
1104
|
+
throw new Error(
|
|
1105
|
+
`topic.uuidv7_identity_required: topic node ${ref} is missing a UUIDv7 globalId.`
|
|
1106
|
+
);
|
|
1102
1107
|
}
|
|
1108
|
+
const metadata = node.metadata ?? {};
|
|
1103
1109
|
return {
|
|
1104
1110
|
topicId: scopeKey,
|
|
1105
1111
|
projectId: asMappedProjectId(node),
|
|
1106
|
-
source: "topic_node"
|
|
1112
|
+
source: "topic_node",
|
|
1113
|
+
tenantId: normalizeScopeValue(node.tenantId) ?? normalizeScopeValue(metadata.tenantId),
|
|
1114
|
+
workspaceId: normalizeScopeValue(node.workspaceId) ?? normalizeScopeValue(metadata.workspaceId)
|
|
1107
1115
|
};
|
|
1108
1116
|
}
|
|
1117
|
+
function canonicalTopicGlobalId(node) {
|
|
1118
|
+
const globalId = normalizeScopeValue(node.globalId);
|
|
1119
|
+
if (globalId && isUuidV7(globalId)) {
|
|
1120
|
+
return globalId;
|
|
1121
|
+
}
|
|
1122
|
+
const topicId = normalizeScopeValue(node.topicId);
|
|
1123
|
+
return topicId && isUuidV7(topicId) ? topicId : null;
|
|
1124
|
+
}
|
|
1125
|
+
function requireUuidV7TopicScope(field, value) {
|
|
1126
|
+
if (!isUuidV7(value)) {
|
|
1127
|
+
throw new Error(
|
|
1128
|
+
`topic.uuidv7_required: ${field} must be a UUIDv7 public topic identifier.`
|
|
1129
|
+
);
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1109
1132
|
function asMappedProjectId(topic) {
|
|
1110
1133
|
if (!topic) {
|
|
1111
1134
|
return;
|
|
@@ -1127,200 +1150,25 @@ function normalizeScopeValue(value) {
|
|
|
1127
1150
|
const normalized = value.trim();
|
|
1128
1151
|
return normalized.length > 0 ? normalized : void 0;
|
|
1129
1152
|
}
|
|
1130
|
-
function pickPrimaryTopic(candidates) {
|
|
1131
|
-
return [...candidates].sort((a, b) => {
|
|
1132
|
-
const depthA = a.depth ?? 9999;
|
|
1133
|
-
const depthB = b.depth ?? 9999;
|
|
1134
|
-
if (depthA !== depthB) {
|
|
1135
|
-
return depthA - depthB;
|
|
1136
|
-
}
|
|
1137
|
-
const createdA = a.createdAt ?? Number.MAX_SAFE_INTEGER;
|
|
1138
|
-
const createdB = b.createdAt ?? Number.MAX_SAFE_INTEGER;
|
|
1139
|
-
if (createdA !== createdB) {
|
|
1140
|
-
return createdA - createdB;
|
|
1141
|
-
}
|
|
1142
|
-
return String(a.name || "").localeCompare(String(b.name || ""));
|
|
1143
|
-
})[0];
|
|
1144
|
-
}
|
|
1145
|
-
async function findTopicsByScopeAlias(ctx, scopeId) {
|
|
1146
|
-
const query = ctx.db.query("topics");
|
|
1147
|
-
try {
|
|
1148
|
-
return await query.withIndex(
|
|
1149
|
-
"by_graph_scope_project",
|
|
1150
|
-
(q) => q.eq(LEGACY_SCOPE_FIELD2, scopeId)
|
|
1151
|
-
).collect();
|
|
1152
|
-
} catch (error) {
|
|
1153
|
-
debugGraphPrimitiveFallback(
|
|
1154
|
-
"[topicScope] Failed to resolve scope alias via index",
|
|
1155
|
-
{
|
|
1156
|
-
error,
|
|
1157
|
-
scopeId
|
|
1158
|
-
}
|
|
1159
|
-
);
|
|
1160
|
-
const topics = await query.collect();
|
|
1161
|
-
return topics.filter((topic) => {
|
|
1162
|
-
const normalizedGlobalId = normalizeScopeValue(topic.globalId);
|
|
1163
|
-
const mappedProjectId = asMappedProjectId(topic);
|
|
1164
|
-
return String(topic._id) === scopeId || normalizedGlobalId === scopeId || mappedProjectId === scopeId;
|
|
1165
|
-
});
|
|
1166
|
-
}
|
|
1167
|
-
}
|
|
1168
|
-
async function tryResolveHostTopicById(ctx, topicId) {
|
|
1169
|
-
if (typeof ctx.runQuery !== "function") {
|
|
1170
|
-
return null;
|
|
1171
|
-
}
|
|
1172
|
-
try {
|
|
1173
|
-
return await ctx.runQuery(api.topics.get, {
|
|
1174
|
-
id: topicId
|
|
1175
|
-
}) ?? null;
|
|
1176
|
-
} catch (error) {
|
|
1177
|
-
debugGraphPrimitiveFallback(
|
|
1178
|
-
"[topicScope] Failed to resolve topic by host query",
|
|
1179
|
-
{
|
|
1180
|
-
error,
|
|
1181
|
-
topicId
|
|
1182
|
-
}
|
|
1183
|
-
);
|
|
1184
|
-
return null;
|
|
1185
|
-
}
|
|
1186
|
-
}
|
|
1187
|
-
async function tryResolveHostTopicByLegacyScope(ctx, legacyScopeId) {
|
|
1188
|
-
if (typeof ctx.runQuery !== "function") {
|
|
1189
|
-
return null;
|
|
1190
|
-
}
|
|
1191
|
-
try {
|
|
1192
|
-
return await ctx.runQuery(api.topics.getByLegacyScopeId, {
|
|
1193
|
-
projectId: legacyScopeId
|
|
1194
|
-
}) ?? null;
|
|
1195
|
-
} catch (error) {
|
|
1196
|
-
debugGraphPrimitiveFallback(
|
|
1197
|
-
"[topicScope] Failed to resolve topic by legacy scope",
|
|
1198
|
-
{
|
|
1199
|
-
error,
|
|
1200
|
-
legacyScopeId
|
|
1201
|
-
}
|
|
1202
|
-
);
|
|
1203
|
-
return null;
|
|
1204
|
-
}
|
|
1205
|
-
}
|
|
1206
|
-
async function resolveInheritedWorkspaceScope(ctx, topic) {
|
|
1207
|
-
const MAX_DEPTH = 10;
|
|
1208
|
-
let tenantId = normalizeScopeValue(topic.tenantId);
|
|
1209
|
-
let workspaceId = normalizeScopeValue(topic.workspaceId);
|
|
1210
|
-
if (tenantId && workspaceId) {
|
|
1211
|
-
return { tenantId, workspaceId };
|
|
1212
|
-
}
|
|
1213
|
-
let current = topic;
|
|
1214
|
-
for (let i = 0; i < MAX_DEPTH && current?.parentTopicId; i++) {
|
|
1215
|
-
current = await ctx.db.get(current.parentTopicId);
|
|
1216
|
-
if (!current) {
|
|
1217
|
-
break;
|
|
1218
|
-
}
|
|
1219
|
-
if (!tenantId) {
|
|
1220
|
-
tenantId = normalizeScopeValue(current.tenantId);
|
|
1221
|
-
}
|
|
1222
|
-
if (!workspaceId) {
|
|
1223
|
-
workspaceId = normalizeScopeValue(current.workspaceId);
|
|
1224
|
-
}
|
|
1225
|
-
if (tenantId && workspaceId) {
|
|
1226
|
-
break;
|
|
1227
|
-
}
|
|
1228
|
-
}
|
|
1229
|
-
return { tenantId, workspaceId };
|
|
1230
|
-
}
|
|
1231
1153
|
async function resolveTopicProjectScope(ctx, args) {
|
|
1232
1154
|
if (args.topicId) {
|
|
1233
1155
|
return await resolveScopeFromTopicId(ctx, args.topicId);
|
|
1234
1156
|
}
|
|
1235
1157
|
if (args.projectId) {
|
|
1236
|
-
|
|
1158
|
+
throw new Error(
|
|
1159
|
+
"topic.uuidv7_required: projectId scope aliases are retired; pass topicId as the UUIDv7 topic globalId."
|
|
1160
|
+
);
|
|
1237
1161
|
}
|
|
1238
|
-
throw new Error(
|
|
1239
|
-
"Missing scope: provide topicId (preferred) or legacy projectId alias."
|
|
1240
|
-
);
|
|
1162
|
+
throw new Error("topic.uuidv7_required: Missing scope: provide topicId.");
|
|
1241
1163
|
}
|
|
1242
1164
|
async function resolveScopeFromTopicId(ctx, topicId) {
|
|
1243
|
-
const
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
}
|
|
1247
|
-
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, String(topicId));
|
|
1165
|
+
const topicGlobalId = String(topicId);
|
|
1166
|
+
requireUuidV7TopicScope("topicId", topicGlobalId);
|
|
1167
|
+
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, topicGlobalId);
|
|
1248
1168
|
if (nodeScope) {
|
|
1249
1169
|
return nodeScope;
|
|
1250
1170
|
}
|
|
1251
|
-
throw new Error(`Topic not found: ${
|
|
1252
|
-
}
|
|
1253
|
-
async function resolveTopicDocFromTopicId(ctx, topicId) {
|
|
1254
|
-
const direct = await tryReadTopicDoc(ctx, topicId, {
|
|
1255
|
-
failureLog: "[topicScope] Failed to load topic by direct id",
|
|
1256
|
-
idLogKey: "topicId"
|
|
1257
|
-
});
|
|
1258
|
-
if (direct) {
|
|
1259
|
-
return direct;
|
|
1260
|
-
}
|
|
1261
|
-
const hostTopic = await tryResolveHostTopicById(ctx, String(topicId));
|
|
1262
|
-
if (hostTopic) {
|
|
1263
|
-
return hostTopic;
|
|
1264
|
-
}
|
|
1265
|
-
return pickPrimaryTopic(await findTopicsByScopeAlias(ctx, String(topicId))) ?? null;
|
|
1266
|
-
}
|
|
1267
|
-
async function resolveScopeFromLegacyProjectId(ctx, legacyProjectId) {
|
|
1268
|
-
const directTopic = await resolveDirectLegacyProjectTopic(
|
|
1269
|
-
ctx,
|
|
1270
|
-
legacyProjectId
|
|
1271
|
-
);
|
|
1272
|
-
if (directTopic) {
|
|
1273
|
-
return await buildTopicScope(ctx, directTopic, "topic_inferred", {
|
|
1274
|
-
fallbackProjectId: legacyProjectId
|
|
1275
|
-
});
|
|
1276
|
-
}
|
|
1277
|
-
const primary = pickPrimaryTopic(
|
|
1278
|
-
await findTopicsByScopeAlias(ctx, legacyProjectId)
|
|
1279
|
-
);
|
|
1280
|
-
if (primary) {
|
|
1281
|
-
return await buildTopicScope(ctx, primary, "project_mapped_topic", {
|
|
1282
|
-
fallbackProjectId: legacyProjectId
|
|
1283
|
-
});
|
|
1284
|
-
}
|
|
1285
|
-
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, legacyProjectId);
|
|
1286
|
-
if (nodeScope) {
|
|
1287
|
-
return {
|
|
1288
|
-
...nodeScope,
|
|
1289
|
-
projectId: nodeScope.projectId ?? legacyProjectId
|
|
1290
|
-
};
|
|
1291
|
-
}
|
|
1292
|
-
throw new Error(
|
|
1293
|
-
`Legacy project scope ${legacyProjectId} has no mapped topic.`
|
|
1294
|
-
);
|
|
1295
|
-
}
|
|
1296
|
-
async function resolveDirectLegacyProjectTopic(ctx, legacyProjectId) {
|
|
1297
|
-
const directTopic = await tryReadTopicDoc(ctx, legacyProjectId, {
|
|
1298
|
-
failureLog: "[topicScope] Failed to load direct project topic",
|
|
1299
|
-
idLogKey: "projectId"
|
|
1300
|
-
});
|
|
1301
|
-
return directTopic ?? tryResolveHostTopicByLegacyScope(ctx, legacyProjectId);
|
|
1302
|
-
}
|
|
1303
|
-
async function tryReadTopicDoc(ctx, id, log) {
|
|
1304
|
-
try {
|
|
1305
|
-
return await ctx.db.get(id);
|
|
1306
|
-
} catch (error) {
|
|
1307
|
-
debugGraphPrimitiveFallback(log.failureLog, {
|
|
1308
|
-
error,
|
|
1309
|
-
[log.idLogKey]: id
|
|
1310
|
-
});
|
|
1311
|
-
return null;
|
|
1312
|
-
}
|
|
1313
|
-
}
|
|
1314
|
-
async function buildTopicScope(ctx, topic, source, options = {}) {
|
|
1315
|
-
const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
|
|
1316
|
-
const mapped = asMappedProjectId(topic);
|
|
1317
|
-
return {
|
|
1318
|
-
topicId: topic._id,
|
|
1319
|
-
...mapped || options.fallbackProjectId ? { projectId: mapped ?? options.fallbackProjectId } : {},
|
|
1320
|
-
tenantId: inherited.tenantId,
|
|
1321
|
-
workspaceId: inherited.workspaceId,
|
|
1322
|
-
source
|
|
1323
|
-
};
|
|
1171
|
+
throw new Error(`Topic not found: ${topicGlobalId}`);
|
|
1324
1172
|
}
|
|
1325
1173
|
var optionalScopeArgs = {
|
|
1326
1174
|
projectId: v.optional(v.string()),
|
|
@@ -3,12 +3,12 @@ import { permissiveReturn } from '@lucern/contracts/schema-helpers/validators';
|
|
|
3
3
|
import { v } from 'convex/values';
|
|
4
4
|
import { unsafeConvexAnyApi } from '@lucern/contracts/convex/unsafeAnyApi';
|
|
5
5
|
import { componentsGeneric, queryGeneric } from 'convex/server';
|
|
6
|
+
import { isUuidV7 } from '@lucern/contracts/ids';
|
|
6
7
|
|
|
7
8
|
// src/epistemicEdges.queries.ts
|
|
8
|
-
|
|
9
|
+
unsafeConvexAnyApi(
|
|
9
10
|
"graph-primitives top-level module bundle lacks a committed Convex _generated/api surface"
|
|
10
11
|
);
|
|
11
|
-
var api = unsafeApi;
|
|
12
12
|
componentsGeneric();
|
|
13
13
|
var query = queryGeneric;
|
|
14
14
|
|
|
@@ -275,6 +275,8 @@ async function collectScopedEdges(ctx, scope, scanLimit) {
|
|
|
275
275
|
}
|
|
276
276
|
return deduped;
|
|
277
277
|
}
|
|
278
|
+
|
|
279
|
+
// src/topicScope.ts
|
|
278
280
|
var LEGACY_SCOPE_FIELD = "graphScopeProjectId";
|
|
279
281
|
async function resolveTopicNodeScopeOrNull(ctx, ref) {
|
|
280
282
|
if (!ctx?.db || typeof ctx.db.query !== "function") {
|
|
@@ -295,16 +297,36 @@ async function resolveTopicNodeScopeOrNull(ctx, ref) {
|
|
|
295
297
|
if (!node) {
|
|
296
298
|
return null;
|
|
297
299
|
}
|
|
298
|
-
const scopeKey =
|
|
300
|
+
const scopeKey = canonicalTopicGlobalId(node);
|
|
299
301
|
if (!scopeKey) {
|
|
300
|
-
|
|
302
|
+
throw new Error(
|
|
303
|
+
`topic.uuidv7_identity_required: topic node ${ref} is missing a UUIDv7 globalId.`
|
|
304
|
+
);
|
|
301
305
|
}
|
|
306
|
+
const metadata = node.metadata ?? {};
|
|
302
307
|
return {
|
|
303
308
|
topicId: scopeKey,
|
|
304
309
|
projectId: asMappedProjectId(node),
|
|
305
|
-
source: "topic_node"
|
|
310
|
+
source: "topic_node",
|
|
311
|
+
tenantId: normalizeScopeValue(node.tenantId) ?? normalizeScopeValue(metadata.tenantId),
|
|
312
|
+
workspaceId: normalizeScopeValue(node.workspaceId) ?? normalizeScopeValue(metadata.workspaceId)
|
|
306
313
|
};
|
|
307
314
|
}
|
|
315
|
+
function canonicalTopicGlobalId(node) {
|
|
316
|
+
const globalId = normalizeScopeValue(node.globalId);
|
|
317
|
+
if (globalId && isUuidV7(globalId)) {
|
|
318
|
+
return globalId;
|
|
319
|
+
}
|
|
320
|
+
const topicId = normalizeScopeValue(node.topicId);
|
|
321
|
+
return topicId && isUuidV7(topicId) ? topicId : null;
|
|
322
|
+
}
|
|
323
|
+
function requireUuidV7TopicScope(field, value) {
|
|
324
|
+
if (!isUuidV7(value)) {
|
|
325
|
+
throw new Error(
|
|
326
|
+
`topic.uuidv7_required: ${field} must be a UUIDv7 public topic identifier.`
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
308
330
|
function asMappedProjectId(topic) {
|
|
309
331
|
if (!topic) {
|
|
310
332
|
return;
|
|
@@ -326,200 +348,25 @@ function normalizeScopeValue(value) {
|
|
|
326
348
|
const normalized = value.trim();
|
|
327
349
|
return normalized.length > 0 ? normalized : void 0;
|
|
328
350
|
}
|
|
329
|
-
function pickPrimaryTopic(candidates) {
|
|
330
|
-
return [...candidates].sort((a, b) => {
|
|
331
|
-
const depthA = a.depth ?? 9999;
|
|
332
|
-
const depthB = b.depth ?? 9999;
|
|
333
|
-
if (depthA !== depthB) {
|
|
334
|
-
return depthA - depthB;
|
|
335
|
-
}
|
|
336
|
-
const createdA = a.createdAt ?? Number.MAX_SAFE_INTEGER;
|
|
337
|
-
const createdB = b.createdAt ?? Number.MAX_SAFE_INTEGER;
|
|
338
|
-
if (createdA !== createdB) {
|
|
339
|
-
return createdA - createdB;
|
|
340
|
-
}
|
|
341
|
-
return String(a.name || "").localeCompare(String(b.name || ""));
|
|
342
|
-
})[0];
|
|
343
|
-
}
|
|
344
|
-
async function findTopicsByScopeAlias(ctx, scopeId) {
|
|
345
|
-
const query2 = ctx.db.query("topics");
|
|
346
|
-
try {
|
|
347
|
-
return await query2.withIndex(
|
|
348
|
-
"by_graph_scope_project",
|
|
349
|
-
(q) => q.eq(LEGACY_SCOPE_FIELD, scopeId)
|
|
350
|
-
).collect();
|
|
351
|
-
} catch (error) {
|
|
352
|
-
debugGraphPrimitiveFallback(
|
|
353
|
-
"[topicScope] Failed to resolve scope alias via index",
|
|
354
|
-
{
|
|
355
|
-
error,
|
|
356
|
-
scopeId
|
|
357
|
-
}
|
|
358
|
-
);
|
|
359
|
-
const topics = await query2.collect();
|
|
360
|
-
return topics.filter((topic) => {
|
|
361
|
-
const normalizedGlobalId = normalizeScopeValue(topic.globalId);
|
|
362
|
-
const mappedProjectId = asMappedProjectId(topic);
|
|
363
|
-
return String(topic._id) === scopeId || normalizedGlobalId === scopeId || mappedProjectId === scopeId;
|
|
364
|
-
});
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
async function tryResolveHostTopicById(ctx, topicId) {
|
|
368
|
-
if (typeof ctx.runQuery !== "function") {
|
|
369
|
-
return null;
|
|
370
|
-
}
|
|
371
|
-
try {
|
|
372
|
-
return await ctx.runQuery(api.topics.get, {
|
|
373
|
-
id: topicId
|
|
374
|
-
}) ?? null;
|
|
375
|
-
} catch (error) {
|
|
376
|
-
debugGraphPrimitiveFallback(
|
|
377
|
-
"[topicScope] Failed to resolve topic by host query",
|
|
378
|
-
{
|
|
379
|
-
error,
|
|
380
|
-
topicId
|
|
381
|
-
}
|
|
382
|
-
);
|
|
383
|
-
return null;
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
async function tryResolveHostTopicByLegacyScope(ctx, legacyScopeId) {
|
|
387
|
-
if (typeof ctx.runQuery !== "function") {
|
|
388
|
-
return null;
|
|
389
|
-
}
|
|
390
|
-
try {
|
|
391
|
-
return await ctx.runQuery(api.topics.getByLegacyScopeId, {
|
|
392
|
-
projectId: legacyScopeId
|
|
393
|
-
}) ?? null;
|
|
394
|
-
} catch (error) {
|
|
395
|
-
debugGraphPrimitiveFallback(
|
|
396
|
-
"[topicScope] Failed to resolve topic by legacy scope",
|
|
397
|
-
{
|
|
398
|
-
error,
|
|
399
|
-
legacyScopeId
|
|
400
|
-
}
|
|
401
|
-
);
|
|
402
|
-
return null;
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
async function resolveInheritedWorkspaceScope(ctx, topic) {
|
|
406
|
-
const MAX_DEPTH = 10;
|
|
407
|
-
let tenantId = normalizeScopeValue(topic.tenantId);
|
|
408
|
-
let workspaceId = normalizeScopeValue(topic.workspaceId);
|
|
409
|
-
if (tenantId && workspaceId) {
|
|
410
|
-
return { tenantId, workspaceId };
|
|
411
|
-
}
|
|
412
|
-
let current = topic;
|
|
413
|
-
for (let i = 0; i < MAX_DEPTH && current?.parentTopicId; i++) {
|
|
414
|
-
current = await ctx.db.get(current.parentTopicId);
|
|
415
|
-
if (!current) {
|
|
416
|
-
break;
|
|
417
|
-
}
|
|
418
|
-
if (!tenantId) {
|
|
419
|
-
tenantId = normalizeScopeValue(current.tenantId);
|
|
420
|
-
}
|
|
421
|
-
if (!workspaceId) {
|
|
422
|
-
workspaceId = normalizeScopeValue(current.workspaceId);
|
|
423
|
-
}
|
|
424
|
-
if (tenantId && workspaceId) {
|
|
425
|
-
break;
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
return { tenantId, workspaceId };
|
|
429
|
-
}
|
|
430
351
|
async function resolveTopicProjectScope(ctx, args) {
|
|
431
352
|
if (args.topicId) {
|
|
432
353
|
return await resolveScopeFromTopicId(ctx, args.topicId);
|
|
433
354
|
}
|
|
434
355
|
if (args.projectId) {
|
|
435
|
-
|
|
356
|
+
throw new Error(
|
|
357
|
+
"topic.uuidv7_required: projectId scope aliases are retired; pass topicId as the UUIDv7 topic globalId."
|
|
358
|
+
);
|
|
436
359
|
}
|
|
437
|
-
throw new Error(
|
|
438
|
-
"Missing scope: provide topicId (preferred) or legacy projectId alias."
|
|
439
|
-
);
|
|
360
|
+
throw new Error("topic.uuidv7_required: Missing scope: provide topicId.");
|
|
440
361
|
}
|
|
441
362
|
async function resolveScopeFromTopicId(ctx, topicId) {
|
|
442
|
-
const
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
}
|
|
446
|
-
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, String(topicId));
|
|
363
|
+
const topicGlobalId = String(topicId);
|
|
364
|
+
requireUuidV7TopicScope("topicId", topicGlobalId);
|
|
365
|
+
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, topicGlobalId);
|
|
447
366
|
if (nodeScope) {
|
|
448
367
|
return nodeScope;
|
|
449
368
|
}
|
|
450
|
-
throw new Error(`Topic not found: ${
|
|
451
|
-
}
|
|
452
|
-
async function resolveTopicDocFromTopicId(ctx, topicId) {
|
|
453
|
-
const direct = await tryReadTopicDoc(ctx, topicId, {
|
|
454
|
-
failureLog: "[topicScope] Failed to load topic by direct id",
|
|
455
|
-
idLogKey: "topicId"
|
|
456
|
-
});
|
|
457
|
-
if (direct) {
|
|
458
|
-
return direct;
|
|
459
|
-
}
|
|
460
|
-
const hostTopic = await tryResolveHostTopicById(ctx, String(topicId));
|
|
461
|
-
if (hostTopic) {
|
|
462
|
-
return hostTopic;
|
|
463
|
-
}
|
|
464
|
-
return pickPrimaryTopic(await findTopicsByScopeAlias(ctx, String(topicId))) ?? null;
|
|
465
|
-
}
|
|
466
|
-
async function resolveScopeFromLegacyProjectId(ctx, legacyProjectId) {
|
|
467
|
-
const directTopic = await resolveDirectLegacyProjectTopic(
|
|
468
|
-
ctx,
|
|
469
|
-
legacyProjectId
|
|
470
|
-
);
|
|
471
|
-
if (directTopic) {
|
|
472
|
-
return await buildTopicScope(ctx, directTopic, "topic_inferred", {
|
|
473
|
-
fallbackProjectId: legacyProjectId
|
|
474
|
-
});
|
|
475
|
-
}
|
|
476
|
-
const primary = pickPrimaryTopic(
|
|
477
|
-
await findTopicsByScopeAlias(ctx, legacyProjectId)
|
|
478
|
-
);
|
|
479
|
-
if (primary) {
|
|
480
|
-
return await buildTopicScope(ctx, primary, "project_mapped_topic", {
|
|
481
|
-
fallbackProjectId: legacyProjectId
|
|
482
|
-
});
|
|
483
|
-
}
|
|
484
|
-
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, legacyProjectId);
|
|
485
|
-
if (nodeScope) {
|
|
486
|
-
return {
|
|
487
|
-
...nodeScope,
|
|
488
|
-
projectId: nodeScope.projectId ?? legacyProjectId
|
|
489
|
-
};
|
|
490
|
-
}
|
|
491
|
-
throw new Error(
|
|
492
|
-
`Legacy project scope ${legacyProjectId} has no mapped topic.`
|
|
493
|
-
);
|
|
494
|
-
}
|
|
495
|
-
async function resolveDirectLegacyProjectTopic(ctx, legacyProjectId) {
|
|
496
|
-
const directTopic = await tryReadTopicDoc(ctx, legacyProjectId, {
|
|
497
|
-
failureLog: "[topicScope] Failed to load direct project topic",
|
|
498
|
-
idLogKey: "projectId"
|
|
499
|
-
});
|
|
500
|
-
return directTopic ?? tryResolveHostTopicByLegacyScope(ctx, legacyProjectId);
|
|
501
|
-
}
|
|
502
|
-
async function tryReadTopicDoc(ctx, id, log) {
|
|
503
|
-
try {
|
|
504
|
-
return await ctx.db.get(id);
|
|
505
|
-
} catch (error) {
|
|
506
|
-
debugGraphPrimitiveFallback(log.failureLog, {
|
|
507
|
-
error,
|
|
508
|
-
[log.idLogKey]: id
|
|
509
|
-
});
|
|
510
|
-
return null;
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
async function buildTopicScope(ctx, topic, source, options = {}) {
|
|
514
|
-
const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
|
|
515
|
-
const mapped = asMappedProjectId(topic);
|
|
516
|
-
return {
|
|
517
|
-
topicId: topic._id,
|
|
518
|
-
...mapped || options.fallbackProjectId ? { projectId: mapped ?? options.fallbackProjectId } : {},
|
|
519
|
-
tenantId: inherited.tenantId,
|
|
520
|
-
workspaceId: inherited.workspaceId,
|
|
521
|
-
source
|
|
522
|
-
};
|
|
369
|
+
throw new Error(`Topic not found: ${topicGlobalId}`);
|
|
523
370
|
}
|
|
524
371
|
var optionalScopeArgs = {
|
|
525
372
|
projectId: v.optional(v.string()),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Id } from './convex.js';
|
|
2
|
-
import { r as resolveTopicProjectScope } from './topicScope-
|
|
2
|
+
import { r as resolveTopicProjectScope } from './topicScope-CL1IVOmv.js';
|
|
3
3
|
import '@lucern/access-control/convex';
|
|
4
4
|
import '@lucern/contracts/convex/unsafeAnyApi';
|
|
5
5
|
import 'convex/values';
|
|
@@ -2,7 +2,7 @@ export { flattenEvidenceNode, resolveEvidenceLinkedWorktreeId } from './epistemi
|
|
|
2
2
|
export { create, createAndLink, flagAsIncorrect, internalCreate, remove, update, updateStatus, updateVerificationStatus } from './epistemicEvidenceMutations.js';
|
|
3
3
|
export { getById, getByProject, getByProjectSystem, getByTopic, getEvidenceBalance, getForBelief, internalGetByProject, internalGetByTopic } from './epistemicEvidenceQueries.js';
|
|
4
4
|
import '@lucern/access-control/convex.js';
|
|
5
|
-
import './topicScope-
|
|
5
|
+
import './topicScope-CL1IVOmv.js';
|
|
6
6
|
import 'convex/values';
|
|
7
7
|
import './convex.js';
|
|
8
8
|
import '@lucern/access-control/convex';
|