@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,3029 @@
|
|
|
1
|
+
import { v } from 'convex/values';
|
|
2
|
+
import { checkProjectAccess } from '@lucern/access-control/access';
|
|
3
|
+
import { getCurrentUserId } from '@lucern/access-control/auth';
|
|
4
|
+
import { permissiveReturn } from '@lucern/contracts/schema-helpers/validators';
|
|
5
|
+
import { normalizeTupleContradictionPolicy, mkOpinion, conditionalDeduction, project, dampedDependencyCascade, hasProjectedOpinionChanged, confidenceFromSL, detectTupleContradiction, evaluateTupleContradictionTransition, deriveContractModulationPlan, deriveContractStatus, trustDiscount, applyNegativeSupport, cumulativeFusion, applyNegativeEvidence, readOpinionFromRecord, parseEvidentialEvaluatorConfig, compareMetricValue, buildEvidentialRationale, parseMetricCheckerConfig, getEvaluatorInputRecord, pickFiniteNumber, resolveComparisonResult, buildComparisonRationale, parseReferenceCheckCounterConfig, parseTemporalDeadlineConfig, parseMarketIndexComparatorConfig, createInheritedContractRecord } from '@lucern/confidence';
|
|
6
|
+
import '@lucern/access-control/audience';
|
|
7
|
+
import { componentsGeneric, internalMutationGeneric, anyApi, queryGeneric, mutationGeneric } from 'convex/server';
|
|
8
|
+
import '@lucern/contracts/schema-helpers/spine/tables/epistemicNodes';
|
|
9
|
+
|
|
10
|
+
// src/epistemicContracts.handlers.ts
|
|
11
|
+
|
|
12
|
+
// src/epistemicContracts.metrics.ts
|
|
13
|
+
var ACTIVE_CONTRADICTION_STATUSES = /* @__PURE__ */ new Set([
|
|
14
|
+
"unresolved",
|
|
15
|
+
"investigating",
|
|
16
|
+
"accepted_as_permanent"
|
|
17
|
+
]);
|
|
18
|
+
var DEPENDENT_EDGE_TYPES = /* @__PURE__ */ new Set([
|
|
19
|
+
"depends_on"
|
|
20
|
+
]);
|
|
21
|
+
function classifyContradictionStatus(status) {
|
|
22
|
+
if (typeof status !== "string") {
|
|
23
|
+
return "active";
|
|
24
|
+
}
|
|
25
|
+
if (ACTIVE_CONTRADICTION_STATUSES.has(status)) {
|
|
26
|
+
return "active";
|
|
27
|
+
}
|
|
28
|
+
if (status === "resolved_support" || status === "resolved_contra" || status === "belief_forked") {
|
|
29
|
+
return "resolved";
|
|
30
|
+
}
|
|
31
|
+
return "resolved";
|
|
32
|
+
}
|
|
33
|
+
function getEdgeTimestamp(edge) {
|
|
34
|
+
if (typeof edge.updatedAt === "number") {
|
|
35
|
+
return edge.updatedAt;
|
|
36
|
+
}
|
|
37
|
+
if (typeof edge.createdAt === "number") {
|
|
38
|
+
return edge.createdAt;
|
|
39
|
+
}
|
|
40
|
+
if (typeof edge._creationTime === "number") {
|
|
41
|
+
return edge._creationTime;
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
async function getEvidenceLinks(ctx, beliefNodeId) {
|
|
46
|
+
const edges = await ctx.db.query("epistemicEdges").withIndex(
|
|
47
|
+
"by_to_type",
|
|
48
|
+
(q) => q.eq("toNodeId", beliefNodeId).eq("edgeType", "informs")
|
|
49
|
+
).collect();
|
|
50
|
+
if (edges.length === 0) {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
const nodes = await Promise.all(edges.map((edge) => ctx.db.get(edge.fromNodeId)));
|
|
54
|
+
return edges.flatMap((edge, index) => {
|
|
55
|
+
const node = nodes[index];
|
|
56
|
+
if (!node || node.nodeType !== "evidence" || node.status === "archived") {
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
return [{ edge, node }];
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function getEvidenceTags(node) {
|
|
63
|
+
const metadata = node.metadata && typeof node.metadata === "object" ? node.metadata : null;
|
|
64
|
+
const tags = metadata?.tags;
|
|
65
|
+
if (!Array.isArray(tags)) {
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
return tags.filter((tag) => typeof tag === "string");
|
|
69
|
+
}
|
|
70
|
+
async function computeEvidenceCountMetric(ctx, beliefNodeId) {
|
|
71
|
+
return (await getEvidenceLinks(ctx, beliefNodeId)).length;
|
|
72
|
+
}
|
|
73
|
+
async function computeTaggedEvidenceCount(args) {
|
|
74
|
+
const expectedTag = args.caseSensitive ? args.tag : args.tag.toLowerCase();
|
|
75
|
+
const matchedEvidenceIds = (await getEvidenceLinks(args.ctx, args.beliefNodeId)).filter(
|
|
76
|
+
({ node }) => getEvidenceTags(node).some(
|
|
77
|
+
(tag) => (args.caseSensitive ? tag : tag.toLowerCase()) === expectedTag
|
|
78
|
+
)
|
|
79
|
+
).map(({ node }) => String(node._id));
|
|
80
|
+
return {
|
|
81
|
+
count: matchedEvidenceIds.length,
|
|
82
|
+
matchedEvidenceIds
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
async function computeContradictionCounts(ctx, beliefNodeId) {
|
|
86
|
+
const contradictions = await ctx.db.query("contradictions").withIndex("by_beliefId", (q) => q.eq("beliefId", beliefNodeId)).collect();
|
|
87
|
+
return contradictions.reduce(
|
|
88
|
+
(counts, contradiction) => {
|
|
89
|
+
const status = contradiction.resolutionStatus ?? contradiction.status ?? "unresolved";
|
|
90
|
+
if (classifyContradictionStatus(status) === "active") {
|
|
91
|
+
counts.activeCount += 1;
|
|
92
|
+
} else {
|
|
93
|
+
counts.resolvedCount += 1;
|
|
94
|
+
}
|
|
95
|
+
return counts;
|
|
96
|
+
},
|
|
97
|
+
{ activeCount: 0, resolvedCount: 0 }
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
async function computeEvidenceFreshness(ctx, beliefNodeId, now = Date.now()) {
|
|
101
|
+
const timestamps = (await getEvidenceLinks(ctx, beliefNodeId)).map(({ edge }) => getEdgeTimestamp(edge)).filter((value) => value !== null);
|
|
102
|
+
if (timestamps.length === 0) {
|
|
103
|
+
return {
|
|
104
|
+
newestAgeMs: null,
|
|
105
|
+
oldestAgeMs: null,
|
|
106
|
+
newestEdgeAt: null,
|
|
107
|
+
oldestEdgeAt: null,
|
|
108
|
+
edgeCount: 0
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const newestEdgeAt = Math.max(...timestamps);
|
|
112
|
+
const oldestEdgeAt = Math.min(...timestamps);
|
|
113
|
+
return {
|
|
114
|
+
newestAgeMs: Math.max(0, now - newestEdgeAt),
|
|
115
|
+
oldestAgeMs: Math.max(0, now - oldestEdgeAt),
|
|
116
|
+
newestEdgeAt,
|
|
117
|
+
oldestEdgeAt,
|
|
118
|
+
edgeCount: timestamps.length
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
async function computeDependentBeliefCount(ctx, beliefNodeId) {
|
|
122
|
+
const incomingEdges = await ctx.db.query("epistemicEdges").withIndex("by_to", (q) => q.eq("toNodeId", beliefNodeId)).collect();
|
|
123
|
+
const dependencyEdges = incomingEdges.filter(
|
|
124
|
+
(edge) => DEPENDENT_EDGE_TYPES.has(edge.edgeType)
|
|
125
|
+
);
|
|
126
|
+
if (dependencyEdges.length === 0) {
|
|
127
|
+
return 0;
|
|
128
|
+
}
|
|
129
|
+
const dependentBeliefs = await Promise.all(
|
|
130
|
+
dependencyEdges.map((edge) => ctx.db.get(edge.fromNodeId))
|
|
131
|
+
);
|
|
132
|
+
const uniqueBeliefIds = /* @__PURE__ */ new Set();
|
|
133
|
+
for (const node of dependentBeliefs) {
|
|
134
|
+
if (node && node.nodeType === "belief" && node.status !== "archived" && node.status !== "deleted") {
|
|
135
|
+
uniqueBeliefIds.add(String(node._id));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return uniqueBeliefIds.size;
|
|
139
|
+
}
|
|
140
|
+
async function snapshotEvidentialMetric(args) {
|
|
141
|
+
switch (args.metric) {
|
|
142
|
+
case "evidence_count": {
|
|
143
|
+
const count = await computeEvidenceCountMetric(args.ctx, args.beliefNodeId);
|
|
144
|
+
return {
|
|
145
|
+
metric: args.metric,
|
|
146
|
+
value: count,
|
|
147
|
+
data: { evidenceCount: count }
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
case "contradiction_status": {
|
|
151
|
+
const counts = await computeContradictionCounts(args.ctx, args.beliefNodeId);
|
|
152
|
+
return {
|
|
153
|
+
metric: args.metric,
|
|
154
|
+
value: counts.activeCount,
|
|
155
|
+
data: counts
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
case "edge_freshness": {
|
|
159
|
+
const freshness = await computeEvidenceFreshness(
|
|
160
|
+
args.ctx,
|
|
161
|
+
args.beliefNodeId,
|
|
162
|
+
args.now
|
|
163
|
+
);
|
|
164
|
+
return {
|
|
165
|
+
metric: args.metric,
|
|
166
|
+
value: freshness.newestAgeMs,
|
|
167
|
+
data: freshness
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
case "dependent_count": {
|
|
171
|
+
const count = await computeDependentBeliefCount(args.ctx, args.beliefNodeId);
|
|
172
|
+
return {
|
|
173
|
+
metric: args.metric,
|
|
174
|
+
value: count,
|
|
175
|
+
data: { dependentCount: count }
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
default:
|
|
179
|
+
return {
|
|
180
|
+
metric: args.metric,
|
|
181
|
+
value: null,
|
|
182
|
+
data: {}
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
async function evaluateBuiltInEvidentialContract(args) {
|
|
187
|
+
const config = parseEvidentialEvaluatorConfig(args.contract.condition.evaluatorConfig);
|
|
188
|
+
const snapshot = await snapshotEvidentialMetric({
|
|
189
|
+
ctx: args.ctx,
|
|
190
|
+
beliefNodeId: args.belief._id,
|
|
191
|
+
metric: config.metric,
|
|
192
|
+
now: args.now
|
|
193
|
+
});
|
|
194
|
+
const comparisonSatisfied = snapshot.value !== null && compareMetricValue(config.operator, snapshot.value, config.threshold);
|
|
195
|
+
const result = args.contract.direction === "falsifies" ? comparisonSatisfied ? "disconfirmed" : "confirmed" : comparisonSatisfied ? "confirmed" : "disconfirmed";
|
|
196
|
+
return {
|
|
197
|
+
result,
|
|
198
|
+
rationale: buildEvidentialRationale({
|
|
199
|
+
config,
|
|
200
|
+
snapshot,
|
|
201
|
+
comparisonSatisfied,
|
|
202
|
+
result
|
|
203
|
+
}),
|
|
204
|
+
data: {
|
|
205
|
+
...snapshot.data,
|
|
206
|
+
metric: config.metric,
|
|
207
|
+
observedValue: snapshot.value,
|
|
208
|
+
operator: config.operator,
|
|
209
|
+
threshold: config.threshold,
|
|
210
|
+
action: config.action ?? "modulate_confidence",
|
|
211
|
+
actionParams: config.actionParams
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
async function evaluateMetricCheckerContract(args) {
|
|
216
|
+
const config = parseMetricCheckerConfig(args.contract.condition.evaluatorConfig);
|
|
217
|
+
const input = getEvaluatorInputRecord(args.inputData, "metricData");
|
|
218
|
+
const metric = typeof input.metric === "string" && input.metric.length > 0 ? input.metric : config.metric;
|
|
219
|
+
const observedValue = pickFiniteNumber(input, [
|
|
220
|
+
"observedValue",
|
|
221
|
+
"currentValue",
|
|
222
|
+
"metricValue",
|
|
223
|
+
"value"
|
|
224
|
+
]) ?? config.observedValue ?? config.currentValue ?? config.metricValue ?? null;
|
|
225
|
+
if (observedValue === null) {
|
|
226
|
+
return {
|
|
227
|
+
result: "inconclusive",
|
|
228
|
+
rationale: `metric_checker is awaiting data for ${metric ?? args.contract.condition.expression}.`,
|
|
229
|
+
data: {
|
|
230
|
+
metric,
|
|
231
|
+
observedValue: null,
|
|
232
|
+
operator: config.operator,
|
|
233
|
+
threshold: config.threshold,
|
|
234
|
+
unit: config.unit
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
const comparisonSatisfied = compareMetricValue(
|
|
239
|
+
config.operator,
|
|
240
|
+
observedValue,
|
|
241
|
+
config.threshold
|
|
242
|
+
);
|
|
243
|
+
const result = resolveComparisonResult(args.contract.direction, comparisonSatisfied);
|
|
244
|
+
return {
|
|
245
|
+
result,
|
|
246
|
+
rationale: buildComparisonRationale({
|
|
247
|
+
label: metric ?? "metric",
|
|
248
|
+
observedValue,
|
|
249
|
+
operator: config.operator,
|
|
250
|
+
threshold: config.threshold,
|
|
251
|
+
comparisonSatisfied,
|
|
252
|
+
result,
|
|
253
|
+
unit: config.unit
|
|
254
|
+
}),
|
|
255
|
+
data: {
|
|
256
|
+
metric,
|
|
257
|
+
observedValue,
|
|
258
|
+
operator: config.operator,
|
|
259
|
+
threshold: config.threshold,
|
|
260
|
+
unit: config.unit
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
async function evaluateReferenceCheckCounterContract(args) {
|
|
265
|
+
const config = parseReferenceCheckCounterConfig(args.contract.condition.evaluatorConfig);
|
|
266
|
+
const input = getEvaluatorInputRecord(args.inputData, "referenceCheckData");
|
|
267
|
+
const tag = typeof input.tag === "string" && input.tag.trim().length > 0 ? input.tag.trim() : config.tag;
|
|
268
|
+
const snapshot = await computeTaggedEvidenceCount({
|
|
269
|
+
ctx: args.ctx,
|
|
270
|
+
beliefNodeId: args.belief._id,
|
|
271
|
+
tag,
|
|
272
|
+
caseSensitive: config.caseSensitive
|
|
273
|
+
});
|
|
274
|
+
const comparisonSatisfied = compareMetricValue(
|
|
275
|
+
config.operator,
|
|
276
|
+
snapshot.count,
|
|
277
|
+
config.threshold
|
|
278
|
+
);
|
|
279
|
+
const result = resolveComparisonResult(args.contract.direction, comparisonSatisfied);
|
|
280
|
+
return {
|
|
281
|
+
result,
|
|
282
|
+
rationale: buildComparisonRationale({
|
|
283
|
+
label: `reference checks tagged "${tag}"`,
|
|
284
|
+
observedValue: snapshot.count,
|
|
285
|
+
operator: config.operator,
|
|
286
|
+
threshold: config.threshold,
|
|
287
|
+
comparisonSatisfied,
|
|
288
|
+
result
|
|
289
|
+
}),
|
|
290
|
+
data: {
|
|
291
|
+
tag,
|
|
292
|
+
observedValue: snapshot.count,
|
|
293
|
+
referenceCheckCount: snapshot.count,
|
|
294
|
+
matchedEvidenceIds: snapshot.matchedEvidenceIds,
|
|
295
|
+
operator: config.operator,
|
|
296
|
+
threshold: config.threshold,
|
|
297
|
+
caseSensitive: config.caseSensitive ?? false
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
async function evaluateTemporalDeadlineContract(args) {
|
|
302
|
+
if (typeof args.contract.deadline !== "number" || !Number.isFinite(args.contract.deadline)) {
|
|
303
|
+
throw new Error(
|
|
304
|
+
"temporal_deadline requires contract.deadline to be set to a finite timestamp."
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
const config = parseTemporalDeadlineConfig(args.contract.condition.evaluatorConfig);
|
|
308
|
+
const input = getEvaluatorInputRecord(args.inputData, "temporalData");
|
|
309
|
+
const label = (typeof input.label === "string" && input.label.length > 0 ? input.label : config.label) ?? args.contract.title ?? args.contract.condition.expression;
|
|
310
|
+
const completedAt = pickFiniteNumber(input, [
|
|
311
|
+
"completedAt",
|
|
312
|
+
"observedAt",
|
|
313
|
+
"satisfiedAt",
|
|
314
|
+
"achievedAt"
|
|
315
|
+
]) ?? config.completedAt ?? config.observedAt ?? config.satisfiedAt ?? config.achievedAt;
|
|
316
|
+
const completed = input.completed === true || config.completed === true || completedAt !== void 0;
|
|
317
|
+
if (completed) {
|
|
318
|
+
if (completedAt !== void 0 && completedAt > args.contract.deadline) {
|
|
319
|
+
return {
|
|
320
|
+
result: "expired",
|
|
321
|
+
rationale: `${label} completed at ${completedAt}, after deadline ${args.contract.deadline}.`,
|
|
322
|
+
data: {
|
|
323
|
+
label,
|
|
324
|
+
deadline: args.contract.deadline,
|
|
325
|
+
completed: true,
|
|
326
|
+
completedAt,
|
|
327
|
+
missedDeadline: true,
|
|
328
|
+
overdueByMs: completedAt - args.contract.deadline
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
const result = args.contract.direction === "falsifies" ? "disconfirmed" : "confirmed";
|
|
333
|
+
return {
|
|
334
|
+
result,
|
|
335
|
+
rationale: `${label} completed before deadline ${args.contract.deadline}.`,
|
|
336
|
+
data: {
|
|
337
|
+
label,
|
|
338
|
+
deadline: args.contract.deadline,
|
|
339
|
+
completed: true,
|
|
340
|
+
completedAt: completedAt ?? null,
|
|
341
|
+
missedDeadline: false
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
if (args.now > args.contract.deadline) {
|
|
346
|
+
return {
|
|
347
|
+
result: "expired",
|
|
348
|
+
rationale: `${label} missed deadline ${args.contract.deadline}; temporal contract expired.`,
|
|
349
|
+
data: {
|
|
350
|
+
label,
|
|
351
|
+
deadline: args.contract.deadline,
|
|
352
|
+
completed: false,
|
|
353
|
+
overdueByMs: args.now - args.contract.deadline
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
return {
|
|
358
|
+
result: "inconclusive",
|
|
359
|
+
rationale: `${label} is still before deadline ${args.contract.deadline}; awaiting outcome.`,
|
|
360
|
+
data: {
|
|
361
|
+
label,
|
|
362
|
+
deadline: args.contract.deadline,
|
|
363
|
+
completed: false,
|
|
364
|
+
timeRemainingMs: args.contract.deadline - args.now
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
async function evaluateMarketIndexComparatorContract(args) {
|
|
369
|
+
const config = parseMarketIndexComparatorConfig(args.contract.condition.evaluatorConfig);
|
|
370
|
+
const input = getEvaluatorInputRecord(args.inputData, "marketIndexData");
|
|
371
|
+
const subject = typeof input.subject === "string" && input.subject.length > 0 ? input.subject : config.subject;
|
|
372
|
+
const benchmark = typeof input.benchmark === "string" && input.benchmark.length > 0 ? input.benchmark : config.benchmark;
|
|
373
|
+
const subjectValue = pickFiniteNumber(input, ["subjectValue", "primaryValue", "leftValue"]) ?? config.subjectValue ?? config.primaryValue ?? null;
|
|
374
|
+
const benchmarkValue = pickFiniteNumber(input, ["benchmarkValue", "comparisonValue", "rightValue"]) ?? config.benchmarkValue ?? config.comparisonValue ?? null;
|
|
375
|
+
if (subjectValue === null || benchmarkValue === null) {
|
|
376
|
+
return {
|
|
377
|
+
result: "inconclusive",
|
|
378
|
+
rationale: "market_index_comparator is awaiting both subject and benchmark values.",
|
|
379
|
+
data: {
|
|
380
|
+
subject,
|
|
381
|
+
subjectValue,
|
|
382
|
+
benchmark,
|
|
383
|
+
benchmarkValue,
|
|
384
|
+
operator: config.operator,
|
|
385
|
+
threshold: config.threshold
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
if (benchmarkValue === 0) {
|
|
390
|
+
throw new Error(
|
|
391
|
+
"market_index_comparator cannot compare against a zero benchmark value."
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
const differentialPercent = (subjectValue - benchmarkValue) / Math.abs(benchmarkValue) * 100;
|
|
395
|
+
const comparisonSatisfied = compareMetricValue(
|
|
396
|
+
config.operator,
|
|
397
|
+
differentialPercent,
|
|
398
|
+
config.threshold
|
|
399
|
+
);
|
|
400
|
+
const result = resolveComparisonResult(args.contract.direction, comparisonSatisfied);
|
|
401
|
+
return {
|
|
402
|
+
result,
|
|
403
|
+
rationale: buildComparisonRationale({
|
|
404
|
+
label: `${subject ?? "subject"} vs ${benchmark ?? "benchmark"} differential`,
|
|
405
|
+
observedValue: differentialPercent,
|
|
406
|
+
operator: config.operator,
|
|
407
|
+
threshold: config.threshold,
|
|
408
|
+
comparisonSatisfied,
|
|
409
|
+
result,
|
|
410
|
+
unit: "%"
|
|
411
|
+
}),
|
|
412
|
+
data: {
|
|
413
|
+
subject,
|
|
414
|
+
subjectValue,
|
|
415
|
+
benchmark,
|
|
416
|
+
benchmarkValue,
|
|
417
|
+
differentialPercent,
|
|
418
|
+
operator: config.operator,
|
|
419
|
+
threshold: config.threshold
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
var METRIC_COMPARATOR_EVALUATOR_NAMES = {
|
|
424
|
+
evidentialAliases: /* @__PURE__ */ new Set(["evidential", "built_in_evidential", "builtin_evidential"]),
|
|
425
|
+
metricChecker: "metric_checker",
|
|
426
|
+
referenceCheckCounter: "reference_check_counter",
|
|
427
|
+
temporalDeadline: "temporal_deadline",
|
|
428
|
+
marketIndexComparator: "market_index_comparator"
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
// src/beliefLifecycle.ts
|
|
432
|
+
var BELIEF_STATUS_VALUES = [
|
|
433
|
+
"assumption",
|
|
434
|
+
"hypothesis",
|
|
435
|
+
"belief",
|
|
436
|
+
"fact"
|
|
437
|
+
];
|
|
438
|
+
var RESOLVED_PREDICTION_OUTCOMES = [
|
|
439
|
+
"confirmed",
|
|
440
|
+
"disconfirmed",
|
|
441
|
+
"partial",
|
|
442
|
+
"expired"
|
|
443
|
+
];
|
|
444
|
+
function isBeliefLifecycleStatus(value) {
|
|
445
|
+
return typeof value === "string" && BELIEF_STATUS_VALUES.includes(value);
|
|
446
|
+
}
|
|
447
|
+
function normalizeBeliefConfidence(confidence) {
|
|
448
|
+
if (typeof confidence !== "number" || !Number.isFinite(confidence)) {
|
|
449
|
+
return null;
|
|
450
|
+
}
|
|
451
|
+
if (confidence >= 0 && confidence <= 1) {
|
|
452
|
+
return confidence;
|
|
453
|
+
}
|
|
454
|
+
if (confidence > 1 && confidence <= 100) {
|
|
455
|
+
return confidence / 100;
|
|
456
|
+
}
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
function isResolvedByConfidence(confidence) {
|
|
460
|
+
const normalized = normalizeBeliefConfidence(confidence);
|
|
461
|
+
if (normalized === null) {
|
|
462
|
+
return false;
|
|
463
|
+
}
|
|
464
|
+
return normalized <= 0 || normalized >= 1;
|
|
465
|
+
}
|
|
466
|
+
function hasResolvedPredictionOutcome(predictionMeta) {
|
|
467
|
+
if (!predictionMeta || typeof predictionMeta !== "object") {
|
|
468
|
+
return false;
|
|
469
|
+
}
|
|
470
|
+
const outcome = predictionMeta.outcome;
|
|
471
|
+
return typeof outcome === "string" && RESOLVED_PREDICTION_OUTCOMES.includes(outcome);
|
|
472
|
+
}
|
|
473
|
+
function getPredictionMetaFromMetadata(metadata) {
|
|
474
|
+
return metadata?.predictionMeta;
|
|
475
|
+
}
|
|
476
|
+
function shouldTreatBeliefAsFact(opts) {
|
|
477
|
+
if (isResolvedByConfidence(opts.confidence)) {
|
|
478
|
+
return true;
|
|
479
|
+
}
|
|
480
|
+
if (hasResolvedPredictionOutcome(opts.predictionMeta)) {
|
|
481
|
+
return true;
|
|
482
|
+
}
|
|
483
|
+
if (hasResolvedPredictionOutcome(getPredictionMetaFromMetadata(opts.metadata))) {
|
|
484
|
+
return true;
|
|
485
|
+
}
|
|
486
|
+
return false;
|
|
487
|
+
}
|
|
488
|
+
function resolveBeliefLifecycleStatus(opts) {
|
|
489
|
+
if (shouldTreatBeliefAsFact(opts)) {
|
|
490
|
+
return "fact";
|
|
491
|
+
}
|
|
492
|
+
const direct = opts.beliefStatus;
|
|
493
|
+
if (isBeliefLifecycleStatus(direct)) {
|
|
494
|
+
const normalized = normalizeBeliefConfidence(opts.confidence);
|
|
495
|
+
if (normalized !== null && isPreValidationBeliefStatus(direct)) {
|
|
496
|
+
return "belief";
|
|
497
|
+
}
|
|
498
|
+
return direct;
|
|
499
|
+
}
|
|
500
|
+
const metaStatus = opts.metadata?.beliefStatus;
|
|
501
|
+
if (isBeliefLifecycleStatus(metaStatus)) {
|
|
502
|
+
const normalized = normalizeBeliefConfidence(opts.confidence);
|
|
503
|
+
if (normalized !== null && isPreValidationBeliefStatus(metaStatus)) {
|
|
504
|
+
return "belief";
|
|
505
|
+
}
|
|
506
|
+
return metaStatus;
|
|
507
|
+
}
|
|
508
|
+
return "assumption";
|
|
509
|
+
}
|
|
510
|
+
function isPreValidationBeliefStatus(status) {
|
|
511
|
+
return status === "assumption" || status === "hypothesis";
|
|
512
|
+
}
|
|
513
|
+
function promoteBeliefStatusAfterScoring(status, opts) {
|
|
514
|
+
if (shouldTreatBeliefAsFact({ ...opts })) {
|
|
515
|
+
return "fact";
|
|
516
|
+
}
|
|
517
|
+
if (isPreValidationBeliefStatus(status)) {
|
|
518
|
+
return "belief";
|
|
519
|
+
}
|
|
520
|
+
return status === "fact" ? "fact" : "belief";
|
|
521
|
+
}
|
|
522
|
+
var api = anyApi;
|
|
523
|
+
componentsGeneric();
|
|
524
|
+
var internal = anyApi;
|
|
525
|
+
var internalMutation = internalMutationGeneric;
|
|
526
|
+
var mutation = mutationGeneric;
|
|
527
|
+
var query = queryGeneric;
|
|
528
|
+
|
|
529
|
+
// src/debug.ts
|
|
530
|
+
function isGraphPrimitiveDebugEnabled() {
|
|
531
|
+
const env = globalThis.process?.env;
|
|
532
|
+
return env?.LUCERN_COMPAT_FALLBACK_DEBUG === "1" || env?.LUCERN_GRAPH_DEBUG === "1";
|
|
533
|
+
}
|
|
534
|
+
function debugGraphPrimitiveFallback(message, context) {
|
|
535
|
+
if (!isGraphPrimitiveDebugEnabled()) {
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
console.debug(message, context ?? {});
|
|
539
|
+
}
|
|
540
|
+
var LEGACY_SCOPE_FIELD = "graphScopeProjectId";
|
|
541
|
+
function asMappedProjectId(topic) {
|
|
542
|
+
if (!topic) {
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
const directLegacyProjectId = normalizeScopeValue(topic[LEGACY_SCOPE_FIELD]);
|
|
546
|
+
if (directLegacyProjectId) {
|
|
547
|
+
return directLegacyProjectId;
|
|
548
|
+
}
|
|
549
|
+
const metadata = topic.metadata || {};
|
|
550
|
+
const candidate = metadata[LEGACY_SCOPE_FIELD] || metadata.legacyProjectId || metadata.projectId || metadata.scopeProjectId;
|
|
551
|
+
return candidate ? candidate : void 0;
|
|
552
|
+
}
|
|
553
|
+
function normalizeScopeValue(value) {
|
|
554
|
+
if (typeof value !== "string") {
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
const normalized = value.trim();
|
|
558
|
+
return normalized.length > 0 ? normalized : void 0;
|
|
559
|
+
}
|
|
560
|
+
function pickPrimaryTopic(candidates) {
|
|
561
|
+
return [...candidates].sort((a, b) => {
|
|
562
|
+
const depthA = a.depth ?? 9999;
|
|
563
|
+
const depthB = b.depth ?? 9999;
|
|
564
|
+
if (depthA !== depthB) {
|
|
565
|
+
return depthA - depthB;
|
|
566
|
+
}
|
|
567
|
+
const createdA = a.createdAt ?? Number.MAX_SAFE_INTEGER;
|
|
568
|
+
const createdB = b.createdAt ?? Number.MAX_SAFE_INTEGER;
|
|
569
|
+
if (createdA !== createdB) {
|
|
570
|
+
return createdA - createdB;
|
|
571
|
+
}
|
|
572
|
+
return String(a.name || "").localeCompare(String(b.name || ""));
|
|
573
|
+
})[0];
|
|
574
|
+
}
|
|
575
|
+
async function findTopicsByScopeAlias(ctx, scopeId) {
|
|
576
|
+
try {
|
|
577
|
+
return await ctx.db.query("topics").withIndex(
|
|
578
|
+
"by_graph_scope_project",
|
|
579
|
+
(q) => q.eq(LEGACY_SCOPE_FIELD, scopeId)
|
|
580
|
+
).collect();
|
|
581
|
+
} catch (error) {
|
|
582
|
+
debugGraphPrimitiveFallback(
|
|
583
|
+
"[topicScope] Failed to resolve scope alias via index",
|
|
584
|
+
{
|
|
585
|
+
error,
|
|
586
|
+
scopeId
|
|
587
|
+
}
|
|
588
|
+
);
|
|
589
|
+
const topics = await ctx.db.query("topics").collect();
|
|
590
|
+
return topics.filter((topic) => {
|
|
591
|
+
const normalizedGlobalId = normalizeScopeValue(topic.globalId);
|
|
592
|
+
const mappedProjectId = asMappedProjectId(topic);
|
|
593
|
+
return String(topic._id) === scopeId || normalizedGlobalId === scopeId || mappedProjectId === scopeId;
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
async function tryResolveHostTopicById(ctx, topicId) {
|
|
598
|
+
if (typeof ctx.runQuery !== "function") {
|
|
599
|
+
return null;
|
|
600
|
+
}
|
|
601
|
+
try {
|
|
602
|
+
return await ctx.runQuery(api.topics.get, {
|
|
603
|
+
id: topicId
|
|
604
|
+
}) ?? null;
|
|
605
|
+
} catch (error) {
|
|
606
|
+
debugGraphPrimitiveFallback(
|
|
607
|
+
"[topicScope] Failed to resolve topic by host query",
|
|
608
|
+
{
|
|
609
|
+
error,
|
|
610
|
+
topicId
|
|
611
|
+
}
|
|
612
|
+
);
|
|
613
|
+
return null;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
async function tryResolveHostTopicByLegacyScope(ctx, legacyScopeId) {
|
|
617
|
+
if (typeof ctx.runQuery !== "function") {
|
|
618
|
+
return null;
|
|
619
|
+
}
|
|
620
|
+
try {
|
|
621
|
+
return await ctx.runQuery(api.topics.getByLegacyScopeId, {
|
|
622
|
+
projectId: legacyScopeId
|
|
623
|
+
}) ?? null;
|
|
624
|
+
} catch (error) {
|
|
625
|
+
debugGraphPrimitiveFallback(
|
|
626
|
+
"[topicScope] Failed to resolve topic by legacy scope",
|
|
627
|
+
{
|
|
628
|
+
error,
|
|
629
|
+
legacyScopeId
|
|
630
|
+
}
|
|
631
|
+
);
|
|
632
|
+
return null;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
async function resolveInheritedWorkspaceScope(ctx, topic) {
|
|
636
|
+
const MAX_DEPTH = 10;
|
|
637
|
+
let tenantId = normalizeScopeValue(topic.tenantId);
|
|
638
|
+
let workspaceId = normalizeScopeValue(topic.workspaceId);
|
|
639
|
+
if (tenantId && workspaceId) {
|
|
640
|
+
return { tenantId, workspaceId };
|
|
641
|
+
}
|
|
642
|
+
let current = topic;
|
|
643
|
+
for (let i = 0; i < MAX_DEPTH && current?.parentTopicId; i++) {
|
|
644
|
+
current = await ctx.db.get(current.parentTopicId);
|
|
645
|
+
if (!current) break;
|
|
646
|
+
if (!tenantId) {
|
|
647
|
+
tenantId = normalizeScopeValue(current.tenantId);
|
|
648
|
+
}
|
|
649
|
+
if (!workspaceId) {
|
|
650
|
+
workspaceId = normalizeScopeValue(current.workspaceId);
|
|
651
|
+
}
|
|
652
|
+
if (tenantId && workspaceId) break;
|
|
653
|
+
}
|
|
654
|
+
return { tenantId, workspaceId };
|
|
655
|
+
}
|
|
656
|
+
async function resolveTopicProjectScope(ctx, args) {
|
|
657
|
+
if (args.topicId) {
|
|
658
|
+
let topic = null;
|
|
659
|
+
try {
|
|
660
|
+
topic = await ctx.db.get(
|
|
661
|
+
args.topicId
|
|
662
|
+
);
|
|
663
|
+
} catch (error) {
|
|
664
|
+
debugGraphPrimitiveFallback(
|
|
665
|
+
"[topicScope] Failed to load topic by direct id",
|
|
666
|
+
{
|
|
667
|
+
error,
|
|
668
|
+
topicId: args.topicId
|
|
669
|
+
}
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
if (!topic) {
|
|
673
|
+
topic = await tryResolveHostTopicById(ctx, String(args.topicId));
|
|
674
|
+
}
|
|
675
|
+
if (!topic) {
|
|
676
|
+
topic = pickPrimaryTopic(
|
|
677
|
+
await findTopicsByScopeAlias(ctx, String(args.topicId))
|
|
678
|
+
) ?? null;
|
|
679
|
+
}
|
|
680
|
+
if (!topic) {
|
|
681
|
+
throw new Error(`Topic not found: ${String(args.topicId)}`);
|
|
682
|
+
}
|
|
683
|
+
const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
|
|
684
|
+
const mapped = asMappedProjectId(topic);
|
|
685
|
+
if (mapped) {
|
|
686
|
+
return {
|
|
687
|
+
topicId: topic._id,
|
|
688
|
+
projectId: mapped,
|
|
689
|
+
tenantId: inherited.tenantId,
|
|
690
|
+
workspaceId: inherited.workspaceId,
|
|
691
|
+
source: "topic"
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
return {
|
|
695
|
+
topicId: topic._id,
|
|
696
|
+
tenantId: inherited.tenantId,
|
|
697
|
+
workspaceId: inherited.workspaceId,
|
|
698
|
+
source: "topic"
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
if (args.projectId) {
|
|
702
|
+
let directTopic = null;
|
|
703
|
+
try {
|
|
704
|
+
directTopic = await ctx.db.get(
|
|
705
|
+
args.projectId
|
|
706
|
+
);
|
|
707
|
+
} catch (error) {
|
|
708
|
+
debugGraphPrimitiveFallback(
|
|
709
|
+
"[topicScope] Failed to load direct project topic",
|
|
710
|
+
{
|
|
711
|
+
error,
|
|
712
|
+
projectId: args.projectId
|
|
713
|
+
}
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
if (directTopic) {
|
|
717
|
+
const inherited = await resolveInheritedWorkspaceScope(ctx, directTopic);
|
|
718
|
+
const mapped = asMappedProjectId(directTopic);
|
|
719
|
+
return {
|
|
720
|
+
topicId: directTopic._id,
|
|
721
|
+
projectId: mapped ?? args.projectId,
|
|
722
|
+
tenantId: inherited.tenantId,
|
|
723
|
+
workspaceId: inherited.workspaceId,
|
|
724
|
+
source: "topic_inferred"
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
directTopic = await tryResolveHostTopicByLegacyScope(ctx, args.projectId);
|
|
728
|
+
if (directTopic) {
|
|
729
|
+
const inherited = await resolveInheritedWorkspaceScope(ctx, directTopic);
|
|
730
|
+
const mapped = asMappedProjectId(directTopic);
|
|
731
|
+
return {
|
|
732
|
+
topicId: directTopic._id,
|
|
733
|
+
projectId: mapped ?? args.projectId,
|
|
734
|
+
tenantId: inherited.tenantId,
|
|
735
|
+
workspaceId: inherited.workspaceId,
|
|
736
|
+
source: "topic_inferred"
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
const topics = await findTopicsByScopeAlias(ctx, args.projectId);
|
|
740
|
+
const primary = pickPrimaryTopic(topics);
|
|
741
|
+
if (primary) {
|
|
742
|
+
const inherited = await resolveInheritedWorkspaceScope(ctx, primary);
|
|
743
|
+
return {
|
|
744
|
+
topicId: primary._id,
|
|
745
|
+
projectId: args.projectId,
|
|
746
|
+
tenantId: inherited.tenantId,
|
|
747
|
+
workspaceId: inherited.workspaceId,
|
|
748
|
+
source: "project_mapped_topic"
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
throw new Error(
|
|
752
|
+
`Legacy project scope ${String(args.projectId)} has no mapped topic.`
|
|
753
|
+
);
|
|
754
|
+
}
|
|
755
|
+
throw new Error(
|
|
756
|
+
"Missing scope: provide topicId (preferred) or legacy projectId alias."
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
({
|
|
760
|
+
projectId: v.optional(v.string()),
|
|
761
|
+
topicId: v.optional(v.string())
|
|
762
|
+
});
|
|
763
|
+
function normalizeScopeValue2(value) {
|
|
764
|
+
if (typeof value !== "string") {
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
const normalized = value.trim();
|
|
768
|
+
return normalized.length > 0 ? normalized : void 0;
|
|
769
|
+
}
|
|
770
|
+
function nodeMatchesWorkspaceReasoningScope(node, scope) {
|
|
771
|
+
if (!node) {
|
|
772
|
+
return false;
|
|
773
|
+
}
|
|
774
|
+
const scopeTenantId = normalizeScopeValue2(scope.tenantId);
|
|
775
|
+
const scopeWorkspaceId = normalizeScopeValue2(scope.workspaceId);
|
|
776
|
+
const nodeTenantId = normalizeScopeValue2(node.tenantId);
|
|
777
|
+
const nodeWorkspaceId = normalizeScopeValue2(node.workspaceId);
|
|
778
|
+
const epistemicLayer = typeof node.epistemicLayer === "string" ? node.epistemicLayer : void 0;
|
|
779
|
+
if (scopeTenantId && nodeTenantId && scopeTenantId !== nodeTenantId) {
|
|
780
|
+
return false;
|
|
781
|
+
}
|
|
782
|
+
if (epistemicLayer === "ontological" && nodeWorkspaceId === void 0) {
|
|
783
|
+
return true;
|
|
784
|
+
}
|
|
785
|
+
if (!scopeWorkspaceId && node.publicationStatus === "published") {
|
|
786
|
+
return true;
|
|
787
|
+
}
|
|
788
|
+
if (!scopeWorkspaceId) {
|
|
789
|
+
return nodeWorkspaceId === void 0;
|
|
790
|
+
}
|
|
791
|
+
return scopeWorkspaceId === nodeWorkspaceId;
|
|
792
|
+
}
|
|
793
|
+
function edgeMatchesWorkspaceReasoningScope(edge, scope) {
|
|
794
|
+
if (!edge) {
|
|
795
|
+
return false;
|
|
796
|
+
}
|
|
797
|
+
const scopeTenantId = normalizeScopeValue2(scope.tenantId);
|
|
798
|
+
const scopeWorkspaceId = normalizeScopeValue2(scope.workspaceId);
|
|
799
|
+
const edgeTenantId = normalizeScopeValue2(edge.tenantId);
|
|
800
|
+
const edgeWorkspaceId = normalizeScopeValue2(edge.workspaceId);
|
|
801
|
+
if (scopeTenantId && edgeTenantId && scopeTenantId !== edgeTenantId) {
|
|
802
|
+
return false;
|
|
803
|
+
}
|
|
804
|
+
if (!scopeWorkspaceId) {
|
|
805
|
+
return edgeWorkspaceId === void 0;
|
|
806
|
+
}
|
|
807
|
+
return scopeWorkspaceId === edgeWorkspaceId;
|
|
808
|
+
}
|
|
809
|
+
async function resolveNodeScopeForWorkspaceIsolation(ctx, node) {
|
|
810
|
+
const epistemicLayer = typeof node?.epistemicLayer === "string" ? node.epistemicLayer : void 0;
|
|
811
|
+
const resolved = {
|
|
812
|
+
tenantId: normalizeScopeValue2(node?.tenantId),
|
|
813
|
+
workspaceId: normalizeScopeValue2(node?.workspaceId),
|
|
814
|
+
epistemicLayer,
|
|
815
|
+
nodeType: typeof node?.nodeType === "string" ? node.nodeType : void 0
|
|
816
|
+
};
|
|
817
|
+
if (!node) {
|
|
818
|
+
return resolved;
|
|
819
|
+
}
|
|
820
|
+
if (resolved.epistemicLayer === "ontological") {
|
|
821
|
+
return resolved;
|
|
822
|
+
}
|
|
823
|
+
if (resolved.tenantId || resolved.workspaceId) {
|
|
824
|
+
return resolved;
|
|
825
|
+
}
|
|
826
|
+
if (node.topicId) {
|
|
827
|
+
const topicScope = await resolveTopicProjectScope(ctx, {
|
|
828
|
+
topicId: node.topicId
|
|
829
|
+
});
|
|
830
|
+
return {
|
|
831
|
+
...resolved,
|
|
832
|
+
tenantId: topicScope.tenantId,
|
|
833
|
+
workspaceId: topicScope.workspaceId
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
if (node.projectId) {
|
|
837
|
+
const topicScope = await resolveTopicProjectScope(ctx, {
|
|
838
|
+
projectId: String(node.projectId)
|
|
839
|
+
});
|
|
840
|
+
return {
|
|
841
|
+
...resolved,
|
|
842
|
+
tenantId: topicScope.tenantId,
|
|
843
|
+
workspaceId: topicScope.workspaceId
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
return resolved;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// src/epistemicBeliefs.helpers.ts
|
|
850
|
+
v.id("epistemicNodes");
|
|
851
|
+
var DEFAULT_CONFIDENCE_POLICY = {
|
|
852
|
+
scoringMode: "after_worktree",
|
|
853
|
+
tupleContradiction: normalizeTupleContradictionPolicy()
|
|
854
|
+
};
|
|
855
|
+
function throwStructuredMutationError(args) {
|
|
856
|
+
const error = new Error(args.message);
|
|
857
|
+
error.status = args.status;
|
|
858
|
+
error.code = args.code;
|
|
859
|
+
error.invariantCode = args.invariantCode;
|
|
860
|
+
error.suggestion = args.suggestion;
|
|
861
|
+
error.details = args.details;
|
|
862
|
+
throw error;
|
|
863
|
+
}
|
|
864
|
+
function buildBeliefConfidenceRow(args) {
|
|
865
|
+
return {
|
|
866
|
+
beliefId: args.beliefId,
|
|
867
|
+
confidence: confidenceFromSL(
|
|
868
|
+
args.belief,
|
|
869
|
+
args.disbelief,
|
|
870
|
+
args.uncertainty,
|
|
871
|
+
args.baseRate
|
|
872
|
+
),
|
|
873
|
+
belief: args.belief,
|
|
874
|
+
disbelief: args.disbelief,
|
|
875
|
+
uncertainty: args.uncertainty,
|
|
876
|
+
baseRate: args.baseRate,
|
|
877
|
+
slOperator: args.slOperator ?? "manual_assessment",
|
|
878
|
+
trigger: args.trigger,
|
|
879
|
+
...args.rationale ? { rationale: args.rationale } : {},
|
|
880
|
+
assessedBy: args.assessedBy,
|
|
881
|
+
assessedAt: args.assessedAt,
|
|
882
|
+
...args.triggeringEvidenceId ? {
|
|
883
|
+
triggeringEvidenceId: args.triggeringEvidenceId,
|
|
884
|
+
triggeringEvidenceIds: [String(args.triggeringEvidenceId)]
|
|
885
|
+
} : {},
|
|
886
|
+
...args.triggeringContradictionId ? {
|
|
887
|
+
triggeringContradictionId: args.triggeringContradictionId
|
|
888
|
+
} : {},
|
|
889
|
+
...args.triggeringWorktreeId ? { triggeringWorktreeId: args.triggeringWorktreeId } : {}
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
function readTupleContradictedFlag(value) {
|
|
893
|
+
return typeof value === "boolean" ? value : void 0;
|
|
894
|
+
}
|
|
895
|
+
function readBeliefOpinionSnapshot(node, metadata) {
|
|
896
|
+
try {
|
|
897
|
+
return readOpinionFromRecord({
|
|
898
|
+
...metadata,
|
|
899
|
+
opinion_b: node.opinion_b,
|
|
900
|
+
opinion_d: node.opinion_d,
|
|
901
|
+
opinion_u: node.opinion_u,
|
|
902
|
+
opinion_a: node.opinion_a
|
|
903
|
+
});
|
|
904
|
+
} catch (error) {
|
|
905
|
+
debugGraphPrimitiveFallback(
|
|
906
|
+
"[epistemicBeliefs] Failed to read belief opinion snapshot",
|
|
907
|
+
{
|
|
908
|
+
error,
|
|
909
|
+
beliefId: node._id
|
|
910
|
+
}
|
|
911
|
+
);
|
|
912
|
+
return mkOpinion(0, 0, 1, 0.5);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
function deriveTupleContradictionSeverity(node) {
|
|
916
|
+
const metadata = node.metadata || {};
|
|
917
|
+
const criticality = typeof metadata.criticality === "string" ? metadata.criticality : void 0;
|
|
918
|
+
if (criticality === "blocking") {
|
|
919
|
+
return "critical";
|
|
920
|
+
}
|
|
921
|
+
if (criticality === "supporting") {
|
|
922
|
+
return "minor";
|
|
923
|
+
}
|
|
924
|
+
return "significant";
|
|
925
|
+
}
|
|
926
|
+
function formatTupleContradictionDescription(args) {
|
|
927
|
+
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)}.`;
|
|
928
|
+
}
|
|
929
|
+
function resolveBeliefStatus(node, metadata) {
|
|
930
|
+
return resolveBeliefLifecycleStatus({
|
|
931
|
+
beliefStatus: node.beliefStatus,
|
|
932
|
+
confidence: node.confidence,
|
|
933
|
+
predictionMeta: node.predictionMeta,
|
|
934
|
+
metadata
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
async function hasCompletedWorktreeForBelief(ctx, beliefNodeId) {
|
|
938
|
+
const clusterMembership = await ctx.db.query("worktreeBeliefCluster").withIndex("by_belief", (q) => q.eq("beliefId", beliefNodeId)).collect();
|
|
939
|
+
for (const membership of clusterMembership) {
|
|
940
|
+
const worktree = await ctx.db.get(membership.worktreeId);
|
|
941
|
+
if (worktree?.status === "completed" || worktree?.status === "merged") {
|
|
942
|
+
return true;
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
return false;
|
|
946
|
+
}
|
|
947
|
+
async function getActiveConfidencePolicy(ctx) {
|
|
948
|
+
try {
|
|
949
|
+
const activeConfig = await ctx.db.query("logicSprintScoring").withIndex("by_active", (q) => q.eq("isActive", true)).first();
|
|
950
|
+
return {
|
|
951
|
+
scoringMode: activeConfig?.confidencePolicy === "always" ? "always" : DEFAULT_CONFIDENCE_POLICY.scoringMode,
|
|
952
|
+
tupleContradiction: normalizeTupleContradictionPolicy(
|
|
953
|
+
activeConfig?.tupleContradictionPolicy
|
|
954
|
+
)
|
|
955
|
+
};
|
|
956
|
+
} catch (error) {
|
|
957
|
+
debugGraphPrimitiveFallback(
|
|
958
|
+
"[epistemicBeliefs] Failed to load active confidence policy",
|
|
959
|
+
{
|
|
960
|
+
error
|
|
961
|
+
}
|
|
962
|
+
);
|
|
963
|
+
return DEFAULT_CONFIDENCE_POLICY;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
async function requireProjectWriteAccess(ctx, projectId, userId) {
|
|
967
|
+
const hasAccess = await checkProjectAccess(
|
|
968
|
+
ctx,
|
|
969
|
+
projectId,
|
|
970
|
+
userId
|
|
971
|
+
);
|
|
972
|
+
if (!hasAccess) {
|
|
973
|
+
throwStructuredMutationError({
|
|
974
|
+
message: "Project access required.",
|
|
975
|
+
status: 403,
|
|
976
|
+
code: "FORBIDDEN",
|
|
977
|
+
invariantCode: "policy.scope_required",
|
|
978
|
+
suggestion: "Request write access for the project and retry.",
|
|
979
|
+
details: { projectId, userId }
|
|
980
|
+
});
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
// src/edges/contains.ts
|
|
985
|
+
var containsPropagationSpec = {
|
|
986
|
+
edgeType: "contains",
|
|
987
|
+
direction: "outgoing",
|
|
988
|
+
transitivity: "none",
|
|
989
|
+
damping: 1,
|
|
990
|
+
maxHops: 1,
|
|
991
|
+
operator: () => null,
|
|
992
|
+
description: "Structural containment only. Traversed for explicit semantics, but it never propagates opinions."
|
|
993
|
+
};
|
|
994
|
+
function readEdgeMetadata(edge) {
|
|
995
|
+
return {
|
|
996
|
+
constraint: edge.constraint ?? void 0,
|
|
997
|
+
normalization: edge.normalization ?? void 0,
|
|
998
|
+
propagation: edge.propagation ?? void 0,
|
|
999
|
+
conditionalA: edge.conditionalA ?? void 0,
|
|
1000
|
+
conditionalNotA: edge.conditionalNotA ?? void 0
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
function applyPerHopDamping(sourceOpinion, damping) {
|
|
1004
|
+
if (damping >= 1) {
|
|
1005
|
+
return sourceOpinion;
|
|
1006
|
+
}
|
|
1007
|
+
return trustDiscount(sourceOpinion, Math.max(0, damping));
|
|
1008
|
+
}
|
|
1009
|
+
function annotateRationale(result, spec, hop) {
|
|
1010
|
+
return {
|
|
1011
|
+
...result,
|
|
1012
|
+
rationale: `hop=${hop} edge=${spec.edgeType} damping=${spec.damping.toFixed(
|
|
1013
|
+
2
|
|
1014
|
+
)} :: ${result.rationale}`
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
function propagatePositiveSupport(sourceOpinion, targetOpinion, edgeWeight) {
|
|
1018
|
+
const discounted = trustDiscount(sourceOpinion, Math.abs(edgeWeight));
|
|
1019
|
+
return {
|
|
1020
|
+
opinion: cumulativeFusion(targetOpinion, discounted),
|
|
1021
|
+
operator: "cumulative_fusion",
|
|
1022
|
+
rationale: `Supporting evidence (weight=${edgeWeight.toFixed(
|
|
1023
|
+
2
|
|
1024
|
+
)}) from source at ${project(sourceOpinion).toFixed(2)}`
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
1027
|
+
function propagatePositiveInform(sourceOpinion, targetOpinion, edgeWeight) {
|
|
1028
|
+
const discounted = trustDiscount(sourceOpinion, Math.abs(edgeWeight));
|
|
1029
|
+
return {
|
|
1030
|
+
opinion: cumulativeFusion(targetOpinion, discounted),
|
|
1031
|
+
operator: "cumulative_fusion",
|
|
1032
|
+
rationale: `Supporting evidence (weight=${edgeWeight.toFixed(2)})`
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
function propagateNegativeSupportWithMetadata(sourceOpinion, targetOpinion, edgeWeight, edge) {
|
|
1036
|
+
return applyNegativeSupport(
|
|
1037
|
+
sourceOpinion,
|
|
1038
|
+
targetOpinion,
|
|
1039
|
+
edgeWeight,
|
|
1040
|
+
readEdgeMetadata(edge)
|
|
1041
|
+
);
|
|
1042
|
+
}
|
|
1043
|
+
var propagateNegativeInform = applyNegativeEvidence;
|
|
1044
|
+
|
|
1045
|
+
// src/edges/contradicts.ts
|
|
1046
|
+
var contradictsPropagationSpec = {
|
|
1047
|
+
edgeType: "contradicts",
|
|
1048
|
+
direction: "bidirectional",
|
|
1049
|
+
transitivity: "none",
|
|
1050
|
+
damping: 0.85,
|
|
1051
|
+
maxHops: 1,
|
|
1052
|
+
operator: (sourceOpinion, targetOpinion, edge, context) => {
|
|
1053
|
+
const dampedSource = applyPerHopDamping(
|
|
1054
|
+
sourceOpinion,
|
|
1055
|
+
context.spec.damping
|
|
1056
|
+
);
|
|
1057
|
+
const negativeWeight = -Math.abs(edge.weight ?? 1);
|
|
1058
|
+
const result = propagateNegativeSupportWithMetadata(
|
|
1059
|
+
dampedSource,
|
|
1060
|
+
targetOpinion,
|
|
1061
|
+
negativeWeight,
|
|
1062
|
+
edge
|
|
1063
|
+
);
|
|
1064
|
+
return annotateRationale(result, context.spec, context.hop);
|
|
1065
|
+
},
|
|
1066
|
+
description: "Legacy contradiction edges move negative pressure in either direction, but never beyond one hop."
|
|
1067
|
+
};
|
|
1068
|
+
var dependsOnPropagationSpec = {
|
|
1069
|
+
edgeType: "depends_on",
|
|
1070
|
+
direction: "incoming",
|
|
1071
|
+
transitivity: "damped",
|
|
1072
|
+
damping: 0.8,
|
|
1073
|
+
maxHops: "unbounded",
|
|
1074
|
+
operator: (sourceOpinion, targetOpinion, edge, context) => {
|
|
1075
|
+
const dampedSource = applyPerHopDamping(
|
|
1076
|
+
sourceOpinion,
|
|
1077
|
+
context.spec.damping
|
|
1078
|
+
);
|
|
1079
|
+
const metadata = readEdgeMetadata(edge);
|
|
1080
|
+
if (metadata.conditionalA && metadata.conditionalNotA) {
|
|
1081
|
+
const deducedOpinion = conditionalDeduction(
|
|
1082
|
+
dampedSource,
|
|
1083
|
+
mkOpinion(
|
|
1084
|
+
metadata.conditionalA.b,
|
|
1085
|
+
metadata.conditionalA.d,
|
|
1086
|
+
metadata.conditionalA.u,
|
|
1087
|
+
metadata.conditionalA.a
|
|
1088
|
+
),
|
|
1089
|
+
mkOpinion(
|
|
1090
|
+
metadata.conditionalNotA.b,
|
|
1091
|
+
metadata.conditionalNotA.d,
|
|
1092
|
+
metadata.conditionalNotA.u,
|
|
1093
|
+
metadata.conditionalNotA.a
|
|
1094
|
+
),
|
|
1095
|
+
targetOpinion.a
|
|
1096
|
+
);
|
|
1097
|
+
return annotateRationale(
|
|
1098
|
+
{
|
|
1099
|
+
opinion: deducedOpinion,
|
|
1100
|
+
operator: "conditional_deduction",
|
|
1101
|
+
rationale: `Conditional deduction: prerequisite at ${project(
|
|
1102
|
+
dampedSource
|
|
1103
|
+
).toFixed(2)}`
|
|
1104
|
+
},
|
|
1105
|
+
context.spec,
|
|
1106
|
+
context.hop
|
|
1107
|
+
);
|
|
1108
|
+
}
|
|
1109
|
+
const result = dampedDependencyCascade(
|
|
1110
|
+
dampedSource,
|
|
1111
|
+
targetOpinion,
|
|
1112
|
+
metadata.propagation ?? "continuous"
|
|
1113
|
+
);
|
|
1114
|
+
return annotateRationale(result, context.spec, context.hop);
|
|
1115
|
+
},
|
|
1116
|
+
description: "Structural gating. Textbook conditional deduction when edge conditionals exist, otherwise damped dependency cascade through downstream chains."
|
|
1117
|
+
};
|
|
1118
|
+
|
|
1119
|
+
// src/edges/derivedFrom.ts
|
|
1120
|
+
var derivedFromPropagationSpec = {
|
|
1121
|
+
edgeType: "derived_from",
|
|
1122
|
+
direction: "incoming",
|
|
1123
|
+
transitivity: "none",
|
|
1124
|
+
damping: 1,
|
|
1125
|
+
maxHops: 1,
|
|
1126
|
+
operator: () => null,
|
|
1127
|
+
description: "Provenance only. The traversal surface stays explicit, but confidence does not move across derived_from edges."
|
|
1128
|
+
};
|
|
1129
|
+
|
|
1130
|
+
// src/edges/elaborates.ts
|
|
1131
|
+
var elaboratesPropagationSpec = {
|
|
1132
|
+
edgeType: "elaborates",
|
|
1133
|
+
direction: "outgoing",
|
|
1134
|
+
transitivity: "damped",
|
|
1135
|
+
damping: 0.7,
|
|
1136
|
+
maxHops: 2,
|
|
1137
|
+
operator: (sourceOpinion, targetOpinion, edge, context) => {
|
|
1138
|
+
const dampedSource = applyPerHopDamping(
|
|
1139
|
+
sourceOpinion,
|
|
1140
|
+
context.spec.damping
|
|
1141
|
+
);
|
|
1142
|
+
const contextualWeight = Math.min(Math.abs(edge.weight ?? 0.35), 0.35);
|
|
1143
|
+
const result = propagatePositiveInform(
|
|
1144
|
+
dampedSource,
|
|
1145
|
+
targetOpinion,
|
|
1146
|
+
contextualWeight
|
|
1147
|
+
);
|
|
1148
|
+
return annotateRationale(result, context.spec, context.hop);
|
|
1149
|
+
},
|
|
1150
|
+
description: "Context-rich supporting detail. Elaborates carries a small positive effect with short, damped chaining."
|
|
1151
|
+
};
|
|
1152
|
+
|
|
1153
|
+
// src/edges/informs.ts
|
|
1154
|
+
var informsPropagationSpec = {
|
|
1155
|
+
edgeType: "informs",
|
|
1156
|
+
direction: "outgoing",
|
|
1157
|
+
transitivity: "full",
|
|
1158
|
+
damping: 0.92,
|
|
1159
|
+
maxHops: "unbounded",
|
|
1160
|
+
operator: (sourceOpinion, targetOpinion, edge, context) => {
|
|
1161
|
+
const dampedSource = applyPerHopDamping(
|
|
1162
|
+
sourceOpinion,
|
|
1163
|
+
context.spec.damping
|
|
1164
|
+
);
|
|
1165
|
+
const weight = edge.weight ?? 1;
|
|
1166
|
+
const result = weight < 0 ? propagateNegativeInform(dampedSource, targetOpinion, weight) : propagatePositiveInform(dampedSource, targetOpinion, weight);
|
|
1167
|
+
return annotateRationale(result, context.spec, context.hop);
|
|
1168
|
+
},
|
|
1169
|
+
description: "Evidence-bearing influence. Informs can chain through the graph with light per-hop damping."
|
|
1170
|
+
};
|
|
1171
|
+
|
|
1172
|
+
// src/edges/propagationTypes.ts
|
|
1173
|
+
function isPropagationTraversalDirection(direction) {
|
|
1174
|
+
return direction === "outgoing" || direction === "incoming";
|
|
1175
|
+
}
|
|
1176
|
+
function canTraverseHop(spec, nextHop) {
|
|
1177
|
+
return spec.maxHops === "unbounded" || nextHop <= spec.maxHops;
|
|
1178
|
+
}
|
|
1179
|
+
function canContinueTransitively(spec, currentHop) {
|
|
1180
|
+
if (spec.transitivity === "none") {
|
|
1181
|
+
return false;
|
|
1182
|
+
}
|
|
1183
|
+
return spec.maxHops === "unbounded" || currentHop < spec.maxHops;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
// src/edges/refutes.ts
|
|
1187
|
+
var refutesPropagationSpec = {
|
|
1188
|
+
edgeType: "refutes",
|
|
1189
|
+
direction: "outgoing",
|
|
1190
|
+
transitivity: "none",
|
|
1191
|
+
damping: 0.9,
|
|
1192
|
+
maxHops: 1,
|
|
1193
|
+
operator: (sourceOpinion, targetOpinion, edge, context) => {
|
|
1194
|
+
const dampedSource = applyPerHopDamping(
|
|
1195
|
+
sourceOpinion,
|
|
1196
|
+
context.spec.damping
|
|
1197
|
+
);
|
|
1198
|
+
const negativeWeight = -Math.abs(edge.weight ?? 1);
|
|
1199
|
+
const result = propagateNegativeInform(
|
|
1200
|
+
dampedSource,
|
|
1201
|
+
targetOpinion,
|
|
1202
|
+
negativeWeight
|
|
1203
|
+
);
|
|
1204
|
+
return annotateRationale(result, context.spec, context.hop);
|
|
1205
|
+
},
|
|
1206
|
+
description: "Explicit negative evidence semantics. Refutes is treated as strong one-hop counter-evidence."
|
|
1207
|
+
};
|
|
1208
|
+
|
|
1209
|
+
// src/edges/supports.ts
|
|
1210
|
+
var supportsPropagationSpec = {
|
|
1211
|
+
edgeType: "supports",
|
|
1212
|
+
direction: "outgoing",
|
|
1213
|
+
transitivity: "full",
|
|
1214
|
+
damping: 0.85,
|
|
1215
|
+
maxHops: "unbounded",
|
|
1216
|
+
operator: (sourceOpinion, targetOpinion, edge, context) => {
|
|
1217
|
+
const dampedSource = applyPerHopDamping(
|
|
1218
|
+
sourceOpinion,
|
|
1219
|
+
context.spec.damping
|
|
1220
|
+
);
|
|
1221
|
+
const weight = edge.weight ?? 1;
|
|
1222
|
+
const result = weight < 0 ? propagateNegativeSupportWithMetadata(
|
|
1223
|
+
dampedSource,
|
|
1224
|
+
targetOpinion,
|
|
1225
|
+
weight,
|
|
1226
|
+
edge
|
|
1227
|
+
) : propagatePositiveSupport(dampedSource, targetOpinion, weight);
|
|
1228
|
+
return annotateRationale(result, context.spec, context.hop);
|
|
1229
|
+
},
|
|
1230
|
+
description: "Belief-to-belief influence. Supports chains transitively with moderate per-hop damping."
|
|
1231
|
+
};
|
|
1232
|
+
|
|
1233
|
+
// src/edges/tests.ts
|
|
1234
|
+
var testsPropagationSpec = {
|
|
1235
|
+
edgeType: "tests",
|
|
1236
|
+
direction: "outgoing",
|
|
1237
|
+
transitivity: "none",
|
|
1238
|
+
damping: 1,
|
|
1239
|
+
maxHops: 1,
|
|
1240
|
+
operator: () => null,
|
|
1241
|
+
description: "Interrogation linkage only. Tests edges do not directly move confidence."
|
|
1242
|
+
};
|
|
1243
|
+
|
|
1244
|
+
// src/edges/index.ts
|
|
1245
|
+
var EDGE_PROPAGATION_SPECS = [
|
|
1246
|
+
supportsPropagationSpec,
|
|
1247
|
+
informsPropagationSpec,
|
|
1248
|
+
dependsOnPropagationSpec,
|
|
1249
|
+
derivedFromPropagationSpec,
|
|
1250
|
+
containsPropagationSpec,
|
|
1251
|
+
testsPropagationSpec,
|
|
1252
|
+
contradictsPropagationSpec,
|
|
1253
|
+
refutesPropagationSpec,
|
|
1254
|
+
elaboratesPropagationSpec
|
|
1255
|
+
];
|
|
1256
|
+
new Map(EDGE_PROPAGATION_SPECS.map((spec) => [spec.edgeType, spec]));
|
|
1257
|
+
function getEdgePropagationSpecs() {
|
|
1258
|
+
return EDGE_PROPAGATION_SPECS;
|
|
1259
|
+
}
|
|
1260
|
+
function getTraversalDirections(direction) {
|
|
1261
|
+
if (isPropagationTraversalDirection(direction)) {
|
|
1262
|
+
return [direction];
|
|
1263
|
+
}
|
|
1264
|
+
return ["outgoing", "incoming"];
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
// src/confidencePropagationDispatch.ts
|
|
1268
|
+
function resolveTraversalTargetNodeId(edge, direction) {
|
|
1269
|
+
const targetNodeId = direction === "outgoing" ? edge.toNodeId : edge.fromNodeId;
|
|
1270
|
+
return targetNodeId ?? void 0;
|
|
1271
|
+
}
|
|
1272
|
+
function readNodeOpinion(node) {
|
|
1273
|
+
const metadata = node.metadata ?? {};
|
|
1274
|
+
try {
|
|
1275
|
+
return readOpinionFromRecord({
|
|
1276
|
+
...metadata,
|
|
1277
|
+
opinion_b: node.opinion_b,
|
|
1278
|
+
opinion_d: node.opinion_d,
|
|
1279
|
+
opinion_u: node.opinion_u,
|
|
1280
|
+
opinion_a: node.opinion_a
|
|
1281
|
+
});
|
|
1282
|
+
} catch {
|
|
1283
|
+
return mkOpinion(0, 0, 1, 0.5);
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
async function collectConfidencePropagationDispatches(args) {
|
|
1287
|
+
const dispatchesByTargetId = /* @__PURE__ */ new Map();
|
|
1288
|
+
const opinionCache = /* @__PURE__ */ new Map();
|
|
1289
|
+
const nodeCache = /* @__PURE__ */ new Map();
|
|
1290
|
+
const traversalSpecs = args.traversalSpecs ?? getEdgePropagationSpecs();
|
|
1291
|
+
const queue = [
|
|
1292
|
+
{
|
|
1293
|
+
nodeId: args.sourceNodeId,
|
|
1294
|
+
opinion: args.sourceOpinion,
|
|
1295
|
+
hop: 0,
|
|
1296
|
+
visitedNodeIds: /* @__PURE__ */ new Set([String(args.sourceNodeId)])
|
|
1297
|
+
}
|
|
1298
|
+
];
|
|
1299
|
+
const loadNode = async (nodeId) => {
|
|
1300
|
+
const cacheKey = String(nodeId);
|
|
1301
|
+
if (!nodeCache.has(cacheKey)) {
|
|
1302
|
+
nodeCache.set(cacheKey, await args.getNode(nodeId));
|
|
1303
|
+
}
|
|
1304
|
+
return nodeCache.get(cacheKey) ?? null;
|
|
1305
|
+
};
|
|
1306
|
+
while (queue.length > 0) {
|
|
1307
|
+
const state = queue.shift();
|
|
1308
|
+
if (!state) {
|
|
1309
|
+
continue;
|
|
1310
|
+
}
|
|
1311
|
+
for (const spec of traversalSpecs) {
|
|
1312
|
+
const nextHop = state.hop + 1;
|
|
1313
|
+
if (!canTraverseHop(spec, nextHop)) {
|
|
1314
|
+
continue;
|
|
1315
|
+
}
|
|
1316
|
+
for (const direction of getTraversalDirections(spec.direction)) {
|
|
1317
|
+
const edges = await args.queryEdges({
|
|
1318
|
+
nodeId: state.nodeId,
|
|
1319
|
+
spec,
|
|
1320
|
+
direction,
|
|
1321
|
+
hop: nextHop
|
|
1322
|
+
});
|
|
1323
|
+
for (const edge of edges) {
|
|
1324
|
+
if (args.sourceScope && !edgeMatchesWorkspaceReasoningScope(edge, args.sourceScope)) {
|
|
1325
|
+
continue;
|
|
1326
|
+
}
|
|
1327
|
+
const targetNodeId = resolveTraversalTargetNodeId(edge, direction);
|
|
1328
|
+
if (!targetNodeId) {
|
|
1329
|
+
continue;
|
|
1330
|
+
}
|
|
1331
|
+
if (state.visitedNodeIds.has(String(targetNodeId))) {
|
|
1332
|
+
continue;
|
|
1333
|
+
}
|
|
1334
|
+
const targetNode = await loadNode(targetNodeId);
|
|
1335
|
+
if (!targetNode || targetNode.nodeType !== "belief") {
|
|
1336
|
+
continue;
|
|
1337
|
+
}
|
|
1338
|
+
if (args.sourceScope && !nodeMatchesWorkspaceReasoningScope(targetNode, args.sourceScope)) {
|
|
1339
|
+
continue;
|
|
1340
|
+
}
|
|
1341
|
+
const cacheKey = String(targetNodeId);
|
|
1342
|
+
const targetOpinion = opinionCache.get(cacheKey) ?? readNodeOpinion(targetNode);
|
|
1343
|
+
const result = spec.operator(state.opinion, targetOpinion, edge, {
|
|
1344
|
+
hop: nextHop,
|
|
1345
|
+
sourceNodeId: state.nodeId,
|
|
1346
|
+
targetNodeId,
|
|
1347
|
+
traversedDirection: direction,
|
|
1348
|
+
spec
|
|
1349
|
+
});
|
|
1350
|
+
if (!result || !hasProjectedOpinionChanged(targetOpinion, result.opinion)) {
|
|
1351
|
+
continue;
|
|
1352
|
+
}
|
|
1353
|
+
const projectedOpinion = mkOpinion(
|
|
1354
|
+
result.opinion.b,
|
|
1355
|
+
result.opinion.d,
|
|
1356
|
+
result.opinion.u,
|
|
1357
|
+
result.opinion.a
|
|
1358
|
+
);
|
|
1359
|
+
opinionCache.set(cacheKey, projectedOpinion);
|
|
1360
|
+
const existingDispatch = dispatchesByTargetId.get(cacheKey);
|
|
1361
|
+
dispatchesByTargetId.set(cacheKey, {
|
|
1362
|
+
targetNodeId,
|
|
1363
|
+
edgeType: spec.edgeType,
|
|
1364
|
+
traversedDirection: direction,
|
|
1365
|
+
weight: edge.weight ?? 1,
|
|
1366
|
+
opinion: projectedOpinion,
|
|
1367
|
+
operator: result.operator,
|
|
1368
|
+
rationale: existingDispatch ? `${existingDispatch.rationale}; ${result.rationale}` : result.rationale,
|
|
1369
|
+
hop: nextHop
|
|
1370
|
+
});
|
|
1371
|
+
if (canContinueTransitively(spec, nextHop)) {
|
|
1372
|
+
queue.push({
|
|
1373
|
+
nodeId: targetNodeId,
|
|
1374
|
+
opinion: projectedOpinion,
|
|
1375
|
+
hop: nextHop,
|
|
1376
|
+
visitedNodeIds: /* @__PURE__ */ new Set([
|
|
1377
|
+
...state.visitedNodeIds,
|
|
1378
|
+
String(targetNodeId)
|
|
1379
|
+
])
|
|
1380
|
+
});
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
return Array.from(dispatchesByTargetId.values()).sort((left, right) => {
|
|
1387
|
+
if (left.hop !== right.hop) {
|
|
1388
|
+
return left.hop - right.hop;
|
|
1389
|
+
}
|
|
1390
|
+
return String(left.targetNodeId).localeCompare(String(right.targetNodeId));
|
|
1391
|
+
});
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
// src/epistemicBeliefs.confidence.ts
|
|
1395
|
+
async function applyBeliefConfidenceChange(ctx, args) {
|
|
1396
|
+
const now = Date.now();
|
|
1397
|
+
const node = await ctx.db.get(args.nodeId);
|
|
1398
|
+
if (!node) {
|
|
1399
|
+
throwStructuredMutationError({
|
|
1400
|
+
message: "Node not found.",
|
|
1401
|
+
status: 404,
|
|
1402
|
+
code: "NOT_FOUND",
|
|
1403
|
+
invariantCode: "belief.exists",
|
|
1404
|
+
suggestion: "Verify nodeId points to an existing node before modulating confidence.",
|
|
1405
|
+
details: { nodeId: args.nodeId }
|
|
1406
|
+
});
|
|
1407
|
+
}
|
|
1408
|
+
if (node.nodeType !== "belief") {
|
|
1409
|
+
throwStructuredMutationError({
|
|
1410
|
+
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.`,
|
|
1411
|
+
status: 400,
|
|
1412
|
+
code: "INVALID_ARGUMENT",
|
|
1413
|
+
invariantCode: "entity.no_confidence",
|
|
1414
|
+
suggestion: "Use entityLifecycle.updateEntityAttributes for entity mutations. modulateConfidence is for belief nodes only.",
|
|
1415
|
+
details: { nodeId: args.nodeId, nodeType: node.nodeType }
|
|
1416
|
+
});
|
|
1417
|
+
}
|
|
1418
|
+
if (!node.projectId) {
|
|
1419
|
+
throwStructuredMutationError({
|
|
1420
|
+
message: "Belief has no project scope.",
|
|
1421
|
+
status: 400,
|
|
1422
|
+
code: "MISSING_SCOPE",
|
|
1423
|
+
invariantCode: "belief.project_required",
|
|
1424
|
+
suggestion: "Belief must have a projectId to modulate confidence.",
|
|
1425
|
+
details: { nodeId: args.nodeId }
|
|
1426
|
+
});
|
|
1427
|
+
}
|
|
1428
|
+
await requireProjectWriteAccess(
|
|
1429
|
+
ctx,
|
|
1430
|
+
node.projectId,
|
|
1431
|
+
args.authenticatedUserId
|
|
1432
|
+
);
|
|
1433
|
+
const existingMetadata = node.metadata || {};
|
|
1434
|
+
const currentBeliefStatus = resolveBeliefStatus(node, existingMetadata);
|
|
1435
|
+
const confidencePolicy = await getActiveConfidencePolicy(ctx);
|
|
1436
|
+
if (confidencePolicy.scoringMode === "after_worktree" && isPreValidationBeliefStatus(currentBeliefStatus)) {
|
|
1437
|
+
const hasCompletedWorktree = await hasCompletedWorktreeForBelief(
|
|
1438
|
+
ctx,
|
|
1439
|
+
args.nodeId
|
|
1440
|
+
);
|
|
1441
|
+
if (!hasCompletedWorktree) {
|
|
1442
|
+
throwStructuredMutationError({
|
|
1443
|
+
message: "Cannot score belief before worktree completion. Complete a worktree that tests this belief first.",
|
|
1444
|
+
status: 409,
|
|
1445
|
+
code: "CONFLICT",
|
|
1446
|
+
invariantCode: "belief.confidence_append_only",
|
|
1447
|
+
suggestion: "Complete a worktree linked to this belief before recording confidence modulation.",
|
|
1448
|
+
details: { nodeId: args.nodeId }
|
|
1449
|
+
});
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
const previousConfidence = node.confidence || 0.5;
|
|
1453
|
+
const predictionMeta = node.predictionMeta || existingMetadata.predictionMeta;
|
|
1454
|
+
const previousOpinion = readBeliefOpinionSnapshot(node, existingMetadata);
|
|
1455
|
+
const slB = args.belief;
|
|
1456
|
+
const slD = args.disbelief;
|
|
1457
|
+
const slU = args.uncertainty;
|
|
1458
|
+
const slA = args.baseRate ?? 0.5;
|
|
1459
|
+
const nextOpinion = { b: slB, d: slD, u: slU, a: slA };
|
|
1460
|
+
const derivedConfidence = confidenceFromSL(slB, slD, slU, slA);
|
|
1461
|
+
const isFirstScoring = typeof node.confidence !== "number" || !Number.isFinite(node.confidence);
|
|
1462
|
+
const previousTupleContradicted = readTupleContradictedFlag(node.tupleContradicted) ?? readTupleContradictedFlag(existingMetadata.tupleContradicted) ?? detectTupleContradiction(
|
|
1463
|
+
previousOpinion,
|
|
1464
|
+
confidencePolicy.tupleContradiction.beliefThreshold,
|
|
1465
|
+
confidencePolicy.tupleContradiction.disbeliefThreshold
|
|
1466
|
+
);
|
|
1467
|
+
const tupleTransition = evaluateTupleContradictionTransition({
|
|
1468
|
+
previousTupleContradicted,
|
|
1469
|
+
opinion: nextOpinion,
|
|
1470
|
+
policy: confidencePolicy.tupleContradiction
|
|
1471
|
+
});
|
|
1472
|
+
const tupleContradictionDescription = formatTupleContradictionDescription({
|
|
1473
|
+
opinion: nextOpinion,
|
|
1474
|
+
policy: tupleTransition.policy
|
|
1475
|
+
});
|
|
1476
|
+
const newBeliefStatus = promoteBeliefStatusAfterScoring(currentBeliefStatus, {
|
|
1477
|
+
confidence: derivedConfidence,
|
|
1478
|
+
predictionMeta,
|
|
1479
|
+
metadata: existingMetadata
|
|
1480
|
+
});
|
|
1481
|
+
let tupleContradictionId;
|
|
1482
|
+
if (tupleTransition.crossedIntoTupleContradiction) {
|
|
1483
|
+
tupleContradictionId = await ctx.runMutation(
|
|
1484
|
+
"contradictions:create",
|
|
1485
|
+
{
|
|
1486
|
+
projectId: node.projectId,
|
|
1487
|
+
topicId: node.topicId,
|
|
1488
|
+
beliefId: args.nodeId,
|
|
1489
|
+
beliefBId: args.nodeId,
|
|
1490
|
+
supportingInsightIds: [],
|
|
1491
|
+
contradictingInsightIds: [],
|
|
1492
|
+
severity: deriveTupleContradictionSeverity(node),
|
|
1493
|
+
source: "tuple_space",
|
|
1494
|
+
detectionMethod: "agent",
|
|
1495
|
+
description: tupleContradictionDescription,
|
|
1496
|
+
createdBy: args.authenticatedUserId
|
|
1497
|
+
}
|
|
1498
|
+
);
|
|
1499
|
+
}
|
|
1500
|
+
await ctx.db.patch(args.nodeId, {
|
|
1501
|
+
confidence: derivedConfidence,
|
|
1502
|
+
beliefStatus: newBeliefStatus,
|
|
1503
|
+
tupleContradicted: tupleTransition.tupleContradicted,
|
|
1504
|
+
updatedAt: now,
|
|
1505
|
+
// Store SL opinion fields at node level for fast access
|
|
1506
|
+
opinion_b: slB,
|
|
1507
|
+
opinion_d: slD,
|
|
1508
|
+
opinion_u: slU,
|
|
1509
|
+
opinion_a: slA,
|
|
1510
|
+
metadata: {
|
|
1511
|
+
...existingMetadata,
|
|
1512
|
+
beliefStatus: newBeliefStatus,
|
|
1513
|
+
slBelief: slB,
|
|
1514
|
+
slDisbelief: slD,
|
|
1515
|
+
slUncertainty: slU,
|
|
1516
|
+
slBaseRate: slA,
|
|
1517
|
+
tupleContradicted: tupleTransition.tupleContradicted
|
|
1518
|
+
}
|
|
1519
|
+
});
|
|
1520
|
+
if (isFirstScoring) {
|
|
1521
|
+
const nodeTopicId = node.topicId;
|
|
1522
|
+
const themeNodes = await ctx.db.query("epistemicNodes").withIndex(
|
|
1523
|
+
"by_topic",
|
|
1524
|
+
(q) => q.eq("topicId", nodeTopicId || node.projectId)
|
|
1525
|
+
).filter((q) => q.eq(q.field("nodeType"), "theme")).collect();
|
|
1526
|
+
for (const theme of themeNodes) {
|
|
1527
|
+
if (theme.globalId && node.globalId) {
|
|
1528
|
+
await ctx.scheduler.runAfter(0, internal.neo4jEdgeAPI.createEdge, {
|
|
1529
|
+
globalId: `edge-${node.globalId}-relates_to_thesis-${theme.globalId}`,
|
|
1530
|
+
fromGlobalId: node.globalId,
|
|
1531
|
+
toGlobalId: theme.globalId,
|
|
1532
|
+
edgeType: "relates_to_thesis",
|
|
1533
|
+
weight: derivedConfidence,
|
|
1534
|
+
createdBy: args.authenticatedUserId,
|
|
1535
|
+
topicId: String(node.projectId),
|
|
1536
|
+
fromNodeType: "belief",
|
|
1537
|
+
toNodeType: "theme",
|
|
1538
|
+
fromLayer: "L3",
|
|
1539
|
+
toLayer: "L3"
|
|
1540
|
+
});
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
const storedRationale = args.rationale ?? `Confidence changed from ${previousConfidence.toFixed(2)} (nodeId: ${args.nodeId})`;
|
|
1545
|
+
const beliefConfidenceId = await ctx.db.insert("beliefConfidence", {
|
|
1546
|
+
...buildBeliefConfidenceRow({
|
|
1547
|
+
beliefId: args.nodeId,
|
|
1548
|
+
belief: slB,
|
|
1549
|
+
disbelief: slD,
|
|
1550
|
+
uncertainty: slU,
|
|
1551
|
+
baseRate: slA,
|
|
1552
|
+
trigger: args.trigger,
|
|
1553
|
+
rationale: storedRationale,
|
|
1554
|
+
assessedBy: args.authenticatedUserId,
|
|
1555
|
+
assessedAt: now,
|
|
1556
|
+
slOperator: args.slOperator,
|
|
1557
|
+
triggeringEvidenceId: args.triggeringEvidenceId,
|
|
1558
|
+
triggeringContradictionId: tupleContradictionId,
|
|
1559
|
+
triggeringWorktreeId: args.triggeringWorktreeId
|
|
1560
|
+
})
|
|
1561
|
+
});
|
|
1562
|
+
await ctx.scheduler.runAfter(0, internal.neo4jSync.syncNodeToNeo4j, {
|
|
1563
|
+
nodeId: args.nodeId,
|
|
1564
|
+
operation: "upsert"
|
|
1565
|
+
});
|
|
1566
|
+
await ctx.db.insert("epistemicAudit", {
|
|
1567
|
+
entityType: "belief",
|
|
1568
|
+
entityId: args.nodeId,
|
|
1569
|
+
changeType: "confidence_changed",
|
|
1570
|
+
previousState: {
|
|
1571
|
+
confidence: previousConfidence,
|
|
1572
|
+
tupleContradicted: previousTupleContradicted
|
|
1573
|
+
},
|
|
1574
|
+
newState: {
|
|
1575
|
+
opinion: nextOpinion,
|
|
1576
|
+
confidence: derivedConfidence,
|
|
1577
|
+
trigger: args.trigger,
|
|
1578
|
+
rationale: storedRationale,
|
|
1579
|
+
tupleContradicted: tupleTransition.tupleContradicted,
|
|
1580
|
+
tupleContradictionPolicy: tupleTransition.policy,
|
|
1581
|
+
...tupleContradictionId ? { tupleContradictionId: String(tupleContradictionId) } : {}
|
|
1582
|
+
},
|
|
1583
|
+
changedBy: args.authenticatedUserId,
|
|
1584
|
+
isAgent: false,
|
|
1585
|
+
changedAt: now,
|
|
1586
|
+
projectId: node.projectId,
|
|
1587
|
+
topicId: node.topicId
|
|
1588
|
+
});
|
|
1589
|
+
if (tupleTransition.crossedIntoTupleContradiction || tupleTransition.crossedOutOfTupleContradiction) {
|
|
1590
|
+
await ctx.db.insert("epistemicAudit", {
|
|
1591
|
+
entityType: "belief",
|
|
1592
|
+
entityId: args.nodeId,
|
|
1593
|
+
changeType: "updated",
|
|
1594
|
+
previousState: { tupleContradicted: previousTupleContradicted },
|
|
1595
|
+
newState: {
|
|
1596
|
+
tupleContradicted: tupleTransition.tupleContradicted,
|
|
1597
|
+
action: tupleTransition.crossedIntoTupleContradiction ? "tuple_contradiction_detected" : "tuple_contradiction_cleared",
|
|
1598
|
+
opinion: nextOpinion,
|
|
1599
|
+
tupleContradictionPolicy: tupleTransition.policy,
|
|
1600
|
+
...tupleContradictionId ? { tupleContradictionId: String(tupleContradictionId) } : {}
|
|
1601
|
+
},
|
|
1602
|
+
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.`,
|
|
1603
|
+
changedBy: args.authenticatedUserId,
|
|
1604
|
+
isAgent: false,
|
|
1605
|
+
changedAt: now,
|
|
1606
|
+
projectId: node.projectId,
|
|
1607
|
+
topicId: node.topicId
|
|
1608
|
+
});
|
|
1609
|
+
}
|
|
1610
|
+
if (Math.abs(derivedConfidence - previousConfidence) >= 0.15) {
|
|
1611
|
+
await ctx.scheduler.runAfter(
|
|
1612
|
+
5e3,
|
|
1613
|
+
internal.bi.contradictionSemanticDetector.scanAffectedBeliefs,
|
|
1614
|
+
{
|
|
1615
|
+
beliefId: args.nodeId,
|
|
1616
|
+
projectId: node.projectId
|
|
1617
|
+
}
|
|
1618
|
+
);
|
|
1619
|
+
}
|
|
1620
|
+
if (node.workspaceId && node.tenantId) {
|
|
1621
|
+
await ctx.scheduler.runAfter(
|
|
1622
|
+
0,
|
|
1623
|
+
internal.publication.evaluateNodePublication,
|
|
1624
|
+
{ nodeId: args.nodeId }
|
|
1625
|
+
);
|
|
1626
|
+
}
|
|
1627
|
+
return {
|
|
1628
|
+
nodeId: args.nodeId,
|
|
1629
|
+
previousConfidence,
|
|
1630
|
+
newConfidence: derivedConfidence,
|
|
1631
|
+
opinion: { b: slB, d: slD, u: slU, a: slA },
|
|
1632
|
+
beliefConfidenceId
|
|
1633
|
+
};
|
|
1634
|
+
}
|
|
1635
|
+
function propagationTriggerForEdge(edgeType, weight) {
|
|
1636
|
+
if (edgeType === "contradicts" || edgeType === "refutes") {
|
|
1637
|
+
return "contradiction_detected";
|
|
1638
|
+
}
|
|
1639
|
+
if ((edgeType === "supports" || edgeType === "informs") && weight < 0) {
|
|
1640
|
+
return "contradiction_detected";
|
|
1641
|
+
}
|
|
1642
|
+
return "evidence_added";
|
|
1643
|
+
}
|
|
1644
|
+
internalMutation({
|
|
1645
|
+
args: {
|
|
1646
|
+
nodeId: v.id("epistemicNodes"),
|
|
1647
|
+
opinion_b: v.number(),
|
|
1648
|
+
opinion_d: v.number(),
|
|
1649
|
+
opinion_u: v.number(),
|
|
1650
|
+
opinion_a: v.number(),
|
|
1651
|
+
userId: v.string()
|
|
1652
|
+
},
|
|
1653
|
+
returns: permissiveReturn,
|
|
1654
|
+
handler: async (ctx, args) => {
|
|
1655
|
+
const sourceOpinion = mkOpinion(
|
|
1656
|
+
args.opinion_b,
|
|
1657
|
+
args.opinion_d,
|
|
1658
|
+
args.opinion_u,
|
|
1659
|
+
args.opinion_a
|
|
1660
|
+
);
|
|
1661
|
+
const sourceNode = await ctx.db.get(args.nodeId);
|
|
1662
|
+
const sourceScope = await resolveNodeScopeForWorkspaceIsolation(
|
|
1663
|
+
ctx,
|
|
1664
|
+
sourceNode
|
|
1665
|
+
);
|
|
1666
|
+
const dispatches = await collectConfidencePropagationDispatches({
|
|
1667
|
+
sourceNodeId: args.nodeId,
|
|
1668
|
+
sourceOpinion,
|
|
1669
|
+
sourceScope,
|
|
1670
|
+
queryEdges: async ({ nodeId, spec, direction }) => {
|
|
1671
|
+
return await ctx.db.query("epistemicEdges").withIndex(
|
|
1672
|
+
direction === "outgoing" ? "by_from_type" : "by_to_type",
|
|
1673
|
+
(q) => direction === "outgoing" ? q.eq("fromNodeId", nodeId).eq("edgeType", spec.edgeType) : q.eq("toNodeId", nodeId).eq("edgeType", spec.edgeType)
|
|
1674
|
+
).collect();
|
|
1675
|
+
},
|
|
1676
|
+
getNode: async (nodeId) => await ctx.db.get(nodeId)
|
|
1677
|
+
});
|
|
1678
|
+
for (const dispatch of dispatches) {
|
|
1679
|
+
await applyBeliefConfidenceChange(ctx, {
|
|
1680
|
+
nodeId: dispatch.targetNodeId,
|
|
1681
|
+
belief: dispatch.opinion.b,
|
|
1682
|
+
disbelief: dispatch.opinion.d,
|
|
1683
|
+
uncertainty: dispatch.opinion.u,
|
|
1684
|
+
baseRate: dispatch.opinion.a,
|
|
1685
|
+
trigger: propagationTriggerForEdge(dispatch.edgeType, dispatch.weight),
|
|
1686
|
+
rationale: `SL propagation via ${dispatch.edgeType} edge: ${dispatch.rationale}`,
|
|
1687
|
+
authenticatedUserId: args.userId,
|
|
1688
|
+
slOperator: dispatch.operator
|
|
1689
|
+
});
|
|
1690
|
+
}
|
|
1691
|
+
return {
|
|
1692
|
+
propagated: dispatches.map((dispatch) => ({
|
|
1693
|
+
targetNodeId: String(dispatch.targetNodeId),
|
|
1694
|
+
edgeType: dispatch.edgeType,
|
|
1695
|
+
operator: dispatch.operator
|
|
1696
|
+
})),
|
|
1697
|
+
count: dispatches.length
|
|
1698
|
+
};
|
|
1699
|
+
}
|
|
1700
|
+
});
|
|
1701
|
+
|
|
1702
|
+
// src/globalId.ts
|
|
1703
|
+
function generateGlobalId() {
|
|
1704
|
+
const bytes = new Uint8Array(16);
|
|
1705
|
+
crypto.getRandomValues(bytes);
|
|
1706
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
1707
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
1708
|
+
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join(
|
|
1709
|
+
""
|
|
1710
|
+
);
|
|
1711
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
// src/epistemicContracts.evaluators.ts
|
|
1715
|
+
var evaluatorRegistry = /* @__PURE__ */ new Map();
|
|
1716
|
+
var BUILT_IN_EVIDENTIAL_ALIASES2 = METRIC_COMPARATOR_EVALUATOR_NAMES.evidentialAliases;
|
|
1717
|
+
var BUILT_IN_METRIC_CHECKER2 = METRIC_COMPARATOR_EVALUATOR_NAMES.metricChecker;
|
|
1718
|
+
var BUILT_IN_REFERENCE_CHECK_COUNTER2 = METRIC_COMPARATOR_EVALUATOR_NAMES.referenceCheckCounter;
|
|
1719
|
+
var BUILT_IN_TEMPORAL_DEADLINE2 = METRIC_COMPARATOR_EVALUATOR_NAMES.temporalDeadline;
|
|
1720
|
+
var BUILT_IN_MARKET_INDEX_COMPARATOR2 = METRIC_COMPARATOR_EVALUATOR_NAMES.marketIndexComparator;
|
|
1721
|
+
var MAX_CONTRACT_EVALUATION_BATCH_SIZE = 50;
|
|
1722
|
+
function ensureBuiltInEvaluators() {
|
|
1723
|
+
for (const name of BUILT_IN_EVIDENTIAL_ALIASES2) {
|
|
1724
|
+
if (evaluatorRegistry.has(name)) {
|
|
1725
|
+
continue;
|
|
1726
|
+
}
|
|
1727
|
+
evaluatorRegistry.set(name, {
|
|
1728
|
+
name,
|
|
1729
|
+
evaluate: evaluateBuiltInEvidentialContract
|
|
1730
|
+
});
|
|
1731
|
+
}
|
|
1732
|
+
for (const evaluator of ENGINEERING_EPISTEMIC_EVALUATORS) {
|
|
1733
|
+
if (evaluatorRegistry.has(evaluator.name)) {
|
|
1734
|
+
continue;
|
|
1735
|
+
}
|
|
1736
|
+
evaluatorRegistry.set(evaluator.name, evaluator);
|
|
1737
|
+
}
|
|
1738
|
+
const researchEvaluators = [
|
|
1739
|
+
[BUILT_IN_METRIC_CHECKER2, evaluateMetricCheckerContract],
|
|
1740
|
+
[BUILT_IN_REFERENCE_CHECK_COUNTER2, evaluateReferenceCheckCounterContract],
|
|
1741
|
+
[BUILT_IN_TEMPORAL_DEADLINE2, evaluateTemporalDeadlineContract],
|
|
1742
|
+
[BUILT_IN_MARKET_INDEX_COMPARATOR2, evaluateMarketIndexComparatorContract]
|
|
1743
|
+
];
|
|
1744
|
+
for (const [name, evaluate] of researchEvaluators) {
|
|
1745
|
+
if (evaluatorRegistry.has(name)) {
|
|
1746
|
+
continue;
|
|
1747
|
+
}
|
|
1748
|
+
evaluatorRegistry.set(name, { name, evaluate });
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
function normalizeTrigger(trigger) {
|
|
1752
|
+
if (trigger === "evidence_added") {
|
|
1753
|
+
return "evidence_added";
|
|
1754
|
+
}
|
|
1755
|
+
if (trigger === "periodic") {
|
|
1756
|
+
return "periodic";
|
|
1757
|
+
}
|
|
1758
|
+
if (trigger === "manual") {
|
|
1759
|
+
return "manual";
|
|
1760
|
+
}
|
|
1761
|
+
return "event_driven";
|
|
1762
|
+
}
|
|
1763
|
+
function resolveSchedulesForTrigger(trigger) {
|
|
1764
|
+
if (trigger === "evidence_added") {
|
|
1765
|
+
return /* @__PURE__ */ new Set(["on_evidence", "event_driven"]);
|
|
1766
|
+
}
|
|
1767
|
+
if (trigger === "periodic") {
|
|
1768
|
+
return /* @__PURE__ */ new Set(["periodic"]);
|
|
1769
|
+
}
|
|
1770
|
+
if (trigger === "manual") {
|
|
1771
|
+
return /* @__PURE__ */ new Set(["on_demand", "event_driven"]);
|
|
1772
|
+
}
|
|
1773
|
+
return /* @__PURE__ */ new Set(["event_driven"]);
|
|
1774
|
+
}
|
|
1775
|
+
async function executeContractEvaluation(args) {
|
|
1776
|
+
ensureBuiltInEvaluators();
|
|
1777
|
+
const evaluator = evaluatorRegistry.get(args.contract.condition.evaluator);
|
|
1778
|
+
let evaluation;
|
|
1779
|
+
if (evaluator) {
|
|
1780
|
+
try {
|
|
1781
|
+
evaluation = await evaluator.evaluate({
|
|
1782
|
+
belief: args.belief,
|
|
1783
|
+
contract: args.contract,
|
|
1784
|
+
ctx: args.ctx,
|
|
1785
|
+
now: args.now,
|
|
1786
|
+
resultData: args.resultData,
|
|
1787
|
+
trigger: args.trigger,
|
|
1788
|
+
inputData: args.inputData
|
|
1789
|
+
});
|
|
1790
|
+
} catch (error) {
|
|
1791
|
+
evaluation = {
|
|
1792
|
+
result: "error",
|
|
1793
|
+
rationale: error instanceof Error ? error.message : "Unknown evaluator error."
|
|
1794
|
+
};
|
|
1795
|
+
}
|
|
1796
|
+
} else {
|
|
1797
|
+
evaluation = {
|
|
1798
|
+
result: "error",
|
|
1799
|
+
rationale: `No epistemic evaluator registered for "${args.contract.condition.evaluator}".`
|
|
1800
|
+
};
|
|
1801
|
+
}
|
|
1802
|
+
const confidenceBefore = typeof args.currentConfidence === "number" ? args.currentConfidence : typeof args.belief.confidence === "number" ? args.belief.confidence : 0.5;
|
|
1803
|
+
const modulationPlan = deriveContractModulationPlan({
|
|
1804
|
+
currentConfidence: confidenceBefore,
|
|
1805
|
+
modulation: args.contract.modulation,
|
|
1806
|
+
result: evaluation.result,
|
|
1807
|
+
resultConfidence: evaluation.confidence
|
|
1808
|
+
});
|
|
1809
|
+
let beliefConfidenceId;
|
|
1810
|
+
let confidenceAfter = confidenceBefore;
|
|
1811
|
+
if (modulationPlan) {
|
|
1812
|
+
const contractB = Math.max(0, Math.min(1, modulationPlan.confidenceAfter));
|
|
1813
|
+
const modulationResult = await applyBeliefConfidenceChange(args.ctx, {
|
|
1814
|
+
nodeId: args.contract.beliefNodeId,
|
|
1815
|
+
belief: contractB,
|
|
1816
|
+
disbelief: 1 - contractB,
|
|
1817
|
+
uncertainty: 0,
|
|
1818
|
+
baseRate: 0.5,
|
|
1819
|
+
trigger: modulationPlan.trigger,
|
|
1820
|
+
rationale: `Epistemic contract "${args.contract.title}" ${evaluation.result}: ${evaluation.rationale}`,
|
|
1821
|
+
authenticatedUserId: args.authenticatedUserId
|
|
1822
|
+
});
|
|
1823
|
+
beliefConfidenceId = modulationResult.beliefConfidenceId;
|
|
1824
|
+
confidenceAfter = typeof modulationResult.newConfidence === "number" ? modulationResult.newConfidence : modulationPlan.confidenceAfter;
|
|
1825
|
+
}
|
|
1826
|
+
const evaluationId = await args.ctx.db.insert("contractEvaluations", {
|
|
1827
|
+
contractId: args.contract.contractId,
|
|
1828
|
+
beliefNodeId: args.contract.beliefNodeId,
|
|
1829
|
+
result: evaluation.result,
|
|
1830
|
+
evaluatedAt: args.now,
|
|
1831
|
+
evaluator: args.contract.condition.evaluator,
|
|
1832
|
+
trigger: args.trigger,
|
|
1833
|
+
resultData: evaluation.data,
|
|
1834
|
+
modulationApplied: Boolean(modulationPlan),
|
|
1835
|
+
confidenceDelta: modulationPlan?.confidenceDelta,
|
|
1836
|
+
confidenceBefore: modulationPlan?.confidenceBefore,
|
|
1837
|
+
confidenceAfter: modulationPlan ? confidenceAfter : void 0,
|
|
1838
|
+
beliefConfidenceId,
|
|
1839
|
+
modulationRationale: evaluation.rationale,
|
|
1840
|
+
topicId: args.contract.topicId
|
|
1841
|
+
});
|
|
1842
|
+
const nextStatus = deriveContractStatus(evaluation.result, args.contract.status);
|
|
1843
|
+
await args.ctx.db.patch(
|
|
1844
|
+
args.contract._id,
|
|
1845
|
+
{
|
|
1846
|
+
status: nextStatus,
|
|
1847
|
+
lastEvaluatedAt: args.now,
|
|
1848
|
+
evaluationCount: (args.contract.evaluationCount ?? 0) + 1,
|
|
1849
|
+
updatedAt: args.now
|
|
1850
|
+
}
|
|
1851
|
+
);
|
|
1852
|
+
return {
|
|
1853
|
+
evaluationId,
|
|
1854
|
+
result: evaluation.result,
|
|
1855
|
+
status: nextStatus,
|
|
1856
|
+
confidenceBefore,
|
|
1857
|
+
confidenceAfter,
|
|
1858
|
+
rationale: evaluation.rationale,
|
|
1859
|
+
data: evaluation.data,
|
|
1860
|
+
currentConfidence: confidenceAfter
|
|
1861
|
+
};
|
|
1862
|
+
}
|
|
1863
|
+
async function evaluateContractsForTriggerBatch(args) {
|
|
1864
|
+
const startedAt = Date.now();
|
|
1865
|
+
const contracts = await loadContractsForTrigger({
|
|
1866
|
+
ctx: args.ctx,
|
|
1867
|
+
beliefNodeId: args.belief._id,
|
|
1868
|
+
trigger: normalizeTrigger(args.trigger),
|
|
1869
|
+
contractIds: args.contractIds
|
|
1870
|
+
});
|
|
1871
|
+
const batchLimit = Math.max(
|
|
1872
|
+
1,
|
|
1873
|
+
Math.min(
|
|
1874
|
+
args.maxBatchSize ?? MAX_CONTRACT_EVALUATION_BATCH_SIZE,
|
|
1875
|
+
MAX_CONTRACT_EVALUATION_BATCH_SIZE
|
|
1876
|
+
)
|
|
1877
|
+
);
|
|
1878
|
+
const currentBatch = contracts.slice(0, batchLimit);
|
|
1879
|
+
const overflowContracts = contracts.slice(batchLimit);
|
|
1880
|
+
let runningConfidence = typeof args.belief.confidence === "number" ? args.belief.confidence : 0.5;
|
|
1881
|
+
const results = [];
|
|
1882
|
+
for (const contract of currentBatch) {
|
|
1883
|
+
const evaluation = await executeContractEvaluation({
|
|
1884
|
+
ctx: args.ctx,
|
|
1885
|
+
belief: args.belief,
|
|
1886
|
+
contract,
|
|
1887
|
+
now: Date.now(),
|
|
1888
|
+
trigger: normalizeTrigger(args.trigger),
|
|
1889
|
+
inputData: args.inputData,
|
|
1890
|
+
authenticatedUserId: args.authenticatedUserId,
|
|
1891
|
+
currentConfidence: runningConfidence
|
|
1892
|
+
});
|
|
1893
|
+
runningConfidence = evaluation.currentConfidence;
|
|
1894
|
+
args.belief.confidence = runningConfidence;
|
|
1895
|
+
results.push({
|
|
1896
|
+
contractId: contract.contractId,
|
|
1897
|
+
result: evaluation.result,
|
|
1898
|
+
status: evaluation.status,
|
|
1899
|
+
confidenceAfter: evaluation.confidenceAfter
|
|
1900
|
+
});
|
|
1901
|
+
}
|
|
1902
|
+
if (overflowContracts.length > 0) {
|
|
1903
|
+
await args.ctx.scheduler.runAfter(
|
|
1904
|
+
0,
|
|
1905
|
+
"epistemicContracts.processContractEvaluationOverflow",
|
|
1906
|
+
{
|
|
1907
|
+
beliefNodeId: args.belief._id,
|
|
1908
|
+
trigger: normalizeTrigger(args.trigger),
|
|
1909
|
+
contractIds: overflowContracts.map((contract) => contract.contractId),
|
|
1910
|
+
inputData: args.inputData,
|
|
1911
|
+
authenticatedUserId: args.authenticatedUserId,
|
|
1912
|
+
maxBatchSize: batchLimit
|
|
1913
|
+
}
|
|
1914
|
+
);
|
|
1915
|
+
}
|
|
1916
|
+
const executionTimeMs = Date.now() - startedAt;
|
|
1917
|
+
console.info("[epistemicContracts] processed contract evaluation batch", {
|
|
1918
|
+
beliefNodeId: String(args.belief._id),
|
|
1919
|
+
trigger: normalizeTrigger(args.trigger),
|
|
1920
|
+
batchSize: currentBatch.length,
|
|
1921
|
+
overflowCount: overflowContracts.length,
|
|
1922
|
+
executionTimeMs
|
|
1923
|
+
});
|
|
1924
|
+
return {
|
|
1925
|
+
totalContracts: contracts.length,
|
|
1926
|
+
processedCount: currentBatch.length,
|
|
1927
|
+
overflowCount: overflowContracts.length,
|
|
1928
|
+
scheduledOverflow: overflowContracts.length > 0,
|
|
1929
|
+
batchSize: currentBatch.length,
|
|
1930
|
+
executionTimeMs,
|
|
1931
|
+
trigger: normalizeTrigger(args.trigger),
|
|
1932
|
+
results
|
|
1933
|
+
};
|
|
1934
|
+
}
|
|
1935
|
+
async function loadContractsForBelief(args) {
|
|
1936
|
+
return await args.ctx.db.query("epistemicContracts").withIndex("by_belief", (q) => q.eq("beliefNodeId", args.beliefNodeId)).collect();
|
|
1937
|
+
}
|
|
1938
|
+
async function loadContractsForTrigger(args) {
|
|
1939
|
+
const contracts = await loadContractsForBelief(args);
|
|
1940
|
+
ensureBuiltInEvaluators();
|
|
1941
|
+
const allowedSchedules = resolveSchedulesForTrigger(args.trigger);
|
|
1942
|
+
const contractIdFilter = args.contractIds && args.contractIds.length > 0 ? new Set(args.contractIds) : null;
|
|
1943
|
+
return contracts.filter((contract) => {
|
|
1944
|
+
if (contract.status === "archived") {
|
|
1945
|
+
return false;
|
|
1946
|
+
}
|
|
1947
|
+
if (contract.conditionType === "composite") {
|
|
1948
|
+
return false;
|
|
1949
|
+
}
|
|
1950
|
+
if (!evaluatorRegistry.has(contract.condition.evaluator)) {
|
|
1951
|
+
return false;
|
|
1952
|
+
}
|
|
1953
|
+
if (contractIdFilter) {
|
|
1954
|
+
return contractIdFilter.has(contract.contractId);
|
|
1955
|
+
}
|
|
1956
|
+
return allowedSchedules.has(contract.evaluationSchedule);
|
|
1957
|
+
});
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
// src/evaluators/shared.ts
|
|
1961
|
+
function asArray(value) {
|
|
1962
|
+
return Array.isArray(value) ? value : [];
|
|
1963
|
+
}
|
|
1964
|
+
function asNumber(value) {
|
|
1965
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
1966
|
+
}
|
|
1967
|
+
function asRecord(value) {
|
|
1968
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
1969
|
+
}
|
|
1970
|
+
function asString(value) {
|
|
1971
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
1972
|
+
}
|
|
1973
|
+
function deriveDirectionalResult(direction, conditionSatisfied) {
|
|
1974
|
+
if (direction === "falsifies") {
|
|
1975
|
+
return conditionSatisfied ? "disconfirmed" : "confirmed";
|
|
1976
|
+
}
|
|
1977
|
+
return conditionSatisfied ? "confirmed" : "disconfirmed";
|
|
1978
|
+
}
|
|
1979
|
+
function extractTextCandidates(value) {
|
|
1980
|
+
const record = asRecord(value);
|
|
1981
|
+
if (!record) {
|
|
1982
|
+
const candidate = asString(value);
|
|
1983
|
+
return candidate ? [candidate] : [];
|
|
1984
|
+
}
|
|
1985
|
+
const candidates = [
|
|
1986
|
+
record.stdout,
|
|
1987
|
+
record.stderr,
|
|
1988
|
+
record.output,
|
|
1989
|
+
record.text,
|
|
1990
|
+
record.message,
|
|
1991
|
+
record.raw
|
|
1992
|
+
].map(asString).filter((candidate) => Boolean(candidate));
|
|
1993
|
+
for (const nestedKey of ["report", "result", "data", "payload"]) {
|
|
1994
|
+
const nested = record[nestedKey];
|
|
1995
|
+
if (nested !== value) {
|
|
1996
|
+
candidates.push(...extractTextCandidates(nested));
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
return Array.from(new Set(candidates));
|
|
2000
|
+
}
|
|
2001
|
+
function normalizeFilePath(value) {
|
|
2002
|
+
return value.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
2003
|
+
}
|
|
2004
|
+
function normalizeToolResultEnvelope(value) {
|
|
2005
|
+
const record = asRecord(value);
|
|
2006
|
+
if (!record) {
|
|
2007
|
+
return {
|
|
2008
|
+
output: asString(value) ?? void 0
|
|
2009
|
+
};
|
|
2010
|
+
}
|
|
2011
|
+
const exitCode = asNumber(record.exitCode) ?? asNumber(record.code) ?? asNumber(record.status) ?? null;
|
|
2012
|
+
return {
|
|
2013
|
+
command: asString(record.command) ?? void 0,
|
|
2014
|
+
data: record.data ?? record.payload,
|
|
2015
|
+
exitCode,
|
|
2016
|
+
output: asString(record.output) ?? void 0,
|
|
2017
|
+
report: record.report ?? record.json ?? record.result ?? record.payload ?? void 0,
|
|
2018
|
+
stderr: asString(record.stderr) ?? void 0,
|
|
2019
|
+
stdout: asString(record.stdout) ?? void 0
|
|
2020
|
+
};
|
|
2021
|
+
}
|
|
2022
|
+
function parseJsonCandidate(value) {
|
|
2023
|
+
if (value === null || value === void 0) {
|
|
2024
|
+
return null;
|
|
2025
|
+
}
|
|
2026
|
+
if (typeof value !== "string") {
|
|
2027
|
+
return value;
|
|
2028
|
+
}
|
|
2029
|
+
try {
|
|
2030
|
+
return JSON.parse(value);
|
|
2031
|
+
} catch (error) {
|
|
2032
|
+
debugGraphPrimitiveFallback(
|
|
2033
|
+
"[evaluators/shared] Failed to parse JSON candidate",
|
|
2034
|
+
{
|
|
2035
|
+
error,
|
|
2036
|
+
valueType: typeof value
|
|
2037
|
+
}
|
|
2038
|
+
);
|
|
2039
|
+
return null;
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
function patternMatchesPath(filePath, pattern) {
|
|
2043
|
+
const normalizedPath = normalizeFilePath(filePath);
|
|
2044
|
+
const normalizedPattern = normalizeFilePath(pattern);
|
|
2045
|
+
const escaped = normalizedPattern.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
2046
|
+
const regexPattern = escaped.replace(/\\\*\\\*/g, ".*").replace(/\\\*/g, "[^/]*").replace(/\\\?/g, "[^/]");
|
|
2047
|
+
return new RegExp(`^${regexPattern}$`).test(normalizedPath);
|
|
2048
|
+
}
|
|
2049
|
+
function somePatternMatches(filePath, patterns) {
|
|
2050
|
+
if (!filePath) {
|
|
2051
|
+
return false;
|
|
2052
|
+
}
|
|
2053
|
+
return patterns.some((pattern) => patternMatchesPath(filePath, pattern));
|
|
2054
|
+
}
|
|
2055
|
+
|
|
2056
|
+
// src/evaluators/lintCheckerEvaluator.ts
|
|
2057
|
+
function parseConfig(condition) {
|
|
2058
|
+
const record = asRecord(condition.evaluatorConfig);
|
|
2059
|
+
if (!record) {
|
|
2060
|
+
throw new Error(
|
|
2061
|
+
'lint_checker requires evaluatorConfig with { filePatterns: string[], linter: "biome" }.'
|
|
2062
|
+
);
|
|
2063
|
+
}
|
|
2064
|
+
const filePatterns = asArray(record.filePatterns).map(asString).filter((pattern) => Boolean(pattern));
|
|
2065
|
+
if (filePatterns.length === 0) {
|
|
2066
|
+
throw new Error("lint_checker requires at least one file pattern.");
|
|
2067
|
+
}
|
|
2068
|
+
const linter = asString(record.linter);
|
|
2069
|
+
if (linter !== "biome") {
|
|
2070
|
+
throw new Error(`Unsupported linter: ${String(record.linter)}`);
|
|
2071
|
+
}
|
|
2072
|
+
return {
|
|
2073
|
+
filePatterns,
|
|
2074
|
+
linter
|
|
2075
|
+
};
|
|
2076
|
+
}
|
|
2077
|
+
function parseDiagnostics(resultData) {
|
|
2078
|
+
const envelope = normalizeToolResultEnvelope(resultData);
|
|
2079
|
+
const record = asRecord(envelope.report) ?? asRecord(envelope.data) ?? asRecord(resultData);
|
|
2080
|
+
if (!record) {
|
|
2081
|
+
return [];
|
|
2082
|
+
}
|
|
2083
|
+
return asArray(record.diagnostics).map((entry) => {
|
|
2084
|
+
const diagnostic = asRecord(entry);
|
|
2085
|
+
if (!diagnostic) {
|
|
2086
|
+
return null;
|
|
2087
|
+
}
|
|
2088
|
+
const location = asRecord(diagnostic.location);
|
|
2089
|
+
const pathRecord = asRecord(location?.path);
|
|
2090
|
+
const filePath = asString(diagnostic.filePath) ?? asString(diagnostic.file) ?? asString(location?.filePath) ?? asString(pathRecord?.file);
|
|
2091
|
+
const severity = asString(diagnostic.severity) ?? "error";
|
|
2092
|
+
const message = asString(diagnostic.message) ?? asString(diagnostic.description) ?? "Lint issue";
|
|
2093
|
+
return {
|
|
2094
|
+
filePath: filePath ? normalizeFilePath(filePath) : null,
|
|
2095
|
+
message,
|
|
2096
|
+
severity
|
|
2097
|
+
};
|
|
2098
|
+
}).filter(
|
|
2099
|
+
(diagnostic) => Boolean(diagnostic)
|
|
2100
|
+
);
|
|
2101
|
+
}
|
|
2102
|
+
function getMatchedDiagnostics(contract, resultData) {
|
|
2103
|
+
const config = parseConfig(contract.condition);
|
|
2104
|
+
return parseDiagnostics(resultData).filter(
|
|
2105
|
+
(diagnostic) => somePatternMatches(diagnostic.filePath, config.filePatterns)
|
|
2106
|
+
);
|
|
2107
|
+
}
|
|
2108
|
+
var lintCheckerEvaluator = {
|
|
2109
|
+
name: "lint_checker",
|
|
2110
|
+
matches({ contract, resultData }) {
|
|
2111
|
+
const envelope = normalizeToolResultEnvelope(resultData);
|
|
2112
|
+
const exitCode = asNumber(envelope.exitCode);
|
|
2113
|
+
if (exitCode === 0) {
|
|
2114
|
+
return true;
|
|
2115
|
+
}
|
|
2116
|
+
return getMatchedDiagnostics(contract, resultData).length > 0;
|
|
2117
|
+
},
|
|
2118
|
+
evaluate(args) {
|
|
2119
|
+
const config = parseConfig(args.contract.condition);
|
|
2120
|
+
if (!args.resultData) {
|
|
2121
|
+
return {
|
|
2122
|
+
result: "error",
|
|
2123
|
+
rationale: "lint_checker requires Biome lint resultData."
|
|
2124
|
+
};
|
|
2125
|
+
}
|
|
2126
|
+
const envelope = normalizeToolResultEnvelope(args.resultData);
|
|
2127
|
+
const exitCode = asNumber(envelope.exitCode);
|
|
2128
|
+
const matchedDiagnostics = getMatchedDiagnostics(args.contract, args.resultData);
|
|
2129
|
+
if (matchedDiagnostics.length === 0 && exitCode !== 0 && exitCode !== null) {
|
|
2130
|
+
return {
|
|
2131
|
+
result: "inconclusive",
|
|
2132
|
+
rationale: "Biome reported issues, but none matched the configured file patterns."
|
|
2133
|
+
};
|
|
2134
|
+
}
|
|
2135
|
+
const conditionSatisfied = exitCode === 0 || exitCode !== null && matchedDiagnostics.length === 0;
|
|
2136
|
+
return {
|
|
2137
|
+
result: deriveDirectionalResult(
|
|
2138
|
+
args.contract.direction,
|
|
2139
|
+
conditionSatisfied
|
|
2140
|
+
),
|
|
2141
|
+
rationale: conditionSatisfied ? `Biome reported no matching lint diagnostics for ${config.filePatterns.join(", ")}.` : `Biome reported ${matchedDiagnostics.length} matching issue(s): ${matchedDiagnostics.map(
|
|
2142
|
+
(diagnostic) => diagnostic.filePath ? `${diagnostic.filePath} (${diagnostic.severity})` : diagnostic.message
|
|
2143
|
+
).join(", ")}.`,
|
|
2144
|
+
data: {
|
|
2145
|
+
exitCode,
|
|
2146
|
+
filePatterns: config.filePatterns,
|
|
2147
|
+
linter: config.linter,
|
|
2148
|
+
matchedDiagnostics
|
|
2149
|
+
}
|
|
2150
|
+
};
|
|
2151
|
+
}
|
|
2152
|
+
};
|
|
2153
|
+
|
|
2154
|
+
// src/evaluators/sentryCheckerEvaluator.ts
|
|
2155
|
+
function parseConfig2(condition) {
|
|
2156
|
+
const record = asRecord(condition.evaluatorConfig);
|
|
2157
|
+
if (!record) {
|
|
2158
|
+
throw new Error(
|
|
2159
|
+
"sentry_checker requires evaluatorConfig with { module, windowDays }."
|
|
2160
|
+
);
|
|
2161
|
+
}
|
|
2162
|
+
const moduleName = asString(record.module);
|
|
2163
|
+
const windowDays = asNumber(record.windowDays);
|
|
2164
|
+
if (!moduleName || windowDays === null) {
|
|
2165
|
+
throw new Error(
|
|
2166
|
+
"sentry_checker requires a module name and numeric windowDays."
|
|
2167
|
+
);
|
|
2168
|
+
}
|
|
2169
|
+
return {
|
|
2170
|
+
module: moduleName,
|
|
2171
|
+
windowDays
|
|
2172
|
+
};
|
|
2173
|
+
}
|
|
2174
|
+
function parseIncidentCount(resultData) {
|
|
2175
|
+
const envelope = normalizeToolResultEnvelope(resultData);
|
|
2176
|
+
const record = asRecord(envelope.report) ?? asRecord(envelope.data) ?? asRecord(resultData);
|
|
2177
|
+
if (!record) {
|
|
2178
|
+
return {
|
|
2179
|
+
incidentCount: null,
|
|
2180
|
+
moduleName: null,
|
|
2181
|
+
windowDays: null
|
|
2182
|
+
};
|
|
2183
|
+
}
|
|
2184
|
+
const incidents = asArray(record.incidents);
|
|
2185
|
+
const incidentCount = asNumber(record.incidentCount) ?? (incidents.length > 0 ? incidents.length : null);
|
|
2186
|
+
return {
|
|
2187
|
+
incidentCount,
|
|
2188
|
+
moduleName: asString(record.module),
|
|
2189
|
+
windowDays: asNumber(record.windowDays)
|
|
2190
|
+
};
|
|
2191
|
+
}
|
|
2192
|
+
function matchesModule(contract, resultData) {
|
|
2193
|
+
const config = parseConfig2(contract.condition);
|
|
2194
|
+
const payload = parseIncidentCount(resultData);
|
|
2195
|
+
return !payload.moduleName || payload.moduleName === config.module;
|
|
2196
|
+
}
|
|
2197
|
+
var sentryCheckerEvaluator = {
|
|
2198
|
+
name: "sentry_checker",
|
|
2199
|
+
matches({ contract, resultData }) {
|
|
2200
|
+
return matchesModule(contract, resultData);
|
|
2201
|
+
},
|
|
2202
|
+
evaluate(args) {
|
|
2203
|
+
const config = parseConfig2(args.contract.condition);
|
|
2204
|
+
if (!args.resultData) {
|
|
2205
|
+
return {
|
|
2206
|
+
result: "error",
|
|
2207
|
+
rationale: "sentry_checker requires incident count resultData."
|
|
2208
|
+
};
|
|
2209
|
+
}
|
|
2210
|
+
const payload = parseIncidentCount(args.resultData);
|
|
2211
|
+
if (payload.incidentCount === null) {
|
|
2212
|
+
return {
|
|
2213
|
+
result: "error",
|
|
2214
|
+
rationale: "sentry_checker could not determine an incident count."
|
|
2215
|
+
};
|
|
2216
|
+
}
|
|
2217
|
+
if (!matchesModule(args.contract, args.resultData)) {
|
|
2218
|
+
return {
|
|
2219
|
+
result: "inconclusive",
|
|
2220
|
+
rationale: `Sentry result targeted ${payload.moduleName}, not ${config.module}.`
|
|
2221
|
+
};
|
|
2222
|
+
}
|
|
2223
|
+
const conditionSatisfied = payload.incidentCount === 0;
|
|
2224
|
+
return {
|
|
2225
|
+
result: deriveDirectionalResult(
|
|
2226
|
+
args.contract.direction,
|
|
2227
|
+
conditionSatisfied
|
|
2228
|
+
),
|
|
2229
|
+
rationale: conditionSatisfied ? `Sentry reported zero incidents for ${config.module} over ${config.windowDays} day(s).` : `Sentry reported ${payload.incidentCount} incident(s) for ${config.module} over ${config.windowDays} day(s).`,
|
|
2230
|
+
data: {
|
|
2231
|
+
incidentCount: payload.incidentCount,
|
|
2232
|
+
module: config.module,
|
|
2233
|
+
windowDays: config.windowDays
|
|
2234
|
+
}
|
|
2235
|
+
};
|
|
2236
|
+
}
|
|
2237
|
+
};
|
|
2238
|
+
|
|
2239
|
+
// src/evaluators/testRunnerEvaluator.ts
|
|
2240
|
+
function parseConfig3(condition) {
|
|
2241
|
+
const record = asRecord(condition.evaluatorConfig);
|
|
2242
|
+
if (!record) {
|
|
2243
|
+
throw new Error(
|
|
2244
|
+
'test_runner requires evaluatorConfig with { testPattern, runner: "vitest" }.'
|
|
2245
|
+
);
|
|
2246
|
+
}
|
|
2247
|
+
const testPattern = asString(record.testPattern);
|
|
2248
|
+
if (!testPattern) {
|
|
2249
|
+
throw new Error("test_runner requires a non-empty testPattern.");
|
|
2250
|
+
}
|
|
2251
|
+
const runner = asString(record.runner);
|
|
2252
|
+
if (runner !== "vitest") {
|
|
2253
|
+
throw new Error(`Unsupported test runner: ${String(record.runner)}`);
|
|
2254
|
+
}
|
|
2255
|
+
return {
|
|
2256
|
+
runner,
|
|
2257
|
+
testPattern
|
|
2258
|
+
};
|
|
2259
|
+
}
|
|
2260
|
+
function collectFailedTests(value) {
|
|
2261
|
+
const record = asRecord(value);
|
|
2262
|
+
if (!record) {
|
|
2263
|
+
return [];
|
|
2264
|
+
}
|
|
2265
|
+
const fullName = asString(record.fullName);
|
|
2266
|
+
const title = asString(record.title);
|
|
2267
|
+
const label = fullName ?? title ?? "unnamed test";
|
|
2268
|
+
const status = asString(record.status);
|
|
2269
|
+
const state = asString(asRecord(record.result)?.state);
|
|
2270
|
+
const isFailed = status === "failed" || status === "fail" || state === "failed" || state === "fail";
|
|
2271
|
+
const failures = isFailed ? [label] : [];
|
|
2272
|
+
for (const nestedKey of ["assertionResults", "tests", "tasks"]) {
|
|
2273
|
+
for (const nested of asArray(record[nestedKey])) {
|
|
2274
|
+
failures.push(...collectFailedTests(nested));
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
return failures;
|
|
2278
|
+
}
|
|
2279
|
+
function collectPassedTests(value) {
|
|
2280
|
+
const record = asRecord(value);
|
|
2281
|
+
if (!record) {
|
|
2282
|
+
return [];
|
|
2283
|
+
}
|
|
2284
|
+
const fullName = asString(record.fullName);
|
|
2285
|
+
const title = asString(record.title);
|
|
2286
|
+
const label = fullName ?? title ?? "unnamed test";
|
|
2287
|
+
const status = asString(record.status);
|
|
2288
|
+
const state = asString(asRecord(record.result)?.state);
|
|
2289
|
+
const isPassed = status === "passed" || state === "passed";
|
|
2290
|
+
const passed = isPassed ? [label] : [];
|
|
2291
|
+
for (const nestedKey of ["assertionResults", "tests", "tasks"]) {
|
|
2292
|
+
for (const nested of asArray(record[nestedKey])) {
|
|
2293
|
+
passed.push(...collectPassedTests(nested));
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
return passed;
|
|
2297
|
+
}
|
|
2298
|
+
function parseSuite(value) {
|
|
2299
|
+
const record = asRecord(value);
|
|
2300
|
+
if (!record) {
|
|
2301
|
+
return null;
|
|
2302
|
+
}
|
|
2303
|
+
const filePath = asString(record.name) ?? asString(record.file) ?? asString(record.filePath) ?? asString(record.filepath);
|
|
2304
|
+
if (!filePath) {
|
|
2305
|
+
return null;
|
|
2306
|
+
}
|
|
2307
|
+
return {
|
|
2308
|
+
failedTests: Array.from(new Set(collectFailedTests(record))),
|
|
2309
|
+
filePath: normalizeFilePath(filePath),
|
|
2310
|
+
passedTests: Array.from(new Set(collectPassedTests(record)))
|
|
2311
|
+
};
|
|
2312
|
+
}
|
|
2313
|
+
function parseVitestSuites(resultData) {
|
|
2314
|
+
const envelope = normalizeToolResultEnvelope(resultData);
|
|
2315
|
+
const candidates = [
|
|
2316
|
+
envelope.report,
|
|
2317
|
+
envelope.data,
|
|
2318
|
+
envelope.stdout,
|
|
2319
|
+
envelope.output,
|
|
2320
|
+
...extractTextCandidates(resultData)
|
|
2321
|
+
];
|
|
2322
|
+
for (const candidate of candidates) {
|
|
2323
|
+
const parsed = parseJsonCandidate(candidate);
|
|
2324
|
+
const report = asRecord(parsed);
|
|
2325
|
+
if (!report) {
|
|
2326
|
+
continue;
|
|
2327
|
+
}
|
|
2328
|
+
const suites = asArray(report.testResults).map(parseSuite).filter((suite) => Boolean(suite));
|
|
2329
|
+
if (suites.length > 0) {
|
|
2330
|
+
return suites;
|
|
2331
|
+
}
|
|
2332
|
+
const fileSuites = asArray(report.files).map(parseSuite).filter((suite) => Boolean(suite));
|
|
2333
|
+
if (fileSuites.length > 0) {
|
|
2334
|
+
return fileSuites;
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
return [];
|
|
2338
|
+
}
|
|
2339
|
+
function getMatchedSuites(contract, resultData) {
|
|
2340
|
+
const config = parseConfig3(contract.condition);
|
|
2341
|
+
return parseVitestSuites(resultData).filter(
|
|
2342
|
+
(suite) => patternMatchesPath(suite.filePath, config.testPattern)
|
|
2343
|
+
);
|
|
2344
|
+
}
|
|
2345
|
+
var testRunnerEvaluator = {
|
|
2346
|
+
name: "test_runner",
|
|
2347
|
+
matches({ contract, resultData }) {
|
|
2348
|
+
return getMatchedSuites(contract, resultData).length > 0;
|
|
2349
|
+
},
|
|
2350
|
+
evaluate(args) {
|
|
2351
|
+
const config = parseConfig3(args.contract.condition);
|
|
2352
|
+
const matchedSuites = getMatchedSuites(args.contract, args.resultData);
|
|
2353
|
+
if (!args.resultData) {
|
|
2354
|
+
return {
|
|
2355
|
+
result: "error",
|
|
2356
|
+
rationale: "test_runner requires Vitest JSON resultData."
|
|
2357
|
+
};
|
|
2358
|
+
}
|
|
2359
|
+
if (matchedSuites.length === 0) {
|
|
2360
|
+
return {
|
|
2361
|
+
result: "inconclusive",
|
|
2362
|
+
rationale: `No Vitest suites matched ${config.testPattern}.`
|
|
2363
|
+
};
|
|
2364
|
+
}
|
|
2365
|
+
const failedTests = matchedSuites.flatMap((suite) => suite.failedTests);
|
|
2366
|
+
const passedTests = matchedSuites.flatMap((suite) => suite.passedTests);
|
|
2367
|
+
const conditionSatisfied = failedTests.length === 0;
|
|
2368
|
+
return {
|
|
2369
|
+
result: deriveDirectionalResult(
|
|
2370
|
+
args.contract.direction,
|
|
2371
|
+
conditionSatisfied
|
|
2372
|
+
),
|
|
2373
|
+
rationale: conditionSatisfied ? `Vitest matched ${matchedSuites.length} suite(s) for ${config.testPattern}; all ${passedTests.length} test assertions passed.` : `Vitest matched ${matchedSuites.length} suite(s) for ${config.testPattern}; failing tests: ${failedTests.join(", ")}.`,
|
|
2374
|
+
data: {
|
|
2375
|
+
runner: config.runner,
|
|
2376
|
+
testPattern: config.testPattern,
|
|
2377
|
+
matchedFiles: matchedSuites.map((suite) => suite.filePath),
|
|
2378
|
+
failedTests,
|
|
2379
|
+
passedTestCount: passedTests.length
|
|
2380
|
+
}
|
|
2381
|
+
};
|
|
2382
|
+
}
|
|
2383
|
+
};
|
|
2384
|
+
|
|
2385
|
+
// src/evaluators/tscCheckerEvaluator.ts
|
|
2386
|
+
function parseConfig4(condition) {
|
|
2387
|
+
const record = asRecord(condition.evaluatorConfig);
|
|
2388
|
+
if (!record) {
|
|
2389
|
+
throw new Error(
|
|
2390
|
+
"tsc_checker requires evaluatorConfig with { filePatterns: string[] }."
|
|
2391
|
+
);
|
|
2392
|
+
}
|
|
2393
|
+
const filePatterns = asArray(record.filePatterns).map(asString).filter((pattern) => Boolean(pattern));
|
|
2394
|
+
if (filePatterns.length === 0) {
|
|
2395
|
+
throw new Error("tsc_checker requires at least one file pattern.");
|
|
2396
|
+
}
|
|
2397
|
+
return { filePatterns };
|
|
2398
|
+
}
|
|
2399
|
+
function parseStructuredDiagnostics(resultData) {
|
|
2400
|
+
const envelope = normalizeToolResultEnvelope(resultData);
|
|
2401
|
+
const record = asRecord(envelope.report) ?? asRecord(envelope.data) ?? asRecord(resultData);
|
|
2402
|
+
if (!record) {
|
|
2403
|
+
return [];
|
|
2404
|
+
}
|
|
2405
|
+
return asArray(record.diagnostics).flatMap((entry) => {
|
|
2406
|
+
const diagnostic = asRecord(entry);
|
|
2407
|
+
if (!diagnostic) {
|
|
2408
|
+
return [];
|
|
2409
|
+
}
|
|
2410
|
+
const filePath = asString(diagnostic.filePath) ?? asString(diagnostic.file) ?? asString(asRecord(diagnostic.location)?.filePath) ?? asString(asRecord(asRecord(diagnostic.location)?.path)?.file);
|
|
2411
|
+
const message = asString(diagnostic.message) ?? "TypeScript error";
|
|
2412
|
+
return [
|
|
2413
|
+
{
|
|
2414
|
+
code: asString(diagnostic.code) ?? void 0,
|
|
2415
|
+
filePath: filePath ? normalizeFilePath(filePath) : null,
|
|
2416
|
+
message
|
|
2417
|
+
}
|
|
2418
|
+
];
|
|
2419
|
+
});
|
|
2420
|
+
}
|
|
2421
|
+
function parseTextDiagnostics(resultData) {
|
|
2422
|
+
const diagnostics = [];
|
|
2423
|
+
const patterns = [
|
|
2424
|
+
/^(.+?)\((\d+),(\d+)\): error TS(\d+): (.+)$/gm,
|
|
2425
|
+
/^(.+?):(\d+):(\d+) - error TS(\d+): (.+)$/gm
|
|
2426
|
+
];
|
|
2427
|
+
for (const text of extractTextCandidates(resultData)) {
|
|
2428
|
+
for (const pattern of patterns) {
|
|
2429
|
+
for (const match of text.matchAll(pattern)) {
|
|
2430
|
+
diagnostics.push({
|
|
2431
|
+
code: match[4],
|
|
2432
|
+
filePath: normalizeFilePath(match[1] ?? ""),
|
|
2433
|
+
message: match[5] ?? "TypeScript error"
|
|
2434
|
+
});
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
2438
|
+
return diagnostics;
|
|
2439
|
+
}
|
|
2440
|
+
function parseDiagnostics2(resultData) {
|
|
2441
|
+
const structured = parseStructuredDiagnostics(resultData);
|
|
2442
|
+
return structured.length > 0 ? structured : parseTextDiagnostics(resultData);
|
|
2443
|
+
}
|
|
2444
|
+
function getMatchedDiagnostics2(contract, resultData) {
|
|
2445
|
+
const config = parseConfig4(contract.condition);
|
|
2446
|
+
return parseDiagnostics2(resultData).filter(
|
|
2447
|
+
(diagnostic) => somePatternMatches(diagnostic.filePath, config.filePatterns)
|
|
2448
|
+
);
|
|
2449
|
+
}
|
|
2450
|
+
var tscCheckerEvaluator = {
|
|
2451
|
+
name: "tsc_checker",
|
|
2452
|
+
matches({ contract, resultData }) {
|
|
2453
|
+
const envelope = normalizeToolResultEnvelope(resultData);
|
|
2454
|
+
const exitCode = asNumber(envelope.exitCode);
|
|
2455
|
+
if (exitCode === 0) {
|
|
2456
|
+
return true;
|
|
2457
|
+
}
|
|
2458
|
+
return getMatchedDiagnostics2(contract, resultData).length > 0;
|
|
2459
|
+
},
|
|
2460
|
+
evaluate(args) {
|
|
2461
|
+
const config = parseConfig4(args.contract.condition);
|
|
2462
|
+
if (!args.resultData) {
|
|
2463
|
+
return {
|
|
2464
|
+
result: "error",
|
|
2465
|
+
rationale: "tsc_checker requires TypeScript diagnostic resultData."
|
|
2466
|
+
};
|
|
2467
|
+
}
|
|
2468
|
+
const envelope = normalizeToolResultEnvelope(args.resultData);
|
|
2469
|
+
const exitCode = asNumber(envelope.exitCode);
|
|
2470
|
+
const matchedDiagnostics = getMatchedDiagnostics2(args.contract, args.resultData);
|
|
2471
|
+
if (matchedDiagnostics.length === 0 && exitCode !== 0 && exitCode !== null) {
|
|
2472
|
+
return {
|
|
2473
|
+
result: "inconclusive",
|
|
2474
|
+
rationale: "TypeScript reported errors, but none matched the configured file patterns."
|
|
2475
|
+
};
|
|
2476
|
+
}
|
|
2477
|
+
const conditionSatisfied = exitCode === 0 || exitCode !== null && matchedDiagnostics.length === 0;
|
|
2478
|
+
return {
|
|
2479
|
+
result: deriveDirectionalResult(
|
|
2480
|
+
args.contract.direction,
|
|
2481
|
+
conditionSatisfied
|
|
2482
|
+
),
|
|
2483
|
+
rationale: conditionSatisfied ? `TypeScript reported no matching diagnostics for ${config.filePatterns.join(", ")}.` : `TypeScript found ${matchedDiagnostics.length} matching diagnostic(s): ${matchedDiagnostics.map(
|
|
2484
|
+
(diagnostic) => diagnostic.filePath ? `${diagnostic.filePath}${diagnostic.code ? ` (TS${diagnostic.code})` : ""}` : diagnostic.message
|
|
2485
|
+
).join(", ")}.`,
|
|
2486
|
+
data: {
|
|
2487
|
+
exitCode,
|
|
2488
|
+
filePatterns: config.filePatterns,
|
|
2489
|
+
matchedDiagnostics
|
|
2490
|
+
}
|
|
2491
|
+
};
|
|
2492
|
+
}
|
|
2493
|
+
};
|
|
2494
|
+
|
|
2495
|
+
// src/evaluators/index.ts
|
|
2496
|
+
var ENGINEERING_EPISTEMIC_EVALUATORS = [
|
|
2497
|
+
testRunnerEvaluator,
|
|
2498
|
+
tscCheckerEvaluator,
|
|
2499
|
+
lintCheckerEvaluator,
|
|
2500
|
+
sentryCheckerEvaluator
|
|
2501
|
+
];
|
|
2502
|
+
var ENGINEERING_EVALUATOR_NAMES = new Set(
|
|
2503
|
+
ENGINEERING_EPISTEMIC_EVALUATORS.map((evaluator) => evaluator.name)
|
|
2504
|
+
);
|
|
2505
|
+
function getEngineeringEpistemicEvaluator(name) {
|
|
2506
|
+
return ENGINEERING_EPISTEMIC_EVALUATORS.find(
|
|
2507
|
+
(evaluator) => evaluator.name === name
|
|
2508
|
+
);
|
|
2509
|
+
}
|
|
2510
|
+
|
|
2511
|
+
// src/epistemicContracts.handlers.ts
|
|
2512
|
+
function resolveSchedulesForTrigger2(trigger) {
|
|
2513
|
+
if (trigger === "evidence_added") {
|
|
2514
|
+
return /* @__PURE__ */ new Set(["on_evidence", "event_driven"]);
|
|
2515
|
+
}
|
|
2516
|
+
if (trigger === "periodic") {
|
|
2517
|
+
return /* @__PURE__ */ new Set(["periodic"]);
|
|
2518
|
+
}
|
|
2519
|
+
if (trigger === "manual") {
|
|
2520
|
+
return /* @__PURE__ */ new Set(["on_demand", "event_driven"]);
|
|
2521
|
+
}
|
|
2522
|
+
return /* @__PURE__ */ new Set(["event_driven"]);
|
|
2523
|
+
}
|
|
2524
|
+
async function requireAuth(ctx) {
|
|
2525
|
+
const userId = await getCurrentUserId(ctx);
|
|
2526
|
+
if (!userId) {
|
|
2527
|
+
throw new Error("Authentication required.");
|
|
2528
|
+
}
|
|
2529
|
+
return userId;
|
|
2530
|
+
}
|
|
2531
|
+
async function resolveAuthenticatedUserId(ctx, explicitUserId) {
|
|
2532
|
+
if (explicitUserId) {
|
|
2533
|
+
return explicitUserId;
|
|
2534
|
+
}
|
|
2535
|
+
return requireAuth(ctx);
|
|
2536
|
+
}
|
|
2537
|
+
async function requireBeliefProjectAccess(ctx, belief, userId) {
|
|
2538
|
+
if (!belief.projectId) {
|
|
2539
|
+
throw new Error("Belief has no project scope.");
|
|
2540
|
+
}
|
|
2541
|
+
const hasAccess = await checkProjectAccess(
|
|
2542
|
+
ctx,
|
|
2543
|
+
belief.projectId,
|
|
2544
|
+
userId
|
|
2545
|
+
);
|
|
2546
|
+
if (!hasAccess) {
|
|
2547
|
+
throw new Error("Project access required.");
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
async function requireTopicReadAccess(ctx, beliefs, userId) {
|
|
2551
|
+
const projectIds = /* @__PURE__ */ new Set();
|
|
2552
|
+
for (const belief of beliefs) {
|
|
2553
|
+
if (!belief.projectId) {
|
|
2554
|
+
throw new Error("Belief has no project scope.");
|
|
2555
|
+
}
|
|
2556
|
+
projectIds.add(belief.projectId);
|
|
2557
|
+
}
|
|
2558
|
+
for (const projectId of projectIds) {
|
|
2559
|
+
const hasAccess = await checkProjectAccess(ctx, projectId, userId);
|
|
2560
|
+
if (!hasAccess) {
|
|
2561
|
+
throw new Error("Project access required.");
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
async function getContractByContractId(ctx, contractId) {
|
|
2566
|
+
return await ctx.db.query("epistemicContracts").withIndex("by_contractId", (q) => q.eq("contractId", contractId)).first();
|
|
2567
|
+
}
|
|
2568
|
+
async function getLatestEvaluation(ctx, contractId) {
|
|
2569
|
+
const evaluations = await ctx.db.query("contractEvaluations").withIndex("by_contract_time", (q) => q.eq("contractId", contractId)).order("desc").take(1);
|
|
2570
|
+
return evaluations[0] ?? null;
|
|
2571
|
+
}
|
|
2572
|
+
var evaluateEvidenceCount = query({
|
|
2573
|
+
args: {
|
|
2574
|
+
beliefNodeId: v.id("epistemicNodes")
|
|
2575
|
+
},
|
|
2576
|
+
returns: permissiveReturn,
|
|
2577
|
+
handler: async (ctx, args) => {
|
|
2578
|
+
const userId = await requireAuth(ctx);
|
|
2579
|
+
const belief = await ctx.db.get(args.beliefNodeId);
|
|
2580
|
+
if (!belief || belief.nodeType !== "belief") {
|
|
2581
|
+
throw new Error("Belief not found.");
|
|
2582
|
+
}
|
|
2583
|
+
await requireBeliefProjectAccess(ctx, belief, userId);
|
|
2584
|
+
return await computeEvidenceCountMetric(ctx, args.beliefNodeId);
|
|
2585
|
+
}
|
|
2586
|
+
});
|
|
2587
|
+
var evaluateContradictionStatus = query({
|
|
2588
|
+
args: {
|
|
2589
|
+
beliefNodeId: v.id("epistemicNodes")
|
|
2590
|
+
},
|
|
2591
|
+
returns: permissiveReturn,
|
|
2592
|
+
handler: async (ctx, args) => {
|
|
2593
|
+
const userId = await requireAuth(ctx);
|
|
2594
|
+
const belief = await ctx.db.get(args.beliefNodeId);
|
|
2595
|
+
if (!belief || belief.nodeType !== "belief") {
|
|
2596
|
+
throw new Error("Belief not found.");
|
|
2597
|
+
}
|
|
2598
|
+
await requireBeliefProjectAccess(ctx, belief, userId);
|
|
2599
|
+
return await computeContradictionCounts(ctx, args.beliefNodeId);
|
|
2600
|
+
}
|
|
2601
|
+
});
|
|
2602
|
+
var evaluateEdgeFreshness = query({
|
|
2603
|
+
args: {
|
|
2604
|
+
beliefNodeId: v.id("epistemicNodes")
|
|
2605
|
+
},
|
|
2606
|
+
returns: permissiveReturn,
|
|
2607
|
+
handler: async (ctx, args) => {
|
|
2608
|
+
const userId = await requireAuth(ctx);
|
|
2609
|
+
const belief = await ctx.db.get(args.beliefNodeId);
|
|
2610
|
+
if (!belief || belief.nodeType !== "belief") {
|
|
2611
|
+
throw new Error("Belief not found.");
|
|
2612
|
+
}
|
|
2613
|
+
await requireBeliefProjectAccess(ctx, belief, userId);
|
|
2614
|
+
return await computeEvidenceFreshness(ctx, args.beliefNodeId);
|
|
2615
|
+
}
|
|
2616
|
+
});
|
|
2617
|
+
var evaluateDependentBeliefCount = query({
|
|
2618
|
+
args: {
|
|
2619
|
+
beliefNodeId: v.id("epistemicNodes")
|
|
2620
|
+
},
|
|
2621
|
+
returns: permissiveReturn,
|
|
2622
|
+
handler: async (ctx, args) => {
|
|
2623
|
+
const userId = await requireAuth(ctx);
|
|
2624
|
+
const belief = await ctx.db.get(args.beliefNodeId);
|
|
2625
|
+
if (!belief || belief.nodeType !== "belief") {
|
|
2626
|
+
throw new Error("Belief not found.");
|
|
2627
|
+
}
|
|
2628
|
+
await requireBeliefProjectAccess(ctx, belief, userId);
|
|
2629
|
+
return await computeDependentBeliefCount(ctx, args.beliefNodeId);
|
|
2630
|
+
}
|
|
2631
|
+
});
|
|
2632
|
+
var createEpistemicContract = mutation({
|
|
2633
|
+
args: {
|
|
2634
|
+
beliefNodeId: v.id("epistemicNodes"),
|
|
2635
|
+
title: v.string(),
|
|
2636
|
+
description: v.optional(v.string()),
|
|
2637
|
+
conditionType: v.union(
|
|
2638
|
+
v.literal("assertion"),
|
|
2639
|
+
v.literal("temporal"),
|
|
2640
|
+
v.literal("evidential"),
|
|
2641
|
+
v.literal("threshold"),
|
|
2642
|
+
v.literal("composite")
|
|
2643
|
+
),
|
|
2644
|
+
direction: v.union(v.literal("confirms"), v.literal("falsifies")),
|
|
2645
|
+
condition: v.object({
|
|
2646
|
+
expression: v.string(),
|
|
2647
|
+
evaluator: v.string(),
|
|
2648
|
+
evaluatorConfig: v.optional(v.any())
|
|
2649
|
+
}),
|
|
2650
|
+
deadline: v.optional(v.number()),
|
|
2651
|
+
compositeOf: v.optional(v.array(v.string())),
|
|
2652
|
+
compositeOperator: v.optional(
|
|
2653
|
+
v.union(v.literal("all"), v.literal("any"), v.literal("majority"))
|
|
2654
|
+
),
|
|
2655
|
+
modulation: v.object({
|
|
2656
|
+
onConfirmed: v.object({
|
|
2657
|
+
delta: v.number(),
|
|
2658
|
+
ceiling: v.optional(v.number())
|
|
2659
|
+
}),
|
|
2660
|
+
onDisconfirmed: v.object({
|
|
2661
|
+
delta: v.number(),
|
|
2662
|
+
floor: v.optional(v.number())
|
|
2663
|
+
}),
|
|
2664
|
+
onExpired: v.optional(
|
|
2665
|
+
v.object({
|
|
2666
|
+
delta: v.number()
|
|
2667
|
+
})
|
|
2668
|
+
),
|
|
2669
|
+
onPartial: v.optional(
|
|
2670
|
+
v.object({
|
|
2671
|
+
threshold: v.number(),
|
|
2672
|
+
delta: v.number()
|
|
2673
|
+
})
|
|
2674
|
+
)
|
|
2675
|
+
}),
|
|
2676
|
+
evaluationSchedule: v.union(
|
|
2677
|
+
v.literal("on_demand"),
|
|
2678
|
+
v.literal("on_evidence"),
|
|
2679
|
+
v.literal("periodic"),
|
|
2680
|
+
v.literal("event_driven")
|
|
2681
|
+
),
|
|
2682
|
+
periodicIntervalMs: v.optional(v.number()),
|
|
2683
|
+
authenticatedUserId: v.optional(v.string())
|
|
2684
|
+
},
|
|
2685
|
+
returns: permissiveReturn,
|
|
2686
|
+
handler: async (ctx, args) => {
|
|
2687
|
+
const userId = await resolveAuthenticatedUserId(
|
|
2688
|
+
ctx,
|
|
2689
|
+
args.authenticatedUserId
|
|
2690
|
+
);
|
|
2691
|
+
const belief = await ctx.db.get(args.beliefNodeId);
|
|
2692
|
+
if (!belief || belief.nodeType !== "belief") {
|
|
2693
|
+
throw new Error("Belief not found.");
|
|
2694
|
+
}
|
|
2695
|
+
await requireBeliefProjectAccess(ctx, belief, userId);
|
|
2696
|
+
const now = Date.now();
|
|
2697
|
+
const contractId = generateGlobalId();
|
|
2698
|
+
const contractDoc = {
|
|
2699
|
+
beliefNodeId: args.beliefNodeId,
|
|
2700
|
+
contractId,
|
|
2701
|
+
title: args.title,
|
|
2702
|
+
description: args.description,
|
|
2703
|
+
conditionType: args.conditionType,
|
|
2704
|
+
direction: args.direction,
|
|
2705
|
+
condition: args.condition,
|
|
2706
|
+
deadline: args.deadline,
|
|
2707
|
+
compositeOf: args.compositeOf,
|
|
2708
|
+
compositeOperator: args.compositeOperator,
|
|
2709
|
+
modulation: args.modulation,
|
|
2710
|
+
evaluationSchedule: args.evaluationSchedule,
|
|
2711
|
+
periodicIntervalMs: args.periodicIntervalMs,
|
|
2712
|
+
status: "active",
|
|
2713
|
+
lineageSource: "declared",
|
|
2714
|
+
evaluationCount: 0,
|
|
2715
|
+
topicId: belief.topicId,
|
|
2716
|
+
createdAt: now,
|
|
2717
|
+
createdBy: userId,
|
|
2718
|
+
updatedAt: now
|
|
2719
|
+
};
|
|
2720
|
+
const insertedId = await ctx.db.insert(
|
|
2721
|
+
"epistemicContracts",
|
|
2722
|
+
contractDoc
|
|
2723
|
+
);
|
|
2724
|
+
return {
|
|
2725
|
+
contractId,
|
|
2726
|
+
id: insertedId,
|
|
2727
|
+
status: contractDoc.status
|
|
2728
|
+
};
|
|
2729
|
+
}
|
|
2730
|
+
});
|
|
2731
|
+
var evaluateContract = mutation({
|
|
2732
|
+
args: {
|
|
2733
|
+
contractId: v.string(),
|
|
2734
|
+
resultData: v.optional(v.any()),
|
|
2735
|
+
trigger: v.optional(v.string()),
|
|
2736
|
+
inputData: v.optional(v.any()),
|
|
2737
|
+
authenticatedUserId: v.optional(v.string())
|
|
2738
|
+
},
|
|
2739
|
+
returns: permissiveReturn,
|
|
2740
|
+
handler: async (ctx, args) => {
|
|
2741
|
+
const userId = await resolveAuthenticatedUserId(
|
|
2742
|
+
ctx,
|
|
2743
|
+
args.authenticatedUserId
|
|
2744
|
+
);
|
|
2745
|
+
const contract = await getContractByContractId(ctx, args.contractId);
|
|
2746
|
+
if (!contract) {
|
|
2747
|
+
throw new Error(`Contract not found: ${args.contractId}`);
|
|
2748
|
+
}
|
|
2749
|
+
if (contract.status === "archived") {
|
|
2750
|
+
throw new Error("Archived contracts cannot be evaluated.");
|
|
2751
|
+
}
|
|
2752
|
+
const belief = await ctx.db.get(contract.beliefNodeId);
|
|
2753
|
+
if (!belief || belief.nodeType !== "belief") {
|
|
2754
|
+
throw new Error("Belief not found for contract.");
|
|
2755
|
+
}
|
|
2756
|
+
await requireBeliefProjectAccess(ctx, belief, userId);
|
|
2757
|
+
const evaluation = await executeContractEvaluation({
|
|
2758
|
+
ctx,
|
|
2759
|
+
belief,
|
|
2760
|
+
contract,
|
|
2761
|
+
now: Date.now(),
|
|
2762
|
+
resultData: args.resultData,
|
|
2763
|
+
trigger: args.trigger ?? "manual",
|
|
2764
|
+
inputData: args.inputData,
|
|
2765
|
+
authenticatedUserId: userId
|
|
2766
|
+
});
|
|
2767
|
+
return {
|
|
2768
|
+
evaluationId: evaluation.evaluationId,
|
|
2769
|
+
result: evaluation.result,
|
|
2770
|
+
status: evaluation.status,
|
|
2771
|
+
confidenceBefore: evaluation.confidenceBefore,
|
|
2772
|
+
confidenceAfter: evaluation.confidenceAfter,
|
|
2773
|
+
rationale: evaluation.rationale
|
|
2774
|
+
};
|
|
2775
|
+
}
|
|
2776
|
+
});
|
|
2777
|
+
var evaluateEngineeringContracts = mutation({
|
|
2778
|
+
args: {
|
|
2779
|
+
beliefNodeId: v.id("epistemicNodes"),
|
|
2780
|
+
testOutput: v.optional(v.any()),
|
|
2781
|
+
tscOutput: v.optional(v.any()),
|
|
2782
|
+
lintOutput: v.optional(v.any()),
|
|
2783
|
+
sentryData: v.optional(v.any()),
|
|
2784
|
+
trigger: v.optional(v.string()),
|
|
2785
|
+
authenticatedUserId: v.optional(v.string())
|
|
2786
|
+
},
|
|
2787
|
+
returns: permissiveReturn,
|
|
2788
|
+
handler: async (ctx, args) => {
|
|
2789
|
+
const userId = await resolveAuthenticatedUserId(
|
|
2790
|
+
ctx,
|
|
2791
|
+
args.authenticatedUserId
|
|
2792
|
+
);
|
|
2793
|
+
const belief = await ctx.db.get(args.beliefNodeId);
|
|
2794
|
+
if (!belief || belief.nodeType !== "belief") {
|
|
2795
|
+
throw new Error("Belief not found.");
|
|
2796
|
+
}
|
|
2797
|
+
await requireBeliefProjectAccess(ctx, belief, userId);
|
|
2798
|
+
const trigger = args.trigger ?? "event_driven";
|
|
2799
|
+
const allowedSchedules = resolveSchedulesForTrigger2(
|
|
2800
|
+
trigger === "manual" ? "manual" : trigger === "evidence_added" ? "evidence_added" : trigger
|
|
2801
|
+
);
|
|
2802
|
+
const payloadByEvaluator = /* @__PURE__ */ new Map([
|
|
2803
|
+
["test_runner", args.testOutput],
|
|
2804
|
+
["tsc_checker", args.tscOutput],
|
|
2805
|
+
["lint_checker", args.lintOutput],
|
|
2806
|
+
["sentry_checker", args.sentryData]
|
|
2807
|
+
]);
|
|
2808
|
+
const contracts = (await loadContractsForBelief({
|
|
2809
|
+
ctx,
|
|
2810
|
+
beliefNodeId: args.beliefNodeId
|
|
2811
|
+
})).filter((contract) => {
|
|
2812
|
+
if (contract.status === "archived") {
|
|
2813
|
+
return false;
|
|
2814
|
+
}
|
|
2815
|
+
if (!allowedSchedules.has(contract.evaluationSchedule)) {
|
|
2816
|
+
return false;
|
|
2817
|
+
}
|
|
2818
|
+
return ENGINEERING_EVALUATOR_NAMES.has(contract.condition.evaluator);
|
|
2819
|
+
});
|
|
2820
|
+
let runningConfidence = typeof belief.confidence === "number" ? belief.confidence : 0.5;
|
|
2821
|
+
const evaluations = [];
|
|
2822
|
+
for (const contract of contracts) {
|
|
2823
|
+
const resultData = payloadByEvaluator.get(contract.condition.evaluator);
|
|
2824
|
+
if (resultData === void 0) {
|
|
2825
|
+
continue;
|
|
2826
|
+
}
|
|
2827
|
+
const engineeringEvaluator = getEngineeringEpistemicEvaluator(
|
|
2828
|
+
contract.condition.evaluator
|
|
2829
|
+
);
|
|
2830
|
+
if (!engineeringEvaluator?.matches({ contract, resultData })) {
|
|
2831
|
+
continue;
|
|
2832
|
+
}
|
|
2833
|
+
const evaluation = await executeContractEvaluation({
|
|
2834
|
+
ctx,
|
|
2835
|
+
belief,
|
|
2836
|
+
contract,
|
|
2837
|
+
now: Date.now(),
|
|
2838
|
+
trigger,
|
|
2839
|
+
authenticatedUserId: userId,
|
|
2840
|
+
currentConfidence: runningConfidence,
|
|
2841
|
+
resultData
|
|
2842
|
+
});
|
|
2843
|
+
runningConfidence = evaluation.currentConfidence;
|
|
2844
|
+
belief.confidence = runningConfidence;
|
|
2845
|
+
evaluations.push({
|
|
2846
|
+
contractId: contract.contractId,
|
|
2847
|
+
evaluator: contract.condition.evaluator,
|
|
2848
|
+
evaluationId: evaluation.evaluationId,
|
|
2849
|
+
result: evaluation.result,
|
|
2850
|
+
status: evaluation.status,
|
|
2851
|
+
confidenceBefore: evaluation.confidenceBefore,
|
|
2852
|
+
confidenceAfter: evaluation.confidenceAfter,
|
|
2853
|
+
rationale: evaluation.rationale
|
|
2854
|
+
});
|
|
2855
|
+
}
|
|
2856
|
+
return {
|
|
2857
|
+
beliefNodeId: args.beliefNodeId,
|
|
2858
|
+
trigger,
|
|
2859
|
+
availableContracts: contracts.length,
|
|
2860
|
+
evaluatedContracts: evaluations.length,
|
|
2861
|
+
evaluations
|
|
2862
|
+
};
|
|
2863
|
+
}
|
|
2864
|
+
});
|
|
2865
|
+
var evaluateContractsForTrigger = mutation({
|
|
2866
|
+
args: {
|
|
2867
|
+
beliefNodeId: v.id("epistemicNodes"),
|
|
2868
|
+
trigger: v.string(),
|
|
2869
|
+
inputData: v.optional(v.any()),
|
|
2870
|
+
maxBatchSize: v.optional(v.number()),
|
|
2871
|
+
authenticatedUserId: v.optional(v.string())
|
|
2872
|
+
},
|
|
2873
|
+
returns: permissiveReturn,
|
|
2874
|
+
handler: async (ctx, args) => {
|
|
2875
|
+
const userId = await resolveAuthenticatedUserId(
|
|
2876
|
+
ctx,
|
|
2877
|
+
args.authenticatedUserId
|
|
2878
|
+
);
|
|
2879
|
+
const belief = await ctx.db.get(args.beliefNodeId);
|
|
2880
|
+
if (!belief || belief.nodeType !== "belief") {
|
|
2881
|
+
throw new Error("Belief not found.");
|
|
2882
|
+
}
|
|
2883
|
+
await requireBeliefProjectAccess(ctx, belief, userId);
|
|
2884
|
+
return await evaluateContractsForTriggerBatch({
|
|
2885
|
+
ctx,
|
|
2886
|
+
belief,
|
|
2887
|
+
trigger: args.trigger,
|
|
2888
|
+
inputData: args.inputData,
|
|
2889
|
+
authenticatedUserId: userId,
|
|
2890
|
+
maxBatchSize: args.maxBatchSize
|
|
2891
|
+
});
|
|
2892
|
+
}
|
|
2893
|
+
});
|
|
2894
|
+
var processContractEvaluationOverflow = internalMutation({
|
|
2895
|
+
args: {
|
|
2896
|
+
beliefNodeId: v.id("epistemicNodes"),
|
|
2897
|
+
trigger: v.string(),
|
|
2898
|
+
contractIds: v.array(v.string()),
|
|
2899
|
+
inputData: v.optional(v.any()),
|
|
2900
|
+
authenticatedUserId: v.string(),
|
|
2901
|
+
maxBatchSize: v.optional(v.number())
|
|
2902
|
+
},
|
|
2903
|
+
returns: permissiveReturn,
|
|
2904
|
+
handler: async (ctx, args) => {
|
|
2905
|
+
const belief = await ctx.db.get(args.beliefNodeId);
|
|
2906
|
+
if (!belief || belief.nodeType !== "belief") {
|
|
2907
|
+
throw new Error("Belief not found.");
|
|
2908
|
+
}
|
|
2909
|
+
return await evaluateContractsForTriggerBatch({
|
|
2910
|
+
ctx,
|
|
2911
|
+
belief,
|
|
2912
|
+
trigger: args.trigger,
|
|
2913
|
+
inputData: args.inputData,
|
|
2914
|
+
authenticatedUserId: args.authenticatedUserId,
|
|
2915
|
+
contractIds: args.contractIds,
|
|
2916
|
+
maxBatchSize: args.maxBatchSize
|
|
2917
|
+
});
|
|
2918
|
+
}
|
|
2919
|
+
});
|
|
2920
|
+
var getContractStatus = query({
|
|
2921
|
+
args: {
|
|
2922
|
+
beliefNodeId: v.optional(v.id("epistemicNodes")),
|
|
2923
|
+
contractId: v.optional(v.string()),
|
|
2924
|
+
status: v.optional(
|
|
2925
|
+
v.union(
|
|
2926
|
+
v.literal("active"),
|
|
2927
|
+
v.literal("satisfied"),
|
|
2928
|
+
v.literal("violated"),
|
|
2929
|
+
v.literal("expired"),
|
|
2930
|
+
v.literal("suspended"),
|
|
2931
|
+
v.literal("archived")
|
|
2932
|
+
)
|
|
2933
|
+
),
|
|
2934
|
+
authenticatedUserId: v.optional(v.string())
|
|
2935
|
+
},
|
|
2936
|
+
returns: permissiveReturn,
|
|
2937
|
+
handler: async (ctx, args) => {
|
|
2938
|
+
const userId = await resolveAuthenticatedUserId(
|
|
2939
|
+
ctx,
|
|
2940
|
+
args.authenticatedUserId
|
|
2941
|
+
);
|
|
2942
|
+
let contracts = [];
|
|
2943
|
+
if (args.contractId) {
|
|
2944
|
+
const contract = await getContractByContractId(ctx, args.contractId);
|
|
2945
|
+
contracts = contract ? [contract] : [];
|
|
2946
|
+
} else if (args.beliefNodeId) {
|
|
2947
|
+
const belief = await ctx.db.get(args.beliefNodeId);
|
|
2948
|
+
if (!belief || belief.nodeType !== "belief") {
|
|
2949
|
+
throw new Error("Belief not found.");
|
|
2950
|
+
}
|
|
2951
|
+
await requireBeliefProjectAccess(ctx, belief, userId);
|
|
2952
|
+
contracts = await ctx.db.query("epistemicContracts").withIndex(
|
|
2953
|
+
"by_belief",
|
|
2954
|
+
(q) => q.eq("beliefNodeId", args.beliefNodeId)
|
|
2955
|
+
).collect();
|
|
2956
|
+
}
|
|
2957
|
+
if (contracts.length > 0) {
|
|
2958
|
+
const contractBeliefDocs = await Promise.all(
|
|
2959
|
+
contracts.map((contract) => ctx.db.get(contract.beliefNodeId))
|
|
2960
|
+
);
|
|
2961
|
+
const contractBeliefs = [];
|
|
2962
|
+
for (const belief of contractBeliefDocs) {
|
|
2963
|
+
if (!belief || belief.nodeType !== "belief") {
|
|
2964
|
+
throw new Error("Belief not found.");
|
|
2965
|
+
}
|
|
2966
|
+
contractBeliefs.push(belief);
|
|
2967
|
+
}
|
|
2968
|
+
await requireTopicReadAccess(ctx, contractBeliefs, userId);
|
|
2969
|
+
}
|
|
2970
|
+
if (args.status) {
|
|
2971
|
+
contracts = contracts.filter((contract) => contract.status === args.status);
|
|
2972
|
+
}
|
|
2973
|
+
const rows = await Promise.all(
|
|
2974
|
+
contracts.map(async (contract) => ({
|
|
2975
|
+
...contract,
|
|
2976
|
+
latestEvaluation: await getLatestEvaluation(ctx, contract.contractId)
|
|
2977
|
+
}))
|
|
2978
|
+
);
|
|
2979
|
+
return rows.sort((a, b) => b.createdAt - a.createdAt);
|
|
2980
|
+
}
|
|
2981
|
+
});
|
|
2982
|
+
var getContractCoverage = query({
|
|
2983
|
+
args: {
|
|
2984
|
+
topicId: v.string(),
|
|
2985
|
+
recentEvaluationLimit: v.optional(v.number()),
|
|
2986
|
+
authenticatedUserId: v.optional(v.string())
|
|
2987
|
+
},
|
|
2988
|
+
returns: permissiveReturn,
|
|
2989
|
+
handler: async (ctx, args) => {
|
|
2990
|
+
const contracts = await ctx.db.query("epistemicContracts").withIndex("by_topic", (q) => q.eq("topicId", args.topicId)).collect();
|
|
2991
|
+
const userId = await resolveAuthenticatedUserId(
|
|
2992
|
+
ctx,
|
|
2993
|
+
args.authenticatedUserId
|
|
2994
|
+
);
|
|
2995
|
+
const beliefs = await ctx.db.query("epistemicNodes").withIndex(
|
|
2996
|
+
"by_topic_type",
|
|
2997
|
+
(q) => q.eq("topicId", args.topicId).eq("nodeType", "belief")
|
|
2998
|
+
).collect();
|
|
2999
|
+
await requireTopicReadAccess(ctx, beliefs, userId);
|
|
3000
|
+
const recentEvaluationLimit = Math.max(
|
|
3001
|
+
0,
|
|
3002
|
+
Math.min(args.recentEvaluationLimit ?? 5, 25)
|
|
3003
|
+
);
|
|
3004
|
+
const recentEvaluations = recentEvaluationLimit === 0 ? [] : await ctx.db.query("contractEvaluations").withIndex("by_topic_time", (q) => q.eq("topicId", args.topicId)).order("desc").take(recentEvaluationLimit) ?? [];
|
|
3005
|
+
const contractBeliefIds = new Set(
|
|
3006
|
+
contracts.filter((contract) => contract.status !== "archived").map((contract) => String(contract.beliefNodeId))
|
|
3007
|
+
);
|
|
3008
|
+
return {
|
|
3009
|
+
active: contracts.filter((contract) => contract.status === "active").length,
|
|
3010
|
+
violated: contracts.filter((contract) => contract.status === "violated").length,
|
|
3011
|
+
expired: contracts.filter((contract) => contract.status === "expired").length,
|
|
3012
|
+
unboundHighConfidence: beliefs.filter(
|
|
3013
|
+
(belief) => typeof belief.confidence === "number" && belief.confidence >= 0.8 && !contractBeliefIds.has(String(belief._id))
|
|
3014
|
+
).map((belief) => ({
|
|
3015
|
+
nodeId: belief._id,
|
|
3016
|
+
canonicalText: belief.canonicalText,
|
|
3017
|
+
confidence: belief.confidence
|
|
3018
|
+
})),
|
|
3019
|
+
recentEvaluations
|
|
3020
|
+
};
|
|
3021
|
+
}
|
|
3022
|
+
});
|
|
3023
|
+
function inheritContractsForFork(contracts, args) {
|
|
3024
|
+
return contracts.filter((contract) => contract.status !== "archived").map((contract) => createInheritedContractRecord(contract, args));
|
|
3025
|
+
}
|
|
3026
|
+
|
|
3027
|
+
export { createEpistemicContract, evaluateContract, evaluateContractsForTrigger, evaluateContradictionStatus, evaluateDependentBeliefCount, evaluateEdgeFreshness, evaluateEngineeringContracts, evaluateEvidenceCount, getContractCoverage, getContractStatus, inheritContractsForFork, processContractEvaluationOverflow };
|
|
3028
|
+
//# sourceMappingURL=epistemicContracts.handlers.js.map
|
|
3029
|
+
//# sourceMappingURL=epistemicContracts.handlers.js.map
|