@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
|
@@ -5,16 +5,15 @@ import { getCurrentUserId } from '@lucern/access-control/auth';
|
|
|
5
5
|
import { throwStructuredMutationError } from '@lucern/access-control/structuredMutationError';
|
|
6
6
|
import { normalizeTupleContradictionPolicy, mkOpinion, conditionalDeduction, project, dampedDependencyCascade, deriveContractModulationPlan, deriveContractStatus, trustDiscount, applyNegativeSupport, cumulativeFusion, applyNegativeEvidence, confidenceFromSL, detectTupleContradiction, evaluateTupleContradictionTransition, readOpinionFromRecord, parseEvidentialEvaluatorConfig, compareMetricValue, resolveComparisonResult, buildEvidentialRationale, parseMetricCheckerConfig, getEvaluatorInputRecord, pickFiniteNumber, buildComparisonRationale, parseReferenceCheckCounterConfig, parseTemporalDeadlineConfig, parseMarketIndexComparatorConfig, hasProjectedOpinionChanged, createInheritedContractRecord } from '@lucern/confidence';
|
|
7
7
|
import { v } from 'convex/values';
|
|
8
|
+
import { generateGlobalId, generateUuidV7, isUuidV7 } from '@lucern/contracts/ids';
|
|
8
9
|
import '@lucern/contracts/schema-helpers/spine/tables/epistemicNodes';
|
|
9
10
|
import { checkProjectAccess, requireScopeWriteAccess } from '@lucern/access-control/access';
|
|
10
|
-
import { generateGlobalId, generateUuidV7 } from '@lucern/contracts/ids';
|
|
11
11
|
import { permissiveReturn } from '@lucern/contracts/schema-helpers/validators';
|
|
12
12
|
|
|
13
13
|
// src/convex.ts
|
|
14
14
|
var unsafeApi = unsafeConvexAnyApi(
|
|
15
15
|
"graph-primitives top-level module bundle lacks a committed Convex _generated/api surface"
|
|
16
16
|
);
|
|
17
|
-
var api = unsafeApi;
|
|
18
17
|
componentsGeneric();
|
|
19
18
|
var internal = unsafeApi;
|
|
20
19
|
var internalMutation = internalMutationGeneric;
|
|
@@ -153,6 +152,8 @@ function promoteBeliefStatusAfterScoring(status, opts) {
|
|
|
153
152
|
}
|
|
154
153
|
return status;
|
|
155
154
|
}
|
|
155
|
+
|
|
156
|
+
// src/topicScope.ts
|
|
156
157
|
var LEGACY_SCOPE_FIELD = "graphScopeProjectId";
|
|
157
158
|
async function resolveTopicNodeScopeOrNull(ctx, ref) {
|
|
158
159
|
if (!ctx?.db || typeof ctx.db.query !== "function") {
|
|
@@ -173,16 +174,36 @@ async function resolveTopicNodeScopeOrNull(ctx, ref) {
|
|
|
173
174
|
if (!node) {
|
|
174
175
|
return null;
|
|
175
176
|
}
|
|
176
|
-
const scopeKey =
|
|
177
|
+
const scopeKey = canonicalTopicGlobalId(node);
|
|
177
178
|
if (!scopeKey) {
|
|
178
|
-
|
|
179
|
+
throw new Error(
|
|
180
|
+
`topic.uuidv7_identity_required: topic node ${ref} is missing a UUIDv7 globalId.`
|
|
181
|
+
);
|
|
179
182
|
}
|
|
183
|
+
const metadata = node.metadata ?? {};
|
|
180
184
|
return {
|
|
181
185
|
topicId: scopeKey,
|
|
182
186
|
projectId: asMappedProjectId(node),
|
|
183
|
-
source: "topic_node"
|
|
187
|
+
source: "topic_node",
|
|
188
|
+
tenantId: normalizeScopeValue(node.tenantId) ?? normalizeScopeValue(metadata.tenantId),
|
|
189
|
+
workspaceId: normalizeScopeValue(node.workspaceId) ?? normalizeScopeValue(metadata.workspaceId)
|
|
184
190
|
};
|
|
185
191
|
}
|
|
192
|
+
function canonicalTopicGlobalId(node) {
|
|
193
|
+
const globalId = normalizeScopeValue(node.globalId);
|
|
194
|
+
if (globalId && isUuidV7(globalId)) {
|
|
195
|
+
return globalId;
|
|
196
|
+
}
|
|
197
|
+
const topicId = normalizeScopeValue(node.topicId);
|
|
198
|
+
return topicId && isUuidV7(topicId) ? topicId : null;
|
|
199
|
+
}
|
|
200
|
+
function requireUuidV7TopicScope(field, value) {
|
|
201
|
+
if (!isUuidV7(value)) {
|
|
202
|
+
throw new Error(
|
|
203
|
+
`topic.uuidv7_required: ${field} must be a UUIDv7 public topic identifier.`
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
186
207
|
function asMappedProjectId(topic) {
|
|
187
208
|
if (!topic) {
|
|
188
209
|
return;
|
|
@@ -204,200 +225,25 @@ function normalizeScopeValue(value) {
|
|
|
204
225
|
const normalized = value.trim();
|
|
205
226
|
return normalized.length > 0 ? normalized : void 0;
|
|
206
227
|
}
|
|
207
|
-
function pickPrimaryTopic(candidates) {
|
|
208
|
-
return [...candidates].sort((a, b) => {
|
|
209
|
-
const depthA = a.depth ?? 9999;
|
|
210
|
-
const depthB = b.depth ?? 9999;
|
|
211
|
-
if (depthA !== depthB) {
|
|
212
|
-
return depthA - depthB;
|
|
213
|
-
}
|
|
214
|
-
const createdA = a.createdAt ?? Number.MAX_SAFE_INTEGER;
|
|
215
|
-
const createdB = b.createdAt ?? Number.MAX_SAFE_INTEGER;
|
|
216
|
-
if (createdA !== createdB) {
|
|
217
|
-
return createdA - createdB;
|
|
218
|
-
}
|
|
219
|
-
return String(a.name || "").localeCompare(String(b.name || ""));
|
|
220
|
-
})[0];
|
|
221
|
-
}
|
|
222
|
-
async function findTopicsByScopeAlias(ctx, scopeId) {
|
|
223
|
-
const query2 = ctx.db.query("topics");
|
|
224
|
-
try {
|
|
225
|
-
return await query2.withIndex(
|
|
226
|
-
"by_graph_scope_project",
|
|
227
|
-
(q) => q.eq(LEGACY_SCOPE_FIELD, scopeId)
|
|
228
|
-
).collect();
|
|
229
|
-
} catch (error) {
|
|
230
|
-
debugGraphPrimitiveFallback(
|
|
231
|
-
"[topicScope] Failed to resolve scope alias via index",
|
|
232
|
-
{
|
|
233
|
-
error,
|
|
234
|
-
scopeId
|
|
235
|
-
}
|
|
236
|
-
);
|
|
237
|
-
const topics = await query2.collect();
|
|
238
|
-
return topics.filter((topic) => {
|
|
239
|
-
const normalizedGlobalId = normalizeScopeValue(topic.globalId);
|
|
240
|
-
const mappedProjectId = asMappedProjectId(topic);
|
|
241
|
-
return String(topic._id) === scopeId || normalizedGlobalId === scopeId || mappedProjectId === scopeId;
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
async function tryResolveHostTopicById(ctx, topicId) {
|
|
246
|
-
if (typeof ctx.runQuery !== "function") {
|
|
247
|
-
return null;
|
|
248
|
-
}
|
|
249
|
-
try {
|
|
250
|
-
return await ctx.runQuery(api.topics.get, {
|
|
251
|
-
id: topicId
|
|
252
|
-
}) ?? null;
|
|
253
|
-
} catch (error) {
|
|
254
|
-
debugGraphPrimitiveFallback(
|
|
255
|
-
"[topicScope] Failed to resolve topic by host query",
|
|
256
|
-
{
|
|
257
|
-
error,
|
|
258
|
-
topicId
|
|
259
|
-
}
|
|
260
|
-
);
|
|
261
|
-
return null;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
async function tryResolveHostTopicByLegacyScope(ctx, legacyScopeId) {
|
|
265
|
-
if (typeof ctx.runQuery !== "function") {
|
|
266
|
-
return null;
|
|
267
|
-
}
|
|
268
|
-
try {
|
|
269
|
-
return await ctx.runQuery(api.topics.getByLegacyScopeId, {
|
|
270
|
-
projectId: legacyScopeId
|
|
271
|
-
}) ?? null;
|
|
272
|
-
} catch (error) {
|
|
273
|
-
debugGraphPrimitiveFallback(
|
|
274
|
-
"[topicScope] Failed to resolve topic by legacy scope",
|
|
275
|
-
{
|
|
276
|
-
error,
|
|
277
|
-
legacyScopeId
|
|
278
|
-
}
|
|
279
|
-
);
|
|
280
|
-
return null;
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
async function resolveInheritedWorkspaceScope(ctx, topic) {
|
|
284
|
-
const MAX_DEPTH = 10;
|
|
285
|
-
let tenantId = normalizeScopeValue(topic.tenantId);
|
|
286
|
-
let workspaceId = normalizeScopeValue(topic.workspaceId);
|
|
287
|
-
if (tenantId && workspaceId) {
|
|
288
|
-
return { tenantId, workspaceId };
|
|
289
|
-
}
|
|
290
|
-
let current = topic;
|
|
291
|
-
for (let i = 0; i < MAX_DEPTH && current?.parentTopicId; i++) {
|
|
292
|
-
current = await ctx.db.get(current.parentTopicId);
|
|
293
|
-
if (!current) {
|
|
294
|
-
break;
|
|
295
|
-
}
|
|
296
|
-
if (!tenantId) {
|
|
297
|
-
tenantId = normalizeScopeValue(current.tenantId);
|
|
298
|
-
}
|
|
299
|
-
if (!workspaceId) {
|
|
300
|
-
workspaceId = normalizeScopeValue(current.workspaceId);
|
|
301
|
-
}
|
|
302
|
-
if (tenantId && workspaceId) {
|
|
303
|
-
break;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
return { tenantId, workspaceId };
|
|
307
|
-
}
|
|
308
228
|
async function resolveTopicProjectScope(ctx, args) {
|
|
309
229
|
if (args.topicId) {
|
|
310
230
|
return await resolveScopeFromTopicId(ctx, args.topicId);
|
|
311
231
|
}
|
|
312
232
|
if (args.projectId) {
|
|
313
|
-
|
|
233
|
+
throw new Error(
|
|
234
|
+
"topic.uuidv7_required: projectId scope aliases are retired; pass topicId as the UUIDv7 topic globalId."
|
|
235
|
+
);
|
|
314
236
|
}
|
|
315
|
-
throw new Error(
|
|
316
|
-
"Missing scope: provide topicId (preferred) or legacy projectId alias."
|
|
317
|
-
);
|
|
237
|
+
throw new Error("topic.uuidv7_required: Missing scope: provide topicId.");
|
|
318
238
|
}
|
|
319
239
|
async function resolveScopeFromTopicId(ctx, topicId) {
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
}
|
|
324
|
-
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, String(topicId));
|
|
240
|
+
const topicGlobalId = String(topicId);
|
|
241
|
+
requireUuidV7TopicScope("topicId", topicGlobalId);
|
|
242
|
+
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, topicGlobalId);
|
|
325
243
|
if (nodeScope) {
|
|
326
244
|
return nodeScope;
|
|
327
245
|
}
|
|
328
|
-
throw new Error(`Topic not found: ${
|
|
329
|
-
}
|
|
330
|
-
async function resolveTopicDocFromTopicId(ctx, topicId) {
|
|
331
|
-
const direct = await tryReadTopicDoc(ctx, topicId, {
|
|
332
|
-
failureLog: "[topicScope] Failed to load topic by direct id",
|
|
333
|
-
idLogKey: "topicId"
|
|
334
|
-
});
|
|
335
|
-
if (direct) {
|
|
336
|
-
return direct;
|
|
337
|
-
}
|
|
338
|
-
const hostTopic = await tryResolveHostTopicById(ctx, String(topicId));
|
|
339
|
-
if (hostTopic) {
|
|
340
|
-
return hostTopic;
|
|
341
|
-
}
|
|
342
|
-
return pickPrimaryTopic(await findTopicsByScopeAlias(ctx, String(topicId))) ?? null;
|
|
343
|
-
}
|
|
344
|
-
async function resolveScopeFromLegacyProjectId(ctx, legacyProjectId) {
|
|
345
|
-
const directTopic = await resolveDirectLegacyProjectTopic(
|
|
346
|
-
ctx,
|
|
347
|
-
legacyProjectId
|
|
348
|
-
);
|
|
349
|
-
if (directTopic) {
|
|
350
|
-
return await buildTopicScope(ctx, directTopic, "topic_inferred", {
|
|
351
|
-
fallbackProjectId: legacyProjectId
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
const primary = pickPrimaryTopic(
|
|
355
|
-
await findTopicsByScopeAlias(ctx, legacyProjectId)
|
|
356
|
-
);
|
|
357
|
-
if (primary) {
|
|
358
|
-
return await buildTopicScope(ctx, primary, "project_mapped_topic", {
|
|
359
|
-
fallbackProjectId: legacyProjectId
|
|
360
|
-
});
|
|
361
|
-
}
|
|
362
|
-
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, legacyProjectId);
|
|
363
|
-
if (nodeScope) {
|
|
364
|
-
return {
|
|
365
|
-
...nodeScope,
|
|
366
|
-
projectId: nodeScope.projectId ?? legacyProjectId
|
|
367
|
-
};
|
|
368
|
-
}
|
|
369
|
-
throw new Error(
|
|
370
|
-
`Legacy project scope ${legacyProjectId} has no mapped topic.`
|
|
371
|
-
);
|
|
372
|
-
}
|
|
373
|
-
async function resolveDirectLegacyProjectTopic(ctx, legacyProjectId) {
|
|
374
|
-
const directTopic = await tryReadTopicDoc(ctx, legacyProjectId, {
|
|
375
|
-
failureLog: "[topicScope] Failed to load direct project topic",
|
|
376
|
-
idLogKey: "projectId"
|
|
377
|
-
});
|
|
378
|
-
return directTopic ?? tryResolveHostTopicByLegacyScope(ctx, legacyProjectId);
|
|
379
|
-
}
|
|
380
|
-
async function tryReadTopicDoc(ctx, id, log) {
|
|
381
|
-
try {
|
|
382
|
-
return await ctx.db.get(id);
|
|
383
|
-
} catch (error) {
|
|
384
|
-
debugGraphPrimitiveFallback(log.failureLog, {
|
|
385
|
-
error,
|
|
386
|
-
[log.idLogKey]: id
|
|
387
|
-
});
|
|
388
|
-
return null;
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
async function buildTopicScope(ctx, topic, source, options = {}) {
|
|
392
|
-
const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
|
|
393
|
-
const mapped = asMappedProjectId(topic);
|
|
394
|
-
return {
|
|
395
|
-
topicId: topic._id,
|
|
396
|
-
...mapped || options.fallbackProjectId ? { projectId: mapped ?? options.fallbackProjectId } : {},
|
|
397
|
-
tenantId: inherited.tenantId,
|
|
398
|
-
workspaceId: inherited.workspaceId,
|
|
399
|
-
source
|
|
400
|
-
};
|
|
246
|
+
throw new Error(`Topic not found: ${topicGlobalId}`);
|
|
401
247
|
}
|
|
402
248
|
({
|
|
403
249
|
projectId: v.optional(v.string()),
|
package/dist/epistemicEdges.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { Id, QueryCtx } from './convex.js';
|
|
|
3
3
|
import { EdgeStatusResult, WorkspaceEdgeScope, WorkspaceScopedEdge } from './epistemicEdges.types.js';
|
|
4
4
|
import '@lucern/access-control/convex';
|
|
5
5
|
import '@lucern/contracts/convex/unsafeAnyApi';
|
|
6
|
-
import './topicScope-
|
|
6
|
+
import './topicScope-CL1IVOmv.js';
|
|
7
7
|
|
|
8
8
|
declare const epistemicLayerValidator: convex_values.VUnion<"L4" | "L3" | "L2" | "L1" | "ontological" | "organizational", [convex_values.VLiteral<"L4", "required">, convex_values.VLiteral<"L3", "required">, convex_values.VLiteral<"L2", "required">, convex_values.VLiteral<"L1", "required">, convex_values.VLiteral<"ontological", "required">, convex_values.VLiteral<"organizational", "required">], "required", never>;
|
|
9
9
|
declare const subjectiveOpinionValidator: convex_values.VObject<{
|
package/dist/epistemicEdges.js
CHANGED
|
@@ -3,7 +3,7 @@ import { REASONING_METHODS } from '@lucern/contracts/types/reasoning-method';
|
|
|
3
3
|
import { v } from 'convex/values';
|
|
4
4
|
import { unsafeConvexAnyApi } from '@lucern/contracts/convex/unsafeAnyApi';
|
|
5
5
|
import { componentsGeneric, internalMutationGeneric, mutationGeneric, queryGeneric } from 'convex/server';
|
|
6
|
-
import { assertUuidV7Identity, assertStorageEdgeVocabulary, assertUuidShapedEdgeEndpoint } from '@lucern/contracts/ids';
|
|
6
|
+
import { assertUuidV7Identity, assertStorageEdgeVocabulary, assertUuidShapedEdgeEndpoint, isUuidV7 } from '@lucern/contracts/ids';
|
|
7
7
|
import { assertEdgePolicyAllowed } from '@lucern/contracts/manifests/edge-policy-manifest';
|
|
8
8
|
import { edgePolicyManifest } from '@lucern/contracts/manifests/edge-policy-manifest.data';
|
|
9
9
|
import { requireScopeWriteAccess, checkScopeAccess } from '@lucern/access-control/access';
|
|
@@ -287,16 +287,36 @@ async function resolveTopicNodeScopeOrNull(ctx, ref) {
|
|
|
287
287
|
if (!node) {
|
|
288
288
|
return null;
|
|
289
289
|
}
|
|
290
|
-
const scopeKey =
|
|
290
|
+
const scopeKey = canonicalTopicGlobalId(node);
|
|
291
291
|
if (!scopeKey) {
|
|
292
|
-
|
|
292
|
+
throw new Error(
|
|
293
|
+
`topic.uuidv7_identity_required: topic node ${ref} is missing a UUIDv7 globalId.`
|
|
294
|
+
);
|
|
293
295
|
}
|
|
296
|
+
const metadata = node.metadata ?? {};
|
|
294
297
|
return {
|
|
295
298
|
topicId: scopeKey,
|
|
296
299
|
projectId: asMappedProjectId(node),
|
|
297
|
-
source: "topic_node"
|
|
300
|
+
source: "topic_node",
|
|
301
|
+
tenantId: normalizeScopeValue(node.tenantId) ?? normalizeScopeValue(metadata.tenantId),
|
|
302
|
+
workspaceId: normalizeScopeValue(node.workspaceId) ?? normalizeScopeValue(metadata.workspaceId)
|
|
298
303
|
};
|
|
299
304
|
}
|
|
305
|
+
function canonicalTopicGlobalId(node) {
|
|
306
|
+
const globalId = normalizeScopeValue(node.globalId);
|
|
307
|
+
if (globalId && isUuidV7(globalId)) {
|
|
308
|
+
return globalId;
|
|
309
|
+
}
|
|
310
|
+
const topicId = normalizeScopeValue(node.topicId);
|
|
311
|
+
return topicId && isUuidV7(topicId) ? topicId : null;
|
|
312
|
+
}
|
|
313
|
+
function requireUuidV7TopicScope(field, value) {
|
|
314
|
+
if (!isUuidV7(value)) {
|
|
315
|
+
throw new Error(
|
|
316
|
+
`topic.uuidv7_required: ${field} must be a UUIDv7 public topic identifier.`
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
300
320
|
function asMappedProjectId(topic) {
|
|
301
321
|
if (!topic) {
|
|
302
322
|
return;
|
|
@@ -318,200 +338,25 @@ function normalizeScopeValue(value) {
|
|
|
318
338
|
const normalized = value.trim();
|
|
319
339
|
return normalized.length > 0 ? normalized : void 0;
|
|
320
340
|
}
|
|
321
|
-
function pickPrimaryTopic(candidates) {
|
|
322
|
-
return [...candidates].sort((a, b) => {
|
|
323
|
-
const depthA = a.depth ?? 9999;
|
|
324
|
-
const depthB = b.depth ?? 9999;
|
|
325
|
-
if (depthA !== depthB) {
|
|
326
|
-
return depthA - depthB;
|
|
327
|
-
}
|
|
328
|
-
const createdA = a.createdAt ?? Number.MAX_SAFE_INTEGER;
|
|
329
|
-
const createdB = b.createdAt ?? Number.MAX_SAFE_INTEGER;
|
|
330
|
-
if (createdA !== createdB) {
|
|
331
|
-
return createdA - createdB;
|
|
332
|
-
}
|
|
333
|
-
return String(a.name || "").localeCompare(String(b.name || ""));
|
|
334
|
-
})[0];
|
|
335
|
-
}
|
|
336
|
-
async function findTopicsByScopeAlias(ctx, scopeId) {
|
|
337
|
-
const query2 = ctx.db.query("topics");
|
|
338
|
-
try {
|
|
339
|
-
return await query2.withIndex(
|
|
340
|
-
"by_graph_scope_project",
|
|
341
|
-
(q) => q.eq(LEGACY_SCOPE_FIELD, scopeId)
|
|
342
|
-
).collect();
|
|
343
|
-
} catch (error) {
|
|
344
|
-
debugGraphPrimitiveFallback(
|
|
345
|
-
"[topicScope] Failed to resolve scope alias via index",
|
|
346
|
-
{
|
|
347
|
-
error,
|
|
348
|
-
scopeId
|
|
349
|
-
}
|
|
350
|
-
);
|
|
351
|
-
const topics = await query2.collect();
|
|
352
|
-
return topics.filter((topic) => {
|
|
353
|
-
const normalizedGlobalId = normalizeScopeValue(topic.globalId);
|
|
354
|
-
const mappedProjectId = asMappedProjectId(topic);
|
|
355
|
-
return String(topic._id) === scopeId || normalizedGlobalId === scopeId || mappedProjectId === scopeId;
|
|
356
|
-
});
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
async function tryResolveHostTopicById(ctx, topicId) {
|
|
360
|
-
if (typeof ctx.runQuery !== "function") {
|
|
361
|
-
return null;
|
|
362
|
-
}
|
|
363
|
-
try {
|
|
364
|
-
return await ctx.runQuery(api.topics.get, {
|
|
365
|
-
id: topicId
|
|
366
|
-
}) ?? null;
|
|
367
|
-
} catch (error) {
|
|
368
|
-
debugGraphPrimitiveFallback(
|
|
369
|
-
"[topicScope] Failed to resolve topic by host query",
|
|
370
|
-
{
|
|
371
|
-
error,
|
|
372
|
-
topicId
|
|
373
|
-
}
|
|
374
|
-
);
|
|
375
|
-
return null;
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
async function tryResolveHostTopicByLegacyScope(ctx, legacyScopeId) {
|
|
379
|
-
if (typeof ctx.runQuery !== "function") {
|
|
380
|
-
return null;
|
|
381
|
-
}
|
|
382
|
-
try {
|
|
383
|
-
return await ctx.runQuery(api.topics.getByLegacyScopeId, {
|
|
384
|
-
projectId: legacyScopeId
|
|
385
|
-
}) ?? null;
|
|
386
|
-
} catch (error) {
|
|
387
|
-
debugGraphPrimitiveFallback(
|
|
388
|
-
"[topicScope] Failed to resolve topic by legacy scope",
|
|
389
|
-
{
|
|
390
|
-
error,
|
|
391
|
-
legacyScopeId
|
|
392
|
-
}
|
|
393
|
-
);
|
|
394
|
-
return null;
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
async function resolveInheritedWorkspaceScope(ctx, topic) {
|
|
398
|
-
const MAX_DEPTH = 10;
|
|
399
|
-
let tenantId = normalizeScopeValue(topic.tenantId);
|
|
400
|
-
let workspaceId = normalizeScopeValue(topic.workspaceId);
|
|
401
|
-
if (tenantId && workspaceId) {
|
|
402
|
-
return { tenantId, workspaceId };
|
|
403
|
-
}
|
|
404
|
-
let current = topic;
|
|
405
|
-
for (let i = 0; i < MAX_DEPTH && current?.parentTopicId; i++) {
|
|
406
|
-
current = await ctx.db.get(current.parentTopicId);
|
|
407
|
-
if (!current) {
|
|
408
|
-
break;
|
|
409
|
-
}
|
|
410
|
-
if (!tenantId) {
|
|
411
|
-
tenantId = normalizeScopeValue(current.tenantId);
|
|
412
|
-
}
|
|
413
|
-
if (!workspaceId) {
|
|
414
|
-
workspaceId = normalizeScopeValue(current.workspaceId);
|
|
415
|
-
}
|
|
416
|
-
if (tenantId && workspaceId) {
|
|
417
|
-
break;
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
return { tenantId, workspaceId };
|
|
421
|
-
}
|
|
422
341
|
async function resolveTopicProjectScope(ctx, args) {
|
|
423
342
|
if (args.topicId) {
|
|
424
343
|
return await resolveScopeFromTopicId(ctx, args.topicId);
|
|
425
344
|
}
|
|
426
345
|
if (args.projectId) {
|
|
427
|
-
|
|
346
|
+
throw new Error(
|
|
347
|
+
"topic.uuidv7_required: projectId scope aliases are retired; pass topicId as the UUIDv7 topic globalId."
|
|
348
|
+
);
|
|
428
349
|
}
|
|
429
|
-
throw new Error(
|
|
430
|
-
"Missing scope: provide topicId (preferred) or legacy projectId alias."
|
|
431
|
-
);
|
|
350
|
+
throw new Error("topic.uuidv7_required: Missing scope: provide topicId.");
|
|
432
351
|
}
|
|
433
352
|
async function resolveScopeFromTopicId(ctx, topicId) {
|
|
434
|
-
const
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
}
|
|
438
|
-
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, String(topicId));
|
|
353
|
+
const topicGlobalId = String(topicId);
|
|
354
|
+
requireUuidV7TopicScope("topicId", topicGlobalId);
|
|
355
|
+
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, topicGlobalId);
|
|
439
356
|
if (nodeScope) {
|
|
440
357
|
return nodeScope;
|
|
441
358
|
}
|
|
442
|
-
throw new Error(`Topic not found: ${
|
|
443
|
-
}
|
|
444
|
-
async function resolveTopicDocFromTopicId(ctx, topicId) {
|
|
445
|
-
const direct = await tryReadTopicDoc(ctx, topicId, {
|
|
446
|
-
failureLog: "[topicScope] Failed to load topic by direct id",
|
|
447
|
-
idLogKey: "topicId"
|
|
448
|
-
});
|
|
449
|
-
if (direct) {
|
|
450
|
-
return direct;
|
|
451
|
-
}
|
|
452
|
-
const hostTopic = await tryResolveHostTopicById(ctx, String(topicId));
|
|
453
|
-
if (hostTopic) {
|
|
454
|
-
return hostTopic;
|
|
455
|
-
}
|
|
456
|
-
return pickPrimaryTopic(await findTopicsByScopeAlias(ctx, String(topicId))) ?? null;
|
|
457
|
-
}
|
|
458
|
-
async function resolveScopeFromLegacyProjectId(ctx, legacyProjectId) {
|
|
459
|
-
const directTopic = await resolveDirectLegacyProjectTopic(
|
|
460
|
-
ctx,
|
|
461
|
-
legacyProjectId
|
|
462
|
-
);
|
|
463
|
-
if (directTopic) {
|
|
464
|
-
return await buildTopicScope(ctx, directTopic, "topic_inferred", {
|
|
465
|
-
fallbackProjectId: legacyProjectId
|
|
466
|
-
});
|
|
467
|
-
}
|
|
468
|
-
const primary = pickPrimaryTopic(
|
|
469
|
-
await findTopicsByScopeAlias(ctx, legacyProjectId)
|
|
470
|
-
);
|
|
471
|
-
if (primary) {
|
|
472
|
-
return await buildTopicScope(ctx, primary, "project_mapped_topic", {
|
|
473
|
-
fallbackProjectId: legacyProjectId
|
|
474
|
-
});
|
|
475
|
-
}
|
|
476
|
-
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, legacyProjectId);
|
|
477
|
-
if (nodeScope) {
|
|
478
|
-
return {
|
|
479
|
-
...nodeScope,
|
|
480
|
-
projectId: nodeScope.projectId ?? legacyProjectId
|
|
481
|
-
};
|
|
482
|
-
}
|
|
483
|
-
throw new Error(
|
|
484
|
-
`Legacy project scope ${legacyProjectId} has no mapped topic.`
|
|
485
|
-
);
|
|
486
|
-
}
|
|
487
|
-
async function resolveDirectLegacyProjectTopic(ctx, legacyProjectId) {
|
|
488
|
-
const directTopic = await tryReadTopicDoc(ctx, legacyProjectId, {
|
|
489
|
-
failureLog: "[topicScope] Failed to load direct project topic",
|
|
490
|
-
idLogKey: "projectId"
|
|
491
|
-
});
|
|
492
|
-
return directTopic ?? tryResolveHostTopicByLegacyScope(ctx, legacyProjectId);
|
|
493
|
-
}
|
|
494
|
-
async function tryReadTopicDoc(ctx, id, log) {
|
|
495
|
-
try {
|
|
496
|
-
return await ctx.db.get(id);
|
|
497
|
-
} catch (error) {
|
|
498
|
-
debugGraphPrimitiveFallback(log.failureLog, {
|
|
499
|
-
error,
|
|
500
|
-
[log.idLogKey]: id
|
|
501
|
-
});
|
|
502
|
-
return null;
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
async function buildTopicScope(ctx, topic, source, options = {}) {
|
|
506
|
-
const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
|
|
507
|
-
const mapped = asMappedProjectId(topic);
|
|
508
|
-
return {
|
|
509
|
-
topicId: topic._id,
|
|
510
|
-
...mapped || options.fallbackProjectId ? { projectId: mapped ?? options.fallbackProjectId } : {},
|
|
511
|
-
tenantId: inherited.tenantId,
|
|
512
|
-
workspaceId: inherited.workspaceId,
|
|
513
|
-
source
|
|
514
|
-
};
|
|
359
|
+
throw new Error(`Topic not found: ${topicGlobalId}`);
|
|
515
360
|
}
|
|
516
361
|
var optionalScopeArgs = {
|
|
517
362
|
projectId: v.optional(v.string()),
|