@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
package/dist/entityLifecycle.js
CHANGED
|
@@ -1,39 +1,125 @@
|
|
|
1
|
-
import { v } from 'convex/values';
|
|
2
1
|
import { requireScopeWriteAccess } from '@lucern/access-control/access';
|
|
3
|
-
import { throwStructuredMutationError } from '@lucern/access-control/structuredMutationError';
|
|
4
2
|
import { getCurrentUserId } from '@lucern/access-control/auth';
|
|
3
|
+
import { throwStructuredMutationError } from '@lucern/access-control/structuredMutationError';
|
|
5
4
|
import { permissiveReturn } from '@lucern/contracts/schema-helpers/validators';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
5
|
+
import { v } from 'convex/values';
|
|
6
|
+
import { unsafeConvexAnyApi } from '@lucern/contracts/convex/unsafeAnyApi';
|
|
7
|
+
import { componentsGeneric, queryGeneric, mutationGeneric } from 'convex/server';
|
|
8
8
|
import { assertEdgePolicyAllowed, edgePolicyManifest } from '@lucern/contracts';
|
|
9
|
+
import { generateGlobalId, assertUuidV7Identity, assertStorageEdgeVocabulary, assertUuidShapedEdgeEndpoint } from '@lucern/contracts/ids';
|
|
9
10
|
|
|
10
11
|
// src/entityLifecycle.ts
|
|
11
|
-
var
|
|
12
|
+
var unsafeApi = unsafeConvexAnyApi(
|
|
13
|
+
"graph-primitives top-level module bundle lacks a committed Convex _generated/api surface"
|
|
14
|
+
);
|
|
15
|
+
var api = unsafeApi;
|
|
12
16
|
componentsGeneric();
|
|
13
|
-
var internal =
|
|
17
|
+
var internal = unsafeApi;
|
|
14
18
|
var mutation = mutationGeneric;
|
|
15
19
|
var query = queryGeneric;
|
|
16
20
|
|
|
21
|
+
// src/entityCanonicalMatch.ts
|
|
22
|
+
function normalizeCanonicalEntityText(value) {
|
|
23
|
+
return value.trim().toLowerCase().replace(/\s+/g, " ");
|
|
24
|
+
}
|
|
25
|
+
function buildEntityTitle(canonicalText) {
|
|
26
|
+
return canonicalText.slice(0, 100) + (canonicalText.length > 100 ? "..." : "");
|
|
27
|
+
}
|
|
28
|
+
function matchesCanonicalEntityRecord(node, args) {
|
|
29
|
+
if (node.epistemicLayer !== "ontological") {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
if (node.workspaceId !== void 0) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
if (args.nodeType && node.nodeType !== args.nodeType) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
if (args.contentHash && node.contentHash !== args.contentHash) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
const rowTenantId = typeof node.tenantId === "string" && node.tenantId.trim().length > 0 ? node.tenantId.trim() : void 0;
|
|
42
|
+
if (rowTenantId !== args.tenantId) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
if (args.includeArchived) {
|
|
46
|
+
return node.status !== "deleted";
|
|
47
|
+
}
|
|
48
|
+
return node.status === "active";
|
|
49
|
+
}
|
|
50
|
+
|
|
17
51
|
// src/entityValidation.ts
|
|
52
|
+
function isObjectLike(value) {
|
|
53
|
+
return value !== null && typeof value === "object";
|
|
54
|
+
}
|
|
55
|
+
function isEntitySchemaConfigField(value) {
|
|
56
|
+
if (!isObjectLike(value)) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
const required = value.required;
|
|
60
|
+
const type = value.type;
|
|
61
|
+
return typeof required === "boolean" && (type === "string" || type === "number" || type === "boolean");
|
|
62
|
+
}
|
|
63
|
+
function readEntitySchema(value) {
|
|
64
|
+
if (!isObjectLike(value)) {
|
|
65
|
+
return {};
|
|
66
|
+
}
|
|
67
|
+
const schema = {};
|
|
68
|
+
for (const [fieldName, fieldValue] of Object.entries(value)) {
|
|
69
|
+
if (!isEntitySchemaConfigField(fieldValue)) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
schema[fieldName] = fieldValue;
|
|
73
|
+
}
|
|
74
|
+
return schema;
|
|
75
|
+
}
|
|
76
|
+
function readSchemaEnumConfigRow(row) {
|
|
77
|
+
if (!isObjectLike(row)) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
const value = row.value;
|
|
81
|
+
if (typeof value !== "string") {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
value,
|
|
86
|
+
status: typeof row.status === "string" ? row.status : void 0,
|
|
87
|
+
metadata: isObjectLike(row.metadata) ? row.metadata : void 0
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function readEntitySchemaFromConfig(row) {
|
|
91
|
+
const parsed = readSchemaEnumConfigRow(row);
|
|
92
|
+
if (parsed?.status !== "active" || !parsed?.metadata) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
const schema = parsed.metadata.schema;
|
|
96
|
+
if (!schema) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
return readEntitySchema(schema);
|
|
100
|
+
}
|
|
18
101
|
async function getEntityTypeSchema(ctx, nodeType, tenantId) {
|
|
19
102
|
if (tenantId) {
|
|
20
|
-
const
|
|
103
|
+
const tenantEntries = await ctx.db.query("schemaEnumConfig").withIndex(
|
|
21
104
|
"by_tenant_category",
|
|
22
105
|
(q) => q.eq("tenantId", tenantId).eq("category", "entity_type")
|
|
23
106
|
).collect();
|
|
24
|
-
const tenantMatch =
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
107
|
+
const tenantMatch = tenantEntries.find((entry) => {
|
|
108
|
+
const parsed = readSchemaEnumConfigRow(entry);
|
|
109
|
+
return parsed !== null && parsed.value === nodeType && parsed.status === "active";
|
|
110
|
+
});
|
|
111
|
+
const tenantSchema = readEntitySchemaFromConfig(tenantMatch);
|
|
112
|
+
if (tenantSchema !== null) {
|
|
113
|
+
return tenantSchema;
|
|
29
114
|
}
|
|
30
115
|
}
|
|
31
116
|
const platformEntry = await ctx.db.query("schemaEnumConfig").withIndex(
|
|
32
117
|
"by_category_value",
|
|
33
118
|
(q) => q.eq("category", "entity_type").eq("value", nodeType)
|
|
34
119
|
).first();
|
|
35
|
-
|
|
36
|
-
|
|
120
|
+
const platformSchema = readEntitySchemaFromConfig(platformEntry);
|
|
121
|
+
if (platformSchema !== null) {
|
|
122
|
+
return platformSchema;
|
|
37
123
|
}
|
|
38
124
|
return null;
|
|
39
125
|
}
|
|
@@ -77,99 +163,54 @@ async function validateEntityMetadata(ctx, nodeType, metadata, tenantId) {
|
|
|
77
163
|
errors
|
|
78
164
|
};
|
|
79
165
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
return value.trim().toLowerCase().replace(/\s+/g, " ");
|
|
84
|
-
}
|
|
85
|
-
function buildEntityTitle(canonicalText) {
|
|
86
|
-
return canonicalText.slice(0, 100) + (canonicalText.length > 100 ? "..." : "");
|
|
87
|
-
}
|
|
88
|
-
function matchesCanonicalEntityRecord(node, args) {
|
|
89
|
-
if (node.epistemicLayer !== "ontological") {
|
|
90
|
-
return false;
|
|
91
|
-
}
|
|
92
|
-
if (node.workspaceId !== void 0) {
|
|
93
|
-
return false;
|
|
94
|
-
}
|
|
95
|
-
if (args.nodeType && node.nodeType !== args.nodeType) {
|
|
96
|
-
return false;
|
|
97
|
-
}
|
|
98
|
-
if (args.contentHash && node.contentHash !== args.contentHash) {
|
|
99
|
-
return false;
|
|
100
|
-
}
|
|
101
|
-
const rowTenantId = typeof node.tenantId === "string" && node.tenantId.trim().length > 0 ? node.tenantId.trim() : void 0;
|
|
102
|
-
if (rowTenantId !== args.tenantId) {
|
|
103
|
-
return false;
|
|
104
|
-
}
|
|
105
|
-
if (args.includeArchived) {
|
|
106
|
-
return node.status !== "deleted";
|
|
107
|
-
}
|
|
108
|
-
return node.status === "active";
|
|
166
|
+
function insertEpistemicNode(ctx, doc) {
|
|
167
|
+
assertUuidV7Identity("epistemicNodes", doc.globalId);
|
|
168
|
+
return ctx.db.insert("epistemicNodes", doc);
|
|
109
169
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (current.ontologyId) {
|
|
121
|
-
const ontologyDef = await ctx.db.get(
|
|
122
|
-
current.ontologyId
|
|
123
|
-
);
|
|
124
|
-
if (!ontologyDef || ontologyDef.status === "archived") {
|
|
125
|
-
if (current.parentTopicId) {
|
|
126
|
-
current = await ctx.db.get(
|
|
127
|
-
current.parentTopicId
|
|
128
|
-
);
|
|
129
|
-
continue;
|
|
130
|
-
}
|
|
131
|
-
return null;
|
|
132
|
-
}
|
|
133
|
-
const versions = await ctx.db.query("ontologyVersions").withIndex(
|
|
134
|
-
"by_ontologyId",
|
|
135
|
-
(q) => q.eq("ontologyId", current?.ontologyId)
|
|
136
|
-
).collect();
|
|
137
|
-
const published = versions.filter((v3) => v3.status === "published").sort((a, b) => (b.publishedAt ?? 0) - (a.publishedAt ?? 0));
|
|
138
|
-
const latestPublished = published[0] ?? null;
|
|
139
|
-
return {
|
|
140
|
-
ontologyId: ontologyDef._id,
|
|
141
|
-
ontologyKey: ontologyDef.ontologyKey,
|
|
142
|
-
ontologyName: ontologyDef.name,
|
|
143
|
-
tier: ontologyDef.tier,
|
|
144
|
-
source: current._id === startTopicId ? "direct" : "inherited",
|
|
145
|
-
sourceTopicId: current._id,
|
|
146
|
-
validEntityTypes: latestPublished ? latestPublished.entityTypes.map((et) => et.value) : [],
|
|
147
|
-
validEdgeTypes: latestPublished ? latestPublished.edgeTypes.map((et) => et.value) : [],
|
|
148
|
-
publishedVersion: latestPublished
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
if (!current.parentTopicId) {
|
|
152
|
-
break;
|
|
153
|
-
}
|
|
154
|
-
current = await ctx.db.get(current.parentTopicId);
|
|
170
|
+
async function assertExistingNodeEndpoint(ctx, endpointRole, endpoint) {
|
|
171
|
+
assertUuidShapedEdgeEndpoint(endpointRole, endpoint);
|
|
172
|
+
const node = await ctx.db.query("epistemicNodes").withIndex(
|
|
173
|
+
"by_globalId",
|
|
174
|
+
(q) => q.eq("globalId", endpoint)
|
|
175
|
+
).first();
|
|
176
|
+
if (!node) {
|
|
177
|
+
throw new Error(
|
|
178
|
+
`edge_endpoint_not_canonical: epistemicEdges insert requires ${endpointRole} to be the globalId of an existing epistemicNodes row, received ${endpoint} (no node with that globalId)`
|
|
179
|
+
);
|
|
155
180
|
}
|
|
156
|
-
return null;
|
|
157
181
|
}
|
|
158
|
-
async function
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
182
|
+
async function insertEpistemicEdge(ctx, doc) {
|
|
183
|
+
assertUuidV7Identity("epistemicEdges", doc.globalId);
|
|
184
|
+
assertStorageEdgeVocabulary(doc.edgeType);
|
|
185
|
+
if (!doc.fromNodeId || typeof doc.fromNodeId !== "string") {
|
|
186
|
+
throw new Error(
|
|
187
|
+
"edge_endpoint_missing: epistemicEdges insert requires a non-empty fromNodeId"
|
|
188
|
+
);
|
|
162
189
|
}
|
|
163
|
-
if (
|
|
164
|
-
|
|
190
|
+
if (!doc.toNodeId || typeof doc.toNodeId !== "string") {
|
|
191
|
+
throw new Error(
|
|
192
|
+
"edge_endpoint_missing: epistemicEdges insert requires a non-empty toNodeId"
|
|
193
|
+
);
|
|
165
194
|
}
|
|
166
|
-
|
|
167
|
-
|
|
195
|
+
await assertExistingNodeEndpoint(ctx, "fromNodeId", doc.fromNodeId);
|
|
196
|
+
await assertExistingNodeEndpoint(ctx, "toNodeId", doc.toNodeId);
|
|
197
|
+
if (doc.fromNodeType && doc.toNodeType && doc.edgeType !== "extracted_from") {
|
|
198
|
+
assertEdgePolicyAllowed(
|
|
199
|
+
edgePolicyManifest,
|
|
200
|
+
doc.edgeType,
|
|
201
|
+
{
|
|
202
|
+
kind: "epistemic_node",
|
|
203
|
+
nodeId: doc.fromNodeId,
|
|
204
|
+
nodeType: doc.fromNodeType
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
kind: "epistemic_node",
|
|
208
|
+
nodeId: doc.toNodeId,
|
|
209
|
+
nodeType: doc.toNodeType
|
|
210
|
+
}
|
|
211
|
+
);
|
|
168
212
|
}
|
|
169
|
-
return
|
|
170
|
-
valid: false,
|
|
171
|
-
error: `Entity type "${nodeType}" is not defined in the ontology "${resolved.ontologyKey}" (${resolved.ontologyName}). Valid entity types: ${resolved.validEntityTypes.join(", ")}. Source: ${resolved.source} from topic ${resolved.sourceTopicId}.`
|
|
172
|
-
};
|
|
213
|
+
return ctx.db.insert("epistemicEdges", doc);
|
|
173
214
|
}
|
|
174
215
|
|
|
175
216
|
// src/debug.ts
|
|
@@ -202,6 +243,10 @@ function readStringArray(value) {
|
|
|
202
243
|
function readMetadata(topic) {
|
|
203
244
|
return topic.metadata && typeof topic.metadata === "object" ? topic.metadata : {};
|
|
204
245
|
}
|
|
246
|
+
function omitMetadataKey(metadata, key) {
|
|
247
|
+
const { [key]: _omitted, ...rest } = metadata;
|
|
248
|
+
return rest;
|
|
249
|
+
}
|
|
205
250
|
function readLegacyProjectId(value) {
|
|
206
251
|
if (!value) {
|
|
207
252
|
return;
|
|
@@ -282,9 +327,12 @@ async function resolveTopicDoc(ctx, scopeId) {
|
|
|
282
327
|
);
|
|
283
328
|
}
|
|
284
329
|
try {
|
|
285
|
-
const topic = await ctx.runQuery(
|
|
286
|
-
|
|
287
|
-
|
|
330
|
+
const topic = await ctx.runQuery(
|
|
331
|
+
api.topics.getByLegacyScopeId,
|
|
332
|
+
{
|
|
333
|
+
projectId: String(scopeId)
|
|
334
|
+
}
|
|
335
|
+
);
|
|
288
336
|
if (topic?.name !== void 0 && topic?.type !== void 0) {
|
|
289
337
|
return topic;
|
|
290
338
|
}
|
|
@@ -304,8 +352,18 @@ function materializeTopicProjectOverlay(topic, idMode = "legacy") {
|
|
|
304
352
|
const outwardId = idMode === "topic" ? topicId : storageProjectId;
|
|
305
353
|
const visibility = coerceVisibility(topic.visibility) || coerceVisibility(metadata.visibility) || "private";
|
|
306
354
|
const status = coerceStatus(topic.status) || coerceStatus(metadata.status) || "active";
|
|
307
|
-
|
|
308
|
-
|
|
355
|
+
let createdAt = 0;
|
|
356
|
+
if (typeof topic.createdAt === "number") {
|
|
357
|
+
createdAt = topic.createdAt;
|
|
358
|
+
} else if (typeof topic._creationTime === "number") {
|
|
359
|
+
createdAt = topic._creationTime;
|
|
360
|
+
}
|
|
361
|
+
let updatedAt = createdAt;
|
|
362
|
+
if (typeof topic.updatedAt === "number") {
|
|
363
|
+
updatedAt = topic.updatedAt;
|
|
364
|
+
} else if (typeof metadata.updatedAt === "number") {
|
|
365
|
+
updatedAt = metadata.updatedAt;
|
|
366
|
+
}
|
|
309
367
|
return {
|
|
310
368
|
...metadata,
|
|
311
369
|
_id: outwardId,
|
|
@@ -374,90 +432,113 @@ async function patchTopicProjectOverlay(ctx, scopeId, value) {
|
|
|
374
432
|
if (!topic) {
|
|
375
433
|
return null;
|
|
376
434
|
}
|
|
377
|
-
const
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
435
|
+
const plan = buildTopicProjectOverlayPatchPlan(topic, value);
|
|
436
|
+
await applyTopicProjectOverlayPatch(ctx, topic, plan);
|
|
437
|
+
return materializeTopicProjectOverlay({
|
|
438
|
+
...topic,
|
|
439
|
+
...plan.patch,
|
|
440
|
+
metadata: plan.nextMetadata
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
function buildTopicProjectOverlayPatchPlan(topic, value) {
|
|
444
|
+
const plan = {
|
|
445
|
+
nextMetadata: { ...readMetadata(topic) },
|
|
446
|
+
patch: {},
|
|
447
|
+
topicUpdateArgs: {
|
|
448
|
+
id: String(topic._id)
|
|
449
|
+
}
|
|
381
450
|
};
|
|
382
451
|
for (const [key, rawValue] of Object.entries(value)) {
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
452
|
+
applyTopicProjectOverlayPatchEntry(plan, key, rawValue);
|
|
453
|
+
}
|
|
454
|
+
plan.patch.updatedAt = Date.now();
|
|
455
|
+
plan.patch.metadata = plan.nextMetadata;
|
|
456
|
+
plan.topicUpdateArgs.metadata = plan.nextMetadata;
|
|
457
|
+
return plan;
|
|
458
|
+
}
|
|
459
|
+
function applyTopicProjectOverlayPatchEntry(plan, key, rawValue) {
|
|
460
|
+
switch (key) {
|
|
461
|
+
case "_id":
|
|
462
|
+
case "projectId":
|
|
463
|
+
case "topicId":
|
|
464
|
+
case "legacyProjectId":
|
|
465
|
+
case "storageProjectId":
|
|
466
|
+
case "updatedAt":
|
|
467
|
+
case "createdAt":
|
|
468
|
+
return;
|
|
469
|
+
case "name":
|
|
470
|
+
case "description":
|
|
471
|
+
plan.patch[key] = rawValue;
|
|
472
|
+
plan.topicUpdateArgs[key] = rawValue;
|
|
473
|
+
return;
|
|
474
|
+
case "tenantId":
|
|
475
|
+
case "workspaceId":
|
|
476
|
+
case "ownerId":
|
|
477
|
+
throw new Error(
|
|
478
|
+
`patchTopicProjectOverlay cannot mutate ${key} via component-owned topics`
|
|
479
|
+
);
|
|
480
|
+
case "status":
|
|
481
|
+
applyTopicStatusPatch(plan, rawValue);
|
|
482
|
+
return;
|
|
483
|
+
case "visibility":
|
|
484
|
+
applyTopicVisibilityPatch(plan, rawValue);
|
|
485
|
+
return;
|
|
486
|
+
case "type":
|
|
487
|
+
applyTopicProjectTypePatch(plan, rawValue);
|
|
488
|
+
return;
|
|
489
|
+
default:
|
|
490
|
+
applyTopicMetadataPatch(plan, key, rawValue);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
function applyTopicStatusPatch(plan, rawValue) {
|
|
494
|
+
const status = coerceStatus(rawValue);
|
|
495
|
+
if (status) {
|
|
496
|
+
plan.patch.status = status;
|
|
497
|
+
plan.topicUpdateArgs.status = status;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
function applyTopicVisibilityPatch(plan, rawValue) {
|
|
501
|
+
const visibility = coerceVisibility(rawValue);
|
|
502
|
+
if (visibility) {
|
|
503
|
+
plan.patch.visibility = visibility;
|
|
504
|
+
plan.topicUpdateArgs.visibility = visibility;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
function applyTopicProjectTypePatch(plan, rawValue) {
|
|
508
|
+
const projectType = readNonEmptyString(rawValue);
|
|
509
|
+
if (projectType) {
|
|
510
|
+
plan.nextMetadata.projectType = projectType;
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
plan.nextMetadata = omitMetadataKey(plan.nextMetadata, "projectType");
|
|
514
|
+
}
|
|
515
|
+
function applyTopicMetadataPatch(plan, key, rawValue) {
|
|
516
|
+
if (rawValue === void 0) {
|
|
517
|
+
plan.nextMetadata = omitMetadataKey(plan.nextMetadata, key);
|
|
518
|
+
return;
|
|
436
519
|
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
520
|
+
plan.nextMetadata[key] = rawValue;
|
|
521
|
+
}
|
|
522
|
+
async function applyTopicProjectOverlayPatch(ctx, topic, plan) {
|
|
440
523
|
if (typeof ctx.runMutation === "function") {
|
|
441
524
|
try {
|
|
442
|
-
await ctx.runMutation(api.topics.update, topicUpdateArgs);
|
|
525
|
+
await ctx.runMutation(api.topics.update, plan.topicUpdateArgs);
|
|
443
526
|
} catch (error) {
|
|
444
|
-
if (!
|
|
527
|
+
if (!canPatchTopicViaLocalDb(ctx, error)) {
|
|
445
528
|
throw error;
|
|
446
529
|
}
|
|
447
|
-
await ctx.db.patch(
|
|
530
|
+
await ctx.db.patch(topic._id, plan.patch);
|
|
448
531
|
}
|
|
449
532
|
} else if (ctx?.db && typeof ctx.db.patch === "function") {
|
|
450
|
-
await ctx.db.patch(
|
|
533
|
+
await ctx.db.patch(topic._id, plan.patch);
|
|
451
534
|
} else {
|
|
452
535
|
throw new Error(
|
|
453
536
|
"Cannot patch topic without component adapter (ctx.runMutation unavailable)"
|
|
454
537
|
);
|
|
455
538
|
}
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
metadata: nextMetadata
|
|
460
|
-
});
|
|
539
|
+
}
|
|
540
|
+
function canPatchTopicViaLocalDb(ctx, error) {
|
|
541
|
+
return isMissingLucernChildComponentError(error) && Boolean(ctx?.db) && typeof ctx.db?.patch === "function";
|
|
461
542
|
}
|
|
462
543
|
|
|
463
544
|
// src/resolvers.ts
|
|
@@ -485,7 +566,7 @@ async function patchProjectWithTolerance(ctx, projectId, value) {
|
|
|
485
566
|
try {
|
|
486
567
|
await patchTopicProjectOverlay(ctx, projectId, value);
|
|
487
568
|
} catch (error) {
|
|
488
|
-
if (!isAdvisoryTopicPatch(value)
|
|
569
|
+
if (!(isAdvisoryTopicPatch(value) && isMissingLucernChildComponentError2(error))) {
|
|
489
570
|
throw error;
|
|
490
571
|
}
|
|
491
572
|
console.warn(
|
|
@@ -518,6 +599,92 @@ function resolveGraphPrimitivesAppResolvers(_ctx) {
|
|
|
518
599
|
...resolverOverrides
|
|
519
600
|
};
|
|
520
601
|
}
|
|
602
|
+
|
|
603
|
+
// src/topicOntologyResolver.ts
|
|
604
|
+
var MAX_RESOLUTION_DEPTH = 10;
|
|
605
|
+
async function loadTopic(ctx, topicId) {
|
|
606
|
+
return await ctx.db.get(topicId);
|
|
607
|
+
}
|
|
608
|
+
async function loadOntologyDefinition(ctx, ontologyId) {
|
|
609
|
+
return await ctx.db.get(ontologyId);
|
|
610
|
+
}
|
|
611
|
+
async function loadPublishedOntologyVersions(ctx, ontologyId) {
|
|
612
|
+
const versions = await ctx.db.query("ontologyVersions").withIndex("by_ontologyId", (q) => q.eq("ontologyId", ontologyId)).collect();
|
|
613
|
+
return versions.filter((version) => version.status === "published").sort((a, b) => (b.publishedAt ?? 0) - (a.publishedAt ?? 0));
|
|
614
|
+
}
|
|
615
|
+
function resolvedTopicOntology(args) {
|
|
616
|
+
return {
|
|
617
|
+
ontologyId: args.ontologyDef._id,
|
|
618
|
+
ontologyKey: args.ontologyDef.ontologyKey,
|
|
619
|
+
ontologyName: args.ontologyDef.name,
|
|
620
|
+
publishedVersion: args.latestPublished,
|
|
621
|
+
source: args.source,
|
|
622
|
+
sourceTopicId: args.sourceTopicId,
|
|
623
|
+
tier: args.ontologyDef.tier,
|
|
624
|
+
validEdgeTypes: args.latestPublished?.edgeTypes.map((edgeType) => edgeType.value) ?? [],
|
|
625
|
+
validEntityTypes: args.latestPublished?.entityTypes.map((entityType) => entityType.value) ?? []
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
async function resolveCurrentTopicOntology(args) {
|
|
629
|
+
if (!args.current.ontologyId) {
|
|
630
|
+
return "continue";
|
|
631
|
+
}
|
|
632
|
+
const ontologyDef = await loadOntologyDefinition(
|
|
633
|
+
args.ctx,
|
|
634
|
+
args.current.ontologyId
|
|
635
|
+
);
|
|
636
|
+
if (!ontologyDef || ontologyDef.status === "archived") {
|
|
637
|
+
return args.current.parentTopicId ? "continue" : null;
|
|
638
|
+
}
|
|
639
|
+
const published = await loadPublishedOntologyVersions(
|
|
640
|
+
args.ctx,
|
|
641
|
+
args.current.ontologyId
|
|
642
|
+
);
|
|
643
|
+
return resolvedTopicOntology({
|
|
644
|
+
latestPublished: published[0] ?? null,
|
|
645
|
+
ontologyDef,
|
|
646
|
+
source: args.current._id === args.startTopicId ? "direct" : "inherited",
|
|
647
|
+
sourceTopicId: args.current._id
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
async function resolveTopicOntologyInternal(ctx, topicId) {
|
|
651
|
+
let current = await loadTopic(ctx, topicId);
|
|
652
|
+
if (!current) {
|
|
653
|
+
return null;
|
|
654
|
+
}
|
|
655
|
+
const startTopicId = topicId;
|
|
656
|
+
for (let i = 0; i < MAX_RESOLUTION_DEPTH && current; i++) {
|
|
657
|
+
const resolved = await resolveCurrentTopicOntology({
|
|
658
|
+
ctx,
|
|
659
|
+
current,
|
|
660
|
+
startTopicId
|
|
661
|
+
});
|
|
662
|
+
if (resolved !== "continue") {
|
|
663
|
+
return resolved;
|
|
664
|
+
}
|
|
665
|
+
if (!current.parentTopicId) {
|
|
666
|
+
break;
|
|
667
|
+
}
|
|
668
|
+
current = await loadTopic(ctx, current.parentTopicId);
|
|
669
|
+
}
|
|
670
|
+
return null;
|
|
671
|
+
}
|
|
672
|
+
async function validateEntityTypeForTopic(ctx, topicId, nodeType) {
|
|
673
|
+
const resolved = await resolveTopicOntologyInternal(ctx, topicId);
|
|
674
|
+
if (!resolved) {
|
|
675
|
+
return { valid: true };
|
|
676
|
+
}
|
|
677
|
+
if (resolved.validEntityTypes.length === 0) {
|
|
678
|
+
return { valid: true };
|
|
679
|
+
}
|
|
680
|
+
if (resolved.validEntityTypes.includes(nodeType)) {
|
|
681
|
+
return { valid: true };
|
|
682
|
+
}
|
|
683
|
+
return {
|
|
684
|
+
valid: false,
|
|
685
|
+
error: `Entity type "${nodeType}" is not defined in the ontology "${resolved.ontologyKey}" (${resolved.ontologyName}). Valid entity types: ${resolved.validEntityTypes.join(", ")}. Source: ${resolved.source} from topic ${resolved.sourceTopicId}.`
|
|
686
|
+
};
|
|
687
|
+
}
|
|
521
688
|
var LEGACY_SCOPE_FIELD2 = "graphScopeProjectId";
|
|
522
689
|
async function resolveTopicNodeScopeOrNull(ctx, ref) {
|
|
523
690
|
if (!ctx?.db || typeof ctx.db.query !== "function") {
|
|
@@ -552,13 +719,15 @@ function asMappedProjectId(topic) {
|
|
|
552
719
|
if (!topic) {
|
|
553
720
|
return;
|
|
554
721
|
}
|
|
555
|
-
const directLegacyProjectId = normalizeScopeValue(
|
|
722
|
+
const directLegacyProjectId = normalizeScopeValue(
|
|
723
|
+
topic[LEGACY_SCOPE_FIELD2]
|
|
724
|
+
);
|
|
556
725
|
if (directLegacyProjectId) {
|
|
557
726
|
return directLegacyProjectId;
|
|
558
727
|
}
|
|
559
728
|
const metadata = topic.metadata || {};
|
|
560
729
|
const candidate = metadata[LEGACY_SCOPE_FIELD2] || metadata.legacyProjectId || metadata.projectId || metadata.scopeProjectId;
|
|
561
|
-
return candidate ? candidate : void 0;
|
|
730
|
+
return typeof candidate === "string" ? normalizeScopeValue(candidate) : void 0;
|
|
562
731
|
}
|
|
563
732
|
function normalizeScopeValue(value) {
|
|
564
733
|
if (typeof value !== "string") {
|
|
@@ -583,8 +752,9 @@ function pickPrimaryTopic(candidates) {
|
|
|
583
752
|
})[0];
|
|
584
753
|
}
|
|
585
754
|
async function findTopicsByScopeAlias(ctx, scopeId) {
|
|
755
|
+
const query2 = ctx.db.query("topics");
|
|
586
756
|
try {
|
|
587
|
-
return await
|
|
757
|
+
return await query2.withIndex(
|
|
588
758
|
"by_graph_scope_project",
|
|
589
759
|
(q) => q.eq(LEGACY_SCOPE_FIELD2, scopeId)
|
|
590
760
|
).collect();
|
|
@@ -596,7 +766,7 @@ async function findTopicsByScopeAlias(ctx, scopeId) {
|
|
|
596
766
|
scopeId
|
|
597
767
|
}
|
|
598
768
|
);
|
|
599
|
-
const topics = await
|
|
769
|
+
const topics = await query2.collect();
|
|
600
770
|
return topics.filter((topic) => {
|
|
601
771
|
const normalizedGlobalId = normalizeScopeValue(topic.globalId);
|
|
602
772
|
const mappedProjectId = asMappedProjectId(topic);
|
|
@@ -652,187 +822,119 @@ async function resolveInheritedWorkspaceScope(ctx, topic) {
|
|
|
652
822
|
let current = topic;
|
|
653
823
|
for (let i = 0; i < MAX_DEPTH && current?.parentTopicId; i++) {
|
|
654
824
|
current = await ctx.db.get(current.parentTopicId);
|
|
655
|
-
if (!current)
|
|
825
|
+
if (!current) {
|
|
826
|
+
break;
|
|
827
|
+
}
|
|
656
828
|
if (!tenantId) {
|
|
657
829
|
tenantId = normalizeScopeValue(current.tenantId);
|
|
658
830
|
}
|
|
659
831
|
if (!workspaceId) {
|
|
660
832
|
workspaceId = normalizeScopeValue(current.workspaceId);
|
|
661
833
|
}
|
|
662
|
-
if (tenantId && workspaceId)
|
|
834
|
+
if (tenantId && workspaceId) {
|
|
835
|
+
break;
|
|
836
|
+
}
|
|
663
837
|
}
|
|
664
838
|
return { tenantId, workspaceId };
|
|
665
839
|
}
|
|
666
840
|
async function resolveTopicProjectScope(ctx, args) {
|
|
667
841
|
if (args.topicId) {
|
|
668
|
-
|
|
669
|
-
try {
|
|
670
|
-
topic = await ctx.db.get(
|
|
671
|
-
args.topicId
|
|
672
|
-
);
|
|
673
|
-
} catch (error) {
|
|
674
|
-
debugGraphPrimitiveFallback(
|
|
675
|
-
"[topicScope] Failed to load topic by direct id",
|
|
676
|
-
{
|
|
677
|
-
error,
|
|
678
|
-
topicId: args.topicId
|
|
679
|
-
}
|
|
680
|
-
);
|
|
681
|
-
}
|
|
682
|
-
if (!topic) {
|
|
683
|
-
topic = await tryResolveHostTopicById(ctx, String(args.topicId));
|
|
684
|
-
}
|
|
685
|
-
if (!topic) {
|
|
686
|
-
topic = pickPrimaryTopic(
|
|
687
|
-
await findTopicsByScopeAlias(ctx, String(args.topicId))
|
|
688
|
-
) ?? null;
|
|
689
|
-
}
|
|
690
|
-
if (!topic) {
|
|
691
|
-
const nodeScope = await resolveTopicNodeScopeOrNull(
|
|
692
|
-
ctx,
|
|
693
|
-
String(args.topicId)
|
|
694
|
-
);
|
|
695
|
-
if (nodeScope) {
|
|
696
|
-
return nodeScope;
|
|
697
|
-
}
|
|
698
|
-
throw new Error(`Topic not found: ${String(args.topicId)}`);
|
|
699
|
-
}
|
|
700
|
-
const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
|
|
701
|
-
const mapped = asMappedProjectId(topic);
|
|
702
|
-
if (mapped) {
|
|
703
|
-
return {
|
|
704
|
-
topicId: topic._id,
|
|
705
|
-
projectId: mapped,
|
|
706
|
-
tenantId: inherited.tenantId,
|
|
707
|
-
workspaceId: inherited.workspaceId,
|
|
708
|
-
source: "topic"
|
|
709
|
-
};
|
|
710
|
-
}
|
|
711
|
-
return {
|
|
712
|
-
topicId: topic._id,
|
|
713
|
-
tenantId: inherited.tenantId,
|
|
714
|
-
workspaceId: inherited.workspaceId,
|
|
715
|
-
source: "topic"
|
|
716
|
-
};
|
|
842
|
+
return await resolveScopeFromTopicId(ctx, args.topicId);
|
|
717
843
|
}
|
|
718
844
|
if (args.projectId) {
|
|
719
|
-
|
|
720
|
-
try {
|
|
721
|
-
directTopic = await ctx.db.get(
|
|
722
|
-
args.projectId
|
|
723
|
-
);
|
|
724
|
-
} catch (error) {
|
|
725
|
-
debugGraphPrimitiveFallback(
|
|
726
|
-
"[topicScope] Failed to load direct project topic",
|
|
727
|
-
{
|
|
728
|
-
error,
|
|
729
|
-
projectId: args.projectId
|
|
730
|
-
}
|
|
731
|
-
);
|
|
732
|
-
}
|
|
733
|
-
if (directTopic) {
|
|
734
|
-
const inherited = await resolveInheritedWorkspaceScope(ctx, directTopic);
|
|
735
|
-
const mapped = asMappedProjectId(directTopic);
|
|
736
|
-
return {
|
|
737
|
-
topicId: directTopic._id,
|
|
738
|
-
projectId: mapped ?? args.projectId,
|
|
739
|
-
tenantId: inherited.tenantId,
|
|
740
|
-
workspaceId: inherited.workspaceId,
|
|
741
|
-
source: "topic_inferred"
|
|
742
|
-
};
|
|
743
|
-
}
|
|
744
|
-
directTopic = await tryResolveHostTopicByLegacyScope(ctx, args.projectId);
|
|
745
|
-
if (directTopic) {
|
|
746
|
-
const inherited = await resolveInheritedWorkspaceScope(ctx, directTopic);
|
|
747
|
-
const mapped = asMappedProjectId(directTopic);
|
|
748
|
-
return {
|
|
749
|
-
topicId: directTopic._id,
|
|
750
|
-
projectId: mapped ?? args.projectId,
|
|
751
|
-
tenantId: inherited.tenantId,
|
|
752
|
-
workspaceId: inherited.workspaceId,
|
|
753
|
-
source: "topic_inferred"
|
|
754
|
-
};
|
|
755
|
-
}
|
|
756
|
-
const topics = await findTopicsByScopeAlias(ctx, args.projectId);
|
|
757
|
-
const primary = pickPrimaryTopic(topics);
|
|
758
|
-
if (primary) {
|
|
759
|
-
const inherited = await resolveInheritedWorkspaceScope(ctx, primary);
|
|
760
|
-
return {
|
|
761
|
-
topicId: primary._id,
|
|
762
|
-
projectId: args.projectId,
|
|
763
|
-
tenantId: inherited.tenantId,
|
|
764
|
-
workspaceId: inherited.workspaceId,
|
|
765
|
-
source: "project_mapped_topic"
|
|
766
|
-
};
|
|
767
|
-
}
|
|
768
|
-
const nodeScope = await resolveTopicNodeScopeOrNull(
|
|
769
|
-
ctx,
|
|
770
|
-
String(args.projectId)
|
|
771
|
-
);
|
|
772
|
-
if (nodeScope) {
|
|
773
|
-
return {
|
|
774
|
-
...nodeScope,
|
|
775
|
-
projectId: nodeScope.projectId ?? String(args.projectId)
|
|
776
|
-
};
|
|
777
|
-
}
|
|
778
|
-
throw new Error(
|
|
779
|
-
`Legacy project scope ${String(args.projectId)} has no mapped topic.`
|
|
780
|
-
);
|
|
845
|
+
return await resolveScopeFromLegacyProjectId(ctx, args.projectId);
|
|
781
846
|
}
|
|
782
847
|
throw new Error(
|
|
783
848
|
"Missing scope: provide topicId (preferred) or legacy projectId alias."
|
|
784
849
|
);
|
|
785
850
|
}
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
851
|
+
async function resolveScopeFromTopicId(ctx, topicId) {
|
|
852
|
+
const topic = await resolveTopicDocFromTopicId(ctx, topicId);
|
|
853
|
+
if (topic) {
|
|
854
|
+
return await buildTopicScope(ctx, topic, "topic");
|
|
855
|
+
}
|
|
856
|
+
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, String(topicId));
|
|
857
|
+
if (nodeScope) {
|
|
858
|
+
return nodeScope;
|
|
859
|
+
}
|
|
860
|
+
throw new Error(`Topic not found: ${String(topicId)}`);
|
|
793
861
|
}
|
|
794
|
-
async function
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
862
|
+
async function resolveTopicDocFromTopicId(ctx, topicId) {
|
|
863
|
+
const direct = await tryReadTopicDoc(ctx, topicId, {
|
|
864
|
+
failureLog: "[topicScope] Failed to load topic by direct id",
|
|
865
|
+
idLogKey: "topicId"
|
|
866
|
+
});
|
|
867
|
+
if (direct) {
|
|
868
|
+
return direct;
|
|
869
|
+
}
|
|
870
|
+
const hostTopic = await tryResolveHostTopicById(ctx, String(topicId));
|
|
871
|
+
if (hostTopic) {
|
|
872
|
+
return hostTopic;
|
|
801
873
|
}
|
|
874
|
+
return pickPrimaryTopic(await findTopicsByScopeAlias(ctx, String(topicId))) ?? null;
|
|
802
875
|
}
|
|
803
|
-
async function
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
876
|
+
async function resolveScopeFromLegacyProjectId(ctx, legacyProjectId) {
|
|
877
|
+
const directTopic = await resolveDirectLegacyProjectTopic(
|
|
878
|
+
ctx,
|
|
879
|
+
legacyProjectId
|
|
880
|
+
);
|
|
881
|
+
if (directTopic) {
|
|
882
|
+
return await buildTopicScope(ctx, directTopic, "topic_inferred", {
|
|
883
|
+
fallbackProjectId: legacyProjectId
|
|
884
|
+
});
|
|
810
885
|
}
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
886
|
+
const primary = pickPrimaryTopic(
|
|
887
|
+
await findTopicsByScopeAlias(ctx, legacyProjectId)
|
|
888
|
+
);
|
|
889
|
+
if (primary) {
|
|
890
|
+
return await buildTopicScope(ctx, primary, "project_mapped_topic", {
|
|
891
|
+
fallbackProjectId: legacyProjectId
|
|
892
|
+
});
|
|
815
893
|
}
|
|
816
|
-
await
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
894
|
+
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, legacyProjectId);
|
|
895
|
+
if (nodeScope) {
|
|
896
|
+
return {
|
|
897
|
+
...nodeScope,
|
|
898
|
+
projectId: nodeScope.projectId ?? legacyProjectId
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
throw new Error(
|
|
902
|
+
`Legacy project scope ${legacyProjectId} has no mapped topic.`
|
|
903
|
+
);
|
|
904
|
+
}
|
|
905
|
+
async function resolveDirectLegacyProjectTopic(ctx, legacyProjectId) {
|
|
906
|
+
const directTopic = await tryReadTopicDoc(ctx, legacyProjectId, {
|
|
907
|
+
failureLog: "[topicScope] Failed to load direct project topic",
|
|
908
|
+
idLogKey: "projectId"
|
|
909
|
+
});
|
|
910
|
+
return directTopic ?? tryResolveHostTopicByLegacyScope(ctx, legacyProjectId);
|
|
911
|
+
}
|
|
912
|
+
async function tryReadTopicDoc(ctx, id, log) {
|
|
913
|
+
try {
|
|
914
|
+
return await ctx.db.get(id);
|
|
915
|
+
} catch (error) {
|
|
916
|
+
debugGraphPrimitiveFallback(log.failureLog, {
|
|
917
|
+
error,
|
|
918
|
+
[log.idLogKey]: id
|
|
919
|
+
});
|
|
920
|
+
return null;
|
|
833
921
|
}
|
|
834
|
-
return ctx.db.insert("epistemicEdges", doc);
|
|
835
922
|
}
|
|
923
|
+
async function buildTopicScope(ctx, topic, source, options = {}) {
|
|
924
|
+
const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
|
|
925
|
+
const mapped = asMappedProjectId(topic);
|
|
926
|
+
return {
|
|
927
|
+
topicId: topic._id,
|
|
928
|
+
...mapped || options.fallbackProjectId ? { projectId: mapped ?? options.fallbackProjectId } : {},
|
|
929
|
+
tenantId: inherited.tenantId,
|
|
930
|
+
workspaceId: inherited.workspaceId,
|
|
931
|
+
source
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
var optionalScopeArgs = {
|
|
935
|
+
projectId: v.optional(v.string()),
|
|
936
|
+
topicId: v.optional(v.string())
|
|
937
|
+
};
|
|
836
938
|
|
|
837
939
|
// src/entityLifecycle.ts
|
|
838
940
|
async function requireAuthenticatedUserId(ctx) {
|
|
@@ -868,14 +970,227 @@ var ONTOLOGICAL_NODE_TYPE_SET = new Set(ONTOLOGICAL_NODE_TYPES);
|
|
|
868
970
|
var isOntologicalNodeType = ONTOLOGICAL_NODE_TYPE_SET.has.bind(
|
|
869
971
|
ONTOLOGICAL_NODE_TYPE_SET
|
|
870
972
|
);
|
|
973
|
+
function isRecord(value) {
|
|
974
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
975
|
+
}
|
|
976
|
+
function readConvexId(value) {
|
|
977
|
+
return typeof value === "string" && value.trim().length > 0 ? value : void 0;
|
|
978
|
+
}
|
|
979
|
+
function readOptionalString(value) {
|
|
980
|
+
return typeof value === "string" && value.trim().length > 0 ? value : void 0;
|
|
981
|
+
}
|
|
982
|
+
function readRecord(value) {
|
|
983
|
+
return isRecord(value) ? value : void 0;
|
|
984
|
+
}
|
|
985
|
+
function readStringArray2(value) {
|
|
986
|
+
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : void 0;
|
|
987
|
+
}
|
|
988
|
+
function readEntityUpdateMetadata(value) {
|
|
989
|
+
if (!isRecord(value)) {
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
const metadata = {};
|
|
993
|
+
const crunchbase = readOptionalString(value.crunchbase);
|
|
994
|
+
const linkedin = readOptionalString(value.linkedin);
|
|
995
|
+
const pitchbook = readOptionalString(value.pitchbook);
|
|
996
|
+
const twitter = readOptionalString(value.twitter);
|
|
997
|
+
const website = readOptionalString(value.website);
|
|
998
|
+
if (crunchbase !== void 0) {
|
|
999
|
+
metadata.crunchbase = crunchbase;
|
|
1000
|
+
}
|
|
1001
|
+
if (linkedin !== void 0) {
|
|
1002
|
+
metadata.linkedin = linkedin;
|
|
1003
|
+
}
|
|
1004
|
+
if (pitchbook !== void 0) {
|
|
1005
|
+
metadata.pitchbook = pitchbook;
|
|
1006
|
+
}
|
|
1007
|
+
if (twitter !== void 0) {
|
|
1008
|
+
metadata.twitter = twitter;
|
|
1009
|
+
}
|
|
1010
|
+
if (website !== void 0) {
|
|
1011
|
+
metadata.website = website;
|
|
1012
|
+
}
|
|
1013
|
+
return metadata;
|
|
1014
|
+
}
|
|
1015
|
+
function readCanonicalEntityRecord(value) {
|
|
1016
|
+
if (!isRecord(value)) {
|
|
1017
|
+
return null;
|
|
1018
|
+
}
|
|
1019
|
+
const id = readConvexId(value._id);
|
|
1020
|
+
if (!id) {
|
|
1021
|
+
return null;
|
|
1022
|
+
}
|
|
1023
|
+
const record = { _id: id };
|
|
1024
|
+
const canonicalText = readOptionalString(value.canonicalText);
|
|
1025
|
+
const contentHash = readOptionalString(value.contentHash);
|
|
1026
|
+
const domain = readOptionalString(value.domain);
|
|
1027
|
+
const epistemicLayer = readOptionalString(value.epistemicLayer);
|
|
1028
|
+
const externalIds = readRecord(value.externalIds);
|
|
1029
|
+
const globalId = readOptionalString(value.globalId);
|
|
1030
|
+
const metadata = readRecord(value.metadata);
|
|
1031
|
+
const nodeType = readOptionalString(value.nodeType);
|
|
1032
|
+
const projectId = readOptionalString(value.projectId);
|
|
1033
|
+
const status = readOptionalString(value.status);
|
|
1034
|
+
const subtype = readOptionalString(value.subtype);
|
|
1035
|
+
const tags = readStringArray2(value.tags);
|
|
1036
|
+
const tenantId = readOptionalString(value.tenantId);
|
|
1037
|
+
const title = readOptionalString(value.title);
|
|
1038
|
+
const verificationStatus = readOptionalString(value.verificationStatus);
|
|
1039
|
+
const workspaceId = readOptionalString(value.workspaceId);
|
|
1040
|
+
if (canonicalText !== void 0) {
|
|
1041
|
+
record.canonicalText = canonicalText;
|
|
1042
|
+
}
|
|
1043
|
+
if (contentHash !== void 0) {
|
|
1044
|
+
record.contentHash = contentHash;
|
|
1045
|
+
}
|
|
1046
|
+
if (domain !== void 0) {
|
|
1047
|
+
record.domain = domain;
|
|
1048
|
+
}
|
|
1049
|
+
if (epistemicLayer !== void 0) {
|
|
1050
|
+
record.epistemicLayer = epistemicLayer;
|
|
1051
|
+
}
|
|
1052
|
+
if (externalIds !== void 0) {
|
|
1053
|
+
record.externalIds = externalIds;
|
|
1054
|
+
}
|
|
1055
|
+
if (globalId !== void 0) {
|
|
1056
|
+
record.globalId = globalId;
|
|
1057
|
+
}
|
|
1058
|
+
if (metadata !== void 0) {
|
|
1059
|
+
record.metadata = metadata;
|
|
1060
|
+
}
|
|
1061
|
+
if (nodeType !== void 0) {
|
|
1062
|
+
record.nodeType = nodeType;
|
|
1063
|
+
}
|
|
1064
|
+
if (projectId !== void 0) {
|
|
1065
|
+
record.projectId = projectId;
|
|
1066
|
+
}
|
|
1067
|
+
if (status !== void 0) {
|
|
1068
|
+
record.status = status;
|
|
1069
|
+
}
|
|
1070
|
+
if (subtype !== void 0) {
|
|
1071
|
+
record.subtype = subtype;
|
|
1072
|
+
}
|
|
1073
|
+
if (tags !== void 0) {
|
|
1074
|
+
record.tags = tags;
|
|
1075
|
+
}
|
|
1076
|
+
if (tenantId !== void 0) {
|
|
1077
|
+
record.tenantId = tenantId;
|
|
1078
|
+
}
|
|
1079
|
+
if (title !== void 0) {
|
|
1080
|
+
record.title = title;
|
|
1081
|
+
}
|
|
1082
|
+
if (verificationStatus !== void 0) {
|
|
1083
|
+
record.verificationStatus = verificationStatus;
|
|
1084
|
+
}
|
|
1085
|
+
if (workspaceId !== void 0) {
|
|
1086
|
+
record.workspaceId = workspaceId;
|
|
1087
|
+
}
|
|
1088
|
+
return record;
|
|
1089
|
+
}
|
|
1090
|
+
function readEntityEdgeRecord(value) {
|
|
1091
|
+
if (!isRecord(value)) {
|
|
1092
|
+
return null;
|
|
1093
|
+
}
|
|
1094
|
+
const id = readConvexId(value._id);
|
|
1095
|
+
if (!id) {
|
|
1096
|
+
return null;
|
|
1097
|
+
}
|
|
1098
|
+
const edge = { _id: id };
|
|
1099
|
+
const fromNodeId = readConvexId(value.fromNodeId);
|
|
1100
|
+
const toNodeId = readConvexId(value.toNodeId);
|
|
1101
|
+
if (fromNodeId !== void 0) {
|
|
1102
|
+
edge.fromNodeId = fromNodeId;
|
|
1103
|
+
}
|
|
1104
|
+
if (toNodeId !== void 0) {
|
|
1105
|
+
edge.toNodeId = toNodeId;
|
|
1106
|
+
}
|
|
1107
|
+
return edge;
|
|
1108
|
+
}
|
|
1109
|
+
function readRowList(values, reader) {
|
|
1110
|
+
return values.flatMap((value) => {
|
|
1111
|
+
const row = reader(value);
|
|
1112
|
+
return row ? [row] : [];
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
function readScopeTopicDoc(value) {
|
|
1116
|
+
if (!isRecord(value)) {
|
|
1117
|
+
return null;
|
|
1118
|
+
}
|
|
1119
|
+
const id = readConvexId(value._id);
|
|
1120
|
+
if (!id) {
|
|
1121
|
+
return null;
|
|
1122
|
+
}
|
|
1123
|
+
const topic = { _id: id };
|
|
1124
|
+
const tenantId = readOptionalString(value.tenantId);
|
|
1125
|
+
const workspaceId = readOptionalString(value.workspaceId);
|
|
1126
|
+
if (tenantId !== void 0) {
|
|
1127
|
+
topic.tenantId = tenantId;
|
|
1128
|
+
}
|
|
1129
|
+
if (workspaceId !== void 0) {
|
|
1130
|
+
topic.workspaceId = workspaceId;
|
|
1131
|
+
}
|
|
1132
|
+
return topic;
|
|
1133
|
+
}
|
|
1134
|
+
function readScopeProjectDoc(value) {
|
|
1135
|
+
if (!isRecord(value)) {
|
|
1136
|
+
return null;
|
|
1137
|
+
}
|
|
1138
|
+
const id = readOptionalString(value._id);
|
|
1139
|
+
if (!id) {
|
|
1140
|
+
return null;
|
|
1141
|
+
}
|
|
1142
|
+
const project = { _id: id };
|
|
1143
|
+
const tenantId = readOptionalString(value.tenantId);
|
|
1144
|
+
const workspaceId = readOptionalString(value.workspaceId);
|
|
1145
|
+
if (tenantId !== void 0) {
|
|
1146
|
+
project.tenantId = tenantId;
|
|
1147
|
+
}
|
|
1148
|
+
if (workspaceId !== void 0) {
|
|
1149
|
+
project.workspaceId = workspaceId;
|
|
1150
|
+
}
|
|
1151
|
+
return project;
|
|
1152
|
+
}
|
|
1153
|
+
function readEntityUpdateNode(value) {
|
|
1154
|
+
const node = readCanonicalEntityRecord(value);
|
|
1155
|
+
const nodeType = node?.nodeType;
|
|
1156
|
+
if (!(node?.canonicalText && nodeType && isOntologicalNodeType(nodeType))) {
|
|
1157
|
+
return null;
|
|
1158
|
+
}
|
|
1159
|
+
return {
|
|
1160
|
+
_id: node._id,
|
|
1161
|
+
canonicalText: node.canonicalText,
|
|
1162
|
+
domain: node.domain,
|
|
1163
|
+
externalIds: readEntityUpdateMetadata(node.externalIds),
|
|
1164
|
+
globalId: node.globalId,
|
|
1165
|
+
metadata: node.metadata,
|
|
1166
|
+
nodeType,
|
|
1167
|
+
projectId: node.projectId,
|
|
1168
|
+
status: node.status,
|
|
1169
|
+
subtype: node.subtype,
|
|
1170
|
+
tags: node.tags,
|
|
1171
|
+
tenantId: node.tenantId,
|
|
1172
|
+
title: node.title,
|
|
1173
|
+
verificationStatus: node.verificationStatus
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
871
1176
|
async function resolveCanonicalEntityScope(ctx, args) {
|
|
872
1177
|
const scope = await resolveTopicProjectScope(ctx, args);
|
|
873
|
-
const topic = scope.topicId ? await ctx.db.get(scope.topicId)
|
|
874
|
-
const project = scope.projectId
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
1178
|
+
const topic = scope.topicId ? readScopeTopicDoc(await ctx.db.get(scope.topicId)) : null;
|
|
1179
|
+
const project = scope.projectId === void 0 ? null : readScopeProjectDoc(
|
|
1180
|
+
await resolveGraphPrimitivesAppResolvers().getProject(
|
|
1181
|
+
ctx,
|
|
1182
|
+
scope.projectId
|
|
1183
|
+
)
|
|
1184
|
+
);
|
|
1185
|
+
let tenantId;
|
|
1186
|
+
if (typeof topic?.tenantId === "string") {
|
|
1187
|
+
const trimmedTopicTenantId = topic.tenantId.trim();
|
|
1188
|
+
tenantId = trimmedTopicTenantId.length > 0 ? trimmedTopicTenantId : void 0;
|
|
1189
|
+
}
|
|
1190
|
+
if (tenantId === void 0 && typeof project?.tenantId === "string") {
|
|
1191
|
+
const trimmedProjectTenantId = project.tenantId.trim();
|
|
1192
|
+
tenantId = trimmedProjectTenantId.length > 0 ? trimmedProjectTenantId : void 0;
|
|
1193
|
+
}
|
|
879
1194
|
return {
|
|
880
1195
|
...scope,
|
|
881
1196
|
tenantId,
|
|
@@ -885,10 +1200,13 @@ async function resolveCanonicalEntityScope(ctx, args) {
|
|
|
885
1200
|
}
|
|
886
1201
|
async function findExistingCanonicalEntity(ctx, args) {
|
|
887
1202
|
const contentHash = generateContentHash(args.nodeType, args.canonicalText);
|
|
888
|
-
const candidates =
|
|
889
|
-
"
|
|
890
|
-
|
|
891
|
-
|
|
1203
|
+
const candidates = readRowList(
|
|
1204
|
+
await ctx.db.query("epistemicNodes").withIndex(
|
|
1205
|
+
"by_contentHash",
|
|
1206
|
+
(q) => q.eq("contentHash", contentHash)
|
|
1207
|
+
).collect(),
|
|
1208
|
+
readCanonicalEntityRecord
|
|
1209
|
+
);
|
|
892
1210
|
return candidates.find(
|
|
893
1211
|
(node) => matchesCanonicalEntityRecord(node, {
|
|
894
1212
|
nodeType: args.nodeType,
|
|
@@ -898,13 +1216,29 @@ async function findExistingCanonicalEntity(ctx, args) {
|
|
|
898
1216
|
) ?? null;
|
|
899
1217
|
}
|
|
900
1218
|
async function listCanonicalEntitiesForScope(ctx, args) {
|
|
901
|
-
|
|
902
|
-
"
|
|
903
|
-
|
|
904
|
-
)
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
1219
|
+
let baseRows = readRowList(
|
|
1220
|
+
await ctx.db.query("epistemicNodes").collect(),
|
|
1221
|
+
readCanonicalEntityRecord
|
|
1222
|
+
);
|
|
1223
|
+
if (args.tenantId === void 0) {
|
|
1224
|
+
if (args.nodeType !== void 0) {
|
|
1225
|
+
baseRows = readRowList(
|
|
1226
|
+
await ctx.db.query("epistemicNodes").withIndex(
|
|
1227
|
+
"by_nodeType",
|
|
1228
|
+
(q) => q.eq("nodeType", args.nodeType)
|
|
1229
|
+
).collect(),
|
|
1230
|
+
readCanonicalEntityRecord
|
|
1231
|
+
);
|
|
1232
|
+
}
|
|
1233
|
+
} else {
|
|
1234
|
+
baseRows = readRowList(
|
|
1235
|
+
await ctx.db.query("epistemicNodes").withIndex(
|
|
1236
|
+
"by_tenantId",
|
|
1237
|
+
(q) => q.eq("tenantId", args.tenantId)
|
|
1238
|
+
).collect(),
|
|
1239
|
+
readCanonicalEntityRecord
|
|
1240
|
+
);
|
|
1241
|
+
}
|
|
908
1242
|
const normalizedSearch = typeof args.searchTerm === "string" && args.searchTerm.trim().length > 0 ? normalizeCanonicalEntityText(args.searchTerm) : void 0;
|
|
909
1243
|
return baseRows.filter(
|
|
910
1244
|
(node) => matchesCanonicalEntityRecord(node, {
|
|
@@ -995,11 +1329,7 @@ var createEntity = mutation({
|
|
|
995
1329
|
projectId: args.projectId
|
|
996
1330
|
});
|
|
997
1331
|
if (scope.projectId) {
|
|
998
|
-
await requireScopeWriteAccess(
|
|
999
|
-
ctx,
|
|
1000
|
-
scope.projectId,
|
|
1001
|
-
authenticatedUserId
|
|
1002
|
-
);
|
|
1332
|
+
await requireScopeWriteAccess(ctx, scope.projectId, authenticatedUserId);
|
|
1003
1333
|
}
|
|
1004
1334
|
if (scope.topicId) {
|
|
1005
1335
|
const ontologyValidation = await validateEntityTypeForTopic(
|
|
@@ -1018,7 +1348,7 @@ var createEntity = mutation({
|
|
|
1018
1348
|
});
|
|
1019
1349
|
}
|
|
1020
1350
|
}
|
|
1021
|
-
const metadata = args.metadata
|
|
1351
|
+
const metadata = readRecord(args.metadata) ?? {};
|
|
1022
1352
|
const validation = await validateEntityMetadata(
|
|
1023
1353
|
ctx,
|
|
1024
1354
|
args.nodeType,
|
|
@@ -1093,6 +1423,100 @@ var createEntity = mutation({
|
|
|
1093
1423
|
return { nodeId, globalId: entityGlobalId, isDuplicate: false };
|
|
1094
1424
|
}
|
|
1095
1425
|
});
|
|
1426
|
+
function normalizeEntityMetadataPatch(metadata) {
|
|
1427
|
+
return metadata === null || metadata === void 0 ? {} : metadata;
|
|
1428
|
+
}
|
|
1429
|
+
function resolveScopeProjectId(node) {
|
|
1430
|
+
return typeof node.projectId === "string" && node.projectId.trim().length > 0 ? node.projectId : void 0;
|
|
1431
|
+
}
|
|
1432
|
+
function prepareEntityAttributePlan(node, args) {
|
|
1433
|
+
const existingMetadata = node.metadata ?? {};
|
|
1434
|
+
const mergedMetadata = {
|
|
1435
|
+
...existingMetadata,
|
|
1436
|
+
...normalizeEntityMetadataPatch(args.metadata)
|
|
1437
|
+
};
|
|
1438
|
+
if (args.subtype !== void 0) {
|
|
1439
|
+
mergedMetadata.subtype = args.subtype;
|
|
1440
|
+
}
|
|
1441
|
+
const nextCanonicalText = args.canonicalText === void 0 ? node.canonicalText : args.canonicalText.trim();
|
|
1442
|
+
if (nextCanonicalText.length === 0) {
|
|
1443
|
+
throwStructuredMutationError({
|
|
1444
|
+
message: "canonicalText cannot be empty.",
|
|
1445
|
+
status: 400,
|
|
1446
|
+
code: "INVALID_REQUEST",
|
|
1447
|
+
invariantCode: "entity.update_requires_entity_lifecycle"
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1450
|
+
return {
|
|
1451
|
+
nextCanonicalText,
|
|
1452
|
+
existingMetadata,
|
|
1453
|
+
mergedMetadata
|
|
1454
|
+
};
|
|
1455
|
+
}
|
|
1456
|
+
async function loadEntityForUpdate(ctx, nodeId) {
|
|
1457
|
+
const node = readEntityUpdateNode(await ctx.db.get(nodeId));
|
|
1458
|
+
if (!node) {
|
|
1459
|
+
throwStructuredMutationError({
|
|
1460
|
+
message: "Entity not found.",
|
|
1461
|
+
status: 404,
|
|
1462
|
+
code: "NOT_FOUND",
|
|
1463
|
+
details: { nodeId }
|
|
1464
|
+
});
|
|
1465
|
+
}
|
|
1466
|
+
return node;
|
|
1467
|
+
}
|
|
1468
|
+
async function assertCanonicalEntityUnique(ctx, node, nodeId, canonicalText) {
|
|
1469
|
+
const duplicate = await findExistingCanonicalEntity(ctx, {
|
|
1470
|
+
nodeType: node.nodeType,
|
|
1471
|
+
canonicalText,
|
|
1472
|
+
tenantId: typeof node.tenantId === "string" ? node.tenantId : void 0
|
|
1473
|
+
});
|
|
1474
|
+
if (duplicate && String(duplicate._id) !== String(nodeId)) {
|
|
1475
|
+
throwStructuredMutationError({
|
|
1476
|
+
message: "A canonical entity with this name already exists in the tenant scope.",
|
|
1477
|
+
status: 409,
|
|
1478
|
+
code: "CONFLICT",
|
|
1479
|
+
invariantCode: "entity.canonical_text_unique_per_tenant",
|
|
1480
|
+
suggestion: "Merge the duplicate entity instead of renaming this node onto an existing canonical record.",
|
|
1481
|
+
details: {
|
|
1482
|
+
nodeId,
|
|
1483
|
+
duplicateNodeId: duplicate._id,
|
|
1484
|
+
canonicalText
|
|
1485
|
+
}
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
function buildEntityAttributeUpdates(node, args, plan) {
|
|
1490
|
+
const updates = {
|
|
1491
|
+
metadata: plan.mergedMetadata
|
|
1492
|
+
};
|
|
1493
|
+
if (args.canonicalText !== void 0) {
|
|
1494
|
+
updates.canonicalText = plan.nextCanonicalText;
|
|
1495
|
+
updates.contentHash = generateContentHash(
|
|
1496
|
+
node.nodeType,
|
|
1497
|
+
plan.nextCanonicalText
|
|
1498
|
+
);
|
|
1499
|
+
updates.title = args.title === void 0 ? buildEntityTitle(plan.nextCanonicalText) : args.title;
|
|
1500
|
+
} else if (args.title !== void 0) {
|
|
1501
|
+
updates.title = args.title;
|
|
1502
|
+
}
|
|
1503
|
+
if (args.subtype !== void 0) {
|
|
1504
|
+
updates.subtype = args.subtype;
|
|
1505
|
+
}
|
|
1506
|
+
if (args.domain !== void 0) {
|
|
1507
|
+
updates.domain = args.domain;
|
|
1508
|
+
}
|
|
1509
|
+
if (args.tags !== void 0) {
|
|
1510
|
+
updates.tags = args.tags;
|
|
1511
|
+
}
|
|
1512
|
+
if (args.verificationStatus !== void 0) {
|
|
1513
|
+
updates.verificationStatus = args.verificationStatus;
|
|
1514
|
+
}
|
|
1515
|
+
if (args.externalIds !== void 0) {
|
|
1516
|
+
updates.externalIds = args.externalIds;
|
|
1517
|
+
}
|
|
1518
|
+
return updates;
|
|
1519
|
+
}
|
|
1096
1520
|
var updateEntityAttributes = mutation({
|
|
1097
1521
|
args: {
|
|
1098
1522
|
nodeId: v.id("epistemicNodes"),
|
|
@@ -1118,68 +1542,33 @@ var updateEntityAttributes = mutation({
|
|
|
1118
1542
|
handler: async (ctx, args) => {
|
|
1119
1543
|
const authenticatedUserId = await requireAuthenticatedUserId(ctx);
|
|
1120
1544
|
const now = Date.now();
|
|
1121
|
-
const node = await ctx
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
message: "Entity not found.",
|
|
1125
|
-
status: 404,
|
|
1126
|
-
code: "NOT_FOUND",
|
|
1127
|
-
details: { nodeId: args.nodeId }
|
|
1128
|
-
});
|
|
1129
|
-
}
|
|
1130
|
-
if (!isOntologicalNodeType(node.nodeType)) {
|
|
1131
|
-
throwStructuredMutationError({
|
|
1132
|
-
message: `Node "${args.nodeId}" is a ${node.nodeType}, not an entity. Use belief/question APIs for epistemic nodes.`,
|
|
1133
|
-
status: 400,
|
|
1134
|
-
code: "LIFECYCLE_VIOLATION",
|
|
1135
|
-
invariantCode: "entity.type_check",
|
|
1136
|
-
suggestion: "Only ontological entities can be updated via updateEntityAttributes."
|
|
1137
|
-
});
|
|
1138
|
-
}
|
|
1139
|
-
const scopeProjectId = typeof node.projectId === "string" && node.projectId.trim().length > 0 ? node.projectId : void 0;
|
|
1140
|
-
if (scopeProjectId) {
|
|
1545
|
+
const node = await loadEntityForUpdate(ctx, args.nodeId);
|
|
1546
|
+
const scopeProjectId = resolveScopeProjectId(node);
|
|
1547
|
+
if (scopeProjectId !== void 0) {
|
|
1141
1548
|
await requireScopeWriteAccess(ctx, scopeProjectId, authenticatedUserId);
|
|
1142
1549
|
}
|
|
1143
|
-
const
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
code: "INVALID_REQUEST",
|
|
1154
|
-
invariantCode: "entity.update_requires_entity_lifecycle"
|
|
1155
|
-
});
|
|
1156
|
-
}
|
|
1157
|
-
const nextCanonicalText = typeof args.canonicalText === "string" && args.canonicalText.trim().length > 0 ? args.canonicalText.trim() : node.canonicalText;
|
|
1550
|
+
const plan = prepareEntityAttributePlan(node, {
|
|
1551
|
+
canonicalText: args.canonicalText,
|
|
1552
|
+
metadata: args.metadata,
|
|
1553
|
+
subtype: args.subtype,
|
|
1554
|
+
domain: args.domain,
|
|
1555
|
+
title: args.title,
|
|
1556
|
+
tags: args.tags,
|
|
1557
|
+
verificationStatus: args.verificationStatus,
|
|
1558
|
+
externalIds: args.externalIds
|
|
1559
|
+
});
|
|
1158
1560
|
if (args.canonicalText !== void 0) {
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
throwStructuredMutationError({
|
|
1166
|
-
message: "A canonical entity with this name already exists in the tenant scope.",
|
|
1167
|
-
status: 409,
|
|
1168
|
-
code: "CONFLICT",
|
|
1169
|
-
invariantCode: "entity.canonical_text_unique_per_tenant",
|
|
1170
|
-
suggestion: "Merge the duplicate entity instead of renaming this node onto an existing canonical record.",
|
|
1171
|
-
details: {
|
|
1172
|
-
nodeId: args.nodeId,
|
|
1173
|
-
duplicateNodeId: duplicate._id,
|
|
1174
|
-
canonicalText: nextCanonicalText
|
|
1175
|
-
}
|
|
1176
|
-
});
|
|
1177
|
-
}
|
|
1561
|
+
await assertCanonicalEntityUnique(
|
|
1562
|
+
ctx,
|
|
1563
|
+
node,
|
|
1564
|
+
args.nodeId,
|
|
1565
|
+
plan.nextCanonicalText
|
|
1566
|
+
);
|
|
1178
1567
|
}
|
|
1179
1568
|
const validation = await validateEntityMetadata(
|
|
1180
1569
|
ctx,
|
|
1181
1570
|
node.nodeType,
|
|
1182
|
-
mergedMetadata,
|
|
1571
|
+
plan.mergedMetadata,
|
|
1183
1572
|
typeof node.tenantId === "string" ? node.tenantId : void 0
|
|
1184
1573
|
);
|
|
1185
1574
|
if (!validation.valid) {
|
|
@@ -1192,31 +1581,22 @@ var updateEntityAttributes = mutation({
|
|
|
1192
1581
|
});
|
|
1193
1582
|
}
|
|
1194
1583
|
const updates = {
|
|
1195
|
-
|
|
1584
|
+
...buildEntityAttributeUpdates(
|
|
1585
|
+
node,
|
|
1586
|
+
{
|
|
1587
|
+
canonicalText: args.canonicalText,
|
|
1588
|
+
title: args.title,
|
|
1589
|
+
metadata: args.metadata,
|
|
1590
|
+
subtype: args.subtype,
|
|
1591
|
+
domain: args.domain,
|
|
1592
|
+
tags: args.tags,
|
|
1593
|
+
verificationStatus: args.verificationStatus,
|
|
1594
|
+
externalIds: args.externalIds
|
|
1595
|
+
},
|
|
1596
|
+
plan
|
|
1597
|
+
),
|
|
1196
1598
|
updatedAt: now
|
|
1197
1599
|
};
|
|
1198
|
-
if (args.canonicalText !== void 0) {
|
|
1199
|
-
updates.canonicalText = nextCanonicalText;
|
|
1200
|
-
updates.contentHash = generateContentHash(node.nodeType, nextCanonicalText);
|
|
1201
|
-
updates.title = args.title !== void 0 ? args.title : buildEntityTitle(nextCanonicalText);
|
|
1202
|
-
} else if (args.title !== void 0) {
|
|
1203
|
-
updates.title = args.title;
|
|
1204
|
-
}
|
|
1205
|
-
if (args.subtype !== void 0) {
|
|
1206
|
-
updates.subtype = args.subtype;
|
|
1207
|
-
}
|
|
1208
|
-
if (args.domain !== void 0) {
|
|
1209
|
-
updates.domain = args.domain;
|
|
1210
|
-
}
|
|
1211
|
-
if (args.tags !== void 0) {
|
|
1212
|
-
updates.tags = args.tags;
|
|
1213
|
-
}
|
|
1214
|
-
if (args.verificationStatus !== void 0) {
|
|
1215
|
-
updates.verificationStatus = args.verificationStatus;
|
|
1216
|
-
}
|
|
1217
|
-
if (args.externalIds !== void 0) {
|
|
1218
|
-
updates.externalIds = args.externalIds;
|
|
1219
|
-
}
|
|
1220
1600
|
await ctx.db.patch(args.nodeId, updates);
|
|
1221
1601
|
await ctx.scheduler.runAfter(0, internal.neo4jSync.syncNodeToNeo4j, {
|
|
1222
1602
|
nodeId: args.nodeId,
|
|
@@ -1237,7 +1617,7 @@ var updateEntityAttributes = mutation({
|
|
|
1237
1617
|
tags: node.tags,
|
|
1238
1618
|
verificationStatus: node.verificationStatus,
|
|
1239
1619
|
externalIds: node.externalIds,
|
|
1240
|
-
metadata: existingMetadata
|
|
1620
|
+
metadata: plan.existingMetadata
|
|
1241
1621
|
},
|
|
1242
1622
|
newState: updates,
|
|
1243
1623
|
projectId: scopeProjectId
|
|
@@ -1254,9 +1634,13 @@ var mergeEntities = mutation({
|
|
|
1254
1634
|
handler: async (ctx, args) => {
|
|
1255
1635
|
const authenticatedUserId = await requireAuthenticatedUserId(ctx);
|
|
1256
1636
|
const now = Date.now();
|
|
1257
|
-
const canonical =
|
|
1258
|
-
|
|
1259
|
-
|
|
1637
|
+
const canonical = readEntityUpdateNode(
|
|
1638
|
+
await ctx.db.get(args.canonicalNodeId)
|
|
1639
|
+
);
|
|
1640
|
+
const duplicate = readEntityUpdateNode(
|
|
1641
|
+
await ctx.db.get(args.duplicateNodeId)
|
|
1642
|
+
);
|
|
1643
|
+
if (!(canonical && duplicate)) {
|
|
1260
1644
|
throwStructuredMutationError({
|
|
1261
1645
|
message: "One or both entity nodes not found.",
|
|
1262
1646
|
status: 404,
|
|
@@ -1267,14 +1651,6 @@ var mergeEntities = mutation({
|
|
|
1267
1651
|
}
|
|
1268
1652
|
});
|
|
1269
1653
|
}
|
|
1270
|
-
if (!isOntologicalNodeType(canonical.nodeType) || !isOntologicalNodeType(duplicate.nodeType)) {
|
|
1271
|
-
throwStructuredMutationError({
|
|
1272
|
-
message: "Both nodes must be ontological entities to merge.",
|
|
1273
|
-
status: 400,
|
|
1274
|
-
code: "LIFECYCLE_VIOLATION",
|
|
1275
|
-
invariantCode: "entity.type_check"
|
|
1276
|
-
});
|
|
1277
|
-
}
|
|
1278
1654
|
if (args.canonicalNodeId === args.duplicateNodeId) {
|
|
1279
1655
|
throwStructuredMutationError({
|
|
1280
1656
|
message: "Cannot merge an entity with itself.",
|
|
@@ -1285,6 +1661,18 @@ var mergeEntities = mutation({
|
|
|
1285
1661
|
details: { canonicalNodeId: args.canonicalNodeId }
|
|
1286
1662
|
});
|
|
1287
1663
|
}
|
|
1664
|
+
if (!(canonical.globalId && duplicate.globalId)) {
|
|
1665
|
+
throwStructuredMutationError({
|
|
1666
|
+
message: "Both entity nodes must have globalId values to merge.",
|
|
1667
|
+
status: 400,
|
|
1668
|
+
code: "LIFECYCLE_VIOLATION",
|
|
1669
|
+
invariantCode: "entity.global_id_required",
|
|
1670
|
+
details: {
|
|
1671
|
+
canonicalNodeId: args.canonicalNodeId,
|
|
1672
|
+
duplicateNodeId: args.duplicateNodeId
|
|
1673
|
+
}
|
|
1674
|
+
});
|
|
1675
|
+
}
|
|
1288
1676
|
if (canonical.nodeType !== duplicate.nodeType) {
|
|
1289
1677
|
throwStructuredMutationError({
|
|
1290
1678
|
message: `Cannot merge different entity types: ${canonical.nodeType} vs ${duplicate.nodeType}.`,
|
|
@@ -1314,10 +1702,13 @@ var mergeEntities = mutation({
|
|
|
1314
1702
|
createdAt: now,
|
|
1315
1703
|
updatedAt: now
|
|
1316
1704
|
});
|
|
1317
|
-
const outgoingEdges =
|
|
1318
|
-
"
|
|
1319
|
-
|
|
1320
|
-
|
|
1705
|
+
const outgoingEdges = readRowList(
|
|
1706
|
+
await ctx.db.query("epistemicEdges").withIndex(
|
|
1707
|
+
"by_from",
|
|
1708
|
+
(q) => q.eq("fromNodeId", args.duplicateNodeId)
|
|
1709
|
+
).collect(),
|
|
1710
|
+
readEntityEdgeRecord
|
|
1711
|
+
);
|
|
1321
1712
|
for (const edge of outgoingEdges) {
|
|
1322
1713
|
if (edge.toNodeId === args.canonicalNodeId) {
|
|
1323
1714
|
continue;
|
|
@@ -1328,10 +1719,13 @@ var mergeEntities = mutation({
|
|
|
1328
1719
|
updatedAt: now
|
|
1329
1720
|
});
|
|
1330
1721
|
}
|
|
1331
|
-
const incomingEdges =
|
|
1332
|
-
"
|
|
1333
|
-
|
|
1334
|
-
|
|
1722
|
+
const incomingEdges = readRowList(
|
|
1723
|
+
await ctx.db.query("epistemicEdges").withIndex(
|
|
1724
|
+
"by_to",
|
|
1725
|
+
(q) => q.eq("toNodeId", args.duplicateNodeId)
|
|
1726
|
+
).collect(),
|
|
1727
|
+
readEntityEdgeRecord
|
|
1728
|
+
);
|
|
1335
1729
|
for (const edge of incomingEdges) {
|
|
1336
1730
|
if (edge.fromNodeId === args.canonicalNodeId) {
|
|
1337
1731
|
continue;
|
|
@@ -1342,7 +1736,7 @@ var mergeEntities = mutation({
|
|
|
1342
1736
|
updatedAt: now
|
|
1343
1737
|
});
|
|
1344
1738
|
}
|
|
1345
|
-
const duplicateMetadata = duplicate.metadata
|
|
1739
|
+
const duplicateMetadata = duplicate.metadata ?? {};
|
|
1346
1740
|
await ctx.db.patch(args.duplicateNodeId, {
|
|
1347
1741
|
status: "superseded",
|
|
1348
1742
|
metadata: {
|
|
@@ -1407,29 +1801,9 @@ var archiveEntity = mutation({
|
|
|
1407
1801
|
handler: async (ctx, args) => {
|
|
1408
1802
|
const authenticatedUserId = await requireAuthenticatedUserId(ctx);
|
|
1409
1803
|
const now = Date.now();
|
|
1410
|
-
const node = await ctx
|
|
1411
|
-
if (!node) {
|
|
1412
|
-
throwStructuredMutationError({
|
|
1413
|
-
message: "Entity not found.",
|
|
1414
|
-
status: 404,
|
|
1415
|
-
code: "NOT_FOUND",
|
|
1416
|
-
details: { nodeId: args.nodeId }
|
|
1417
|
-
});
|
|
1418
|
-
}
|
|
1419
|
-
if (!isOntologicalNodeType(node.nodeType)) {
|
|
1420
|
-
throwStructuredMutationError({
|
|
1421
|
-
message: `Node "${args.nodeId}" is a ${node.nodeType}, not an entity. Use belief archive APIs for epistemic nodes.`,
|
|
1422
|
-
status: 400,
|
|
1423
|
-
code: "LIFECYCLE_VIOLATION",
|
|
1424
|
-
invariantCode: "entity.type_check"
|
|
1425
|
-
});
|
|
1426
|
-
}
|
|
1804
|
+
const node = await loadEntityForUpdate(ctx, args.nodeId);
|
|
1427
1805
|
if (node.projectId) {
|
|
1428
|
-
await requireScopeWriteAccess(
|
|
1429
|
-
ctx,
|
|
1430
|
-
node.projectId,
|
|
1431
|
-
authenticatedUserId
|
|
1432
|
-
);
|
|
1806
|
+
await requireScopeWriteAccess(ctx, node.projectId, authenticatedUserId);
|
|
1433
1807
|
}
|
|
1434
1808
|
await ctx.db.patch(args.nodeId, {
|
|
1435
1809
|
status: "archived",
|