@lucern/graph-primitives 1.0.29 → 1.0.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{beliefDecay-DZ6tkLYq.d.ts → beliefDecay-BmkEk5OJ.d.ts} +3 -3
- package/dist/beliefDecay.d.ts +1 -1
- package/dist/beliefDecay.js +448 -314
- package/dist/beliefDecay.js.map +1 -1
- package/dist/{beliefEvidenceLinks-CWOXxxJg.d.ts → beliefEvidenceLinks-BzfjON_6.d.ts} +13 -13
- package/dist/beliefEvidenceLinks.d.ts +1 -1
- package/dist/beliefEvidenceLinks.js +843 -624
- package/dist/beliefEvidenceLinks.js.map +1 -1
- package/dist/beliefEvidenceLinks.operational.d.ts +7 -5
- package/dist/beliefEvidenceLinks.operational.js +91 -18
- package/dist/beliefEvidenceLinks.operational.js.map +1 -1
- package/dist/beliefLifecycle.js.map +1 -1
- package/dist/confidencePropagationDispatch.d.ts +28 -27
- package/dist/confidencePropagationDispatch.js +157 -99
- package/dist/confidencePropagationDispatch.js.map +1 -1
- package/dist/{contradictions-51VLsESq.d.ts → contradictions-BATPuZTL.d.ts} +10 -10
- package/dist/contradictions.d.ts +1 -1
- package/dist/contradictions.js +395 -225
- package/dist/contradictions.js.map +1 -1
- package/dist/convex.d.ts +65 -30
- package/dist/convex.js +7 -3
- package/dist/convex.js.map +1 -1
- package/dist/debug.js.map +1 -1
- package/dist/edgeValidation.js +293 -85
- package/dist/edgeValidation.js.map +1 -1
- package/dist/edges/contains.d.ts +1 -1
- package/dist/edges/contains.js.map +1 -1
- package/dist/edges/contradicts.d.ts +1 -1
- package/dist/edges/contradicts.js.map +1 -1
- package/dist/edges/{dependsOn.d.ts → depends-on.d.ts} +1 -1
- package/dist/edges/{dependsOn.js → depends-on.js} +4 -4
- package/dist/edges/depends-on.js.map +1 -0
- package/dist/edges/{derivedFrom.d.ts → derived-from.d.ts} +1 -1
- package/dist/edges/{derivedFrom.js → derived-from.js} +3 -3
- package/dist/edges/derived-from.js.map +1 -0
- package/dist/edges/elaborates.d.ts +1 -1
- package/dist/edges/elaborates.js.map +1 -1
- package/dist/edges/index.d.ts +7 -3
- package/dist/edges/index.js +7 -4
- package/dist/edges/index.js.map +1 -1
- package/dist/edges/informs.d.ts +1 -1
- package/dist/edges/informs.js.map +1 -1
- package/dist/edges/{propagationTypes.d.ts → propagation-types.d.ts} +14 -14
- package/dist/edges/{propagationTypes.js → propagation-types.js} +3 -3
- package/dist/edges/propagation-types.js.map +1 -0
- package/dist/edges/refutes.d.ts +1 -1
- package/dist/edges/refutes.js.map +1 -1
- package/dist/edges/supports.d.ts +1 -1
- package/dist/edges/supports.js.map +1 -1
- package/dist/edges/tests.d.ts +1 -1
- package/dist/edges/tests.js.map +1 -1
- package/dist/edges/utils.d.ts +1 -1
- package/dist/edges/utils.js.map +1 -1
- package/dist/embeddingTrigger.d.ts +14 -6
- package/dist/embeddingTrigger.js +11 -14
- package/dist/embeddingTrigger.js.map +1 -1
- package/dist/{entityBridge-DMaKooYn.d.ts → entityBridge-BhVDM3pc.d.ts} +5 -5
- package/dist/entityBridge.d.ts +1 -1
- package/dist/entityBridge.js +602 -225
- package/dist/entityBridge.js.map +1 -1
- package/dist/entityCanonicalMatch.d.ts +14 -12
- package/dist/entityCanonicalMatch.js.map +1 -1
- package/dist/{entityLifecycle-CvgSK5FV.d.ts → entityLifecycle-BsfCz9pS.d.ts} +5 -9
- package/dist/entityLifecycle.d.ts +1 -1
- package/dist/entityLifecycle.js +854 -480
- package/dist/entityLifecycle.js.map +1 -1
- package/dist/{entityValidation-KLZ_Xl2D.d.ts → entityValidation-B1yNEHJx.d.ts} +7 -6
- package/dist/entityValidation.d.ts +3 -1
- package/dist/entityValidation.js +60 -8
- package/dist/entityValidation.js.map +1 -1
- package/dist/{epistemicAnswers-C5ib4z6_.d.ts → epistemicAnswers-f47YMu9U.d.ts} +6 -6
- package/dist/epistemicAnswers.d.ts +1 -1
- package/dist/epistemicAnswers.js +587 -545
- package/dist/epistemicAnswers.js.map +1 -1
- package/dist/epistemicBeliefs.admin.d.ts +8 -8
- package/dist/epistemicBeliefs.admin.js +365 -166
- package/dist/epistemicBeliefs.admin.js.map +1 -1
- package/dist/epistemicBeliefs.backfills.d.ts +8 -8
- package/dist/epistemicBeliefs.backfills.js +655 -289
- package/dist/epistemicBeliefs.backfills.js.map +1 -1
- package/dist/epistemicBeliefs.confidence.d.ts +19 -15
- package/dist/epistemicBeliefs.confidence.js +633 -386
- package/dist/epistemicBeliefs.confidence.js.map +1 -1
- package/dist/epistemicBeliefs.core.d.ts +6 -6
- package/dist/epistemicBeliefs.core.js +717 -371
- package/dist/epistemicBeliefs.core.js.map +1 -1
- package/dist/epistemicBeliefs.d.ts +11 -9
- package/dist/epistemicBeliefs.forkEvidence.d.ts +2 -0
- package/dist/epistemicBeliefs.forkEvidence.js +8 -8
- package/dist/epistemicBeliefs.forkEvidence.js.map +1 -1
- package/dist/epistemicBeliefs.helpers.d.ts +68 -49
- package/dist/epistemicBeliefs.helpers.js +358 -211
- package/dist/epistemicBeliefs.helpers.js.map +1 -1
- package/dist/epistemicBeliefs.internal.d.ts +5 -5
- package/dist/epistemicBeliefs.internal.js +1248 -1026
- package/dist/epistemicBeliefs.internal.js.map +1 -1
- package/dist/epistemicBeliefs.js +4942 -3590
- package/dist/epistemicBeliefs.js.map +1 -1
- package/dist/epistemicBeliefs.lifecycle.d.ts +5 -5
- package/dist/epistemicBeliefs.lifecycle.js +1138 -781
- package/dist/epistemicBeliefs.lifecycle.js.map +1 -1
- package/dist/epistemicBeliefs.links.d.ts +7 -7
- package/dist/epistemicBeliefs.links.js +404 -267
- package/dist/epistemicBeliefs.links.js.map +1 -1
- package/dist/epistemicBeliefs.queries.d.ts +4 -4
- package/dist/epistemicBeliefs.queries.js +175 -20
- package/dist/epistemicBeliefs.queries.js.map +1 -1
- package/dist/epistemicBeliefs.topicAnchor.d.ts +6 -4
- package/dist/epistemicBeliefs.topicAnchor.js +12 -5
- package/dist/epistemicBeliefs.topicAnchor.js.map +1 -1
- package/dist/epistemicContracts.d.ts +28 -3
- package/dist/epistemicContracts.evaluators.d.ts +2 -0
- package/dist/epistemicContracts.evaluators.js +1062 -576
- package/dist/epistemicContracts.evaluators.js.map +1 -1
- package/dist/epistemicContracts.handlers.d.ts +15 -32
- package/dist/epistemicContracts.handlers.js +1829 -1351
- package/dist/epistemicContracts.handlers.js.map +1 -1
- package/dist/epistemicContracts.js +1131 -636
- package/dist/epistemicContracts.js.map +1 -1
- package/dist/epistemicContracts.metrics.d.ts +2 -0
- package/dist/epistemicContracts.metrics.js +375 -158
- package/dist/epistemicContracts.metrics.js.map +1 -1
- package/dist/epistemicContracts.types.d.ts +87 -81
- package/dist/epistemicEdgeCreation.d.ts +2 -0
- package/dist/epistemicEdgeCreation.js +87 -16
- package/dist/epistemicEdgeCreation.js.map +1 -1
- package/dist/{epistemicEdges-BF-cn4i3.d.ts → epistemicEdges-BGBh0QSP.d.ts} +4 -7
- package/dist/epistemicEdges.d.ts +6 -5
- package/dist/epistemicEdges.handlers.d.ts +3 -3
- package/dist/epistemicEdges.handlers.js +129 -24
- package/dist/epistemicEdges.handlers.js.map +1 -1
- package/dist/epistemicEdges.helpers.d.ts +6 -4
- package/dist/epistemicEdges.helpers.js +37 -2
- package/dist/epistemicEdges.helpers.js.map +1 -1
- package/dist/epistemicEdges.js +1966 -1202
- package/dist/epistemicEdges.js.map +1 -1
- package/dist/epistemicEdges.mutations.d.ts +7 -7
- package/dist/epistemicEdges.mutations.js +956 -579
- package/dist/epistemicEdges.mutations.js.map +1 -1
- package/dist/epistemicEdges.queries.d.ts +16 -16
- package/dist/epistemicEdges.queries.js +639 -367
- package/dist/epistemicEdges.queries.js.map +1 -1
- package/dist/epistemicEdges.types.d.ts +10 -8
- package/dist/epistemicEvidence.d.ts +4 -1
- package/dist/epistemicEvidence.js +933 -532
- package/dist/epistemicEvidence.js.map +1 -1
- package/dist/epistemicEvidenceHelpers.d.ts +26 -10
- package/dist/epistemicEvidenceHelpers.js +239 -200
- package/dist/epistemicEvidenceHelpers.js.map +1 -1
- package/dist/epistemicEvidenceMutations.d.ts +8 -8
- package/dist/epistemicEvidenceMutations.js +840 -692
- package/dist/epistemicEvidenceMutations.js.map +1 -1
- package/dist/epistemicEvidenceQueries.d.ts +8 -8
- package/dist/epistemicEvidenceQueries.js +514 -238
- package/dist/epistemicEvidenceQueries.js.map +1 -1
- package/dist/epistemicHelpers.d.ts +4 -2
- package/dist/epistemicHelpers.js +308 -134
- package/dist/epistemicHelpers.js.map +1 -1
- package/dist/epistemicInsert.d.ts +16 -4
- package/dist/epistemicInsert.js +6 -3
- package/dist/epistemicInsert.js.map +1 -1
- package/dist/epistemicLayerRules.d.ts +10 -8
- package/dist/epistemicLayerRules.js +1 -5
- package/dist/epistemicLayerRules.js.map +1 -1
- package/dist/{epistemicLinking-CfE00tHJ.d.ts → epistemicLinking-CsCDv2cN.d.ts} +3 -3
- package/dist/epistemicLinking.d.ts +1 -1
- package/dist/epistemicLinking.js +177 -100
- package/dist/epistemicLinking.js.map +1 -1
- package/dist/epistemicNodeCreation.d.ts +2 -0
- package/dist/epistemicNodeCreation.js +203 -40
- package/dist/epistemicNodeCreation.js.map +1 -1
- package/dist/{epistemicNodes-BCQxpYx_.d.ts → epistemicNodes-CokAgBHg.d.ts} +3 -3
- package/dist/epistemicNodes.d.ts +3 -3
- package/dist/epistemicNodes.helpers.d.ts +24 -15
- package/dist/epistemicNodes.helpers.js.map +1 -1
- package/dist/epistemicNodes.internal.d.ts +6 -6
- package/dist/epistemicNodes.internal.js +389 -319
- package/dist/epistemicNodes.internal.js.map +1 -1
- package/dist/epistemicNodes.js +700 -504
- package/dist/epistemicNodes.js.map +1 -1
- package/dist/epistemicNodes.mutations.d.ts +6 -6
- package/dist/epistemicNodes.mutations.js +560 -463
- package/dist/epistemicNodes.mutations.js.map +1 -1
- package/dist/epistemicNodes.queries.d.ts +8 -8
- package/dist/epistemicNodes.queries.js +311 -314
- package/dist/epistemicNodes.queries.js.map +1 -1
- package/dist/epistemicNodes.validators.d.ts +2 -2
- package/dist/epistemicNodes.validators.js.map +1 -1
- package/dist/epistemicQuestions.conviction.d.ts +8 -8
- package/dist/epistemicQuestions.conviction.js +665 -484
- package/dist/epistemicQuestions.conviction.js.map +1 -1
- package/dist/epistemicQuestions.create.d.ts +4 -4
- package/dist/epistemicQuestions.create.js +640 -612
- package/dist/epistemicQuestions.create.js.map +1 -1
- package/dist/epistemicQuestions.d.ts +8 -5
- package/dist/epistemicQuestions.evidence.d.ts +2 -2
- package/dist/epistemicQuestions.evidence.js +475 -383
- package/dist/epistemicQuestions.evidence.js.map +1 -1
- package/dist/epistemicQuestions.helpers.d.ts +125 -24
- package/dist/epistemicQuestions.helpers.js +240 -209
- package/dist/epistemicQuestions.helpers.js.map +1 -1
- package/dist/epistemicQuestions.js +3474 -2823
- package/dist/epistemicQuestions.js.map +1 -1
- package/dist/epistemicQuestions.lifecycle.d.ts +2 -2
- package/dist/epistemicQuestions.lifecycle.js +607 -546
- package/dist/epistemicQuestions.lifecycle.js.map +1 -1
- package/dist/epistemicQuestions.queries.d.ts +12 -7
- package/dist/epistemicQuestions.queries.js +305 -244
- package/dist/epistemicQuestions.queries.js.map +1 -1
- package/dist/epistemicQuestions.sprint.d.ts +2 -2
- package/dist/epistemicQuestions.sprint.js +600 -394
- package/dist/epistemicQuestions.sprint.js.map +1 -1
- package/dist/epistemicQuestions.tail.d.ts +6 -6
- package/dist/epistemicQuestions.tail.js +572 -433
- package/dist/epistemicQuestions.tail.js.map +1 -1
- package/dist/{epistemicSources-dlKj58Jp.d.ts → epistemicSources-DQtaEkWs.d.ts} +4 -4
- package/dist/epistemicSources.d.ts +1 -1
- package/dist/epistemicSources.js +351 -311
- package/dist/epistemicSources.js.map +1 -1
- package/dist/evaluators/index.d.ts +8 -6
- package/dist/evaluators/index.js +399 -167
- package/dist/evaluators/index.js.map +1 -1
- package/dist/evaluators/lint-checker-evaluator.d.ts +16 -0
- package/dist/evaluators/{lintCheckerEvaluator.js → lint-checker-evaluator.js} +10 -5
- package/dist/evaluators/lint-checker-evaluator.js.map +1 -0
- package/dist/evaluators/{sentryCheckerEvaluator.d.ts → sentry-checker-evaluator.d.ts} +7 -2
- package/dist/evaluators/{sentryCheckerEvaluator.js → sentry-checker-evaluator.js} +3 -3
- package/dist/evaluators/sentry-checker-evaluator.js.map +1 -0
- package/dist/evaluators/shared.d.ts +2 -2
- package/dist/evaluators/shared.js +3 -1
- package/dist/evaluators/shared.js.map +1 -1
- package/dist/evaluators/{testRunnerEvaluator.d.ts → test-runner-evaluator.d.ts} +6 -1
- package/dist/evaluators/{testRunnerEvaluator.js → test-runner-evaluator.js} +6 -4
- package/dist/evaluators/test-runner-evaluator.js.map +1 -0
- package/dist/evaluators/tsc-checker-evaluator.d.ts +16 -0
- package/dist/evaluators/{tscCheckerEvaluator.js → tsc-checker-evaluator.js} +10 -5
- package/dist/evaluators/tsc-checker-evaluator.js.map +1 -0
- package/dist/graphTypes.js +6 -2
- package/dist/graphTypes.js.map +1 -1
- package/dist/helpers.d.ts +2 -0
- package/dist/helpers.js +313 -93
- package/dist/helpers.js.map +1 -1
- package/dist/{index-C-Kyd7hD.d.ts → index-DZxyC9Pb.d.ts} +7 -6
- package/dist/index.d.ts +86 -83
- package/dist/index.js +16914 -11760
- package/dist/index.js.map +1 -1
- package/dist/invariantEnforcement.d.ts +3 -3
- package/dist/invariantEnforcement.js.map +1 -1
- package/dist/logicalRoleInference.d.ts +2 -0
- package/dist/logicalRoleInference.js +1 -1
- package/dist/logicalRoleInference.js.map +1 -1
- package/dist/matcherFeedbackUtils.d.ts +2 -2
- package/dist/matcherFeedbackUtils.js.map +1 -1
- package/dist/{ontology-matching-C6rrz2VP.d.ts → ontology-matching-C-mYFrir.d.ts} +16 -16
- package/dist/ontology-matching.d.ts +1 -1
- package/dist/{ontologyApproval-CFYmqKmk.d.ts → ontologyApproval-BVt0feJi.d.ts} +10 -10
- package/dist/ontologyApproval.d.ts +1 -1
- package/dist/ontologyApproval.js +7 -1
- package/dist/ontologyApproval.js.map +1 -1
- package/dist/ontologyDefinitions.d.ts +14 -24
- package/dist/ontologyDefinitions.js +269 -34
- package/dist/ontologyDefinitions.js.map +1 -1
- package/dist/ontologyHelpers.d.ts +13 -13
- package/dist/ontologyHelpers.js.map +1 -1
- package/dist/{ontologyRegistry-B67rPJ16.d.ts → ontologyRegistry-CljS-ENv.d.ts} +2 -2
- package/dist/ontologyRegistry.d.ts +1 -1
- package/dist/ontologyRegistry.js +34 -6
- package/dist/ontologyRegistry.js.map +1 -1
- package/dist/{projectionReconciliation-jww2fBI0.d.ts → projectionReconciliation-DnrSgHSQ.d.ts} +4 -4
- package/dist/projectionReconciliation.d.ts +1 -1
- package/dist/projectionReconciliation.js +57 -10
- package/dist/projectionReconciliation.js.map +1 -1
- package/dist/{projectionStaleness-CmdbpjVK.d.ts → projectionStaleness-C8ImQ2zP.d.ts} +17 -17
- package/dist/projectionStaleness.d.ts +1 -1
- package/dist/projectionStaleness.js +8 -2
- package/dist/projectionStaleness.js.map +1 -1
- package/dist/proof-attestation.json +1 -1
- package/dist/{questionEvidenceLinks-DFlyPpAj.d.ts → questionEvidenceLinks-_nPRa-LY.d.ts} +10 -10
- package/dist/questionEvidenceLinks.d.ts +1 -1
- package/dist/questionEvidenceLinks.js +564 -347
- package/dist/questionEvidenceLinks.js.map +1 -1
- package/dist/{resolverTypes-CC8Ea2E2.d.ts → resolverTypes-BOXPxLET.d.ts} +8 -7
- package/dist/resolverTypes.d.ts +4 -2
- package/dist/{resolvers-Br1a6eLV.d.ts → resolvers-B1TIBmRO.d.ts} +3 -1
- package/dist/resolvers.d.ts +5 -3
- package/dist/resolvers.js +121 -77
- package/dist/resolvers.js.map +1 -1
- package/dist/scopeResolverCompat.d.ts +10 -7
- package/dist/scopeResolverCompat.js +106 -123
- package/dist/scopeResolverCompat.js.map +1 -1
- package/dist/{text-matching-DNg4M5Wd.d.ts → text-matching-DzFooju6.d.ts} +7 -7
- package/dist/text-matching.d.ts +1 -1
- package/dist/topicOntologyResolver.d.ts +22 -21
- package/dist/topicOntologyResolver.js +54 -32
- package/dist/topicOntologyResolver.js.map +1 -1
- package/dist/topicProjectOverlay.d.ts +30 -20
- package/dist/topicProjectOverlay.js +120 -76
- package/dist/topicProjectOverlay.js.map +1 -1
- package/dist/{topicScope-7zhyeGl7.d.ts → topicScope-DJVa0mLa.d.ts} +22 -7
- package/dist/topicScope.d.ts +3 -1
- package/dist/topicScope.js +104 -119
- package/dist/topicScope.js.map +1 -1
- package/dist/workflowBridge.d.ts +26 -15
- package/dist/workflowBridge.js +140 -144
- package/dist/workflowBridge.js.map +1 -1
- package/dist/workspaceIsolation.d.ts +14 -12
- package/dist/workspaceIsolation.js +108 -122
- package/dist/workspaceIsolation.js.map +1 -1
- package/package.json +4 -4
- package/dist/edges/dependsOn.js.map +0 -1
- package/dist/edges/derivedFrom.js.map +0 -1
- package/dist/edges/propagationTypes.js.map +0 -1
- package/dist/evaluators/lintCheckerEvaluator.d.ts +0 -11
- package/dist/evaluators/lintCheckerEvaluator.js.map +0 -1
- package/dist/evaluators/sentryCheckerEvaluator.js.map +0 -1
- package/dist/evaluators/testRunnerEvaluator.js.map +0 -1
- package/dist/evaluators/tscCheckerEvaluator.d.ts +0 -11
- package/dist/evaluators/tscCheckerEvaluator.js.map +0 -1
- package/dist/{epistemicQuestions-bwHd2FWE.d.ts → epistemicQuestions-Do1fhYm5.d.ts} +4 -4
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
import { v } from 'convex/values';
|
|
2
1
|
import { checkScopeAccess } from '@lucern/access-control/access';
|
|
3
2
|
import { permissiveReturn } from '@lucern/contracts/schema-helpers/validators';
|
|
4
|
-
import {
|
|
3
|
+
import { v } from 'convex/values';
|
|
4
|
+
import { unsafeConvexAnyApi } from '@lucern/contracts/convex/unsafeAnyApi';
|
|
5
|
+
import { componentsGeneric, mutationGeneric, queryGeneric } from 'convex/server';
|
|
5
6
|
|
|
6
7
|
// src/epistemicQuestions.conviction.ts
|
|
7
|
-
var
|
|
8
|
+
var unsafeApi = unsafeConvexAnyApi(
|
|
9
|
+
"graph-primitives top-level module bundle lacks a committed Convex _generated/api surface"
|
|
10
|
+
);
|
|
11
|
+
var api = unsafeApi;
|
|
8
12
|
componentsGeneric();
|
|
9
|
-
var internal =
|
|
13
|
+
var internal = unsafeApi;
|
|
10
14
|
var mutation = mutationGeneric;
|
|
11
15
|
var query = queryGeneric;
|
|
12
16
|
|
|
@@ -59,6 +63,10 @@ function readStringArray(value) {
|
|
|
59
63
|
function readMetadata(topic) {
|
|
60
64
|
return topic.metadata && typeof topic.metadata === "object" ? topic.metadata : {};
|
|
61
65
|
}
|
|
66
|
+
function omitMetadataKey(metadata, key) {
|
|
67
|
+
const { [key]: _omitted, ...rest } = metadata;
|
|
68
|
+
return rest;
|
|
69
|
+
}
|
|
62
70
|
function readLegacyProjectId(value) {
|
|
63
71
|
if (!value) {
|
|
64
72
|
return;
|
|
@@ -139,9 +147,12 @@ async function resolveTopicDoc(ctx, scopeId) {
|
|
|
139
147
|
);
|
|
140
148
|
}
|
|
141
149
|
try {
|
|
142
|
-
const topic = await ctx.runQuery(
|
|
143
|
-
|
|
144
|
-
|
|
150
|
+
const topic = await ctx.runQuery(
|
|
151
|
+
api.topics.getByLegacyScopeId,
|
|
152
|
+
{
|
|
153
|
+
projectId: String(scopeId)
|
|
154
|
+
}
|
|
155
|
+
);
|
|
145
156
|
if (topic?.name !== void 0 && topic?.type !== void 0) {
|
|
146
157
|
return topic;
|
|
147
158
|
}
|
|
@@ -161,8 +172,18 @@ function materializeTopicProjectOverlay(topic, idMode = "legacy") {
|
|
|
161
172
|
const outwardId = idMode === "topic" ? topicId : storageProjectId;
|
|
162
173
|
const visibility = coerceVisibility(topic.visibility) || coerceVisibility(metadata.visibility) || "private";
|
|
163
174
|
const status = coerceStatus(topic.status) || coerceStatus(metadata.status) || "active";
|
|
164
|
-
|
|
165
|
-
|
|
175
|
+
let createdAt = 0;
|
|
176
|
+
if (typeof topic.createdAt === "number") {
|
|
177
|
+
createdAt = topic.createdAt;
|
|
178
|
+
} else if (typeof topic._creationTime === "number") {
|
|
179
|
+
createdAt = topic._creationTime;
|
|
180
|
+
}
|
|
181
|
+
let updatedAt = createdAt;
|
|
182
|
+
if (typeof topic.updatedAt === "number") {
|
|
183
|
+
updatedAt = topic.updatedAt;
|
|
184
|
+
} else if (typeof metadata.updatedAt === "number") {
|
|
185
|
+
updatedAt = metadata.updatedAt;
|
|
186
|
+
}
|
|
166
187
|
return {
|
|
167
188
|
...metadata,
|
|
168
189
|
_id: outwardId,
|
|
@@ -231,90 +252,113 @@ async function patchTopicProjectOverlay(ctx, scopeId, value) {
|
|
|
231
252
|
if (!topic) {
|
|
232
253
|
return null;
|
|
233
254
|
}
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
255
|
+
const plan = buildTopicProjectOverlayPatchPlan(topic, value);
|
|
256
|
+
await applyTopicProjectOverlayPatch(ctx, topic, plan);
|
|
257
|
+
return materializeTopicProjectOverlay({
|
|
258
|
+
...topic,
|
|
259
|
+
...plan.patch,
|
|
260
|
+
metadata: plan.nextMetadata
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
function buildTopicProjectOverlayPatchPlan(topic, value) {
|
|
264
|
+
const plan = {
|
|
265
|
+
nextMetadata: { ...readMetadata(topic) },
|
|
266
|
+
patch: {},
|
|
267
|
+
topicUpdateArgs: {
|
|
268
|
+
id: String(topic._id)
|
|
269
|
+
}
|
|
238
270
|
};
|
|
239
271
|
for (const [key, rawValue] of Object.entries(value)) {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
272
|
+
applyTopicProjectOverlayPatchEntry(plan, key, rawValue);
|
|
273
|
+
}
|
|
274
|
+
plan.patch.updatedAt = Date.now();
|
|
275
|
+
plan.patch.metadata = plan.nextMetadata;
|
|
276
|
+
plan.topicUpdateArgs.metadata = plan.nextMetadata;
|
|
277
|
+
return plan;
|
|
278
|
+
}
|
|
279
|
+
function applyTopicProjectOverlayPatchEntry(plan, key, rawValue) {
|
|
280
|
+
switch (key) {
|
|
281
|
+
case "_id":
|
|
282
|
+
case "projectId":
|
|
283
|
+
case "topicId":
|
|
284
|
+
case "legacyProjectId":
|
|
285
|
+
case "storageProjectId":
|
|
286
|
+
case "updatedAt":
|
|
287
|
+
case "createdAt":
|
|
288
|
+
return;
|
|
289
|
+
case "name":
|
|
290
|
+
case "description":
|
|
291
|
+
plan.patch[key] = rawValue;
|
|
292
|
+
plan.topicUpdateArgs[key] = rawValue;
|
|
293
|
+
return;
|
|
294
|
+
case "tenantId":
|
|
295
|
+
case "workspaceId":
|
|
296
|
+
case "ownerId":
|
|
297
|
+
throw new Error(
|
|
298
|
+
`patchTopicProjectOverlay cannot mutate ${key} via component-owned topics`
|
|
299
|
+
);
|
|
300
|
+
case "status":
|
|
301
|
+
applyTopicStatusPatch(plan, rawValue);
|
|
302
|
+
return;
|
|
303
|
+
case "visibility":
|
|
304
|
+
applyTopicVisibilityPatch(plan, rawValue);
|
|
305
|
+
return;
|
|
306
|
+
case "type":
|
|
307
|
+
applyTopicProjectTypePatch(plan, rawValue);
|
|
308
|
+
return;
|
|
309
|
+
default:
|
|
310
|
+
applyTopicMetadataPatch(plan, key, rawValue);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
function applyTopicStatusPatch(plan, rawValue) {
|
|
314
|
+
const status = coerceStatus(rawValue);
|
|
315
|
+
if (status) {
|
|
316
|
+
plan.patch.status = status;
|
|
317
|
+
plan.topicUpdateArgs.status = status;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
function applyTopicVisibilityPatch(plan, rawValue) {
|
|
321
|
+
const visibility = coerceVisibility(rawValue);
|
|
322
|
+
if (visibility) {
|
|
323
|
+
plan.patch.visibility = visibility;
|
|
324
|
+
plan.topicUpdateArgs.visibility = visibility;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
function applyTopicProjectTypePatch(plan, rawValue) {
|
|
328
|
+
const projectType = readNonEmptyString(rawValue);
|
|
329
|
+
if (projectType) {
|
|
330
|
+
plan.nextMetadata.projectType = projectType;
|
|
331
|
+
return;
|
|
293
332
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
333
|
+
plan.nextMetadata = omitMetadataKey(plan.nextMetadata, "projectType");
|
|
334
|
+
}
|
|
335
|
+
function applyTopicMetadataPatch(plan, key, rawValue) {
|
|
336
|
+
if (rawValue === void 0) {
|
|
337
|
+
plan.nextMetadata = omitMetadataKey(plan.nextMetadata, key);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
plan.nextMetadata[key] = rawValue;
|
|
341
|
+
}
|
|
342
|
+
async function applyTopicProjectOverlayPatch(ctx, topic, plan) {
|
|
297
343
|
if (typeof ctx.runMutation === "function") {
|
|
298
344
|
try {
|
|
299
|
-
await ctx.runMutation(api.topics.update, topicUpdateArgs);
|
|
345
|
+
await ctx.runMutation(api.topics.update, plan.topicUpdateArgs);
|
|
300
346
|
} catch (error) {
|
|
301
|
-
if (!
|
|
347
|
+
if (!canPatchTopicViaLocalDb(ctx, error)) {
|
|
302
348
|
throw error;
|
|
303
349
|
}
|
|
304
|
-
await ctx.db.patch(
|
|
350
|
+
await ctx.db.patch(topic._id, plan.patch);
|
|
305
351
|
}
|
|
306
352
|
} else if (ctx?.db && typeof ctx.db.patch === "function") {
|
|
307
|
-
await ctx.db.patch(
|
|
353
|
+
await ctx.db.patch(topic._id, plan.patch);
|
|
308
354
|
} else {
|
|
309
355
|
throw new Error(
|
|
310
356
|
"Cannot patch topic without component adapter (ctx.runMutation unavailable)"
|
|
311
357
|
);
|
|
312
358
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
metadata: nextMetadata
|
|
317
|
-
});
|
|
359
|
+
}
|
|
360
|
+
function canPatchTopicViaLocalDb(ctx, error) {
|
|
361
|
+
return isMissingLucernChildComponentError(error) && Boolean(ctx?.db) && typeof ctx.db?.patch === "function";
|
|
318
362
|
}
|
|
319
363
|
|
|
320
364
|
// src/resolvers.ts
|
|
@@ -342,7 +386,7 @@ async function patchProjectWithTolerance(ctx, projectId, value) {
|
|
|
342
386
|
try {
|
|
343
387
|
await patchTopicProjectOverlay(ctx, projectId, value);
|
|
344
388
|
} catch (error) {
|
|
345
|
-
if (!isAdvisoryTopicPatch(value)
|
|
389
|
+
if (!(isAdvisoryTopicPatch(value) && isMissingLucernChildComponentError2(error))) {
|
|
346
390
|
throw error;
|
|
347
391
|
}
|
|
348
392
|
console.warn(
|
|
@@ -409,13 +453,15 @@ function asMappedProjectId(topic) {
|
|
|
409
453
|
if (!topic) {
|
|
410
454
|
return;
|
|
411
455
|
}
|
|
412
|
-
const directLegacyProjectId = normalizeScopeValue(
|
|
456
|
+
const directLegacyProjectId = normalizeScopeValue(
|
|
457
|
+
topic[LEGACY_SCOPE_FIELD2]
|
|
458
|
+
);
|
|
413
459
|
if (directLegacyProjectId) {
|
|
414
460
|
return directLegacyProjectId;
|
|
415
461
|
}
|
|
416
462
|
const metadata = topic.metadata || {};
|
|
417
463
|
const candidate = metadata[LEGACY_SCOPE_FIELD2] || metadata.legacyProjectId || metadata.projectId || metadata.scopeProjectId;
|
|
418
|
-
return candidate ? candidate : void 0;
|
|
464
|
+
return typeof candidate === "string" ? normalizeScopeValue(candidate) : void 0;
|
|
419
465
|
}
|
|
420
466
|
function normalizeScopeValue(value) {
|
|
421
467
|
if (typeof value !== "string") {
|
|
@@ -440,8 +486,9 @@ function pickPrimaryTopic(candidates) {
|
|
|
440
486
|
})[0];
|
|
441
487
|
}
|
|
442
488
|
async function findTopicsByScopeAlias(ctx, scopeId) {
|
|
489
|
+
const query2 = ctx.db.query("topics");
|
|
443
490
|
try {
|
|
444
|
-
return await
|
|
491
|
+
return await query2.withIndex(
|
|
445
492
|
"by_graph_scope_project",
|
|
446
493
|
(q) => q.eq(LEGACY_SCOPE_FIELD2, scopeId)
|
|
447
494
|
).collect();
|
|
@@ -453,7 +500,7 @@ async function findTopicsByScopeAlias(ctx, scopeId) {
|
|
|
453
500
|
scopeId
|
|
454
501
|
}
|
|
455
502
|
);
|
|
456
|
-
const topics = await
|
|
503
|
+
const topics = await query2.collect();
|
|
457
504
|
return topics.filter((topic) => {
|
|
458
505
|
const normalizedGlobalId = normalizeScopeValue(topic.globalId);
|
|
459
506
|
const mappedProjectId = asMappedProjectId(topic);
|
|
@@ -509,137 +556,115 @@ async function resolveInheritedWorkspaceScope(ctx, topic) {
|
|
|
509
556
|
let current = topic;
|
|
510
557
|
for (let i = 0; i < MAX_DEPTH && current?.parentTopicId; i++) {
|
|
511
558
|
current = await ctx.db.get(current.parentTopicId);
|
|
512
|
-
if (!current)
|
|
559
|
+
if (!current) {
|
|
560
|
+
break;
|
|
561
|
+
}
|
|
513
562
|
if (!tenantId) {
|
|
514
563
|
tenantId = normalizeScopeValue(current.tenantId);
|
|
515
564
|
}
|
|
516
565
|
if (!workspaceId) {
|
|
517
566
|
workspaceId = normalizeScopeValue(current.workspaceId);
|
|
518
567
|
}
|
|
519
|
-
if (tenantId && workspaceId)
|
|
568
|
+
if (tenantId && workspaceId) {
|
|
569
|
+
break;
|
|
570
|
+
}
|
|
520
571
|
}
|
|
521
572
|
return { tenantId, workspaceId };
|
|
522
573
|
}
|
|
523
574
|
async function resolveTopicProjectScope(ctx, args) {
|
|
524
575
|
if (args.topicId) {
|
|
525
|
-
|
|
526
|
-
try {
|
|
527
|
-
topic = await ctx.db.get(
|
|
528
|
-
args.topicId
|
|
529
|
-
);
|
|
530
|
-
} catch (error) {
|
|
531
|
-
debugGraphPrimitiveFallback(
|
|
532
|
-
"[topicScope] Failed to load topic by direct id",
|
|
533
|
-
{
|
|
534
|
-
error,
|
|
535
|
-
topicId: args.topicId
|
|
536
|
-
}
|
|
537
|
-
);
|
|
538
|
-
}
|
|
539
|
-
if (!topic) {
|
|
540
|
-
topic = await tryResolveHostTopicById(ctx, String(args.topicId));
|
|
541
|
-
}
|
|
542
|
-
if (!topic) {
|
|
543
|
-
topic = pickPrimaryTopic(
|
|
544
|
-
await findTopicsByScopeAlias(ctx, String(args.topicId))
|
|
545
|
-
) ?? null;
|
|
546
|
-
}
|
|
547
|
-
if (!topic) {
|
|
548
|
-
const nodeScope = await resolveTopicNodeScopeOrNull(
|
|
549
|
-
ctx,
|
|
550
|
-
String(args.topicId)
|
|
551
|
-
);
|
|
552
|
-
if (nodeScope) {
|
|
553
|
-
return nodeScope;
|
|
554
|
-
}
|
|
555
|
-
throw new Error(`Topic not found: ${String(args.topicId)}`);
|
|
556
|
-
}
|
|
557
|
-
const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
|
|
558
|
-
const mapped = asMappedProjectId(topic);
|
|
559
|
-
if (mapped) {
|
|
560
|
-
return {
|
|
561
|
-
topicId: topic._id,
|
|
562
|
-
projectId: mapped,
|
|
563
|
-
tenantId: inherited.tenantId,
|
|
564
|
-
workspaceId: inherited.workspaceId,
|
|
565
|
-
source: "topic"
|
|
566
|
-
};
|
|
567
|
-
}
|
|
568
|
-
return {
|
|
569
|
-
topicId: topic._id,
|
|
570
|
-
tenantId: inherited.tenantId,
|
|
571
|
-
workspaceId: inherited.workspaceId,
|
|
572
|
-
source: "topic"
|
|
573
|
-
};
|
|
576
|
+
return await resolveScopeFromTopicId(ctx, args.topicId);
|
|
574
577
|
}
|
|
575
578
|
if (args.projectId) {
|
|
576
|
-
|
|
577
|
-
try {
|
|
578
|
-
directTopic = await ctx.db.get(
|
|
579
|
-
args.projectId
|
|
580
|
-
);
|
|
581
|
-
} catch (error) {
|
|
582
|
-
debugGraphPrimitiveFallback(
|
|
583
|
-
"[topicScope] Failed to load direct project topic",
|
|
584
|
-
{
|
|
585
|
-
error,
|
|
586
|
-
projectId: args.projectId
|
|
587
|
-
}
|
|
588
|
-
);
|
|
589
|
-
}
|
|
590
|
-
if (directTopic) {
|
|
591
|
-
const inherited = await resolveInheritedWorkspaceScope(ctx, directTopic);
|
|
592
|
-
const mapped = asMappedProjectId(directTopic);
|
|
593
|
-
return {
|
|
594
|
-
topicId: directTopic._id,
|
|
595
|
-
projectId: mapped ?? args.projectId,
|
|
596
|
-
tenantId: inherited.tenantId,
|
|
597
|
-
workspaceId: inherited.workspaceId,
|
|
598
|
-
source: "topic_inferred"
|
|
599
|
-
};
|
|
600
|
-
}
|
|
601
|
-
directTopic = await tryResolveHostTopicByLegacyScope(ctx, args.projectId);
|
|
602
|
-
if (directTopic) {
|
|
603
|
-
const inherited = await resolveInheritedWorkspaceScope(ctx, directTopic);
|
|
604
|
-
const mapped = asMappedProjectId(directTopic);
|
|
605
|
-
return {
|
|
606
|
-
topicId: directTopic._id,
|
|
607
|
-
projectId: mapped ?? args.projectId,
|
|
608
|
-
tenantId: inherited.tenantId,
|
|
609
|
-
workspaceId: inherited.workspaceId,
|
|
610
|
-
source: "topic_inferred"
|
|
611
|
-
};
|
|
612
|
-
}
|
|
613
|
-
const topics = await findTopicsByScopeAlias(ctx, args.projectId);
|
|
614
|
-
const primary = pickPrimaryTopic(topics);
|
|
615
|
-
if (primary) {
|
|
616
|
-
const inherited = await resolveInheritedWorkspaceScope(ctx, primary);
|
|
617
|
-
return {
|
|
618
|
-
topicId: primary._id,
|
|
619
|
-
projectId: args.projectId,
|
|
620
|
-
tenantId: inherited.tenantId,
|
|
621
|
-
workspaceId: inherited.workspaceId,
|
|
622
|
-
source: "project_mapped_topic"
|
|
623
|
-
};
|
|
624
|
-
}
|
|
625
|
-
const nodeScope = await resolveTopicNodeScopeOrNull(
|
|
626
|
-
ctx,
|
|
627
|
-
String(args.projectId)
|
|
628
|
-
);
|
|
629
|
-
if (nodeScope) {
|
|
630
|
-
return {
|
|
631
|
-
...nodeScope,
|
|
632
|
-
projectId: nodeScope.projectId ?? String(args.projectId)
|
|
633
|
-
};
|
|
634
|
-
}
|
|
635
|
-
throw new Error(
|
|
636
|
-
`Legacy project scope ${String(args.projectId)} has no mapped topic.`
|
|
637
|
-
);
|
|
579
|
+
return await resolveScopeFromLegacyProjectId(ctx, args.projectId);
|
|
638
580
|
}
|
|
639
581
|
throw new Error(
|
|
640
582
|
"Missing scope: provide topicId (preferred) or legacy projectId alias."
|
|
641
583
|
);
|
|
642
584
|
}
|
|
585
|
+
async function resolveScopeFromTopicId(ctx, topicId) {
|
|
586
|
+
const topic = await resolveTopicDocFromTopicId(ctx, topicId);
|
|
587
|
+
if (topic) {
|
|
588
|
+
return await buildTopicScope(ctx, topic, "topic");
|
|
589
|
+
}
|
|
590
|
+
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, String(topicId));
|
|
591
|
+
if (nodeScope) {
|
|
592
|
+
return nodeScope;
|
|
593
|
+
}
|
|
594
|
+
throw new Error(`Topic not found: ${String(topicId)}`);
|
|
595
|
+
}
|
|
596
|
+
async function resolveTopicDocFromTopicId(ctx, topicId) {
|
|
597
|
+
const direct = await tryReadTopicDoc(ctx, topicId, {
|
|
598
|
+
failureLog: "[topicScope] Failed to load topic by direct id",
|
|
599
|
+
idLogKey: "topicId"
|
|
600
|
+
});
|
|
601
|
+
if (direct) {
|
|
602
|
+
return direct;
|
|
603
|
+
}
|
|
604
|
+
const hostTopic = await tryResolveHostTopicById(ctx, String(topicId));
|
|
605
|
+
if (hostTopic) {
|
|
606
|
+
return hostTopic;
|
|
607
|
+
}
|
|
608
|
+
return pickPrimaryTopic(await findTopicsByScopeAlias(ctx, String(topicId))) ?? null;
|
|
609
|
+
}
|
|
610
|
+
async function resolveScopeFromLegacyProjectId(ctx, legacyProjectId) {
|
|
611
|
+
const directTopic = await resolveDirectLegacyProjectTopic(
|
|
612
|
+
ctx,
|
|
613
|
+
legacyProjectId
|
|
614
|
+
);
|
|
615
|
+
if (directTopic) {
|
|
616
|
+
return await buildTopicScope(ctx, directTopic, "topic_inferred", {
|
|
617
|
+
fallbackProjectId: legacyProjectId
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
const primary = pickPrimaryTopic(
|
|
621
|
+
await findTopicsByScopeAlias(ctx, legacyProjectId)
|
|
622
|
+
);
|
|
623
|
+
if (primary) {
|
|
624
|
+
return await buildTopicScope(ctx, primary, "project_mapped_topic", {
|
|
625
|
+
fallbackProjectId: legacyProjectId
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, legacyProjectId);
|
|
629
|
+
if (nodeScope) {
|
|
630
|
+
return {
|
|
631
|
+
...nodeScope,
|
|
632
|
+
projectId: nodeScope.projectId ?? legacyProjectId
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
throw new Error(
|
|
636
|
+
`Legacy project scope ${legacyProjectId} has no mapped topic.`
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
async function resolveDirectLegacyProjectTopic(ctx, legacyProjectId) {
|
|
640
|
+
const directTopic = await tryReadTopicDoc(ctx, legacyProjectId, {
|
|
641
|
+
failureLog: "[topicScope] Failed to load direct project topic",
|
|
642
|
+
idLogKey: "projectId"
|
|
643
|
+
});
|
|
644
|
+
return directTopic ?? tryResolveHostTopicByLegacyScope(ctx, legacyProjectId);
|
|
645
|
+
}
|
|
646
|
+
async function tryReadTopicDoc(ctx, id, log) {
|
|
647
|
+
try {
|
|
648
|
+
return await ctx.db.get(id);
|
|
649
|
+
} catch (error) {
|
|
650
|
+
debugGraphPrimitiveFallback(log.failureLog, {
|
|
651
|
+
error,
|
|
652
|
+
[log.idLogKey]: id
|
|
653
|
+
});
|
|
654
|
+
return null;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
async function buildTopicScope(ctx, topic, source, options = {}) {
|
|
658
|
+
const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
|
|
659
|
+
const mapped = asMappedProjectId(topic);
|
|
660
|
+
return {
|
|
661
|
+
topicId: topic._id,
|
|
662
|
+
...mapped || options.fallbackProjectId ? { projectId: mapped ?? options.fallbackProjectId } : {},
|
|
663
|
+
tenantId: inherited.tenantId,
|
|
664
|
+
workspaceId: inherited.workspaceId,
|
|
665
|
+
source
|
|
666
|
+
};
|
|
667
|
+
}
|
|
643
668
|
({
|
|
644
669
|
projectId: v.optional(v.string()),
|
|
645
670
|
topicId: v.optional(v.string())
|
|
@@ -649,9 +674,14 @@ async function resolveTopicProjectScope(ctx, args) {
|
|
|
649
674
|
function generateContentHash(text) {
|
|
650
675
|
const content = `question:${text.trim().toLowerCase().replace(/\s+/g, " ")}`;
|
|
651
676
|
let hash = 5381;
|
|
677
|
+
const maxSigned32Bit = 2147483647;
|
|
678
|
+
const uint32Size = 4294967296;
|
|
652
679
|
for (let i = 0; i < content.length; i++) {
|
|
653
|
-
hash = (hash
|
|
654
|
-
hash
|
|
680
|
+
hash = Math.imul(hash, 33) + content.charCodeAt(i);
|
|
681
|
+
hash %= uint32Size;
|
|
682
|
+
if (hash > maxSigned32Bit) {
|
|
683
|
+
hash -= uint32Size;
|
|
684
|
+
}
|
|
655
685
|
}
|
|
656
686
|
return Math.abs(hash).toString(16).padStart(8, "0");
|
|
657
687
|
}
|
|
@@ -659,9 +689,10 @@ function buildTestsEdgeGlobalId(fromGlobalId, toGlobalId) {
|
|
|
659
689
|
return `edge-${fromGlobalId}-tests-${toGlobalId}`;
|
|
660
690
|
}
|
|
661
691
|
async function markProjectGraphDirty(ctx, projectId, topicId) {
|
|
692
|
+
const markCacheStaleByTopic = internal.graphAnalysisCache.markCacheStaleByTopic;
|
|
662
693
|
const normalizedProjectId = typeof projectId === "string" && projectId.trim().length > 0 ? projectId : void 0;
|
|
663
694
|
const normalizedTopicId = typeof topicId === "string" && topicId.trim().length > 0 ? topicId : void 0;
|
|
664
|
-
if (!normalizedProjectId
|
|
695
|
+
if (!(normalizedProjectId || normalizedTopicId)) {
|
|
665
696
|
return;
|
|
666
697
|
}
|
|
667
698
|
if (normalizedProjectId) {
|
|
@@ -674,17 +705,17 @@ async function markProjectGraphDirty(ctx, projectId, topicId) {
|
|
|
674
705
|
);
|
|
675
706
|
}
|
|
676
707
|
if (normalizedTopicId) {
|
|
677
|
-
await ctx.scheduler.runAfter(
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
708
|
+
await ctx.scheduler.runAfter(0, markCacheStaleByTopic, {
|
|
709
|
+
topicId: normalizedTopicId
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
const resolvedProjectId = normalizedTopicId ?? normalizedProjectId;
|
|
713
|
+
if (!resolvedProjectId) {
|
|
714
|
+
return;
|
|
684
715
|
}
|
|
685
716
|
await resolveGraphPrimitivesAppResolvers().patchProject(
|
|
686
717
|
ctx,
|
|
687
|
-
|
|
718
|
+
resolvedProjectId,
|
|
688
719
|
{
|
|
689
720
|
lastActivityAt: Date.now()
|
|
690
721
|
}
|
|
@@ -716,7 +747,7 @@ function logQuestionFallback(message, error, context) {
|
|
|
716
747
|
});
|
|
717
748
|
}
|
|
718
749
|
async function resolveQuestionScopeOrNull(ctx, args) {
|
|
719
|
-
if (!args.projectId
|
|
750
|
+
if (!(args.projectId || args.topicId)) {
|
|
720
751
|
return null;
|
|
721
752
|
}
|
|
722
753
|
try {
|
|
@@ -758,6 +789,362 @@ function buildLinkedWorktreeMetadata(linkedWorktreeId) {
|
|
|
758
789
|
}
|
|
759
790
|
|
|
760
791
|
// src/epistemicQuestions.conviction.ts
|
|
792
|
+
function readOptionalString(value) {
|
|
793
|
+
return typeof value === "string" && value.trim().length > 0 ? value : void 0;
|
|
794
|
+
}
|
|
795
|
+
function readConvexId(value) {
|
|
796
|
+
const normalized = readOptionalString(value);
|
|
797
|
+
return normalized ? normalized : null;
|
|
798
|
+
}
|
|
799
|
+
function readRecord(value) {
|
|
800
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
801
|
+
}
|
|
802
|
+
function readStringArray2(value) {
|
|
803
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string") ? value : [];
|
|
804
|
+
}
|
|
805
|
+
function readQuestionNodeRow(value) {
|
|
806
|
+
const record = readRecord(value);
|
|
807
|
+
if (!record) {
|
|
808
|
+
return null;
|
|
809
|
+
}
|
|
810
|
+
const id = readConvexId(record._id);
|
|
811
|
+
const nodeType = readOptionalString(record.nodeType);
|
|
812
|
+
if (!(id && nodeType)) {
|
|
813
|
+
return null;
|
|
814
|
+
}
|
|
815
|
+
const metadata = readRecord(record.metadata) ?? {};
|
|
816
|
+
const node = { _id: id, metadata, nodeType };
|
|
817
|
+
const canonicalText = readOptionalString(record.canonicalText);
|
|
818
|
+
if (canonicalText !== void 0) {
|
|
819
|
+
node.canonicalText = canonicalText;
|
|
820
|
+
}
|
|
821
|
+
const globalId = readOptionalString(record.globalId);
|
|
822
|
+
if (globalId !== void 0) {
|
|
823
|
+
node.globalId = globalId;
|
|
824
|
+
}
|
|
825
|
+
const projectId = readOptionalString(record.projectId);
|
|
826
|
+
if (projectId !== void 0) {
|
|
827
|
+
node.projectId = projectId;
|
|
828
|
+
}
|
|
829
|
+
const sourceType = readOptionalString(record.sourceType);
|
|
830
|
+
if (sourceType !== void 0) {
|
|
831
|
+
node.sourceType = sourceType;
|
|
832
|
+
}
|
|
833
|
+
const topicId = readOptionalString(record.topicId);
|
|
834
|
+
if (topicId !== void 0) {
|
|
835
|
+
node.topicId = topicId;
|
|
836
|
+
}
|
|
837
|
+
return node;
|
|
838
|
+
}
|
|
839
|
+
function requireQuestionNode(value) {
|
|
840
|
+
const node = readQuestionNodeRow(value);
|
|
841
|
+
if (node?.nodeType !== "question") {
|
|
842
|
+
throw new Error("Question not found");
|
|
843
|
+
}
|
|
844
|
+
return node;
|
|
845
|
+
}
|
|
846
|
+
function readBeliefNode(value) {
|
|
847
|
+
const node = readQuestionNodeRow(value);
|
|
848
|
+
return node?.nodeType === "belief" ? node : null;
|
|
849
|
+
}
|
|
850
|
+
function readEvidenceNode(value) {
|
|
851
|
+
const node = readQuestionNodeRow(value);
|
|
852
|
+
return node?.nodeType === "evidence" ? node : null;
|
|
853
|
+
}
|
|
854
|
+
function readQuestionEvidenceLinkRow(value) {
|
|
855
|
+
const record = readRecord(value);
|
|
856
|
+
if (!record) {
|
|
857
|
+
return null;
|
|
858
|
+
}
|
|
859
|
+
const id = readConvexId(record._id);
|
|
860
|
+
if (!id) {
|
|
861
|
+
return null;
|
|
862
|
+
}
|
|
863
|
+
const link = { _id: id };
|
|
864
|
+
const insightId = readConvexId(record.insightId) ?? readOptionalString(record.insightId);
|
|
865
|
+
if (insightId !== void 0 && insightId !== null) {
|
|
866
|
+
link.insightId = insightId;
|
|
867
|
+
}
|
|
868
|
+
return link;
|
|
869
|
+
}
|
|
870
|
+
function readQuestionEdgeRow(value) {
|
|
871
|
+
const record = readRecord(value);
|
|
872
|
+
if (!record) {
|
|
873
|
+
return null;
|
|
874
|
+
}
|
|
875
|
+
const id = readConvexId(record._id);
|
|
876
|
+
if (!id) {
|
|
877
|
+
return null;
|
|
878
|
+
}
|
|
879
|
+
const edge = { _id: id };
|
|
880
|
+
const edgeType = readOptionalString(record.edgeType);
|
|
881
|
+
if (edgeType !== void 0) {
|
|
882
|
+
edge.edgeType = edgeType;
|
|
883
|
+
}
|
|
884
|
+
const globalId = readOptionalString(record.globalId);
|
|
885
|
+
if (globalId !== void 0) {
|
|
886
|
+
edge.globalId = globalId;
|
|
887
|
+
}
|
|
888
|
+
return edge;
|
|
889
|
+
}
|
|
890
|
+
function resolveConvictionCompleteness(explicit, conviction) {
|
|
891
|
+
if (explicit !== void 0) {
|
|
892
|
+
return explicit;
|
|
893
|
+
}
|
|
894
|
+
if (conviction >= 0.8) {
|
|
895
|
+
return "comprehensive";
|
|
896
|
+
}
|
|
897
|
+
if (conviction >= 0.6) {
|
|
898
|
+
return "sufficient";
|
|
899
|
+
}
|
|
900
|
+
if (conviction >= 0.3) {
|
|
901
|
+
return "partial";
|
|
902
|
+
}
|
|
903
|
+
return "unanswered";
|
|
904
|
+
}
|
|
905
|
+
function buildFinalizedConvictionMetadata(args, meta, now, clampedConviction) {
|
|
906
|
+
return {
|
|
907
|
+
...meta,
|
|
908
|
+
convictionStage: "scored",
|
|
909
|
+
conviction: clampedConviction,
|
|
910
|
+
convictionRationale: args.convictionRationale,
|
|
911
|
+
convictionUpdatedAt: now,
|
|
912
|
+
convictionUpdatedBy: args.userId,
|
|
913
|
+
answer: args.answer,
|
|
914
|
+
answerStatus: "final",
|
|
915
|
+
questionStatus: args.answerCompleteness === "unanswerable" ? "blocked" : "answered",
|
|
916
|
+
answeredAt: now,
|
|
917
|
+
answeredBy: args.userId,
|
|
918
|
+
answerCompleteness: args.answerCompleteness,
|
|
919
|
+
whatWeNeed: args.whatWeNeed
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
function resolveLinkedBeliefNodeIdFromMetadata(meta) {
|
|
923
|
+
return readOptionalString(meta.linkedBeliefNodeId) || readOptionalString(meta.linkedBeliefId) || readOptionalString(meta.beliefId);
|
|
924
|
+
}
|
|
925
|
+
async function scheduleEvidenceCreationFromScoredQuestion(ctx, options) {
|
|
926
|
+
if (!(options.projectId || options.topicId)) {
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
const linkedBeliefId = resolveLinkedBeliefNodeIdFromMetadata(
|
|
930
|
+
options.questionMetadata
|
|
931
|
+
);
|
|
932
|
+
if (!linkedBeliefId) {
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
try {
|
|
936
|
+
await ctx.scheduler.runAfter(
|
|
937
|
+
0,
|
|
938
|
+
internal.epistemicQuestions.createEvidenceFromScoredQuestion,
|
|
939
|
+
{
|
|
940
|
+
questionNodeId: options.questionNodeId,
|
|
941
|
+
questionText: options.questionText,
|
|
942
|
+
answerText: options.answerText,
|
|
943
|
+
beliefId: linkedBeliefId,
|
|
944
|
+
relatedBeliefIds: options.relatedBeliefIds,
|
|
945
|
+
conviction: options.conviction,
|
|
946
|
+
rationale: options.rationale || "",
|
|
947
|
+
projectId: options.projectId,
|
|
948
|
+
topicId: options.topicId ? String(options.topicId) : void 0,
|
|
949
|
+
userId: options.userId
|
|
950
|
+
}
|
|
951
|
+
);
|
|
952
|
+
} catch (error) {
|
|
953
|
+
logQuestionFallback(
|
|
954
|
+
"[epistemicQuestions] Failed to schedule evidence creation from scored question",
|
|
955
|
+
error,
|
|
956
|
+
{
|
|
957
|
+
questionId: options.questionNodeId,
|
|
958
|
+
beliefId: linkedBeliefId,
|
|
959
|
+
projectId: options.projectId
|
|
960
|
+
}
|
|
961
|
+
);
|
|
962
|
+
console.error(
|
|
963
|
+
"[finalizeConviction] Failed to schedule evidence creation:",
|
|
964
|
+
error
|
|
965
|
+
);
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
async function resolveBeliefNodeForBeliefLink(ctx, beliefId) {
|
|
969
|
+
try {
|
|
970
|
+
return readBeliefNode(await ctx.db.get(beliefId));
|
|
971
|
+
} catch (error) {
|
|
972
|
+
debugGraphPrimitiveFallback(
|
|
973
|
+
"[epistemicQuestions] Failed to resolve belief node directly",
|
|
974
|
+
{
|
|
975
|
+
error: formatGraphPrimitiveError(error),
|
|
976
|
+
beliefId
|
|
977
|
+
}
|
|
978
|
+
);
|
|
979
|
+
try {
|
|
980
|
+
return readBeliefNode(await ctx.db.get(beliefId));
|
|
981
|
+
} catch (legacyError) {
|
|
982
|
+
debugGraphPrimitiveFallback(
|
|
983
|
+
"[epistemicQuestions] Failed to resolve legacy belief node",
|
|
984
|
+
{
|
|
985
|
+
error: formatGraphPrimitiveError(legacyError),
|
|
986
|
+
beliefId
|
|
987
|
+
}
|
|
988
|
+
);
|
|
989
|
+
return null;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
function buildBeliefLinkMetadata(questionMetadata, args) {
|
|
994
|
+
const currentBeliefs = readStringArray2(questionMetadata.relatedBeliefIds);
|
|
995
|
+
const beliefId = String(args.beliefId);
|
|
996
|
+
const relatedBeliefs = currentBeliefs.includes(beliefId) ? currentBeliefs : [...currentBeliefs, beliefId];
|
|
997
|
+
const metaUpdates = {
|
|
998
|
+
...questionMetadata,
|
|
999
|
+
relatedBeliefIds: relatedBeliefs
|
|
1000
|
+
};
|
|
1001
|
+
if (args.isPrimaryBelief || args.testType) {
|
|
1002
|
+
metaUpdates.linkedBeliefNodeId = args.beliefId;
|
|
1003
|
+
metaUpdates.linkedBeliefId = args.beliefId;
|
|
1004
|
+
metaUpdates.questionType = "belief_test";
|
|
1005
|
+
if (args.testType) {
|
|
1006
|
+
metaUpdates.testType = args.testType;
|
|
1007
|
+
}
|
|
1008
|
+
if (args.answerImpact) {
|
|
1009
|
+
metaUpdates.answerImpact = args.answerImpact;
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
return metaUpdates;
|
|
1013
|
+
}
|
|
1014
|
+
async function syncBeliefTestEdge(ctx, questionNode, beliefNode, args) {
|
|
1015
|
+
try {
|
|
1016
|
+
const beliefGlobalId = beliefNode.globalId;
|
|
1017
|
+
if (!(beliefGlobalId && questionNode.globalId)) {
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
await ctx.scheduler.runAfter(0, internal.neo4jSync.syncNodeToNeo4j, {
|
|
1021
|
+
nodeId: questionNode._id,
|
|
1022
|
+
operation: "upsert"
|
|
1023
|
+
});
|
|
1024
|
+
await ctx.scheduler.runAfter(100, internal.neo4jSync.syncNodeToNeo4j, {
|
|
1025
|
+
nodeId: beliefNode._id,
|
|
1026
|
+
operation: "upsert"
|
|
1027
|
+
});
|
|
1028
|
+
const edgeGlobalId = buildTestsEdgeGlobalId(
|
|
1029
|
+
questionNode.globalId,
|
|
1030
|
+
beliefGlobalId
|
|
1031
|
+
);
|
|
1032
|
+
await ctx.scheduler.runAfter(250, internal.neo4jEdgeAPI.createEdge, {
|
|
1033
|
+
globalId: edgeGlobalId,
|
|
1034
|
+
fromGlobalId: questionNode.globalId,
|
|
1035
|
+
toGlobalId: beliefGlobalId,
|
|
1036
|
+
edgeType: "tests",
|
|
1037
|
+
context: args.testType || "tests",
|
|
1038
|
+
topicId: normalizeQuestionTopicId(questionNode.topicId) ?? questionNode.projectId,
|
|
1039
|
+
createdBy: args.userId,
|
|
1040
|
+
fromNodeType: "question",
|
|
1041
|
+
toNodeType: "belief",
|
|
1042
|
+
fromLayer: "L3",
|
|
1043
|
+
toLayer: "L3"
|
|
1044
|
+
});
|
|
1045
|
+
} catch (error) {
|
|
1046
|
+
logQuestionFallback(
|
|
1047
|
+
"[epistemicQuestions] Failed to create tests edge",
|
|
1048
|
+
error,
|
|
1049
|
+
{
|
|
1050
|
+
questionId: questionNode._id,
|
|
1051
|
+
beliefId: args.beliefId
|
|
1052
|
+
}
|
|
1053
|
+
);
|
|
1054
|
+
console.error("[linkToBelief] Failed to create tests edge:", error);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
async function assertQuestionScopeAccess(ctx, questionNode, userId) {
|
|
1058
|
+
const questionScopeId = resolveQuestionScopeId({
|
|
1059
|
+
projectId: questionNode.projectId,
|
|
1060
|
+
topicId: normalizeQuestionTopicId(questionNode.topicId)
|
|
1061
|
+
});
|
|
1062
|
+
if (!questionScopeId) {
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
const hasAccess = await checkScopeAccess(ctx, questionScopeId, userId);
|
|
1066
|
+
if (!hasAccess) {
|
|
1067
|
+
throw new Error("Access denied");
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
async function removeQuestionEvidenceLinks(ctx, args) {
|
|
1071
|
+
try {
|
|
1072
|
+
const links = await ctx.db.query("questionEvidenceLinks").withIndex("by_questionId", (q) => q.eq("questionId", args.questionId)).collect();
|
|
1073
|
+
for (const rawLink of links) {
|
|
1074
|
+
const link = readQuestionEvidenceLinkRow(rawLink);
|
|
1075
|
+
if (link && String(link.insightId) === String(args.insightId)) {
|
|
1076
|
+
await ctx.db.delete(link._id);
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
} catch (error) {
|
|
1080
|
+
logQuestionFallback(
|
|
1081
|
+
"[epistemicQuestions] Failed to remove questionEvidenceLink",
|
|
1082
|
+
error,
|
|
1083
|
+
{
|
|
1084
|
+
questionId: args.questionId,
|
|
1085
|
+
insightId: args.insightId
|
|
1086
|
+
}
|
|
1087
|
+
);
|
|
1088
|
+
console.error(
|
|
1089
|
+
"[unlinkInsight] Failed to remove questionEvidenceLink:",
|
|
1090
|
+
error
|
|
1091
|
+
);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
function buildEvidenceQuestionEndpointPairs(evidenceNode, questionNode) {
|
|
1095
|
+
return [String(evidenceNode._id), evidenceNode.globalId].filter(
|
|
1096
|
+
(endpoint) => typeof endpoint === "string" && endpoint.length > 0
|
|
1097
|
+
).flatMap((fromNodeId) => [
|
|
1098
|
+
{ fromNodeId, toNodeId: String(questionNode._id) },
|
|
1099
|
+
...questionNode.globalId ? [{ fromNodeId, toNodeId: questionNode.globalId }] : []
|
|
1100
|
+
]);
|
|
1101
|
+
}
|
|
1102
|
+
async function collectEdgesForEndpointPairs(ctx, endpointPairs) {
|
|
1103
|
+
const edgeGroups = await Promise.all(
|
|
1104
|
+
endpointPairs.map(
|
|
1105
|
+
(endpoint) => ctx.db.query("epistemicEdges").withIndex(
|
|
1106
|
+
"by_from_to",
|
|
1107
|
+
(q) => q.eq("fromNodeId", endpoint.fromNodeId).eq("toNodeId", endpoint.toNodeId)
|
|
1108
|
+
).collect()
|
|
1109
|
+
)
|
|
1110
|
+
);
|
|
1111
|
+
return edgeGroups.flat();
|
|
1112
|
+
}
|
|
1113
|
+
async function removeDerivedEvidenceEdges(ctx, args) {
|
|
1114
|
+
try {
|
|
1115
|
+
const evidenceNode = readEvidenceNode(await ctx.db.get(args.insightId));
|
|
1116
|
+
if (!evidenceNode) {
|
|
1117
|
+
return;
|
|
1118
|
+
}
|
|
1119
|
+
const endpointPairs = buildEvidenceQuestionEndpointPairs(
|
|
1120
|
+
evidenceNode,
|
|
1121
|
+
args.questionNode
|
|
1122
|
+
);
|
|
1123
|
+
const rawEdges = await collectEdgesForEndpointPairs(ctx, endpointPairs);
|
|
1124
|
+
for (const rawEdge of rawEdges) {
|
|
1125
|
+
const edge = readQuestionEdgeRow(rawEdge);
|
|
1126
|
+
if (!(edge && edge.edgeType === "derived_from")) {
|
|
1127
|
+
continue;
|
|
1128
|
+
}
|
|
1129
|
+
if (edge.globalId) {
|
|
1130
|
+
await ctx.scheduler.runAfter(0, internal.neo4jEdgeAPI.deleteEdge, {
|
|
1131
|
+
globalId: edge.globalId
|
|
1132
|
+
});
|
|
1133
|
+
}
|
|
1134
|
+
await ctx.db.delete(edge._id);
|
|
1135
|
+
}
|
|
1136
|
+
} catch (error) {
|
|
1137
|
+
logQuestionFallback(
|
|
1138
|
+
"[epistemicQuestions] Failed to remove derived edge",
|
|
1139
|
+
error,
|
|
1140
|
+
{
|
|
1141
|
+
questionId: args.questionId,
|
|
1142
|
+
insightId: args.insightId
|
|
1143
|
+
}
|
|
1144
|
+
);
|
|
1145
|
+
console.error("[unlinkInsight] Failed to remove edge:", error);
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
761
1148
|
var advanceToConviction = mutation({
|
|
762
1149
|
args: {
|
|
763
1150
|
questionId: v.id("epistemicNodes"),
|
|
@@ -767,11 +1154,8 @@ var advanceToConviction = mutation({
|
|
|
767
1154
|
},
|
|
768
1155
|
returns: permissiveReturn,
|
|
769
1156
|
handler: async (ctx, args) => {
|
|
770
|
-
const node = await ctx.db.get(args.questionId);
|
|
771
|
-
|
|
772
|
-
throw new Error("Question not found");
|
|
773
|
-
}
|
|
774
|
-
const meta = node.metadata || {};
|
|
1157
|
+
const node = requireQuestionNode(await ctx.db.get(args.questionId));
|
|
1158
|
+
const meta = node.metadata;
|
|
775
1159
|
if (meta.convictionStage === "in_conviction" || meta.convictionStage === "scored") {
|
|
776
1160
|
throw new Error("Question is already in conviction stage");
|
|
777
1161
|
}
|
|
@@ -837,12 +1221,9 @@ var updateConviction = mutation({
|
|
|
837
1221
|
},
|
|
838
1222
|
returns: permissiveReturn,
|
|
839
1223
|
handler: async (ctx, args) => {
|
|
840
|
-
const node = await ctx.db.get(args.questionId);
|
|
841
|
-
if (!node || node.nodeType !== "question") {
|
|
842
|
-
throw new Error("Question not found");
|
|
843
|
-
}
|
|
1224
|
+
const node = requireQuestionNode(await ctx.db.get(args.questionId));
|
|
844
1225
|
const now = Date.now();
|
|
845
|
-
const meta = node.metadata
|
|
1226
|
+
const meta = node.metadata;
|
|
846
1227
|
const updates = { ...meta };
|
|
847
1228
|
if (args.conviction !== void 0) {
|
|
848
1229
|
updates.conviction = Math.max(0, Math.min(1, args.conviction));
|
|
@@ -887,35 +1268,28 @@ var finalizeConviction = mutation({
|
|
|
887
1268
|
},
|
|
888
1269
|
returns: permissiveReturn,
|
|
889
1270
|
handler: async (ctx, args) => {
|
|
890
|
-
const node = await ctx.db.get(args.questionId);
|
|
891
|
-
if (!node || node.nodeType !== "question") {
|
|
892
|
-
throw new Error("Question not found");
|
|
893
|
-
}
|
|
1271
|
+
const node = requireQuestionNode(await ctx.db.get(args.questionId));
|
|
894
1272
|
const now = Date.now();
|
|
895
1273
|
const clampedConviction = Math.max(0, Math.min(1, args.conviction));
|
|
896
|
-
const
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
}
|
|
1274
|
+
const completeness = resolveConvictionCompleteness(
|
|
1275
|
+
args.answerCompleteness,
|
|
1276
|
+
clampedConviction
|
|
1277
|
+
);
|
|
1278
|
+
const meta = node.metadata;
|
|
902
1279
|
await ctx.db.patch(args.questionId, {
|
|
903
1280
|
updatedAt: now,
|
|
904
|
-
metadata:
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
answerCompleteness: completeness,
|
|
917
|
-
whatWeNeed: args.whatWeNeed
|
|
918
|
-
}
|
|
1281
|
+
metadata: buildFinalizedConvictionMetadata(
|
|
1282
|
+
{
|
|
1283
|
+
userId: args.userId,
|
|
1284
|
+
answer: args.answer,
|
|
1285
|
+
convictionRationale: args.convictionRationale,
|
|
1286
|
+
answerCompleteness: completeness,
|
|
1287
|
+
whatWeNeed: args.whatWeNeed
|
|
1288
|
+
},
|
|
1289
|
+
meta,
|
|
1290
|
+
now,
|
|
1291
|
+
clampedConviction
|
|
1292
|
+
)
|
|
919
1293
|
});
|
|
920
1294
|
try {
|
|
921
1295
|
await ctx.db.insert("epistemicAudit", {
|
|
@@ -946,43 +1320,18 @@ var finalizeConviction = mutation({
|
|
|
946
1320
|
);
|
|
947
1321
|
console.error("[EpistemicAudit] Failed to log finalizeConviction:", e);
|
|
948
1322
|
}
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
relatedBeliefIds: meta.relatedBeliefIds || [],
|
|
962
|
-
conviction: clampedConviction,
|
|
963
|
-
rationale: args.convictionRationale || "",
|
|
964
|
-
projectId: node.projectId,
|
|
965
|
-
topicId: normalizeQuestionTopicId(node.topicId),
|
|
966
|
-
userId: args.userId
|
|
967
|
-
}
|
|
968
|
-
);
|
|
969
|
-
} catch (e) {
|
|
970
|
-
logQuestionFallback(
|
|
971
|
-
"[epistemicQuestions] Failed to schedule evidence creation from scored question",
|
|
972
|
-
e,
|
|
973
|
-
{
|
|
974
|
-
questionId: args.questionId,
|
|
975
|
-
beliefId,
|
|
976
|
-
projectId: node.projectId
|
|
977
|
-
}
|
|
978
|
-
);
|
|
979
|
-
console.error(
|
|
980
|
-
"[finalizeConviction] Failed to schedule evidence creation:",
|
|
981
|
-
e
|
|
982
|
-
);
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
}
|
|
1323
|
+
await scheduleEvidenceCreationFromScoredQuestion(ctx, {
|
|
1324
|
+
questionNodeId: args.questionId,
|
|
1325
|
+
questionText: node.canonicalText ?? "",
|
|
1326
|
+
answerText: args.answer,
|
|
1327
|
+
questionMetadata: meta,
|
|
1328
|
+
conviction: clampedConviction,
|
|
1329
|
+
rationale: args.convictionRationale || "",
|
|
1330
|
+
projectId: node.projectId,
|
|
1331
|
+
topicId: normalizeQuestionTopicId(node.topicId),
|
|
1332
|
+
userId: args.userId,
|
|
1333
|
+
relatedBeliefIds: readStringArray2(meta.relatedBeliefIds)
|
|
1334
|
+
});
|
|
986
1335
|
await ctx.scheduler.runAfter(0, internal.neo4jSync.syncNodeToNeo4j, {
|
|
987
1336
|
nodeId: args.questionId,
|
|
988
1337
|
operation: "upsert"
|
|
@@ -1004,7 +1353,9 @@ var getByBeliefWithAccess = query({
|
|
|
1004
1353
|
handler: async (ctx, args) => {
|
|
1005
1354
|
let beliefNode = null;
|
|
1006
1355
|
try {
|
|
1007
|
-
beliefNode =
|
|
1356
|
+
beliefNode = readBeliefNode(
|
|
1357
|
+
await ctx.db.get(args.beliefId)
|
|
1358
|
+
);
|
|
1008
1359
|
} catch (error) {
|
|
1009
1360
|
debugGraphPrimitiveFallback(
|
|
1010
1361
|
"[epistemicQuestions] Failed to resolve belief node directly",
|
|
@@ -1014,7 +1365,9 @@ var getByBeliefWithAccess = query({
|
|
|
1014
1365
|
}
|
|
1015
1366
|
);
|
|
1016
1367
|
try {
|
|
1017
|
-
beliefNode =
|
|
1368
|
+
beliefNode = readBeliefNode(
|
|
1369
|
+
await ctx.db.get(args.beliefId)
|
|
1370
|
+
);
|
|
1018
1371
|
} catch (legacyError) {
|
|
1019
1372
|
debugGraphPrimitiveFallback(
|
|
1020
1373
|
"[epistemicQuestions] Failed to resolve legacy belief node",
|
|
@@ -1034,7 +1387,7 @@ var getByBeliefWithAccess = query({
|
|
|
1034
1387
|
topicId: normalizeQuestionTopicId(beliefNode.topicId)
|
|
1035
1388
|
});
|
|
1036
1389
|
const scopeId = scope ? resolveQuestionScopeId(scope) : void 0;
|
|
1037
|
-
if (!scope
|
|
1390
|
+
if (!(scope && scopeId)) {
|
|
1038
1391
|
return [];
|
|
1039
1392
|
}
|
|
1040
1393
|
const hasAccess = await checkScopeAccess(ctx, scopeId, args.userId);
|
|
@@ -1044,7 +1397,7 @@ var getByBeliefWithAccess = query({
|
|
|
1044
1397
|
const questionNodes = await getQuestionNodesForScope(ctx, scope);
|
|
1045
1398
|
const beliefIdStr = String(args.beliefId);
|
|
1046
1399
|
return questionNodes.filter((q) => {
|
|
1047
|
-
const meta = q.metadata
|
|
1400
|
+
const meta = readRecord(q.metadata) ?? {};
|
|
1048
1401
|
if (String(meta.linkedBeliefNodeId || "") === beliefIdStr) {
|
|
1049
1402
|
return true;
|
|
1050
1403
|
}
|
|
@@ -1054,7 +1407,7 @@ var getByBeliefWithAccess = query({
|
|
|
1054
1407
|
if (String(meta.beliefId || "") === beliefIdStr) {
|
|
1055
1408
|
return true;
|
|
1056
1409
|
}
|
|
1057
|
-
const relatedIds = meta.relatedBeliefIds
|
|
1410
|
+
const relatedIds = readStringArray2(meta.relatedBeliefIds);
|
|
1058
1411
|
if (relatedIds.some((id) => String(id) === beliefIdStr)) {
|
|
1059
1412
|
return true;
|
|
1060
1413
|
}
|
|
@@ -1089,12 +1442,9 @@ var updateQuestion = mutation({
|
|
|
1089
1442
|
},
|
|
1090
1443
|
returns: permissiveReturn,
|
|
1091
1444
|
handler: async (ctx, args) => {
|
|
1092
|
-
const node = await ctx.db.get(args.questionId);
|
|
1093
|
-
if (!node || node.nodeType !== "question") {
|
|
1094
|
-
throw new Error("Question not found");
|
|
1095
|
-
}
|
|
1445
|
+
const node = requireQuestionNode(await ctx.db.get(args.questionId));
|
|
1096
1446
|
const now = Date.now();
|
|
1097
|
-
const meta = node.metadata
|
|
1447
|
+
const meta = node.metadata;
|
|
1098
1448
|
const metaUpdates = { ...meta };
|
|
1099
1449
|
if (args.category) {
|
|
1100
1450
|
const category = args.category === "financial" ? "financials" : args.category;
|
|
@@ -1145,115 +1495,27 @@ var linkToBelief = mutation({
|
|
|
1145
1495
|
},
|
|
1146
1496
|
returns: permissiveReturn,
|
|
1147
1497
|
handler: async (ctx, args) => {
|
|
1148
|
-
const questionNode = await ctx.db.get(args.questionId);
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
}
|
|
1152
|
-
const questionScopeId = resolveQuestionScopeId({
|
|
1153
|
-
projectId: questionNode.projectId,
|
|
1154
|
-
topicId: normalizeQuestionTopicId(questionNode.topicId)
|
|
1155
|
-
});
|
|
1156
|
-
if (questionScopeId) {
|
|
1157
|
-
const hasAccess = await checkScopeAccess(
|
|
1158
|
-
ctx,
|
|
1159
|
-
questionScopeId,
|
|
1160
|
-
args.userId
|
|
1161
|
-
);
|
|
1162
|
-
if (!hasAccess) {
|
|
1163
|
-
throw new Error("Access denied");
|
|
1164
|
-
}
|
|
1165
|
-
}
|
|
1166
|
-
let beliefNode = null;
|
|
1167
|
-
try {
|
|
1168
|
-
beliefNode = await ctx.db.get(args.beliefId);
|
|
1169
|
-
} catch (error) {
|
|
1170
|
-
debugGraphPrimitiveFallback(
|
|
1171
|
-
"[epistemicQuestions] Failed to resolve belief node directly",
|
|
1172
|
-
{
|
|
1173
|
-
error: formatGraphPrimitiveError(error),
|
|
1174
|
-
beliefId: args.beliefId
|
|
1175
|
-
}
|
|
1176
|
-
);
|
|
1177
|
-
try {
|
|
1178
|
-
beliefNode = await ctx.db.get(args.beliefId);
|
|
1179
|
-
} catch (legacyError) {
|
|
1180
|
-
debugGraphPrimitiveFallback(
|
|
1181
|
-
"[epistemicQuestions] Failed to resolve legacy belief node",
|
|
1182
|
-
{
|
|
1183
|
-
error: formatGraphPrimitiveError(legacyError),
|
|
1184
|
-
beliefId: args.beliefId
|
|
1185
|
-
}
|
|
1186
|
-
);
|
|
1187
|
-
throw new Error("Belief not found");
|
|
1188
|
-
}
|
|
1189
|
-
}
|
|
1498
|
+
const questionNode = requireQuestionNode(await ctx.db.get(args.questionId));
|
|
1499
|
+
await assertQuestionScopeAccess(ctx, questionNode, args.userId);
|
|
1500
|
+
const beliefNode = await resolveBeliefNodeForBeliefLink(ctx, args.beliefId);
|
|
1190
1501
|
if (!beliefNode) {
|
|
1191
1502
|
throw new Error("Belief not found");
|
|
1192
1503
|
}
|
|
1193
1504
|
const now = Date.now();
|
|
1194
|
-
const meta = questionNode.metadata
|
|
1195
|
-
const
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1505
|
+
const meta = questionNode.metadata;
|
|
1506
|
+
const linkArgs = {
|
|
1507
|
+
beliefId: args.beliefId,
|
|
1508
|
+
userId: args.userId,
|
|
1509
|
+
testType: args.testType,
|
|
1510
|
+
answerImpact: args.answerImpact,
|
|
1511
|
+
isPrimaryBelief: args.isPrimaryBelief
|
|
1201
1512
|
};
|
|
1202
|
-
|
|
1203
|
-
metaUpdates.linkedBeliefNodeId = args.beliefId;
|
|
1204
|
-
metaUpdates.linkedBeliefId = args.beliefId;
|
|
1205
|
-
metaUpdates.questionType = "belief_test";
|
|
1206
|
-
if (args.testType) {
|
|
1207
|
-
metaUpdates.testType = args.testType;
|
|
1208
|
-
}
|
|
1209
|
-
if (args.answerImpact) {
|
|
1210
|
-
metaUpdates.answerImpact = args.answerImpact;
|
|
1211
|
-
}
|
|
1212
|
-
}
|
|
1513
|
+
const metaUpdates = buildBeliefLinkMetadata(meta, linkArgs);
|
|
1213
1514
|
await ctx.db.patch(args.questionId, {
|
|
1214
1515
|
updatedAt: now,
|
|
1215
1516
|
metadata: metaUpdates
|
|
1216
1517
|
});
|
|
1217
|
-
|
|
1218
|
-
const beliefGlobalId = beliefNode.globalId;
|
|
1219
|
-
if (beliefGlobalId && questionNode.globalId) {
|
|
1220
|
-
await ctx.scheduler.runAfter(0, internal.neo4jSync.syncNodeToNeo4j, {
|
|
1221
|
-
nodeId: args.questionId,
|
|
1222
|
-
operation: "upsert"
|
|
1223
|
-
});
|
|
1224
|
-
await ctx.scheduler.runAfter(100, internal.neo4jSync.syncNodeToNeo4j, {
|
|
1225
|
-
nodeId: args.beliefId,
|
|
1226
|
-
operation: "upsert"
|
|
1227
|
-
});
|
|
1228
|
-
const edgeGlobalId = buildTestsEdgeGlobalId(
|
|
1229
|
-
questionNode.globalId,
|
|
1230
|
-
beliefGlobalId
|
|
1231
|
-
);
|
|
1232
|
-
await ctx.scheduler.runAfter(250, internal.neo4jEdgeAPI.createEdge, {
|
|
1233
|
-
globalId: edgeGlobalId,
|
|
1234
|
-
fromGlobalId: questionNode.globalId,
|
|
1235
|
-
toGlobalId: beliefGlobalId,
|
|
1236
|
-
edgeType: "tests",
|
|
1237
|
-
context: args.testType || "tests",
|
|
1238
|
-
topicId: questionNode.projectId ? String(questionNode.projectId) : void 0,
|
|
1239
|
-
createdBy: args.userId,
|
|
1240
|
-
fromNodeType: "question",
|
|
1241
|
-
toNodeType: "belief",
|
|
1242
|
-
fromLayer: "L3",
|
|
1243
|
-
toLayer: "L3"
|
|
1244
|
-
});
|
|
1245
|
-
}
|
|
1246
|
-
} catch (e) {
|
|
1247
|
-
logQuestionFallback(
|
|
1248
|
-
"[epistemicQuestions] Failed to create tests edge",
|
|
1249
|
-
e,
|
|
1250
|
-
{
|
|
1251
|
-
questionId: args.questionId,
|
|
1252
|
-
beliefId: args.beliefId
|
|
1253
|
-
}
|
|
1254
|
-
);
|
|
1255
|
-
console.error("[linkToBelief] Failed to create tests edge:", e);
|
|
1256
|
-
}
|
|
1518
|
+
await syncBeliefTestEdge(ctx, questionNode, beliefNode, linkArgs);
|
|
1257
1519
|
await markProjectGraphDirty(
|
|
1258
1520
|
ctx,
|
|
1259
1521
|
questionNode.projectId,
|
|
@@ -1273,31 +1535,15 @@ var linkToInsight = mutation({
|
|
|
1273
1535
|
},
|
|
1274
1536
|
returns: permissiveReturn,
|
|
1275
1537
|
handler: async (ctx, args) => {
|
|
1276
|
-
const questionNode = await ctx.db.get(args.questionId);
|
|
1277
|
-
|
|
1278
|
-
throw new Error("Question not found");
|
|
1279
|
-
}
|
|
1280
|
-
const insight = await ctx.db.get(args.insightId);
|
|
1538
|
+
const questionNode = requireQuestionNode(await ctx.db.get(args.questionId));
|
|
1539
|
+
const insight = readQuestionNodeRow(await ctx.db.get(args.insightId));
|
|
1281
1540
|
if (!insight) {
|
|
1282
1541
|
throw new Error("Insight not found");
|
|
1283
1542
|
}
|
|
1284
|
-
|
|
1285
|
-
projectId: questionNode.projectId,
|
|
1286
|
-
topicId: normalizeQuestionTopicId(questionNode.topicId)
|
|
1287
|
-
});
|
|
1288
|
-
if (questionScopeId) {
|
|
1289
|
-
const hasAccess = await checkScopeAccess(
|
|
1290
|
-
ctx,
|
|
1291
|
-
questionScopeId,
|
|
1292
|
-
args.userId
|
|
1293
|
-
);
|
|
1294
|
-
if (!hasAccess) {
|
|
1295
|
-
throw new Error("Access denied");
|
|
1296
|
-
}
|
|
1297
|
-
}
|
|
1543
|
+
await assertQuestionScopeAccess(ctx, questionNode, args.userId);
|
|
1298
1544
|
const now = Date.now();
|
|
1299
|
-
const meta = questionNode.metadata
|
|
1300
|
-
const currentInsights = meta.relatedInsightIds
|
|
1545
|
+
const meta = questionNode.metadata;
|
|
1546
|
+
const currentInsights = readStringArray2(meta.relatedInsightIds);
|
|
1301
1547
|
const insightIdStr = String(args.insightId);
|
|
1302
1548
|
if (!currentInsights.includes(insightIdStr)) {
|
|
1303
1549
|
await ctx.db.patch(args.questionId, {
|
|
@@ -1312,8 +1558,8 @@ var linkToInsight = mutation({
|
|
|
1312
1558
|
"by_questionId",
|
|
1313
1559
|
(q) => q.eq("questionId", args.questionId)
|
|
1314
1560
|
).collect();
|
|
1315
|
-
const duplicate = existingLinks.find(
|
|
1316
|
-
(link) => String(link.insightId) === String(args.insightId)
|
|
1561
|
+
const duplicate = existingLinks.map(readQuestionEvidenceLinkRow).find(
|
|
1562
|
+
(link) => link && String(link.insightId) === String(args.insightId)
|
|
1317
1563
|
);
|
|
1318
1564
|
if (!duplicate) {
|
|
1319
1565
|
await ctx.db.insert("questionEvidenceLinks", {
|
|
@@ -1348,27 +1594,11 @@ var unlinkInsight = mutation({
|
|
|
1348
1594
|
},
|
|
1349
1595
|
returns: permissiveReturn,
|
|
1350
1596
|
handler: async (ctx, args) => {
|
|
1351
|
-
const questionNode = await ctx.db.get(args.questionId);
|
|
1352
|
-
|
|
1353
|
-
throw new Error("Question not found");
|
|
1354
|
-
}
|
|
1355
|
-
const questionScopeId = resolveQuestionScopeId({
|
|
1356
|
-
projectId: questionNode.projectId,
|
|
1357
|
-
topicId: normalizeQuestionTopicId(questionNode.topicId)
|
|
1358
|
-
});
|
|
1359
|
-
if (questionScopeId) {
|
|
1360
|
-
const hasAccess = await checkScopeAccess(
|
|
1361
|
-
ctx,
|
|
1362
|
-
questionScopeId,
|
|
1363
|
-
args.userId
|
|
1364
|
-
);
|
|
1365
|
-
if (!hasAccess) {
|
|
1366
|
-
throw new Error("Access denied");
|
|
1367
|
-
}
|
|
1368
|
-
}
|
|
1597
|
+
const questionNode = requireQuestionNode(await ctx.db.get(args.questionId));
|
|
1598
|
+
await assertQuestionScopeAccess(ctx, questionNode, args.userId);
|
|
1369
1599
|
const now = Date.now();
|
|
1370
|
-
const meta = questionNode.metadata
|
|
1371
|
-
const currentInsights = meta.relatedInsightIds
|
|
1600
|
+
const meta = questionNode.metadata;
|
|
1601
|
+
const currentInsights = readStringArray2(meta.relatedInsightIds);
|
|
1372
1602
|
const newInsights = currentInsights.filter(
|
|
1373
1603
|
(id) => String(id) !== String(args.insightId)
|
|
1374
1604
|
);
|
|
@@ -1379,57 +1609,8 @@ var unlinkInsight = mutation({
|
|
|
1379
1609
|
relatedInsightIds: newInsights
|
|
1380
1610
|
}
|
|
1381
1611
|
});
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
"by_questionId",
|
|
1385
|
-
(q) => q.eq("questionId", args.questionId)
|
|
1386
|
-
).collect();
|
|
1387
|
-
for (const link of links) {
|
|
1388
|
-
if (String(link.insightId) === String(args.insightId)) {
|
|
1389
|
-
await ctx.db.delete(link._id);
|
|
1390
|
-
}
|
|
1391
|
-
}
|
|
1392
|
-
} catch (e) {
|
|
1393
|
-
logQuestionFallback(
|
|
1394
|
-
"[epistemicQuestions] Failed to remove questionEvidenceLink",
|
|
1395
|
-
e,
|
|
1396
|
-
{
|
|
1397
|
-
questionId: args.questionId,
|
|
1398
|
-
insightId: args.insightId
|
|
1399
|
-
}
|
|
1400
|
-
);
|
|
1401
|
-
console.error(
|
|
1402
|
-
"[unlinkInsight] Failed to remove questionEvidenceLink:",
|
|
1403
|
-
e
|
|
1404
|
-
);
|
|
1405
|
-
}
|
|
1406
|
-
try {
|
|
1407
|
-
const evidenceNode = await ctx.db.get(args.insightId);
|
|
1408
|
-
if (evidenceNode && evidenceNode.nodeType === "evidence") {
|
|
1409
|
-
const edges = await ctx.db.query("epistemicEdges").withIndex(
|
|
1410
|
-
"by_from_to",
|
|
1411
|
-
(q) => q.eq("fromNodeId", evidenceNode._id).eq("toNodeId", args.questionId)
|
|
1412
|
-
).collect();
|
|
1413
|
-
for (const edge of edges) {
|
|
1414
|
-
if (edge.edgeType === "derived_from") {
|
|
1415
|
-
await ctx.scheduler.runAfter(0, internal.neo4jEdgeAPI.deleteEdge, {
|
|
1416
|
-
globalId: edge.globalId
|
|
1417
|
-
});
|
|
1418
|
-
await ctx.db.delete(edge._id);
|
|
1419
|
-
}
|
|
1420
|
-
}
|
|
1421
|
-
}
|
|
1422
|
-
} catch (e) {
|
|
1423
|
-
logQuestionFallback(
|
|
1424
|
-
"[epistemicQuestions] Failed to remove derived edge",
|
|
1425
|
-
e,
|
|
1426
|
-
{
|
|
1427
|
-
questionId: args.questionId,
|
|
1428
|
-
insightId: args.insightId
|
|
1429
|
-
}
|
|
1430
|
-
);
|
|
1431
|
-
console.error("[unlinkInsight] Failed to remove edge:", e);
|
|
1432
|
-
}
|
|
1612
|
+
await removeQuestionEvidenceLinks(ctx, args);
|
|
1613
|
+
await removeDerivedEvidenceEdges(ctx, { ...args, questionNode });
|
|
1433
1614
|
await markProjectGraphDirty(
|
|
1434
1615
|
ctx,
|
|
1435
1616
|
questionNode.projectId,
|