@lucern/graph-primitives 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -12
- package/dist/beliefDecay.js +24 -17
- package/dist/beliefDecay.js.map +1 -1
- package/dist/beliefEvidenceLinks.js +32 -8
- package/dist/beliefEvidenceLinks.js.map +1 -1
- package/dist/confidencePropagationDispatch.js.map +1 -1
- package/dist/contradictions.js +32 -9
- package/dist/contradictions.js.map +1 -1
- package/dist/convex.d.ts +55 -12
- package/dist/convex.js.map +1 -1
- package/dist/edgeValidation.d.ts +25 -2
- package/dist/edges/index.d.ts +9 -2
- package/dist/edges/index.js.map +1 -1
- package/dist/edges/propagationTypes.d.ts +2 -3
- package/dist/edges/propagationTypes.js.map +1 -1
- package/dist/entityBridge.js +10 -3
- package/dist/entityBridge.js.map +1 -1
- package/dist/entityLifecycle.js +15 -3
- package/dist/entityLifecycle.js.map +1 -1
- package/dist/epistemicAnswers.js.map +1 -1
- package/dist/epistemicBeliefs.admin.d.ts +36 -0
- package/dist/epistemicBeliefs.admin.js +745 -0
- package/dist/epistemicBeliefs.admin.js.map +1 -0
- package/dist/epistemicBeliefs.backfills.d.ts +62 -0
- package/dist/epistemicBeliefs.backfills.js +1004 -0
- package/dist/epistemicBeliefs.backfills.js.map +1 -0
- package/dist/epistemicBeliefs.confidence.d.ts +45 -0
- package/dist/epistemicBeliefs.confidence.js +1285 -0
- package/dist/epistemicBeliefs.confidence.js.map +1 -0
- package/dist/epistemicBeliefs.core.d.ts +35 -0
- package/dist/epistemicBeliefs.core.js +1508 -0
- package/dist/epistemicBeliefs.core.js.map +1 -0
- package/dist/epistemicBeliefs.d.ts +12 -3
- package/dist/epistemicBeliefs.helpers.d.ts +168 -0
- package/dist/epistemicBeliefs.helpers.js +1060 -0
- package/dist/epistemicBeliefs.helpers.js.map +1 -0
- package/dist/epistemicBeliefs.internal.d.ts +30 -0
- package/dist/epistemicBeliefs.internal.js +1329 -0
- package/dist/epistemicBeliefs.internal.js.map +1 -0
- package/dist/epistemicBeliefs.js +1196 -1184
- package/dist/epistemicBeliefs.js.map +1 -1
- package/dist/epistemicBeliefs.lifecycle.d.ts +19 -0
- package/dist/epistemicBeliefs.lifecycle.js +1608 -0
- package/dist/epistemicBeliefs.lifecycle.js.map +1 -0
- package/dist/epistemicBeliefs.links.d.ts +30 -0
- package/dist/epistemicBeliefs.links.js +761 -0
- package/dist/epistemicBeliefs.links.js.map +1 -0
- package/dist/epistemicBeliefs.queries.d.ts +16 -0
- package/dist/epistemicBeliefs.queries.js +90 -0
- package/dist/epistemicBeliefs.queries.js.map +1 -0
- package/dist/epistemicContractHelpers.d.ts +1 -1
- package/dist/epistemicContractHelpers.js +1 -1
- package/dist/epistemicContracts.d.ts +5 -76
- package/dist/epistemicContracts.evaluators.d.ts +36 -0
- package/dist/epistemicContracts.evaluators.js +2506 -0
- package/dist/epistemicContracts.evaluators.js.map +1 -0
- package/dist/epistemicContracts.handlers.d.ts +40 -0
- package/dist/epistemicContracts.handlers.js +3029 -0
- package/dist/epistemicContracts.handlers.js.map +1 -0
- package/dist/epistemicContracts.js +2006 -5281
- package/dist/epistemicContracts.js.map +1 -1
- package/dist/epistemicContracts.metrics.d.ts +26 -0
- package/dist/epistemicContracts.metrics.js +427 -0
- package/dist/epistemicContracts.metrics.js.map +1 -0
- package/dist/epistemicContracts.types.d.ts +159 -0
- package/dist/epistemicContracts.types.js +3 -0
- package/dist/epistemicContracts.types.js.map +1 -0
- package/dist/epistemicEdgeCreation.d.ts +73 -0
- package/dist/epistemicEdgeCreation.js +450 -0
- package/dist/epistemicEdgeCreation.js.map +1 -0
- package/dist/epistemicEdges-BF-cn4i3.d.ts +43 -0
- package/dist/epistemicEdges.d.ts +8 -1
- package/dist/epistemicEdges.handlers.d.ts +20 -0
- package/dist/epistemicEdges.handlers.js +289 -0
- package/dist/epistemicEdges.handlers.js.map +1 -0
- package/dist/epistemicEdges.helpers.d.ts +27 -0
- package/dist/epistemicEdges.helpers.js +162 -0
- package/dist/epistemicEdges.helpers.js.map +1 -0
- package/dist/epistemicEdges.js +797 -875
- package/dist/epistemicEdges.js.map +1 -1
- package/dist/epistemicEdges.mutations.d.ts +39 -0
- package/dist/epistemicEdges.mutations.js +1365 -0
- package/dist/epistemicEdges.mutations.js.map +1 -0
- package/dist/epistemicEdges.queries.d.ts +95 -0
- package/dist/epistemicEdges.queries.js +851 -0
- package/dist/epistemicEdges.queries.js.map +1 -0
- package/dist/epistemicEdges.types.d.ts +32 -0
- package/dist/epistemicEdges.types.js +3 -0
- package/dist/epistemicEdges.types.js.map +1 -0
- package/dist/epistemicEvidence-DvfchNt7.d.ts +46 -0
- package/dist/epistemicEvidence.d.ts +5 -2
- package/dist/epistemicEvidence.js +801 -807
- package/dist/epistemicEvidence.js.map +1 -1
- package/dist/epistemicEvidenceHelpers.d.ts +71 -0
- package/dist/epistemicEvidenceHelpers.js +769 -0
- package/dist/epistemicEvidenceHelpers.js.map +1 -0
- package/dist/epistemicEvidenceMutations.d.ts +10 -0
- package/dist/epistemicEvidenceMutations.js +1421 -0
- package/dist/epistemicEvidenceMutations.js.map +1 -0
- package/dist/epistemicEvidenceQueries.d.ts +10 -0
- package/dist/epistemicEvidenceQueries.js +1049 -0
- package/dist/epistemicEvidenceQueries.js.map +1 -0
- package/dist/epistemicHelpers.d.ts +4 -2
- package/dist/epistemicHelpers.js +132 -127
- package/dist/epistemicHelpers.js.map +1 -1
- package/dist/epistemicLayerRules.d.ts +138 -0
- package/dist/epistemicLayerRules.js +481 -0
- package/dist/epistemicLayerRules.js.map +1 -0
- package/dist/epistemicLinking.js +1 -1
- package/dist/epistemicLinking.js.map +1 -1
- package/dist/epistemicNodeCreation.d.ts +101 -0
- package/dist/epistemicNodeCreation.js +709 -0
- package/dist/epistemicNodeCreation.js.map +1 -0
- package/dist/epistemicNodes-BCQxpYx_.d.ts +54 -0
- package/dist/epistemicNodes.d.ts +5 -1
- package/dist/epistemicNodes.helpers.d.ts +51 -0
- package/dist/epistemicNodes.helpers.js +73 -0
- package/dist/epistemicNodes.helpers.js.map +1 -0
- package/dist/epistemicNodes.internal.d.ts +34 -0
- package/dist/epistemicNodes.internal.js +658 -0
- package/dist/epistemicNodes.internal.js.map +1 -0
- package/dist/epistemicNodes.js +698 -693
- package/dist/epistemicNodes.js.map +1 -1
- package/dist/epistemicNodes.mutations.d.ts +34 -0
- package/dist/epistemicNodes.mutations.js +1153 -0
- package/dist/epistemicNodes.mutations.js.map +1 -0
- package/dist/epistemicNodes.queries.d.ts +36 -0
- package/dist/epistemicNodes.queries.js +619 -0
- package/dist/epistemicNodes.queries.js.map +1 -0
- package/dist/epistemicNodes.validators.d.ts +23 -0
- package/dist/epistemicNodes.validators.js +105 -0
- package/dist/epistemicNodes.validators.js.map +1 -0
- package/dist/epistemicQuestions-bwHd2FWE.d.ts +68 -0
- package/dist/epistemicQuestions.conviction.d.ts +52 -0
- package/dist/epistemicQuestions.conviction.js +1389 -0
- package/dist/epistemicQuestions.conviction.js.map +1 -0
- package/dist/epistemicQuestions.create.d.ts +29 -0
- package/dist/epistemicQuestions.create.js +1300 -0
- package/dist/epistemicQuestions.create.js.map +1 -0
- package/dist/epistemicQuestions.d.ts +10 -2
- package/dist/epistemicQuestions.evidence.d.ts +22 -0
- package/dist/epistemicQuestions.evidence.js +929 -0
- package/dist/epistemicQuestions.evidence.js.map +1 -0
- package/dist/epistemicQuestions.helpers.d.ts +69 -0
- package/dist/epistemicQuestions.helpers.js +824 -0
- package/dist/epistemicQuestions.helpers.js.map +1 -0
- package/dist/epistemicQuestions.js +2435 -2430
- package/dist/epistemicQuestions.js.map +1 -1
- package/dist/epistemicQuestions.lifecycle.d.ts +24 -0
- package/dist/epistemicQuestions.lifecycle.js +838 -0
- package/dist/epistemicQuestions.lifecycle.js.map +1 -0
- package/dist/epistemicQuestions.queries.d.ts +41 -0
- package/dist/epistemicQuestions.queries.js +1013 -0
- package/dist/epistemicQuestions.queries.js.map +1 -0
- package/dist/epistemicQuestions.sprint.d.ts +22 -0
- package/dist/epistemicQuestions.sprint.js +757 -0
- package/dist/epistemicQuestions.sprint.js.map +1 -0
- package/dist/epistemicQuestions.tail.d.ts +42 -0
- package/dist/epistemicQuestions.tail.js +1345 -0
- package/dist/epistemicQuestions.tail.js.map +1 -0
- package/dist/epistemicSources.js +6 -2
- package/dist/epistemicSources.js.map +1 -1
- package/dist/evaluators/index.d.ts +2 -2
- package/dist/evaluators/index.js +45 -5320
- package/dist/evaluators/index.js.map +1 -1
- package/dist/evaluators/lintCheckerEvaluator.d.ts +1 -1
- package/dist/evaluators/sentryCheckerEvaluator.d.ts +1 -1
- package/dist/evaluators/testRunnerEvaluator.d.ts +1 -1
- package/dist/evaluators/tscCheckerEvaluator.d.ts +1 -1
- package/dist/{graphTypes-CpgIuCdo.d.ts → graphTypes-B8VaIjnl.d.ts} +1 -1
- package/dist/graphTypes.d.ts +1 -1
- package/dist/{helpers-BYHIk5vU.d.ts → helpers-DNYfg6mo.d.ts} +2 -3
- package/dist/helpers.d.ts +2 -2
- package/dist/helpers.js.map +1 -1
- package/dist/{index-Dq-7R-gi.d.ts → index-C-Kyd7hD.d.ts} +1 -1
- package/dist/index.d.ts +160 -14
- package/dist/index.js +12291 -13001
- package/dist/index.js.map +1 -1
- package/dist/logicalRoleInference.js.map +1 -1
- package/dist/ontologyApproval.js +1 -1
- package/dist/ontologyApproval.js.map +1 -1
- package/dist/ontologyDefinitions.js +25 -7
- package/dist/ontologyDefinitions.js.map +1 -1
- package/dist/ontologyRegistry.js.map +1 -1
- package/dist/projectionReconciliation.js.map +1 -1
- package/dist/questionEvidenceLinks.js +28 -7
- package/dist/questionEvidenceLinks.js.map +1 -1
- package/dist/resolvers.js.map +1 -1
- package/dist/scopeResolverCompat.js.map +1 -1
- package/dist/topicProjectOverlay.js.map +1 -1
- package/dist/topicScope.js.map +1 -1
- package/dist/workflowBridge.js.map +1 -1
- package/dist/workspaceIsolation.js.map +1 -1
- package/package.json +4 -5
- package/dist/edgeValidation-CeI0wc0r.d.ts +0 -35
- package/dist/epistemicBeliefs-DzKjZAeC.d.ts +0 -377
- package/dist/epistemicEdges-CvlKnEyy.d.ts +0 -191
- package/dist/epistemicEvidence-xw6UUrwh.d.ts +0 -128
- package/dist/epistemicHelpers-DevrYgPN.d.ts +0 -329
- package/dist/epistemicNodes-DjSUfvyD.d.ts +0 -167
- package/dist/epistemicQuestions-B_nUclrH.d.ts +0 -214
- package/dist/index-Dct1T70K.d.ts +0 -25
|
@@ -0,0 +1,1608 @@
|
|
|
1
|
+
import { v } from 'convex/values';
|
|
2
|
+
import { permissiveReturn } from '@lucern/contracts/schema-helpers/validators';
|
|
3
|
+
import { componentsGeneric, anyApi, internalMutationGeneric, mutationGeneric } from 'convex/server';
|
|
4
|
+
import '@lucern/contracts/schema-helpers/spine/tables/epistemicNodes';
|
|
5
|
+
import { normalizeTupleContradictionPolicy, mkOpinion, conditionalDeduction, project, dampedDependencyCascade, hasProjectedOpinionChanged, confidenceFromSL, detectTupleContradiction, evaluateTupleContradictionTransition, trustDiscount, applyNegativeSupport, cumulativeFusion, applyNegativeEvidence, readOpinionFromRecord } from '@lucern/confidence';
|
|
6
|
+
import { checkProjectAccess } from '@lucern/access-control/access';
|
|
7
|
+
import '@lucern/access-control/audience';
|
|
8
|
+
import { getCurrentUserId } from '@lucern/access-control/auth';
|
|
9
|
+
|
|
10
|
+
// src/epistemicBeliefs.lifecycle.ts
|
|
11
|
+
var api = anyApi;
|
|
12
|
+
componentsGeneric();
|
|
13
|
+
var internal = anyApi;
|
|
14
|
+
var internalMutation = internalMutationGeneric;
|
|
15
|
+
var mutation = mutationGeneric;
|
|
16
|
+
|
|
17
|
+
// src/debug.ts
|
|
18
|
+
function isGraphPrimitiveDebugEnabled() {
|
|
19
|
+
const env = globalThis.process?.env;
|
|
20
|
+
return env?.LUCERN_COMPAT_FALLBACK_DEBUG === "1" || env?.LUCERN_GRAPH_DEBUG === "1";
|
|
21
|
+
}
|
|
22
|
+
function debugGraphPrimitiveFallback(message, context) {
|
|
23
|
+
if (!isGraphPrimitiveDebugEnabled()) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
console.debug(message, context ?? {});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// src/topicScope.ts
|
|
30
|
+
var LEGACY_SCOPE_FIELD = "graphScopeProjectId";
|
|
31
|
+
function asMappedProjectId(topic) {
|
|
32
|
+
if (!topic) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const directLegacyProjectId = normalizeScopeValue(topic[LEGACY_SCOPE_FIELD]);
|
|
36
|
+
if (directLegacyProjectId) {
|
|
37
|
+
return directLegacyProjectId;
|
|
38
|
+
}
|
|
39
|
+
const metadata = topic.metadata || {};
|
|
40
|
+
const candidate = metadata[LEGACY_SCOPE_FIELD] || metadata.legacyProjectId || metadata.projectId || metadata.scopeProjectId;
|
|
41
|
+
return candidate ? candidate : void 0;
|
|
42
|
+
}
|
|
43
|
+
function normalizeScopeValue(value) {
|
|
44
|
+
if (typeof value !== "string") {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const normalized = value.trim();
|
|
48
|
+
return normalized.length > 0 ? normalized : void 0;
|
|
49
|
+
}
|
|
50
|
+
function pickPrimaryTopic(candidates) {
|
|
51
|
+
return [...candidates].sort((a, b) => {
|
|
52
|
+
const depthA = a.depth ?? 9999;
|
|
53
|
+
const depthB = b.depth ?? 9999;
|
|
54
|
+
if (depthA !== depthB) {
|
|
55
|
+
return depthA - depthB;
|
|
56
|
+
}
|
|
57
|
+
const createdA = a.createdAt ?? Number.MAX_SAFE_INTEGER;
|
|
58
|
+
const createdB = b.createdAt ?? Number.MAX_SAFE_INTEGER;
|
|
59
|
+
if (createdA !== createdB) {
|
|
60
|
+
return createdA - createdB;
|
|
61
|
+
}
|
|
62
|
+
return String(a.name || "").localeCompare(String(b.name || ""));
|
|
63
|
+
})[0];
|
|
64
|
+
}
|
|
65
|
+
async function findTopicsByScopeAlias(ctx, scopeId) {
|
|
66
|
+
try {
|
|
67
|
+
return await ctx.db.query("topics").withIndex(
|
|
68
|
+
"by_graph_scope_project",
|
|
69
|
+
(q) => q.eq(LEGACY_SCOPE_FIELD, scopeId)
|
|
70
|
+
).collect();
|
|
71
|
+
} catch (error) {
|
|
72
|
+
debugGraphPrimitiveFallback(
|
|
73
|
+
"[topicScope] Failed to resolve scope alias via index",
|
|
74
|
+
{
|
|
75
|
+
error,
|
|
76
|
+
scopeId
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
const topics = await ctx.db.query("topics").collect();
|
|
80
|
+
return topics.filter((topic) => {
|
|
81
|
+
const normalizedGlobalId = normalizeScopeValue(topic.globalId);
|
|
82
|
+
const mappedProjectId = asMappedProjectId(topic);
|
|
83
|
+
return String(topic._id) === scopeId || normalizedGlobalId === scopeId || mappedProjectId === scopeId;
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async function tryResolveHostTopicById(ctx, topicId) {
|
|
88
|
+
if (typeof ctx.runQuery !== "function") {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
return await ctx.runQuery(api.topics.get, {
|
|
93
|
+
id: topicId
|
|
94
|
+
}) ?? null;
|
|
95
|
+
} catch (error) {
|
|
96
|
+
debugGraphPrimitiveFallback(
|
|
97
|
+
"[topicScope] Failed to resolve topic by host query",
|
|
98
|
+
{
|
|
99
|
+
error,
|
|
100
|
+
topicId
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async function tryResolveHostTopicByLegacyScope(ctx, legacyScopeId) {
|
|
107
|
+
if (typeof ctx.runQuery !== "function") {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
return await ctx.runQuery(api.topics.getByLegacyScopeId, {
|
|
112
|
+
projectId: legacyScopeId
|
|
113
|
+
}) ?? null;
|
|
114
|
+
} catch (error) {
|
|
115
|
+
debugGraphPrimitiveFallback(
|
|
116
|
+
"[topicScope] Failed to resolve topic by legacy scope",
|
|
117
|
+
{
|
|
118
|
+
error,
|
|
119
|
+
legacyScopeId
|
|
120
|
+
}
|
|
121
|
+
);
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
async function resolveInheritedWorkspaceScope(ctx, topic) {
|
|
126
|
+
const MAX_DEPTH = 10;
|
|
127
|
+
let tenantId = normalizeScopeValue(topic.tenantId);
|
|
128
|
+
let workspaceId = normalizeScopeValue(topic.workspaceId);
|
|
129
|
+
if (tenantId && workspaceId) {
|
|
130
|
+
return { tenantId, workspaceId };
|
|
131
|
+
}
|
|
132
|
+
let current = topic;
|
|
133
|
+
for (let i = 0; i < MAX_DEPTH && current?.parentTopicId; i++) {
|
|
134
|
+
current = await ctx.db.get(current.parentTopicId);
|
|
135
|
+
if (!current) break;
|
|
136
|
+
if (!tenantId) {
|
|
137
|
+
tenantId = normalizeScopeValue(current.tenantId);
|
|
138
|
+
}
|
|
139
|
+
if (!workspaceId) {
|
|
140
|
+
workspaceId = normalizeScopeValue(current.workspaceId);
|
|
141
|
+
}
|
|
142
|
+
if (tenantId && workspaceId) break;
|
|
143
|
+
}
|
|
144
|
+
return { tenantId, workspaceId };
|
|
145
|
+
}
|
|
146
|
+
async function resolveTopicProjectScope(ctx, args) {
|
|
147
|
+
if (args.topicId) {
|
|
148
|
+
let topic = null;
|
|
149
|
+
try {
|
|
150
|
+
topic = await ctx.db.get(
|
|
151
|
+
args.topicId
|
|
152
|
+
);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
debugGraphPrimitiveFallback(
|
|
155
|
+
"[topicScope] Failed to load topic by direct id",
|
|
156
|
+
{
|
|
157
|
+
error,
|
|
158
|
+
topicId: args.topicId
|
|
159
|
+
}
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
if (!topic) {
|
|
163
|
+
topic = await tryResolveHostTopicById(ctx, String(args.topicId));
|
|
164
|
+
}
|
|
165
|
+
if (!topic) {
|
|
166
|
+
topic = pickPrimaryTopic(
|
|
167
|
+
await findTopicsByScopeAlias(ctx, String(args.topicId))
|
|
168
|
+
) ?? null;
|
|
169
|
+
}
|
|
170
|
+
if (!topic) {
|
|
171
|
+
throw new Error(`Topic not found: ${String(args.topicId)}`);
|
|
172
|
+
}
|
|
173
|
+
const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
|
|
174
|
+
const mapped = asMappedProjectId(topic);
|
|
175
|
+
if (mapped) {
|
|
176
|
+
return {
|
|
177
|
+
topicId: topic._id,
|
|
178
|
+
projectId: mapped,
|
|
179
|
+
tenantId: inherited.tenantId,
|
|
180
|
+
workspaceId: inherited.workspaceId,
|
|
181
|
+
source: "topic"
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
topicId: topic._id,
|
|
186
|
+
tenantId: inherited.tenantId,
|
|
187
|
+
workspaceId: inherited.workspaceId,
|
|
188
|
+
source: "topic"
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
if (args.projectId) {
|
|
192
|
+
let directTopic = null;
|
|
193
|
+
try {
|
|
194
|
+
directTopic = await ctx.db.get(
|
|
195
|
+
args.projectId
|
|
196
|
+
);
|
|
197
|
+
} catch (error) {
|
|
198
|
+
debugGraphPrimitiveFallback(
|
|
199
|
+
"[topicScope] Failed to load direct project topic",
|
|
200
|
+
{
|
|
201
|
+
error,
|
|
202
|
+
projectId: args.projectId
|
|
203
|
+
}
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
if (directTopic) {
|
|
207
|
+
const inherited = await resolveInheritedWorkspaceScope(ctx, directTopic);
|
|
208
|
+
const mapped = asMappedProjectId(directTopic);
|
|
209
|
+
return {
|
|
210
|
+
topicId: directTopic._id,
|
|
211
|
+
projectId: mapped ?? args.projectId,
|
|
212
|
+
tenantId: inherited.tenantId,
|
|
213
|
+
workspaceId: inherited.workspaceId,
|
|
214
|
+
source: "topic_inferred"
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
directTopic = await tryResolveHostTopicByLegacyScope(ctx, args.projectId);
|
|
218
|
+
if (directTopic) {
|
|
219
|
+
const inherited = await resolveInheritedWorkspaceScope(ctx, directTopic);
|
|
220
|
+
const mapped = asMappedProjectId(directTopic);
|
|
221
|
+
return {
|
|
222
|
+
topicId: directTopic._id,
|
|
223
|
+
projectId: mapped ?? args.projectId,
|
|
224
|
+
tenantId: inherited.tenantId,
|
|
225
|
+
workspaceId: inherited.workspaceId,
|
|
226
|
+
source: "topic_inferred"
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
const topics = await findTopicsByScopeAlias(ctx, args.projectId);
|
|
230
|
+
const primary = pickPrimaryTopic(topics);
|
|
231
|
+
if (primary) {
|
|
232
|
+
const inherited = await resolveInheritedWorkspaceScope(ctx, primary);
|
|
233
|
+
return {
|
|
234
|
+
topicId: primary._id,
|
|
235
|
+
projectId: args.projectId,
|
|
236
|
+
tenantId: inherited.tenantId,
|
|
237
|
+
workspaceId: inherited.workspaceId,
|
|
238
|
+
source: "project_mapped_topic"
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
throw new Error(
|
|
242
|
+
`Legacy project scope ${String(args.projectId)} has no mapped topic.`
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
throw new Error(
|
|
246
|
+
"Missing scope: provide topicId (preferred) or legacy projectId alias."
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
({
|
|
250
|
+
projectId: v.optional(v.string()),
|
|
251
|
+
topicId: v.optional(v.string())
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// src/workspaceIsolation.ts
|
|
255
|
+
function normalizeScopeValue2(value) {
|
|
256
|
+
if (typeof value !== "string") {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
const normalized = value.trim();
|
|
260
|
+
return normalized.length > 0 ? normalized : void 0;
|
|
261
|
+
}
|
|
262
|
+
function throwWorkspaceIsolationError(args) {
|
|
263
|
+
const error = new Error(args.message);
|
|
264
|
+
error.status = 409;
|
|
265
|
+
error.code = "INVARIANT_VIOLATION";
|
|
266
|
+
error.invariantCode = args.invariantCode;
|
|
267
|
+
error.suggestion = args.suggestion;
|
|
268
|
+
error.details = args.details;
|
|
269
|
+
throw error;
|
|
270
|
+
}
|
|
271
|
+
function nodeMatchesWorkspaceReasoningScope(node, scope) {
|
|
272
|
+
if (!node) {
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
const scopeTenantId = normalizeScopeValue2(scope.tenantId);
|
|
276
|
+
const scopeWorkspaceId = normalizeScopeValue2(scope.workspaceId);
|
|
277
|
+
const nodeTenantId = normalizeScopeValue2(node.tenantId);
|
|
278
|
+
const nodeWorkspaceId = normalizeScopeValue2(node.workspaceId);
|
|
279
|
+
const epistemicLayer = typeof node.epistemicLayer === "string" ? node.epistemicLayer : void 0;
|
|
280
|
+
if (scopeTenantId && nodeTenantId && scopeTenantId !== nodeTenantId) {
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
if (epistemicLayer === "ontological" && nodeWorkspaceId === void 0) {
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
if (!scopeWorkspaceId && node.publicationStatus === "published") {
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
if (!scopeWorkspaceId) {
|
|
290
|
+
return nodeWorkspaceId === void 0;
|
|
291
|
+
}
|
|
292
|
+
return scopeWorkspaceId === nodeWorkspaceId;
|
|
293
|
+
}
|
|
294
|
+
function edgeMatchesWorkspaceReasoningScope(edge, scope) {
|
|
295
|
+
if (!edge) {
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
const scopeTenantId = normalizeScopeValue2(scope.tenantId);
|
|
299
|
+
const scopeWorkspaceId = normalizeScopeValue2(scope.workspaceId);
|
|
300
|
+
const edgeTenantId = normalizeScopeValue2(edge.tenantId);
|
|
301
|
+
const edgeWorkspaceId = normalizeScopeValue2(edge.workspaceId);
|
|
302
|
+
if (scopeTenantId && edgeTenantId && scopeTenantId !== edgeTenantId) {
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
if (!scopeWorkspaceId) {
|
|
306
|
+
return edgeWorkspaceId === void 0;
|
|
307
|
+
}
|
|
308
|
+
return scopeWorkspaceId === edgeWorkspaceId;
|
|
309
|
+
}
|
|
310
|
+
async function resolveNodeScopeForWorkspaceIsolation(ctx, node) {
|
|
311
|
+
const epistemicLayer = typeof node?.epistemicLayer === "string" ? node.epistemicLayer : void 0;
|
|
312
|
+
const resolved = {
|
|
313
|
+
tenantId: normalizeScopeValue2(node?.tenantId),
|
|
314
|
+
workspaceId: normalizeScopeValue2(node?.workspaceId),
|
|
315
|
+
epistemicLayer,
|
|
316
|
+
nodeType: typeof node?.nodeType === "string" ? node.nodeType : void 0
|
|
317
|
+
};
|
|
318
|
+
if (!node) {
|
|
319
|
+
return resolved;
|
|
320
|
+
}
|
|
321
|
+
if (resolved.epistemicLayer === "ontological") {
|
|
322
|
+
return resolved;
|
|
323
|
+
}
|
|
324
|
+
if (resolved.tenantId || resolved.workspaceId) {
|
|
325
|
+
return resolved;
|
|
326
|
+
}
|
|
327
|
+
if (node.topicId) {
|
|
328
|
+
const topicScope = await resolveTopicProjectScope(ctx, {
|
|
329
|
+
topicId: node.topicId
|
|
330
|
+
});
|
|
331
|
+
return {
|
|
332
|
+
...resolved,
|
|
333
|
+
tenantId: topicScope.tenantId,
|
|
334
|
+
workspaceId: topicScope.workspaceId
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
if (node.projectId) {
|
|
338
|
+
const topicScope = await resolveTopicProjectScope(ctx, {
|
|
339
|
+
projectId: String(node.projectId)
|
|
340
|
+
});
|
|
341
|
+
return {
|
|
342
|
+
...resolved,
|
|
343
|
+
tenantId: topicScope.tenantId,
|
|
344
|
+
workspaceId: topicScope.workspaceId
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
return resolved;
|
|
348
|
+
}
|
|
349
|
+
function resolveRuntimePackMutationContext(args) {
|
|
350
|
+
if (!args.runtimeToolName && !args.runtimePackKey && !args.runtimePackInstallScope) {
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
return {
|
|
354
|
+
toolName: args.runtimeToolName,
|
|
355
|
+
packKey: args.runtimePackKey,
|
|
356
|
+
packInstallScope: args.runtimePackInstallScope
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
function assertTenantPackWorkspaceMutationAllowed(args) {
|
|
360
|
+
if (!args.runtime?.packKey || args.runtime.packInstallScope !== "tenant") {
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
const targetWorkspaceId = normalizeScopeValue2(args.target.workspaceId);
|
|
364
|
+
const targetLayer = typeof args.target.epistemicLayer === "string" ? args.target.epistemicLayer : void 0;
|
|
365
|
+
if (!targetWorkspaceId || targetLayer === "ontological") {
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
throwWorkspaceIsolationError({
|
|
369
|
+
message: `Tenant-scoped pack "${args.runtime.packKey}" cannot mutate workspace-scoped reasoning state.`,
|
|
370
|
+
invariantCode: "workspace.tenant_pack_reasoning_write_forbidden",
|
|
371
|
+
suggestion: "Use a workspace-scoped pack for workspace-local graph mutations, or route the change through tenant-global canonical entity flows.",
|
|
372
|
+
details: {
|
|
373
|
+
mutationName: args.mutationName,
|
|
374
|
+
toolName: args.runtime.toolName,
|
|
375
|
+
packKey: args.runtime.packKey,
|
|
376
|
+
targetWorkspaceId,
|
|
377
|
+
targetNodeType: args.target.nodeType,
|
|
378
|
+
targetLayer
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// src/beliefLifecycle.ts
|
|
384
|
+
var BELIEF_STATUS_VALUES = [
|
|
385
|
+
"assumption",
|
|
386
|
+
"hypothesis",
|
|
387
|
+
"belief",
|
|
388
|
+
"fact"
|
|
389
|
+
];
|
|
390
|
+
var RESOLVED_PREDICTION_OUTCOMES = [
|
|
391
|
+
"confirmed",
|
|
392
|
+
"disconfirmed",
|
|
393
|
+
"partial",
|
|
394
|
+
"expired"
|
|
395
|
+
];
|
|
396
|
+
function isBeliefLifecycleStatus(value) {
|
|
397
|
+
return typeof value === "string" && BELIEF_STATUS_VALUES.includes(value);
|
|
398
|
+
}
|
|
399
|
+
function normalizeBeliefConfidence(confidence) {
|
|
400
|
+
if (typeof confidence !== "number" || !Number.isFinite(confidence)) {
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
if (confidence >= 0 && confidence <= 1) {
|
|
404
|
+
return confidence;
|
|
405
|
+
}
|
|
406
|
+
if (confidence > 1 && confidence <= 100) {
|
|
407
|
+
return confidence / 100;
|
|
408
|
+
}
|
|
409
|
+
return null;
|
|
410
|
+
}
|
|
411
|
+
function isResolvedByConfidence(confidence) {
|
|
412
|
+
const normalized = normalizeBeliefConfidence(confidence);
|
|
413
|
+
if (normalized === null) {
|
|
414
|
+
return false;
|
|
415
|
+
}
|
|
416
|
+
return normalized <= 0 || normalized >= 1;
|
|
417
|
+
}
|
|
418
|
+
function hasResolvedPredictionOutcome(predictionMeta) {
|
|
419
|
+
if (!predictionMeta || typeof predictionMeta !== "object") {
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
const outcome = predictionMeta.outcome;
|
|
423
|
+
return typeof outcome === "string" && RESOLVED_PREDICTION_OUTCOMES.includes(outcome);
|
|
424
|
+
}
|
|
425
|
+
function getPredictionMetaFromMetadata(metadata) {
|
|
426
|
+
return metadata?.predictionMeta;
|
|
427
|
+
}
|
|
428
|
+
function shouldTreatBeliefAsFact(opts) {
|
|
429
|
+
if (isResolvedByConfidence(opts.confidence)) {
|
|
430
|
+
return true;
|
|
431
|
+
}
|
|
432
|
+
if (hasResolvedPredictionOutcome(opts.predictionMeta)) {
|
|
433
|
+
return true;
|
|
434
|
+
}
|
|
435
|
+
if (hasResolvedPredictionOutcome(getPredictionMetaFromMetadata(opts.metadata))) {
|
|
436
|
+
return true;
|
|
437
|
+
}
|
|
438
|
+
return false;
|
|
439
|
+
}
|
|
440
|
+
function resolveBeliefLifecycleStatus(opts) {
|
|
441
|
+
if (shouldTreatBeliefAsFact(opts)) {
|
|
442
|
+
return "fact";
|
|
443
|
+
}
|
|
444
|
+
const direct = opts.beliefStatus;
|
|
445
|
+
if (isBeliefLifecycleStatus(direct)) {
|
|
446
|
+
const normalized = normalizeBeliefConfidence(opts.confidence);
|
|
447
|
+
if (normalized !== null && isPreValidationBeliefStatus(direct)) {
|
|
448
|
+
return "belief";
|
|
449
|
+
}
|
|
450
|
+
return direct;
|
|
451
|
+
}
|
|
452
|
+
const metaStatus = opts.metadata?.beliefStatus;
|
|
453
|
+
if (isBeliefLifecycleStatus(metaStatus)) {
|
|
454
|
+
const normalized = normalizeBeliefConfidence(opts.confidence);
|
|
455
|
+
if (normalized !== null && isPreValidationBeliefStatus(metaStatus)) {
|
|
456
|
+
return "belief";
|
|
457
|
+
}
|
|
458
|
+
return metaStatus;
|
|
459
|
+
}
|
|
460
|
+
return "assumption";
|
|
461
|
+
}
|
|
462
|
+
function isPreValidationBeliefStatus(status) {
|
|
463
|
+
return status === "assumption" || status === "hypothesis";
|
|
464
|
+
}
|
|
465
|
+
function promoteBeliefStatusAfterScoring(status, opts) {
|
|
466
|
+
if (shouldTreatBeliefAsFact({ ...opts })) {
|
|
467
|
+
return "fact";
|
|
468
|
+
}
|
|
469
|
+
if (isPreValidationBeliefStatus(status)) {
|
|
470
|
+
return "belief";
|
|
471
|
+
}
|
|
472
|
+
return status === "fact" ? "fact" : "belief";
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// src/edges/contains.ts
|
|
476
|
+
var containsPropagationSpec = {
|
|
477
|
+
edgeType: "contains",
|
|
478
|
+
direction: "outgoing",
|
|
479
|
+
transitivity: "none",
|
|
480
|
+
damping: 1,
|
|
481
|
+
maxHops: 1,
|
|
482
|
+
operator: () => null,
|
|
483
|
+
description: "Structural containment only. Traversed for explicit semantics, but it never propagates opinions."
|
|
484
|
+
};
|
|
485
|
+
function readEdgeMetadata(edge) {
|
|
486
|
+
return {
|
|
487
|
+
constraint: edge.constraint ?? void 0,
|
|
488
|
+
normalization: edge.normalization ?? void 0,
|
|
489
|
+
propagation: edge.propagation ?? void 0,
|
|
490
|
+
conditionalA: edge.conditionalA ?? void 0,
|
|
491
|
+
conditionalNotA: edge.conditionalNotA ?? void 0
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
function applyPerHopDamping(sourceOpinion, damping) {
|
|
495
|
+
if (damping >= 1) {
|
|
496
|
+
return sourceOpinion;
|
|
497
|
+
}
|
|
498
|
+
return trustDiscount(sourceOpinion, Math.max(0, damping));
|
|
499
|
+
}
|
|
500
|
+
function annotateRationale(result, spec, hop) {
|
|
501
|
+
return {
|
|
502
|
+
...result,
|
|
503
|
+
rationale: `hop=${hop} edge=${spec.edgeType} damping=${spec.damping.toFixed(
|
|
504
|
+
2
|
|
505
|
+
)} :: ${result.rationale}`
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
function propagatePositiveSupport(sourceOpinion, targetOpinion, edgeWeight) {
|
|
509
|
+
const discounted = trustDiscount(sourceOpinion, Math.abs(edgeWeight));
|
|
510
|
+
return {
|
|
511
|
+
opinion: cumulativeFusion(targetOpinion, discounted),
|
|
512
|
+
operator: "cumulative_fusion",
|
|
513
|
+
rationale: `Supporting evidence (weight=${edgeWeight.toFixed(
|
|
514
|
+
2
|
|
515
|
+
)}) from source at ${project(sourceOpinion).toFixed(2)}`
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
function propagatePositiveInform(sourceOpinion, targetOpinion, edgeWeight) {
|
|
519
|
+
const discounted = trustDiscount(sourceOpinion, Math.abs(edgeWeight));
|
|
520
|
+
return {
|
|
521
|
+
opinion: cumulativeFusion(targetOpinion, discounted),
|
|
522
|
+
operator: "cumulative_fusion",
|
|
523
|
+
rationale: `Supporting evidence (weight=${edgeWeight.toFixed(2)})`
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
function propagateNegativeSupportWithMetadata(sourceOpinion, targetOpinion, edgeWeight, edge) {
|
|
527
|
+
return applyNegativeSupport(
|
|
528
|
+
sourceOpinion,
|
|
529
|
+
targetOpinion,
|
|
530
|
+
edgeWeight,
|
|
531
|
+
readEdgeMetadata(edge)
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
var propagateNegativeInform = applyNegativeEvidence;
|
|
535
|
+
|
|
536
|
+
// src/edges/contradicts.ts
|
|
537
|
+
var contradictsPropagationSpec = {
|
|
538
|
+
edgeType: "contradicts",
|
|
539
|
+
direction: "bidirectional",
|
|
540
|
+
transitivity: "none",
|
|
541
|
+
damping: 0.85,
|
|
542
|
+
maxHops: 1,
|
|
543
|
+
operator: (sourceOpinion, targetOpinion, edge, context) => {
|
|
544
|
+
const dampedSource = applyPerHopDamping(
|
|
545
|
+
sourceOpinion,
|
|
546
|
+
context.spec.damping
|
|
547
|
+
);
|
|
548
|
+
const negativeWeight = -Math.abs(edge.weight ?? 1);
|
|
549
|
+
const result = propagateNegativeSupportWithMetadata(
|
|
550
|
+
dampedSource,
|
|
551
|
+
targetOpinion,
|
|
552
|
+
negativeWeight,
|
|
553
|
+
edge
|
|
554
|
+
);
|
|
555
|
+
return annotateRationale(result, context.spec, context.hop);
|
|
556
|
+
},
|
|
557
|
+
description: "Legacy contradiction edges move negative pressure in either direction, but never beyond one hop."
|
|
558
|
+
};
|
|
559
|
+
var dependsOnPropagationSpec = {
|
|
560
|
+
edgeType: "depends_on",
|
|
561
|
+
direction: "incoming",
|
|
562
|
+
transitivity: "damped",
|
|
563
|
+
damping: 0.8,
|
|
564
|
+
maxHops: "unbounded",
|
|
565
|
+
operator: (sourceOpinion, targetOpinion, edge, context) => {
|
|
566
|
+
const dampedSource = applyPerHopDamping(
|
|
567
|
+
sourceOpinion,
|
|
568
|
+
context.spec.damping
|
|
569
|
+
);
|
|
570
|
+
const metadata = readEdgeMetadata(edge);
|
|
571
|
+
if (metadata.conditionalA && metadata.conditionalNotA) {
|
|
572
|
+
const deducedOpinion = conditionalDeduction(
|
|
573
|
+
dampedSource,
|
|
574
|
+
mkOpinion(
|
|
575
|
+
metadata.conditionalA.b,
|
|
576
|
+
metadata.conditionalA.d,
|
|
577
|
+
metadata.conditionalA.u,
|
|
578
|
+
metadata.conditionalA.a
|
|
579
|
+
),
|
|
580
|
+
mkOpinion(
|
|
581
|
+
metadata.conditionalNotA.b,
|
|
582
|
+
metadata.conditionalNotA.d,
|
|
583
|
+
metadata.conditionalNotA.u,
|
|
584
|
+
metadata.conditionalNotA.a
|
|
585
|
+
),
|
|
586
|
+
targetOpinion.a
|
|
587
|
+
);
|
|
588
|
+
return annotateRationale(
|
|
589
|
+
{
|
|
590
|
+
opinion: deducedOpinion,
|
|
591
|
+
operator: "conditional_deduction",
|
|
592
|
+
rationale: `Conditional deduction: prerequisite at ${project(
|
|
593
|
+
dampedSource
|
|
594
|
+
).toFixed(2)}`
|
|
595
|
+
},
|
|
596
|
+
context.spec,
|
|
597
|
+
context.hop
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
const result = dampedDependencyCascade(
|
|
601
|
+
dampedSource,
|
|
602
|
+
targetOpinion,
|
|
603
|
+
metadata.propagation ?? "continuous"
|
|
604
|
+
);
|
|
605
|
+
return annotateRationale(result, context.spec, context.hop);
|
|
606
|
+
},
|
|
607
|
+
description: "Structural gating. Textbook conditional deduction when edge conditionals exist, otherwise damped dependency cascade through downstream chains."
|
|
608
|
+
};
|
|
609
|
+
|
|
610
|
+
// src/edges/derivedFrom.ts
|
|
611
|
+
var derivedFromPropagationSpec = {
|
|
612
|
+
edgeType: "derived_from",
|
|
613
|
+
direction: "incoming",
|
|
614
|
+
transitivity: "none",
|
|
615
|
+
damping: 1,
|
|
616
|
+
maxHops: 1,
|
|
617
|
+
operator: () => null,
|
|
618
|
+
description: "Provenance only. The traversal surface stays explicit, but confidence does not move across derived_from edges."
|
|
619
|
+
};
|
|
620
|
+
|
|
621
|
+
// src/edges/elaborates.ts
|
|
622
|
+
var elaboratesPropagationSpec = {
|
|
623
|
+
edgeType: "elaborates",
|
|
624
|
+
direction: "outgoing",
|
|
625
|
+
transitivity: "damped",
|
|
626
|
+
damping: 0.7,
|
|
627
|
+
maxHops: 2,
|
|
628
|
+
operator: (sourceOpinion, targetOpinion, edge, context) => {
|
|
629
|
+
const dampedSource = applyPerHopDamping(
|
|
630
|
+
sourceOpinion,
|
|
631
|
+
context.spec.damping
|
|
632
|
+
);
|
|
633
|
+
const contextualWeight = Math.min(Math.abs(edge.weight ?? 0.35), 0.35);
|
|
634
|
+
const result = propagatePositiveInform(
|
|
635
|
+
dampedSource,
|
|
636
|
+
targetOpinion,
|
|
637
|
+
contextualWeight
|
|
638
|
+
);
|
|
639
|
+
return annotateRationale(result, context.spec, context.hop);
|
|
640
|
+
},
|
|
641
|
+
description: "Context-rich supporting detail. Elaborates carries a small positive effect with short, damped chaining."
|
|
642
|
+
};
|
|
643
|
+
|
|
644
|
+
// src/edges/informs.ts
|
|
645
|
+
var informsPropagationSpec = {
|
|
646
|
+
edgeType: "informs",
|
|
647
|
+
direction: "outgoing",
|
|
648
|
+
transitivity: "full",
|
|
649
|
+
damping: 0.92,
|
|
650
|
+
maxHops: "unbounded",
|
|
651
|
+
operator: (sourceOpinion, targetOpinion, edge, context) => {
|
|
652
|
+
const dampedSource = applyPerHopDamping(
|
|
653
|
+
sourceOpinion,
|
|
654
|
+
context.spec.damping
|
|
655
|
+
);
|
|
656
|
+
const weight = edge.weight ?? 1;
|
|
657
|
+
const result = weight < 0 ? propagateNegativeInform(dampedSource, targetOpinion, weight) : propagatePositiveInform(dampedSource, targetOpinion, weight);
|
|
658
|
+
return annotateRationale(result, context.spec, context.hop);
|
|
659
|
+
},
|
|
660
|
+
description: "Evidence-bearing influence. Informs can chain through the graph with light per-hop damping."
|
|
661
|
+
};
|
|
662
|
+
|
|
663
|
+
// src/edges/propagationTypes.ts
|
|
664
|
+
function isPropagationTraversalDirection(direction) {
|
|
665
|
+
return direction === "outgoing" || direction === "incoming";
|
|
666
|
+
}
|
|
667
|
+
function canTraverseHop(spec, nextHop) {
|
|
668
|
+
return spec.maxHops === "unbounded" || nextHop <= spec.maxHops;
|
|
669
|
+
}
|
|
670
|
+
function canContinueTransitively(spec, currentHop) {
|
|
671
|
+
if (spec.transitivity === "none") {
|
|
672
|
+
return false;
|
|
673
|
+
}
|
|
674
|
+
return spec.maxHops === "unbounded" || currentHop < spec.maxHops;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// src/edges/refutes.ts
|
|
678
|
+
var refutesPropagationSpec = {
|
|
679
|
+
edgeType: "refutes",
|
|
680
|
+
direction: "outgoing",
|
|
681
|
+
transitivity: "none",
|
|
682
|
+
damping: 0.9,
|
|
683
|
+
maxHops: 1,
|
|
684
|
+
operator: (sourceOpinion, targetOpinion, edge, context) => {
|
|
685
|
+
const dampedSource = applyPerHopDamping(
|
|
686
|
+
sourceOpinion,
|
|
687
|
+
context.spec.damping
|
|
688
|
+
);
|
|
689
|
+
const negativeWeight = -Math.abs(edge.weight ?? 1);
|
|
690
|
+
const result = propagateNegativeInform(
|
|
691
|
+
dampedSource,
|
|
692
|
+
targetOpinion,
|
|
693
|
+
negativeWeight
|
|
694
|
+
);
|
|
695
|
+
return annotateRationale(result, context.spec, context.hop);
|
|
696
|
+
},
|
|
697
|
+
description: "Explicit negative evidence semantics. Refutes is treated as strong one-hop counter-evidence."
|
|
698
|
+
};
|
|
699
|
+
|
|
700
|
+
// src/edges/supports.ts
|
|
701
|
+
var supportsPropagationSpec = {
|
|
702
|
+
edgeType: "supports",
|
|
703
|
+
direction: "outgoing",
|
|
704
|
+
transitivity: "full",
|
|
705
|
+
damping: 0.85,
|
|
706
|
+
maxHops: "unbounded",
|
|
707
|
+
operator: (sourceOpinion, targetOpinion, edge, context) => {
|
|
708
|
+
const dampedSource = applyPerHopDamping(
|
|
709
|
+
sourceOpinion,
|
|
710
|
+
context.spec.damping
|
|
711
|
+
);
|
|
712
|
+
const weight = edge.weight ?? 1;
|
|
713
|
+
const result = weight < 0 ? propagateNegativeSupportWithMetadata(
|
|
714
|
+
dampedSource,
|
|
715
|
+
targetOpinion,
|
|
716
|
+
weight,
|
|
717
|
+
edge
|
|
718
|
+
) : propagatePositiveSupport(dampedSource, targetOpinion, weight);
|
|
719
|
+
return annotateRationale(result, context.spec, context.hop);
|
|
720
|
+
},
|
|
721
|
+
description: "Belief-to-belief influence. Supports chains transitively with moderate per-hop damping."
|
|
722
|
+
};
|
|
723
|
+
|
|
724
|
+
// src/edges/tests.ts
|
|
725
|
+
var testsPropagationSpec = {
|
|
726
|
+
edgeType: "tests",
|
|
727
|
+
direction: "outgoing",
|
|
728
|
+
transitivity: "none",
|
|
729
|
+
damping: 1,
|
|
730
|
+
maxHops: 1,
|
|
731
|
+
operator: () => null,
|
|
732
|
+
description: "Interrogation linkage only. Tests edges do not directly move confidence."
|
|
733
|
+
};
|
|
734
|
+
|
|
735
|
+
// src/edges/index.ts
|
|
736
|
+
var EDGE_PROPAGATION_SPECS = [
|
|
737
|
+
supportsPropagationSpec,
|
|
738
|
+
informsPropagationSpec,
|
|
739
|
+
dependsOnPropagationSpec,
|
|
740
|
+
derivedFromPropagationSpec,
|
|
741
|
+
containsPropagationSpec,
|
|
742
|
+
testsPropagationSpec,
|
|
743
|
+
contradictsPropagationSpec,
|
|
744
|
+
refutesPropagationSpec,
|
|
745
|
+
elaboratesPropagationSpec
|
|
746
|
+
];
|
|
747
|
+
new Map(EDGE_PROPAGATION_SPECS.map((spec) => [spec.edgeType, spec]));
|
|
748
|
+
function getEdgePropagationSpecs() {
|
|
749
|
+
return EDGE_PROPAGATION_SPECS;
|
|
750
|
+
}
|
|
751
|
+
function getTraversalDirections(direction) {
|
|
752
|
+
if (isPropagationTraversalDirection(direction)) {
|
|
753
|
+
return [direction];
|
|
754
|
+
}
|
|
755
|
+
return ["outgoing", "incoming"];
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
// src/confidencePropagationDispatch.ts
|
|
759
|
+
function resolveTraversalTargetNodeId(edge, direction) {
|
|
760
|
+
const targetNodeId = direction === "outgoing" ? edge.toNodeId : edge.fromNodeId;
|
|
761
|
+
return targetNodeId ?? void 0;
|
|
762
|
+
}
|
|
763
|
+
function readNodeOpinion(node) {
|
|
764
|
+
const metadata = node.metadata ?? {};
|
|
765
|
+
try {
|
|
766
|
+
return readOpinionFromRecord({
|
|
767
|
+
...metadata,
|
|
768
|
+
opinion_b: node.opinion_b,
|
|
769
|
+
opinion_d: node.opinion_d,
|
|
770
|
+
opinion_u: node.opinion_u,
|
|
771
|
+
opinion_a: node.opinion_a
|
|
772
|
+
});
|
|
773
|
+
} catch {
|
|
774
|
+
return mkOpinion(0, 0, 1, 0.5);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
async function collectConfidencePropagationDispatches(args) {
|
|
778
|
+
const dispatchesByTargetId = /* @__PURE__ */ new Map();
|
|
779
|
+
const opinionCache = /* @__PURE__ */ new Map();
|
|
780
|
+
const nodeCache = /* @__PURE__ */ new Map();
|
|
781
|
+
const traversalSpecs = args.traversalSpecs ?? getEdgePropagationSpecs();
|
|
782
|
+
const queue = [
|
|
783
|
+
{
|
|
784
|
+
nodeId: args.sourceNodeId,
|
|
785
|
+
opinion: args.sourceOpinion,
|
|
786
|
+
hop: 0,
|
|
787
|
+
visitedNodeIds: /* @__PURE__ */ new Set([String(args.sourceNodeId)])
|
|
788
|
+
}
|
|
789
|
+
];
|
|
790
|
+
const loadNode = async (nodeId) => {
|
|
791
|
+
const cacheKey = String(nodeId);
|
|
792
|
+
if (!nodeCache.has(cacheKey)) {
|
|
793
|
+
nodeCache.set(cacheKey, await args.getNode(nodeId));
|
|
794
|
+
}
|
|
795
|
+
return nodeCache.get(cacheKey) ?? null;
|
|
796
|
+
};
|
|
797
|
+
while (queue.length > 0) {
|
|
798
|
+
const state = queue.shift();
|
|
799
|
+
if (!state) {
|
|
800
|
+
continue;
|
|
801
|
+
}
|
|
802
|
+
for (const spec of traversalSpecs) {
|
|
803
|
+
const nextHop = state.hop + 1;
|
|
804
|
+
if (!canTraverseHop(spec, nextHop)) {
|
|
805
|
+
continue;
|
|
806
|
+
}
|
|
807
|
+
for (const direction of getTraversalDirections(spec.direction)) {
|
|
808
|
+
const edges = await args.queryEdges({
|
|
809
|
+
nodeId: state.nodeId,
|
|
810
|
+
spec,
|
|
811
|
+
direction,
|
|
812
|
+
hop: nextHop
|
|
813
|
+
});
|
|
814
|
+
for (const edge of edges) {
|
|
815
|
+
if (args.sourceScope && !edgeMatchesWorkspaceReasoningScope(edge, args.sourceScope)) {
|
|
816
|
+
continue;
|
|
817
|
+
}
|
|
818
|
+
const targetNodeId = resolveTraversalTargetNodeId(edge, direction);
|
|
819
|
+
if (!targetNodeId) {
|
|
820
|
+
continue;
|
|
821
|
+
}
|
|
822
|
+
if (state.visitedNodeIds.has(String(targetNodeId))) {
|
|
823
|
+
continue;
|
|
824
|
+
}
|
|
825
|
+
const targetNode = await loadNode(targetNodeId);
|
|
826
|
+
if (!targetNode || targetNode.nodeType !== "belief") {
|
|
827
|
+
continue;
|
|
828
|
+
}
|
|
829
|
+
if (args.sourceScope && !nodeMatchesWorkspaceReasoningScope(targetNode, args.sourceScope)) {
|
|
830
|
+
continue;
|
|
831
|
+
}
|
|
832
|
+
const cacheKey = String(targetNodeId);
|
|
833
|
+
const targetOpinion = opinionCache.get(cacheKey) ?? readNodeOpinion(targetNode);
|
|
834
|
+
const result = spec.operator(state.opinion, targetOpinion, edge, {
|
|
835
|
+
hop: nextHop,
|
|
836
|
+
sourceNodeId: state.nodeId,
|
|
837
|
+
targetNodeId,
|
|
838
|
+
traversedDirection: direction,
|
|
839
|
+
spec
|
|
840
|
+
});
|
|
841
|
+
if (!result || !hasProjectedOpinionChanged(targetOpinion, result.opinion)) {
|
|
842
|
+
continue;
|
|
843
|
+
}
|
|
844
|
+
const projectedOpinion = mkOpinion(
|
|
845
|
+
result.opinion.b,
|
|
846
|
+
result.opinion.d,
|
|
847
|
+
result.opinion.u,
|
|
848
|
+
result.opinion.a
|
|
849
|
+
);
|
|
850
|
+
opinionCache.set(cacheKey, projectedOpinion);
|
|
851
|
+
const existingDispatch = dispatchesByTargetId.get(cacheKey);
|
|
852
|
+
dispatchesByTargetId.set(cacheKey, {
|
|
853
|
+
targetNodeId,
|
|
854
|
+
edgeType: spec.edgeType,
|
|
855
|
+
traversedDirection: direction,
|
|
856
|
+
weight: edge.weight ?? 1,
|
|
857
|
+
opinion: projectedOpinion,
|
|
858
|
+
operator: result.operator,
|
|
859
|
+
rationale: existingDispatch ? `${existingDispatch.rationale}; ${result.rationale}` : result.rationale,
|
|
860
|
+
hop: nextHop
|
|
861
|
+
});
|
|
862
|
+
if (canContinueTransitively(spec, nextHop)) {
|
|
863
|
+
queue.push({
|
|
864
|
+
nodeId: targetNodeId,
|
|
865
|
+
opinion: projectedOpinion,
|
|
866
|
+
hop: nextHop,
|
|
867
|
+
visitedNodeIds: /* @__PURE__ */ new Set([
|
|
868
|
+
...state.visitedNodeIds,
|
|
869
|
+
String(targetNodeId)
|
|
870
|
+
])
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
return Array.from(dispatchesByTargetId.values()).sort((left, right) => {
|
|
878
|
+
if (left.hop !== right.hop) {
|
|
879
|
+
return left.hop - right.hop;
|
|
880
|
+
}
|
|
881
|
+
return String(left.targetNodeId).localeCompare(String(right.targetNodeId));
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
v.id("epistemicNodes");
|
|
885
|
+
var DEFAULT_CONFIDENCE_POLICY = {
|
|
886
|
+
scoringMode: "after_worktree",
|
|
887
|
+
tupleContradiction: normalizeTupleContradictionPolicy()
|
|
888
|
+
};
|
|
889
|
+
function throwStructuredMutationError(args) {
|
|
890
|
+
const error = new Error(args.message);
|
|
891
|
+
error.status = args.status;
|
|
892
|
+
error.code = args.code;
|
|
893
|
+
error.invariantCode = args.invariantCode;
|
|
894
|
+
error.suggestion = args.suggestion;
|
|
895
|
+
error.details = args.details;
|
|
896
|
+
throw error;
|
|
897
|
+
}
|
|
898
|
+
function buildBeliefConfidenceRow(args) {
|
|
899
|
+
return {
|
|
900
|
+
beliefId: args.beliefId,
|
|
901
|
+
confidence: confidenceFromSL(
|
|
902
|
+
args.belief,
|
|
903
|
+
args.disbelief,
|
|
904
|
+
args.uncertainty,
|
|
905
|
+
args.baseRate
|
|
906
|
+
),
|
|
907
|
+
belief: args.belief,
|
|
908
|
+
disbelief: args.disbelief,
|
|
909
|
+
uncertainty: args.uncertainty,
|
|
910
|
+
baseRate: args.baseRate,
|
|
911
|
+
slOperator: args.slOperator ?? "manual_assessment",
|
|
912
|
+
trigger: args.trigger,
|
|
913
|
+
...args.rationale ? { rationale: args.rationale } : {},
|
|
914
|
+
assessedBy: args.assessedBy,
|
|
915
|
+
assessedAt: args.assessedAt,
|
|
916
|
+
...args.triggeringEvidenceId ? {
|
|
917
|
+
triggeringEvidenceId: args.triggeringEvidenceId,
|
|
918
|
+
triggeringEvidenceIds: [String(args.triggeringEvidenceId)]
|
|
919
|
+
} : {},
|
|
920
|
+
...args.triggeringContradictionId ? {
|
|
921
|
+
triggeringContradictionId: args.triggeringContradictionId
|
|
922
|
+
} : {},
|
|
923
|
+
...args.triggeringWorktreeId ? { triggeringWorktreeId: args.triggeringWorktreeId } : {}
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
function readTupleContradictedFlag(value) {
|
|
927
|
+
return typeof value === "boolean" ? value : void 0;
|
|
928
|
+
}
|
|
929
|
+
function readBeliefOpinionSnapshot(node, metadata) {
|
|
930
|
+
try {
|
|
931
|
+
return readOpinionFromRecord({
|
|
932
|
+
...metadata,
|
|
933
|
+
opinion_b: node.opinion_b,
|
|
934
|
+
opinion_d: node.opinion_d,
|
|
935
|
+
opinion_u: node.opinion_u,
|
|
936
|
+
opinion_a: node.opinion_a
|
|
937
|
+
});
|
|
938
|
+
} catch (error) {
|
|
939
|
+
debugGraphPrimitiveFallback(
|
|
940
|
+
"[epistemicBeliefs] Failed to read belief opinion snapshot",
|
|
941
|
+
{
|
|
942
|
+
error,
|
|
943
|
+
beliefId: node._id
|
|
944
|
+
}
|
|
945
|
+
);
|
|
946
|
+
return mkOpinion(0, 0, 1, 0.5);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
function deriveTupleContradictionSeverity(node) {
|
|
950
|
+
const metadata = node.metadata || {};
|
|
951
|
+
const criticality = typeof metadata.criticality === "string" ? metadata.criticality : void 0;
|
|
952
|
+
if (criticality === "blocking") {
|
|
953
|
+
return "critical";
|
|
954
|
+
}
|
|
955
|
+
if (criticality === "supporting") {
|
|
956
|
+
return "minor";
|
|
957
|
+
}
|
|
958
|
+
return "significant";
|
|
959
|
+
}
|
|
960
|
+
function formatTupleContradictionDescription(args) {
|
|
961
|
+
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)}.`;
|
|
962
|
+
}
|
|
963
|
+
function resolveBeliefStatus(node, metadata) {
|
|
964
|
+
return resolveBeliefLifecycleStatus({
|
|
965
|
+
beliefStatus: node.beliefStatus,
|
|
966
|
+
confidence: node.confidence,
|
|
967
|
+
predictionMeta: node.predictionMeta,
|
|
968
|
+
metadata
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
async function hasCompletedWorktreeForBelief(ctx, beliefNodeId) {
|
|
972
|
+
const clusterMembership = await ctx.db.query("worktreeBeliefCluster").withIndex("by_belief", (q) => q.eq("beliefId", beliefNodeId)).collect();
|
|
973
|
+
for (const membership of clusterMembership) {
|
|
974
|
+
const worktree = await ctx.db.get(membership.worktreeId);
|
|
975
|
+
if (worktree?.status === "completed" || worktree?.status === "merged") {
|
|
976
|
+
return true;
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
return false;
|
|
980
|
+
}
|
|
981
|
+
async function getActiveConfidencePolicy(ctx) {
|
|
982
|
+
try {
|
|
983
|
+
const activeConfig = await ctx.db.query("logicSprintScoring").withIndex("by_active", (q) => q.eq("isActive", true)).first();
|
|
984
|
+
return {
|
|
985
|
+
scoringMode: activeConfig?.confidencePolicy === "always" ? "always" : DEFAULT_CONFIDENCE_POLICY.scoringMode,
|
|
986
|
+
tupleContradiction: normalizeTupleContradictionPolicy(
|
|
987
|
+
activeConfig?.tupleContradictionPolicy
|
|
988
|
+
)
|
|
989
|
+
};
|
|
990
|
+
} catch (error) {
|
|
991
|
+
debugGraphPrimitiveFallback(
|
|
992
|
+
"[epistemicBeliefs] Failed to load active confidence policy",
|
|
993
|
+
{
|
|
994
|
+
error
|
|
995
|
+
}
|
|
996
|
+
);
|
|
997
|
+
return DEFAULT_CONFIDENCE_POLICY;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
async function requireAuthenticatedUserId(ctx) {
|
|
1001
|
+
const userId = await getCurrentUserId(
|
|
1002
|
+
ctx
|
|
1003
|
+
);
|
|
1004
|
+
if (!userId) {
|
|
1005
|
+
throwStructuredMutationError({
|
|
1006
|
+
message: "Authentication required.",
|
|
1007
|
+
status: 401,
|
|
1008
|
+
code: "AUTHENTICATION_REQUIRED",
|
|
1009
|
+
invariantCode: "auth.required",
|
|
1010
|
+
suggestion: "Provide a valid bearer token before invoking belief mutations."
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
1013
|
+
return userId;
|
|
1014
|
+
}
|
|
1015
|
+
async function requireProjectWriteAccess(ctx, projectId, userId) {
|
|
1016
|
+
const hasAccess = await checkProjectAccess(
|
|
1017
|
+
ctx,
|
|
1018
|
+
projectId,
|
|
1019
|
+
userId
|
|
1020
|
+
);
|
|
1021
|
+
if (!hasAccess) {
|
|
1022
|
+
throwStructuredMutationError({
|
|
1023
|
+
message: "Project access required.",
|
|
1024
|
+
status: 403,
|
|
1025
|
+
code: "FORBIDDEN",
|
|
1026
|
+
invariantCode: "policy.scope_required",
|
|
1027
|
+
suggestion: "Request write access for the project and retry.",
|
|
1028
|
+
details: { projectId, userId }
|
|
1029
|
+
});
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
// src/epistemicBeliefs.confidence.ts
|
|
1034
|
+
async function applyBeliefConfidenceChange(ctx, args) {
|
|
1035
|
+
const now = Date.now();
|
|
1036
|
+
const node = await ctx.db.get(args.nodeId);
|
|
1037
|
+
if (!node) {
|
|
1038
|
+
throwStructuredMutationError({
|
|
1039
|
+
message: "Node not found.",
|
|
1040
|
+
status: 404,
|
|
1041
|
+
code: "NOT_FOUND",
|
|
1042
|
+
invariantCode: "belief.exists",
|
|
1043
|
+
suggestion: "Verify nodeId points to an existing node before modulating confidence.",
|
|
1044
|
+
details: { nodeId: args.nodeId }
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1047
|
+
if (node.nodeType !== "belief") {
|
|
1048
|
+
throwStructuredMutationError({
|
|
1049
|
+
message: `modulateConfidence only applies to belief nodes. Received nodeType "${node.nodeType}". Entity nodes (company, person, investor, etc.) do not have confidence \u2014 use entityLifecycle.updateEntityAttributes for mutable entity data.`,
|
|
1050
|
+
status: 400,
|
|
1051
|
+
code: "INVALID_ARGUMENT",
|
|
1052
|
+
invariantCode: "entity.no_confidence",
|
|
1053
|
+
suggestion: "Use entityLifecycle.updateEntityAttributes for entity mutations. modulateConfidence is for belief nodes only.",
|
|
1054
|
+
details: { nodeId: args.nodeId, nodeType: node.nodeType }
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
if (!node.projectId) {
|
|
1058
|
+
throwStructuredMutationError({
|
|
1059
|
+
message: "Belief has no project scope.",
|
|
1060
|
+
status: 400,
|
|
1061
|
+
code: "MISSING_SCOPE",
|
|
1062
|
+
invariantCode: "belief.project_required",
|
|
1063
|
+
suggestion: "Belief must have a projectId to modulate confidence.",
|
|
1064
|
+
details: { nodeId: args.nodeId }
|
|
1065
|
+
});
|
|
1066
|
+
}
|
|
1067
|
+
await requireProjectWriteAccess(
|
|
1068
|
+
ctx,
|
|
1069
|
+
node.projectId,
|
|
1070
|
+
args.authenticatedUserId
|
|
1071
|
+
);
|
|
1072
|
+
const existingMetadata = node.metadata || {};
|
|
1073
|
+
const currentBeliefStatus = resolveBeliefStatus(node, existingMetadata);
|
|
1074
|
+
const confidencePolicy = await getActiveConfidencePolicy(ctx);
|
|
1075
|
+
if (confidencePolicy.scoringMode === "after_worktree" && isPreValidationBeliefStatus(currentBeliefStatus)) {
|
|
1076
|
+
const hasCompletedWorktree = await hasCompletedWorktreeForBelief(
|
|
1077
|
+
ctx,
|
|
1078
|
+
args.nodeId
|
|
1079
|
+
);
|
|
1080
|
+
if (!hasCompletedWorktree) {
|
|
1081
|
+
throwStructuredMutationError({
|
|
1082
|
+
message: "Cannot score belief before worktree completion. Complete a worktree that tests this belief first.",
|
|
1083
|
+
status: 409,
|
|
1084
|
+
code: "CONFLICT",
|
|
1085
|
+
invariantCode: "belief.confidence_append_only",
|
|
1086
|
+
suggestion: "Complete a worktree linked to this belief before recording confidence modulation.",
|
|
1087
|
+
details: { nodeId: args.nodeId }
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
const previousConfidence = node.confidence || 0.5;
|
|
1092
|
+
const predictionMeta = node.predictionMeta || existingMetadata.predictionMeta;
|
|
1093
|
+
const previousOpinion = readBeliefOpinionSnapshot(node, existingMetadata);
|
|
1094
|
+
const slB = args.belief;
|
|
1095
|
+
const slD = args.disbelief;
|
|
1096
|
+
const slU = args.uncertainty;
|
|
1097
|
+
const slA = args.baseRate ?? 0.5;
|
|
1098
|
+
const nextOpinion = { b: slB, d: slD, u: slU, a: slA };
|
|
1099
|
+
const derivedConfidence = confidenceFromSL(slB, slD, slU, slA);
|
|
1100
|
+
const isFirstScoring = typeof node.confidence !== "number" || !Number.isFinite(node.confidence);
|
|
1101
|
+
const previousTupleContradicted = readTupleContradictedFlag(node.tupleContradicted) ?? readTupleContradictedFlag(existingMetadata.tupleContradicted) ?? detectTupleContradiction(
|
|
1102
|
+
previousOpinion,
|
|
1103
|
+
confidencePolicy.tupleContradiction.beliefThreshold,
|
|
1104
|
+
confidencePolicy.tupleContradiction.disbeliefThreshold
|
|
1105
|
+
);
|
|
1106
|
+
const tupleTransition = evaluateTupleContradictionTransition({
|
|
1107
|
+
previousTupleContradicted,
|
|
1108
|
+
opinion: nextOpinion,
|
|
1109
|
+
policy: confidencePolicy.tupleContradiction
|
|
1110
|
+
});
|
|
1111
|
+
const tupleContradictionDescription = formatTupleContradictionDescription({
|
|
1112
|
+
opinion: nextOpinion,
|
|
1113
|
+
policy: tupleTransition.policy
|
|
1114
|
+
});
|
|
1115
|
+
const newBeliefStatus = promoteBeliefStatusAfterScoring(currentBeliefStatus, {
|
|
1116
|
+
confidence: derivedConfidence,
|
|
1117
|
+
predictionMeta,
|
|
1118
|
+
metadata: existingMetadata
|
|
1119
|
+
});
|
|
1120
|
+
let tupleContradictionId;
|
|
1121
|
+
if (tupleTransition.crossedIntoTupleContradiction) {
|
|
1122
|
+
tupleContradictionId = await ctx.runMutation(
|
|
1123
|
+
"contradictions:create",
|
|
1124
|
+
{
|
|
1125
|
+
projectId: node.projectId,
|
|
1126
|
+
topicId: node.topicId,
|
|
1127
|
+
beliefId: args.nodeId,
|
|
1128
|
+
beliefBId: args.nodeId,
|
|
1129
|
+
supportingInsightIds: [],
|
|
1130
|
+
contradictingInsightIds: [],
|
|
1131
|
+
severity: deriveTupleContradictionSeverity(node),
|
|
1132
|
+
source: "tuple_space",
|
|
1133
|
+
detectionMethod: "agent",
|
|
1134
|
+
description: tupleContradictionDescription,
|
|
1135
|
+
createdBy: args.authenticatedUserId
|
|
1136
|
+
}
|
|
1137
|
+
);
|
|
1138
|
+
}
|
|
1139
|
+
await ctx.db.patch(args.nodeId, {
|
|
1140
|
+
confidence: derivedConfidence,
|
|
1141
|
+
beliefStatus: newBeliefStatus,
|
|
1142
|
+
tupleContradicted: tupleTransition.tupleContradicted,
|
|
1143
|
+
updatedAt: now,
|
|
1144
|
+
// Store SL opinion fields at node level for fast access
|
|
1145
|
+
opinion_b: slB,
|
|
1146
|
+
opinion_d: slD,
|
|
1147
|
+
opinion_u: slU,
|
|
1148
|
+
opinion_a: slA,
|
|
1149
|
+
metadata: {
|
|
1150
|
+
...existingMetadata,
|
|
1151
|
+
beliefStatus: newBeliefStatus,
|
|
1152
|
+
slBelief: slB,
|
|
1153
|
+
slDisbelief: slD,
|
|
1154
|
+
slUncertainty: slU,
|
|
1155
|
+
slBaseRate: slA,
|
|
1156
|
+
tupleContradicted: tupleTransition.tupleContradicted
|
|
1157
|
+
}
|
|
1158
|
+
});
|
|
1159
|
+
if (isFirstScoring) {
|
|
1160
|
+
const nodeTopicId = node.topicId;
|
|
1161
|
+
const themeNodes = await ctx.db.query("epistemicNodes").withIndex(
|
|
1162
|
+
"by_topic",
|
|
1163
|
+
(q) => q.eq("topicId", nodeTopicId || node.projectId)
|
|
1164
|
+
).filter((q) => q.eq(q.field("nodeType"), "theme")).collect();
|
|
1165
|
+
for (const theme of themeNodes) {
|
|
1166
|
+
if (theme.globalId && node.globalId) {
|
|
1167
|
+
await ctx.scheduler.runAfter(0, internal.neo4jEdgeAPI.createEdge, {
|
|
1168
|
+
globalId: `edge-${node.globalId}-relates_to_thesis-${theme.globalId}`,
|
|
1169
|
+
fromGlobalId: node.globalId,
|
|
1170
|
+
toGlobalId: theme.globalId,
|
|
1171
|
+
edgeType: "relates_to_thesis",
|
|
1172
|
+
weight: derivedConfidence,
|
|
1173
|
+
createdBy: args.authenticatedUserId,
|
|
1174
|
+
topicId: String(node.projectId),
|
|
1175
|
+
fromNodeType: "belief",
|
|
1176
|
+
toNodeType: "theme",
|
|
1177
|
+
fromLayer: "L3",
|
|
1178
|
+
toLayer: "L3"
|
|
1179
|
+
});
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
const storedRationale = args.rationale ?? `Confidence changed from ${previousConfidence.toFixed(2)} (nodeId: ${args.nodeId})`;
|
|
1184
|
+
const beliefConfidenceId = await ctx.db.insert("beliefConfidence", {
|
|
1185
|
+
...buildBeliefConfidenceRow({
|
|
1186
|
+
beliefId: args.nodeId,
|
|
1187
|
+
belief: slB,
|
|
1188
|
+
disbelief: slD,
|
|
1189
|
+
uncertainty: slU,
|
|
1190
|
+
baseRate: slA,
|
|
1191
|
+
trigger: args.trigger,
|
|
1192
|
+
rationale: storedRationale,
|
|
1193
|
+
assessedBy: args.authenticatedUserId,
|
|
1194
|
+
assessedAt: now,
|
|
1195
|
+
slOperator: args.slOperator,
|
|
1196
|
+
triggeringEvidenceId: args.triggeringEvidenceId,
|
|
1197
|
+
triggeringContradictionId: tupleContradictionId,
|
|
1198
|
+
triggeringWorktreeId: args.triggeringWorktreeId
|
|
1199
|
+
})
|
|
1200
|
+
});
|
|
1201
|
+
await ctx.scheduler.runAfter(0, internal.neo4jSync.syncNodeToNeo4j, {
|
|
1202
|
+
nodeId: args.nodeId,
|
|
1203
|
+
operation: "upsert"
|
|
1204
|
+
});
|
|
1205
|
+
await ctx.db.insert("epistemicAudit", {
|
|
1206
|
+
entityType: "belief",
|
|
1207
|
+
entityId: args.nodeId,
|
|
1208
|
+
changeType: "confidence_changed",
|
|
1209
|
+
previousState: {
|
|
1210
|
+
confidence: previousConfidence,
|
|
1211
|
+
tupleContradicted: previousTupleContradicted
|
|
1212
|
+
},
|
|
1213
|
+
newState: {
|
|
1214
|
+
opinion: nextOpinion,
|
|
1215
|
+
confidence: derivedConfidence,
|
|
1216
|
+
trigger: args.trigger,
|
|
1217
|
+
rationale: storedRationale,
|
|
1218
|
+
tupleContradicted: tupleTransition.tupleContradicted,
|
|
1219
|
+
tupleContradictionPolicy: tupleTransition.policy,
|
|
1220
|
+
...tupleContradictionId ? { tupleContradictionId: String(tupleContradictionId) } : {}
|
|
1221
|
+
},
|
|
1222
|
+
changedBy: args.authenticatedUserId,
|
|
1223
|
+
isAgent: false,
|
|
1224
|
+
changedAt: now,
|
|
1225
|
+
projectId: node.projectId,
|
|
1226
|
+
topicId: node.topicId
|
|
1227
|
+
});
|
|
1228
|
+
if (tupleTransition.crossedIntoTupleContradiction || tupleTransition.crossedOutOfTupleContradiction) {
|
|
1229
|
+
await ctx.db.insert("epistemicAudit", {
|
|
1230
|
+
entityType: "belief",
|
|
1231
|
+
entityId: args.nodeId,
|
|
1232
|
+
changeType: "updated",
|
|
1233
|
+
previousState: { tupleContradicted: previousTupleContradicted },
|
|
1234
|
+
newState: {
|
|
1235
|
+
tupleContradicted: tupleTransition.tupleContradicted,
|
|
1236
|
+
action: tupleTransition.crossedIntoTupleContradiction ? "tuple_contradiction_detected" : "tuple_contradiction_cleared",
|
|
1237
|
+
opinion: nextOpinion,
|
|
1238
|
+
tupleContradictionPolicy: tupleTransition.policy,
|
|
1239
|
+
...tupleContradictionId ? { tupleContradictionId: String(tupleContradictionId) } : {}
|
|
1240
|
+
},
|
|
1241
|
+
rationale: tupleTransition.crossedIntoTupleContradiction ? tupleContradictionDescription : `Tuple-space contradiction cleared: b=${nextOpinion.b.toFixed(2)}, d=${nextOpinion.d.toFixed(2)} no longer exceed the configured policy thresholds.`,
|
|
1242
|
+
changedBy: args.authenticatedUserId,
|
|
1243
|
+
isAgent: false,
|
|
1244
|
+
changedAt: now,
|
|
1245
|
+
projectId: node.projectId,
|
|
1246
|
+
topicId: node.topicId
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
if (Math.abs(derivedConfidence - previousConfidence) >= 0.15) {
|
|
1250
|
+
await ctx.scheduler.runAfter(
|
|
1251
|
+
5e3,
|
|
1252
|
+
internal.bi.contradictionSemanticDetector.scanAffectedBeliefs,
|
|
1253
|
+
{
|
|
1254
|
+
beliefId: args.nodeId,
|
|
1255
|
+
projectId: node.projectId
|
|
1256
|
+
}
|
|
1257
|
+
);
|
|
1258
|
+
}
|
|
1259
|
+
if (node.workspaceId && node.tenantId) {
|
|
1260
|
+
await ctx.scheduler.runAfter(
|
|
1261
|
+
0,
|
|
1262
|
+
internal.publication.evaluateNodePublication,
|
|
1263
|
+
{ nodeId: args.nodeId }
|
|
1264
|
+
);
|
|
1265
|
+
}
|
|
1266
|
+
return {
|
|
1267
|
+
nodeId: args.nodeId,
|
|
1268
|
+
previousConfidence,
|
|
1269
|
+
newConfidence: derivedConfidence,
|
|
1270
|
+
opinion: { b: slB, d: slD, u: slU, a: slA },
|
|
1271
|
+
beliefConfidenceId
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
function propagationTriggerForEdge(edgeType, weight) {
|
|
1275
|
+
if (edgeType === "contradicts" || edgeType === "refutes") {
|
|
1276
|
+
return "contradiction_detected";
|
|
1277
|
+
}
|
|
1278
|
+
if ((edgeType === "supports" || edgeType === "informs") && weight < 0) {
|
|
1279
|
+
return "contradiction_detected";
|
|
1280
|
+
}
|
|
1281
|
+
return "evidence_added";
|
|
1282
|
+
}
|
|
1283
|
+
internalMutation({
|
|
1284
|
+
args: {
|
|
1285
|
+
nodeId: v.id("epistemicNodes"),
|
|
1286
|
+
opinion_b: v.number(),
|
|
1287
|
+
opinion_d: v.number(),
|
|
1288
|
+
opinion_u: v.number(),
|
|
1289
|
+
opinion_a: v.number(),
|
|
1290
|
+
userId: v.string()
|
|
1291
|
+
},
|
|
1292
|
+
returns: permissiveReturn,
|
|
1293
|
+
handler: async (ctx, args) => {
|
|
1294
|
+
const sourceOpinion = mkOpinion(
|
|
1295
|
+
args.opinion_b,
|
|
1296
|
+
args.opinion_d,
|
|
1297
|
+
args.opinion_u,
|
|
1298
|
+
args.opinion_a
|
|
1299
|
+
);
|
|
1300
|
+
const sourceNode = await ctx.db.get(args.nodeId);
|
|
1301
|
+
const sourceScope = await resolveNodeScopeForWorkspaceIsolation(
|
|
1302
|
+
ctx,
|
|
1303
|
+
sourceNode
|
|
1304
|
+
);
|
|
1305
|
+
const dispatches = await collectConfidencePropagationDispatches({
|
|
1306
|
+
sourceNodeId: args.nodeId,
|
|
1307
|
+
sourceOpinion,
|
|
1308
|
+
sourceScope,
|
|
1309
|
+
queryEdges: async ({ nodeId, spec, direction }) => {
|
|
1310
|
+
return await ctx.db.query("epistemicEdges").withIndex(
|
|
1311
|
+
direction === "outgoing" ? "by_from_type" : "by_to_type",
|
|
1312
|
+
(q) => direction === "outgoing" ? q.eq("fromNodeId", nodeId).eq("edgeType", spec.edgeType) : q.eq("toNodeId", nodeId).eq("edgeType", spec.edgeType)
|
|
1313
|
+
).collect();
|
|
1314
|
+
},
|
|
1315
|
+
getNode: async (nodeId) => await ctx.db.get(nodeId)
|
|
1316
|
+
});
|
|
1317
|
+
for (const dispatch of dispatches) {
|
|
1318
|
+
await applyBeliefConfidenceChange(ctx, {
|
|
1319
|
+
nodeId: dispatch.targetNodeId,
|
|
1320
|
+
belief: dispatch.opinion.b,
|
|
1321
|
+
disbelief: dispatch.opinion.d,
|
|
1322
|
+
uncertainty: dispatch.opinion.u,
|
|
1323
|
+
baseRate: dispatch.opinion.a,
|
|
1324
|
+
trigger: propagationTriggerForEdge(dispatch.edgeType, dispatch.weight),
|
|
1325
|
+
rationale: `SL propagation via ${dispatch.edgeType} edge: ${dispatch.rationale}`,
|
|
1326
|
+
authenticatedUserId: args.userId,
|
|
1327
|
+
slOperator: dispatch.operator
|
|
1328
|
+
});
|
|
1329
|
+
}
|
|
1330
|
+
return {
|
|
1331
|
+
propagated: dispatches.map((dispatch) => ({
|
|
1332
|
+
targetNodeId: String(dispatch.targetNodeId),
|
|
1333
|
+
edgeType: dispatch.edgeType,
|
|
1334
|
+
operator: dispatch.operator
|
|
1335
|
+
})),
|
|
1336
|
+
count: dispatches.length
|
|
1337
|
+
};
|
|
1338
|
+
}
|
|
1339
|
+
});
|
|
1340
|
+
|
|
1341
|
+
// src/epistemicBeliefs.lifecycle.ts
|
|
1342
|
+
var modulateConfidence = mutation({
|
|
1343
|
+
args: {
|
|
1344
|
+
nodeId: v.id("epistemicNodes"),
|
|
1345
|
+
// SL opinion — the ONLY confidence input (EK-7)
|
|
1346
|
+
belief: v.number(),
|
|
1347
|
+
// b: evidence FOR [0,1]
|
|
1348
|
+
disbelief: v.number(),
|
|
1349
|
+
// d: evidence AGAINST [0,1]
|
|
1350
|
+
uncertainty: v.number(),
|
|
1351
|
+
// u: lack of evidence [0,1]
|
|
1352
|
+
baseRate: v.number(),
|
|
1353
|
+
// a: prior probability [0,1]
|
|
1354
|
+
trigger: v.union(
|
|
1355
|
+
v.literal("evidence_added"),
|
|
1356
|
+
v.literal("evidence_removed"),
|
|
1357
|
+
v.literal("contradiction_detected"),
|
|
1358
|
+
v.literal("contradiction_resolved"),
|
|
1359
|
+
v.literal("manual"),
|
|
1360
|
+
v.literal("decay"),
|
|
1361
|
+
v.literal("agent_assessment"),
|
|
1362
|
+
v.literal("worktree_outcome"),
|
|
1363
|
+
v.literal("worktree_completed"),
|
|
1364
|
+
// SL-specific triggers
|
|
1365
|
+
v.literal("fusion"),
|
|
1366
|
+
v.literal("discount"),
|
|
1367
|
+
v.literal("deduction"),
|
|
1368
|
+
v.literal("backfill_synthetic")
|
|
1369
|
+
),
|
|
1370
|
+
rationale: v.optional(v.string()),
|
|
1371
|
+
userId: v.string(),
|
|
1372
|
+
// SL operator provenance (optional — defaults to manual_assessment)
|
|
1373
|
+
slOperator: v.optional(
|
|
1374
|
+
v.union(
|
|
1375
|
+
v.literal("cumulative_fusion"),
|
|
1376
|
+
v.literal("averaging_fusion"),
|
|
1377
|
+
v.literal("trust_discount"),
|
|
1378
|
+
v.literal("conditional_deduction"),
|
|
1379
|
+
v.literal("dependency_cascade"),
|
|
1380
|
+
v.literal("negation"),
|
|
1381
|
+
v.literal("constraint_fusion"),
|
|
1382
|
+
v.literal("manual_assessment")
|
|
1383
|
+
)
|
|
1384
|
+
),
|
|
1385
|
+
triggeringEvidenceId: v.optional(v.id("epistemicNodes")),
|
|
1386
|
+
triggeringWorktreeId: v.optional(v.string())
|
|
1387
|
+
},
|
|
1388
|
+
returns: permissiveReturn,
|
|
1389
|
+
handler: async (ctx, args) => {
|
|
1390
|
+
const authenticatedUserId = await requireAuthenticatedUserId(ctx);
|
|
1391
|
+
const result = await applyBeliefConfidenceChange(ctx, {
|
|
1392
|
+
nodeId: args.nodeId,
|
|
1393
|
+
belief: args.belief,
|
|
1394
|
+
disbelief: args.disbelief,
|
|
1395
|
+
uncertainty: args.uncertainty,
|
|
1396
|
+
baseRate: args.baseRate,
|
|
1397
|
+
trigger: args.trigger,
|
|
1398
|
+
rationale: args.rationale,
|
|
1399
|
+
authenticatedUserId,
|
|
1400
|
+
slOperator: args.slOperator,
|
|
1401
|
+
triggeringEvidenceId: args.triggeringEvidenceId,
|
|
1402
|
+
triggeringWorktreeId: args.triggeringWorktreeId
|
|
1403
|
+
});
|
|
1404
|
+
if (Math.abs(result.newConfidence - result.previousConfidence) >= 0.01) {
|
|
1405
|
+
await ctx.scheduler.runAfter(
|
|
1406
|
+
0,
|
|
1407
|
+
internal.epistemicBeliefs.propagateConfidenceChange,
|
|
1408
|
+
{
|
|
1409
|
+
nodeId: args.nodeId,
|
|
1410
|
+
opinion_b: args.belief,
|
|
1411
|
+
opinion_d: args.disbelief,
|
|
1412
|
+
opinion_u: args.uncertainty,
|
|
1413
|
+
opinion_a: args.baseRate ?? 0.5,
|
|
1414
|
+
userId: authenticatedUserId
|
|
1415
|
+
}
|
|
1416
|
+
);
|
|
1417
|
+
}
|
|
1418
|
+
return {
|
|
1419
|
+
nodeId: result.nodeId,
|
|
1420
|
+
previousConfidence: result.previousConfidence,
|
|
1421
|
+
newConfidence: result.newConfidence,
|
|
1422
|
+
opinion: result.opinion
|
|
1423
|
+
};
|
|
1424
|
+
}
|
|
1425
|
+
});
|
|
1426
|
+
var updateStatus = mutation({
|
|
1427
|
+
args: {
|
|
1428
|
+
nodeId: v.id("epistemicNodes"),
|
|
1429
|
+
status: v.union(
|
|
1430
|
+
v.literal("active"),
|
|
1431
|
+
v.literal("superseded"),
|
|
1432
|
+
v.literal("archived")
|
|
1433
|
+
),
|
|
1434
|
+
reason: v.optional(v.string()),
|
|
1435
|
+
userId: v.string()
|
|
1436
|
+
},
|
|
1437
|
+
returns: permissiveReturn,
|
|
1438
|
+
handler: async (ctx, args) => {
|
|
1439
|
+
const authenticatedUserId = await requireAuthenticatedUserId(ctx);
|
|
1440
|
+
const now = Date.now();
|
|
1441
|
+
const node = await ctx.db.get(args.nodeId);
|
|
1442
|
+
if (!node || node.nodeType !== "belief") {
|
|
1443
|
+
throw new Error("Belief not found");
|
|
1444
|
+
}
|
|
1445
|
+
if (!node.projectId) {
|
|
1446
|
+
throw new Error("Belief has no project scope");
|
|
1447
|
+
}
|
|
1448
|
+
await requireProjectWriteAccess(ctx, node.projectId, authenticatedUserId);
|
|
1449
|
+
const previousStatus = node.status;
|
|
1450
|
+
const metadata = node.metadata || {};
|
|
1451
|
+
await ctx.db.patch(args.nodeId, {
|
|
1452
|
+
status: args.status,
|
|
1453
|
+
updatedAt: now,
|
|
1454
|
+
metadata: {
|
|
1455
|
+
...metadata,
|
|
1456
|
+
status: args.status
|
|
1457
|
+
}
|
|
1458
|
+
});
|
|
1459
|
+
await ctx.scheduler.runAfter(0, internal.neo4jSync.syncNodeToNeo4j, {
|
|
1460
|
+
nodeId: args.nodeId,
|
|
1461
|
+
operation: "upsert"
|
|
1462
|
+
});
|
|
1463
|
+
await ctx.db.insert("epistemicAudit", {
|
|
1464
|
+
entityType: "belief",
|
|
1465
|
+
entityId: args.nodeId,
|
|
1466
|
+
changeType: "status_changed",
|
|
1467
|
+
previousState: { status: previousStatus },
|
|
1468
|
+
newState: { status: args.status, reason: args.reason },
|
|
1469
|
+
changedBy: authenticatedUserId,
|
|
1470
|
+
isAgent: false,
|
|
1471
|
+
changedAt: now,
|
|
1472
|
+
projectId: node.projectId
|
|
1473
|
+
});
|
|
1474
|
+
return { nodeId: args.nodeId, previousStatus, newStatus: args.status };
|
|
1475
|
+
}
|
|
1476
|
+
});
|
|
1477
|
+
var archive = mutation({
|
|
1478
|
+
args: {
|
|
1479
|
+
nodeId: v.id("epistemicNodes"),
|
|
1480
|
+
reason: v.optional(v.string()),
|
|
1481
|
+
userId: v.string()
|
|
1482
|
+
},
|
|
1483
|
+
returns: permissiveReturn,
|
|
1484
|
+
handler: async (ctx, args) => {
|
|
1485
|
+
const authenticatedUserId = await requireAuthenticatedUserId(ctx);
|
|
1486
|
+
const node = await ctx.db.get(args.nodeId);
|
|
1487
|
+
if (!node || node.nodeType !== "belief") {
|
|
1488
|
+
throw new Error("Belief not found");
|
|
1489
|
+
}
|
|
1490
|
+
if (!node.projectId) {
|
|
1491
|
+
throw new Error("Belief has no project scope");
|
|
1492
|
+
}
|
|
1493
|
+
await requireProjectWriteAccess(ctx, node.projectId, authenticatedUserId);
|
|
1494
|
+
return await ctx.runMutation(
|
|
1495
|
+
// Use updateStatus internally
|
|
1496
|
+
internal.epistemicBeliefs.updateStatusInternal,
|
|
1497
|
+
{
|
|
1498
|
+
nodeId: args.nodeId,
|
|
1499
|
+
status: "archived",
|
|
1500
|
+
reason: args.reason,
|
|
1501
|
+
userId: authenticatedUserId
|
|
1502
|
+
}
|
|
1503
|
+
);
|
|
1504
|
+
}
|
|
1505
|
+
});
|
|
1506
|
+
var updateRationale = mutation({
|
|
1507
|
+
args: {
|
|
1508
|
+
nodeId: v.id("epistemicNodes"),
|
|
1509
|
+
rationale: v.optional(v.string()),
|
|
1510
|
+
userId: v.string()
|
|
1511
|
+
},
|
|
1512
|
+
returns: permissiveReturn,
|
|
1513
|
+
handler: async (ctx, args) => {
|
|
1514
|
+
const authenticatedUserId = await requireAuthenticatedUserId(ctx);
|
|
1515
|
+
const now = Date.now();
|
|
1516
|
+
const node = await ctx.db.get(args.nodeId);
|
|
1517
|
+
if (!node || node.nodeType !== "belief") {
|
|
1518
|
+
throw new Error("Belief not found");
|
|
1519
|
+
}
|
|
1520
|
+
if (!node.projectId) {
|
|
1521
|
+
throw new Error("Belief has no project scope");
|
|
1522
|
+
}
|
|
1523
|
+
await requireProjectWriteAccess(ctx, node.projectId, authenticatedUserId);
|
|
1524
|
+
const metadata = node.metadata || {};
|
|
1525
|
+
const previousRationale = typeof metadata.rationale === "string" ? metadata.rationale : void 0;
|
|
1526
|
+
const nextRationale = args.rationale?.trim();
|
|
1527
|
+
await ctx.db.patch(args.nodeId, {
|
|
1528
|
+
metadata: {
|
|
1529
|
+
...metadata,
|
|
1530
|
+
rationale: nextRationale && nextRationale.length > 0 ? nextRationale : void 0
|
|
1531
|
+
},
|
|
1532
|
+
updatedAt: now
|
|
1533
|
+
});
|
|
1534
|
+
await ctx.scheduler.runAfter(0, internal.neo4jSync.syncNodeToNeo4j, {
|
|
1535
|
+
nodeId: args.nodeId,
|
|
1536
|
+
operation: "upsert"
|
|
1537
|
+
});
|
|
1538
|
+
await ctx.db.insert("epistemicAudit", {
|
|
1539
|
+
entityType: "belief",
|
|
1540
|
+
entityId: args.nodeId,
|
|
1541
|
+
changeType: "updated",
|
|
1542
|
+
previousState: { rationale: previousRationale },
|
|
1543
|
+
newState: {
|
|
1544
|
+
rationale: nextRationale && nextRationale.length > 0 ? nextRationale : void 0
|
|
1545
|
+
},
|
|
1546
|
+
changedBy: authenticatedUserId,
|
|
1547
|
+
isAgent: false,
|
|
1548
|
+
changedAt: now,
|
|
1549
|
+
projectId: node.projectId
|
|
1550
|
+
});
|
|
1551
|
+
return {
|
|
1552
|
+
nodeId: args.nodeId,
|
|
1553
|
+
previousRationale,
|
|
1554
|
+
newRationale: nextRationale
|
|
1555
|
+
};
|
|
1556
|
+
}
|
|
1557
|
+
});
|
|
1558
|
+
var updateStatusInternal = internalMutation({
|
|
1559
|
+
args: {
|
|
1560
|
+
nodeId: v.id("epistemicNodes"),
|
|
1561
|
+
status: v.union(
|
|
1562
|
+
v.literal("active"),
|
|
1563
|
+
v.literal("superseded"),
|
|
1564
|
+
v.literal("archived")
|
|
1565
|
+
),
|
|
1566
|
+
reason: v.optional(v.string()),
|
|
1567
|
+
userId: v.string(),
|
|
1568
|
+
runtimeToolName: v.optional(v.string()),
|
|
1569
|
+
runtimePackKey: v.optional(v.string()),
|
|
1570
|
+
runtimePackInstallScope: v.optional(
|
|
1571
|
+
v.union(v.literal("tenant"), v.literal("workspace"))
|
|
1572
|
+
)
|
|
1573
|
+
},
|
|
1574
|
+
returns: permissiveReturn,
|
|
1575
|
+
handler: async (ctx, args) => {
|
|
1576
|
+
const now = Date.now();
|
|
1577
|
+
const node = await ctx.db.get(args.nodeId);
|
|
1578
|
+
if (!node || node.nodeType !== "belief") {
|
|
1579
|
+
throw new Error("Belief not found");
|
|
1580
|
+
}
|
|
1581
|
+
assertTenantPackWorkspaceMutationAllowed({
|
|
1582
|
+
runtime: resolveRuntimePackMutationContext(args),
|
|
1583
|
+
target: await resolveNodeScopeForWorkspaceIsolation(ctx, node),
|
|
1584
|
+
mutationName: "epistemicBeliefs.updateStatusInternal"
|
|
1585
|
+
});
|
|
1586
|
+
const previousStatus = node.status;
|
|
1587
|
+
await ctx.db.patch(args.nodeId, {
|
|
1588
|
+
status: args.status,
|
|
1589
|
+
updatedAt: now
|
|
1590
|
+
});
|
|
1591
|
+
await ctx.db.insert("epistemicAudit", {
|
|
1592
|
+
entityType: "belief",
|
|
1593
|
+
entityId: args.nodeId,
|
|
1594
|
+
changeType: "status_changed",
|
|
1595
|
+
previousState: { status: previousStatus },
|
|
1596
|
+
newState: { status: args.status, reason: args.reason },
|
|
1597
|
+
changedBy: args.userId,
|
|
1598
|
+
isAgent: false,
|
|
1599
|
+
changedAt: now,
|
|
1600
|
+
projectId: node.projectId
|
|
1601
|
+
});
|
|
1602
|
+
return { nodeId: args.nodeId, previousStatus, newStatus: args.status };
|
|
1603
|
+
}
|
|
1604
|
+
});
|
|
1605
|
+
|
|
1606
|
+
export { archive, modulateConfidence, updateRationale, updateStatus, updateStatusInternal };
|
|
1607
|
+
//# sourceMappingURL=epistemicBeliefs.lifecycle.js.map
|
|
1608
|
+
//# sourceMappingURL=epistemicBeliefs.lifecycle.js.map
|