@lucern/graph-primitives 1.0.28 → 1.0.29

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 (52) hide show
  1. package/dist/contradictions.js +3 -3
  2. package/dist/contradictions.js.map +1 -1
  3. package/dist/entityLifecycle.js +7 -39
  4. package/dist/entityLifecycle.js.map +1 -1
  5. package/dist/epistemicBeliefs.admin.js +6 -42
  6. package/dist/epistemicBeliefs.admin.js.map +1 -1
  7. package/dist/epistemicBeliefs.backfills.js +2 -21
  8. package/dist/epistemicBeliefs.backfills.js.map +1 -1
  9. package/dist/epistemicBeliefs.confidence.d.ts +1 -0
  10. package/dist/epistemicBeliefs.confidence.js +4 -40
  11. package/dist/epistemicBeliefs.confidence.js.map +1 -1
  12. package/dist/epistemicBeliefs.core.js +6 -44
  13. package/dist/epistemicBeliefs.core.js.map +1 -1
  14. package/dist/epistemicBeliefs.d.ts +1 -0
  15. package/dist/epistemicBeliefs.forkEvidence.js +2 -22
  16. package/dist/epistemicBeliefs.forkEvidence.js.map +1 -1
  17. package/dist/epistemicBeliefs.helpers.d.ts +3 -27
  18. package/dist/epistemicBeliefs.helpers.js +4 -40
  19. package/dist/epistemicBeliefs.helpers.js.map +1 -1
  20. package/dist/epistemicBeliefs.internal.js +2 -22
  21. package/dist/epistemicBeliefs.internal.js.map +1 -1
  22. package/dist/epistemicBeliefs.js +17 -55
  23. package/dist/epistemicBeliefs.js.map +1 -1
  24. package/dist/epistemicBeliefs.lifecycle.js +7 -45
  25. package/dist/epistemicBeliefs.lifecycle.js.map +1 -1
  26. package/dist/epistemicBeliefs.links.js +7 -43
  27. package/dist/epistemicBeliefs.links.js.map +1 -1
  28. package/dist/epistemicContracts.evaluators.js +4 -40
  29. package/dist/epistemicContracts.evaluators.js.map +1 -1
  30. package/dist/epistemicContracts.handlers.js +4 -40
  31. package/dist/epistemicContracts.handlers.js.map +1 -1
  32. package/dist/epistemicContracts.js +4 -40
  33. package/dist/epistemicContracts.js.map +1 -1
  34. package/dist/epistemicEdges.js +4 -4
  35. package/dist/epistemicEdges.js.map +1 -1
  36. package/dist/epistemicEdges.mutations.js +4 -4
  37. package/dist/epistemicEdges.mutations.js.map +1 -1
  38. package/dist/epistemicEvidence.js +5 -5
  39. package/dist/epistemicEvidence.js.map +1 -1
  40. package/dist/epistemicEvidenceMutations.js +5 -5
  41. package/dist/epistemicEvidenceMutations.js.map +1 -1
  42. package/dist/epistemicNodes.js +4 -4
  43. package/dist/epistemicNodes.js.map +1 -1
  44. package/dist/epistemicNodes.mutations.js +4 -4
  45. package/dist/epistemicNodes.mutations.js.map +1 -1
  46. package/dist/epistemicSources.js +2 -2
  47. package/dist/epistemicSources.js.map +1 -1
  48. package/dist/index.d.ts +1 -0
  49. package/dist/index.js +50 -121
  50. package/dist/index.js.map +1 -1
  51. package/dist/proof-attestation.json +1 -1
  52. package/package.json +4 -4
@@ -10,5 +10,6 @@ export { backfillBeliefSprintIds, backfillMandatoryPriors, backfillScoredBeliefE
10
10
  import './topicScope-7zhyeGl7.js';
11
11
  import 'convex/values';
12
12
  import './convex.js';
13
+ import '@lucern/access-control/structuredMutationError';
13
14
  import '@lucern/confidence';
14
15
  import './beliefLifecycle-CXwdDw5e.js';
@@ -1,6 +1,6 @@
1
- import { v, ConvexError } from 'convex/values';
1
+ import { v } from 'convex/values';
2
+ import { throwStructuredMutationError } from '@lucern/access-control/structuredMutationError';
2
3
  import { normalizeTupleContradictionPolicy } from '@lucern/confidence';
3
- import '@lucern/access-control/access';
4
4
  import '@lucern/access-control/audience';
5
5
  import '@lucern/access-control/auth';
6
6
 
@@ -9,26 +9,6 @@ v.id("epistemicNodes");
9
9
  ({
10
10
  tupleContradiction: normalizeTupleContradictionPolicy()
11
11
  });
12
- function throwStructuredMutationError(args) {
13
- const data = {
14
- structuredMutationError: true,
15
- message: args.message,
16
- status: args.status,
17
- code: args.code,
18
- invariantCode: args.invariantCode,
19
- suggestion: args.suggestion,
20
- details: args.details
21
- };
22
- const error = new ConvexError(
23
- data
24
- );
25
- error.status = args.status;
26
- error.code = args.code;
27
- error.invariantCode = args.invariantCode;
28
- error.suggestion = args.suggestion;
29
- error.details = args.details;
30
- throw error;
31
- }
32
12
 
33
13
  // src/epistemicBeliefs.forkEvidence.ts
34
14
  function normalizeForkTriggerRelation(value) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/epistemicBeliefs.helpers.ts","../src/epistemicBeliefs.forkEvidence.ts"],"names":[],"mappings":"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","file":"epistemicBeliefs.forkEvidence.js","sourcesContent":["/**\n * Epistemic Beliefs API\n *\n * Clean API for managing beliefs in the epistemic spine (epistemicNodes).\n * This is the NEW API that replaces beliefs.ts.\n *\n * Key differences from beliefs.ts:\n * - Writes ONLY to epistemicNodes (no dual-write)\n * - Uses epistemicNodes IDs directly (no runtime ID translation fallback)\n * - Syncs to Neo4j after mutations\n * - Follows Lucern invariants strictly\n *\n * @see /docs/epistemic-invariants/00-epistemic-invariants.md\n */\n\nimport { v } from \"convex/values\";\nimport { ConvexError } from \"convex/values\";\nimport {\n detectTupleContradiction,\n evaluateTupleContradictionTransition,\n confidenceFromSL,\n mkOpinion,\n normalizeTupleContradictionPolicy,\n readOpinionFromRecord,\n type ConfidencePolicyConfig,\n type Opinion,\n type SLOpinion,\n type SLOperator,\n} from \"@lucern/confidence\";\nimport {\n checkProjectAccess,\n checkScopeAccess,\n} from \"@lucern/access-control/access\";\nimport {\n canAudienceClassAccess,\n classFromAudienceKey,\n normalizeAudienceKey,\n} from \"@lucern/access-control/audience\";\nimport { listAudienceRegistryRows } from \"@lucern/access-control/audienceRegistry\";\nimport { getCurrentUserId } from \"@lucern/access-control/auth\";\nimport { assertSchemaEnumValue } from \"@lucern/contracts/schema-helpers/enumValidation\";\nimport { permissiveReturn } from \"@lucern/contracts/schema-helpers/validators\";\nimport {\n type BeliefLifecycleStatus,\n isPreValidationBeliefStatus,\n promoteBeliefStatusAfterScoring,\n resolveBeliefLifecycleStatus,\n} from \"./beliefLifecycle\";\nimport type { Doc, Id, MutationCtx, QueryCtx } from \"./convex\";\nimport {\n internal,\n internalMutation,\n internalQuery,\n mutation,\n query,\n} from \"./convex\";\nimport { collectConfidencePropagationDispatches } from \"./confidencePropagationDispatch\";\nimport { debugGraphPrimitiveFallback } from \"./debug\";\nimport {\n createInheritedContractRecord,\n type VerificationConfidenceTrigger,\n} from \"./epistemicContractHelpers\";\nimport { scheduleEmbeddingGeneration } from \"./embeddingTrigger\";\nimport { generateGlobalId } from \"./globalId\";\nimport { computeLogicalRole } from \"./logicalRoleInference\";\nimport { resolveGraphPrimitivesAppResolvers } from \"./resolvers\";\nimport { optionalScopeArgs, resolveTopicProjectScope } from \"./topicScope\";\nimport {\n assertTenantPackWorkspaceMutationAllowed,\n assertWorkspaceScopedEpistemicNodeScope,\n nodeMatchesWorkspaceReasoningScope,\n resolveNodeScopeForWorkspaceIsolation,\n resolveRuntimePackMutationContext,\n} from \"./workspaceIsolation\";\n\n// All IDs now use epistemicNodes exclusively\nexport const insightIdUnion = v.id(\"epistemicNodes\");\nconst DEFAULT_PROJECT_BELIEF_LIMIT = 250;\nexport const MAX_PROJECT_BELIEF_LIMIT = 1000;\nexport const optionalBeliefScopeArgs = optionalScopeArgs;\n\ntype StructuredMutationError = Error & {\n status: number;\n code: string;\n invariantCode?: string;\n suggestion?: string;\n details?: unknown;\n};\n\n/**\n * Boundary-safe payload carried inside a {@link ConvexError}. Mirrors the kernel\n * copy in packages/reasoning-kernel/src/adapters/beliefs.shared.ts so structured\n * refusals from the graph-primitives module survive the Convex client boundary\n * (only `ConvexError.data` is preserved across serialization).\n */\nexport type StructuredMutationErrorData = {\n structuredMutationError: true;\n message: string;\n status: number;\n code: string;\n invariantCode?: string;\n suggestion?: string;\n details?: unknown;\n};\n\ntype BeliefScopeArgs = {\n projectId?: string | null;\n topicId?: string | null;\n};\n\ntype EpistemicNodeOpinionDoc = Doc<\"epistemicNodes\"> & {\n opinion_a?: number;\n opinion_b?: number;\n opinion_d?: number;\n opinion_u?: number;\n tupleContradicted?: boolean;\n userId?: string;\n metadata?: Record<string, unknown> | null;\n};\n\ntype EpistemicIndexQuery = {\n eq(field: string, value: unknown): EpistemicIndexQuery;\n gt(field: string, value: unknown): EpistemicIndexQuery;\n lt(field: string, value: unknown): EpistemicIndexQuery;\n field(field: string): string;\n};\n\ntype EpistemicQueryChain<T> = {\n withIndex(\n _name: string,\n callback: (q: EpistemicIndexQuery) => EpistemicIndexQuery,\n ): EpistemicQueryChain<T>;\n order(direction: \"asc\" | \"desc\"): EpistemicQueryChain<T>;\n filter(\n callback: (q: EpistemicIndexQuery) => EpistemicIndexQuery,\n ): EpistemicQueryChain<T>;\n first(): Promise<T | null>;\n collect(): Promise<T[]>;\n take(limit: number): Promise<T[]>;\n};\n\ntype EpistemicBeliefCtx = QueryCtx & {\n db: {\n query<T = unknown>(_table: string): EpistemicQueryChain<T>;\n get<T = unknown>(id: Id<string>): Promise<T | null>;\n };\n scheduler: {\n runAfter(\n _delayMs: number,\n _handler: unknown,\n _args: unknown,\n ): Promise<unknown>;\n };\n};\n\ntype AudienceClass = \"internal\" | \"restricted_external\" | \"public\";\n\nconst DEFAULT_CONFIDENCE_POLICY: ConfidencePolicyConfig = {\n scoringMode: \"after_worktree\",\n tupleContradiction: normalizeTupleContradictionPolicy(),\n};\n\nexport type BeliefConfidenceTrigger =\n | \"initial\"\n | \"evidence_added\"\n | \"evidence_removed\"\n | \"contradiction_detected\"\n | \"contradiction_resolved\"\n | \"propagation\"\n | \"agent_assessment\"\n | \"worktree_outcome\"\n | \"worktree_completed\"\n // SL-specific triggers\n | \"fusion\"\n | \"discount\"\n | \"deduction\"\n | \"backfill_synthetic\"\n | VerificationConfidenceTrigger;\n\nexport function throwStructuredMutationError(args: {\n message: string;\n status: number;\n code: string;\n invariantCode?: string;\n suggestion?: string;\n details?: unknown;\n}): never {\n // FR.7 (parity with kernel beliefs.shared.ts): throw a ConvexError carrying\n // the structured payload in `.data` so the gateway can reconstruct the real\n // HTTP status/code instead of collapsing the refusal into a retryable 500.\n // The legacy `.status`/`.code` props are retained for in-process tests.\n const data: StructuredMutationErrorData = {\n structuredMutationError: true,\n message: args.message,\n status: args.status,\n code: args.code,\n invariantCode: args.invariantCode,\n suggestion: args.suggestion,\n details: args.details,\n };\n const error = new ConvexError(\n data as unknown as string,\n ) as unknown as ConvexError<string> & Partial<StructuredMutationError>;\n error.status = args.status;\n error.code = args.code;\n error.invariantCode = args.invariantCode;\n error.suggestion = args.suggestion;\n error.details = args.details;\n throw error;\n}\n\nexport function readFiniteNumber(value: unknown): number | undefined {\n return typeof value === \"number\" && Number.isFinite(value)\n ? value\n : undefined;\n}\n\nfunction clamp01(value: number): number {\n return Math.max(0, Math.min(1, value));\n}\n\nexport function assertBaseRateInRange(baseRate: number, field = \"baseRate\"): number {\n if (baseRate < 0 || baseRate > 1) {\n throwStructuredMutationError({\n message: `${field} must be within [0, 1].`,\n status: 400,\n code: \"INVALID_ARGUMENT\",\n invariantCode: \"request.valid_shape\",\n suggestion: `Clamp ${field} into the inclusive [0, 1] interval.`,\n details: { field, baseRate },\n });\n }\n return baseRate;\n}\n\nexport function buildBeliefConfidenceRow(args: {\n beliefId: Id<\"epistemicNodes\">;\n belief: number;\n disbelief: number;\n uncertainty: number;\n baseRate: number;\n trigger: BeliefConfidenceTrigger;\n rationale?: string;\n assessedBy: string;\n assessedAt: number;\n slOperator?: SLOperator;\n triggeringEvidenceId?: Id<\"epistemicNodes\">;\n triggeringContradictionId?: Id<\"contradictions\">;\n triggeringWorktreeId?: string;\n}) {\n return {\n beliefId: args.beliefId,\n confidence: confidenceFromSL(\n args.belief,\n args.disbelief,\n args.uncertainty,\n args.baseRate,\n ),\n belief: args.belief,\n disbelief: args.disbelief,\n uncertainty: args.uncertainty,\n baseRate: args.baseRate,\n slOperator: args.slOperator ?? (\"prior_seed\" as const),\n trigger: args.trigger,\n ...(args.rationale ? { rationale: args.rationale } : {}),\n assessedBy: args.assessedBy,\n assessedAt: args.assessedAt,\n ...(args.triggeringEvidenceId\n ? {\n triggeringEvidenceId: args.triggeringEvidenceId,\n triggeringEvidenceIds: [String(args.triggeringEvidenceId)],\n }\n : {}),\n ...(args.triggeringContradictionId\n ? {\n triggeringContradictionId: args.triggeringContradictionId,\n }\n : {}),\n ...(args.triggeringWorktreeId\n ? { triggeringWorktreeId: args.triggeringWorktreeId }\n : {}),\n };\n}\n\ntype BeliefStatusResult =\n | { success: true }\n | { success: false; message: \"Evidence node not found\" };\n\nexport function buildBeliefStatusSuccessResult(): BeliefStatusResult {\n return { success: true };\n}\n\nexport function buildBeliefEvidenceNotFoundResult(): BeliefStatusResult {\n const result = {} as Extract<BeliefStatusResult, { success: false }>;\n result.success = false;\n result.message = \"Evidence node not found\";\n return result;\n}\n\nexport function deriveSyntheticBackfillOpinion(\n source: Record<string, unknown>,\n): SLOpinion {\n const belief =\n readFiniteNumber(source.opinion_b) ?? readFiniteNumber(source.belief);\n const disbelief =\n readFiniteNumber(source.opinion_d) ?? readFiniteNumber(source.disbelief);\n const uncertainty =\n readFiniteNumber(source.opinion_u) ?? readFiniteNumber(source.uncertainty);\n const baseRate =\n readFiniteNumber(source.opinion_a) ?? readFiniteNumber(source.baseRate);\n\n if (\n belief !== undefined ||\n disbelief !== undefined ||\n uncertainty !== undefined ||\n baseRate !== undefined\n ) {\n try {\n return readOpinionFromRecord(source);\n } catch (error) {\n debugGraphPrimitiveFallback(\n \"[epistemicBeliefs] Failed to decode legacy belief opinion\",\n {\n error,\n },\n );\n return mkOpinion(0, 0, 1, 0.5);\n }\n }\n\n const confidence = clamp01(readFiniteNumber(source.confidence) ?? 0);\n return mkOpinion(confidence, 1 - confidence, 0, 0.5);\n}\n\nexport function clampBeliefLimit(\n limit: number | undefined,\n fallback = DEFAULT_PROJECT_BELIEF_LIMIT,\n): number {\n if (!Number.isFinite(limit)) {\n return fallback;\n }\n return Math.max(\n 1,\n Math.min(Math.floor(limit as number), MAX_PROJECT_BELIEF_LIMIT),\n );\n}\n\nexport function readTupleContradictedFlag(value: unknown): boolean | undefined {\n return typeof value === \"boolean\" ? value : undefined;\n}\n\nexport function readBeliefOpinionSnapshot(\n node: Doc<\"epistemicNodes\">,\n metadata: Record<string, unknown>,\n): SLOpinion {\n try {\n return readOpinionFromRecord({\n ...metadata,\n opinion_b: (node as any).opinion_b,\n opinion_d: (node as any).opinion_d,\n opinion_u: (node as any).opinion_u,\n opinion_a: (node as any).opinion_a,\n });\n } catch (error) {\n debugGraphPrimitiveFallback(\n \"[epistemicBeliefs] Failed to read belief opinion snapshot\",\n {\n error,\n beliefId: node._id,\n },\n );\n return mkOpinion(0, 0, 1, 0.5);\n }\n}\n\nexport function deriveTupleContradictionSeverity(\n node: Doc<\"epistemicNodes\">,\n): \"critical\" | \"significant\" | \"minor\" {\n const metadata = (node.metadata || {}) as Record<string, unknown>;\n const criticality =\n typeof metadata.criticality === \"string\" ? metadata.criticality : undefined;\n\n if (criticality === \"blocking\") {\n return \"critical\";\n }\n if (criticality === \"supporting\") {\n return \"minor\";\n }\n return \"significant\";\n}\n\nexport function formatTupleContradictionDescription(args: {\n opinion: Opinion;\n policy: ConfidencePolicyConfig[\"tupleContradiction\"];\n}): string {\n return `Tuple-space contradiction detected: b=${args.opinion.b.toFixed(2)} > ${args.policy.beliefThreshold.toFixed(2)} and d=${args.opinion.d.toFixed(2)} > ${args.policy.disbeliefThreshold.toFixed(2)}.`;\n}\n\n// =============================================================================\n// HELPERS\n// =============================================================================\n\nexport function generateContentHash(text: string): string {\n const content = `belief:${text.trim().toLowerCase().replace(/\\s+/g, \" \")}`;\n let hash = 5381;\n for (let i = 0; i < content.length; i++) {\n hash = (hash << 5) + hash + content.charCodeAt(i);\n hash &= hash;\n }\n return Math.abs(hash).toString(16).padStart(8, \"0\");\n}\n\nexport function resolveBeliefWorktreeId(\n metadata: Record<string, unknown> | undefined,\n): string | undefined {\n const worktreeId = metadata?.worktreeId;\n if (typeof worktreeId === \"string\" && worktreeId.trim().length > 0) {\n return worktreeId;\n }\n\n const sprintId = metadata?.sprintId;\n return typeof sprintId === \"string\" && sprintId.trim().length > 0\n ? sprintId\n : undefined;\n}\n\n// Map pillar names to valid pillar literals\ntype ValidPillar =\n | \"market\"\n | \"competition\"\n | \"product\"\n | \"team\"\n | \"financials\"\n | \"regulatory\"\n | \"timing\"\n | \"customer\"\n | \"technology\"\n | \"distribution\"\n | \"deal\"\n | \"risks\"\n | \"other\";\n\nexport function normalizePillar(pillar?: string): ValidPillar {\n if (!pillar) {\n return \"other\";\n }\n const lower = pillar.toLowerCase();\n const validPillars: ValidPillar[] = [\n \"market\",\n \"competition\",\n \"product\",\n \"team\",\n \"financials\",\n \"regulatory\",\n \"timing\",\n \"customer\",\n \"technology\",\n \"distribution\",\n \"deal\",\n \"risks\",\n ];\n return (validPillars.find((p) => lower.includes(p)) ||\n \"other\") as ValidPillar;\n}\n\nexport async function markBeliefGraphDirty(\n ctx: EpistemicBeliefCtx,\n scope: { projectId?: string | null; topicId?: string | null },\n): Promise<void> {\n const projectId =\n typeof scope.projectId === \"string\" && scope.projectId.trim().length > 0\n ? scope.projectId\n : undefined;\n const topicId =\n typeof scope.topicId === \"string\" && scope.topicId.trim().length > 0\n ? scope.topicId\n : undefined;\n\n if (!projectId && !topicId) {\n return;\n }\n\n if (projectId) {\n await ctx.scheduler.runAfter(\n 0,\n internal.graphAnalysisCache.markCacheStaleInternal,\n { projectId },\n );\n }\n if (topicId) {\n await ctx.scheduler.runAfter(\n 0,\n internal.graphAnalysisCache.markCacheStaleByTopic,\n { topicId },\n );\n }\n\n await resolveGraphPrimitivesAppResolvers(ctx).patchProject(\n ctx,\n topicId ?? projectId!,\n {\n lastActivityAt: Date.now(),\n },\n );\n}\n\nexport async function resolveBeliefScopeOrNull(\n ctx: QueryCtx,\n args: BeliefScopeArgs,\n) {\n if (!args.projectId && !args.topicId) {\n return null;\n }\n\n try {\n return await resolveTopicProjectScope(ctx, {\n projectId: args.projectId ?? undefined,\n topicId: args.topicId ?? undefined,\n });\n } catch (error) {\n debugGraphPrimitiveFallback(\n \"[epistemicBeliefs] Failed to resolve belief scope\",\n {\n error,\n projectId: args.projectId,\n topicId: args.topicId,\n },\n );\n return null;\n }\n}\n\nexport async function getBeliefNodesForScope(\n ctx: EpistemicBeliefCtx,\n scope: Awaited<ReturnType<typeof resolveTopicProjectScope>>,\n args?: { scanLimit?: number; status?: string },\n) {\n const baseQuery = ctx.db\n .query(\"epistemicNodes\")\n .withIndex(\"by_topic_type\", (q: any) =>\n q.eq(\"topicId\", scope.topicId).eq(\"nodeType\", \"belief\"),\n );\n const nodes: EpistemicNodeOpinionDoc[] =\n typeof args?.scanLimit === \"number\"\n ? (await baseQuery.order(\"desc\").take(args.scanLimit)) as EpistemicNodeOpinionDoc[]\n : (await baseQuery.collect()) as EpistemicNodeOpinionDoc[];\n const scopedNodes = nodes.filter((node: Doc<\"epistemicNodes\">) =>\n nodeMatchesWorkspaceReasoningScope(node, scope),\n );\n\n if (!args?.status) {\n return scopedNodes;\n }\n\n return scopedNodes.filter((node: EpistemicNodeOpinionDoc) => node.status === args.status);\n}\n\nexport function createBeliefAudienceResolver(\n registryRows: Array<{\n audienceKey?: string | null;\n audienceClass: AudienceClass;\n }>,\n) {\n const audienceClassByKey = new Map<string, AudienceClass>(\n registryRows.map((row) => [\n normalizeAudienceKey(row.audienceKey),\n row.audienceClass,\n ]),\n );\n\n return (\n audienceKey: string | undefined | null,\n fallback: AudienceClass,\n ): AudienceClass => {\n const key = normalizeAudienceKey(audienceKey);\n if (!key) {\n return fallback;\n }\n return (\n audienceClassByKey.get(key) ??\n (classFromAudienceKey(key, fallback) as AudienceClass)\n );\n };\n}\n\nexport function flattenBeliefNode(node: Doc<\"epistemicNodes\">) {\n const meta = (node.metadata || {}) as Record<string, unknown>;\n const worktreeId = resolveBeliefWorktreeId(meta);\n const tupleContradicted =\n readTupleContradictedFlag(\n (node as EpistemicNodeOpinionDoc).tupleContradicted,\n ) ??\n readTupleContradictedFlag(meta.tupleContradicted) ??\n false;\n\n return {\n _id: node._id,\n _epistemicNodeId: node._id,\n _creationTime: node._creationTime,\n belief: node.canonicalText,\n formulation: node.canonicalText,\n projectId: node.projectId,\n topicId: node.topicId,\n userId: (node as EpistemicNodeOpinionDoc).userId || node.createdBy || \"\",\n confidence: (meta.confidence as \"high\" | \"medium\" | \"low\") || \"untested\",\n status: node.status,\n beliefStatus: resolveBeliefStatus(node, meta),\n topic: (meta.topic as string) || (meta.pillar as string) || \"other\",\n pillar: (meta.pillar as string) || (meta.topic as string) || \"\",\n category: (meta.category as string) || \"\",\n subcategory: (meta.subcategory as string) || \"\",\n categoryIcon: (meta.categoryIcon as string) || \"\",\n supportingEvidence: (meta.supportingEvidenceIds as string[]) || [],\n contradictingEvidence: (meta.contradictingEvidenceIds as string[]) || [],\n testingQuestions: (meta.testingQuestionIds as string[]) || [],\n linkedInsights: (meta.linkedInsightIds as string[]) || [],\n createdAt: node.createdAt,\n updatedAt: node.updatedAt || node.createdAt,\n tupleContradicted,\n sprintId: (meta.sprintId as string) || undefined,\n worktreeId,\n sourceBeliefIds: (meta.sourceBeliefIds as string[]) || undefined,\n criticality:\n (meta.criticality as\n | \"critical\"\n | \"supporting\"\n | \"nice_to_have\"\n | \"unanalyzed\"\n | \"blocking\"\n | \"important\") || \"unanalyzed\",\n rationale: (meta.rationale as string) || \"\",\n audienceLabel: node.audienceLabel,\n policyTags: node.policyTags,\n sensitivityTier: node.sensitivityTier,\n exportClass: node.exportClass,\n anonymizationClass: node.anonymizationClass,\n };\n}\n\nexport function resolveBeliefStatus(\n node: {\n beliefStatus?: unknown;\n confidence?: unknown;\n predictionMeta?: unknown;\n },\n metadata: Record<string, unknown>,\n): BeliefLifecycleStatus {\n return resolveBeliefLifecycleStatus({\n beliefStatus: node.beliefStatus,\n confidence: node.confidence,\n predictionMeta: node.predictionMeta,\n metadata,\n });\n}\n\nexport async function hasCompletedWorktreeForBelief(\n ctx: EpistemicBeliefCtx,\n beliefNodeId: Id<\"epistemicNodes\">,\n): Promise<boolean> {\n // Check if the belief is linked to a completed worktree via worktreeBeliefCluster\n const clusterMembership = await ctx.db\n .query(\"worktreeBeliefCluster\")\n .withIndex(\"by_belief\", (q: any) => q.eq(\"beliefId\", beliefNodeId))\n .collect();\n for (const membership of clusterMembership) {\n const worktree = await ctx.db.get(membership.worktreeId);\n if (worktree?.status === \"completed\" || worktree?.status === \"merged\") {\n return true;\n }\n }\n return false;\n}\n\nexport async function getActiveConfidencePolicy(\n ctx: EpistemicBeliefCtx,\n): Promise<ConfidencePolicyConfig> {\n try {\n const activeConfig = await ctx.db\n .query(\"logicSprintScoring\")\n .withIndex(\"by_active\", (q: any) => q.eq(\"isActive\", true))\n .first();\n\n return {\n scoringMode:\n activeConfig?.confidencePolicy === \"always\"\n ? \"always\"\n : DEFAULT_CONFIDENCE_POLICY.scoringMode,\n tupleContradiction: normalizeTupleContradictionPolicy(\n activeConfig?.tupleContradictionPolicy as\n | Partial<ConfidencePolicyConfig[\"tupleContradiction\"]>\n | undefined,\n ),\n };\n } catch (error) {\n debugGraphPrimitiveFallback(\n \"[epistemicBeliefs] Failed to load active confidence policy\",\n {\n error,\n },\n );\n // K-tier/component environments do not carry logicSprintScoring.\n return DEFAULT_CONFIDENCE_POLICY;\n }\n}\n\nexport async function requireAuthenticatedUserId(ctx: {\n auth: { getUserIdentity: () => Promise<unknown> };\n}): Promise<string> {\n const userId = await getCurrentUserId(\n ctx as Parameters<typeof getCurrentUserId>[0],\n );\n if (!userId) {\n throwStructuredMutationError({\n message: \"Authentication required.\",\n status: 401,\n code: \"AUTHENTICATION_REQUIRED\",\n invariantCode: \"auth.required\",\n suggestion:\n \"Provide a valid bearer token before invoking belief mutations.\",\n });\n }\n return userId;\n}\n\nexport async function requireProjectWriteAccess(\n ctx: { db: unknown },\n projectId: string,\n userId: string,\n): Promise<void> {\n const hasAccess = await checkProjectAccess(\n ctx as Parameters<typeof checkProjectAccess>[0],\n projectId,\n userId,\n );\n if (!hasAccess) {\n // FR.7: structured, non-retryable 403 naming ONLY the targeted topic and\n // the caller's own principal (no cross-tenant leakage; security condition e).\n throwStructuredMutationError({\n message: `Project write access denied for topic ${projectId}.`,\n status: 403,\n code: \"PROJECT_ACCESS_DENIED\",\n invariantCode: \"policy.scope_required\",\n suggestion:\n \"The acting principal lacks project-write access to this topic. Request a topic grant (or, if the principal created this topic, run the creator-grant backfill) and retry.\",\n details: { topicId: projectId, principalId: userId },\n });\n }\n}\n","/** Evidence-gated fork precondition helpers. */\n\nimport type { Doc, Id, MutationCtx } from \"./convex\";\nimport { throwStructuredMutationError } from \"./epistemicBeliefs.helpers\";\n\nexport type ForkMode = \"supersede\" | \"branch\";\nexport type ForkTriggerRelation = \"supports\" | \"contradicts\";\n\nfunction normalizeForkTriggerRelation(\n value: unknown,\n): ForkTriggerRelation | null {\n if (value === \"supports\" || value === \"supporting\") {\n return \"supports\";\n }\n if (value === \"contradicts\" || value === \"contradicting\") {\n return \"contradicts\";\n }\n return null;\n}\n\nexport async function resolveForkTriggerEvidence(\n ctx: MutationCtx,\n args: {\n parentNodeId: Id<\"epistemicNodes\">;\n parent: Doc<\"epistemicNodes\">;\n triggeringEvidenceId: Id<\"epistemicNodes\">;\n forkMode: ForkMode;\n },\n): Promise<{ evidenceNodeId: Id<\"epistemicNodes\">; relation: ForkTriggerRelation }> {\n const evidence = await ctx.db.get(args.triggeringEvidenceId);\n if (!evidence || evidence.nodeType !== \"evidence\") {\n throwStructuredMutationError({\n message: \"Fork requires an existing evidence node.\",\n status: 400,\n code: \"INVALID_ARGUMENT\",\n invariantCode: \"belief.fork_requires_evidence\",\n suggestion:\n \"Create or link evidence first, then fork with triggeringEvidenceId.\",\n details: { triggeringEvidenceId: args.triggeringEvidenceId },\n });\n }\n if (evidence.topicId && evidence.topicId !== args.parent.topicId) {\n throwStructuredMutationError({\n message: \"Fork evidence belongs to a different topic scope.\",\n status: 400,\n code: \"INVALID_ARGUMENT\",\n invariantCode: \"belief.fork_evidence_scope\",\n suggestion: \"Use evidence from the same topic/workspace scope as the parent belief.\",\n details: {\n parentNodeId: args.parentNodeId,\n triggeringEvidenceId: args.triggeringEvidenceId,\n },\n });\n }\n\n const evidenceMetadata =\n evidence.metadata && typeof evidence.metadata === \"object\"\n ? (evidence.metadata as Record<string, unknown>)\n : {};\n const parentRefs = new Set(\n [\n String(args.parentNodeId),\n String(args.parent.globalId ?? \"\"),\n String(args.parent._id),\n ].filter(Boolean),\n );\n const evidenceRefs = new Set(\n [\n String(args.triggeringEvidenceId),\n String(evidence.globalId ?? \"\"),\n String(evidence._id),\n ].filter(Boolean),\n );\n\n let relation: ForkTriggerRelation | null = null;\n const linkedBeliefNodeId = String(\n evidenceMetadata.linkedBeliefNodeId ?? \"\",\n );\n if (linkedBeliefNodeId && parentRefs.has(linkedBeliefNodeId)) {\n relation = normalizeForkTriggerRelation(evidenceMetadata.evidenceRelation);\n }\n\n if (!relation) {\n for (const parentRef of parentRefs) {\n const links = await ctx.db\n .query(\"beliefEvidenceLinks\")\n .withIndex(\"by_beliefId\", (q) => q.eq(\"beliefId\", parentRef))\n .collect();\n const matched = links.find((link) => evidenceRefs.has(String(link.insightId)));\n if (matched) {\n relation = normalizeForkTriggerRelation(matched.relation);\n break;\n }\n }\n }\n\n if (!relation) {\n throwStructuredMutationError({\n message:\n \"Fork evidence must already be attached to the parent belief through an SL evidence relation.\",\n status: 409,\n code: \"CONFLICT\",\n invariantCode: \"belief.fork_requires_attached_evidence\",\n suggestion:\n \"Attach the evidence to the parent belief as supports or contradicts before forking.\",\n details: {\n parentNodeId: args.parentNodeId,\n triggeringEvidenceId: args.triggeringEvidenceId,\n },\n });\n }\n if (args.forkMode === \"supersede\" && relation !== \"contradicts\") {\n throwStructuredMutationError({\n message:\n \"Superseding fork requires contradicting evidence against the parent belief.\",\n status: 409,\n code: \"CONFLICT\",\n invariantCode: \"belief.supersede_requires_contradiction\",\n suggestion:\n \"Use forkMode='branch' for a non-replacing fork, or attach contradicting evidence before superseding.\",\n details: {\n parentNodeId: args.parentNodeId,\n triggeringEvidenceId: args.triggeringEvidenceId,\n relation,\n },\n });\n }\n\n return { evidenceNodeId: args.triggeringEvidenceId, relation };\n}\n"]}
1
+ {"version":3,"sources":["../src/epistemicBeliefs.helpers.ts","../src/epistemicBeliefs.forkEvidence.ts"],"names":[],"mappings":"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","file":"epistemicBeliefs.forkEvidence.js","sourcesContent":["/**\n * Epistemic Beliefs API\n *\n * Clean API for managing beliefs in the epistemic spine (epistemicNodes).\n * This is the NEW API that replaces beliefs.ts.\n *\n * Key differences from beliefs.ts:\n * - Writes ONLY to epistemicNodes (no dual-write)\n * - Uses epistemicNodes IDs directly (no runtime ID translation fallback)\n * - Syncs to Neo4j after mutations\n * - Follows Lucern invariants strictly\n *\n * @see /docs/epistemic-invariants/00-epistemic-invariants.md\n */\n\nimport { v } from \"convex/values\";\nimport {\n throwStructuredMutationError,\n type StructuredMutationErrorData,\n} from \"@lucern/access-control/structuredMutationError\";\nimport {\n detectTupleContradiction,\n evaluateTupleContradictionTransition,\n confidenceFromSL,\n mkOpinion,\n normalizeTupleContradictionPolicy,\n readOpinionFromRecord,\n type ConfidencePolicyConfig,\n type Opinion,\n type SLOpinion,\n type SLOperator,\n} from \"@lucern/confidence\";\nimport {\n canAudienceClassAccess,\n classFromAudienceKey,\n normalizeAudienceKey,\n} from \"@lucern/access-control/audience\";\nimport { listAudienceRegistryRows } from \"@lucern/access-control/audienceRegistry\";\nimport { getCurrentUserId } from \"@lucern/access-control/auth\";\nimport { assertSchemaEnumValue } from \"@lucern/contracts/schema-helpers/enumValidation\";\nimport { permissiveReturn } from \"@lucern/contracts/schema-helpers/validators\";\nimport {\n type BeliefLifecycleStatus,\n isPreValidationBeliefStatus,\n promoteBeliefStatusAfterScoring,\n resolveBeliefLifecycleStatus,\n} from \"./beliefLifecycle\";\nimport type { Doc, Id, MutationCtx, QueryCtx } from \"./convex\";\nimport {\n internal,\n internalMutation,\n internalQuery,\n mutation,\n query,\n} from \"./convex\";\nimport { collectConfidencePropagationDispatches } from \"./confidencePropagationDispatch\";\nimport { debugGraphPrimitiveFallback } from \"./debug\";\nimport {\n createInheritedContractRecord,\n type VerificationConfidenceTrigger,\n} from \"./epistemicContractHelpers\";\nimport { scheduleEmbeddingGeneration } from \"./embeddingTrigger\";\nimport { generateGlobalId } from \"./globalId\";\nimport { computeLogicalRole } from \"./logicalRoleInference\";\nimport { resolveGraphPrimitivesAppResolvers } from \"./resolvers\";\nimport { optionalScopeArgs, resolveTopicProjectScope } from \"./topicScope\";\nimport {\n assertTenantPackWorkspaceMutationAllowed,\n assertWorkspaceScopedEpistemicNodeScope,\n nodeMatchesWorkspaceReasoningScope,\n resolveNodeScopeForWorkspaceIsolation,\n resolveRuntimePackMutationContext,\n} from \"./workspaceIsolation\";\n\n// All IDs now use epistemicNodes exclusively\nexport const insightIdUnion = v.id(\"epistemicNodes\");\nconst DEFAULT_PROJECT_BELIEF_LIMIT = 250;\nexport const MAX_PROJECT_BELIEF_LIMIT = 1000;\nexport const optionalBeliefScopeArgs = optionalScopeArgs;\n\n// CQ.11: the structured-refusal primitive is the single shared copy in\n// `@lucern/access-control/structuredMutationError` (the same home the shared\n// access gate throws from). Re-exported here so the graph-primitives modules\n// that imported it from this helper keep their import path unchanged.\nexport { throwStructuredMutationError };\nexport type { StructuredMutationErrorData };\n\ntype BeliefScopeArgs = {\n projectId?: string | null;\n topicId?: string | null;\n};\n\ntype EpistemicNodeOpinionDoc = Doc<\"epistemicNodes\"> & {\n opinion_a?: number;\n opinion_b?: number;\n opinion_d?: number;\n opinion_u?: number;\n tupleContradicted?: boolean;\n userId?: string;\n metadata?: Record<string, unknown> | null;\n};\n\ntype EpistemicIndexQuery = {\n eq(field: string, value: unknown): EpistemicIndexQuery;\n gt(field: string, value: unknown): EpistemicIndexQuery;\n lt(field: string, value: unknown): EpistemicIndexQuery;\n field(field: string): string;\n};\n\ntype EpistemicQueryChain<T> = {\n withIndex(\n _name: string,\n callback: (q: EpistemicIndexQuery) => EpistemicIndexQuery,\n ): EpistemicQueryChain<T>;\n order(direction: \"asc\" | \"desc\"): EpistemicQueryChain<T>;\n filter(\n callback: (q: EpistemicIndexQuery) => EpistemicIndexQuery,\n ): EpistemicQueryChain<T>;\n first(): Promise<T | null>;\n collect(): Promise<T[]>;\n take(limit: number): Promise<T[]>;\n};\n\ntype EpistemicBeliefCtx = QueryCtx & {\n db: {\n query<T = unknown>(_table: string): EpistemicQueryChain<T>;\n get<T = unknown>(id: Id<string>): Promise<T | null>;\n };\n scheduler: {\n runAfter(\n _delayMs: number,\n _handler: unknown,\n _args: unknown,\n ): Promise<unknown>;\n };\n};\n\ntype AudienceClass = \"internal\" | \"restricted_external\" | \"public\";\n\nconst DEFAULT_CONFIDENCE_POLICY: ConfidencePolicyConfig = {\n scoringMode: \"after_worktree\",\n tupleContradiction: normalizeTupleContradictionPolicy(),\n};\n\nexport type BeliefConfidenceTrigger =\n | \"initial\"\n | \"evidence_added\"\n | \"evidence_removed\"\n | \"contradiction_detected\"\n | \"contradiction_resolved\"\n | \"propagation\"\n | \"agent_assessment\"\n | \"worktree_outcome\"\n | \"worktree_completed\"\n // SL-specific triggers\n | \"fusion\"\n | \"discount\"\n | \"deduction\"\n | \"backfill_synthetic\"\n | VerificationConfidenceTrigger;\n\n\nexport function readFiniteNumber(value: unknown): number | undefined {\n return typeof value === \"number\" && Number.isFinite(value)\n ? value\n : undefined;\n}\n\nfunction clamp01(value: number): number {\n return Math.max(0, Math.min(1, value));\n}\n\nexport function assertBaseRateInRange(baseRate: number, field = \"baseRate\"): number {\n if (baseRate < 0 || baseRate > 1) {\n throwStructuredMutationError({\n message: `${field} must be within [0, 1].`,\n status: 400,\n code: \"INVALID_ARGUMENT\",\n invariantCode: \"request.valid_shape\",\n suggestion: `Clamp ${field} into the inclusive [0, 1] interval.`,\n details: { field, baseRate },\n });\n }\n return baseRate;\n}\n\nexport function buildBeliefConfidenceRow(args: {\n beliefId: Id<\"epistemicNodes\">;\n belief: number;\n disbelief: number;\n uncertainty: number;\n baseRate: number;\n trigger: BeliefConfidenceTrigger;\n rationale?: string;\n assessedBy: string;\n assessedAt: number;\n slOperator?: SLOperator;\n triggeringEvidenceId?: Id<\"epistemicNodes\">;\n triggeringContradictionId?: Id<\"contradictions\">;\n triggeringWorktreeId?: string;\n}) {\n return {\n beliefId: args.beliefId,\n confidence: confidenceFromSL(\n args.belief,\n args.disbelief,\n args.uncertainty,\n args.baseRate,\n ),\n belief: args.belief,\n disbelief: args.disbelief,\n uncertainty: args.uncertainty,\n baseRate: args.baseRate,\n slOperator: args.slOperator ?? (\"prior_seed\" as const),\n trigger: args.trigger,\n ...(args.rationale ? { rationale: args.rationale } : {}),\n assessedBy: args.assessedBy,\n assessedAt: args.assessedAt,\n ...(args.triggeringEvidenceId\n ? {\n triggeringEvidenceId: args.triggeringEvidenceId,\n triggeringEvidenceIds: [String(args.triggeringEvidenceId)],\n }\n : {}),\n ...(args.triggeringContradictionId\n ? {\n triggeringContradictionId: args.triggeringContradictionId,\n }\n : {}),\n ...(args.triggeringWorktreeId\n ? { triggeringWorktreeId: args.triggeringWorktreeId }\n : {}),\n };\n}\n\ntype BeliefStatusResult =\n | { success: true }\n | { success: false; message: \"Evidence node not found\" };\n\nexport function buildBeliefStatusSuccessResult(): BeliefStatusResult {\n return { success: true };\n}\n\nexport function buildBeliefEvidenceNotFoundResult(): BeliefStatusResult {\n const result = {} as Extract<BeliefStatusResult, { success: false }>;\n result.success = false;\n result.message = \"Evidence node not found\";\n return result;\n}\n\nexport function deriveSyntheticBackfillOpinion(\n source: Record<string, unknown>,\n): SLOpinion {\n const belief =\n readFiniteNumber(source.opinion_b) ?? readFiniteNumber(source.belief);\n const disbelief =\n readFiniteNumber(source.opinion_d) ?? readFiniteNumber(source.disbelief);\n const uncertainty =\n readFiniteNumber(source.opinion_u) ?? readFiniteNumber(source.uncertainty);\n const baseRate =\n readFiniteNumber(source.opinion_a) ?? readFiniteNumber(source.baseRate);\n\n if (\n belief !== undefined ||\n disbelief !== undefined ||\n uncertainty !== undefined ||\n baseRate !== undefined\n ) {\n try {\n return readOpinionFromRecord(source);\n } catch (error) {\n debugGraphPrimitiveFallback(\n \"[epistemicBeliefs] Failed to decode legacy belief opinion\",\n {\n error,\n },\n );\n return mkOpinion(0, 0, 1, 0.5);\n }\n }\n\n const confidence = clamp01(readFiniteNumber(source.confidence) ?? 0);\n return mkOpinion(confidence, 1 - confidence, 0, 0.5);\n}\n\nexport function clampBeliefLimit(\n limit: number | undefined,\n fallback = DEFAULT_PROJECT_BELIEF_LIMIT,\n): number {\n if (!Number.isFinite(limit)) {\n return fallback;\n }\n return Math.max(\n 1,\n Math.min(Math.floor(limit as number), MAX_PROJECT_BELIEF_LIMIT),\n );\n}\n\nexport function readTupleContradictedFlag(value: unknown): boolean | undefined {\n return typeof value === \"boolean\" ? value : undefined;\n}\n\nexport function readBeliefOpinionSnapshot(\n node: Doc<\"epistemicNodes\">,\n metadata: Record<string, unknown>,\n): SLOpinion {\n try {\n return readOpinionFromRecord({\n ...metadata,\n opinion_b: (node as any).opinion_b,\n opinion_d: (node as any).opinion_d,\n opinion_u: (node as any).opinion_u,\n opinion_a: (node as any).opinion_a,\n });\n } catch (error) {\n debugGraphPrimitiveFallback(\n \"[epistemicBeliefs] Failed to read belief opinion snapshot\",\n {\n error,\n beliefId: node._id,\n },\n );\n return mkOpinion(0, 0, 1, 0.5);\n }\n}\n\nexport function deriveTupleContradictionSeverity(\n node: Doc<\"epistemicNodes\">,\n): \"critical\" | \"significant\" | \"minor\" {\n const metadata = (node.metadata || {}) as Record<string, unknown>;\n const criticality =\n typeof metadata.criticality === \"string\" ? metadata.criticality : undefined;\n\n if (criticality === \"blocking\") {\n return \"critical\";\n }\n if (criticality === \"supporting\") {\n return \"minor\";\n }\n return \"significant\";\n}\n\nexport function formatTupleContradictionDescription(args: {\n opinion: Opinion;\n policy: ConfidencePolicyConfig[\"tupleContradiction\"];\n}): string {\n return `Tuple-space contradiction detected: b=${args.opinion.b.toFixed(2)} > ${args.policy.beliefThreshold.toFixed(2)} and d=${args.opinion.d.toFixed(2)} > ${args.policy.disbeliefThreshold.toFixed(2)}.`;\n}\n\n// =============================================================================\n// HELPERS\n// =============================================================================\n\nexport function generateContentHash(text: string): string {\n const content = `belief:${text.trim().toLowerCase().replace(/\\s+/g, \" \")}`;\n let hash = 5381;\n for (let i = 0; i < content.length; i++) {\n hash = (hash << 5) + hash + content.charCodeAt(i);\n hash &= hash;\n }\n return Math.abs(hash).toString(16).padStart(8, \"0\");\n}\n\nexport function resolveBeliefWorktreeId(\n metadata: Record<string, unknown> | undefined,\n): string | undefined {\n const worktreeId = metadata?.worktreeId;\n if (typeof worktreeId === \"string\" && worktreeId.trim().length > 0) {\n return worktreeId;\n }\n\n const sprintId = metadata?.sprintId;\n return typeof sprintId === \"string\" && sprintId.trim().length > 0\n ? sprintId\n : undefined;\n}\n\n// Map pillar names to valid pillar literals\ntype ValidPillar =\n | \"market\"\n | \"competition\"\n | \"product\"\n | \"team\"\n | \"financials\"\n | \"regulatory\"\n | \"timing\"\n | \"customer\"\n | \"technology\"\n | \"distribution\"\n | \"deal\"\n | \"risks\"\n | \"other\";\n\nexport function normalizePillar(pillar?: string): ValidPillar {\n if (!pillar) {\n return \"other\";\n }\n const lower = pillar.toLowerCase();\n const validPillars: ValidPillar[] = [\n \"market\",\n \"competition\",\n \"product\",\n \"team\",\n \"financials\",\n \"regulatory\",\n \"timing\",\n \"customer\",\n \"technology\",\n \"distribution\",\n \"deal\",\n \"risks\",\n ];\n return (validPillars.find((p) => lower.includes(p)) ||\n \"other\") as ValidPillar;\n}\n\nexport async function markBeliefGraphDirty(\n ctx: EpistemicBeliefCtx,\n scope: { projectId?: string | null; topicId?: string | null },\n): Promise<void> {\n const projectId =\n typeof scope.projectId === \"string\" && scope.projectId.trim().length > 0\n ? scope.projectId\n : undefined;\n const topicId =\n typeof scope.topicId === \"string\" && scope.topicId.trim().length > 0\n ? scope.topicId\n : undefined;\n\n if (!projectId && !topicId) {\n return;\n }\n\n if (projectId) {\n await ctx.scheduler.runAfter(\n 0,\n internal.graphAnalysisCache.markCacheStaleInternal,\n { projectId },\n );\n }\n if (topicId) {\n await ctx.scheduler.runAfter(\n 0,\n internal.graphAnalysisCache.markCacheStaleByTopic,\n { topicId },\n );\n }\n\n await resolveGraphPrimitivesAppResolvers(ctx).patchProject(\n ctx,\n topicId ?? projectId!,\n {\n lastActivityAt: Date.now(),\n },\n );\n}\n\nexport async function resolveBeliefScopeOrNull(\n ctx: QueryCtx,\n args: BeliefScopeArgs,\n) {\n if (!args.projectId && !args.topicId) {\n return null;\n }\n\n try {\n return await resolveTopicProjectScope(ctx, {\n projectId: args.projectId ?? undefined,\n topicId: args.topicId ?? undefined,\n });\n } catch (error) {\n debugGraphPrimitiveFallback(\n \"[epistemicBeliefs] Failed to resolve belief scope\",\n {\n error,\n projectId: args.projectId,\n topicId: args.topicId,\n },\n );\n return null;\n }\n}\n\nexport async function getBeliefNodesForScope(\n ctx: EpistemicBeliefCtx,\n scope: Awaited<ReturnType<typeof resolveTopicProjectScope>>,\n args?: { scanLimit?: number; status?: string },\n) {\n const baseQuery = ctx.db\n .query(\"epistemicNodes\")\n .withIndex(\"by_topic_type\", (q: any) =>\n q.eq(\"topicId\", scope.topicId).eq(\"nodeType\", \"belief\"),\n );\n const nodes: EpistemicNodeOpinionDoc[] =\n typeof args?.scanLimit === \"number\"\n ? (await baseQuery.order(\"desc\").take(args.scanLimit)) as EpistemicNodeOpinionDoc[]\n : (await baseQuery.collect()) as EpistemicNodeOpinionDoc[];\n const scopedNodes = nodes.filter((node: Doc<\"epistemicNodes\">) =>\n nodeMatchesWorkspaceReasoningScope(node, scope),\n );\n\n if (!args?.status) {\n return scopedNodes;\n }\n\n return scopedNodes.filter((node: EpistemicNodeOpinionDoc) => node.status === args.status);\n}\n\nexport function createBeliefAudienceResolver(\n registryRows: Array<{\n audienceKey?: string | null;\n audienceClass: AudienceClass;\n }>,\n) {\n const audienceClassByKey = new Map<string, AudienceClass>(\n registryRows.map((row) => [\n normalizeAudienceKey(row.audienceKey),\n row.audienceClass,\n ]),\n );\n\n return (\n audienceKey: string | undefined | null,\n fallback: AudienceClass,\n ): AudienceClass => {\n const key = normalizeAudienceKey(audienceKey);\n if (!key) {\n return fallback;\n }\n return (\n audienceClassByKey.get(key) ??\n (classFromAudienceKey(key, fallback) as AudienceClass)\n );\n };\n}\n\nexport function flattenBeliefNode(node: Doc<\"epistemicNodes\">) {\n const meta = (node.metadata || {}) as Record<string, unknown>;\n const worktreeId = resolveBeliefWorktreeId(meta);\n const tupleContradicted =\n readTupleContradictedFlag(\n (node as EpistemicNodeOpinionDoc).tupleContradicted,\n ) ??\n readTupleContradictedFlag(meta.tupleContradicted) ??\n false;\n\n return {\n _id: node._id,\n _epistemicNodeId: node._id,\n _creationTime: node._creationTime,\n belief: node.canonicalText,\n formulation: node.canonicalText,\n projectId: node.projectId,\n topicId: node.topicId,\n userId: (node as EpistemicNodeOpinionDoc).userId || node.createdBy || \"\",\n confidence: (meta.confidence as \"high\" | \"medium\" | \"low\") || \"untested\",\n status: node.status,\n beliefStatus: resolveBeliefStatus(node, meta),\n topic: (meta.topic as string) || (meta.pillar as string) || \"other\",\n pillar: (meta.pillar as string) || (meta.topic as string) || \"\",\n category: (meta.category as string) || \"\",\n subcategory: (meta.subcategory as string) || \"\",\n categoryIcon: (meta.categoryIcon as string) || \"\",\n supportingEvidence: (meta.supportingEvidenceIds as string[]) || [],\n contradictingEvidence: (meta.contradictingEvidenceIds as string[]) || [],\n testingQuestions: (meta.testingQuestionIds as string[]) || [],\n linkedInsights: (meta.linkedInsightIds as string[]) || [],\n createdAt: node.createdAt,\n updatedAt: node.updatedAt || node.createdAt,\n tupleContradicted,\n sprintId: (meta.sprintId as string) || undefined,\n worktreeId,\n sourceBeliefIds: (meta.sourceBeliefIds as string[]) || undefined,\n criticality:\n (meta.criticality as\n | \"critical\"\n | \"supporting\"\n | \"nice_to_have\"\n | \"unanalyzed\"\n | \"blocking\"\n | \"important\") || \"unanalyzed\",\n rationale: (meta.rationale as string) || \"\",\n audienceLabel: node.audienceLabel,\n policyTags: node.policyTags,\n sensitivityTier: node.sensitivityTier,\n exportClass: node.exportClass,\n anonymizationClass: node.anonymizationClass,\n };\n}\n\nexport function resolveBeliefStatus(\n node: {\n beliefStatus?: unknown;\n confidence?: unknown;\n predictionMeta?: unknown;\n },\n metadata: Record<string, unknown>,\n): BeliefLifecycleStatus {\n return resolveBeliefLifecycleStatus({\n beliefStatus: node.beliefStatus,\n confidence: node.confidence,\n predictionMeta: node.predictionMeta,\n metadata,\n });\n}\n\nexport async function hasCompletedWorktreeForBelief(\n ctx: EpistemicBeliefCtx,\n beliefNodeId: Id<\"epistemicNodes\">,\n): Promise<boolean> {\n // Check if the belief is linked to a completed worktree via worktreeBeliefCluster\n const clusterMembership = await ctx.db\n .query(\"worktreeBeliefCluster\")\n .withIndex(\"by_belief\", (q: any) => q.eq(\"beliefId\", beliefNodeId))\n .collect();\n for (const membership of clusterMembership) {\n const worktree = await ctx.db.get(membership.worktreeId);\n if (worktree?.status === \"completed\" || worktree?.status === \"merged\") {\n return true;\n }\n }\n return false;\n}\n\nexport async function getActiveConfidencePolicy(\n ctx: EpistemicBeliefCtx,\n): Promise<ConfidencePolicyConfig> {\n try {\n const activeConfig = await ctx.db\n .query(\"logicSprintScoring\")\n .withIndex(\"by_active\", (q: any) => q.eq(\"isActive\", true))\n .first();\n\n return {\n scoringMode:\n activeConfig?.confidencePolicy === \"always\"\n ? \"always\"\n : DEFAULT_CONFIDENCE_POLICY.scoringMode,\n tupleContradiction: normalizeTupleContradictionPolicy(\n activeConfig?.tupleContradictionPolicy as\n | Partial<ConfidencePolicyConfig[\"tupleContradiction\"]>\n | undefined,\n ),\n };\n } catch (error) {\n debugGraphPrimitiveFallback(\n \"[epistemicBeliefs] Failed to load active confidence policy\",\n {\n error,\n },\n );\n // K-tier/component environments do not carry logicSprintScoring.\n return DEFAULT_CONFIDENCE_POLICY;\n }\n}\n\nexport async function requireAuthenticatedUserId(ctx: {\n auth: { getUserIdentity: () => Promise<unknown> };\n}): Promise<string> {\n const userId = await getCurrentUserId(\n ctx as Parameters<typeof getCurrentUserId>[0],\n );\n if (!userId) {\n throwStructuredMutationError({\n message: \"Authentication required.\",\n status: 401,\n code: \"AUTHENTICATION_REQUIRED\",\n invariantCode: \"auth.required\",\n suggestion:\n \"Provide a valid bearer token before invoking belief mutations.\",\n });\n }\n return userId;\n}\n","/** Evidence-gated fork precondition helpers. */\n\nimport type { Doc, Id, MutationCtx } from \"./convex\";\nimport { throwStructuredMutationError } from \"./epistemicBeliefs.helpers\";\n\nexport type ForkMode = \"supersede\" | \"branch\";\nexport type ForkTriggerRelation = \"supports\" | \"contradicts\";\n\nfunction normalizeForkTriggerRelation(\n value: unknown,\n): ForkTriggerRelation | null {\n if (value === \"supports\" || value === \"supporting\") {\n return \"supports\";\n }\n if (value === \"contradicts\" || value === \"contradicting\") {\n return \"contradicts\";\n }\n return null;\n}\n\nexport async function resolveForkTriggerEvidence(\n ctx: MutationCtx,\n args: {\n parentNodeId: Id<\"epistemicNodes\">;\n parent: Doc<\"epistemicNodes\">;\n triggeringEvidenceId: Id<\"epistemicNodes\">;\n forkMode: ForkMode;\n },\n): Promise<{ evidenceNodeId: Id<\"epistemicNodes\">; relation: ForkTriggerRelation }> {\n const evidence = await ctx.db.get(args.triggeringEvidenceId);\n if (!evidence || evidence.nodeType !== \"evidence\") {\n throwStructuredMutationError({\n message: \"Fork requires an existing evidence node.\",\n status: 400,\n code: \"INVALID_ARGUMENT\",\n invariantCode: \"belief.fork_requires_evidence\",\n suggestion:\n \"Create or link evidence first, then fork with triggeringEvidenceId.\",\n details: { triggeringEvidenceId: args.triggeringEvidenceId },\n });\n }\n if (evidence.topicId && evidence.topicId !== args.parent.topicId) {\n throwStructuredMutationError({\n message: \"Fork evidence belongs to a different topic scope.\",\n status: 400,\n code: \"INVALID_ARGUMENT\",\n invariantCode: \"belief.fork_evidence_scope\",\n suggestion: \"Use evidence from the same topic/workspace scope as the parent belief.\",\n details: {\n parentNodeId: args.parentNodeId,\n triggeringEvidenceId: args.triggeringEvidenceId,\n },\n });\n }\n\n const evidenceMetadata =\n evidence.metadata && typeof evidence.metadata === \"object\"\n ? (evidence.metadata as Record<string, unknown>)\n : {};\n const parentRefs = new Set(\n [\n String(args.parentNodeId),\n String(args.parent.globalId ?? \"\"),\n String(args.parent._id),\n ].filter(Boolean),\n );\n const evidenceRefs = new Set(\n [\n String(args.triggeringEvidenceId),\n String(evidence.globalId ?? \"\"),\n String(evidence._id),\n ].filter(Boolean),\n );\n\n let relation: ForkTriggerRelation | null = null;\n const linkedBeliefNodeId = String(\n evidenceMetadata.linkedBeliefNodeId ?? \"\",\n );\n if (linkedBeliefNodeId && parentRefs.has(linkedBeliefNodeId)) {\n relation = normalizeForkTriggerRelation(evidenceMetadata.evidenceRelation);\n }\n\n if (!relation) {\n for (const parentRef of parentRefs) {\n const links = await ctx.db\n .query(\"beliefEvidenceLinks\")\n .withIndex(\"by_beliefId\", (q) => q.eq(\"beliefId\", parentRef))\n .collect();\n const matched = links.find((link) => evidenceRefs.has(String(link.insightId)));\n if (matched) {\n relation = normalizeForkTriggerRelation(matched.relation);\n break;\n }\n }\n }\n\n if (!relation) {\n throwStructuredMutationError({\n message:\n \"Fork evidence must already be attached to the parent belief through an SL evidence relation.\",\n status: 409,\n code: \"CONFLICT\",\n invariantCode: \"belief.fork_requires_attached_evidence\",\n suggestion:\n \"Attach the evidence to the parent belief as supports or contradicts before forking.\",\n details: {\n parentNodeId: args.parentNodeId,\n triggeringEvidenceId: args.triggeringEvidenceId,\n },\n });\n }\n if (args.forkMode === \"supersede\" && relation !== \"contradicts\") {\n throwStructuredMutationError({\n message:\n \"Superseding fork requires contradicting evidence against the parent belief.\",\n status: 409,\n code: \"CONFLICT\",\n invariantCode: \"belief.supersede_requires_contradiction\",\n suggestion:\n \"Use forkMode='branch' for a non-replacing fork, or attach contradicting evidence before superseding.\",\n details: {\n parentNodeId: args.parentNodeId,\n triggeringEvidenceId: args.triggeringEvidenceId,\n relation,\n },\n });\n }\n\n return { evidenceNodeId: args.triggeringEvidenceId, relation };\n}\n"]}
@@ -1,5 +1,6 @@
1
1
  import { r as resolveTopicProjectScope, T as TopicProjectScope } from './topicScope-7zhyeGl7.js';
2
2
  import * as convex_values from 'convex/values';
3
+ export { StructuredMutationErrorData, throwStructuredMutationError } from '@lucern/access-control/structuredMutationError';
3
4
  import { VerificationConfidenceTrigger, SLOperator, SLOpinion, Opinion, ConfidencePolicyConfig } from '@lucern/confidence';
4
5
  import { B as BeliefLifecycleStatus } from './beliefLifecycle-CXwdDw5e.js';
5
6
  import { Id, Doc, QueryCtx } from './convex.js';
@@ -10,21 +11,7 @@ declare const optionalBeliefScopeArgs: {
10
11
  readonly projectId: convex_values.VString<string | undefined, "optional">;
11
12
  readonly topicId: convex_values.VString<string | undefined, "optional">;
12
13
  };
13
- /**
14
- * Boundary-safe payload carried inside a {@link ConvexError}. Mirrors the kernel
15
- * copy in packages/reasoning-kernel/src/adapters/beliefs.shared.ts so structured
16
- * refusals from the graph-primitives module survive the Convex client boundary
17
- * (only `ConvexError.data` is preserved across serialization).
18
- */
19
- type StructuredMutationErrorData = {
20
- structuredMutationError: true;
21
- message: string;
22
- status: number;
23
- code: string;
24
- invariantCode?: string;
25
- suggestion?: string;
26
- details?: unknown;
27
- };
14
+
28
15
  type BeliefScopeArgs = {
29
16
  projectId?: string | null;
30
17
  topicId?: string | null;
@@ -54,14 +41,6 @@ type EpistemicBeliefCtx = QueryCtx & {
54
41
  };
55
42
  type AudienceClass = "internal" | "restricted_external" | "public";
56
43
  type BeliefConfidenceTrigger = "initial" | "evidence_added" | "evidence_removed" | "contradiction_detected" | "contradiction_resolved" | "propagation" | "agent_assessment" | "worktree_outcome" | "worktree_completed" | "fusion" | "discount" | "deduction" | "backfill_synthetic" | VerificationConfidenceTrigger;
57
- declare function throwStructuredMutationError(args: {
58
- message: string;
59
- status: number;
60
- code: string;
61
- invariantCode?: string;
62
- suggestion?: string;
63
- details?: unknown;
64
- }): never;
65
44
  declare function readFiniteNumber(value: unknown): number | undefined;
66
45
  declare function assertBaseRateInRange(baseRate: number, field?: string): number;
67
46
  declare function buildBeliefConfidenceRow(args: {
@@ -176,8 +155,5 @@ declare function requireAuthenticatedUserId(ctx: {
176
155
  getUserIdentity: () => Promise<unknown>;
177
156
  };
178
157
  }): Promise<string>;
179
- declare function requireProjectWriteAccess(ctx: {
180
- db: unknown;
181
- }, projectId: string, userId: string): Promise<void>;
182
158
 
183
- export { type BeliefConfidenceTrigger, MAX_PROJECT_BELIEF_LIMIT, type StructuredMutationErrorData, assertBaseRateInRange, buildBeliefConfidenceRow, buildBeliefEvidenceNotFoundResult, buildBeliefStatusSuccessResult, clampBeliefLimit, createBeliefAudienceResolver, deriveSyntheticBackfillOpinion, deriveTupleContradictionSeverity, flattenBeliefNode, formatTupleContradictionDescription, generateContentHash, getActiveConfidencePolicy, getBeliefNodesForScope, hasCompletedWorktreeForBelief, insightIdUnion, markBeliefGraphDirty, normalizePillar, optionalBeliefScopeArgs, readBeliefOpinionSnapshot, readFiniteNumber, readTupleContradictedFlag, requireAuthenticatedUserId, requireProjectWriteAccess, resolveBeliefScopeOrNull, resolveBeliefStatus, resolveBeliefWorktreeId, throwStructuredMutationError };
159
+ export { type BeliefConfidenceTrigger, MAX_PROJECT_BELIEF_LIMIT, assertBaseRateInRange, buildBeliefConfidenceRow, buildBeliefEvidenceNotFoundResult, buildBeliefStatusSuccessResult, clampBeliefLimit, createBeliefAudienceResolver, deriveSyntheticBackfillOpinion, deriveTupleContradictionSeverity, flattenBeliefNode, formatTupleContradictionDescription, generateContentHash, getActiveConfidencePolicy, getBeliefNodesForScope, hasCompletedWorktreeForBelief, insightIdUnion, markBeliefGraphDirty, normalizePillar, optionalBeliefScopeArgs, readBeliefOpinionSnapshot, readFiniteNumber, readTupleContradictedFlag, requireAuthenticatedUserId, resolveBeliefScopeOrNull, resolveBeliefStatus, resolveBeliefWorktreeId };
@@ -1,6 +1,7 @@
1
- import { v, ConvexError } from 'convex/values';
1
+ import { v } from 'convex/values';
2
+ import { throwStructuredMutationError } from '@lucern/access-control/structuredMutationError';
3
+ export { throwStructuredMutationError } from '@lucern/access-control/structuredMutationError';
2
4
  import { normalizeTupleContradictionPolicy, confidenceFromSL, readOpinionFromRecord, mkOpinion } from '@lucern/confidence';
3
- import { checkProjectAccess } from '@lucern/access-control/access';
4
5
  import { normalizeAudienceKey, classFromAudienceKey } from '@lucern/access-control/audience';
5
6
  import { getCurrentUserId } from '@lucern/access-control/auth';
6
7
  import { componentsGeneric, anyApi } from 'convex/server';
@@ -777,26 +778,6 @@ var DEFAULT_CONFIDENCE_POLICY = {
777
778
  scoringMode: "after_worktree",
778
779
  tupleContradiction: normalizeTupleContradictionPolicy()
779
780
  };
780
- function throwStructuredMutationError(args) {
781
- const data = {
782
- structuredMutationError: true,
783
- message: args.message,
784
- status: args.status,
785
- code: args.code,
786
- invariantCode: args.invariantCode,
787
- suggestion: args.suggestion,
788
- details: args.details
789
- };
790
- const error = new ConvexError(
791
- data
792
- );
793
- error.status = args.status;
794
- error.code = args.code;
795
- error.invariantCode = args.invariantCode;
796
- error.suggestion = args.suggestion;
797
- error.details = args.details;
798
- throw error;
799
- }
800
781
  function readFiniteNumber(value) {
801
782
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
802
783
  }
@@ -1130,24 +1111,7 @@ async function requireAuthenticatedUserId(ctx) {
1130
1111
  }
1131
1112
  return userId;
1132
1113
  }
1133
- async function requireProjectWriteAccess(ctx, projectId, userId) {
1134
- const hasAccess = await checkProjectAccess(
1135
- ctx,
1136
- projectId,
1137
- userId
1138
- );
1139
- if (!hasAccess) {
1140
- throwStructuredMutationError({
1141
- message: `Project write access denied for topic ${projectId}.`,
1142
- status: 403,
1143
- code: "PROJECT_ACCESS_DENIED",
1144
- invariantCode: "policy.scope_required",
1145
- suggestion: "The acting principal lacks project-write access to this topic. Request a topic grant (or, if the principal created this topic, run the creator-grant backfill) and retry.",
1146
- details: { topicId: projectId, principalId: userId }
1147
- });
1148
- }
1149
- }
1150
1114
 
1151
- export { MAX_PROJECT_BELIEF_LIMIT, assertBaseRateInRange, buildBeliefConfidenceRow, buildBeliefEvidenceNotFoundResult, buildBeliefStatusSuccessResult, clampBeliefLimit, createBeliefAudienceResolver, deriveSyntheticBackfillOpinion, deriveTupleContradictionSeverity, flattenBeliefNode, formatTupleContradictionDescription, generateContentHash, getActiveConfidencePolicy, getBeliefNodesForScope, hasCompletedWorktreeForBelief, insightIdUnion, markBeliefGraphDirty, normalizePillar, optionalBeliefScopeArgs, readBeliefOpinionSnapshot, readFiniteNumber, readTupleContradictedFlag, requireAuthenticatedUserId, requireProjectWriteAccess, resolveBeliefScopeOrNull, resolveBeliefStatus, resolveBeliefWorktreeId, throwStructuredMutationError };
1115
+ export { MAX_PROJECT_BELIEF_LIMIT, assertBaseRateInRange, buildBeliefConfidenceRow, buildBeliefEvidenceNotFoundResult, buildBeliefStatusSuccessResult, clampBeliefLimit, createBeliefAudienceResolver, deriveSyntheticBackfillOpinion, deriveTupleContradictionSeverity, flattenBeliefNode, formatTupleContradictionDescription, generateContentHash, getActiveConfidencePolicy, getBeliefNodesForScope, hasCompletedWorktreeForBelief, insightIdUnion, markBeliefGraphDirty, normalizePillar, optionalBeliefScopeArgs, readBeliefOpinionSnapshot, readFiniteNumber, readTupleContradictedFlag, requireAuthenticatedUserId, resolveBeliefScopeOrNull, resolveBeliefStatus, resolveBeliefWorktreeId };
1152
1116
  //# sourceMappingURL=epistemicBeliefs.helpers.js.map
1153
1117
  //# sourceMappingURL=epistemicBeliefs.helpers.js.map