@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,1365 @@
|
|
|
1
|
+
import { v } from 'convex/values';
|
|
2
|
+
import { requireProjectAccess } from '@lucern/access-control/access';
|
|
3
|
+
import { permissiveReturn } from '@lucern/contracts/schema-helpers/validators';
|
|
4
|
+
import { componentsGeneric, anyApi, mutationGeneric } from 'convex/server';
|
|
5
|
+
|
|
6
|
+
// src/epistemicEdges.mutations.ts
|
|
7
|
+
v.union(
|
|
8
|
+
v.literal("L4"),
|
|
9
|
+
v.literal("L3"),
|
|
10
|
+
v.literal("L2"),
|
|
11
|
+
v.literal("L1"),
|
|
12
|
+
v.literal("ontological"),
|
|
13
|
+
v.literal("organizational")
|
|
14
|
+
);
|
|
15
|
+
var subjectiveOpinionValidator = v.object({
|
|
16
|
+
b: v.number(),
|
|
17
|
+
d: v.number(),
|
|
18
|
+
u: v.number(),
|
|
19
|
+
a: v.number()
|
|
20
|
+
});
|
|
21
|
+
var edgeTypeValidator = v.union(
|
|
22
|
+
// --- L4 Decision Edges (Phase 2A) ---
|
|
23
|
+
v.literal("based_on_belief"),
|
|
24
|
+
v.literal("based_on_question"),
|
|
25
|
+
v.literal("blocked_by_contradiction"),
|
|
26
|
+
v.literal("informed_by_theme"),
|
|
27
|
+
// --- Evidence Flow (L2 → L3, L2 → L1) ---
|
|
28
|
+
v.literal("derived_from"),
|
|
29
|
+
v.literal("responds_to"),
|
|
30
|
+
v.literal("informs"),
|
|
31
|
+
v.literal("tests"),
|
|
32
|
+
v.literal("explores"),
|
|
33
|
+
v.literal("qualifies"),
|
|
34
|
+
// --- Synthesis (L2 → L2, L2 → L1) ---
|
|
35
|
+
// "based_on" removed — use "derived_from" instead
|
|
36
|
+
// --- Theme Relationships (L3 → L3) ---
|
|
37
|
+
v.literal("relates_to_thesis"),
|
|
38
|
+
v.literal("belongs_to"),
|
|
39
|
+
v.literal("plays_theme"),
|
|
40
|
+
v.literal("scoped_by"),
|
|
41
|
+
// --- Deal/Company ---
|
|
42
|
+
v.literal("evaluates"),
|
|
43
|
+
// --- People (ontological → ontological, ontological → L3) ---
|
|
44
|
+
v.literal("perspective_on"),
|
|
45
|
+
v.literal("works_at"),
|
|
46
|
+
v.literal("mentioned_in"),
|
|
47
|
+
v.literal("founded_by"),
|
|
48
|
+
// --- Value Chain (ontological → ontological) ---
|
|
49
|
+
v.literal("participates_in"),
|
|
50
|
+
v.literal("performs"),
|
|
51
|
+
v.literal("function_in"),
|
|
52
|
+
v.literal("impacts"),
|
|
53
|
+
// --- Investment (ontological → ontological) ---
|
|
54
|
+
v.literal("invested_in"),
|
|
55
|
+
v.literal("raised_from"),
|
|
56
|
+
// --- Lifecycle (same layer only) ---
|
|
57
|
+
v.literal("supersedes"),
|
|
58
|
+
v.literal("same_as"),
|
|
59
|
+
// --- Same-Type Relationships: Belief ↔ Belief (L3 → L3) ---
|
|
60
|
+
v.literal("depends_on"),
|
|
61
|
+
v.literal("supports"),
|
|
62
|
+
v.literal("contains"),
|
|
63
|
+
// --- Belief Cluster Mapping (L3 → L3) ---
|
|
64
|
+
v.literal("counterfactual_of"),
|
|
65
|
+
v.literal("cascade_to"),
|
|
66
|
+
v.literal("cascade_from"),
|
|
67
|
+
v.literal("mutually_exclusive"),
|
|
68
|
+
v.literal("correlates_with"),
|
|
69
|
+
v.literal("amplifies"),
|
|
70
|
+
v.literal("precondition_for"),
|
|
71
|
+
v.literal("in_tension_with"),
|
|
72
|
+
// --- Belief ↔ Belief: Epistemic Impact (Confidence Propagation) ---
|
|
73
|
+
v.literal("falsified_by"),
|
|
74
|
+
v.literal("exclusive_with"),
|
|
75
|
+
v.literal("contradicts"),
|
|
76
|
+
v.literal("collapses_if"),
|
|
77
|
+
v.literal("strengthened_by"),
|
|
78
|
+
v.literal("weakened_by"),
|
|
79
|
+
v.literal("alternative_to"),
|
|
80
|
+
v.literal("subsumes"),
|
|
81
|
+
v.literal("validated_by"),
|
|
82
|
+
v.literal("required_for"),
|
|
83
|
+
v.literal("blocks"),
|
|
84
|
+
// --- Same-Type Relationships: Question ↔ Question (L3 → L3) ---
|
|
85
|
+
v.literal("prerequisite_for"),
|
|
86
|
+
v.literal("parallel_to"),
|
|
87
|
+
// --- Same-Type Relationships: Evidence ↔ Evidence (L2 → L2) ---
|
|
88
|
+
v.literal("corroborates"),
|
|
89
|
+
v.literal("extends"),
|
|
90
|
+
v.literal("same_source_as"),
|
|
91
|
+
v.literal("same_theme_as"),
|
|
92
|
+
// --- NEW: Deep Epistemic Analysis Edges (Phase: Schema Upgrade) ---
|
|
93
|
+
v.literal("assumes"),
|
|
94
|
+
v.literal("would_predict"),
|
|
95
|
+
v.literal("analogous_to"),
|
|
96
|
+
v.literal("independent_of"),
|
|
97
|
+
// --- Entity↔Belief Bridge (OE-B) ---
|
|
98
|
+
v.literal("competes_with")
|
|
99
|
+
);
|
|
100
|
+
function buildEdgeStatusSuccessResult() {
|
|
101
|
+
return { success: true };
|
|
102
|
+
}
|
|
103
|
+
function buildEdgeNotFoundResult() {
|
|
104
|
+
const result = {};
|
|
105
|
+
result.success = false;
|
|
106
|
+
result.error = "Edge not found";
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// src/graphTypes.ts
|
|
111
|
+
var EDGE_TYPE_TO_REL = {
|
|
112
|
+
// === THE SIX CANONICAL EPISTEMIC EDGE TYPES ===
|
|
113
|
+
supports: "SUPPORTS",
|
|
114
|
+
// L3↔L3: belief bears on belief (weight -1 to +1)
|
|
115
|
+
informs: "INFORMS",
|
|
116
|
+
// L2→L3: evidence bears on belief
|
|
117
|
+
depends_on: "DEPENDS_ON",
|
|
118
|
+
// L3→L3, Q→Q: structural gate
|
|
119
|
+
derived_from: "DERIVED_FROM",
|
|
120
|
+
// Any→Any: provenance chain (fork, synthesis, extraction, answer)
|
|
121
|
+
contains: "CONTAINS",
|
|
122
|
+
// Any→Any: hierarchy, scoping, membership
|
|
123
|
+
tests: "TESTS",
|
|
124
|
+
// Q→L3: question interrogates belief
|
|
125
|
+
// === L4 DECISION EDGES (derived_from with derivationType=decision) ===
|
|
126
|
+
// Kept as separate Neo4j relationship types for backward compat with L4 queries.
|
|
127
|
+
// New code should use derived_from + metadata.
|
|
128
|
+
based_on_belief: "BASED_ON_BELIEF",
|
|
129
|
+
based_on_question: "BASED_ON_QUESTION",
|
|
130
|
+
blocked_by_contradiction: "BLOCKED_BY_CONTRADICTION",
|
|
131
|
+
informed_by_theme: "INFORMED_BY_THEME",
|
|
132
|
+
// === ONTOLOGICAL EDGES (tenant-extensible, managed by ontology system) ===
|
|
133
|
+
works_at: "WORKS_AT",
|
|
134
|
+
invested_in: "INVESTED_IN",
|
|
135
|
+
competes_with: "COMPETES_WITH",
|
|
136
|
+
participates_in: "PARTICIPATES_IN",
|
|
137
|
+
founded_by: "FOUNDED_BY",
|
|
138
|
+
evaluates: "EVALUATES",
|
|
139
|
+
performs: "PERFORMS",
|
|
140
|
+
function_in: "FUNCTION_IN",
|
|
141
|
+
impacts: "IMPACTS",
|
|
142
|
+
raised_from: "RAISED_FROM",
|
|
143
|
+
mentioned_in: "MENTIONED_IN",
|
|
144
|
+
perspective_on: "PERSPECTIVE_ON",
|
|
145
|
+
about_entity: "ABOUT_ENTITY",
|
|
146
|
+
entity_referenced_in: "ENTITY_REFERENCED_IN"
|
|
147
|
+
};
|
|
148
|
+
function getNodeLayer(nodeType) {
|
|
149
|
+
const L4_TYPES = ["decision"];
|
|
150
|
+
const L3_TYPES = ["belief", "question", "theme", "deal"];
|
|
151
|
+
const L2_TYPES = ["claim", "evidence", "synthesis", "answer"];
|
|
152
|
+
const L1_TYPES = ["atomic_fact", "excerpt", "source"];
|
|
153
|
+
const ONTOLOGICAL_TYPES = [
|
|
154
|
+
"company",
|
|
155
|
+
"person",
|
|
156
|
+
"investor",
|
|
157
|
+
"function",
|
|
158
|
+
"value_chain"
|
|
159
|
+
];
|
|
160
|
+
const ORGANIZATIONAL_TYPES = ["topic"];
|
|
161
|
+
if (L4_TYPES.includes(nodeType)) {
|
|
162
|
+
return "L4";
|
|
163
|
+
}
|
|
164
|
+
if (L3_TYPES.includes(nodeType)) {
|
|
165
|
+
return "L3";
|
|
166
|
+
}
|
|
167
|
+
if (L2_TYPES.includes(nodeType)) {
|
|
168
|
+
return "L2";
|
|
169
|
+
}
|
|
170
|
+
if (L1_TYPES.includes(nodeType)) {
|
|
171
|
+
return "L1";
|
|
172
|
+
}
|
|
173
|
+
if (ONTOLOGICAL_TYPES.includes(nodeType)) {
|
|
174
|
+
return "ontological";
|
|
175
|
+
}
|
|
176
|
+
if (ORGANIZATIONAL_TYPES.includes(nodeType)) {
|
|
177
|
+
return "organizational";
|
|
178
|
+
}
|
|
179
|
+
console.warn(`[GraphTypes] Unknown nodeType "${nodeType}", defaulting to L2`);
|
|
180
|
+
return "L2";
|
|
181
|
+
}
|
|
182
|
+
var CANONICAL_EPISTEMIC_TYPES = /* @__PURE__ */ new Set([
|
|
183
|
+
"supports",
|
|
184
|
+
"informs",
|
|
185
|
+
"depends_on",
|
|
186
|
+
"derived_from",
|
|
187
|
+
"contains",
|
|
188
|
+
"tests"
|
|
189
|
+
]);
|
|
190
|
+
function isDeprecatedEdgeType(edgeType) {
|
|
191
|
+
if (CANONICAL_EPISTEMIC_TYPES.has(edgeType)) return false;
|
|
192
|
+
if (edgeType in EDGE_TYPE_TO_REL) return false;
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
var api = anyApi;
|
|
196
|
+
componentsGeneric();
|
|
197
|
+
var internal = anyApi;
|
|
198
|
+
var mutation = mutationGeneric;
|
|
199
|
+
|
|
200
|
+
// src/debug.ts
|
|
201
|
+
function isGraphPrimitiveDebugEnabled() {
|
|
202
|
+
const env = globalThis.process?.env;
|
|
203
|
+
return env?.LUCERN_COMPAT_FALLBACK_DEBUG === "1" || env?.LUCERN_GRAPH_DEBUG === "1";
|
|
204
|
+
}
|
|
205
|
+
function debugGraphPrimitiveFallback(message, context) {
|
|
206
|
+
if (!isGraphPrimitiveDebugEnabled()) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
console.debug(message, context ?? {});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// src/topicProjectOverlay.ts
|
|
213
|
+
var LEGACY_SCOPE_FIELD = "graphScopeProjectId";
|
|
214
|
+
function readNonEmptyString(value) {
|
|
215
|
+
if (typeof value !== "string") {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
const normalized = value.trim();
|
|
219
|
+
return normalized.length > 0 ? normalized : void 0;
|
|
220
|
+
}
|
|
221
|
+
function readStringArray(value) {
|
|
222
|
+
if (!Array.isArray(value)) {
|
|
223
|
+
return [];
|
|
224
|
+
}
|
|
225
|
+
return value.map((entry) => readNonEmptyString(entry)).filter((entry) => Boolean(entry));
|
|
226
|
+
}
|
|
227
|
+
function readMetadata(topic) {
|
|
228
|
+
return topic.metadata && typeof topic.metadata === "object" ? topic.metadata : {};
|
|
229
|
+
}
|
|
230
|
+
function readLegacyProjectId(value) {
|
|
231
|
+
if (!value) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
return readNonEmptyString(value[LEGACY_SCOPE_FIELD]);
|
|
235
|
+
}
|
|
236
|
+
function coerceVisibility(value) {
|
|
237
|
+
return value === "private" || value === "team" || value === "firm" || value === "external" || value === "public" ? value : void 0;
|
|
238
|
+
}
|
|
239
|
+
function coerceStatus(value) {
|
|
240
|
+
return value === "active" || value === "archived" || value === "watching" ? value : void 0;
|
|
241
|
+
}
|
|
242
|
+
function mapProjectType(topic, metadata) {
|
|
243
|
+
const explicit = readNonEmptyString(metadata.projectType);
|
|
244
|
+
if (explicit) {
|
|
245
|
+
return explicit;
|
|
246
|
+
}
|
|
247
|
+
if (topic.type === "theme") {
|
|
248
|
+
return "thematic";
|
|
249
|
+
}
|
|
250
|
+
return readNonEmptyString(topic.type) || "general";
|
|
251
|
+
}
|
|
252
|
+
function isProjectLikeTopic(topic) {
|
|
253
|
+
const metadata = readMetadata(topic);
|
|
254
|
+
return topic.type === "theme" || topic.type === "thematic" || topic.type === "deal" || topic.type === "monitoring" || readLegacyProjectId(topic) !== void 0 || readNonEmptyString(metadata.projectType) !== void 0;
|
|
255
|
+
}
|
|
256
|
+
function isMissingLucernChildComponentError(error) {
|
|
257
|
+
const message = getErrorMessage(error);
|
|
258
|
+
return message.includes(
|
|
259
|
+
'Child component ComponentName(Identifier("lucern")) not found'
|
|
260
|
+
) || message.includes("Child component") && message.includes("lucern") && message.includes("not found");
|
|
261
|
+
}
|
|
262
|
+
function getErrorMessage(error) {
|
|
263
|
+
if (error instanceof Error) {
|
|
264
|
+
return error.message;
|
|
265
|
+
}
|
|
266
|
+
if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") {
|
|
267
|
+
return error.message;
|
|
268
|
+
}
|
|
269
|
+
return "unknown error";
|
|
270
|
+
}
|
|
271
|
+
async function resolveTopicDoc(ctx, scopeId) {
|
|
272
|
+
if (ctx?.db && typeof ctx.db.get === "function") {
|
|
273
|
+
try {
|
|
274
|
+
const directTopic = await ctx.db.get(
|
|
275
|
+
scopeId
|
|
276
|
+
);
|
|
277
|
+
if (directTopic) {
|
|
278
|
+
return directTopic;
|
|
279
|
+
}
|
|
280
|
+
} catch (error) {
|
|
281
|
+
debugGraphPrimitiveFallback(
|
|
282
|
+
"[topicProjectOverlay] Failed to resolve topic by direct ID",
|
|
283
|
+
{
|
|
284
|
+
error,
|
|
285
|
+
scopeId
|
|
286
|
+
}
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
if (typeof ctx.runQuery !== "function") {
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
try {
|
|
294
|
+
const topic = await ctx.runQuery(api.topics.get, {
|
|
295
|
+
id: String(scopeId)
|
|
296
|
+
});
|
|
297
|
+
if (topic?.name !== void 0 && topic?.type !== void 0) {
|
|
298
|
+
return topic;
|
|
299
|
+
}
|
|
300
|
+
} catch (error) {
|
|
301
|
+
debugGraphPrimitiveFallback(
|
|
302
|
+
"[topicProjectOverlay] Failed to resolve topic by ID query",
|
|
303
|
+
{
|
|
304
|
+
error,
|
|
305
|
+
scopeId
|
|
306
|
+
}
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
try {
|
|
310
|
+
const topic = await ctx.runQuery(api.topics.getByLegacyScopeId, {
|
|
311
|
+
projectId: String(scopeId)
|
|
312
|
+
});
|
|
313
|
+
if (topic?.name !== void 0 && topic?.type !== void 0) {
|
|
314
|
+
return topic;
|
|
315
|
+
}
|
|
316
|
+
} catch (error) {
|
|
317
|
+
debugGraphPrimitiveFallback(
|
|
318
|
+
"[topicProjectOverlay] Failed to resolve topic by legacy scope ID",
|
|
319
|
+
{ error, scopeId }
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
function materializeTopicProjectOverlay(topic, idMode = "legacy") {
|
|
325
|
+
const metadata = readMetadata(topic);
|
|
326
|
+
const topicId = String(topic._id);
|
|
327
|
+
const legacyProjectId = readLegacyProjectId(topic) || readLegacyProjectId(metadata) || readNonEmptyString(metadata.legacyProjectId);
|
|
328
|
+
const storageProjectId = legacyProjectId || topicId;
|
|
329
|
+
const outwardId = idMode === "topic" ? topicId : storageProjectId;
|
|
330
|
+
const visibility = coerceVisibility(topic.visibility) || coerceVisibility(metadata.visibility) || "private";
|
|
331
|
+
const status = coerceStatus(topic.status) || coerceStatus(metadata.status) || "active";
|
|
332
|
+
const createdAt = typeof topic.createdAt === "number" ? topic.createdAt : typeof topic._creationTime === "number" ? topic._creationTime : 0;
|
|
333
|
+
const updatedAt = typeof topic.updatedAt === "number" ? topic.updatedAt : typeof metadata.updatedAt === "number" ? metadata.updatedAt : createdAt;
|
|
334
|
+
return {
|
|
335
|
+
...metadata,
|
|
336
|
+
_id: outwardId,
|
|
337
|
+
projectId: outwardId,
|
|
338
|
+
topicId,
|
|
339
|
+
storageProjectId,
|
|
340
|
+
legacyProjectId,
|
|
341
|
+
name: readNonEmptyString(topic.name) || "Untitled Theme",
|
|
342
|
+
type: mapProjectType(topic, metadata),
|
|
343
|
+
description: readNonEmptyString(topic.description),
|
|
344
|
+
ownerId: readNonEmptyString(metadata.ownerId) || readNonEmptyString(topic.createdBy) || "system",
|
|
345
|
+
sharedWith: readStringArray(metadata.sharedWith),
|
|
346
|
+
visibility,
|
|
347
|
+
tenantId: readNonEmptyString(topic.tenantId) || readNonEmptyString(metadata.tenantId),
|
|
348
|
+
workspaceId: readNonEmptyString(topic.workspaceId) || readNonEmptyString(metadata.workspaceId),
|
|
349
|
+
status,
|
|
350
|
+
tags: readStringArray(metadata.tags),
|
|
351
|
+
chatCount: typeof metadata.chatCount === "number" ? metadata.chatCount : 0,
|
|
352
|
+
artifactCount: typeof metadata.artifactCount === "number" ? metadata.artifactCount : 0,
|
|
353
|
+
lastActivityAt: typeof metadata.lastActivityAt === "number" ? metadata.lastActivityAt : updatedAt,
|
|
354
|
+
_creationTime: typeof topic._creationTime === "number" ? topic._creationTime : createdAt,
|
|
355
|
+
createdAt,
|
|
356
|
+
updatedAt
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
async function resolveTopicProjectOverlay(ctx, scopeId, options = {}) {
|
|
360
|
+
const topic = await resolveTopicDoc(ctx, scopeId);
|
|
361
|
+
if (!topic) {
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
if (options.projectLikeOnly !== false && !isProjectLikeTopic(topic)) {
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
return materializeTopicProjectOverlay(topic, options.idMode);
|
|
368
|
+
}
|
|
369
|
+
async function listTopicProjectOverlays(ctx, options = {}) {
|
|
370
|
+
let allTopics = [];
|
|
371
|
+
if (ctx?.db?.query && typeof ctx.db.query === "function") {
|
|
372
|
+
try {
|
|
373
|
+
allTopics = await ctx.db.query("topics").collect();
|
|
374
|
+
} catch (error) {
|
|
375
|
+
debugGraphPrimitiveFallback(
|
|
376
|
+
"[topicProjectOverlay] Failed to read topics table; falling back to API",
|
|
377
|
+
{ error }
|
|
378
|
+
);
|
|
379
|
+
allTopics = [];
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
if (allTopics.length === 0 && typeof ctx.runQuery === "function") {
|
|
383
|
+
allTopics = (await ctx.runQuery(api.topics.list, {}) ?? []) || [];
|
|
384
|
+
}
|
|
385
|
+
return allTopics.filter(
|
|
386
|
+
(topic) => options.projectLikeOnly === false || isProjectLikeTopic(topic)
|
|
387
|
+
).map((topic) => materializeTopicProjectOverlay(topic, options.idMode));
|
|
388
|
+
}
|
|
389
|
+
async function patchTopicProjectOverlay(ctx, scopeId, value) {
|
|
390
|
+
const topic = await resolveTopicDoc(ctx, scopeId);
|
|
391
|
+
if (!topic) {
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
const nextMetadata = { ...readMetadata(topic) };
|
|
395
|
+
const patch = {};
|
|
396
|
+
const topicUpdateArgs = {
|
|
397
|
+
id: String(topic._id)
|
|
398
|
+
};
|
|
399
|
+
for (const [key, rawValue] of Object.entries(value)) {
|
|
400
|
+
switch (key) {
|
|
401
|
+
case "_id":
|
|
402
|
+
case "projectId":
|
|
403
|
+
case "topicId":
|
|
404
|
+
case "legacyProjectId":
|
|
405
|
+
case "storageProjectId":
|
|
406
|
+
break;
|
|
407
|
+
case "name":
|
|
408
|
+
case "description":
|
|
409
|
+
patch[key] = rawValue;
|
|
410
|
+
topicUpdateArgs[key] = rawValue;
|
|
411
|
+
break;
|
|
412
|
+
case "tenantId":
|
|
413
|
+
case "workspaceId":
|
|
414
|
+
case "ownerId":
|
|
415
|
+
throw new Error(
|
|
416
|
+
`patchTopicProjectOverlay cannot mutate ${key} via component-owned topics`
|
|
417
|
+
);
|
|
418
|
+
case "status": {
|
|
419
|
+
const status = coerceStatus(rawValue);
|
|
420
|
+
if (status) {
|
|
421
|
+
patch.status = status;
|
|
422
|
+
topicUpdateArgs.status = status;
|
|
423
|
+
}
|
|
424
|
+
break;
|
|
425
|
+
}
|
|
426
|
+
case "visibility": {
|
|
427
|
+
const visibility = coerceVisibility(rawValue);
|
|
428
|
+
if (visibility) {
|
|
429
|
+
patch.visibility = visibility;
|
|
430
|
+
topicUpdateArgs.visibility = visibility;
|
|
431
|
+
}
|
|
432
|
+
break;
|
|
433
|
+
}
|
|
434
|
+
case "type": {
|
|
435
|
+
const projectType = readNonEmptyString(rawValue);
|
|
436
|
+
if (projectType) {
|
|
437
|
+
nextMetadata.projectType = projectType;
|
|
438
|
+
} else {
|
|
439
|
+
delete nextMetadata.projectType;
|
|
440
|
+
}
|
|
441
|
+
break;
|
|
442
|
+
}
|
|
443
|
+
case "updatedAt":
|
|
444
|
+
case "createdAt":
|
|
445
|
+
break;
|
|
446
|
+
default:
|
|
447
|
+
if (rawValue === void 0) {
|
|
448
|
+
delete nextMetadata[key];
|
|
449
|
+
} else {
|
|
450
|
+
nextMetadata[key] = rawValue;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
patch.updatedAt = Date.now();
|
|
455
|
+
patch.metadata = nextMetadata;
|
|
456
|
+
topicUpdateArgs.metadata = nextMetadata;
|
|
457
|
+
if (typeof ctx.runMutation === "function") {
|
|
458
|
+
try {
|
|
459
|
+
await ctx.runMutation(api.topics.update, topicUpdateArgs);
|
|
460
|
+
} catch (error) {
|
|
461
|
+
if (!isMissingLucernChildComponentError(error) || !ctx?.db || typeof ctx.db.patch !== "function") {
|
|
462
|
+
throw error;
|
|
463
|
+
}
|
|
464
|
+
await ctx.db.patch(String(topic._id), patch);
|
|
465
|
+
}
|
|
466
|
+
} else if (ctx?.db && typeof ctx.db.patch === "function") {
|
|
467
|
+
await ctx.db.patch(String(topic._id), patch);
|
|
468
|
+
} else {
|
|
469
|
+
throw new Error(
|
|
470
|
+
"Cannot patch topic without component adapter (ctx.runMutation unavailable)"
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
return materializeTopicProjectOverlay({
|
|
474
|
+
...topic,
|
|
475
|
+
...patch,
|
|
476
|
+
metadata: nextMetadata
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// src/resolvers.ts
|
|
481
|
+
function isMissingLucernChildComponentError2(error) {
|
|
482
|
+
const message = getErrorMessage2(error);
|
|
483
|
+
return message.includes(
|
|
484
|
+
'Child component ComponentName(Identifier("lucern")) not found'
|
|
485
|
+
) || message.includes("Child component") && message.includes("lucern") && message.includes("not found");
|
|
486
|
+
}
|
|
487
|
+
function getErrorMessage2(error) {
|
|
488
|
+
if (error instanceof Error) {
|
|
489
|
+
return error.message;
|
|
490
|
+
}
|
|
491
|
+
if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") {
|
|
492
|
+
return error.message;
|
|
493
|
+
}
|
|
494
|
+
return "unknown error";
|
|
495
|
+
}
|
|
496
|
+
function isAdvisoryTopicPatch(value) {
|
|
497
|
+
const advisoryKeys = /* @__PURE__ */ new Set(["lastActivityAt", "updatedAt"]);
|
|
498
|
+
const keys = Object.keys(value);
|
|
499
|
+
return keys.length > 0 && keys.every((key) => advisoryKeys.has(key));
|
|
500
|
+
}
|
|
501
|
+
async function patchProjectWithTolerance(ctx, projectId, value) {
|
|
502
|
+
try {
|
|
503
|
+
await patchTopicProjectOverlay(ctx, projectId, value);
|
|
504
|
+
} catch (error) {
|
|
505
|
+
if (!isAdvisoryTopicPatch(value) || !isMissingLucernChildComponentError2(error)) {
|
|
506
|
+
throw error;
|
|
507
|
+
}
|
|
508
|
+
console.warn(
|
|
509
|
+
"[lucern graph-primitives] Non-fatal advisory topic patch failure",
|
|
510
|
+
{
|
|
511
|
+
projectId,
|
|
512
|
+
keys: Object.keys(value),
|
|
513
|
+
error: getErrorMessage2(error)
|
|
514
|
+
}
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
function defaultResolvers() {
|
|
519
|
+
return {
|
|
520
|
+
getProject: (ctx, projectId) => resolveTopicProjectOverlay(ctx, projectId, {
|
|
521
|
+
idMode: "legacy",
|
|
522
|
+
projectLikeOnly: false
|
|
523
|
+
}),
|
|
524
|
+
patchProject: (ctx, projectId, value) => patchProjectWithTolerance(ctx, projectId, value),
|
|
525
|
+
listTopics: (ctx) => listTopicProjectOverlays(ctx, {
|
|
526
|
+
idMode: "legacy"
|
|
527
|
+
}),
|
|
528
|
+
getFinalArtifact: (ctx, artifactId) => ctx.db.get(artifactId)
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
var resolverOverrides = {};
|
|
532
|
+
function resolveGraphPrimitivesAppResolvers(_ctx) {
|
|
533
|
+
return {
|
|
534
|
+
...defaultResolvers(),
|
|
535
|
+
...resolverOverrides
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
var LEGACY_SCOPE_FIELD2 = "graphScopeProjectId";
|
|
539
|
+
function asMappedProjectId(topic) {
|
|
540
|
+
if (!topic) {
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
const directLegacyProjectId = normalizeScopeValue(topic[LEGACY_SCOPE_FIELD2]);
|
|
544
|
+
if (directLegacyProjectId) {
|
|
545
|
+
return directLegacyProjectId;
|
|
546
|
+
}
|
|
547
|
+
const metadata = topic.metadata || {};
|
|
548
|
+
const candidate = metadata[LEGACY_SCOPE_FIELD2] || metadata.legacyProjectId || metadata.projectId || metadata.scopeProjectId;
|
|
549
|
+
return candidate ? candidate : void 0;
|
|
550
|
+
}
|
|
551
|
+
function normalizeScopeValue(value) {
|
|
552
|
+
if (typeof value !== "string") {
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
const normalized = value.trim();
|
|
556
|
+
return normalized.length > 0 ? normalized : void 0;
|
|
557
|
+
}
|
|
558
|
+
function pickPrimaryTopic(candidates) {
|
|
559
|
+
return [...candidates].sort((a, b) => {
|
|
560
|
+
const depthA = a.depth ?? 9999;
|
|
561
|
+
const depthB = b.depth ?? 9999;
|
|
562
|
+
if (depthA !== depthB) {
|
|
563
|
+
return depthA - depthB;
|
|
564
|
+
}
|
|
565
|
+
const createdA = a.createdAt ?? Number.MAX_SAFE_INTEGER;
|
|
566
|
+
const createdB = b.createdAt ?? Number.MAX_SAFE_INTEGER;
|
|
567
|
+
if (createdA !== createdB) {
|
|
568
|
+
return createdA - createdB;
|
|
569
|
+
}
|
|
570
|
+
return String(a.name || "").localeCompare(String(b.name || ""));
|
|
571
|
+
})[0];
|
|
572
|
+
}
|
|
573
|
+
async function findTopicsByScopeAlias(ctx, scopeId) {
|
|
574
|
+
try {
|
|
575
|
+
return await ctx.db.query("topics").withIndex(
|
|
576
|
+
"by_graph_scope_project",
|
|
577
|
+
(q) => q.eq(LEGACY_SCOPE_FIELD2, scopeId)
|
|
578
|
+
).collect();
|
|
579
|
+
} catch (error) {
|
|
580
|
+
debugGraphPrimitiveFallback(
|
|
581
|
+
"[topicScope] Failed to resolve scope alias via index",
|
|
582
|
+
{
|
|
583
|
+
error,
|
|
584
|
+
scopeId
|
|
585
|
+
}
|
|
586
|
+
);
|
|
587
|
+
const topics = await ctx.db.query("topics").collect();
|
|
588
|
+
return topics.filter((topic) => {
|
|
589
|
+
const normalizedGlobalId = normalizeScopeValue(topic.globalId);
|
|
590
|
+
const mappedProjectId = asMappedProjectId(topic);
|
|
591
|
+
return String(topic._id) === scopeId || normalizedGlobalId === scopeId || mappedProjectId === scopeId;
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
async function tryResolveHostTopicById(ctx, topicId) {
|
|
596
|
+
if (typeof ctx.runQuery !== "function") {
|
|
597
|
+
return null;
|
|
598
|
+
}
|
|
599
|
+
try {
|
|
600
|
+
return await ctx.runQuery(api.topics.get, {
|
|
601
|
+
id: topicId
|
|
602
|
+
}) ?? null;
|
|
603
|
+
} catch (error) {
|
|
604
|
+
debugGraphPrimitiveFallback(
|
|
605
|
+
"[topicScope] Failed to resolve topic by host query",
|
|
606
|
+
{
|
|
607
|
+
error,
|
|
608
|
+
topicId
|
|
609
|
+
}
|
|
610
|
+
);
|
|
611
|
+
return null;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
async function tryResolveHostTopicByLegacyScope(ctx, legacyScopeId) {
|
|
615
|
+
if (typeof ctx.runQuery !== "function") {
|
|
616
|
+
return null;
|
|
617
|
+
}
|
|
618
|
+
try {
|
|
619
|
+
return await ctx.runQuery(api.topics.getByLegacyScopeId, {
|
|
620
|
+
projectId: legacyScopeId
|
|
621
|
+
}) ?? null;
|
|
622
|
+
} catch (error) {
|
|
623
|
+
debugGraphPrimitiveFallback(
|
|
624
|
+
"[topicScope] Failed to resolve topic by legacy scope",
|
|
625
|
+
{
|
|
626
|
+
error,
|
|
627
|
+
legacyScopeId
|
|
628
|
+
}
|
|
629
|
+
);
|
|
630
|
+
return null;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
async function resolveInheritedWorkspaceScope(ctx, topic) {
|
|
634
|
+
const MAX_DEPTH = 10;
|
|
635
|
+
let tenantId = normalizeScopeValue(topic.tenantId);
|
|
636
|
+
let workspaceId = normalizeScopeValue(topic.workspaceId);
|
|
637
|
+
if (tenantId && workspaceId) {
|
|
638
|
+
return { tenantId, workspaceId };
|
|
639
|
+
}
|
|
640
|
+
let current = topic;
|
|
641
|
+
for (let i = 0; i < MAX_DEPTH && current?.parentTopicId; i++) {
|
|
642
|
+
current = await ctx.db.get(current.parentTopicId);
|
|
643
|
+
if (!current) break;
|
|
644
|
+
if (!tenantId) {
|
|
645
|
+
tenantId = normalizeScopeValue(current.tenantId);
|
|
646
|
+
}
|
|
647
|
+
if (!workspaceId) {
|
|
648
|
+
workspaceId = normalizeScopeValue(current.workspaceId);
|
|
649
|
+
}
|
|
650
|
+
if (tenantId && workspaceId) break;
|
|
651
|
+
}
|
|
652
|
+
return { tenantId, workspaceId };
|
|
653
|
+
}
|
|
654
|
+
async function resolveTopicProjectScope(ctx, args) {
|
|
655
|
+
if (args.topicId) {
|
|
656
|
+
let topic = null;
|
|
657
|
+
try {
|
|
658
|
+
topic = await ctx.db.get(
|
|
659
|
+
args.topicId
|
|
660
|
+
);
|
|
661
|
+
} catch (error) {
|
|
662
|
+
debugGraphPrimitiveFallback(
|
|
663
|
+
"[topicScope] Failed to load topic by direct id",
|
|
664
|
+
{
|
|
665
|
+
error,
|
|
666
|
+
topicId: args.topicId
|
|
667
|
+
}
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
if (!topic) {
|
|
671
|
+
topic = await tryResolveHostTopicById(ctx, String(args.topicId));
|
|
672
|
+
}
|
|
673
|
+
if (!topic) {
|
|
674
|
+
topic = pickPrimaryTopic(
|
|
675
|
+
await findTopicsByScopeAlias(ctx, String(args.topicId))
|
|
676
|
+
) ?? null;
|
|
677
|
+
}
|
|
678
|
+
if (!topic) {
|
|
679
|
+
throw new Error(`Topic not found: ${String(args.topicId)}`);
|
|
680
|
+
}
|
|
681
|
+
const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
|
|
682
|
+
const mapped = asMappedProjectId(topic);
|
|
683
|
+
if (mapped) {
|
|
684
|
+
return {
|
|
685
|
+
topicId: topic._id,
|
|
686
|
+
projectId: mapped,
|
|
687
|
+
tenantId: inherited.tenantId,
|
|
688
|
+
workspaceId: inherited.workspaceId,
|
|
689
|
+
source: "topic"
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
return {
|
|
693
|
+
topicId: topic._id,
|
|
694
|
+
tenantId: inherited.tenantId,
|
|
695
|
+
workspaceId: inherited.workspaceId,
|
|
696
|
+
source: "topic"
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
if (args.projectId) {
|
|
700
|
+
let directTopic = null;
|
|
701
|
+
try {
|
|
702
|
+
directTopic = await ctx.db.get(
|
|
703
|
+
args.projectId
|
|
704
|
+
);
|
|
705
|
+
} catch (error) {
|
|
706
|
+
debugGraphPrimitiveFallback(
|
|
707
|
+
"[topicScope] Failed to load direct project topic",
|
|
708
|
+
{
|
|
709
|
+
error,
|
|
710
|
+
projectId: args.projectId
|
|
711
|
+
}
|
|
712
|
+
);
|
|
713
|
+
}
|
|
714
|
+
if (directTopic) {
|
|
715
|
+
const inherited = await resolveInheritedWorkspaceScope(ctx, directTopic);
|
|
716
|
+
const mapped = asMappedProjectId(directTopic);
|
|
717
|
+
return {
|
|
718
|
+
topicId: directTopic._id,
|
|
719
|
+
projectId: mapped ?? args.projectId,
|
|
720
|
+
tenantId: inherited.tenantId,
|
|
721
|
+
workspaceId: inherited.workspaceId,
|
|
722
|
+
source: "topic_inferred"
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
directTopic = await tryResolveHostTopicByLegacyScope(ctx, args.projectId);
|
|
726
|
+
if (directTopic) {
|
|
727
|
+
const inherited = await resolveInheritedWorkspaceScope(ctx, directTopic);
|
|
728
|
+
const mapped = asMappedProjectId(directTopic);
|
|
729
|
+
return {
|
|
730
|
+
topicId: directTopic._id,
|
|
731
|
+
projectId: mapped ?? args.projectId,
|
|
732
|
+
tenantId: inherited.tenantId,
|
|
733
|
+
workspaceId: inherited.workspaceId,
|
|
734
|
+
source: "topic_inferred"
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
const topics = await findTopicsByScopeAlias(ctx, args.projectId);
|
|
738
|
+
const primary = pickPrimaryTopic(topics);
|
|
739
|
+
if (primary) {
|
|
740
|
+
const inherited = await resolveInheritedWorkspaceScope(ctx, primary);
|
|
741
|
+
return {
|
|
742
|
+
topicId: primary._id,
|
|
743
|
+
projectId: args.projectId,
|
|
744
|
+
tenantId: inherited.tenantId,
|
|
745
|
+
workspaceId: inherited.workspaceId,
|
|
746
|
+
source: "project_mapped_topic"
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
throw new Error(
|
|
750
|
+
`Legacy project scope ${String(args.projectId)} has no mapped topic.`
|
|
751
|
+
);
|
|
752
|
+
}
|
|
753
|
+
throw new Error(
|
|
754
|
+
"Missing scope: provide topicId (preferred) or legacy projectId alias."
|
|
755
|
+
);
|
|
756
|
+
}
|
|
757
|
+
var optionalScopeArgs = {
|
|
758
|
+
projectId: v.optional(v.string()),
|
|
759
|
+
topicId: v.optional(v.string())
|
|
760
|
+
};
|
|
761
|
+
|
|
762
|
+
// src/edgeValidation.ts
|
|
763
|
+
function getLayerDepth(layer) {
|
|
764
|
+
switch (layer) {
|
|
765
|
+
case "L4":
|
|
766
|
+
return 4;
|
|
767
|
+
case "L3":
|
|
768
|
+
return 3;
|
|
769
|
+
case "L2":
|
|
770
|
+
return 2;
|
|
771
|
+
case "L1":
|
|
772
|
+
return 1;
|
|
773
|
+
case "ontological":
|
|
774
|
+
return 0;
|
|
775
|
+
default:
|
|
776
|
+
return -1;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
function isValidLayerConnection(fromLayer, toLayer) {
|
|
780
|
+
if (fromLayer === toLayer) {
|
|
781
|
+
return true;
|
|
782
|
+
}
|
|
783
|
+
if (fromLayer === "ontological" && toLayer === "ontological") {
|
|
784
|
+
return true;
|
|
785
|
+
}
|
|
786
|
+
if (fromLayer === "ontological" && (toLayer === "L3" || toLayer === "L2" || toLayer === "L1")) {
|
|
787
|
+
return true;
|
|
788
|
+
}
|
|
789
|
+
if ((fromLayer === "L3" || fromLayer === "L2" || fromLayer === "L1") && toLayer === "ontological") {
|
|
790
|
+
return true;
|
|
791
|
+
}
|
|
792
|
+
if (fromLayer === "L2" && toLayer === "L1") {
|
|
793
|
+
return true;
|
|
794
|
+
}
|
|
795
|
+
if (fromLayer === "L2" && toLayer === "L3") {
|
|
796
|
+
return true;
|
|
797
|
+
}
|
|
798
|
+
if (fromLayer === "L3" && toLayer === "L2") {
|
|
799
|
+
return true;
|
|
800
|
+
}
|
|
801
|
+
if (fromLayer === "L3" && toLayer === "L4") {
|
|
802
|
+
return true;
|
|
803
|
+
}
|
|
804
|
+
if (fromLayer === "L4" && toLayer === "L3") {
|
|
805
|
+
return true;
|
|
806
|
+
}
|
|
807
|
+
if (fromLayer === "L3" && toLayer === "organizational") {
|
|
808
|
+
return true;
|
|
809
|
+
}
|
|
810
|
+
if (fromLayer === "organizational" && toLayer === "L3") {
|
|
811
|
+
return true;
|
|
812
|
+
}
|
|
813
|
+
const fromDepth = getLayerDepth(fromLayer);
|
|
814
|
+
const toDepth = getLayerDepth(toLayer);
|
|
815
|
+
if (Math.abs(fromDepth - toDepth) > 1) {
|
|
816
|
+
return false;
|
|
817
|
+
}
|
|
818
|
+
return true;
|
|
819
|
+
}
|
|
820
|
+
var EDGE_LAYER_RULES = {
|
|
821
|
+
// === 6 Canonical Epistemic Types ===
|
|
822
|
+
supports: {
|
|
823
|
+
from: ["L3"],
|
|
824
|
+
to: ["L3"],
|
|
825
|
+
description: "Belief bears on Belief (weight: +1 supports, -1 contradicts)"
|
|
826
|
+
},
|
|
827
|
+
informs: {
|
|
828
|
+
from: ["L2"],
|
|
829
|
+
to: ["L3"],
|
|
830
|
+
description: "Evidence -> Belief (L2 -> L3)"
|
|
831
|
+
},
|
|
832
|
+
depends_on: {
|
|
833
|
+
from: ["L3"],
|
|
834
|
+
to: ["L3"],
|
|
835
|
+
description: "Belief B requires Belief A (structural gate)"
|
|
836
|
+
},
|
|
837
|
+
derived_from: {
|
|
838
|
+
from: ["L2", "L3", "L4"],
|
|
839
|
+
to: ["L1", "L2", "L3"],
|
|
840
|
+
description: "A was produced from B (provenance chain)"
|
|
841
|
+
},
|
|
842
|
+
contains: {
|
|
843
|
+
from: ["L3", "L4", "ontological"],
|
|
844
|
+
to: ["L2", "L3", "ontological"],
|
|
845
|
+
description: "A scopes/aggregates B (hierarchy)"
|
|
846
|
+
},
|
|
847
|
+
tests: {
|
|
848
|
+
from: ["L3"],
|
|
849
|
+
to: ["L3"],
|
|
850
|
+
description: "Question -> Belief (L3 -> L3)"
|
|
851
|
+
},
|
|
852
|
+
// === Structural / Lifecycle ===
|
|
853
|
+
supersedes: {
|
|
854
|
+
from: ["L3"],
|
|
855
|
+
to: ["L3"],
|
|
856
|
+
description: "NewNode -> OldNode (fork lineage)"
|
|
857
|
+
},
|
|
858
|
+
responds_to: {
|
|
859
|
+
from: ["L2", "L3"],
|
|
860
|
+
to: ["L3"],
|
|
861
|
+
description: "Answer -> Question (L2/L3 -> L3)"
|
|
862
|
+
},
|
|
863
|
+
belongs_to: {
|
|
864
|
+
from: ["L2", "L3", "ontological"],
|
|
865
|
+
to: ["L3"],
|
|
866
|
+
description: "Membership (migrating to contains)"
|
|
867
|
+
},
|
|
868
|
+
relates_to_thesis: {
|
|
869
|
+
from: ["L3"],
|
|
870
|
+
to: ["L3"],
|
|
871
|
+
description: "Belief -> Theme (L3 -> L3)"
|
|
872
|
+
},
|
|
873
|
+
// === Ontological (entity-entity or entity-epistemic bridge) ===
|
|
874
|
+
works_at: {
|
|
875
|
+
from: ["ontological"],
|
|
876
|
+
to: ["ontological"],
|
|
877
|
+
description: "Person -> Company"
|
|
878
|
+
},
|
|
879
|
+
invested_in: {
|
|
880
|
+
from: ["ontological"],
|
|
881
|
+
to: ["ontological"],
|
|
882
|
+
description: "Investor -> Company"
|
|
883
|
+
},
|
|
884
|
+
competes_with: {
|
|
885
|
+
from: ["ontological"],
|
|
886
|
+
to: ["ontological"],
|
|
887
|
+
description: "Company -> Company"
|
|
888
|
+
},
|
|
889
|
+
participates_in: {
|
|
890
|
+
from: ["ontological"],
|
|
891
|
+
to: ["ontological"],
|
|
892
|
+
description: "Company -> ValueChain"
|
|
893
|
+
},
|
|
894
|
+
founded_by: {
|
|
895
|
+
from: ["ontological"],
|
|
896
|
+
to: ["ontological"],
|
|
897
|
+
description: "Company -> Person"
|
|
898
|
+
},
|
|
899
|
+
evaluates: {
|
|
900
|
+
from: ["ontological"],
|
|
901
|
+
to: ["ontological"],
|
|
902
|
+
description: "Deal -> Company"
|
|
903
|
+
},
|
|
904
|
+
performs: {
|
|
905
|
+
from: ["ontological"],
|
|
906
|
+
to: ["ontological"],
|
|
907
|
+
description: "Company -> Function"
|
|
908
|
+
},
|
|
909
|
+
function_in: {
|
|
910
|
+
from: ["ontological"],
|
|
911
|
+
to: ["ontological"],
|
|
912
|
+
description: "Function -> ValueChain"
|
|
913
|
+
},
|
|
914
|
+
impacts: {
|
|
915
|
+
from: ["ontological", "L3"],
|
|
916
|
+
to: ["ontological"],
|
|
917
|
+
description: "Theme/Entity -> ValueChain"
|
|
918
|
+
},
|
|
919
|
+
raised_from: {
|
|
920
|
+
from: ["ontological"],
|
|
921
|
+
to: ["ontological"],
|
|
922
|
+
description: "Company -> Investor"
|
|
923
|
+
},
|
|
924
|
+
mentioned_in: {
|
|
925
|
+
from: ["ontological"],
|
|
926
|
+
to: ["L1", "L2"],
|
|
927
|
+
description: "Entity -> Source/Evidence"
|
|
928
|
+
},
|
|
929
|
+
perspective_on: {
|
|
930
|
+
from: ["ontological"],
|
|
931
|
+
to: ["L3"],
|
|
932
|
+
description: "Person -> Belief/Theme"
|
|
933
|
+
},
|
|
934
|
+
plays_theme: {
|
|
935
|
+
from: ["ontological"],
|
|
936
|
+
to: ["L3"],
|
|
937
|
+
description: "Deal -> Theme"
|
|
938
|
+
}
|
|
939
|
+
};
|
|
940
|
+
function validateEdgeLayers(edgeType, fromLayer, toLayer) {
|
|
941
|
+
const rules = EDGE_LAYER_RULES[edgeType];
|
|
942
|
+
if (!rules) {
|
|
943
|
+
console.warn(
|
|
944
|
+
`[EdgeValidation] Unknown edge type: ${edgeType}, allowing by default`
|
|
945
|
+
);
|
|
946
|
+
return { valid: true };
|
|
947
|
+
}
|
|
948
|
+
if (edgeType === "supersedes") {
|
|
949
|
+
if (fromLayer !== toLayer) {
|
|
950
|
+
return {
|
|
951
|
+
valid: false,
|
|
952
|
+
reason: `${edgeType} edges must be between nodes of the same layer. Got ${fromLayer} -> ${toLayer}`
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
return { valid: true };
|
|
956
|
+
}
|
|
957
|
+
if (!rules.from.includes(fromLayer)) {
|
|
958
|
+
return {
|
|
959
|
+
valid: false,
|
|
960
|
+
reason: `Edge type '${edgeType}' does not allow source layer ${fromLayer}. Allowed: ${rules.from.join(", ")}`
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
if (!rules.to.includes(toLayer)) {
|
|
964
|
+
return {
|
|
965
|
+
valid: false,
|
|
966
|
+
reason: `Edge type '${edgeType}' does not allow target layer ${toLayer}. Allowed: ${rules.to.join(", ")}`
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
if (!isValidLayerConnection(fromLayer, toLayer)) {
|
|
970
|
+
return {
|
|
971
|
+
valid: false,
|
|
972
|
+
reason: `Layer transition ${fromLayer} -> ${toLayer} is not permitted`
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
return { valid: true };
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
// src/epistemicEdges.mutations.ts
|
|
979
|
+
var create = mutation({
|
|
980
|
+
args: {
|
|
981
|
+
globalId: v.string(),
|
|
982
|
+
fromNodeId: v.id("epistemicNodes"),
|
|
983
|
+
toNodeId: v.id("epistemicNodes"),
|
|
984
|
+
edgeType: edgeTypeValidator,
|
|
985
|
+
weight: v.optional(v.number()),
|
|
986
|
+
confidence: v.optional(v.number()),
|
|
987
|
+
context: v.optional(v.string()),
|
|
988
|
+
derivationType: v.optional(v.string()),
|
|
989
|
+
createdBy: v.string(),
|
|
990
|
+
...optionalScopeArgs,
|
|
991
|
+
skipLayerValidation: v.optional(v.boolean())
|
|
992
|
+
},
|
|
993
|
+
returns: permissiveReturn,
|
|
994
|
+
handler: async (ctx, args) => {
|
|
995
|
+
const fromNode = await ctx.db.get(args.fromNodeId);
|
|
996
|
+
const toNode = await ctx.db.get(args.toNodeId);
|
|
997
|
+
if (!fromNode || !toNode) {
|
|
998
|
+
throw new Error("One or both nodes not found");
|
|
999
|
+
}
|
|
1000
|
+
const resolvedScope = args.topicId || args.projectId ? await resolveTopicProjectScope(ctx, {
|
|
1001
|
+
topicId: args.topicId,
|
|
1002
|
+
projectId: args.projectId
|
|
1003
|
+
}) : void 0;
|
|
1004
|
+
const resolvedProjectId = resolvedScope?.projectId ?? args.projectId;
|
|
1005
|
+
if (resolvedProjectId) {
|
|
1006
|
+
await requireProjectAccess(ctx, resolvedProjectId, args.createdBy);
|
|
1007
|
+
}
|
|
1008
|
+
const fromLayer = fromNode.epistemicLayer || getNodeLayer(fromNode.nodeType);
|
|
1009
|
+
const toLayer = toNode.epistemicLayer || getNodeLayer(toNode.nodeType);
|
|
1010
|
+
if (!args.skipLayerValidation) {
|
|
1011
|
+
const validation = validateEdgeLayers(args.edgeType, fromLayer, toLayer);
|
|
1012
|
+
if (!validation.valid) {
|
|
1013
|
+
throw new Error(
|
|
1014
|
+
`[EdgeValidation] Invalid edge: ${validation.reason}. Attempted: ${args.edgeType} from ${fromNode.nodeType}(${fromLayer}) \u2192 ${toNode.nodeType}(${toLayer})`
|
|
1015
|
+
);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
if (isDeprecatedEdgeType(args.edgeType)) {
|
|
1019
|
+
throw new Error(
|
|
1020
|
+
`FORBIDDEN: Edge type '${args.edgeType}' has been removed from StackOS schema.`
|
|
1021
|
+
);
|
|
1022
|
+
}
|
|
1023
|
+
const existing = await ctx.db.query("epistemicEdges").withIndex(
|
|
1024
|
+
"by_from_to",
|
|
1025
|
+
(q) => q.eq("fromNodeId", args.fromNodeId).eq("toNodeId", args.toNodeId)
|
|
1026
|
+
).collect();
|
|
1027
|
+
const duplicateEdge = existing.find((e) => e.edgeType === args.edgeType);
|
|
1028
|
+
if (duplicateEdge) {
|
|
1029
|
+
return { edgeId: duplicateEdge._id, isDuplicate: true };
|
|
1030
|
+
}
|
|
1031
|
+
await ctx.scheduler.runAfter(0, internal.neo4jEdgeAPI.createEdge, {
|
|
1032
|
+
globalId: args.globalId,
|
|
1033
|
+
fromGlobalId: fromNode.globalId,
|
|
1034
|
+
toGlobalId: toNode.globalId,
|
|
1035
|
+
edgeType: args.edgeType,
|
|
1036
|
+
weight: args.weight,
|
|
1037
|
+
confidence: args.confidence,
|
|
1038
|
+
context: args.context,
|
|
1039
|
+
derivationType: args.derivationType,
|
|
1040
|
+
createdBy: args.createdBy,
|
|
1041
|
+
topicId: resolvedProjectId ? String(resolvedProjectId) : void 0,
|
|
1042
|
+
fromNodeType: fromNode.nodeType,
|
|
1043
|
+
toNodeType: toNode.nodeType,
|
|
1044
|
+
fromLayer,
|
|
1045
|
+
toLayer
|
|
1046
|
+
});
|
|
1047
|
+
const auditProjectId = resolvedProjectId || fromNode.projectId;
|
|
1048
|
+
if (auditProjectId) {
|
|
1049
|
+
await ctx.db.insert("epistemicAudit", {
|
|
1050
|
+
entityType: "edge",
|
|
1051
|
+
entityId: args.globalId,
|
|
1052
|
+
changeType: "edge_added",
|
|
1053
|
+
changedAt: Date.now(),
|
|
1054
|
+
changedBy: args.createdBy,
|
|
1055
|
+
isAgent: false,
|
|
1056
|
+
newState: {
|
|
1057
|
+
edgeType: args.edgeType,
|
|
1058
|
+
fromNodeId: String(args.fromNodeId),
|
|
1059
|
+
toNodeId: String(args.toNodeId),
|
|
1060
|
+
weight: args.weight
|
|
1061
|
+
},
|
|
1062
|
+
projectId: auditProjectId
|
|
1063
|
+
});
|
|
1064
|
+
await resolveGraphPrimitivesAppResolvers().patchProject(
|
|
1065
|
+
ctx,
|
|
1066
|
+
auditProjectId,
|
|
1067
|
+
{
|
|
1068
|
+
lastActivityAt: Date.now()
|
|
1069
|
+
}
|
|
1070
|
+
);
|
|
1071
|
+
}
|
|
1072
|
+
return { edgeGlobalId: args.globalId, isDuplicate: false };
|
|
1073
|
+
}
|
|
1074
|
+
});
|
|
1075
|
+
var update = mutation({
|
|
1076
|
+
args: {
|
|
1077
|
+
edgeId: v.id("epistemicEdges"),
|
|
1078
|
+
weight: v.optional(v.number()),
|
|
1079
|
+
confidence: v.optional(v.number()),
|
|
1080
|
+
context: v.optional(v.string()),
|
|
1081
|
+
derivationType: v.optional(v.string()),
|
|
1082
|
+
userId: v.optional(v.string()),
|
|
1083
|
+
constraint: v.optional(v.string()),
|
|
1084
|
+
propagation: v.optional(v.string()),
|
|
1085
|
+
blocking: v.optional(v.boolean()),
|
|
1086
|
+
implicit: v.optional(v.boolean()),
|
|
1087
|
+
conditionalA: v.optional(subjectiveOpinionValidator),
|
|
1088
|
+
conditionalNotA: v.optional(subjectiveOpinionValidator),
|
|
1089
|
+
containment: v.optional(v.string()),
|
|
1090
|
+
interrogation: v.optional(v.string()),
|
|
1091
|
+
reasoningMethod: v.optional(v.string()),
|
|
1092
|
+
analogyBasis: v.optional(v.string()),
|
|
1093
|
+
disanalogies: v.optional(v.array(v.string())),
|
|
1094
|
+
analogyStrength: v.optional(v.number()),
|
|
1095
|
+
causal: v.optional(v.boolean()),
|
|
1096
|
+
predictionFulfilled: v.optional(v.boolean()),
|
|
1097
|
+
predictionFulfilledAt: v.optional(v.number()),
|
|
1098
|
+
predictionFulfilledBy: v.optional(v.string())
|
|
1099
|
+
},
|
|
1100
|
+
returns: permissiveReturn,
|
|
1101
|
+
handler: async (ctx, args) => {
|
|
1102
|
+
const { edgeId, userId, ...updates } = args;
|
|
1103
|
+
const edge = await ctx.db.get(edgeId);
|
|
1104
|
+
if (!edge) {
|
|
1105
|
+
throw new Error("Edge not found");
|
|
1106
|
+
}
|
|
1107
|
+
if (edge.projectId && userId) {
|
|
1108
|
+
await requireProjectAccess(ctx, edge.projectId, userId);
|
|
1109
|
+
}
|
|
1110
|
+
const cleanUpdates = {};
|
|
1111
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
1112
|
+
if (value !== void 0) {
|
|
1113
|
+
cleanUpdates[key] = value;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
await ctx.db.patch(edgeId, cleanUpdates);
|
|
1117
|
+
if (edge.projectId) {
|
|
1118
|
+
await ctx.db.insert("epistemicAudit", {
|
|
1119
|
+
entityType: "edge",
|
|
1120
|
+
entityId: edge.globalId,
|
|
1121
|
+
changeType: "updated",
|
|
1122
|
+
changedAt: Date.now(),
|
|
1123
|
+
changedBy: userId || edge.createdBy,
|
|
1124
|
+
isAgent: false,
|
|
1125
|
+
previousState: {
|
|
1126
|
+
weight: edge.weight,
|
|
1127
|
+
confidence: edge.confidence,
|
|
1128
|
+
context: edge.context
|
|
1129
|
+
},
|
|
1130
|
+
newState: cleanUpdates,
|
|
1131
|
+
projectId: edge.projectId
|
|
1132
|
+
});
|
|
1133
|
+
}
|
|
1134
|
+
return buildEdgeStatusSuccessResult();
|
|
1135
|
+
}
|
|
1136
|
+
});
|
|
1137
|
+
var remove = mutation({
|
|
1138
|
+
args: {
|
|
1139
|
+
edgeId: v.id("epistemicEdges"),
|
|
1140
|
+
userId: v.optional(v.string())
|
|
1141
|
+
},
|
|
1142
|
+
returns: permissiveReturn,
|
|
1143
|
+
handler: async (ctx, args) => {
|
|
1144
|
+
const edge = await ctx.db.get(args.edgeId);
|
|
1145
|
+
if (!edge) {
|
|
1146
|
+
return buildEdgeNotFoundResult();
|
|
1147
|
+
}
|
|
1148
|
+
if (edge.projectId && args.userId) {
|
|
1149
|
+
await requireProjectAccess(ctx, edge.projectId, args.userId);
|
|
1150
|
+
}
|
|
1151
|
+
if (edge.projectId) {
|
|
1152
|
+
await ctx.db.insert("epistemicAudit", {
|
|
1153
|
+
entityType: "edge",
|
|
1154
|
+
entityId: edge.globalId,
|
|
1155
|
+
changeType: "edge_removed",
|
|
1156
|
+
changedAt: Date.now(),
|
|
1157
|
+
changedBy: args.userId || edge.createdBy,
|
|
1158
|
+
isAgent: false,
|
|
1159
|
+
previousState: {
|
|
1160
|
+
edgeType: edge.edgeType,
|
|
1161
|
+
fromNodeId: String(edge.fromNodeId),
|
|
1162
|
+
toNodeId: edge.toNodeId ? String(edge.toNodeId) : void 0,
|
|
1163
|
+
weight: edge.weight
|
|
1164
|
+
},
|
|
1165
|
+
projectId: edge.projectId
|
|
1166
|
+
});
|
|
1167
|
+
await resolveGraphPrimitivesAppResolvers().patchProject(
|
|
1168
|
+
ctx,
|
|
1169
|
+
edge.projectId,
|
|
1170
|
+
{
|
|
1171
|
+
lastActivityAt: Date.now()
|
|
1172
|
+
}
|
|
1173
|
+
);
|
|
1174
|
+
}
|
|
1175
|
+
await ctx.scheduler.runAfter(0, internal.neo4jEdgeAPI.deleteEdge, {
|
|
1176
|
+
globalId: edge.globalId
|
|
1177
|
+
});
|
|
1178
|
+
await ctx.db.delete(args.edgeId);
|
|
1179
|
+
return buildEdgeStatusSuccessResult();
|
|
1180
|
+
}
|
|
1181
|
+
});
|
|
1182
|
+
var removeBetween = mutation({
|
|
1183
|
+
args: {
|
|
1184
|
+
fromNodeId: v.id("epistemicNodes"),
|
|
1185
|
+
toNodeId: v.id("epistemicNodes"),
|
|
1186
|
+
edgeType: v.optional(edgeTypeValidator)
|
|
1187
|
+
},
|
|
1188
|
+
returns: permissiveReturn,
|
|
1189
|
+
handler: async (ctx, args) => {
|
|
1190
|
+
const edges = await ctx.db.query("epistemicEdges").withIndex(
|
|
1191
|
+
"by_from_to",
|
|
1192
|
+
(q) => q.eq("fromNodeId", args.fromNodeId).eq("toNodeId", args.toNodeId)
|
|
1193
|
+
).collect();
|
|
1194
|
+
let deleted = 0;
|
|
1195
|
+
for (const edge of edges) {
|
|
1196
|
+
if (!args.edgeType || edge.edgeType === args.edgeType) {
|
|
1197
|
+
await ctx.scheduler.runAfter(0, internal.neo4jEdgeAPI.deleteEdge, {
|
|
1198
|
+
globalId: edge.globalId
|
|
1199
|
+
});
|
|
1200
|
+
await ctx.db.delete(edge._id);
|
|
1201
|
+
deleted++;
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
return { deleted };
|
|
1205
|
+
}
|
|
1206
|
+
});
|
|
1207
|
+
var batchCreate = mutation({
|
|
1208
|
+
args: {
|
|
1209
|
+
edges: v.array(
|
|
1210
|
+
v.object({
|
|
1211
|
+
globalId: v.string(),
|
|
1212
|
+
fromNodeId: v.id("epistemicNodes"),
|
|
1213
|
+
toNodeId: v.id("epistemicNodes"),
|
|
1214
|
+
edgeType: edgeTypeValidator,
|
|
1215
|
+
weight: v.optional(v.number()),
|
|
1216
|
+
confidence: v.optional(v.number()),
|
|
1217
|
+
context: v.optional(v.string()),
|
|
1218
|
+
derivationType: v.optional(v.string()),
|
|
1219
|
+
createdBy: v.string(),
|
|
1220
|
+
...optionalScopeArgs
|
|
1221
|
+
})
|
|
1222
|
+
),
|
|
1223
|
+
skipLayerValidation: v.optional(v.boolean())
|
|
1224
|
+
},
|
|
1225
|
+
returns: permissiveReturn,
|
|
1226
|
+
handler: async (ctx, args) => {
|
|
1227
|
+
const results = [];
|
|
1228
|
+
const errors = [];
|
|
1229
|
+
for (const edge of args.edges) {
|
|
1230
|
+
const fromNode = await ctx.db.get(edge.fromNodeId);
|
|
1231
|
+
const toNode = await ctx.db.get(edge.toNodeId);
|
|
1232
|
+
if (!fromNode || !toNode) {
|
|
1233
|
+
errors.push({
|
|
1234
|
+
globalId: edge.globalId,
|
|
1235
|
+
error: "One or both nodes not found"
|
|
1236
|
+
});
|
|
1237
|
+
continue;
|
|
1238
|
+
}
|
|
1239
|
+
const fromLayer = fromNode.epistemicLayer || getNodeLayer(fromNode.nodeType);
|
|
1240
|
+
const toLayer = toNode.epistemicLayer || getNodeLayer(toNode.nodeType);
|
|
1241
|
+
if (!args.skipLayerValidation) {
|
|
1242
|
+
const validation = validateEdgeLayers(edge.edgeType, fromLayer, toLayer);
|
|
1243
|
+
if (!validation.valid) {
|
|
1244
|
+
errors.push({
|
|
1245
|
+
globalId: edge.globalId,
|
|
1246
|
+
error: validation.reason || "Invalid layer combination"
|
|
1247
|
+
});
|
|
1248
|
+
continue;
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
if (isDeprecatedEdgeType(edge.edgeType)) {
|
|
1252
|
+
errors.push({
|
|
1253
|
+
globalId: edge.globalId,
|
|
1254
|
+
error: `FORBIDDEN: Edge type '${edge.edgeType}' has been removed from StackOS schema.`
|
|
1255
|
+
});
|
|
1256
|
+
continue;
|
|
1257
|
+
}
|
|
1258
|
+
await ctx.scheduler.runAfter(0, internal.neo4jEdgeAPI.createEdge, {
|
|
1259
|
+
globalId: edge.globalId,
|
|
1260
|
+
fromGlobalId: fromNode.globalId,
|
|
1261
|
+
toGlobalId: toNode.globalId,
|
|
1262
|
+
edgeType: edge.edgeType,
|
|
1263
|
+
weight: edge.weight,
|
|
1264
|
+
confidence: edge.confidence,
|
|
1265
|
+
context: edge.context,
|
|
1266
|
+
derivationType: edge.derivationType,
|
|
1267
|
+
createdBy: edge.createdBy,
|
|
1268
|
+
topicId: edge.projectId ? String(edge.projectId) : void 0,
|
|
1269
|
+
fromNodeType: fromNode.nodeType,
|
|
1270
|
+
toNodeType: toNode.nodeType,
|
|
1271
|
+
fromLayer,
|
|
1272
|
+
toLayer
|
|
1273
|
+
});
|
|
1274
|
+
results.push({ globalId: edge.globalId, edgeGlobalId: edge.globalId });
|
|
1275
|
+
}
|
|
1276
|
+
const projectIds = new Set(
|
|
1277
|
+
args.edges.flatMap(
|
|
1278
|
+
(edge) => typeof edge.projectId === "string" ? [edge.projectId] : []
|
|
1279
|
+
)
|
|
1280
|
+
);
|
|
1281
|
+
for (const pid of projectIds) {
|
|
1282
|
+
if (pid) {
|
|
1283
|
+
await resolveGraphPrimitivesAppResolvers().patchProject(ctx, pid, {
|
|
1284
|
+
lastActivityAt: Date.now()
|
|
1285
|
+
});
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
return { created: results.length, results, errors };
|
|
1289
|
+
}
|
|
1290
|
+
});
|
|
1291
|
+
var cleanupDeprecatedEdges = mutation({
|
|
1292
|
+
args: {
|
|
1293
|
+
...optionalScopeArgs,
|
|
1294
|
+
dryRun: v.optional(v.boolean())
|
|
1295
|
+
},
|
|
1296
|
+
returns: permissiveReturn,
|
|
1297
|
+
handler: async (ctx, args) => {
|
|
1298
|
+
const DEPRECATED_TYPES = ["contradicts"];
|
|
1299
|
+
const scopeId = args.topicId || args.projectId;
|
|
1300
|
+
const allEdges = scopeId ? await ctx.db.query("epistemicEdges").withIndex("by_topic", (q) => q.eq("topicId", scopeId)).collect() : [];
|
|
1301
|
+
const deprecatedEdges = allEdges.filter(
|
|
1302
|
+
(edge) => DEPRECATED_TYPES.includes(edge.edgeType)
|
|
1303
|
+
);
|
|
1304
|
+
if (args.dryRun) {
|
|
1305
|
+
return {
|
|
1306
|
+
dryRun: true,
|
|
1307
|
+
found: deprecatedEdges.length,
|
|
1308
|
+
edges: deprecatedEdges.map((e) => ({
|
|
1309
|
+
id: e._id,
|
|
1310
|
+
type: e.edgeType,
|
|
1311
|
+
from: e.fromNodeId,
|
|
1312
|
+
to: e.toNodeId,
|
|
1313
|
+
description: typeof e.context === "object" && e.context && "description" in e.context ? e.context.description : e.description
|
|
1314
|
+
})),
|
|
1315
|
+
message: `Would delete ${deprecatedEdges.length} deprecated edges`
|
|
1316
|
+
};
|
|
1317
|
+
}
|
|
1318
|
+
for (const edge of deprecatedEdges) {
|
|
1319
|
+
await ctx.scheduler.runAfter(0, internal.neo4jEdgeAPI.deleteEdge, {
|
|
1320
|
+
globalId: edge.globalId
|
|
1321
|
+
});
|
|
1322
|
+
await ctx.db.delete(edge._id);
|
|
1323
|
+
}
|
|
1324
|
+
return {
|
|
1325
|
+
dryRun: false,
|
|
1326
|
+
deleted: deprecatedEdges.length,
|
|
1327
|
+
edges: deprecatedEdges.map((e) => ({
|
|
1328
|
+
id: e._id,
|
|
1329
|
+
type: e.edgeType
|
|
1330
|
+
})),
|
|
1331
|
+
message: `Deleted ${deprecatedEdges.length} deprecated edges`
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
1334
|
+
});
|
|
1335
|
+
var deleteEdges = mutation({
|
|
1336
|
+
args: {
|
|
1337
|
+
edgeIds: v.array(v.id("epistemicEdges"))
|
|
1338
|
+
},
|
|
1339
|
+
returns: permissiveReturn,
|
|
1340
|
+
handler: async (ctx, args) => {
|
|
1341
|
+
const deleted = [];
|
|
1342
|
+
const notFound = [];
|
|
1343
|
+
for (const edgeId of args.edgeIds) {
|
|
1344
|
+
const edge = await ctx.db.get(edgeId);
|
|
1345
|
+
if (edge) {
|
|
1346
|
+
await ctx.scheduler.runAfter(0, internal.neo4jEdgeAPI.deleteEdge, {
|
|
1347
|
+
globalId: edge.globalId
|
|
1348
|
+
});
|
|
1349
|
+
await ctx.db.delete(edgeId);
|
|
1350
|
+
deleted.push(edgeId);
|
|
1351
|
+
} else {
|
|
1352
|
+
notFound.push(edgeId);
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
return {
|
|
1356
|
+
deleted: deleted.length,
|
|
1357
|
+
notFound: notFound.length,
|
|
1358
|
+
message: `Deleted ${deleted.length} edges, ${notFound.length} not found`
|
|
1359
|
+
};
|
|
1360
|
+
}
|
|
1361
|
+
});
|
|
1362
|
+
|
|
1363
|
+
export { batchCreate, cleanupDeprecatedEdges, create, deleteEdges, remove, removeBetween, update };
|
|
1364
|
+
//# sourceMappingURL=epistemicEdges.mutations.js.map
|
|
1365
|
+
//# sourceMappingURL=epistemicEdges.mutations.js.map
|