@lucern/graph-primitives 0.3.0-alpha.0 → 0.3.0-alpha.1

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.
Files changed (76) hide show
  1. package/dist/beliefDecay.js +37 -1104
  2. package/dist/beliefDecay.js.map +1 -1
  3. package/dist/beliefEvidenceLinks.js +53 -834
  4. package/dist/beliefEvidenceLinks.js.map +1 -1
  5. package/dist/confidencePropagationDispatch.d.ts +3 -3
  6. package/dist/confidencePropagationDispatch.js +30 -308
  7. package/dist/confidencePropagationDispatch.js.map +1 -1
  8. package/dist/contradictions.js +5 -797
  9. package/dist/contradictions.js.map +1 -1
  10. package/dist/edges/contradicts.js +1 -122
  11. package/dist/edges/contradicts.js.map +1 -1
  12. package/dist/edges/dependsOn.js +14 -172
  13. package/dist/edges/dependsOn.js.map +1 -1
  14. package/dist/edges/elaborates.js +1 -49
  15. package/dist/edges/elaborates.js.map +1 -1
  16. package/dist/edges/index.js +14 -277
  17. package/dist/edges/index.js.map +1 -1
  18. package/dist/edges/informs.js +1 -62
  19. package/dist/edges/informs.js.map +1 -1
  20. package/dist/edges/propagationTypes.d.ts +2 -2
  21. package/dist/edges/propagationTypes.js.map +1 -1
  22. package/dist/edges/refutes.js +1 -62
  23. package/dist/edges/refutes.js.map +1 -1
  24. package/dist/edges/supports.js +1 -122
  25. package/dist/edges/supports.js.map +1 -1
  26. package/dist/edges/utils.d.ts +6 -6
  27. package/dist/edges/utils.js +1 -130
  28. package/dist/edges/utils.js.map +1 -1
  29. package/dist/entityBridge.js +2 -17
  30. package/dist/entityBridge.js.map +1 -1
  31. package/dist/entityLifecycle.js +62 -848
  32. package/dist/entityLifecycle.js.map +1 -1
  33. package/dist/epistemicAnswers.js +6 -802
  34. package/dist/epistemicAnswers.js.map +1 -1
  35. package/dist/epistemicBeliefs.js +125 -1594
  36. package/dist/epistemicBeliefs.js.map +1 -1
  37. package/dist/epistemicContractHelpers.js +1 -318
  38. package/dist/epistemicContractHelpers.js.map +1 -1
  39. package/dist/epistemicContracts.js +129 -1874
  40. package/dist/epistemicContracts.js.map +1 -1
  41. package/dist/epistemicEdges.js +60 -863
  42. package/dist/epistemicEdges.js.map +1 -1
  43. package/dist/epistemicEvidence.js +69 -1041
  44. package/dist/epistemicEvidence.js.map +1 -1
  45. package/dist/epistemicLinking.js +2 -785
  46. package/dist/epistemicLinking.js.map +1 -1
  47. package/dist/epistemicNodes.js +9 -866
  48. package/dist/epistemicNodes.js.map +1 -1
  49. package/dist/epistemicQuestions.js +66 -1071
  50. package/dist/epistemicQuestions.js.map +1 -1
  51. package/dist/epistemicSources.js +23 -880
  52. package/dist/epistemicSources.js.map +1 -1
  53. package/dist/evaluators/index.js +129 -1874
  54. package/dist/evaluators/index.js.map +1 -1
  55. package/dist/index.js +182 -2744
  56. package/dist/index.js.map +1 -1
  57. package/dist/ontology-matching.js +1 -344
  58. package/dist/ontology-matching.js.map +1 -1
  59. package/dist/ontologyApproval.js +1 -13
  60. package/dist/ontologyApproval.js.map +1 -1
  61. package/dist/ontologyDefinitions.js +2 -17
  62. package/dist/ontologyDefinitions.js.map +1 -1
  63. package/dist/ontologyRegistry.js +2 -17
  64. package/dist/ontologyRegistry.js.map +1 -1
  65. package/dist/projectionReconciliation.js +2 -17
  66. package/dist/projectionReconciliation.js.map +1 -1
  67. package/dist/questionEvidenceLinks.js +60 -841
  68. package/dist/questionEvidenceLinks.js.map +1 -1
  69. package/dist/text-matching.js +1 -244
  70. package/dist/text-matching.js.map +1 -1
  71. package/dist/workflowBridge.d.ts +27 -0
  72. package/dist/workflowBridge.js +303 -0
  73. package/dist/workflowBridge.js.map +1 -0
  74. package/dist/workspaceIsolation.js +2 -52
  75. package/dist/workspaceIsolation.js.map +1 -1
  76. package/package.json +6 -5
@@ -1,1440 +1,16 @@
1
1
  import { v } from 'convex/values';
2
+ import { normalizeTupleContradictionPolicy, mkOpinion, createInheritedContractRecord, confidenceFromSL, conditionalDeduction, project, dampedDependencyCascade, hasProjectedOpinionChanged, detectTupleContradiction, evaluateTupleContradictionTransition, readOpinionFromRecord, trustDiscount, applyNegativeSupport, cumulativeFusion, applyNegativeEvidence } from '@lucern/confidence';
3
+ import { checkScopeAccess, checkProjectAccess } from '@lucern/access-control/access';
4
+ import { canAudienceClassAccess, normalizeAudienceKey, classFromAudienceKey } from '@lucern/access-control/audience';
5
+ import { listAudienceRegistryRows } from '@lucern/access-control/audienceRegistry';
6
+ import { getCurrentUserId } from '@lucern/access-control/auth';
7
+ import { assertSchemaEnumValue } from '@lucern/contracts/schema-helpers/enumValidation';
8
+ import { permissiveReturn } from '@lucern/contracts/schema-helpers/validators';
2
9
  import { componentsGeneric, internalMutationGeneric, mutationGeneric, anyApi, queryGeneric, internalQueryGeneric } from 'convex/server';
10
+ import { isNodeType, getLayerForNodeType } from '@lucern/contracts/schema-helpers/spine/tables/epistemicNodes';
3
11
 
4
12
  // src/epistemicBeliefs.ts
5
13
 
6
- // ../confidence/src/v1/operations/subjectiveLogic/index.ts
7
- function opinion(belief, disbelief, uncertainty, baseRate = 0.5) {
8
- const b = Math.max(0, Math.min(1, belief));
9
- const d = Math.max(0, Math.min(1, disbelief));
10
- const u = Math.max(0, Math.min(1, uncertainty));
11
- const a = Math.max(0, Math.min(1, baseRate));
12
- const sum = b + d + u;
13
- if (sum === 0) {
14
- return { b: 0, d: 0, u: 1, a };
15
- }
16
- return {
17
- b: b / sum,
18
- d: d / sum,
19
- u: u / sum,
20
- a
21
- };
22
- }
23
- function vacuous(baseRate = 0.5) {
24
- return { b: 0, d: 0, u: 1, a: baseRate };
25
- }
26
- function project(o) {
27
- return o.b + o.a * o.u;
28
- }
29
- function cumulativeFusion(left, right) {
30
- if (left.u === 0 && right.u === 0) {
31
- return opinion(
32
- (left.b + right.b) / 2,
33
- (left.d + right.d) / 2,
34
- 0,
35
- (left.a + right.a) / 2
36
- );
37
- }
38
- const k = left.u + right.u - left.u * right.u;
39
- if (k === 0) {
40
- return vacuous((left.a + right.a) / 2);
41
- }
42
- return opinion(
43
- (left.b * right.u + right.b * left.u) / k,
44
- (left.d * right.u + right.d * left.u) / k,
45
- left.u * right.u / k,
46
- (left.a + right.a) / 2
47
- );
48
- }
49
- function trustDiscount(sourceOpinion, trust) {
50
- const weight = Math.max(0, Math.min(1, Math.abs(trust)));
51
- return opinion(
52
- weight * sourceOpinion.b,
53
- weight * sourceOpinion.d,
54
- 1 - weight * (sourceOpinion.b + sourceOpinion.d),
55
- sourceOpinion.a
56
- );
57
- }
58
- var EPSILON = 1e-9;
59
- function childBaseRateFallback(ifTrue, ifFalse, fallbackBaseRate) {
60
- if (fallbackBaseRate !== void 0) {
61
- return Math.max(0, Math.min(1, fallbackBaseRate));
62
- }
63
- if (Math.abs(ifTrue.a - ifFalse.a) <= EPSILON) {
64
- return ifTrue.a;
65
- }
66
- return (ifTrue.a + ifFalse.a) / 2;
67
- }
68
- function computeConditionalDeductionBaseRate(opinionA, ifTrue, ifFalse, fallbackBaseRate) {
69
- const denominator = 1 - opinionA.a * ifTrue.u - (1 - opinionA.a) * ifFalse.u;
70
- if (ifTrue.u + ifFalse.u < 2 - EPSILON && Math.abs(denominator) > EPSILON) {
71
- const baseRate = (opinionA.a * ifTrue.b + (1 - opinionA.a) * ifFalse.b) / denominator;
72
- if (baseRate >= -EPSILON && baseRate <= 1 + EPSILON) {
73
- return Math.max(0, Math.min(1, baseRate));
74
- }
75
- }
76
- return fallbackBaseRate;
77
- }
78
- function safeCorrectionTerm(numerator, denominator) {
79
- if (Math.abs(denominator) <= EPSILON) {
80
- return void 0;
81
- }
82
- const value = numerator / denominator;
83
- if (!Number.isFinite(value)) {
84
- return void 0;
85
- }
86
- return Math.max(0, value);
87
- }
88
- function conditionalDeduction(opinionA, ifTrue, ifFalse, fallbackBaseRate) {
89
- const fallbackChildBaseRate = childBaseRateFallback(
90
- ifTrue,
91
- ifFalse,
92
- fallbackBaseRate
93
- );
94
- const childBaseRate = computeConditionalDeductionBaseRate(
95
- opinionA,
96
- ifTrue,
97
- ifFalse,
98
- fallbackChildBaseRate
99
- );
100
- const projectedAntecedent = project(opinionA);
101
- const projectedAntecedentComplement = 1 - projectedAntecedent;
102
- const intermediateBelief = opinionA.b * ifTrue.b + opinionA.d * ifFalse.b + opinionA.u * (ifTrue.b * opinionA.a + ifFalse.b * (1 - opinionA.a));
103
- const intermediateDisbelief = opinionA.b * ifTrue.d + opinionA.d * ifFalse.d + opinionA.u * (ifTrue.d * opinionA.a + ifFalse.d * (1 - opinionA.a));
104
- const intermediateUncertainty = opinionA.b * ifTrue.u + opinionA.d * ifFalse.u + opinionA.u * (ifTrue.u * opinionA.a + ifFalse.u * (1 - opinionA.a));
105
- const projectedVacuousDeduction = ifTrue.b * opinionA.a + ifFalse.b * (1 - opinionA.a) + childBaseRate * (ifTrue.u * opinionA.a + ifFalse.u * (1 - opinionA.a));
106
- const projectedConditionalA = ifTrue.b + childBaseRate * (1 - ifTrue.b - ifTrue.d);
107
- let correction = 0;
108
- if (ifTrue.b > ifFalse.b && ifTrue.d > ifFalse.d || ifTrue.b <= ifFalse.b && ifTrue.d <= ifFalse.d) {
109
- correction = 0;
110
- } else if (ifTrue.b > ifFalse.b && ifTrue.d <= ifFalse.d) {
111
- const beliefGap = ifTrue.b - ifFalse.b;
112
- const disbeliefGap = ifFalse.d - ifTrue.d;
113
- if (projectedVacuousDeduction <= projectedConditionalA && projectedAntecedent <= opinionA.a) {
114
- correction = safeCorrectionTerm(
115
- opinionA.a * opinionA.u * (intermediateBelief - ifTrue.b),
116
- projectedAntecedent * childBaseRate
117
- ) ?? 0;
118
- } else if (projectedVacuousDeduction <= projectedConditionalA && projectedAntecedent > opinionA.a) {
119
- correction = safeCorrectionTerm(
120
- opinionA.a * opinionA.u * (intermediateDisbelief - ifTrue.d) * beliefGap,
121
- projectedAntecedentComplement * childBaseRate * disbeliefGap
122
- ) ?? 0;
123
- } else if (projectedVacuousDeduction > projectedConditionalA && projectedAntecedent <= opinionA.a) {
124
- correction = safeCorrectionTerm(
125
- (1 - opinionA.a) * opinionA.u * (intermediateBelief - ifTrue.b) * disbeliefGap,
126
- projectedAntecedent * (1 - childBaseRate) * beliefGap
127
- ) ?? 0;
128
- } else {
129
- correction = safeCorrectionTerm(
130
- (1 - opinionA.a) * opinionA.u * (intermediateDisbelief - ifTrue.d),
131
- projectedAntecedentComplement * (1 - childBaseRate)
132
- ) ?? 0;
133
- }
134
- } else {
135
- const beliefGap = ifFalse.b - ifTrue.b;
136
- const disbeliefGap = ifTrue.d - ifFalse.d;
137
- if (projectedVacuousDeduction <= projectedConditionalA && projectedAntecedent <= opinionA.a) {
138
- correction = safeCorrectionTerm(
139
- (1 - opinionA.a) * opinionA.u * (intermediateDisbelief - ifTrue.d) * beliefGap,
140
- projectedAntecedent * childBaseRate * disbeliefGap
141
- ) ?? 0;
142
- } else if (projectedVacuousDeduction <= projectedConditionalA && projectedAntecedent > opinionA.a) {
143
- correction = safeCorrectionTerm(
144
- (1 - opinionA.a) * opinionA.u * (intermediateBelief - ifTrue.b),
145
- projectedAntecedentComplement * childBaseRate
146
- ) ?? 0;
147
- } else if (projectedVacuousDeduction > projectedConditionalA && projectedAntecedent <= opinionA.a) {
148
- correction = safeCorrectionTerm(
149
- opinionA.a * opinionA.u * (intermediateDisbelief - ifTrue.d),
150
- projectedAntecedent * (1 - childBaseRate)
151
- ) ?? 0;
152
- } else {
153
- correction = safeCorrectionTerm(
154
- opinionA.a * opinionA.u * (intermediateBelief - ifTrue.b) * disbeliefGap,
155
- projectedAntecedentComplement * (1 - childBaseRate) * beliefGap
156
- ) ?? 0;
157
- }
158
- }
159
- return opinion(
160
- intermediateBelief - childBaseRate * correction,
161
- intermediateDisbelief - (1 - childBaseRate) * correction,
162
- intermediateUncertainty + correction,
163
- childBaseRate
164
- );
165
- }
166
- function negate(o) {
167
- return { b: o.d, d: o.b, u: o.u, a: 1 - o.a };
168
- }
169
- function constraintFusion(left, right, mode = "pressure") {
170
- if (mode === "redistribute") {
171
- const leftProjected = project(left);
172
- const rightProjected = project(right);
173
- const total = leftProjected + rightProjected;
174
- if (total <= 1) {
175
- return { o1: left, o2: right };
176
- }
177
- const scale = 1 / total;
178
- return {
179
- o1: opinion(
180
- left.b * scale,
181
- left.d + left.b * (1 - scale),
182
- left.u,
183
- left.a
184
- ),
185
- o2: opinion(
186
- right.b * scale,
187
- right.d + right.b * (1 - scale),
188
- right.u,
189
- right.a
190
- )
191
- };
192
- }
193
- const pressureLeft = right.b * 0.5;
194
- const pressureRight = left.b * 0.5;
195
- return {
196
- o1: opinion(
197
- left.b - pressureLeft * 0.3,
198
- left.d + pressureLeft * 0.3,
199
- left.u,
200
- left.a
201
- ),
202
- o2: opinion(
203
- right.b - pressureRight * 0.3,
204
- right.d + pressureRight * 0.3,
205
- right.u,
206
- right.a
207
- )
208
- };
209
- }
210
-
211
- // ../confidence/src/v1/operations/scoring.ts
212
- function finiteNumber(value) {
213
- return typeof value === "number" && Number.isFinite(value) ? value : void 0;
214
- }
215
- function clamp01(value) {
216
- return Math.max(0, Math.min(1, value));
217
- }
218
- function confidenceFromOpinion(opinion2) {
219
- return clamp01(opinion2.b + opinion2.a * opinion2.u);
220
- }
221
- function confidenceFromSL(belief, _disbelief, uncertainty, baseRate = 0.5) {
222
- return confidenceFromOpinion({
223
- b: belief,
224
- u: uncertainty,
225
- a: baseRate
226
- });
227
- }
228
- function readOpinionFromRecord(source, fallback = {}) {
229
- const record = source && typeof source === "object" ? source : {};
230
- return {
231
- b: finiteNumber(record.b) ?? finiteNumber(record.belief) ?? finiteNumber(record.slBelief) ?? finiteNumber(record.opinion_b) ?? fallback.b ?? 0,
232
- d: finiteNumber(record.d) ?? finiteNumber(record.disbelief) ?? finiteNumber(record.slDisbelief) ?? finiteNumber(record.opinion_d) ?? fallback.d ?? 0,
233
- u: finiteNumber(record.u) ?? finiteNumber(record.uncertainty) ?? finiteNumber(record.slUncertainty) ?? finiteNumber(record.opinion_u) ?? fallback.u ?? 1,
234
- a: finiteNumber(record.a) ?? finiteNumber(record.baseRate) ?? finiteNumber(record.slBaseRate) ?? finiteNumber(record.opinion_a) ?? fallback.a ?? 0.5
235
- };
236
- }
237
- function hasProjectedOpinionChanged(current, next, tolerance = 0.01) {
238
- return Math.abs(confidenceFromOpinion(next) - confidenceFromOpinion(current)) >= tolerance;
239
- }
240
-
241
- // ../confidence/src/v1/operations/contradiction/detectTupleContradiction.ts
242
- var DEFAULT_TUPLE_CONTRADICTION_BELIEF_THRESHOLD = 0.7;
243
- var DEFAULT_TUPLE_CONTRADICTION_DISBELIEF_THRESHOLD = 0.7;
244
- function normalizeTupleContradictionPolicy(policy = {}) {
245
- return {
246
- beliefThreshold: clamp01(
247
- policy.beliefThreshold ?? DEFAULT_TUPLE_CONTRADICTION_BELIEF_THRESHOLD
248
- ),
249
- disbeliefThreshold: clamp01(
250
- policy.disbeliefThreshold ?? DEFAULT_TUPLE_CONTRADICTION_DISBELIEF_THRESHOLD
251
- )
252
- };
253
- }
254
- function detectTupleContradiction(opinion2, tauB = DEFAULT_TUPLE_CONTRADICTION_BELIEF_THRESHOLD, tauD = DEFAULT_TUPLE_CONTRADICTION_DISBELIEF_THRESHOLD) {
255
- return opinion2.b > tauB && opinion2.d > tauD;
256
- }
257
- function evaluateTupleContradictionTransition(args) {
258
- const policy = normalizeTupleContradictionPolicy(args.policy);
259
- const tupleContradicted = detectTupleContradiction(
260
- args.opinion,
261
- policy.beliefThreshold,
262
- policy.disbeliefThreshold
263
- );
264
- const previousTupleContradicted = Boolean(args.previousTupleContradicted);
265
- return {
266
- tupleContradicted,
267
- crossedIntoTupleContradiction: !previousTupleContradicted && tupleContradicted,
268
- crossedOutOfTupleContradiction: previousTupleContradicted && !tupleContradicted,
269
- policy
270
- };
271
- }
272
-
273
- // ../confidence/src/v1/operations/dynamics/cascade.ts
274
- function dampedDependencyOpinion(dependencyOpinion, beliefOpinion, mode = "continuous", threshold = 0.3) {
275
- const dependencyProjection = project(dependencyOpinion);
276
- if (mode === "threshold") {
277
- if (dependencyProjection < threshold) {
278
- return opinion(
279
- 0,
280
- beliefOpinion.d + beliefOpinion.b * 0.5,
281
- 0.5,
282
- beliefOpinion.a
283
- );
284
- }
285
- return beliefOpinion;
286
- }
287
- const dampingFactor = Math.pow(dependencyProjection, 0.5);
288
- return opinion(
289
- beliefOpinion.b * dampingFactor,
290
- beliefOpinion.d + beliefOpinion.b * (1 - dampingFactor) * 0.3,
291
- beliefOpinion.u + beliefOpinion.b * (1 - dampingFactor) * 0.7,
292
- beliefOpinion.a
293
- );
294
- }
295
- function dampedDependencyCascade(dependencyOpinion, beliefOpinion, mode = "continuous") {
296
- return {
297
- opinion: dampedDependencyOpinion(dependencyOpinion, beliefOpinion, mode),
298
- operator: "dependency_cascade",
299
- rationale: `Damped dependency cascade (${mode}): prerequisite at ${project(
300
- dependencyOpinion
301
- ).toFixed(2)}`
302
- };
303
- }
304
-
305
- // ../confidence/src/v1/operations/dynamics/defeat.ts
306
- function applyNegativeSupport(source, target, weight, metadata = {}) {
307
- if (metadata.constraint === "xor") {
308
- const result = constraintFusion(
309
- source,
310
- target,
311
- metadata.normalization ?? "pressure"
312
- );
313
- return {
314
- opinion: result.o2,
315
- operator: "constraint_fusion",
316
- rationale: `XOR constraint: source belief at ${project(source).toFixed(
317
- 2
318
- )} pressures target`
319
- };
320
- }
321
- const discounted = trustDiscount(negate(source), Math.abs(weight));
322
- return {
323
- opinion: cumulativeFusion(target, discounted),
324
- operator: "cumulative_fusion",
325
- rationale: `Contradicting evidence (weight=${weight.toFixed(
326
- 2
327
- )}) from source at ${project(source).toFixed(2)}`
328
- };
329
- }
330
- function applyNegativeEvidence(source, target, weight) {
331
- const discounted = trustDiscount(negate(source), Math.abs(weight));
332
- return {
333
- opinion: cumulativeFusion(target, discounted),
334
- operator: "cumulative_fusion",
335
- rationale: `Contradicting evidence (weight=${weight.toFixed(2)})`
336
- };
337
- }
338
-
339
- // ../confidence/src/v1/operations/contracts/epistemicContract.ts
340
- function generateContractId() {
341
- if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
342
- return crypto.randomUUID();
343
- }
344
- return `contract-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
345
- }
346
- function createInheritedContractRecord(contract, args) {
347
- return {
348
- beliefNodeId: args.beliefNodeId,
349
- contractId: generateContractId(),
350
- title: contract.title,
351
- description: contract.description,
352
- conditionType: contract.conditionType,
353
- direction: contract.direction,
354
- condition: contract.condition,
355
- deadline: contract.deadline,
356
- compositeOf: contract.compositeOf,
357
- compositeOperator: contract.compositeOperator,
358
- modulation: contract.modulation,
359
- evaluationSchedule: contract.evaluationSchedule,
360
- periodicIntervalMs: contract.periodicIntervalMs,
361
- status: "active",
362
- lineageSource: "inherited",
363
- inheritedFromContractId: contract.contractId,
364
- inheritedFromBeliefNodeId: contract.beliefNodeId,
365
- inheritedAt: args.now,
366
- topicId: args.topicId,
367
- createdAt: args.now,
368
- createdBy: args.createdBy,
369
- updatedAt: args.now
370
- };
371
- }
372
- var api = anyApi;
373
- componentsGeneric();
374
-
375
- // ../access-control/src/topicProjectOverlay.ts
376
- var LEGACY_SCOPE_FIELD = "graphScopeProjectId";
377
- function readNonEmptyString(value) {
378
- if (typeof value !== "string") {
379
- return;
380
- }
381
- const normalized = value.trim();
382
- return normalized.length > 0 ? normalized : void 0;
383
- }
384
- function readStringArray(value) {
385
- if (!Array.isArray(value)) {
386
- return [];
387
- }
388
- return value.map((entry) => readNonEmptyString(entry)).filter((entry) => Boolean(entry));
389
- }
390
- function readMetadata(topic) {
391
- return topic.metadata && typeof topic.metadata === "object" ? topic.metadata : {};
392
- }
393
- function readLegacyProjectId(value) {
394
- if (!value) {
395
- return;
396
- }
397
- return readNonEmptyString(value[LEGACY_SCOPE_FIELD]);
398
- }
399
- function coerceVisibility(value) {
400
- return value === "private" || value === "team" || value === "firm" || value === "external" || value === "public" ? value : void 0;
401
- }
402
- function coerceStatus(value) {
403
- return value === "active" || value === "archived" || value === "watching" ? value : void 0;
404
- }
405
- function mapProjectType(topic, metadata) {
406
- const explicit = readNonEmptyString(metadata.projectType);
407
- if (explicit) {
408
- return explicit;
409
- }
410
- if (topic.type === "theme") {
411
- return "thematic";
412
- }
413
- return readNonEmptyString(topic.type) || "general";
414
- }
415
- function isProjectLikeTopic(topic) {
416
- const metadata = readMetadata(topic);
417
- return topic.type === "theme" || topic.type === "thematic" || topic.type === "deal" || topic.type === "monitoring" || readLegacyProjectId(topic) !== void 0 || readNonEmptyString(metadata.projectType) !== void 0;
418
- }
419
- async function resolveTopicDoc(ctx, scopeId) {
420
- if (ctx?.db && typeof ctx.db.get === "function") {
421
- try {
422
- const directTopic = await ctx.db.get(scopeId);
423
- if (directTopic) {
424
- return directTopic;
425
- }
426
- } catch {
427
- }
428
- }
429
- if (typeof ctx.runQuery !== "function") {
430
- return null;
431
- }
432
- try {
433
- const topic = await ctx.runQuery(api.topics.get, {
434
- id: String(scopeId)
435
- });
436
- if (topic?.name !== void 0 && topic?.type !== void 0) {
437
- return topic;
438
- }
439
- } catch {
440
- }
441
- try {
442
- const topic = await ctx.runQuery(api.topics.getByLegacyScopeId, {
443
- projectId: String(scopeId)
444
- });
445
- if (topic?.name !== void 0 && topic?.type !== void 0) {
446
- return topic;
447
- }
448
- } catch {
449
- }
450
- return null;
451
- }
452
- function materializeTopicProjectOverlay(topic, idMode = "legacy") {
453
- const metadata = readMetadata(topic);
454
- const topicId = String(topic._id);
455
- const legacyProjectId = readLegacyProjectId(topic) || readLegacyProjectId(metadata) || readNonEmptyString(metadata.legacyProjectId);
456
- const storageProjectId = legacyProjectId || topicId;
457
- const outwardId = idMode === "topic" ? topicId : storageProjectId;
458
- const visibility = coerceVisibility(topic.visibility) || coerceVisibility(metadata.visibility) || "private";
459
- const status = coerceStatus(topic.status) || coerceStatus(metadata.status) || "active";
460
- const createdAt = typeof topic.createdAt === "number" ? topic.createdAt : typeof topic._creationTime === "number" ? topic._creationTime : 0;
461
- const updatedAt = typeof topic.updatedAt === "number" ? topic.updatedAt : typeof metadata.updatedAt === "number" ? metadata.updatedAt : createdAt;
462
- return {
463
- ...metadata,
464
- _id: outwardId,
465
- projectId: outwardId,
466
- topicId,
467
- storageProjectId,
468
- legacyProjectId,
469
- name: readNonEmptyString(topic.name) || "Untitled Theme",
470
- type: mapProjectType(topic, metadata),
471
- description: readNonEmptyString(topic.description),
472
- ownerId: readNonEmptyString(metadata.ownerId) || readNonEmptyString(topic.createdBy) || "system",
473
- sharedWith: readStringArray(metadata.sharedWith),
474
- visibility,
475
- tenantId: readNonEmptyString(topic.tenantId) || readNonEmptyString(metadata.tenantId),
476
- workspaceId: readNonEmptyString(topic.workspaceId) || readNonEmptyString(metadata.workspaceId),
477
- status,
478
- tags: readStringArray(metadata.tags),
479
- chatCount: typeof metadata.chatCount === "number" ? metadata.chatCount : 0,
480
- artifactCount: typeof metadata.artifactCount === "number" ? metadata.artifactCount : 0,
481
- lastActivityAt: typeof metadata.lastActivityAt === "number" ? metadata.lastActivityAt : updatedAt,
482
- _creationTime: typeof topic._creationTime === "number" ? topic._creationTime : createdAt,
483
- createdAt,
484
- updatedAt
485
- };
486
- }
487
- async function resolveTopicProjectOverlay(ctx, scopeId, options = {}) {
488
- const topic = await resolveTopicDoc(ctx, scopeId);
489
- if (!topic) {
490
- return null;
491
- }
492
- if (options.projectLikeOnly !== false && !isProjectLikeTopic(topic)) {
493
- return null;
494
- }
495
- return materializeTopicProjectOverlay(topic, options.idMode);
496
- }
497
- async function listTopicProjectOverlays(ctx, options = {}) {
498
- let allTopics = [];
499
- if (ctx?.db?.query && typeof ctx.db.query === "function") {
500
- try {
501
- allTopics = await ctx.db.query("topics").collect();
502
- } catch {
503
- allTopics = [];
504
- }
505
- }
506
- if (allTopics.length === 0 && typeof ctx.runQuery === "function") {
507
- allTopics = (await ctx.runQuery(api.topics.list, {}) ?? []) || [];
508
- }
509
- return allTopics.filter(
510
- (topic) => options.projectLikeOnly === false || isProjectLikeTopic(topic)
511
- ).map((topic) => materializeTopicProjectOverlay(topic, options.idMode));
512
- }
513
-
514
- // ../access-control/src/projectGrantsBridge.ts
515
- var PROJECT_GRANT_STATUSES = ["active", "revoked", "expired"];
516
- function normalizeString(value) {
517
- if (typeof value !== "string") {
518
- return;
519
- }
520
- const trimmed = value.trim();
521
- return trimmed.length > 0 ? trimmed : void 0;
522
- }
523
- async function resolveGrantScopeIds(ctx, args) {
524
- const topicId = normalizeString(args.topicId);
525
- const projectId = normalizeString(args.projectId);
526
- for (const scopeId of [topicId, projectId]) {
527
- if (!scopeId) {
528
- continue;
529
- }
530
- try {
531
- const overlay = await resolveTopicProjectOverlay(ctx, scopeId, {
532
- idMode: "legacy",
533
- projectLikeOnly: false
534
- });
535
- if (overlay) {
536
- return {
537
- topicId: normalizeString(overlay.topicId) ?? topicId,
538
- projectId: normalizeString(overlay.projectId) ?? projectId ?? scopeId
539
- };
540
- }
541
- } catch {
542
- }
543
- }
544
- return { topicId, projectId };
545
- }
546
- async function normalizeProjectGrantRow(ctx, row) {
547
- const scope = await resolveGrantScopeIds(ctx, {
548
- topicId: row.topicId,
549
- projectId: row.projectId
550
- });
551
- return {
552
- ...row,
553
- ...scope.topicId ? { topicId: scope.topicId } : {},
554
- ...scope.projectId ?? scope.topicId ? { projectId: scope.projectId ?? scope.topicId } : {}
555
- };
556
- }
557
- async function normalizeProjectGrantRows(ctx, rows) {
558
- return await Promise.all(rows.map((row) => normalizeProjectGrantRow(ctx, row)));
559
- }
560
- async function listProjectGrantsByPrincipal(ctx, principalId) {
561
- const rows = await Promise.all(
562
- PROJECT_GRANT_STATUSES.map(
563
- (status) => ctx.db.query("projectGrants").withIndex(
564
- "by_principal_status",
565
- (q) => q.eq("principalId", principalId).eq("status", status)
566
- ).collect()
567
- )
568
- );
569
- return await normalizeProjectGrantRows(ctx, rows.flat());
570
- }
571
- async function listProjectGrantsByGroup(ctx, groupId) {
572
- const rows = await Promise.all(
573
- PROJECT_GRANT_STATUSES.map(
574
- (status) => ctx.db.query("projectGrants").withIndex(
575
- "by_group_status",
576
- (q) => q.eq("groupId", groupId).eq("status", status)
577
- ).collect()
578
- )
579
- );
580
- return await normalizeProjectGrantRows(ctx, rows.flat());
581
- }
582
- function buildScopeMatchers(inputScopeId, resolved) {
583
- return new Set(
584
- [inputScopeId, resolved.topicId, resolved.projectId].map((value) => normalizeString(value)).filter((value) => Boolean(value))
585
- );
586
- }
587
- function matchesResolvedScope(row, scopeIds) {
588
- const rowTopicId = normalizeString(row.topicId);
589
- const rowProjectId = normalizeString(row.projectId);
590
- return rowTopicId !== void 0 && scopeIds.has(rowTopicId) || rowProjectId !== void 0 && scopeIds.has(rowProjectId);
591
- }
592
- async function bridgeListProjectGrantsByTopicAndPrincipal(ctx, topicId, principalId) {
593
- const resolved = await resolveGrantScopeIds(ctx, { topicId });
594
- const scopeIds = buildScopeMatchers(topicId, resolved);
595
- const rows = await listProjectGrantsByPrincipal(ctx, principalId);
596
- return rows.filter((row) => matchesResolvedScope(row, scopeIds));
597
- }
598
- async function bridgeListProjectGrantsByTopicAndGroup(ctx, topicId, groupId) {
599
- const resolved = await resolveGrantScopeIds(ctx, { topicId });
600
- const scopeIds = buildScopeMatchers(topicId, resolved);
601
- const rows = await listProjectGrantsByGroup(ctx, groupId);
602
- return rows.filter((row) => matchesResolvedScope(row, scopeIds));
603
- }
604
- async function bridgeListProjectGrantsByPrincipalStatus(ctx, principalId, status) {
605
- const rows = await listProjectGrantsByPrincipal(ctx, principalId);
606
- return rows.filter((row) => row.status === status);
607
- }
608
- async function bridgeListProjectGrantsByGroupStatus(ctx, groupId, status) {
609
- const rows = await listProjectGrantsByGroup(ctx, groupId);
610
- return rows.filter((row) => row.status === status);
611
- }
612
- async function bridgeInsertProjectGrant(ctx, value) {
613
- const resolved = await resolveGrantScopeIds(ctx, value);
614
- return await ctx.db.insert("projectGrants", {
615
- ...value,
616
- ...resolved.topicId ? { topicId: resolved.topicId } : {},
617
- ...resolved.projectId ?? resolved.topicId ? { projectId: resolved.projectId ?? resolved.topicId } : {}
618
- });
619
- }
620
-
621
- // ../access-control/src/resolvers.ts
622
- async function findUserByClerkId(ctx, clerkId) {
623
- const normalizedClerkId = clerkId.trim();
624
- if (!normalizedClerkId) {
625
- return null;
626
- }
627
- if (typeof ctx.runQuery === "function") {
628
- try {
629
- const bridgedUser = await ctx.runQuery(api.users.getUserByClerkId, {
630
- clerkId: normalizedClerkId
631
- });
632
- if (bridgedUser) {
633
- return bridgedUser;
634
- }
635
- } catch {
636
- }
637
- }
638
- try {
639
- const users = await ctx.db.query("users").collect();
640
- return users.find((user) => String(user.clerkId ?? "") === normalizedClerkId) ?? null;
641
- } catch {
642
- return null;
643
- }
644
- }
645
- async function findUserByPrincipalId(ctx, principalId) {
646
- const normalizedPrincipalId = principalId.trim();
647
- if (!normalizedPrincipalId) {
648
- return null;
649
- }
650
- try {
651
- const users = await ctx.db.query("users").collect();
652
- return users.find(
653
- (user) => String(user.defaultPrincipalId ?? "") === normalizedPrincipalId
654
- ) ?? null;
655
- } catch {
656
- return null;
657
- }
658
- }
659
- async function findAgentByPrincipalId(ctx, principalId) {
660
- const normalizedPrincipalId = principalId.trim();
661
- if (!normalizedPrincipalId) {
662
- return null;
663
- }
664
- if (typeof ctx.runQuery === "function") {
665
- try {
666
- const bridgedAgent = await ctx.runQuery(
667
- api.agents.getAgentByPrincipalId,
668
- {
669
- principalId: normalizedPrincipalId
670
- }
671
- );
672
- if (bridgedAgent) {
673
- return bridgedAgent;
674
- }
675
- } catch {
676
- }
677
- }
678
- try {
679
- const agents = await ctx.db.query("agents").collect();
680
- return agents.find(
681
- (agent) => String(agent.principalId ?? "") === normalizedPrincipalId
682
- ) ?? null;
683
- } catch {
684
- return null;
685
- }
686
- }
687
- function defaultResolvers() {
688
- return {
689
- async getProject(ctx, topicId) {
690
- return await resolveTopicProjectOverlay(ctx, topicId, {
691
- idMode: "legacy",
692
- projectLikeOnly: false
693
- });
694
- },
695
- async listTopics(ctx) {
696
- return await listTopicProjectOverlays(ctx, { idMode: "legacy" });
697
- },
698
- async listTopicsByOwner(ctx, ownerId) {
699
- const topics = await listTopicProjectOverlays(ctx, { idMode: "legacy" });
700
- return topics.filter((topic) => topic.ownerId === ownerId);
701
- },
702
- async listTopicsByVisibility(ctx, visibility) {
703
- const topics = await listTopicProjectOverlays(ctx, { idMode: "legacy" });
704
- return topics.filter((topic) => topic.visibility === visibility);
705
- },
706
- async listProjectGrantsByProjectAndPrincipal(ctx, topicId, principalId) {
707
- return await bridgeListProjectGrantsByTopicAndPrincipal(
708
- ctx,
709
- topicId,
710
- principalId
711
- );
712
- },
713
- async listProjectGrantsByProjectAndGroup(ctx, topicId, groupId) {
714
- return await bridgeListProjectGrantsByTopicAndGroup(ctx, topicId, groupId);
715
- },
716
- async listProjectGrantsByPrincipalStatus(ctx, principalId, status) {
717
- return await bridgeListProjectGrantsByPrincipalStatus(
718
- ctx,
719
- principalId,
720
- status
721
- );
722
- },
723
- async listProjectGrantsByGroupStatus(ctx, groupId, status) {
724
- return await bridgeListProjectGrantsByGroupStatus(ctx, groupId, status);
725
- },
726
- async insertProjectGrant(ctx, value) {
727
- return await bridgeInsertProjectGrant(ctx, value);
728
- },
729
- async getAgentByPrincipalId(ctx, principalId) {
730
- return await findAgentByPrincipalId(ctx, principalId);
731
- },
732
- async getUserByClerkId(ctx, clerkId) {
733
- return await findUserByClerkId(ctx, clerkId);
734
- },
735
- async getUserByPrincipalId(ctx, principalId) {
736
- return await findUserByPrincipalId(ctx, principalId);
737
- }
738
- };
739
- }
740
- var resolverOverrides = {};
741
- function resolveAccessControlAppResolvers(_ctx) {
742
- return {
743
- ...defaultResolvers(),
744
- ...resolverOverrides
745
- };
746
- }
747
-
748
- // ../access-control/src/principalContext.ts
749
- function requireCanonicalResolvedUser(user, clerkId) {
750
- const resolved = user;
751
- if (!resolved) {
752
- throw new Error(
753
- `[AccessControl] Canonical user identity required for ${clerkId}. Sync users.upsertUser before user-bound access checks.`
754
- );
755
- }
756
- const { mcRole, defaultTenantId, defaultWorkspaceId, defaultPrincipalId } = resolved;
757
- if (mcRole !== "platform_admin" && mcRole !== "tenant_admin" && mcRole !== "workspace_admin" && mcRole !== "editor" && mcRole !== "viewer" && mcRole !== "auditor" && mcRole !== "service_agent") {
758
- throw new Error(
759
- `[AccessControl] Canonical MC role required for ${clerkId}. Re-sync Master Control identity before user-bound access checks.`
760
- );
761
- }
762
- if (typeof defaultTenantId !== "string" || defaultTenantId.trim().length === 0) {
763
- throw new Error(
764
- `[AccessControl] Canonical home tenant required for ${clerkId}. Re-sync Master Control identity before user-bound access checks.`
765
- );
766
- }
767
- if (typeof defaultWorkspaceId !== "string" || defaultWorkspaceId.trim().length === 0) {
768
- throw new Error(
769
- `[AccessControl] Canonical home workspace required for ${clerkId}. Re-sync Master Control identity before user-bound access checks.`
770
- );
771
- }
772
- if (typeof defaultPrincipalId !== "string" || defaultPrincipalId.trim().length === 0) {
773
- throw new Error(
774
- `[AccessControl] Canonical federated principal required for ${clerkId}. Re-sync Master Control identity before user-bound access checks.`
775
- );
776
- }
777
- return {
778
- mcRole,
779
- defaultTenantId: defaultTenantId.trim(),
780
- defaultWorkspaceId: defaultWorkspaceId.trim(),
781
- defaultPrincipalId: defaultPrincipalId.trim()
782
- };
783
- }
784
- function isPrincipalIdInput(value) {
785
- return value.startsWith("user:") || value.startsWith("group:") || value.startsWith("service:") || value.startsWith("agent:") || value.startsWith("external_viewer:");
786
- }
787
- async function resolveCanonicalUserRecord(ctx, actorId) {
788
- const normalizedActorId = actorId.trim();
789
- const clerkId = isPrincipalIdInput(normalizedActorId) && normalizedActorId.startsWith("user:") ? normalizedActorId.slice("user:".length) : normalizedActorId;
790
- const resolvers = resolveAccessControlAppResolvers();
791
- const resolvedByClerkId = await resolvers.getUserByClerkId(ctx, clerkId);
792
- if (resolvedByClerkId) {
793
- return {
794
- resolvedUser: resolvedByClerkId,
795
- clerkId,
796
- contextClerkId: clerkId
797
- };
798
- }
799
- const resolvedByPrincipalId = await resolvers.getUserByPrincipalId(
800
- ctx,
801
- normalizedActorId
802
- );
803
- return {
804
- resolvedUser: resolvedByPrincipalId ?? null,
805
- clerkId,
806
- contextClerkId: normalizedActorId.startsWith("user:") && clerkId.length > 0 ? clerkId : normalizedActorId
807
- };
808
- }
809
- function uniqRoles(roles) {
810
- const roleSet = /* @__PURE__ */ new Set();
811
- for (const role of roles) {
812
- if (role === "platform_admin" || role === "tenant_admin" || role === "workspace_admin" || role === "editor" || role === "viewer" || role === "auditor" || role === "service_agent") {
813
- roleSet.add(role);
814
- }
815
- }
816
- return [...roleSet];
817
- }
818
- function normalizeGroupIds(value) {
819
- if (!Array.isArray(value)) {
820
- return [];
821
- }
822
- return [...new Set(
823
- value.filter((entry) => typeof entry === "string").map((entry) => entry.trim()).filter(Boolean)
824
- )];
825
- }
826
- function requireServiceAgentUser(user, actorId) {
827
- const canonicalUser = requireCanonicalResolvedUser(user, actorId);
828
- if (canonicalUser.mcRole !== "service_agent") {
829
- throw new Error(
830
- `[AccessControl] Canonical service_agent identity required for ${actorId}. Sync users.upsertUser before agent-bound access checks.`
831
- );
832
- }
833
- return canonicalUser;
834
- }
835
- function requireCanonicalResolvedAgent(agent, actorId) {
836
- const resolved = agent;
837
- if (!resolved) {
838
- throw new Error(
839
- `[AccessControl] Agent "${actorId}" not found in agents or users table.`
840
- );
841
- }
842
- if (typeof resolved.principalId !== "string" || resolved.principalId.trim().length === 0) {
843
- throw new Error(
844
- `[AccessControl] Canonical agent principalId required for ${actorId}.`
845
- );
846
- }
847
- if (typeof resolved.tenantId !== "string" || resolved.tenantId.trim().length === 0) {
848
- throw new Error(
849
- `[AccessControl] Canonical home tenant required for ${actorId}.`
850
- );
851
- }
852
- if (typeof resolved.workspaceId !== "string" || resolved.workspaceId.trim().length === 0) {
853
- throw new Error(
854
- `[AccessControl] Canonical home workspace required for ${actorId}.`
855
- );
856
- }
857
- return {
858
- principalId: resolved.principalId.trim(),
859
- tenantId: resolved.tenantId.trim(),
860
- workspaceId: resolved.workspaceId.trim(),
861
- roles: uniqRoles(Array.isArray(resolved.roles) ? resolved.roles : []) ?? ["service_agent"],
862
- groupIds: normalizeGroupIds(resolved.groupIds)
863
- };
864
- }
865
- async function resolvePrincipalContext(ctx, actorId) {
866
- if (actorId.startsWith("agent:")) {
867
- const resolvers = resolveAccessControlAppResolvers();
868
- const resolvedAgent = await resolvers.getAgentByPrincipalId(ctx, actorId);
869
- if (resolvedAgent) {
870
- const agent = requireCanonicalResolvedAgent(
871
- resolvedAgent,
872
- actorId
873
- );
874
- return {
875
- principalId: agent.principalId,
876
- principalType: "service",
877
- clerkId: actorId,
878
- tenantId: agent.tenantId,
879
- workspaceId: agent.workspaceId,
880
- roles: agent.roles.length > 0 ? agent.roles : ["service_agent"],
881
- groupIds: agent.groupIds,
882
- isPlatformAdmin: false,
883
- isTenantAdmin: false,
884
- isWorkspaceAdmin: false,
885
- isSystemFallback: false
886
- };
887
- }
888
- const resolvedUser2 = await resolvers.getUserByClerkId(
889
- ctx,
890
- actorId
891
- );
892
- if (!resolvedUser2) {
893
- throw new Error(
894
- `[AccessControl] Agent "${actorId}" not found in agents or users table.`
895
- );
896
- }
897
- const user2 = requireServiceAgentUser(
898
- resolvedUser2,
899
- actorId
900
- );
901
- console.warn(
902
- `[AccessControl] Deprecated legacy service-agent fallback for ${actorId}; migrate this principal into identity.agents.`
903
- );
904
- return {
905
- principalId: user2.defaultPrincipalId,
906
- principalType: "service",
907
- clerkId: actorId,
908
- tenantId: user2.defaultTenantId,
909
- workspaceId: user2.defaultWorkspaceId,
910
- roles: ["service_agent"],
911
- groupIds: normalizeGroupIds(resolvedUser2?.principalGroupIds),
912
- isPlatformAdmin: false,
913
- isTenantAdmin: false,
914
- isWorkspaceAdmin: false,
915
- isSystemFallback: false
916
- };
917
- }
918
- const {
919
- resolvedUser,
920
- contextClerkId
921
- } = await resolveCanonicalUserRecord(ctx, actorId);
922
- const user = requireCanonicalResolvedUser(
923
- resolvedUser,
924
- contextClerkId
925
- );
926
- if (!user.defaultPrincipalId) {
927
- throw new Error(
928
- `[AccessControl] Canonical federated principal required for ${contextClerkId}. Re-sync Master Control identity before user-bound access checks.`
929
- );
930
- }
931
- if (user.mcRole === "service_agent") {
932
- return {
933
- principalId: user.defaultPrincipalId,
934
- principalType: "service",
935
- clerkId: contextClerkId,
936
- tenantId: user.defaultTenantId,
937
- workspaceId: user.defaultWorkspaceId,
938
- roles: ["service_agent"],
939
- groupIds: normalizeGroupIds(resolvedUser?.principalGroupIds),
940
- isPlatformAdmin: false,
941
- isTenantAdmin: false,
942
- isWorkspaceAdmin: false,
943
- isSystemFallback: false
944
- };
945
- }
946
- const principalId = user.defaultPrincipalId;
947
- const effectiveRole = user.mcRole;
948
- const roles = effectiveRole === "platform_admin" ? ["platform_admin", "tenant_admin"] : effectiveRole === "tenant_admin" ? ["tenant_admin"] : [effectiveRole];
949
- const tenantId = user.defaultTenantId;
950
- const workspaceId = user.defaultWorkspaceId;
951
- const isPlatformAdmin = effectiveRole === "platform_admin";
952
- return {
953
- principalId,
954
- principalType: "user",
955
- clerkId: contextClerkId,
956
- tenantId,
957
- workspaceId,
958
- roles: uniqRoles(roles),
959
- groupIds: normalizeGroupIds(resolvedUser?.principalGroupIds),
960
- isPlatformAdmin,
961
- isTenantAdmin: isPlatformAdmin || effectiveRole === "tenant_admin",
962
- isWorkspaceAdmin: isPlatformAdmin || effectiveRole === "tenant_admin" || effectiveRole === "workspace_admin",
963
- isSystemFallback: false
964
- };
965
- }
966
-
967
- // ../access-control/src/access.ts
968
- function isTopicInPrincipalTenant(topic, principalTenantId) {
969
- if (!topic.tenantId) {
970
- return false;
971
- }
972
- if (!principalTenantId) {
973
- return false;
974
- }
975
- return String(topic.tenantId) === String(principalTenantId);
976
- }
977
- function isTopicInPrincipalWorkspace(topic, principalWorkspaceId) {
978
- if (!topic.workspaceId) {
979
- return false;
980
- }
981
- if (!principalWorkspaceId) {
982
- return false;
983
- }
984
- return String(topic.workspaceId) === String(principalWorkspaceId);
985
- }
986
- function isLegacyUnscopedTopic(topic) {
987
- return !topic.tenantId || !topic.workspaceId;
988
- }
989
- function isGrantScopeAlignedToTopic(topic, grant) {
990
- if (topic.tenantId && grant.tenantId && String(topic.tenantId) !== String(grant.tenantId)) {
991
- return false;
992
- }
993
- if (topic.workspaceId && grant.workspaceId && String(topic.workspaceId) !== String(grant.workspaceId)) {
994
- return false;
995
- }
996
- return true;
997
- }
998
- function isGrantSourceAllowedForVisibility(visibility, source) {
999
- if (source !== "external_share") {
1000
- return true;
1001
- }
1002
- return visibility === "external" || visibility === "public";
1003
- }
1004
- function isGrantActive(grant) {
1005
- if (grant.status !== "active") {
1006
- return false;
1007
- }
1008
- if (grant.expiresAt !== void 0 && grant.expiresAt <= Date.now()) {
1009
- return false;
1010
- }
1011
- return true;
1012
- }
1013
- async function hasPrincipalGrant(ctx, args) {
1014
- const grants = await resolveAccessControlAppResolvers().listProjectGrantsByProjectAndPrincipal(
1015
- ctx,
1016
- args.topic._id,
1017
- args.principalId
1018
- );
1019
- if (grants.some(
1020
- (grant) => isGrantActive(grant) && isGrantScopeAlignedToTopic(args.topic, grant) && isGrantSourceAllowedForVisibility(
1021
- args.topic.visibility,
1022
- grant.source
1023
- ) && (!args.principalIsExternal || args.topic.visibility === "public" || grant.source === "external_share")
1024
- )) {
1025
- return true;
1026
- }
1027
- return false;
1028
- }
1029
- async function hasGroupGrant(ctx, args) {
1030
- if (args.groupIds.length === 0) {
1031
- return false;
1032
- }
1033
- for (const groupId of args.groupIds) {
1034
- const grants = await resolveAccessControlAppResolvers().listProjectGrantsByProjectAndGroup(ctx, args.topic._id, groupId);
1035
- if (grants.some(
1036
- (grant) => isGrantActive(grant) && isGrantScopeAlignedToTopic(args.topic, grant) && isGrantSourceAllowedForVisibility(
1037
- args.topic.visibility,
1038
- grant.source
1039
- )
1040
- )) {
1041
- return true;
1042
- }
1043
- }
1044
- return false;
1045
- }
1046
- function isExternalPrincipal(_ctx, _args) {
1047
- return false;
1048
- }
1049
- async function evaluateTopicAccessDetailed(ctx, args) {
1050
- if (args.legacyUserId) {
1051
- return {
1052
- hasAccess: true,
1053
- isAdmin: false,
1054
- isOwner: false,
1055
- isShared: false,
1056
- hasGrant: true,
1057
- isFirmVisible: true,
1058
- isExternalVisible: false,
1059
- isPublicVisible: false,
1060
- isTenantScopeMatch: true,
1061
- isWorkspaceScopeMatch: true,
1062
- isPrincipalExternal: false
1063
- };
1064
- }
1065
- const topic = await resolveAccessControlAppResolvers().getProject(
1066
- ctx,
1067
- args.topicId
1068
- );
1069
- if (!topic) {
1070
- return {
1071
- hasAccess: false,
1072
- isAdmin: false,
1073
- isOwner: false,
1074
- isShared: false,
1075
- hasGrant: false,
1076
- isFirmVisible: false,
1077
- isExternalVisible: false,
1078
- isPublicVisible: false,
1079
- isTenantScopeMatch: false,
1080
- isWorkspaceScopeMatch: false,
1081
- isPrincipalExternal: false
1082
- };
1083
- }
1084
- const { principalContext, legacyUserId } = args;
1085
- const userIsAdmin = principalContext.isPlatformAdmin;
1086
- const isOwner = topic.ownerId === legacyUserId;
1087
- const isShared = (topic.sharedWith ?? []).includes(legacyUserId);
1088
- const principalIsExternal = await isExternalPrincipal(ctx, {
1089
- groupIds: principalContext.groupIds,
1090
- topicTenantId: topic.tenantId,
1091
- topicWorkspaceId: topic.workspaceId
1092
- });
1093
- const hasPrincipalGrantResult = await hasPrincipalGrant(ctx, {
1094
- topic,
1095
- principalId: principalContext.principalId,
1096
- principalIsExternal
1097
- });
1098
- const hasGroupGrantResult = await hasGroupGrant(ctx, {
1099
- topic,
1100
- groupIds: principalContext.groupIds
1101
- });
1102
- const hasGrant = isShared || hasPrincipalGrantResult || hasGroupGrantResult;
1103
- const legacyUnscoped = isLegacyUnscopedTopic(topic);
1104
- const tenantScopeMatch = isTopicInPrincipalTenant(
1105
- topic,
1106
- principalContext.tenantId
1107
- );
1108
- const workspaceScopeMatch = isTopicInPrincipalWorkspace(
1109
- topic,
1110
- principalContext.workspaceId
1111
- );
1112
- const isPublicVisible = topic.visibility === "public";
1113
- const isFirmVisible = topic.visibility === "firm" && !legacyUnscoped && tenantScopeMatch && workspaceScopeMatch && !principalIsExternal;
1114
- const hasScopedGrant = hasGrant && (legacyUnscoped || tenantScopeMatch && workspaceScopeMatch);
1115
- const isExternalVisible = topic.visibility === "external" && hasScopedGrant;
1116
- const hasAccess = userIsAdmin || isOwner || hasScopedGrant || isPublicVisible || isFirmVisible;
1117
- return {
1118
- hasAccess,
1119
- isAdmin: userIsAdmin,
1120
- isOwner,
1121
- isShared,
1122
- hasGrant,
1123
- isFirmVisible,
1124
- isExternalVisible,
1125
- isPublicVisible,
1126
- isTenantScopeMatch: tenantScopeMatch,
1127
- isWorkspaceScopeMatch: workspaceScopeMatch,
1128
- isPrincipalExternal: principalIsExternal
1129
- };
1130
- }
1131
- async function checkTopicAccessDetailed(ctx, topicId, userId) {
1132
- const principalContext = await resolvePrincipalContext(ctx, userId);
1133
- return evaluateTopicAccessDetailed(ctx, {
1134
- topicId,
1135
- legacyUserId: userId,
1136
- principalContext
1137
- });
1138
- }
1139
- async function checkTopicAccess(ctx, topicId, userId) {
1140
- const result = await checkTopicAccessDetailed(ctx, topicId, userId);
1141
- return result.hasAccess;
1142
- }
1143
- async function checkScopeAccess(ctx, scopeId, userId) {
1144
- try {
1145
- const topic = await ctx.db.get(scopeId);
1146
- if (topic && topic.name !== void 0 && topic.type !== void 0) {
1147
- return true;
1148
- }
1149
- } catch {
1150
- }
1151
- try {
1152
- return await checkTopicAccess(ctx, scopeId, userId);
1153
- } catch {
1154
- return false;
1155
- }
1156
- }
1157
- var checkProjectAccess = checkTopicAccess;
1158
-
1159
- // ../access-control/src/audience.ts
1160
- var AUDIENCE_CLASS_RANK = {
1161
- public: 0,
1162
- restricted_external: 1,
1163
- internal: 2
1164
- };
1165
- function normalizeKey(key) {
1166
- return (key ?? "").trim().toLowerCase().replace(/[^a-z0-9:_-]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "");
1167
- }
1168
- function normalizeAudienceKey(key) {
1169
- return normalizeKey(key);
1170
- }
1171
- function classFromAudienceKey(audienceKey, fallback = "internal") {
1172
- const key = normalizeKey(audienceKey);
1173
- if (!key) {
1174
- return fallback;
1175
- }
1176
- if (key === "internal") {
1177
- return "internal";
1178
- }
1179
- if (key === "public") {
1180
- return "public";
1181
- }
1182
- if (key === "lp" || key === "external" || key === "client" || key === "partner" || key === "portfolio" || key === "network" || key === "restricted_external") {
1183
- return "restricted_external";
1184
- }
1185
- return fallback;
1186
- }
1187
- function canAudienceClassAccess(viewerClass, resourceClass) {
1188
- return AUDIENCE_CLASS_RANK[viewerClass] >= AUDIENCE_CLASS_RANK[resourceClass];
1189
- }
1190
-
1191
- // ../access-control/src/audienceRegistry.ts
1192
- var DEFAULT_AUDIENCES = [
1193
- {
1194
- audienceKey: "internal",
1195
- audienceLabel: "Internal",
1196
- audienceClass: "internal"
1197
- },
1198
- {
1199
- audienceKey: "lp",
1200
- audienceLabel: "Limited Partners",
1201
- audienceClass: "restricted_external"
1202
- },
1203
- {
1204
- audienceKey: "public",
1205
- audienceLabel: "Public",
1206
- audienceClass: "public"
1207
- }
1208
- ];
1209
- var AUDIENCE_CLASS_PRIORITY = {
1210
- internal: 0,
1211
- restricted_external: 1,
1212
- public: 2
1213
- };
1214
- function normalizeRegistryRow(row) {
1215
- return {
1216
- audienceKey: normalizeAudienceKey(row.audienceKey),
1217
- audienceLabel: row.audienceLabel,
1218
- audienceClass: row.audienceClass,
1219
- workspaceId: row.workspaceId
1220
- };
1221
- }
1222
- function dedupeRegistryRows(rows) {
1223
- const byKey = /* @__PURE__ */ new Map();
1224
- for (const row of rows) {
1225
- const key = normalizeAudienceKey(row.audienceKey);
1226
- if (!key) {
1227
- continue;
1228
- }
1229
- const existing = byKey.get(key);
1230
- const isWorkspaceScoped = row.workspaceId !== void 0;
1231
- const existingWorkspaceScoped = existing?.workspaceId !== void 0;
1232
- if (!existing || isWorkspaceScoped && !existingWorkspaceScoped) {
1233
- byKey.set(key, {
1234
- ...row,
1235
- audienceKey: key
1236
- });
1237
- }
1238
- }
1239
- const normalized = [...byKey.values()];
1240
- normalized.sort((a, b) => {
1241
- const classDelta = AUDIENCE_CLASS_PRIORITY[a.audienceClass] - AUDIENCE_CLASS_PRIORITY[b.audienceClass];
1242
- if (classDelta !== 0) {
1243
- return classDelta;
1244
- }
1245
- return a.audienceKey.localeCompare(b.audienceKey);
1246
- });
1247
- return normalized;
1248
- }
1249
- async function queryRegistryRows(ctx, args) {
1250
- if (!args.tenantId) {
1251
- return [...DEFAULT_AUDIENCES];
1252
- }
1253
- const rows = await ctx.db.query("platformAudiences").withIndex("by_tenantId", (q) => q.eq("tenantId", args.tenantId)).collect();
1254
- const workspaceIdString = args.workspaceId ? String(args.workspaceId) : null;
1255
- const tenantScoped = rows.filter((row) => row.status === "active");
1256
- const applicable = tenantScoped.filter((row) => {
1257
- if (!row.workspaceId) {
1258
- return true;
1259
- }
1260
- if (!workspaceIdString) {
1261
- return false;
1262
- }
1263
- return String(row.workspaceId) === workspaceIdString;
1264
- });
1265
- return dedupeRegistryRows([
1266
- ...DEFAULT_AUDIENCES,
1267
- ...applicable.map(
1268
- (row) => normalizeRegistryRow({
1269
- audienceKey: row.audienceKey,
1270
- audienceLabel: row.audienceLabel,
1271
- audienceClass: row.audienceClass,
1272
- workspaceId: row.workspaceId
1273
- })
1274
- )
1275
- ]);
1276
- }
1277
- async function listAudienceRegistryRows(ctx, args) {
1278
- return queryRegistryRows(ctx, args);
1279
- }
1280
-
1281
- // ../access-control/src/auth.ts
1282
- async function getCurrentUserId(ctx) {
1283
- const identity = await ctx.auth.getUserIdentity();
1284
- return identity?.subject ?? null;
1285
- }
1286
-
1287
- // ../../packages/contracts/src/schema-helpers/enumValidation.ts
1288
- var BUILTIN_ENUM_FALLBACK = {
1289
- topic_type: /* @__PURE__ */ new Set([
1290
- "domain",
1291
- "theme",
1292
- "deal",
1293
- "strategy",
1294
- "constitution",
1295
- "project",
1296
- "portfolio",
1297
- "architecture",
1298
- "capability",
1299
- "runtime",
1300
- "interface",
1301
- "governance",
1302
- "operations",
1303
- "security",
1304
- "data"
1305
- ]),
1306
- branch_schema: /* @__PURE__ */ new Set(["pillar", "track", "dimension", "axis", "phase"]),
1307
- lens_perspective_type: /* @__PURE__ */ new Set([
1308
- "investigation",
1309
- "monitoring",
1310
- "analysis",
1311
- "comparison",
1312
- "taxonomy"
1313
- ]),
1314
- belief_type: /* @__PURE__ */ new Set([
1315
- "belief",
1316
- "hypothesis",
1317
- "principle",
1318
- "invariant",
1319
- "assumption",
1320
- "tenet",
1321
- "prior",
1322
- "preference",
1323
- "goal",
1324
- "forecast",
1325
- "decision",
1326
- "constraint",
1327
- "tradeoff",
1328
- "policy",
1329
- "implementation_choice",
1330
- "implementation_decision",
1331
- "interface_contract",
1332
- "migration_state",
1333
- "code_pattern",
1334
- "deprecation_notice"
1335
- ]),
1336
- edge_type: /* @__PURE__ */ new Set([
1337
- "supports",
1338
- "informs",
1339
- "depends_on",
1340
- "derived_from",
1341
- "contains",
1342
- "tests",
1343
- "supersedes",
1344
- "responds_to",
1345
- "belongs_to",
1346
- "relates_to_thesis",
1347
- "works_at",
1348
- "invested_in",
1349
- "competes_with",
1350
- "participates_in",
1351
- "founded_by",
1352
- "evaluates",
1353
- "performs",
1354
- "function_in",
1355
- "impacts",
1356
- "raised_from",
1357
- "mentioned_in",
1358
- "perspective_on",
1359
- "plays_theme"
1360
- ]),
1361
- worktree_type: /* @__PURE__ */ new Set([
1362
- "belief_test",
1363
- "existential",
1364
- "contradiction",
1365
- "refinement",
1366
- "coverage",
1367
- "discovery",
1368
- "clarification",
1369
- "confirmation"
1370
- ]),
1371
- worktree_phase: /* @__PURE__ */ new Set([
1372
- "cluster_mapping",
1373
- "hypothesis_formation",
1374
- "question_generation",
1375
- "evidence_collection",
1376
- "synthesis",
1377
- "decision",
1378
- "retrospective"
1379
- ]),
1380
- activity_type: /* @__PURE__ */ new Set([
1381
- "create",
1382
- "update",
1383
- "review",
1384
- "merge",
1385
- "archive",
1386
- "comment",
1387
- "status_change",
1388
- "evidence_added",
1389
- "question_added"
1390
- ])
1391
- };
1392
- function normalizeEnumValue(value) {
1393
- return value.trim().toLowerCase();
1394
- }
1395
- async function validateSchemaEnumValue(_ctx, args) {
1396
- const normalized = normalizeEnumValue(args.value);
1397
- if (!normalized) {
1398
- return { valid: false, source: "none" };
1399
- }
1400
- if (BUILTIN_ENUM_FALLBACK[args.category].has(normalized)) {
1401
- return { valid: true, source: "builtin" };
1402
- }
1403
- return { valid: false, source: "none" };
1404
- }
1405
- async function assertSchemaEnumValue(ctx, args) {
1406
- if (typeof args.value !== "string") {
1407
- return;
1408
- }
1409
- const normalized = normalizeEnumValue(args.value);
1410
- if (!normalized) {
1411
- return;
1412
- }
1413
- const validation = await validateSchemaEnumValue(ctx, {
1414
- category: args.category,
1415
- value: normalized,
1416
- tenantId: args.tenantId
1417
- });
1418
- if (!validation.valid) {
1419
- const tenantHint = args.tenantId ? ` for tenant ${args.tenantId}` : "";
1420
- throw new Error(
1421
- `[${args.context}] Invalid value "${normalized}" for category "${args.category}"${tenantHint}. Add it to the contracts schema enum manifest before use.`
1422
- );
1423
- }
1424
- return normalized;
1425
- }
1426
- var permissiveReturn = v.optional(v.any());
1427
- var looseJsonObject = v.record(v.string(), v.any());
1428
- var looseJsonArray = v.array(v.any());
1429
- v.union(
1430
- v.string(),
1431
- v.number(),
1432
- v.boolean(),
1433
- v.null(),
1434
- looseJsonObject,
1435
- looseJsonArray
1436
- );
1437
-
1438
14
  // src/beliefLifecycle.ts
1439
15
  var BELIEF_STATUS_VALUES = [
1440
16
  "assumption",
@@ -1526,7 +102,7 @@ function promoteBeliefStatusAfterScoring(status, opts) {
1526
102
  }
1527
103
  return status === "fact" ? "fact" : "belief";
1528
104
  }
1529
- var api2 = anyApi;
105
+ var api = anyApi;
1530
106
  componentsGeneric();
1531
107
  var internal = anyApi;
1532
108
  var internalMutation = internalMutationGeneric;
@@ -1544,8 +120,6 @@ var containsPropagationSpec = {
1544
120
  operator: () => null,
1545
121
  description: "Structural containment only. Traversed for explicit semantics, but it never propagates opinions."
1546
122
  };
1547
-
1548
- // src/edges/utils.ts
1549
123
  function readEdgeMetadata(edge) {
1550
124
  return {
1551
125
  constraint: edge.constraint ?? void 0,
@@ -1622,8 +196,6 @@ var contradictsPropagationSpec = {
1622
196
  },
1623
197
  description: "Legacy contradiction edges move negative pressure in either direction, but never beyond one hop."
1624
198
  };
1625
-
1626
- // src/edges/dependsOn.ts
1627
199
  var dependsOnPropagationSpec = {
1628
200
  edgeType: "depends_on",
1629
201
  direction: "incoming",
@@ -1639,8 +211,18 @@ var dependsOnPropagationSpec = {
1639
211
  if (metadata.conditionalA && metadata.conditionalNotA) {
1640
212
  const deducedOpinion = conditionalDeduction(
1641
213
  dampedSource,
1642
- metadata.conditionalA,
1643
- metadata.conditionalNotA,
214
+ mkOpinion(
215
+ metadata.conditionalA.b,
216
+ metadata.conditionalA.d,
217
+ metadata.conditionalA.u,
218
+ metadata.conditionalA.a
219
+ ),
220
+ mkOpinion(
221
+ metadata.conditionalNotA.b,
222
+ metadata.conditionalNotA.d,
223
+ metadata.conditionalNotA.u,
224
+ metadata.conditionalNotA.a
225
+ ),
1644
226
  targetOpinion.a
1645
227
  );
1646
228
  return annotateRationale(
@@ -1812,70 +394,17 @@ function getTraversalDirections(direction) {
1812
394
  }
1813
395
  return ["outgoing", "incoming"];
1814
396
  }
1815
-
1816
- // ../../packages/contracts/src/schema-helpers/spine/tables/epistemicNodes.ts
1817
- var NODE_TYPES = [
1818
- "decision",
1819
- "belief",
1820
- "question",
1821
- "theme",
1822
- "deal",
1823
- "topic",
1824
- "claim",
1825
- "evidence",
1826
- "synthesis",
1827
- "answer",
1828
- "atomic_fact",
1829
- "excerpt",
1830
- "source",
1831
- "company",
1832
- "person",
1833
- "investor",
1834
- "function",
1835
- "value_chain"
1836
- ];
1837
- function isNodeType(value) {
1838
- return NODE_TYPES.includes(value);
1839
- }
1840
- function getLayerForNodeType(type) {
1841
- switch (type) {
1842
- case "decision":
1843
- return "L4";
1844
- case "belief":
1845
- case "question":
1846
- case "theme":
1847
- case "deal":
1848
- return "L3";
1849
- case "claim":
1850
- case "evidence":
1851
- case "synthesis":
1852
- case "answer":
1853
- return "L2";
1854
- case "atomic_fact":
1855
- case "excerpt":
1856
- case "source":
1857
- return "L1";
1858
- case "topic":
1859
- return "organizational";
1860
- case "company":
1861
- case "person":
1862
- case "investor":
1863
- case "function":
1864
- case "value_chain":
1865
- return "ontological";
1866
- }
1867
- }
1868
- var LEGACY_SCOPE_FIELD2 = "graphScopeProjectId";
397
+ var LEGACY_SCOPE_FIELD = "graphScopeProjectId";
1869
398
  function asMappedProjectId(topic) {
1870
399
  if (!topic) {
1871
400
  return;
1872
401
  }
1873
- const directLegacyProjectId = normalizeScopeValue(topic[LEGACY_SCOPE_FIELD2]);
402
+ const directLegacyProjectId = normalizeScopeValue(topic[LEGACY_SCOPE_FIELD]);
1874
403
  if (directLegacyProjectId) {
1875
404
  return directLegacyProjectId;
1876
405
  }
1877
406
  const metadata = topic.metadata || {};
1878
- const candidate = metadata[LEGACY_SCOPE_FIELD2] || metadata.legacyProjectId || metadata.projectId || metadata.scopeProjectId;
407
+ const candidate = metadata[LEGACY_SCOPE_FIELD] || metadata.legacyProjectId || metadata.projectId || metadata.scopeProjectId;
1879
408
  return candidate ? candidate : void 0;
1880
409
  }
1881
410
  function normalizeScopeValue(value) {
@@ -1904,7 +433,7 @@ async function findTopicsByScopeAlias(ctx, scopeId) {
1904
433
  try {
1905
434
  return await ctx.db.query("topics").withIndex(
1906
435
  "by_graph_scope_project",
1907
- (q) => q.eq(LEGACY_SCOPE_FIELD2, scopeId)
436
+ (q) => q.eq(LEGACY_SCOPE_FIELD, scopeId)
1908
437
  ).collect();
1909
438
  } catch {
1910
439
  const topics = await ctx.db.query("topics").collect();
@@ -1920,7 +449,7 @@ async function tryResolveHostTopicById(ctx, topicId) {
1920
449
  return null;
1921
450
  }
1922
451
  try {
1923
- return await ctx.runQuery(api2.topics.get, {
452
+ return await ctx.runQuery(api.topics.get, {
1924
453
  id: topicId
1925
454
  }) ?? null;
1926
455
  } catch {
@@ -1932,7 +461,7 @@ async function tryResolveHostTopicByLegacyScope(ctx, legacyScopeId) {
1932
461
  return null;
1933
462
  }
1934
463
  try {
1935
- return await ctx.runQuery(api2.topics.getByLegacyScopeId, {
464
+ return await ctx.runQuery(api.topics.getByLegacyScopeId, {
1936
465
  projectId: legacyScopeId
1937
466
  }) ?? null;
1938
467
  } catch {
@@ -2209,16 +738,17 @@ function resolveTraversalTargetNodeId(edge, direction) {
2209
738
  }
2210
739
  function readNodeOpinion(node) {
2211
740
  const metadata = node.metadata ?? {};
2212
- return readOpinionFromRecord(
2213
- {
741
+ try {
742
+ return readOpinionFromRecord({
2214
743
  ...metadata,
2215
744
  opinion_b: node.opinion_b,
2216
745
  opinion_d: node.opinion_d,
2217
746
  opinion_u: node.opinion_u,
2218
747
  opinion_a: node.opinion_a
2219
- },
2220
- { b: 0, d: 0, u: 1, a: 0.5 }
2221
- );
748
+ });
749
+ } catch {
750
+ return mkOpinion(0, 0, 1, 0.5);
751
+ }
2222
752
  }
2223
753
  async function collectConfidencePropagationDispatches(args) {
2224
754
  const dispatchesByTargetId = /* @__PURE__ */ new Map();
@@ -2287,14 +817,20 @@ async function collectConfidencePropagationDispatches(args) {
2287
817
  if (!result || !hasProjectedOpinionChanged(targetOpinion, result.opinion)) {
2288
818
  continue;
2289
819
  }
2290
- opinionCache.set(cacheKey, result.opinion);
820
+ const projectedOpinion = mkOpinion(
821
+ result.opinion.b,
822
+ result.opinion.d,
823
+ result.opinion.u,
824
+ result.opinion.a
825
+ );
826
+ opinionCache.set(cacheKey, projectedOpinion);
2291
827
  const existingDispatch = dispatchesByTargetId.get(cacheKey);
2292
828
  dispatchesByTargetId.set(cacheKey, {
2293
829
  targetNodeId,
2294
830
  edgeType: spec.edgeType,
2295
831
  traversedDirection: direction,
2296
832
  weight: edge.weight ?? 1,
2297
- opinion: result.opinion,
833
+ opinion: projectedOpinion,
2298
834
  operator: result.operator,
2299
835
  rationale: existingDispatch ? `${existingDispatch.rationale}; ${result.rationale}` : result.rationale,
2300
836
  hop: nextHop
@@ -2302,7 +838,7 @@ async function collectConfidencePropagationDispatches(args) {
2302
838
  if (canContinueTransitively(spec, nextHop)) {
2303
839
  queue.push({
2304
840
  nodeId: targetNodeId,
2305
- opinion: result.opinion,
841
+ opinion: projectedOpinion,
2306
842
  hop: nextHop,
2307
843
  visitedNodeIds: /* @__PURE__ */ new Set([
2308
844
  ...state.visitedNodeIds,
@@ -2417,48 +953,48 @@ async function getQuestionsAnsweredByEvidence(ctx, evidenceId) {
2417
953
  }
2418
954
 
2419
955
  // src/topicProjectOverlay.ts
2420
- var LEGACY_SCOPE_FIELD3 = "graphScopeProjectId";
2421
- function readNonEmptyString2(value) {
956
+ var LEGACY_SCOPE_FIELD2 = "graphScopeProjectId";
957
+ function readNonEmptyString(value) {
2422
958
  if (typeof value !== "string") {
2423
959
  return;
2424
960
  }
2425
961
  const normalized = value.trim();
2426
962
  return normalized.length > 0 ? normalized : void 0;
2427
963
  }
2428
- function readStringArray2(value) {
964
+ function readStringArray(value) {
2429
965
  if (!Array.isArray(value)) {
2430
966
  return [];
2431
967
  }
2432
- return value.map((entry) => readNonEmptyString2(entry)).filter((entry) => Boolean(entry));
968
+ return value.map((entry) => readNonEmptyString(entry)).filter((entry) => Boolean(entry));
2433
969
  }
2434
- function readMetadata2(topic) {
970
+ function readMetadata(topic) {
2435
971
  return topic.metadata && typeof topic.metadata === "object" ? topic.metadata : {};
2436
972
  }
2437
- function readLegacyProjectId2(value) {
973
+ function readLegacyProjectId(value) {
2438
974
  if (!value) {
2439
975
  return;
2440
976
  }
2441
- return readNonEmptyString2(value[LEGACY_SCOPE_FIELD3]);
977
+ return readNonEmptyString(value[LEGACY_SCOPE_FIELD2]);
2442
978
  }
2443
- function coerceVisibility2(value) {
979
+ function coerceVisibility(value) {
2444
980
  return value === "private" || value === "team" || value === "firm" || value === "external" || value === "public" ? value : void 0;
2445
981
  }
2446
- function coerceStatus2(value) {
982
+ function coerceStatus(value) {
2447
983
  return value === "active" || value === "archived" || value === "watching" ? value : void 0;
2448
984
  }
2449
- function mapProjectType2(topic, metadata) {
2450
- const explicit = readNonEmptyString2(metadata.projectType);
985
+ function mapProjectType(topic, metadata) {
986
+ const explicit = readNonEmptyString(metadata.projectType);
2451
987
  if (explicit) {
2452
988
  return explicit;
2453
989
  }
2454
990
  if (topic.type === "theme") {
2455
991
  return "thematic";
2456
992
  }
2457
- return readNonEmptyString2(topic.type) || "general";
993
+ return readNonEmptyString(topic.type) || "general";
2458
994
  }
2459
- function isProjectLikeTopic2(topic) {
2460
- const metadata = readMetadata2(topic);
2461
- return topic.type === "theme" || topic.type === "thematic" || topic.type === "deal" || topic.type === "monitoring" || readLegacyProjectId2(topic) !== void 0 || readNonEmptyString2(metadata.projectType) !== void 0;
995
+ function isProjectLikeTopic(topic) {
996
+ const metadata = readMetadata(topic);
997
+ return topic.type === "theme" || topic.type === "thematic" || topic.type === "deal" || topic.type === "monitoring" || readLegacyProjectId(topic) !== void 0 || readNonEmptyString(metadata.projectType) !== void 0;
2462
998
  }
2463
999
  function isMissingLucernChildComponentError(error) {
2464
1000
  const message = error instanceof Error ? error.message : String(error);
@@ -2466,7 +1002,7 @@ function isMissingLucernChildComponentError(error) {
2466
1002
  'Child component ComponentName(Identifier("lucern")) not found'
2467
1003
  ) || message.includes("Child component") && message.includes("lucern") && message.includes("not found");
2468
1004
  }
2469
- async function resolveTopicDoc2(ctx, scopeId) {
1005
+ async function resolveTopicDoc(ctx, scopeId) {
2470
1006
  if (ctx?.db && typeof ctx.db.get === "function") {
2471
1007
  try {
2472
1008
  const directTopic = await ctx.db.get(scopeId);
@@ -2480,7 +1016,7 @@ async function resolveTopicDoc2(ctx, scopeId) {
2480
1016
  return null;
2481
1017
  }
2482
1018
  try {
2483
- const topic = await ctx.runQuery(api2.topics.get, {
1019
+ const topic = await ctx.runQuery(api.topics.get, {
2484
1020
  id: String(scopeId)
2485
1021
  });
2486
1022
  if (topic?.name !== void 0 && topic?.type !== void 0) {
@@ -2489,7 +1025,7 @@ async function resolveTopicDoc2(ctx, scopeId) {
2489
1025
  } catch {
2490
1026
  }
2491
1027
  try {
2492
- const topic = await ctx.runQuery(api2.topics.getByLegacyScopeId, {
1028
+ const topic = await ctx.runQuery(api.topics.getByLegacyScopeId, {
2493
1029
  projectId: String(scopeId)
2494
1030
  });
2495
1031
  if (topic?.name !== void 0 && topic?.type !== void 0) {
@@ -2499,14 +1035,14 @@ async function resolveTopicDoc2(ctx, scopeId) {
2499
1035
  }
2500
1036
  return null;
2501
1037
  }
2502
- function materializeTopicProjectOverlay2(topic, idMode = "legacy") {
2503
- const metadata = readMetadata2(topic);
1038
+ function materializeTopicProjectOverlay(topic, idMode = "legacy") {
1039
+ const metadata = readMetadata(topic);
2504
1040
  const topicId = String(topic._id);
2505
- const legacyProjectId = readLegacyProjectId2(topic) || readLegacyProjectId2(metadata) || readNonEmptyString2(metadata.legacyProjectId);
1041
+ const legacyProjectId = readLegacyProjectId(topic) || readLegacyProjectId(metadata) || readNonEmptyString(metadata.legacyProjectId);
2506
1042
  const storageProjectId = legacyProjectId || topicId;
2507
1043
  const outwardId = idMode === "topic" ? topicId : storageProjectId;
2508
- const visibility = coerceVisibility2(topic.visibility) || coerceVisibility2(metadata.visibility) || "private";
2509
- const status = coerceStatus2(topic.status) || coerceStatus2(metadata.status) || "active";
1044
+ const visibility = coerceVisibility(topic.visibility) || coerceVisibility(metadata.visibility) || "private";
1045
+ const status = coerceStatus(topic.status) || coerceStatus(metadata.status) || "active";
2510
1046
  const createdAt = typeof topic.createdAt === "number" ? topic.createdAt : typeof topic._creationTime === "number" ? topic._creationTime : 0;
2511
1047
  const updatedAt = typeof topic.updatedAt === "number" ? topic.updatedAt : typeof metadata.updatedAt === "number" ? metadata.updatedAt : createdAt;
2512
1048
  return {
@@ -2516,16 +1052,16 @@ function materializeTopicProjectOverlay2(topic, idMode = "legacy") {
2516
1052
  topicId,
2517
1053
  storageProjectId,
2518
1054
  legacyProjectId,
2519
- name: readNonEmptyString2(topic.name) || "Untitled Theme",
2520
- type: mapProjectType2(topic, metadata),
2521
- description: readNonEmptyString2(topic.description),
2522
- ownerId: readNonEmptyString2(metadata.ownerId) || readNonEmptyString2(topic.createdBy) || "system",
2523
- sharedWith: readStringArray2(metadata.sharedWith),
1055
+ name: readNonEmptyString(topic.name) || "Untitled Theme",
1056
+ type: mapProjectType(topic, metadata),
1057
+ description: readNonEmptyString(topic.description),
1058
+ ownerId: readNonEmptyString(metadata.ownerId) || readNonEmptyString(topic.createdBy) || "system",
1059
+ sharedWith: readStringArray(metadata.sharedWith),
2524
1060
  visibility,
2525
- tenantId: readNonEmptyString2(topic.tenantId) || readNonEmptyString2(metadata.tenantId),
2526
- workspaceId: readNonEmptyString2(topic.workspaceId) || readNonEmptyString2(metadata.workspaceId),
1061
+ tenantId: readNonEmptyString(topic.tenantId) || readNonEmptyString(metadata.tenantId),
1062
+ workspaceId: readNonEmptyString(topic.workspaceId) || readNonEmptyString(metadata.workspaceId),
2527
1063
  status,
2528
- tags: readStringArray2(metadata.tags),
1064
+ tags: readStringArray(metadata.tags),
2529
1065
  chatCount: typeof metadata.chatCount === "number" ? metadata.chatCount : 0,
2530
1066
  artifactCount: typeof metadata.artifactCount === "number" ? metadata.artifactCount : 0,
2531
1067
  lastActivityAt: typeof metadata.lastActivityAt === "number" ? metadata.lastActivityAt : updatedAt,
@@ -2534,17 +1070,17 @@ function materializeTopicProjectOverlay2(topic, idMode = "legacy") {
2534
1070
  updatedAt
2535
1071
  };
2536
1072
  }
2537
- async function resolveTopicProjectOverlay2(ctx, scopeId, options = {}) {
2538
- const topic = await resolveTopicDoc2(ctx, scopeId);
1073
+ async function resolveTopicProjectOverlay(ctx, scopeId, options = {}) {
1074
+ const topic = await resolveTopicDoc(ctx, scopeId);
2539
1075
  if (!topic) {
2540
1076
  return null;
2541
1077
  }
2542
- if (options.projectLikeOnly !== false && !isProjectLikeTopic2(topic)) {
1078
+ if (options.projectLikeOnly !== false && !isProjectLikeTopic(topic)) {
2543
1079
  return null;
2544
1080
  }
2545
- return materializeTopicProjectOverlay2(topic, options.idMode);
1081
+ return materializeTopicProjectOverlay(topic, options.idMode);
2546
1082
  }
2547
- async function listTopicProjectOverlays2(ctx, options = {}) {
1083
+ async function listTopicProjectOverlays(ctx, options = {}) {
2548
1084
  let allTopics = [];
2549
1085
  if (ctx?.db?.query && typeof ctx.db.query === "function") {
2550
1086
  try {
@@ -2554,18 +1090,18 @@ async function listTopicProjectOverlays2(ctx, options = {}) {
2554
1090
  }
2555
1091
  }
2556
1092
  if (allTopics.length === 0 && typeof ctx.runQuery === "function") {
2557
- allTopics = (await ctx.runQuery(api2.topics.list, {}) ?? []) || [];
1093
+ allTopics = (await ctx.runQuery(api.topics.list, {}) ?? []) || [];
2558
1094
  }
2559
1095
  return allTopics.filter(
2560
- (topic) => options.projectLikeOnly === false || isProjectLikeTopic2(topic)
2561
- ).map((topic) => materializeTopicProjectOverlay2(topic, options.idMode));
1096
+ (topic) => options.projectLikeOnly === false || isProjectLikeTopic(topic)
1097
+ ).map((topic) => materializeTopicProjectOverlay(topic, options.idMode));
2562
1098
  }
2563
1099
  async function patchTopicProjectOverlay(ctx, scopeId, value) {
2564
- const topic = await resolveTopicDoc2(ctx, scopeId);
1100
+ const topic = await resolveTopicDoc(ctx, scopeId);
2565
1101
  if (!topic) {
2566
1102
  return null;
2567
1103
  }
2568
- const nextMetadata = { ...readMetadata2(topic) };
1104
+ const nextMetadata = { ...readMetadata(topic) };
2569
1105
  const patch = {};
2570
1106
  const topicUpdateArgs = {
2571
1107
  id: String(topic._id)
@@ -2590,7 +1126,7 @@ async function patchTopicProjectOverlay(ctx, scopeId, value) {
2590
1126
  `patchTopicProjectOverlay cannot mutate ${key} via component-owned topics`
2591
1127
  );
2592
1128
  case "status": {
2593
- const status = coerceStatus2(rawValue);
1129
+ const status = coerceStatus(rawValue);
2594
1130
  if (status) {
2595
1131
  patch.status = status;
2596
1132
  topicUpdateArgs.status = status;
@@ -2598,7 +1134,7 @@ async function patchTopicProjectOverlay(ctx, scopeId, value) {
2598
1134
  break;
2599
1135
  }
2600
1136
  case "visibility": {
2601
- const visibility = coerceVisibility2(rawValue);
1137
+ const visibility = coerceVisibility(rawValue);
2602
1138
  if (visibility) {
2603
1139
  patch.visibility = visibility;
2604
1140
  topicUpdateArgs.visibility = visibility;
@@ -2606,7 +1142,7 @@ async function patchTopicProjectOverlay(ctx, scopeId, value) {
2606
1142
  break;
2607
1143
  }
2608
1144
  case "type": {
2609
- const projectType = readNonEmptyString2(rawValue);
1145
+ const projectType = readNonEmptyString(rawValue);
2610
1146
  if (projectType) {
2611
1147
  nextMetadata.projectType = projectType;
2612
1148
  } else {
@@ -2630,7 +1166,7 @@ async function patchTopicProjectOverlay(ctx, scopeId, value) {
2630
1166
  topicUpdateArgs.metadata = nextMetadata;
2631
1167
  if (typeof ctx.runMutation === "function") {
2632
1168
  try {
2633
- await ctx.runMutation(api2.topics.update, topicUpdateArgs);
1169
+ await ctx.runMutation(api.topics.update, topicUpdateArgs);
2634
1170
  } catch (error) {
2635
1171
  if (!isMissingLucernChildComponentError(error) || !ctx?.db || typeof ctx.db.patch !== "function") {
2636
1172
  throw error;
@@ -2644,7 +1180,7 @@ async function patchTopicProjectOverlay(ctx, scopeId, value) {
2644
1180
  "Cannot patch topic without component adapter (ctx.runMutation unavailable)"
2645
1181
  );
2646
1182
  }
2647
- return materializeTopicProjectOverlay2(
1183
+ return materializeTopicProjectOverlay(
2648
1184
  {
2649
1185
  ...topic,
2650
1186
  ...patch,
@@ -2677,10 +1213,10 @@ async function patchProjectWithTolerance(ctx, projectId, value) {
2677
1213
  });
2678
1214
  }
2679
1215
  }
2680
- function defaultResolvers2() {
1216
+ function defaultResolvers() {
2681
1217
  return {
2682
1218
  async getProject(ctx, projectId) {
2683
- return await resolveTopicProjectOverlay2(ctx, projectId, {
1219
+ return await resolveTopicProjectOverlay(ctx, projectId, {
2684
1220
  idMode: "legacy",
2685
1221
  projectLikeOnly: false
2686
1222
  });
@@ -2689,7 +1225,7 @@ function defaultResolvers2() {
2689
1225
  await patchProjectWithTolerance(ctx, projectId, value);
2690
1226
  },
2691
1227
  async listTopics(ctx) {
2692
- return await listTopicProjectOverlays2(ctx, {
1228
+ return await listTopicProjectOverlays(ctx, {
2693
1229
  idMode: "legacy"
2694
1230
  });
2695
1231
  },
@@ -2698,11 +1234,11 @@ function defaultResolvers2() {
2698
1234
  }
2699
1235
  };
2700
1236
  }
2701
- var resolverOverrides2 = {};
1237
+ var resolverOverrides = {};
2702
1238
  function resolveGraphPrimitivesAppResolvers(_ctx) {
2703
1239
  return {
2704
- ...defaultResolvers2(),
2705
- ...resolverOverrides2
1240
+ ...defaultResolvers(),
1241
+ ...resolverOverrides
2706
1242
  };
2707
1243
  }
2708
1244
 
@@ -2727,7 +1263,7 @@ function throwStructuredMutationError(args) {
2727
1263
  function readFiniteNumber(value) {
2728
1264
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
2729
1265
  }
2730
- function clamp012(value) {
1266
+ function clamp01(value) {
2731
1267
  return Math.max(0, Math.min(1, value));
2732
1268
  }
2733
1269
  function assertBaseRateInRange(baseRate, field = "baseRate") {
@@ -2777,20 +1313,14 @@ function deriveSyntheticBackfillOpinion(source) {
2777
1313
  const uncertainty = readFiniteNumber(source.opinion_u) ?? readFiniteNumber(source.uncertainty);
2778
1314
  const baseRate = readFiniteNumber(source.opinion_a) ?? readFiniteNumber(source.baseRate);
2779
1315
  if (belief !== void 0 || disbelief !== void 0 || uncertainty !== void 0 || baseRate !== void 0) {
2780
- return readOpinionFromRecord(source, {
2781
- b: 0,
2782
- d: 0,
2783
- u: 1,
2784
- a: 0.5
2785
- });
1316
+ try {
1317
+ return readOpinionFromRecord(source);
1318
+ } catch {
1319
+ return mkOpinion(0, 0, 1, 0.5);
1320
+ }
2786
1321
  }
2787
- const confidence = clamp012(readFiniteNumber(source.confidence) ?? 0);
2788
- return {
2789
- b: confidence,
2790
- d: 1 - confidence,
2791
- u: 0,
2792
- a: 0.5
2793
- };
1322
+ const confidence = clamp01(readFiniteNumber(source.confidence) ?? 0);
1323
+ return mkOpinion(confidence, 1 - confidence, 0, 0.5);
2794
1324
  }
2795
1325
  function clampBeliefLimit(limit, fallback = DEFAULT_PROJECT_BELIEF_LIMIT) {
2796
1326
  if (!Number.isFinite(limit)) {
@@ -2805,16 +1335,17 @@ function readTupleContradictedFlag(value) {
2805
1335
  return typeof value === "boolean" ? value : void 0;
2806
1336
  }
2807
1337
  function readBeliefOpinionSnapshot(node, metadata) {
2808
- return readOpinionFromRecord(
2809
- {
1338
+ try {
1339
+ return readOpinionFromRecord({
2810
1340
  ...metadata,
2811
1341
  opinion_b: node.opinion_b,
2812
1342
  opinion_d: node.opinion_d,
2813
1343
  opinion_u: node.opinion_u,
2814
1344
  opinion_a: node.opinion_a
2815
- },
2816
- { b: 0, d: 0, u: 1, a: 0.5 }
2817
- );
1345
+ });
1346
+ } catch {
1347
+ return mkOpinion(0, 0, 1, 0.5);
1348
+ }
2818
1349
  }
2819
1350
  function deriveTupleContradictionSeverity(node) {
2820
1351
  const metadata = node.metadata || {};
@@ -3262,12 +1793,12 @@ var propagateConfidenceChange = internalMutation({
3262
1793
  },
3263
1794
  returns: permissiveReturn,
3264
1795
  handler: async (ctx, args) => {
3265
- const sourceOpinion = {
3266
- b: args.opinion_b,
3267
- d: args.opinion_d,
3268
- u: args.opinion_u,
3269
- a: args.opinion_a
3270
- };
1796
+ const sourceOpinion = mkOpinion(
1797
+ args.opinion_b,
1798
+ args.opinion_d,
1799
+ args.opinion_u,
1800
+ args.opinion_a
1801
+ );
3271
1802
  const sourceNode = await ctx.db.get(args.nodeId);
3272
1803
  const sourceScope = await resolveNodeScopeForWorkspaceIsolation(
3273
1804
  ctx,
@@ -3374,7 +1905,7 @@ var create = mutation({
3374
1905
  expectedBy: v.optional(v.number())
3375
1906
  })
3376
1907
  ),
3377
- baseRate: v.number(),
1908
+ baseRate: v.optional(v.number()),
3378
1909
  metadata: v.optional(v.any())
3379
1910
  // Additional metadata including isConditional
3380
1911
  },
@@ -3405,7 +1936,7 @@ var create = mutation({
3405
1936
  );
3406
1937
  }
3407
1938
  const now = Date.now();
3408
- const baseRate = assertBaseRateInRange(args.baseRate);
1939
+ const baseRate = assertBaseRateInRange(args.baseRate ?? 0.5);
3409
1940
  const initialBeliefStatus = args.worktreeId ? "hypothesis" : "assumption";
3410
1941
  const initialEpistemicStatus = args.worktreeId ? "hypothesis" : "assumption";
3411
1942
  const seedOpinion = {
@@ -3979,8 +2510,8 @@ var modulateConfidence = mutation({
3979
2510
  // d: evidence AGAINST [0,1]
3980
2511
  uncertainty: v.number(),
3981
2512
  // u: lack of evidence [0,1]
3982
- baseRate: v.optional(v.number()),
3983
- // a: prior probability [0,1], defaults to 0.5
2513
+ baseRate: v.number(),
2514
+ // a: prior probability [0,1]
3984
2515
  trigger: v.union(
3985
2516
  v.literal("evidence_added"),
3986
2517
  v.literal("evidence_removed"),
@@ -5131,7 +3662,7 @@ var internalCreate = internalMutation({
5131
3662
  args: {
5132
3663
  ...optionalBeliefScopeArgs,
5133
3664
  formulation: v.string(),
5134
- baseRate: v.number(),
3665
+ baseRate: v.optional(v.number()),
5135
3666
  confidence: v.optional(
5136
3667
  v.union(v.literal("high"), v.literal("medium"), v.literal("low"))
5137
3668
  ),
@@ -5167,7 +3698,7 @@ var internalCreate = internalMutation({
5167
3698
  returns: permissiveReturn,
5168
3699
  handler: async (ctx, args) => {
5169
3700
  const now = Date.now();
5170
- const baseRate = assertBaseRateInRange(args.baseRate);
3701
+ const baseRate = assertBaseRateInRange(args.baseRate ?? 0.5);
5171
3702
  const scope = await resolveTopicProjectScope(ctx, {
5172
3703
  topicId: args.topicId,
5173
3704
  projectId: args.projectId
@@ -5416,15 +3947,15 @@ var backfillSyntheticOpinionHistory = internalMutation({
5416
3947
  skippedHasHistory++;
5417
3948
  continue;
5418
3949
  }
5419
- const opinion2 = deriveSyntheticBackfillOpinion(node);
3950
+ const opinion = deriveSyntheticBackfillOpinion(node);
5420
3951
  await ctx.db.insert(
5421
3952
  "beliefConfidence",
5422
3953
  buildBeliefConfidenceRow({
5423
3954
  beliefId: node._id,
5424
- belief: opinion2.b,
5425
- disbelief: opinion2.d,
5426
- uncertainty: opinion2.u,
5427
- baseRate: opinion2.a,
3955
+ belief: opinion.b,
3956
+ disbelief: opinion.d,
3957
+ uncertainty: opinion.u,
3958
+ baseRate: opinion.a,
5428
3959
  trigger: "backfill_synthetic",
5429
3960
  rationale: "LK-6 backfill: synthesized t0 from node-level opinion fields (no prior beliefConfidence row found).",
5430
3961
  assessedAt: readFiniteNumber(node.createdAt) ?? readFiniteNumber(node.updatedAt) ?? Date.now(),