@lucern/graph-primitives 1.0.28 → 1.0.30
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 +398 -228
- 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 +857 -515
- 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 +366 -203
- package/dist/epistemicBeliefs.admin.js.map +1 -1
- package/dist/epistemicBeliefs.backfills.d.ts +8 -8
- package/dist/epistemicBeliefs.backfills.js +655 -308
- package/dist/epistemicBeliefs.backfills.js.map +1 -1
- package/dist/epistemicBeliefs.confidence.d.ts +19 -14
- package/dist/epistemicBeliefs.confidence.js +634 -423
- package/dist/epistemicBeliefs.confidence.js.map +1 -1
- package/dist/epistemicBeliefs.core.d.ts +6 -6
- package/dist/epistemicBeliefs.core.js +719 -411
- package/dist/epistemicBeliefs.core.js.map +1 -1
- package/dist/epistemicBeliefs.d.ts +11 -8
- package/dist/epistemicBeliefs.forkEvidence.d.ts +2 -0
- package/dist/epistemicBeliefs.forkEvidence.js +8 -28
- package/dist/epistemicBeliefs.forkEvidence.js.map +1 -1
- package/dist/epistemicBeliefs.helpers.d.ts +69 -74
- package/dist/epistemicBeliefs.helpers.js +359 -248
- package/dist/epistemicBeliefs.helpers.js.map +1 -1
- package/dist/epistemicBeliefs.internal.d.ts +5 -5
- package/dist/epistemicBeliefs.internal.js +1246 -1044
- package/dist/epistemicBeliefs.internal.js.map +1 -1
- package/dist/epistemicBeliefs.js +4922 -3608
- package/dist/epistemicBeliefs.js.map +1 -1
- package/dist/epistemicBeliefs.lifecycle.d.ts +5 -5
- package/dist/epistemicBeliefs.lifecycle.js +1137 -818
- package/dist/epistemicBeliefs.lifecycle.js.map +1 -1
- package/dist/epistemicBeliefs.links.d.ts +7 -7
- package/dist/epistemicBeliefs.links.js +408 -307
- 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 +1063 -613
- package/dist/epistemicContracts.evaluators.js.map +1 -1
- package/dist/epistemicContracts.handlers.d.ts +15 -32
- package/dist/epistemicContracts.handlers.js +2086 -1644
- package/dist/epistemicContracts.handlers.js.map +1 -1
- package/dist/epistemicContracts.js +1131 -672
- 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 +1969 -1205
- package/dist/epistemicEdges.js.map +1 -1
- package/dist/epistemicEdges.mutations.d.ts +7 -7
- package/dist/epistemicEdges.mutations.js +960 -583
- 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 +937 -536
- 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 +844 -696
- 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 +704 -508
- package/dist/epistemicNodes.js.map +1 -1
- package/dist/epistemicNodes.mutations.d.ts +6 -6
- package/dist/epistemicNodes.mutations.js +564 -467
- 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 +352 -312
- 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 +87 -83
- package/dist/index.js +15677 -10594
- 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,38 +1,125 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { checkProjectAccess } from '@lucern/access-control/access';
|
|
1
|
+
import { requireScopeWriteAccess } from '@lucern/access-control/access';
|
|
3
2
|
import { getCurrentUserId } from '@lucern/access-control/auth';
|
|
3
|
+
import { throwStructuredMutationError } from '@lucern/access-control/structuredMutationError';
|
|
4
4
|
import { permissiveReturn } from '@lucern/contracts/schema-helpers/validators';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { v } from 'convex/values';
|
|
6
|
+
import { unsafeConvexAnyApi } from '@lucern/contracts/convex/unsafeAnyApi';
|
|
7
|
+
import { componentsGeneric, queryGeneric, mutationGeneric } from 'convex/server';
|
|
7
8
|
import { assertEdgePolicyAllowed, edgePolicyManifest } from '@lucern/contracts';
|
|
9
|
+
import { generateGlobalId, assertUuidV7Identity, assertStorageEdgeVocabulary, assertUuidShapedEdgeEndpoint } from '@lucern/contracts/ids';
|
|
8
10
|
|
|
9
11
|
// src/entityLifecycle.ts
|
|
10
|
-
var
|
|
12
|
+
var unsafeApi = unsafeConvexAnyApi(
|
|
13
|
+
"graph-primitives top-level module bundle lacks a committed Convex _generated/api surface"
|
|
14
|
+
);
|
|
15
|
+
var api = unsafeApi;
|
|
11
16
|
componentsGeneric();
|
|
12
|
-
var internal =
|
|
17
|
+
var internal = unsafeApi;
|
|
13
18
|
var mutation = mutationGeneric;
|
|
14
19
|
var query = queryGeneric;
|
|
15
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
|
+
|
|
16
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
|
+
}
|
|
17
101
|
async function getEntityTypeSchema(ctx, nodeType, tenantId) {
|
|
18
102
|
if (tenantId) {
|
|
19
|
-
const
|
|
103
|
+
const tenantEntries = await ctx.db.query("schemaEnumConfig").withIndex(
|
|
20
104
|
"by_tenant_category",
|
|
21
105
|
(q) => q.eq("tenantId", tenantId).eq("category", "entity_type")
|
|
22
106
|
).collect();
|
|
23
|
-
const tenantMatch =
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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;
|
|
28
114
|
}
|
|
29
115
|
}
|
|
30
116
|
const platformEntry = await ctx.db.query("schemaEnumConfig").withIndex(
|
|
31
117
|
"by_category_value",
|
|
32
118
|
(q) => q.eq("category", "entity_type").eq("value", nodeType)
|
|
33
119
|
).first();
|
|
34
|
-
|
|
35
|
-
|
|
120
|
+
const platformSchema = readEntitySchemaFromConfig(platformEntry);
|
|
121
|
+
if (platformSchema !== null) {
|
|
122
|
+
return platformSchema;
|
|
36
123
|
}
|
|
37
124
|
return null;
|
|
38
125
|
}
|
|
@@ -76,99 +163,54 @@ async function validateEntityMetadata(ctx, nodeType, metadata, tenantId) {
|
|
|
76
163
|
errors
|
|
77
164
|
};
|
|
78
165
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
return value.trim().toLowerCase().replace(/\s+/g, " ");
|
|
83
|
-
}
|
|
84
|
-
function buildEntityTitle(canonicalText) {
|
|
85
|
-
return canonicalText.slice(0, 100) + (canonicalText.length > 100 ? "..." : "");
|
|
86
|
-
}
|
|
87
|
-
function matchesCanonicalEntityRecord(node, args) {
|
|
88
|
-
if (node.epistemicLayer !== "ontological") {
|
|
89
|
-
return false;
|
|
90
|
-
}
|
|
91
|
-
if (node.workspaceId !== void 0) {
|
|
92
|
-
return false;
|
|
93
|
-
}
|
|
94
|
-
if (args.nodeType && node.nodeType !== args.nodeType) {
|
|
95
|
-
return false;
|
|
96
|
-
}
|
|
97
|
-
if (args.contentHash && node.contentHash !== args.contentHash) {
|
|
98
|
-
return false;
|
|
99
|
-
}
|
|
100
|
-
const rowTenantId = typeof node.tenantId === "string" && node.tenantId.trim().length > 0 ? node.tenantId.trim() : void 0;
|
|
101
|
-
if (rowTenantId !== args.tenantId) {
|
|
102
|
-
return false;
|
|
103
|
-
}
|
|
104
|
-
if (args.includeArchived) {
|
|
105
|
-
return node.status !== "deleted";
|
|
106
|
-
}
|
|
107
|
-
return node.status === "active";
|
|
166
|
+
function insertEpistemicNode(ctx, doc) {
|
|
167
|
+
assertUuidV7Identity("epistemicNodes", doc.globalId);
|
|
168
|
+
return ctx.db.insert("epistemicNodes", doc);
|
|
108
169
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
if (current.ontologyId) {
|
|
120
|
-
const ontologyDef = await ctx.db.get(
|
|
121
|
-
current.ontologyId
|
|
122
|
-
);
|
|
123
|
-
if (!ontologyDef || ontologyDef.status === "archived") {
|
|
124
|
-
if (current.parentTopicId) {
|
|
125
|
-
current = await ctx.db.get(
|
|
126
|
-
current.parentTopicId
|
|
127
|
-
);
|
|
128
|
-
continue;
|
|
129
|
-
}
|
|
130
|
-
return null;
|
|
131
|
-
}
|
|
132
|
-
const versions = await ctx.db.query("ontologyVersions").withIndex(
|
|
133
|
-
"by_ontologyId",
|
|
134
|
-
(q) => q.eq("ontologyId", current?.ontologyId)
|
|
135
|
-
).collect();
|
|
136
|
-
const published = versions.filter((v3) => v3.status === "published").sort((a, b) => (b.publishedAt ?? 0) - (a.publishedAt ?? 0));
|
|
137
|
-
const latestPublished = published[0] ?? null;
|
|
138
|
-
return {
|
|
139
|
-
ontologyId: ontologyDef._id,
|
|
140
|
-
ontologyKey: ontologyDef.ontologyKey,
|
|
141
|
-
ontologyName: ontologyDef.name,
|
|
142
|
-
tier: ontologyDef.tier,
|
|
143
|
-
source: current._id === startTopicId ? "direct" : "inherited",
|
|
144
|
-
sourceTopicId: current._id,
|
|
145
|
-
validEntityTypes: latestPublished ? latestPublished.entityTypes.map((et) => et.value) : [],
|
|
146
|
-
validEdgeTypes: latestPublished ? latestPublished.edgeTypes.map((et) => et.value) : [],
|
|
147
|
-
publishedVersion: latestPublished
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
if (!current.parentTopicId) {
|
|
151
|
-
break;
|
|
152
|
-
}
|
|
153
|
-
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
|
+
);
|
|
154
180
|
}
|
|
155
|
-
return null;
|
|
156
181
|
}
|
|
157
|
-
async function
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
+
);
|
|
161
189
|
}
|
|
162
|
-
if (
|
|
163
|
-
|
|
190
|
+
if (!doc.toNodeId || typeof doc.toNodeId !== "string") {
|
|
191
|
+
throw new Error(
|
|
192
|
+
"edge_endpoint_missing: epistemicEdges insert requires a non-empty toNodeId"
|
|
193
|
+
);
|
|
164
194
|
}
|
|
165
|
-
|
|
166
|
-
|
|
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
|
+
);
|
|
167
212
|
}
|
|
168
|
-
return
|
|
169
|
-
valid: false,
|
|
170
|
-
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}.`
|
|
171
|
-
};
|
|
213
|
+
return ctx.db.insert("epistemicEdges", doc);
|
|
172
214
|
}
|
|
173
215
|
|
|
174
216
|
// src/debug.ts
|
|
@@ -201,6 +243,10 @@ function readStringArray(value) {
|
|
|
201
243
|
function readMetadata(topic) {
|
|
202
244
|
return topic.metadata && typeof topic.metadata === "object" ? topic.metadata : {};
|
|
203
245
|
}
|
|
246
|
+
function omitMetadataKey(metadata, key) {
|
|
247
|
+
const { [key]: _omitted, ...rest } = metadata;
|
|
248
|
+
return rest;
|
|
249
|
+
}
|
|
204
250
|
function readLegacyProjectId(value) {
|
|
205
251
|
if (!value) {
|
|
206
252
|
return;
|
|
@@ -281,9 +327,12 @@ async function resolveTopicDoc(ctx, scopeId) {
|
|
|
281
327
|
);
|
|
282
328
|
}
|
|
283
329
|
try {
|
|
284
|
-
const topic = await ctx.runQuery(
|
|
285
|
-
|
|
286
|
-
|
|
330
|
+
const topic = await ctx.runQuery(
|
|
331
|
+
api.topics.getByLegacyScopeId,
|
|
332
|
+
{
|
|
333
|
+
projectId: String(scopeId)
|
|
334
|
+
}
|
|
335
|
+
);
|
|
287
336
|
if (topic?.name !== void 0 && topic?.type !== void 0) {
|
|
288
337
|
return topic;
|
|
289
338
|
}
|
|
@@ -303,8 +352,18 @@ function materializeTopicProjectOverlay(topic, idMode = "legacy") {
|
|
|
303
352
|
const outwardId = idMode === "topic" ? topicId : storageProjectId;
|
|
304
353
|
const visibility = coerceVisibility(topic.visibility) || coerceVisibility(metadata.visibility) || "private";
|
|
305
354
|
const status = coerceStatus(topic.status) || coerceStatus(metadata.status) || "active";
|
|
306
|
-
|
|
307
|
-
|
|
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
|
+
}
|
|
308
367
|
return {
|
|
309
368
|
...metadata,
|
|
310
369
|
_id: outwardId,
|
|
@@ -373,90 +432,113 @@ async function patchTopicProjectOverlay(ctx, scopeId, value) {
|
|
|
373
432
|
if (!topic) {
|
|
374
433
|
return null;
|
|
375
434
|
}
|
|
376
|
-
const
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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
|
+
}
|
|
380
450
|
};
|
|
381
451
|
for (const [key, rawValue] of Object.entries(value)) {
|
|
382
|
-
|
|
383
|
-
case "_id":
|
|
384
|
-
case "projectId":
|
|
385
|
-
case "topicId":
|
|
386
|
-
case "legacyProjectId":
|
|
387
|
-
case "storageProjectId":
|
|
388
|
-
break;
|
|
389
|
-
case "name":
|
|
390
|
-
case "description":
|
|
391
|
-
patch[key] = rawValue;
|
|
392
|
-
topicUpdateArgs[key] = rawValue;
|
|
393
|
-
break;
|
|
394
|
-
case "tenantId":
|
|
395
|
-
case "workspaceId":
|
|
396
|
-
case "ownerId":
|
|
397
|
-
throw new Error(
|
|
398
|
-
`patchTopicProjectOverlay cannot mutate ${key} via component-owned topics`
|
|
399
|
-
);
|
|
400
|
-
case "status": {
|
|
401
|
-
const status = coerceStatus(rawValue);
|
|
402
|
-
if (status) {
|
|
403
|
-
patch.status = status;
|
|
404
|
-
topicUpdateArgs.status = status;
|
|
405
|
-
}
|
|
406
|
-
break;
|
|
407
|
-
}
|
|
408
|
-
case "visibility": {
|
|
409
|
-
const visibility = coerceVisibility(rawValue);
|
|
410
|
-
if (visibility) {
|
|
411
|
-
patch.visibility = visibility;
|
|
412
|
-
topicUpdateArgs.visibility = visibility;
|
|
413
|
-
}
|
|
414
|
-
break;
|
|
415
|
-
}
|
|
416
|
-
case "type": {
|
|
417
|
-
const projectType = readNonEmptyString(rawValue);
|
|
418
|
-
if (projectType) {
|
|
419
|
-
nextMetadata.projectType = projectType;
|
|
420
|
-
} else {
|
|
421
|
-
delete nextMetadata.projectType;
|
|
422
|
-
}
|
|
423
|
-
break;
|
|
424
|
-
}
|
|
425
|
-
case "updatedAt":
|
|
426
|
-
case "createdAt":
|
|
427
|
-
break;
|
|
428
|
-
default:
|
|
429
|
-
if (rawValue === void 0) {
|
|
430
|
-
delete nextMetadata[key];
|
|
431
|
-
} else {
|
|
432
|
-
nextMetadata[key] = rawValue;
|
|
433
|
-
}
|
|
434
|
-
}
|
|
452
|
+
applyTopicProjectOverlayPatchEntry(plan, key, rawValue);
|
|
435
453
|
}
|
|
436
|
-
patch.updatedAt = Date.now();
|
|
437
|
-
patch.metadata = nextMetadata;
|
|
438
|
-
topicUpdateArgs.metadata = nextMetadata;
|
|
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;
|
|
519
|
+
}
|
|
520
|
+
plan.nextMetadata[key] = rawValue;
|
|
521
|
+
}
|
|
522
|
+
async function applyTopicProjectOverlayPatch(ctx, topic, plan) {
|
|
439
523
|
if (typeof ctx.runMutation === "function") {
|
|
440
524
|
try {
|
|
441
|
-
await ctx.runMutation(api.topics.update, topicUpdateArgs);
|
|
525
|
+
await ctx.runMutation(api.topics.update, plan.topicUpdateArgs);
|
|
442
526
|
} catch (error) {
|
|
443
|
-
if (!
|
|
527
|
+
if (!canPatchTopicViaLocalDb(ctx, error)) {
|
|
444
528
|
throw error;
|
|
445
529
|
}
|
|
446
|
-
await ctx.db.patch(
|
|
530
|
+
await ctx.db.patch(topic._id, plan.patch);
|
|
447
531
|
}
|
|
448
532
|
} else if (ctx?.db && typeof ctx.db.patch === "function") {
|
|
449
|
-
await ctx.db.patch(
|
|
533
|
+
await ctx.db.patch(topic._id, plan.patch);
|
|
450
534
|
} else {
|
|
451
535
|
throw new Error(
|
|
452
536
|
"Cannot patch topic without component adapter (ctx.runMutation unavailable)"
|
|
453
537
|
);
|
|
454
538
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
metadata: nextMetadata
|
|
459
|
-
});
|
|
539
|
+
}
|
|
540
|
+
function canPatchTopicViaLocalDb(ctx, error) {
|
|
541
|
+
return isMissingLucernChildComponentError(error) && Boolean(ctx?.db) && typeof ctx.db?.patch === "function";
|
|
460
542
|
}
|
|
461
543
|
|
|
462
544
|
// src/resolvers.ts
|
|
@@ -484,7 +566,7 @@ async function patchProjectWithTolerance(ctx, projectId, value) {
|
|
|
484
566
|
try {
|
|
485
567
|
await patchTopicProjectOverlay(ctx, projectId, value);
|
|
486
568
|
} catch (error) {
|
|
487
|
-
if (!isAdvisoryTopicPatch(value)
|
|
569
|
+
if (!(isAdvisoryTopicPatch(value) && isMissingLucernChildComponentError2(error))) {
|
|
488
570
|
throw error;
|
|
489
571
|
}
|
|
490
572
|
console.warn(
|
|
@@ -517,6 +599,92 @@ function resolveGraphPrimitivesAppResolvers(_ctx) {
|
|
|
517
599
|
...resolverOverrides
|
|
518
600
|
};
|
|
519
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
|
+
}
|
|
520
688
|
var LEGACY_SCOPE_FIELD2 = "graphScopeProjectId";
|
|
521
689
|
async function resolveTopicNodeScopeOrNull(ctx, ref) {
|
|
522
690
|
if (!ctx?.db || typeof ctx.db.query !== "function") {
|
|
@@ -551,13 +719,15 @@ function asMappedProjectId(topic) {
|
|
|
551
719
|
if (!topic) {
|
|
552
720
|
return;
|
|
553
721
|
}
|
|
554
|
-
const directLegacyProjectId = normalizeScopeValue(
|
|
722
|
+
const directLegacyProjectId = normalizeScopeValue(
|
|
723
|
+
topic[LEGACY_SCOPE_FIELD2]
|
|
724
|
+
);
|
|
555
725
|
if (directLegacyProjectId) {
|
|
556
726
|
return directLegacyProjectId;
|
|
557
727
|
}
|
|
558
728
|
const metadata = topic.metadata || {};
|
|
559
729
|
const candidate = metadata[LEGACY_SCOPE_FIELD2] || metadata.legacyProjectId || metadata.projectId || metadata.scopeProjectId;
|
|
560
|
-
return candidate ? candidate : void 0;
|
|
730
|
+
return typeof candidate === "string" ? normalizeScopeValue(candidate) : void 0;
|
|
561
731
|
}
|
|
562
732
|
function normalizeScopeValue(value) {
|
|
563
733
|
if (typeof value !== "string") {
|
|
@@ -582,8 +752,9 @@ function pickPrimaryTopic(candidates) {
|
|
|
582
752
|
})[0];
|
|
583
753
|
}
|
|
584
754
|
async function findTopicsByScopeAlias(ctx, scopeId) {
|
|
755
|
+
const query2 = ctx.db.query("topics");
|
|
585
756
|
try {
|
|
586
|
-
return await
|
|
757
|
+
return await query2.withIndex(
|
|
587
758
|
"by_graph_scope_project",
|
|
588
759
|
(q) => q.eq(LEGACY_SCOPE_FIELD2, scopeId)
|
|
589
760
|
).collect();
|
|
@@ -595,7 +766,7 @@ async function findTopicsByScopeAlias(ctx, scopeId) {
|
|
|
595
766
|
scopeId
|
|
596
767
|
}
|
|
597
768
|
);
|
|
598
|
-
const topics = await
|
|
769
|
+
const topics = await query2.collect();
|
|
599
770
|
return topics.filter((topic) => {
|
|
600
771
|
const normalizedGlobalId = normalizeScopeValue(topic.globalId);
|
|
601
772
|
const mappedProjectId = asMappedProjectId(topic);
|
|
@@ -651,209 +822,121 @@ async function resolveInheritedWorkspaceScope(ctx, topic) {
|
|
|
651
822
|
let current = topic;
|
|
652
823
|
for (let i = 0; i < MAX_DEPTH && current?.parentTopicId; i++) {
|
|
653
824
|
current = await ctx.db.get(current.parentTopicId);
|
|
654
|
-
if (!current)
|
|
825
|
+
if (!current) {
|
|
826
|
+
break;
|
|
827
|
+
}
|
|
655
828
|
if (!tenantId) {
|
|
656
829
|
tenantId = normalizeScopeValue(current.tenantId);
|
|
657
830
|
}
|
|
658
831
|
if (!workspaceId) {
|
|
659
832
|
workspaceId = normalizeScopeValue(current.workspaceId);
|
|
660
833
|
}
|
|
661
|
-
if (tenantId && workspaceId)
|
|
834
|
+
if (tenantId && workspaceId) {
|
|
835
|
+
break;
|
|
836
|
+
}
|
|
662
837
|
}
|
|
663
838
|
return { tenantId, workspaceId };
|
|
664
839
|
}
|
|
665
840
|
async function resolveTopicProjectScope(ctx, args) {
|
|
666
841
|
if (args.topicId) {
|
|
667
|
-
|
|
668
|
-
try {
|
|
669
|
-
topic = await ctx.db.get(
|
|
670
|
-
args.topicId
|
|
671
|
-
);
|
|
672
|
-
} catch (error) {
|
|
673
|
-
debugGraphPrimitiveFallback(
|
|
674
|
-
"[topicScope] Failed to load topic by direct id",
|
|
675
|
-
{
|
|
676
|
-
error,
|
|
677
|
-
topicId: args.topicId
|
|
678
|
-
}
|
|
679
|
-
);
|
|
680
|
-
}
|
|
681
|
-
if (!topic) {
|
|
682
|
-
topic = await tryResolveHostTopicById(ctx, String(args.topicId));
|
|
683
|
-
}
|
|
684
|
-
if (!topic) {
|
|
685
|
-
topic = pickPrimaryTopic(
|
|
686
|
-
await findTopicsByScopeAlias(ctx, String(args.topicId))
|
|
687
|
-
) ?? null;
|
|
688
|
-
}
|
|
689
|
-
if (!topic) {
|
|
690
|
-
const nodeScope = await resolveTopicNodeScopeOrNull(
|
|
691
|
-
ctx,
|
|
692
|
-
String(args.topicId)
|
|
693
|
-
);
|
|
694
|
-
if (nodeScope) {
|
|
695
|
-
return nodeScope;
|
|
696
|
-
}
|
|
697
|
-
throw new Error(`Topic not found: ${String(args.topicId)}`);
|
|
698
|
-
}
|
|
699
|
-
const inherited = await resolveInheritedWorkspaceScope(ctx, topic);
|
|
700
|
-
const mapped = asMappedProjectId(topic);
|
|
701
|
-
if (mapped) {
|
|
702
|
-
return {
|
|
703
|
-
topicId: topic._id,
|
|
704
|
-
projectId: mapped,
|
|
705
|
-
tenantId: inherited.tenantId,
|
|
706
|
-
workspaceId: inherited.workspaceId,
|
|
707
|
-
source: "topic"
|
|
708
|
-
};
|
|
709
|
-
}
|
|
710
|
-
return {
|
|
711
|
-
topicId: topic._id,
|
|
712
|
-
tenantId: inherited.tenantId,
|
|
713
|
-
workspaceId: inherited.workspaceId,
|
|
714
|
-
source: "topic"
|
|
715
|
-
};
|
|
842
|
+
return await resolveScopeFromTopicId(ctx, args.topicId);
|
|
716
843
|
}
|
|
717
844
|
if (args.projectId) {
|
|
718
|
-
|
|
719
|
-
try {
|
|
720
|
-
directTopic = await ctx.db.get(
|
|
721
|
-
args.projectId
|
|
722
|
-
);
|
|
723
|
-
} catch (error) {
|
|
724
|
-
debugGraphPrimitiveFallback(
|
|
725
|
-
"[topicScope] Failed to load direct project topic",
|
|
726
|
-
{
|
|
727
|
-
error,
|
|
728
|
-
projectId: args.projectId
|
|
729
|
-
}
|
|
730
|
-
);
|
|
731
|
-
}
|
|
732
|
-
if (directTopic) {
|
|
733
|
-
const inherited = await resolveInheritedWorkspaceScope(ctx, directTopic);
|
|
734
|
-
const mapped = asMappedProjectId(directTopic);
|
|
735
|
-
return {
|
|
736
|
-
topicId: directTopic._id,
|
|
737
|
-
projectId: mapped ?? args.projectId,
|
|
738
|
-
tenantId: inherited.tenantId,
|
|
739
|
-
workspaceId: inherited.workspaceId,
|
|
740
|
-
source: "topic_inferred"
|
|
741
|
-
};
|
|
742
|
-
}
|
|
743
|
-
directTopic = await tryResolveHostTopicByLegacyScope(ctx, args.projectId);
|
|
744
|
-
if (directTopic) {
|
|
745
|
-
const inherited = await resolveInheritedWorkspaceScope(ctx, directTopic);
|
|
746
|
-
const mapped = asMappedProjectId(directTopic);
|
|
747
|
-
return {
|
|
748
|
-
topicId: directTopic._id,
|
|
749
|
-
projectId: mapped ?? args.projectId,
|
|
750
|
-
tenantId: inherited.tenantId,
|
|
751
|
-
workspaceId: inherited.workspaceId,
|
|
752
|
-
source: "topic_inferred"
|
|
753
|
-
};
|
|
754
|
-
}
|
|
755
|
-
const topics = await findTopicsByScopeAlias(ctx, args.projectId);
|
|
756
|
-
const primary = pickPrimaryTopic(topics);
|
|
757
|
-
if (primary) {
|
|
758
|
-
const inherited = await resolveInheritedWorkspaceScope(ctx, primary);
|
|
759
|
-
return {
|
|
760
|
-
topicId: primary._id,
|
|
761
|
-
projectId: args.projectId,
|
|
762
|
-
tenantId: inherited.tenantId,
|
|
763
|
-
workspaceId: inherited.workspaceId,
|
|
764
|
-
source: "project_mapped_topic"
|
|
765
|
-
};
|
|
766
|
-
}
|
|
767
|
-
const nodeScope = await resolveTopicNodeScopeOrNull(
|
|
768
|
-
ctx,
|
|
769
|
-
String(args.projectId)
|
|
770
|
-
);
|
|
771
|
-
if (nodeScope) {
|
|
772
|
-
return {
|
|
773
|
-
...nodeScope,
|
|
774
|
-
projectId: nodeScope.projectId ?? String(args.projectId)
|
|
775
|
-
};
|
|
776
|
-
}
|
|
777
|
-
throw new Error(
|
|
778
|
-
`Legacy project scope ${String(args.projectId)} has no mapped topic.`
|
|
779
|
-
);
|
|
845
|
+
return await resolveScopeFromLegacyProjectId(ctx, args.projectId);
|
|
780
846
|
}
|
|
781
847
|
throw new Error(
|
|
782
848
|
"Missing scope: provide topicId (preferred) or legacy projectId alias."
|
|
783
849
|
);
|
|
784
850
|
}
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
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)}`);
|
|
792
861
|
}
|
|
793
|
-
async function
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
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;
|
|
800
873
|
}
|
|
874
|
+
return pickPrimaryTopic(await findTopicsByScopeAlias(ctx, String(topicId))) ?? null;
|
|
801
875
|
}
|
|
802
|
-
async function
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
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
|
+
});
|
|
809
885
|
}
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
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
|
+
});
|
|
814
893
|
}
|
|
815
|
-
await
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
{
|
|
822
|
-
kind: "epistemic_node",
|
|
823
|
-
nodeId: doc.fromNodeId,
|
|
824
|
-
nodeType: doc.fromNodeType
|
|
825
|
-
},
|
|
826
|
-
{
|
|
827
|
-
kind: "epistemic_node",
|
|
828
|
-
nodeId: doc.toNodeId,
|
|
829
|
-
nodeType: doc.toNodeType
|
|
830
|
-
}
|
|
831
|
-
);
|
|
894
|
+
const nodeScope = await resolveTopicNodeScopeOrNull(ctx, legacyProjectId);
|
|
895
|
+
if (nodeScope) {
|
|
896
|
+
return {
|
|
897
|
+
...nodeScope,
|
|
898
|
+
projectId: nodeScope.projectId ?? legacyProjectId
|
|
899
|
+
};
|
|
832
900
|
}
|
|
833
|
-
|
|
901
|
+
throw new Error(
|
|
902
|
+
`Legacy project scope ${legacyProjectId} has no mapped topic.`
|
|
903
|
+
);
|
|
834
904
|
}
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
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;
|
|
921
|
+
}
|
|
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
|
|
846
932
|
};
|
|
847
|
-
const error = new ConvexError(
|
|
848
|
-
data
|
|
849
|
-
);
|
|
850
|
-
error.status = args.status;
|
|
851
|
-
error.code = args.code;
|
|
852
|
-
error.invariantCode = args.invariantCode;
|
|
853
|
-
error.suggestion = args.suggestion;
|
|
854
|
-
error.details = args.details;
|
|
855
|
-
throw error;
|
|
856
933
|
}
|
|
934
|
+
var optionalScopeArgs = {
|
|
935
|
+
projectId: v.optional(v.string()),
|
|
936
|
+
topicId: v.optional(v.string())
|
|
937
|
+
};
|
|
938
|
+
|
|
939
|
+
// src/entityLifecycle.ts
|
|
857
940
|
async function requireAuthenticatedUserId(ctx) {
|
|
858
941
|
const userId = await getCurrentUserId(ctx);
|
|
859
942
|
if (!userId) {
|
|
@@ -867,19 +950,6 @@ async function requireAuthenticatedUserId(ctx) {
|
|
|
867
950
|
}
|
|
868
951
|
return userId;
|
|
869
952
|
}
|
|
870
|
-
async function requireProjectWriteAccess(ctx, projectId, userId) {
|
|
871
|
-
const hasAccess = await checkProjectAccess(ctx, projectId, userId);
|
|
872
|
-
if (!hasAccess) {
|
|
873
|
-
throwStructuredMutationError({
|
|
874
|
-
message: `Project write access denied for topic ${projectId}.`,
|
|
875
|
-
status: 403,
|
|
876
|
-
code: "PROJECT_ACCESS_DENIED",
|
|
877
|
-
invariantCode: "policy.scope_required",
|
|
878
|
-
suggestion: "The acting principal lacks project-write access to this topic. Request a topic grant (or, if the principal created this topic, run the creator-grant backfill) and retry.",
|
|
879
|
-
details: { topicId: projectId, principalId: userId }
|
|
880
|
-
});
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
953
|
function generateContentHash(nodeType, text) {
|
|
884
954
|
const content = `${nodeType}:${text.trim().toLowerCase().replace(/\s+/g, " ")}`;
|
|
885
955
|
let hash = 5381;
|
|
@@ -900,14 +970,227 @@ var ONTOLOGICAL_NODE_TYPE_SET = new Set(ONTOLOGICAL_NODE_TYPES);
|
|
|
900
970
|
var isOntologicalNodeType = ONTOLOGICAL_NODE_TYPE_SET.has.bind(
|
|
901
971
|
ONTOLOGICAL_NODE_TYPE_SET
|
|
902
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
|
+
}
|
|
903
1176
|
async function resolveCanonicalEntityScope(ctx, args) {
|
|
904
1177
|
const scope = await resolveTopicProjectScope(ctx, args);
|
|
905
|
-
const topic = scope.topicId ? await ctx.db.get(scope.topicId)
|
|
906
|
-
const project = scope.projectId
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
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
|
+
}
|
|
911
1194
|
return {
|
|
912
1195
|
...scope,
|
|
913
1196
|
tenantId,
|
|
@@ -917,10 +1200,13 @@ async function resolveCanonicalEntityScope(ctx, args) {
|
|
|
917
1200
|
}
|
|
918
1201
|
async function findExistingCanonicalEntity(ctx, args) {
|
|
919
1202
|
const contentHash = generateContentHash(args.nodeType, args.canonicalText);
|
|
920
|
-
const candidates =
|
|
921
|
-
"
|
|
922
|
-
|
|
923
|
-
|
|
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
|
+
);
|
|
924
1210
|
return candidates.find(
|
|
925
1211
|
(node) => matchesCanonicalEntityRecord(node, {
|
|
926
1212
|
nodeType: args.nodeType,
|
|
@@ -930,13 +1216,29 @@ async function findExistingCanonicalEntity(ctx, args) {
|
|
|
930
1216
|
) ?? null;
|
|
931
1217
|
}
|
|
932
1218
|
async function listCanonicalEntitiesForScope(ctx, args) {
|
|
933
|
-
|
|
934
|
-
"
|
|
935
|
-
|
|
936
|
-
)
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
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
|
+
}
|
|
940
1242
|
const normalizedSearch = typeof args.searchTerm === "string" && args.searchTerm.trim().length > 0 ? normalizeCanonicalEntityText(args.searchTerm) : void 0;
|
|
941
1243
|
return baseRows.filter(
|
|
942
1244
|
(node) => matchesCanonicalEntityRecord(node, {
|
|
@@ -1027,11 +1329,7 @@ var createEntity = mutation({
|
|
|
1027
1329
|
projectId: args.projectId
|
|
1028
1330
|
});
|
|
1029
1331
|
if (scope.projectId) {
|
|
1030
|
-
await
|
|
1031
|
-
ctx,
|
|
1032
|
-
scope.projectId,
|
|
1033
|
-
authenticatedUserId
|
|
1034
|
-
);
|
|
1332
|
+
await requireScopeWriteAccess(ctx, scope.projectId, authenticatedUserId);
|
|
1035
1333
|
}
|
|
1036
1334
|
if (scope.topicId) {
|
|
1037
1335
|
const ontologyValidation = await validateEntityTypeForTopic(
|
|
@@ -1050,7 +1348,7 @@ var createEntity = mutation({
|
|
|
1050
1348
|
});
|
|
1051
1349
|
}
|
|
1052
1350
|
}
|
|
1053
|
-
const metadata = args.metadata
|
|
1351
|
+
const metadata = readRecord(args.metadata) ?? {};
|
|
1054
1352
|
const validation = await validateEntityMetadata(
|
|
1055
1353
|
ctx,
|
|
1056
1354
|
args.nodeType,
|
|
@@ -1125,6 +1423,100 @@ var createEntity = mutation({
|
|
|
1125
1423
|
return { nodeId, globalId: entityGlobalId, isDuplicate: false };
|
|
1126
1424
|
}
|
|
1127
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
|
+
}
|
|
1128
1520
|
var updateEntityAttributes = mutation({
|
|
1129
1521
|
args: {
|
|
1130
1522
|
nodeId: v.id("epistemicNodes"),
|
|
@@ -1150,68 +1542,33 @@ var updateEntityAttributes = mutation({
|
|
|
1150
1542
|
handler: async (ctx, args) => {
|
|
1151
1543
|
const authenticatedUserId = await requireAuthenticatedUserId(ctx);
|
|
1152
1544
|
const now = Date.now();
|
|
1153
|
-
const node = await ctx
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
status: 404,
|
|
1158
|
-
code: "NOT_FOUND",
|
|
1159
|
-
details: { nodeId: args.nodeId }
|
|
1160
|
-
});
|
|
1161
|
-
}
|
|
1162
|
-
if (!isOntologicalNodeType(node.nodeType)) {
|
|
1163
|
-
throwStructuredMutationError({
|
|
1164
|
-
message: `Node "${args.nodeId}" is a ${node.nodeType}, not an entity. Use belief/question APIs for epistemic nodes.`,
|
|
1165
|
-
status: 400,
|
|
1166
|
-
code: "LIFECYCLE_VIOLATION",
|
|
1167
|
-
invariantCode: "entity.type_check",
|
|
1168
|
-
suggestion: "Only ontological entities can be updated via updateEntityAttributes."
|
|
1169
|
-
});
|
|
1170
|
-
}
|
|
1171
|
-
const scopeProjectId = typeof node.projectId === "string" && node.projectId.trim().length > 0 ? node.projectId : void 0;
|
|
1172
|
-
if (scopeProjectId) {
|
|
1173
|
-
await requireProjectWriteAccess(ctx, scopeProjectId, authenticatedUserId);
|
|
1174
|
-
}
|
|
1175
|
-
const existingMetadata = node.metadata || {};
|
|
1176
|
-
const newMetadataFields = args.metadata !== void 0 ? args.metadata || {} : {};
|
|
1177
|
-
const mergedMetadata = { ...existingMetadata, ...newMetadataFields };
|
|
1178
|
-
if (args.subtype !== void 0) {
|
|
1179
|
-
mergedMetadata.subtype = args.subtype;
|
|
1180
|
-
}
|
|
1181
|
-
if (args.canonicalText !== void 0 && args.canonicalText.trim().length === 0) {
|
|
1182
|
-
throwStructuredMutationError({
|
|
1183
|
-
message: "canonicalText cannot be empty.",
|
|
1184
|
-
status: 400,
|
|
1185
|
-
code: "INVALID_REQUEST",
|
|
1186
|
-
invariantCode: "entity.update_requires_entity_lifecycle"
|
|
1187
|
-
});
|
|
1545
|
+
const node = await loadEntityForUpdate(ctx, args.nodeId);
|
|
1546
|
+
const scopeProjectId = resolveScopeProjectId(node);
|
|
1547
|
+
if (scopeProjectId !== void 0) {
|
|
1548
|
+
await requireScopeWriteAccess(ctx, scopeProjectId, authenticatedUserId);
|
|
1188
1549
|
}
|
|
1189
|
-
const
|
|
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
|
+
});
|
|
1190
1560
|
if (args.canonicalText !== void 0) {
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
throwStructuredMutationError({
|
|
1198
|
-
message: "A canonical entity with this name already exists in the tenant scope.",
|
|
1199
|
-
status: 409,
|
|
1200
|
-
code: "CONFLICT",
|
|
1201
|
-
invariantCode: "entity.canonical_text_unique_per_tenant",
|
|
1202
|
-
suggestion: "Merge the duplicate entity instead of renaming this node onto an existing canonical record.",
|
|
1203
|
-
details: {
|
|
1204
|
-
nodeId: args.nodeId,
|
|
1205
|
-
duplicateNodeId: duplicate._id,
|
|
1206
|
-
canonicalText: nextCanonicalText
|
|
1207
|
-
}
|
|
1208
|
-
});
|
|
1209
|
-
}
|
|
1561
|
+
await assertCanonicalEntityUnique(
|
|
1562
|
+
ctx,
|
|
1563
|
+
node,
|
|
1564
|
+
args.nodeId,
|
|
1565
|
+
plan.nextCanonicalText
|
|
1566
|
+
);
|
|
1210
1567
|
}
|
|
1211
1568
|
const validation = await validateEntityMetadata(
|
|
1212
1569
|
ctx,
|
|
1213
1570
|
node.nodeType,
|
|
1214
|
-
mergedMetadata,
|
|
1571
|
+
plan.mergedMetadata,
|
|
1215
1572
|
typeof node.tenantId === "string" ? node.tenantId : void 0
|
|
1216
1573
|
);
|
|
1217
1574
|
if (!validation.valid) {
|
|
@@ -1224,31 +1581,22 @@ var updateEntityAttributes = mutation({
|
|
|
1224
1581
|
});
|
|
1225
1582
|
}
|
|
1226
1583
|
const updates = {
|
|
1227
|
-
|
|
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
|
+
),
|
|
1228
1598
|
updatedAt: now
|
|
1229
1599
|
};
|
|
1230
|
-
if (args.canonicalText !== void 0) {
|
|
1231
|
-
updates.canonicalText = nextCanonicalText;
|
|
1232
|
-
updates.contentHash = generateContentHash(node.nodeType, nextCanonicalText);
|
|
1233
|
-
updates.title = args.title !== void 0 ? args.title : buildEntityTitle(nextCanonicalText);
|
|
1234
|
-
} else if (args.title !== void 0) {
|
|
1235
|
-
updates.title = args.title;
|
|
1236
|
-
}
|
|
1237
|
-
if (args.subtype !== void 0) {
|
|
1238
|
-
updates.subtype = args.subtype;
|
|
1239
|
-
}
|
|
1240
|
-
if (args.domain !== void 0) {
|
|
1241
|
-
updates.domain = args.domain;
|
|
1242
|
-
}
|
|
1243
|
-
if (args.tags !== void 0) {
|
|
1244
|
-
updates.tags = args.tags;
|
|
1245
|
-
}
|
|
1246
|
-
if (args.verificationStatus !== void 0) {
|
|
1247
|
-
updates.verificationStatus = args.verificationStatus;
|
|
1248
|
-
}
|
|
1249
|
-
if (args.externalIds !== void 0) {
|
|
1250
|
-
updates.externalIds = args.externalIds;
|
|
1251
|
-
}
|
|
1252
1600
|
await ctx.db.patch(args.nodeId, updates);
|
|
1253
1601
|
await ctx.scheduler.runAfter(0, internal.neo4jSync.syncNodeToNeo4j, {
|
|
1254
1602
|
nodeId: args.nodeId,
|
|
@@ -1269,7 +1617,7 @@ var updateEntityAttributes = mutation({
|
|
|
1269
1617
|
tags: node.tags,
|
|
1270
1618
|
verificationStatus: node.verificationStatus,
|
|
1271
1619
|
externalIds: node.externalIds,
|
|
1272
|
-
metadata: existingMetadata
|
|
1620
|
+
metadata: plan.existingMetadata
|
|
1273
1621
|
},
|
|
1274
1622
|
newState: updates,
|
|
1275
1623
|
projectId: scopeProjectId
|
|
@@ -1286,9 +1634,13 @@ var mergeEntities = mutation({
|
|
|
1286
1634
|
handler: async (ctx, args) => {
|
|
1287
1635
|
const authenticatedUserId = await requireAuthenticatedUserId(ctx);
|
|
1288
1636
|
const now = Date.now();
|
|
1289
|
-
const canonical =
|
|
1290
|
-
|
|
1291
|
-
|
|
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)) {
|
|
1292
1644
|
throwStructuredMutationError({
|
|
1293
1645
|
message: "One or both entity nodes not found.",
|
|
1294
1646
|
status: 404,
|
|
@@ -1299,14 +1651,6 @@ var mergeEntities = mutation({
|
|
|
1299
1651
|
}
|
|
1300
1652
|
});
|
|
1301
1653
|
}
|
|
1302
|
-
if (!isOntologicalNodeType(canonical.nodeType) || !isOntologicalNodeType(duplicate.nodeType)) {
|
|
1303
|
-
throwStructuredMutationError({
|
|
1304
|
-
message: "Both nodes must be ontological entities to merge.",
|
|
1305
|
-
status: 400,
|
|
1306
|
-
code: "LIFECYCLE_VIOLATION",
|
|
1307
|
-
invariantCode: "entity.type_check"
|
|
1308
|
-
});
|
|
1309
|
-
}
|
|
1310
1654
|
if (args.canonicalNodeId === args.duplicateNodeId) {
|
|
1311
1655
|
throwStructuredMutationError({
|
|
1312
1656
|
message: "Cannot merge an entity with itself.",
|
|
@@ -1317,6 +1661,18 @@ var mergeEntities = mutation({
|
|
|
1317
1661
|
details: { canonicalNodeId: args.canonicalNodeId }
|
|
1318
1662
|
});
|
|
1319
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
|
+
}
|
|
1320
1676
|
if (canonical.nodeType !== duplicate.nodeType) {
|
|
1321
1677
|
throwStructuredMutationError({
|
|
1322
1678
|
message: `Cannot merge different entity types: ${canonical.nodeType} vs ${duplicate.nodeType}.`,
|
|
@@ -1325,7 +1681,7 @@ var mergeEntities = mutation({
|
|
|
1325
1681
|
});
|
|
1326
1682
|
}
|
|
1327
1683
|
if (canonical.projectId) {
|
|
1328
|
-
await
|
|
1684
|
+
await requireScopeWriteAccess(
|
|
1329
1685
|
ctx,
|
|
1330
1686
|
canonical.projectId,
|
|
1331
1687
|
authenticatedUserId
|
|
@@ -1346,10 +1702,13 @@ var mergeEntities = mutation({
|
|
|
1346
1702
|
createdAt: now,
|
|
1347
1703
|
updatedAt: now
|
|
1348
1704
|
});
|
|
1349
|
-
const outgoingEdges =
|
|
1350
|
-
"
|
|
1351
|
-
|
|
1352
|
-
|
|
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
|
+
);
|
|
1353
1712
|
for (const edge of outgoingEdges) {
|
|
1354
1713
|
if (edge.toNodeId === args.canonicalNodeId) {
|
|
1355
1714
|
continue;
|
|
@@ -1360,10 +1719,13 @@ var mergeEntities = mutation({
|
|
|
1360
1719
|
updatedAt: now
|
|
1361
1720
|
});
|
|
1362
1721
|
}
|
|
1363
|
-
const incomingEdges =
|
|
1364
|
-
"
|
|
1365
|
-
|
|
1366
|
-
|
|
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
|
+
);
|
|
1367
1729
|
for (const edge of incomingEdges) {
|
|
1368
1730
|
if (edge.fromNodeId === args.canonicalNodeId) {
|
|
1369
1731
|
continue;
|
|
@@ -1374,7 +1736,7 @@ var mergeEntities = mutation({
|
|
|
1374
1736
|
updatedAt: now
|
|
1375
1737
|
});
|
|
1376
1738
|
}
|
|
1377
|
-
const duplicateMetadata = duplicate.metadata
|
|
1739
|
+
const duplicateMetadata = duplicate.metadata ?? {};
|
|
1378
1740
|
await ctx.db.patch(args.duplicateNodeId, {
|
|
1379
1741
|
status: "superseded",
|
|
1380
1742
|
metadata: {
|
|
@@ -1439,29 +1801,9 @@ var archiveEntity = mutation({
|
|
|
1439
1801
|
handler: async (ctx, args) => {
|
|
1440
1802
|
const authenticatedUserId = await requireAuthenticatedUserId(ctx);
|
|
1441
1803
|
const now = Date.now();
|
|
1442
|
-
const node = await ctx
|
|
1443
|
-
if (!node) {
|
|
1444
|
-
throwStructuredMutationError({
|
|
1445
|
-
message: "Entity not found.",
|
|
1446
|
-
status: 404,
|
|
1447
|
-
code: "NOT_FOUND",
|
|
1448
|
-
details: { nodeId: args.nodeId }
|
|
1449
|
-
});
|
|
1450
|
-
}
|
|
1451
|
-
if (!isOntologicalNodeType(node.nodeType)) {
|
|
1452
|
-
throwStructuredMutationError({
|
|
1453
|
-
message: `Node "${args.nodeId}" is a ${node.nodeType}, not an entity. Use belief archive APIs for epistemic nodes.`,
|
|
1454
|
-
status: 400,
|
|
1455
|
-
code: "LIFECYCLE_VIOLATION",
|
|
1456
|
-
invariantCode: "entity.type_check"
|
|
1457
|
-
});
|
|
1458
|
-
}
|
|
1804
|
+
const node = await loadEntityForUpdate(ctx, args.nodeId);
|
|
1459
1805
|
if (node.projectId) {
|
|
1460
|
-
await
|
|
1461
|
-
ctx,
|
|
1462
|
-
node.projectId,
|
|
1463
|
-
authenticatedUserId
|
|
1464
|
-
);
|
|
1806
|
+
await requireScopeWriteAccess(ctx, node.projectId, authenticatedUserId);
|
|
1465
1807
|
}
|
|
1466
1808
|
await ctx.db.patch(args.nodeId, {
|
|
1467
1809
|
status: "archived",
|