@lucern/graph-primitives 1.0.23 → 1.0.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/beliefDecay.js +3 -0
- package/dist/beliefDecay.js.map +1 -1
- package/dist/beliefEvidenceLinks.js +15 -3
- package/dist/beliefEvidenceLinks.js.map +1 -1
- package/dist/beliefEvidenceLinks.operational.js +12 -3
- package/dist/beliefEvidenceLinks.operational.js.map +1 -1
- package/dist/entityCanonicalMatch.d.ts +40 -0
- package/dist/entityCanonicalMatch.js +33 -0
- package/dist/entityCanonicalMatch.js.map +1 -0
- package/dist/entityLifecycle.js +62 -37
- package/dist/entityLifecycle.js.map +1 -1
- package/dist/epistemicAnswers.js.map +1 -1
- package/dist/epistemicBeliefs.admin.js +17 -6
- package/dist/epistemicBeliefs.admin.js.map +1 -1
- package/dist/epistemicBeliefs.backfills.js +13 -2
- package/dist/epistemicBeliefs.backfills.js.map +1 -1
- package/dist/epistemicBeliefs.confidence.js +17 -6
- package/dist/epistemicBeliefs.confidence.js.map +1 -1
- package/dist/epistemicBeliefs.core.js +32 -9
- package/dist/epistemicBeliefs.core.js.map +1 -1
- package/dist/epistemicBeliefs.forkEvidence.js +13 -2
- package/dist/epistemicBeliefs.forkEvidence.js.map +1 -1
- package/dist/epistemicBeliefs.helpers.d.ts +16 -1
- package/dist/epistemicBeliefs.helpers.js +20 -6
- package/dist/epistemicBeliefs.helpers.js.map +1 -1
- package/dist/epistemicBeliefs.internal.js +28 -5
- package/dist/epistemicBeliefs.internal.js.map +1 -1
- package/dist/epistemicBeliefs.js +32 -9
- package/dist/epistemicBeliefs.js.map +1 -1
- package/dist/epistemicBeliefs.lifecycle.js +17 -6
- package/dist/epistemicBeliefs.lifecycle.js.map +1 -1
- package/dist/epistemicBeliefs.links.js +29 -9
- package/dist/epistemicBeliefs.links.js.map +1 -1
- package/dist/epistemicBeliefs.topicAnchor.js +12 -3
- package/dist/epistemicBeliefs.topicAnchor.js.map +1 -1
- package/dist/epistemicContracts.evaluators.js +17 -6
- package/dist/epistemicContracts.evaluators.js.map +1 -1
- package/dist/epistemicContracts.handlers.js +17 -6
- package/dist/epistemicContracts.handlers.js.map +1 -1
- package/dist/epistemicContracts.js +17 -6
- package/dist/epistemicContracts.js.map +1 -1
- package/dist/epistemicEdges.handlers.js +12 -3
- package/dist/epistemicEdges.handlers.js.map +1 -1
- package/dist/epistemicEdges.helpers.d.ts +2 -2
- package/dist/epistemicEdges.js +15 -3
- package/dist/epistemicEdges.js.map +1 -1
- package/dist/epistemicEdges.mutations.js +3 -0
- package/dist/epistemicEdges.mutations.js.map +1 -1
- package/dist/epistemicEvidence.js +15 -3
- package/dist/epistemicEvidence.js.map +1 -1
- package/dist/epistemicEvidenceHelpers.js +3 -0
- package/dist/epistemicEvidenceHelpers.js.map +1 -1
- package/dist/epistemicEvidenceMutations.js +15 -3
- package/dist/epistemicEvidenceMutations.js.map +1 -1
- package/dist/epistemicEvidenceQueries.js +3 -0
- package/dist/epistemicEvidenceQueries.js.map +1 -1
- package/dist/epistemicHelpers.js.map +1 -1
- package/dist/epistemicInsert.js +12 -3
- package/dist/epistemicInsert.js.map +1 -1
- package/dist/epistemicNodeCreation.js.map +1 -1
- package/dist/epistemicNodes.internal.js.map +1 -1
- package/dist/epistemicNodes.js.map +1 -1
- package/dist/epistemicNodes.mutations.js.map +1 -1
- package/dist/epistemicNodes.validators.d.ts +2 -2
- package/dist/epistemicQuestions.conviction.js +3 -0
- package/dist/epistemicQuestions.conviction.js.map +1 -1
- package/dist/epistemicQuestions.create.js +3 -0
- package/dist/epistemicQuestions.create.js.map +1 -1
- package/dist/epistemicQuestions.evidence.js +3 -0
- package/dist/epistemicQuestions.evidence.js.map +1 -1
- package/dist/epistemicQuestions.helpers.js +3 -0
- package/dist/epistemicQuestions.helpers.js.map +1 -1
- package/dist/epistemicQuestions.js +3 -0
- package/dist/epistemicQuestions.js.map +1 -1
- package/dist/epistemicQuestions.lifecycle.js +3 -0
- package/dist/epistemicQuestions.lifecycle.js.map +1 -1
- package/dist/epistemicQuestions.queries.js +3 -0
- package/dist/epistemicQuestions.queries.js.map +1 -1
- package/dist/epistemicQuestions.tail.js +3 -0
- package/dist/epistemicQuestions.tail.js.map +1 -1
- package/dist/epistemicSources.js.map +1 -1
- package/dist/index.js +78 -42
- package/dist/index.js.map +1 -1
- package/dist/proof-attestation.json +1 -1
- package/dist/questionEvidenceLinks.js +3 -0
- package/dist/questionEvidenceLinks.js.map +1 -1
- package/dist/resolvers.js +3 -0
- package/dist/resolvers.js.map +1 -1
- package/dist/topicProjectOverlay.d.ts +4 -0
- package/dist/topicProjectOverlay.js +3 -0
- package/dist/topicProjectOverlay.js.map +1 -1
- package/package.json +4 -4
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { v } from 'convex/values';
|
|
1
|
+
import { v, ConvexError } from 'convex/values';
|
|
2
2
|
import { normalizeTupleContradictionPolicy } from '@lucern/confidence';
|
|
3
3
|
import '@lucern/access-control/access';
|
|
4
4
|
import '@lucern/access-control/audience';
|
|
@@ -10,7 +10,18 @@ v.id("epistemicNodes");
|
|
|
10
10
|
tupleContradiction: normalizeTupleContradictionPolicy()
|
|
11
11
|
});
|
|
12
12
|
function throwStructuredMutationError(args) {
|
|
13
|
-
const
|
|
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
|
+
);
|
|
14
25
|
error.status = args.status;
|
|
15
26
|
error.code = args.code;
|
|
16
27
|
error.invariantCode = args.invariantCode;
|
|
@@ -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 {\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\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 const error = new Error(args.message) as 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 throwStructuredMutationError({\n message: \"Project access required.\",\n status: 403,\n code: \"FORBIDDEN\",\n invariantCode: \"policy.scope_required\",\n suggestion: \"Request write access for the project and retry.\",\n details: { projectId, 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 { 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"]}
|
|
@@ -10,6 +10,21 @@ declare const optionalBeliefScopeArgs: {
|
|
|
10
10
|
readonly projectId: convex_values.VString<string | undefined, "optional">;
|
|
11
11
|
readonly topicId: convex_values.VString<string | undefined, "optional">;
|
|
12
12
|
};
|
|
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
|
+
};
|
|
13
28
|
type BeliefScopeArgs = {
|
|
14
29
|
projectId?: string | null;
|
|
15
30
|
topicId?: string | null;
|
|
@@ -165,4 +180,4 @@ declare function requireProjectWriteAccess(ctx: {
|
|
|
165
180
|
db: unknown;
|
|
166
181
|
}, projectId: string, userId: string): Promise<void>;
|
|
167
182
|
|
|
168
|
-
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, requireProjectWriteAccess, resolveBeliefScopeOrNull, resolveBeliefStatus, resolveBeliefWorktreeId, throwStructuredMutationError };
|
|
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 };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { v } from 'convex/values';
|
|
1
|
+
import { v, ConvexError } from 'convex/values';
|
|
2
2
|
import { normalizeTupleContradictionPolicy, confidenceFromSL, readOpinionFromRecord, mkOpinion } from '@lucern/confidence';
|
|
3
3
|
import { checkProjectAccess } from '@lucern/access-control/access';
|
|
4
4
|
import { normalizeAudienceKey, classFromAudienceKey } from '@lucern/access-control/audience';
|
|
@@ -267,6 +267,9 @@ function materializeTopicProjectOverlay(topic, idMode = "legacy") {
|
|
|
267
267
|
type: mapProjectType(topic, metadata),
|
|
268
268
|
description: readNonEmptyString(topic.description),
|
|
269
269
|
ownerId: readNonEmptyString(metadata.ownerId) || readNonEmptyString(topic.createdBy) || "system",
|
|
270
|
+
// FR.7 creator-grant: surface the principal-shaped owner field (column-first,
|
|
271
|
+
// metadata fallback for legacy rows that recorded it in metadata).
|
|
272
|
+
ownerPrincipalId: readNonEmptyString(topic.ownerPrincipalId) || readNonEmptyString(metadata.ownerPrincipalId),
|
|
270
273
|
sharedWith: readStringArray(metadata.sharedWith),
|
|
271
274
|
visibility,
|
|
272
275
|
tenantId: readNonEmptyString(topic.tenantId) || readNonEmptyString(metadata.tenantId),
|
|
@@ -770,7 +773,18 @@ var DEFAULT_CONFIDENCE_POLICY = {
|
|
|
770
773
|
tupleContradiction: normalizeTupleContradictionPolicy()
|
|
771
774
|
};
|
|
772
775
|
function throwStructuredMutationError(args) {
|
|
773
|
-
const
|
|
776
|
+
const data = {
|
|
777
|
+
structuredMutationError: true,
|
|
778
|
+
message: args.message,
|
|
779
|
+
status: args.status,
|
|
780
|
+
code: args.code,
|
|
781
|
+
invariantCode: args.invariantCode,
|
|
782
|
+
suggestion: args.suggestion,
|
|
783
|
+
details: args.details
|
|
784
|
+
};
|
|
785
|
+
const error = new ConvexError(
|
|
786
|
+
data
|
|
787
|
+
);
|
|
774
788
|
error.status = args.status;
|
|
775
789
|
error.code = args.code;
|
|
776
790
|
error.invariantCode = args.invariantCode;
|
|
@@ -1119,12 +1133,12 @@ async function requireProjectWriteAccess(ctx, projectId, userId) {
|
|
|
1119
1133
|
);
|
|
1120
1134
|
if (!hasAccess) {
|
|
1121
1135
|
throwStructuredMutationError({
|
|
1122
|
-
message:
|
|
1136
|
+
message: `Project write access denied for topic ${projectId}.`,
|
|
1123
1137
|
status: 403,
|
|
1124
|
-
code: "
|
|
1138
|
+
code: "PROJECT_ACCESS_DENIED",
|
|
1125
1139
|
invariantCode: "policy.scope_required",
|
|
1126
|
-
suggestion: "
|
|
1127
|
-
details: { projectId, userId }
|
|
1140
|
+
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.",
|
|
1141
|
+
details: { topicId: projectId, principalId: userId }
|
|
1128
1142
|
});
|
|
1129
1143
|
}
|
|
1130
1144
|
}
|