@oscharko-dev/keiko-server 0.2.0
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/.tsbuildinfo +1 -0
- package/dist/assistant-response.d.ts +6 -0
- package/dist/assistant-response.d.ts.map +1 -0
- package/dist/assistant-response.js +12 -0
- package/dist/browser.d.ts +11 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +245 -0
- package/dist/chat-handlers.d.ts +48 -0
- package/dist/chat-handlers.d.ts.map +1 -0
- package/dist/chat-handlers.js +821 -0
- package/dist/chat-stream-handlers.d.ts +4 -0
- package/dist/chat-stream-handlers.d.ts.map +1 -0
- package/dist/chat-stream-handlers.js +136 -0
- package/dist/conversation-prompt.d.ts +8 -0
- package/dist/conversation-prompt.d.ts.map +1 -0
- package/dist/conversation-prompt.js +36 -0
- package/dist/conversation-validation.d.ts +26 -0
- package/dist/conversation-validation.d.ts.map +1 -0
- package/dist/conversation-validation.js +125 -0
- package/dist/credentialPersistence.d.ts +23 -0
- package/dist/credentialPersistence.d.ts.map +1 -0
- package/dist/credentialPersistence.js +93 -0
- package/dist/credentialVault.d.ts +30 -0
- package/dist/credentialVault.d.ts.map +1 -0
- package/dist/credentialVault.js +206 -0
- package/dist/csp.d.ts +3 -0
- package/dist/csp.d.ts.map +1 -0
- package/dist/csp.js +75 -0
- package/dist/deps.d.ts +78 -0
- package/dist/deps.d.ts.map +1 -0
- package/dist/deps.js +457 -0
- package/dist/editor/agentRoutes.d.ts +7 -0
- package/dist/editor/agentRoutes.d.ts.map +1 -0
- package/dist/editor/agentRoutes.js +197 -0
- package/dist/editor/assuredGateRunner.d.ts +36 -0
- package/dist/editor/assuredGateRunner.d.ts.map +1 -0
- package/dist/editor/assuredGateRunner.js +100 -0
- package/dist/editor/assuredPreFilter.d.ts +34 -0
- package/dist/editor/assuredPreFilter.d.ts.map +1 -0
- package/dist/editor/assuredPreFilter.js +134 -0
- package/dist/editor/assuredPreFilterRunner.d.ts +31 -0
- package/dist/editor/assuredPreFilterRunner.d.ts.map +1 -0
- package/dist/editor/assuredPreFilterRunner.js +312 -0
- package/dist/editor/builtinLanguageProviders.d.ts +6 -0
- package/dist/editor/builtinLanguageProviders.d.ts.map +1 -0
- package/dist/editor/builtinLanguageProviders.js +221 -0
- package/dist/editor/codingContext.d.ts +12 -0
- package/dist/editor/codingContext.d.ts.map +1 -0
- package/dist/editor/codingContext.js +121 -0
- package/dist/editor/codingContextEvidence.d.ts +7 -0
- package/dist/editor/codingContextEvidence.d.ts.map +1 -0
- package/dist/editor/codingContextEvidence.js +52 -0
- package/dist/editor/codingContextProviders.d.ts +36 -0
- package/dist/editor/codingContextProviders.d.ts.map +1 -0
- package/dist/editor/codingContextProviders.js +348 -0
- package/dist/editor/completionModelEvidence.d.ts +16 -0
- package/dist/editor/completionModelEvidence.d.ts.map +1 -0
- package/dist/editor/completionModelEvidence.js +50 -0
- package/dist/editor/completionRoutes.d.ts +37 -0
- package/dist/editor/completionRoutes.d.ts.map +1 -0
- package/dist/editor/completionRoutes.js +411 -0
- package/dist/editor/contextRoutes.d.ts +6 -0
- package/dist/editor/contextRoutes.d.ts.map +1 -0
- package/dist/editor/contextRoutes.js +411 -0
- package/dist/editor/disposableAssuredExecution.d.ts +22 -0
- package/dist/editor/disposableAssuredExecution.d.ts.map +1 -0
- package/dist/editor/disposableAssuredExecution.js +57 -0
- package/dist/editor/editorCompletionModel.d.ts +47 -0
- package/dist/editor/editorCompletionModel.d.ts.map +1 -0
- package/dist/editor/editorCompletionModel.js +156 -0
- package/dist/editor/editorInlineCompletionModel.d.ts +34 -0
- package/dist/editor/editorInlineCompletionModel.d.ts.map +1 -0
- package/dist/editor/editorInlineCompletionModel.js +112 -0
- package/dist/editor/editorModelTokenBudget.d.ts +46 -0
- package/dist/editor/editorModelTokenBudget.d.ts.map +1 -0
- package/dist/editor/editorModelTokenBudget.js +121 -0
- package/dist/editor/inlineCompletionRateLimiter.d.ts +19 -0
- package/dist/editor/inlineCompletionRateLimiter.d.ts.map +1 -0
- package/dist/editor/inlineCompletionRateLimiter.js +46 -0
- package/dist/editor/inlineCompletionRoutes.d.ts +26 -0
- package/dist/editor/inlineCompletionRoutes.d.ts.map +1 -0
- package/dist/editor/inlineCompletionRoutes.js +404 -0
- package/dist/editor/inlineCompletionTelemetryEvidence.d.ts +5 -0
- package/dist/editor/inlineCompletionTelemetryEvidence.d.ts.map +1 -0
- package/dist/editor/inlineCompletionTelemetryEvidence.js +42 -0
- package/dist/editor/languageCancellation.d.ts +19 -0
- package/dist/editor/languageCancellation.d.ts.map +1 -0
- package/dist/editor/languageCancellation.js +48 -0
- package/dist/editor/languageProvider.d.ts +39 -0
- package/dist/editor/languageProvider.d.ts.map +1 -0
- package/dist/editor/languageProvider.js +11 -0
- package/dist/editor/languageRoutes.d.ts +15 -0
- package/dist/editor/languageRoutes.d.ts.map +1 -0
- package/dist/editor/languageRoutes.js +106 -0
- package/dist/editor/languageSanitize.d.ts +8 -0
- package/dist/editor/languageSanitize.d.ts.map +1 -0
- package/dist/editor/languageSanitize.js +101 -0
- package/dist/editor/languageService.d.ts +36 -0
- package/dist/editor/languageService.d.ts.map +1 -0
- package/dist/editor/languageService.js +93 -0
- package/dist/editor/languageServiceHost.d.ts +14 -0
- package/dist/editor/languageServiceHost.d.ts.map +1 -0
- package/dist/editor/languageServiceHost.js +242 -0
- package/dist/editor/localKnowledgeRetrieval.d.ts +21 -0
- package/dist/editor/localKnowledgeRetrieval.d.ts.map +1 -0
- package/dist/editor/localKnowledgeRetrieval.js +44 -0
- package/dist/editor/patchApplyEvidence.d.ts +21 -0
- package/dist/editor/patchApplyEvidence.d.ts.map +1 -0
- package/dist/editor/patchApplyEvidence.js +87 -0
- package/dist/editor/patchApplyRoutes.d.ts +16 -0
- package/dist/editor/patchApplyRoutes.d.ts.map +1 -0
- package/dist/editor/patchApplyRoutes.js +307 -0
- package/dist/editor/postApplyVerification.d.ts +42 -0
- package/dist/editor/postApplyVerification.d.ts.map +1 -0
- package/dist/editor/postApplyVerification.js +177 -0
- package/dist/editor/testGenerationEvidence.d.ts +6 -0
- package/dist/editor/testGenerationEvidence.d.ts.map +1 -0
- package/dist/editor/testGenerationEvidence.js +72 -0
- package/dist/editor/testGenerationPatch.d.ts +10 -0
- package/dist/editor/testGenerationPatch.d.ts.map +1 -0
- package/dist/editor/testGenerationPatch.js +66 -0
- package/dist/editor/testGenerationRoutes.d.ts +21 -0
- package/dist/editor/testGenerationRoutes.d.ts.map +1 -0
- package/dist/editor/testGenerationRoutes.js +254 -0
- package/dist/editor/testGenerationRunner.d.ts +23 -0
- package/dist/editor/testGenerationRunner.d.ts.map +1 -0
- package/dist/editor/testGenerationRunner.js +120 -0
- package/dist/editor/textOffsets.d.ts +6 -0
- package/dist/editor/textOffsets.d.ts.map +1 -0
- package/dist/editor/textOffsets.js +82 -0
- package/dist/editor/typescriptLanguageProvider.d.ts +3 -0
- package/dist/editor/typescriptLanguageProvider.d.ts.map +1 -0
- package/dist/editor/typescriptLanguageProvider.js +217 -0
- package/dist/evidence.d.ts +28 -0
- package/dist/evidence.d.ts.map +1 -0
- package/dist/evidence.js +145 -0
- package/dist/files-deny.d.ts +3 -0
- package/dist/files-deny.d.ts.map +1 -0
- package/dist/files-deny.js +12 -0
- package/dist/files.d.ts +97 -0
- package/dist/files.d.ts.map +1 -0
- package/dist/files.js +733 -0
- package/dist/gateway-setup.d.ts +10 -0
- package/dist/gateway-setup.d.ts.map +1 -0
- package/dist/gateway-setup.js +896 -0
- package/dist/governed-workflow.d.ts +17 -0
- package/dist/governed-workflow.d.ts.map +1 -0
- package/dist/governed-workflow.js +147 -0
- package/dist/grounded-answer.d.ts +12 -0
- package/dist/grounded-answer.d.ts.map +1 -0
- package/dist/grounded-answer.js +69 -0
- package/dist/grounded-context-index.d.ts +25 -0
- package/dist/grounded-context-index.d.ts.map +1 -0
- package/dist/grounded-context-index.js +169 -0
- package/dist/grounded-document-evidence.d.ts +28 -0
- package/dist/grounded-document-evidence.d.ts.map +1 -0
- package/dist/grounded-document-evidence.js +430 -0
- package/dist/grounded-handoff.d.ts +4 -0
- package/dist/grounded-handoff.d.ts.map +1 -0
- package/dist/grounded-handoff.js +445 -0
- package/dist/grounded-orchestrator.d.ts +43 -0
- package/dist/grounded-orchestrator.d.ts.map +1 -0
- package/dist/grounded-orchestrator.js +1445 -0
- package/dist/grounded-prompt.d.ts +2 -0
- package/dist/grounded-prompt.d.ts.map +1 -0
- package/dist/grounded-prompt.js +17 -0
- package/dist/grounded-qa-hybrid.d.ts +36 -0
- package/dist/grounded-qa-hybrid.d.ts.map +1 -0
- package/dist/grounded-qa-hybrid.js +762 -0
- package/dist/grounded-qa-multi-source.d.ts +38 -0
- package/dist/grounded-qa-multi-source.d.ts.map +1 -0
- package/dist/grounded-qa-multi-source.js +461 -0
- package/dist/grounded-qa.d.ts +45 -0
- package/dist/grounded-qa.d.ts.map +1 -0
- package/dist/grounded-qa.js +877 -0
- package/dist/grounded-rerank.d.ts +26 -0
- package/dist/grounded-rerank.d.ts.map +1 -0
- package/dist/grounded-rerank.js +72 -0
- package/dist/grounded-turn-registry.d.ts +23 -0
- package/dist/grounded-turn-registry.d.ts.map +1 -0
- package/dist/grounded-turn-registry.js +102 -0
- package/dist/headers.d.ts +3 -0
- package/dist/headers.d.ts.map +1 -0
- package/dist/headers.js +22 -0
- package/dist/host-check.d.ts +3 -0
- package/dist/host-check.d.ts.map +1 -0
- package/dist/host-check.js +58 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/load-csp.d.ts +3 -0
- package/dist/load-csp.d.ts.map +1 -0
- package/dist/load-csp.js +100 -0
- package/dist/local-knowledge-grounded-qa.d.ts +42 -0
- package/dist/local-knowledge-grounded-qa.d.ts.map +1 -0
- package/dist/local-knowledge-grounded-qa.js +678 -0
- package/dist/local-knowledge-handlers.d.ts +24 -0
- package/dist/local-knowledge-handlers.d.ts.map +1 -0
- package/dist/local-knowledge-handlers.js +1285 -0
- package/dist/local-knowledge-indexing-registry.d.ts +13 -0
- package/dist/local-knowledge-indexing-registry.d.ts.map +1 -0
- package/dist/local-knowledge-indexing-registry.js +53 -0
- package/dist/localKnowledgeKeyProvider.d.ts +11 -0
- package/dist/localKnowledgeKeyProvider.d.ts.map +1 -0
- package/dist/localKnowledgeKeyProvider.js +48 -0
- package/dist/memory-audit-event-builders.d.ts +21 -0
- package/dist/memory-audit-event-builders.d.ts.map +1 -0
- package/dist/memory-audit-event-builders.js +187 -0
- package/dist/memory-audit-handler.d.ts +23 -0
- package/dist/memory-audit-handler.d.ts.map +1 -0
- package/dist/memory-audit-handler.js +191 -0
- package/dist/memory-capture-policy.d.ts +10 -0
- package/dist/memory-capture-policy.d.ts.map +1 -0
- package/dist/memory-capture-policy.js +44 -0
- package/dist/memory-consolidation-handlers.d.ts +6 -0
- package/dist/memory-consolidation-handlers.d.ts.map +1 -0
- package/dist/memory-consolidation-handlers.js +491 -0
- package/dist/memory-consolidation-registry.d.ts +47 -0
- package/dist/memory-consolidation-registry.d.ts.map +1 -0
- package/dist/memory-consolidation-registry.js +106 -0
- package/dist/memory-conv-handlers.d.ts +8 -0
- package/dist/memory-conv-handlers.d.ts.map +1 -0
- package/dist/memory-conv-handlers.js +369 -0
- package/dist/memory-conversation-context.d.ts +13 -0
- package/dist/memory-conversation-context.d.ts.map +1 -0
- package/dist/memory-conversation-context.js +22 -0
- package/dist/memory-diagnostics.d.ts +29 -0
- package/dist/memory-diagnostics.d.ts.map +1 -0
- package/dist/memory-diagnostics.js +122 -0
- package/dist/memory-embedding.d.ts +21 -0
- package/dist/memory-embedding.d.ts.map +1 -0
- package/dist/memory-embedding.js +264 -0
- package/dist/memory-handlers.d.ts +19 -0
- package/dist/memory-handlers.d.ts.map +1 -0
- package/dist/memory-handlers.js +1204 -0
- package/dist/memory-maintenance-handlers.d.ts +35 -0
- package/dist/memory-maintenance-handlers.d.ts.map +1 -0
- package/dist/memory-maintenance-handlers.js +219 -0
- package/dist/memory-record-builders.d.ts +4 -0
- package/dist/memory-record-builders.d.ts.map +1 -0
- package/dist/memory-record-builders.js +19 -0
- package/dist/memory-retention.d.ts +31 -0
- package/dist/memory-retention.d.ts.map +1 -0
- package/dist/memory-retention.js +151 -0
- package/dist/memory-retrieval-signals.d.ts +12 -0
- package/dist/memory-retrieval-signals.d.ts.map +1 -0
- package/dist/memory-retrieval-signals.js +100 -0
- package/dist/memory-salience.d.ts +12 -0
- package/dist/memory-salience.d.ts.map +1 -0
- package/dist/memory-salience.js +154 -0
- package/dist/memory-scope-sanitizer.d.ts +6 -0
- package/dist/memory-scope-sanitizer.d.ts.map +1 -0
- package/dist/memory-scope-sanitizer.js +106 -0
- package/dist/memory-target-resolver.d.ts +4 -0
- package/dist/memory-target-resolver.d.ts.map +1 -0
- package/dist/memory-target-resolver.js +73 -0
- package/dist/memory-workflow-port.d.ts +14 -0
- package/dist/memory-workflow-port.d.ts.map +1 -0
- package/dist/memory-workflow-port.js +186 -0
- package/dist/private-json.d.ts +3 -0
- package/dist/private-json.d.ts.map +1 -0
- package/dist/private-json.js +62 -0
- package/dist/promptEnhancer/index.d.ts +3 -0
- package/dist/promptEnhancer/index.d.ts.map +1 -0
- package/dist/promptEnhancer/index.js +5 -0
- package/dist/promptEnhancer/orchestrate.d.ts +2 -0
- package/dist/promptEnhancer/orchestrate.d.ts.map +1 -0
- package/dist/promptEnhancer/orchestrate.js +5 -0
- package/dist/promptEnhancer/routes.d.ts +9 -0
- package/dist/promptEnhancer/routes.d.ts.map +1 -0
- package/dist/promptEnhancer/routes.js +205 -0
- package/dist/qualityIntelligence/capsuleAdapter.d.ts +27 -0
- package/dist/qualityIntelligence/capsuleAdapter.d.ts.map +1 -0
- package/dist/qualityIntelligence/capsuleAdapter.js +57 -0
- package/dist/qualityIntelligence/connectorAuthorization.d.ts +22 -0
- package/dist/qualityIntelligence/connectorAuthorization.d.ts.map +1 -0
- package/dist/qualityIntelligence/connectorAuthorization.js +35 -0
- package/dist/qualityIntelligence/connectorErrors.d.ts +16 -0
- package/dist/qualityIntelligence/connectorErrors.d.ts.map +1 -0
- package/dist/qualityIntelligence/connectorErrors.js +56 -0
- package/dist/qualityIntelligence/connectorRoutes.d.ts +7 -0
- package/dist/qualityIntelligence/connectorRoutes.d.ts.map +1 -0
- package/dist/qualityIntelligence/connectorRoutes.js +167 -0
- package/dist/qualityIntelligence/editRoutes.d.ts +5 -0
- package/dist/qualityIntelligence/editRoutes.d.ts.map +1 -0
- package/dist/qualityIntelligence/editRoutes.js +293 -0
- package/dist/qualityIntelligence/exportAssembly.d.ts +22 -0
- package/dist/qualityIntelligence/exportAssembly.d.ts.map +1 -0
- package/dist/qualityIntelligence/exportAssembly.js +352 -0
- package/dist/qualityIntelligence/exportRoutes.d.ts +5 -0
- package/dist/qualityIntelligence/exportRoutes.d.ts.map +1 -0
- package/dist/qualityIntelligence/exportRoutes.js +320 -0
- package/dist/qualityIntelligence/figma/figmaConcurrency.d.ts +8 -0
- package/dist/qualityIntelligence/figma/figmaConcurrency.d.ts.map +1 -0
- package/dist/qualityIntelligence/figma/figmaConcurrency.js +34 -0
- package/dist/qualityIntelligence/figma/figmaConnector.d.ts +65 -0
- package/dist/qualityIntelligence/figma/figmaConnector.d.ts.map +1 -0
- package/dist/qualityIntelligence/figma/figmaConnector.js +184 -0
- package/dist/qualityIntelligence/figma/figmaConnectorAudit.d.ts +52 -0
- package/dist/qualityIntelligence/figma/figmaConnectorAudit.d.ts.map +1 -0
- package/dist/qualityIntelligence/figma/figmaConnectorAudit.js +63 -0
- package/dist/qualityIntelligence/figma/figmaConnectorErrors.d.ts +31 -0
- package/dist/qualityIntelligence/figma/figmaConnectorErrors.d.ts.map +1 -0
- package/dist/qualityIntelligence/figma/figmaConnectorErrors.js +220 -0
- package/dist/qualityIntelligence/figma/figmaConnectorMetrics.d.ts +44 -0
- package/dist/qualityIntelligence/figma/figmaConnectorMetrics.d.ts.map +1 -0
- package/dist/qualityIntelligence/figma/figmaConnectorMetrics.js +49 -0
- package/dist/qualityIntelligence/figma/figmaConsent.d.ts +39 -0
- package/dist/qualityIntelligence/figma/figmaConsent.d.ts.map +1 -0
- package/dist/qualityIntelligence/figma/figmaConsent.js +62 -0
- package/dist/qualityIntelligence/figma/figmaHttpPort.d.ts +28 -0
- package/dist/qualityIntelligence/figma/figmaHttpPort.d.ts.map +1 -0
- package/dist/qualityIntelligence/figma/figmaHttpPort.js +70 -0
- package/dist/qualityIntelligence/figma/figmaObservedActions.d.ts +49 -0
- package/dist/qualityIntelligence/figma/figmaObservedActions.d.ts.map +1 -0
- package/dist/qualityIntelligence/figma/figmaObservedActions.js +89 -0
- package/dist/qualityIntelligence/figma/figmaReadiness.d.ts +32 -0
- package/dist/qualityIntelligence/figma/figmaReadiness.d.ts.map +1 -0
- package/dist/qualityIntelligence/figma/figmaReadiness.js +67 -0
- package/dist/qualityIntelligence/figma/figmaRenderPort.d.ts +29 -0
- package/dist/qualityIntelligence/figma/figmaRenderPort.d.ts.map +1 -0
- package/dist/qualityIntelligence/figma/figmaRenderPort.js +93 -0
- package/dist/qualityIntelligence/figma/figmaResnapshot.d.ts +28 -0
- package/dist/qualityIntelligence/figma/figmaResnapshot.d.ts.map +1 -0
- package/dist/qualityIntelligence/figma/figmaResnapshot.js +38 -0
- package/dist/qualityIntelligence/figma/figmaRetry.d.ts +31 -0
- package/dist/qualityIntelligence/figma/figmaRetry.d.ts.map +1 -0
- package/dist/qualityIntelligence/figma/figmaRetry.js +62 -0
- package/dist/qualityIntelligence/figma/figmaScopeRef.d.ts +9 -0
- package/dist/qualityIntelligence/figma/figmaScopeRef.d.ts.map +1 -0
- package/dist/qualityIntelligence/figma/figmaScopeRef.js +18 -0
- package/dist/qualityIntelligence/figma/figmaScopedPagination.d.ts +86 -0
- package/dist/qualityIntelligence/figma/figmaScopedPagination.d.ts.map +1 -0
- package/dist/qualityIntelligence/figma/figmaScopedPagination.js +308 -0
- package/dist/qualityIntelligence/figma/figmaSnapshotBuilder.d.ts +31 -0
- package/dist/qualityIntelligence/figma/figmaSnapshotBuilder.d.ts.map +1 -0
- package/dist/qualityIntelligence/figma/figmaSnapshotBuilder.js +314 -0
- package/dist/qualityIntelligence/figma/figmaSnapshotHash.d.ts +18 -0
- package/dist/qualityIntelligence/figma/figmaSnapshotHash.d.ts.map +1 -0
- package/dist/qualityIntelligence/figma/figmaSnapshotHash.js +63 -0
- package/dist/qualityIntelligence/figma/figmaSnapshotTypes.d.ts +65 -0
- package/dist/qualityIntelligence/figma/figmaSnapshotTypes.d.ts.map +1 -0
- package/dist/qualityIntelligence/figma/figmaSnapshotTypes.js +13 -0
- package/dist/qualityIntelligence/figma/figmaTokenSource.d.ts +9 -0
- package/dist/qualityIntelligence/figma/figmaTokenSource.d.ts.map +1 -0
- package/dist/qualityIntelligence/figma/figmaTokenSource.js +61 -0
- package/dist/qualityIntelligence/figma/figmaTokenStore.d.ts +19 -0
- package/dist/qualityIntelligence/figma/figmaTokenStore.d.ts.map +1 -0
- package/dist/qualityIntelligence/figma/figmaTokenStore.js +156 -0
- package/dist/qualityIntelligence/figma/figmaUrl.d.ts +6 -0
- package/dist/qualityIntelligence/figma/figmaUrl.d.ts.map +1 -0
- package/dist/qualityIntelligence/figma/figmaUrl.js +36 -0
- package/dist/qualityIntelligence/figma/index.d.ts +20 -0
- package/dist/qualityIntelligence/figma/index.d.ts.map +1 -0
- package/dist/qualityIntelligence/figma/index.js +26 -0
- package/dist/qualityIntelligence/figmaCodegenRoutes.d.ts +28 -0
- package/dist/qualityIntelligence/figmaCodegenRoutes.d.ts.map +1 -0
- package/dist/qualityIntelligence/figmaCodegenRoutes.js +165 -0
- package/dist/qualityIntelligence/figmaSnapshotAdapter.d.ts +55 -0
- package/dist/qualityIntelligence/figmaSnapshotAdapter.d.ts.map +1 -0
- package/dist/qualityIntelligence/figmaSnapshotAdapter.js +219 -0
- package/dist/qualityIntelligence/figmaSnapshotOrchestration.d.ts +64 -0
- package/dist/qualityIntelligence/figmaSnapshotOrchestration.d.ts.map +1 -0
- package/dist/qualityIntelligence/figmaSnapshotOrchestration.js +203 -0
- package/dist/qualityIntelligence/figmaSnapshotRoutes.d.ts +112 -0
- package/dist/qualityIntelligence/figmaSnapshotRoutes.d.ts.map +1 -0
- package/dist/qualityIntelligence/figmaSnapshotRoutes.js +1063 -0
- package/dist/qualityIntelligence/figmaSnapshotScreenIds.d.ts +19 -0
- package/dist/qualityIntelligence/figmaSnapshotScreenIds.d.ts.map +1 -0
- package/dist/qualityIntelligence/figmaSnapshotScreenIds.js +75 -0
- package/dist/qualityIntelligence/generationPort.d.ts +15 -0
- package/dist/qualityIntelligence/generationPort.d.ts.map +1 -0
- package/dist/qualityIntelligence/generationPort.js +185 -0
- package/dist/qualityIntelligence/handoffErrors.d.ts +9 -0
- package/dist/qualityIntelligence/handoffErrors.d.ts.map +1 -0
- package/dist/qualityIntelligence/handoffErrors.js +21 -0
- package/dist/qualityIntelligence/handoffRoutes.d.ts +15 -0
- package/dist/qualityIntelligence/handoffRoutes.d.ts.map +1 -0
- package/dist/qualityIntelligence/handoffRoutes.js +341 -0
- package/dist/qualityIntelligence/index.d.ts +17 -0
- package/dist/qualityIntelligence/index.d.ts.map +1 -0
- package/dist/qualityIntelligence/index.js +36 -0
- package/dist/qualityIntelligence/judgePort.d.ts +30 -0
- package/dist/qualityIntelligence/judgePort.d.ts.map +1 -0
- package/dist/qualityIntelligence/judgePort.js +326 -0
- package/dist/qualityIntelligence/modelSelection.d.ts +58 -0
- package/dist/qualityIntelligence/modelSelection.d.ts.map +1 -0
- package/dist/qualityIntelligence/modelSelection.js +148 -0
- package/dist/qualityIntelligence/reCheckRoutes.d.ts +6 -0
- package/dist/qualityIntelligence/reCheckRoutes.d.ts.map +1 -0
- package/dist/qualityIntelligence/reCheckRoutes.js +1157 -0
- package/dist/qualityIntelligence/retentionEnforcement.d.ts +13 -0
- package/dist/qualityIntelligence/retentionEnforcement.d.ts.map +1 -0
- package/dist/qualityIntelligence/retentionEnforcement.js +47 -0
- package/dist/qualityIntelligence/retentionRoutes.d.ts +8 -0
- package/dist/qualityIntelligence/retentionRoutes.d.ts.map +1 -0
- package/dist/qualityIntelligence/retentionRoutes.js +74 -0
- package/dist/qualityIntelligence/reviewRoutes.d.ts +5 -0
- package/dist/qualityIntelligence/reviewRoutes.d.ts.map +1 -0
- package/dist/qualityIntelligence/reviewRoutes.js +145 -0
- package/dist/qualityIntelligence/reviewStore.d.ts +75 -0
- package/dist/qualityIntelligence/reviewStore.d.ts.map +1 -0
- package/dist/qualityIntelligence/reviewStore.js +170 -0
- package/dist/qualityIntelligence/runExecution.d.ts +36 -0
- package/dist/qualityIntelligence/runExecution.d.ts.map +1 -0
- package/dist/qualityIntelligence/runExecution.js +180 -0
- package/dist/qualityIntelligence/runIngestion.d.ts +70 -0
- package/dist/qualityIntelligence/runIngestion.d.ts.map +1 -0
- package/dist/qualityIntelligence/runIngestion.js +1235 -0
- package/dist/qualityIntelligence/runRegistry.d.ts +31 -0
- package/dist/qualityIntelligence/runRegistry.d.ts.map +1 -0
- package/dist/qualityIntelligence/runRegistry.js +66 -0
- package/dist/qualityIntelligence/runRoutes.d.ts +16 -0
- package/dist/qualityIntelligence/runRoutes.d.ts.map +1 -0
- package/dist/qualityIntelligence/runRoutes.js +357 -0
- package/dist/qualityIntelligence/traceabilityRoutes.d.ts +5 -0
- package/dist/qualityIntelligence/traceabilityRoutes.d.ts.map +1 -0
- package/dist/qualityIntelligence/traceabilityRoutes.js +173 -0
- package/dist/qualityIntelligence/uiRoutes.d.ts +7 -0
- package/dist/qualityIntelligence/uiRoutes.d.ts.map +1 -0
- package/dist/qualityIntelligence/uiRoutes.js +336 -0
- package/dist/read-handlers.d.ts +9 -0
- package/dist/read-handlers.d.ts.map +1 -0
- package/dist/read-handlers.js +265 -0
- package/dist/relationship-handlers.d.ts +191 -0
- package/dist/relationship-handlers.d.ts.map +1 -0
- package/dist/relationship-handlers.js +0 -0
- package/dist/routes.d.ts +37 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +507 -0
- package/dist/run-engine.d.ts +25 -0
- package/dist/run-engine.d.ts.map +1 -0
- package/dist/run-engine.js +385 -0
- package/dist/run-handlers.d.ts +9 -0
- package/dist/run-handlers.d.ts.map +1 -0
- package/dist/run-handlers.js +465 -0
- package/dist/run-request.d.ts +17 -0
- package/dist/run-request.d.ts.map +1 -0
- package/dist/run-request.js +219 -0
- package/dist/runs.d.ts +47 -0
- package/dist/runs.d.ts.map +1 -0
- package/dist/runs.js +100 -0
- package/dist/server.d.ts +13 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +152 -0
- package/dist/sink.d.ts +28 -0
- package/dist/sink.d.ts.map +1 -0
- package/dist/sink.js +80 -0
- package/dist/sse-write.d.ts +9 -0
- package/dist/sse-write.d.ts.map +1 -0
- package/dist/sse-write.js +26 -0
- package/dist/sse.d.ts +8 -0
- package/dist/sse.d.ts.map +1 -0
- package/dist/sse.js +27 -0
- package/dist/static.d.ts +5 -0
- package/dist/static.d.ts.map +1 -0
- package/dist/static.js +76 -0
- package/dist/store/chats.d.ts +17 -0
- package/dist/store/chats.d.ts.map +1 -0
- package/dist/store/chats.js +624 -0
- package/dist/store/db.d.ts +11 -0
- package/dist/store/db.d.ts.map +1 -0
- package/dist/store/db.js +203 -0
- package/dist/store/errors.d.ts +13 -0
- package/dist/store/errors.d.ts.map +1 -0
- package/dist/store/errors.js +30 -0
- package/dist/store/index.d.ts +7 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +6 -0
- package/dist/store/messages.d.ts +8 -0
- package/dist/store/messages.d.ts.map +1 -0
- package/dist/store/messages.js +149 -0
- package/dist/store/paths.d.ts +5 -0
- package/dist/store/paths.d.ts.map +1 -0
- package/dist/store/paths.js +84 -0
- package/dist/store/projects.d.ts +8 -0
- package/dist/store/projects.d.ts.map +1 -0
- package/dist/store/projects.js +59 -0
- package/dist/store/relationship-audit.d.ts +42 -0
- package/dist/store/relationship-audit.d.ts.map +1 -0
- package/dist/store/relationship-audit.js +155 -0
- package/dist/store/relationships.d.ts +191 -0
- package/dist/store/relationships.d.ts.map +1 -0
- package/dist/store/relationships.js +724 -0
- package/dist/store/schema.d.ts +4 -0
- package/dist/store/schema.d.ts.map +1 -0
- package/dist/store/schema.js +220 -0
- package/dist/store/types.d.ts +29 -0
- package/dist/store/types.d.ts.map +1 -0
- package/dist/store/types.js +8 -0
- package/dist/store/validation.d.ts +7 -0
- package/dist/store/validation.d.ts.map +1 -0
- package/dist/store/validation.js +117 -0
- package/dist/store-handlers.d.ts +17 -0
- package/dist/store-handlers.d.ts.map +1 -0
- package/dist/store-handlers.js +872 -0
- package/dist/terminal-errors.d.ts +22 -0
- package/dist/terminal-errors.d.ts.map +1 -0
- package/dist/terminal-errors.js +45 -0
- package/dist/terminal-evidence.d.ts +21 -0
- package/dist/terminal-evidence.d.ts.map +1 -0
- package/dist/terminal-evidence.js +65 -0
- package/dist/terminal-routes.d.ts +10 -0
- package/dist/terminal-routes.d.ts.map +1 -0
- package/dist/terminal-routes.js +219 -0
- package/dist/terminal.d.ts +68 -0
- package/dist/terminal.d.ts.map +1 -0
- package/dist/terminal.js +855 -0
- package/package.json +52 -0
|
@@ -0,0 +1,724 @@
|
|
|
1
|
+
// Epic #532 / Issue #539 — relationship engine store layer.
|
|
2
|
+
//
|
|
3
|
+
// Module-scope SQL constants only; NO string interpolation into SQL; every read and write
|
|
4
|
+
// is workspace-scoped (storage.md §3.3, audit-events.md §10). Mutations run in a single
|
|
5
|
+
// transaction together with their audit row write (storage.md §4); the audit-row writer
|
|
6
|
+
// lives in `./relationship-audit.ts` and is invoked by the API layer inside the same
|
|
7
|
+
// BEGIN..COMMIT block.
|
|
8
|
+
//
|
|
9
|
+
// This file owns the lifecycle of the `relationships` and `relationship_lifecycle_history`
|
|
10
|
+
// tables only. The validator (#538) is pure and lives in `@oscharko-dev/keiko-contracts`;
|
|
11
|
+
// the API layer (`../relationship-handlers.ts`) composes the validator with this store.
|
|
12
|
+
//
|
|
13
|
+
// Bounded-query caps mirror api-contract.md §7. Hard caps are exported as `const` so the
|
|
14
|
+
// handlers cite the same numbers.
|
|
15
|
+
import { randomUUID } from "node:crypto";
|
|
16
|
+
import { RELATIONSHIP_SCHEMA_VERSION, RELATIONSHIP_SUPPORTED_OBJECT_KINDS, } from "@oscharko-dev/keiko-contracts";
|
|
17
|
+
import { invalidRequest, notFound, UiStoreError } from "./errors.js";
|
|
18
|
+
// ─── Bounded-query caps (api-contract.md §7) ──────────────────────────────────
|
|
19
|
+
export const MAX_LIST_LIMIT = 256;
|
|
20
|
+
export const DEFAULT_LIST_LIMIT = 64;
|
|
21
|
+
export const MAX_IMPACT_DEPTH = 3;
|
|
22
|
+
export const DEFAULT_IMPACT_DEPTH = 1;
|
|
23
|
+
export const MAX_IMPACT_NODES = 1024;
|
|
24
|
+
export const DEFAULT_IMPACT_NODES = 256;
|
|
25
|
+
export const MAX_IMPACT_RELATIONSHIPS = 2048;
|
|
26
|
+
export const DEFAULT_IMPACT_RELATIONSHIPS = 512;
|
|
27
|
+
export const LIFECYCLE_HISTORY_RETAIN = 32;
|
|
28
|
+
// Alias used by #542 health surface — counts in every finding category are hard-capped at
|
|
29
|
+
// this number. It is the same hard cap as MAX_IMPACT_RELATIONSHIPS; the alias is exported
|
|
30
|
+
// so callers can name the contract intent.
|
|
31
|
+
export const MAX_RELATIONSHIPS_PER_QUERY = MAX_IMPACT_RELATIONSHIPS;
|
|
32
|
+
function rebuildScope(row) {
|
|
33
|
+
const wsId = row.workspace_scope_id;
|
|
34
|
+
switch (row.scope_kind) {
|
|
35
|
+
case "user":
|
|
36
|
+
return { kind: "user", userId: row.scope_coordinate, workspaceId: wsId };
|
|
37
|
+
case "workspace":
|
|
38
|
+
return { kind: "workspace", workspaceId: row.scope_coordinate };
|
|
39
|
+
case "project":
|
|
40
|
+
return { kind: "project", projectId: row.scope_coordinate, workspaceId: wsId };
|
|
41
|
+
case "workflow":
|
|
42
|
+
return {
|
|
43
|
+
kind: "workflow",
|
|
44
|
+
workflowDefinitionId: row.scope_coordinate,
|
|
45
|
+
workspaceId: wsId,
|
|
46
|
+
};
|
|
47
|
+
case "global":
|
|
48
|
+
return { kind: "global", workspaceId: wsId };
|
|
49
|
+
default:
|
|
50
|
+
// STRICT mode + CHECK constraint at the schema layer prevent this; surfaced as a typed
|
|
51
|
+
// error so callers never see a partial row silently.
|
|
52
|
+
throw new UiStoreError("internal", "Unknown relationship scope kind.", 500);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function rowToRelationship(row) {
|
|
56
|
+
const stored = {
|
|
57
|
+
id: row.id,
|
|
58
|
+
schemaVersion: RELATIONSHIP_SCHEMA_VERSION,
|
|
59
|
+
workspaceId: row.workspace_scope_id,
|
|
60
|
+
source: {
|
|
61
|
+
kind: row.source_kind,
|
|
62
|
+
id: row.source_id,
|
|
63
|
+
workspaceId: row.workspace_scope_id,
|
|
64
|
+
},
|
|
65
|
+
target: {
|
|
66
|
+
kind: row.target_kind,
|
|
67
|
+
id: row.target_id,
|
|
68
|
+
workspaceId: row.workspace_scope_id,
|
|
69
|
+
},
|
|
70
|
+
type: row.type,
|
|
71
|
+
lifecycleState: row.lifecycle,
|
|
72
|
+
createdAt: new Date(row.created_at).toISOString(),
|
|
73
|
+
updatedAt: new Date(row.updated_at).toISOString(),
|
|
74
|
+
etag: row.updated_at, // legacy numeric etag in the contract; we expose the opaque string via getRelationshipEtag(id, workspaceId)
|
|
75
|
+
scope: rebuildScope(row),
|
|
76
|
+
...(row.confidence === null ? {} : { confidence: row.confidence }),
|
|
77
|
+
...(row.summary === null ? {} : { summary: row.summary }),
|
|
78
|
+
};
|
|
79
|
+
return stored;
|
|
80
|
+
}
|
|
81
|
+
// ─── SQL ──────────────────────────────────────────────────────────────────────
|
|
82
|
+
const SQL_INSERT = `
|
|
83
|
+
INSERT INTO relationships(
|
|
84
|
+
id, schema_version, workspace_scope_id, scope_kind, scope_coordinate, type,
|
|
85
|
+
source_kind, source_id, target_kind, target_id, lifecycle,
|
|
86
|
+
created_at, updated_at, etag, confidence, summary
|
|
87
|
+
)
|
|
88
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
89
|
+
`;
|
|
90
|
+
const SQL_GET = `
|
|
91
|
+
SELECT id, schema_version, workspace_scope_id, scope_kind, scope_coordinate, type,
|
|
92
|
+
source_kind, source_id, target_kind, target_id, lifecycle,
|
|
93
|
+
created_at, updated_at, etag, confidence, summary
|
|
94
|
+
FROM relationships
|
|
95
|
+
WHERE id = ? AND workspace_scope_id = ?
|
|
96
|
+
`;
|
|
97
|
+
const SQL_GET_ETAG_SCOPED = "SELECT etag FROM relationships WHERE id = ? AND workspace_scope_id = ?";
|
|
98
|
+
const SQL_UPDATE_LIFECYCLE = `
|
|
99
|
+
UPDATE relationships
|
|
100
|
+
SET lifecycle = ?, updated_at = ?, etag = ?, summary = COALESCE(?, summary)
|
|
101
|
+
WHERE id = ? AND workspace_scope_id = ?
|
|
102
|
+
`;
|
|
103
|
+
const SQL_UPDATE_RECONNECT = `
|
|
104
|
+
UPDATE relationships
|
|
105
|
+
SET target_kind = ?, target_id = ?, updated_at = ?, etag = ?, summary = COALESCE(?, summary)
|
|
106
|
+
WHERE id = ? AND workspace_scope_id = ?
|
|
107
|
+
`;
|
|
108
|
+
const SQL_COUNT_PRODUCES_EVIDENCE_FOR_SOURCE = `
|
|
109
|
+
SELECT COUNT(*) AS n FROM relationships
|
|
110
|
+
WHERE workspace_scope_id = ? AND type = 'produces-evidence'
|
|
111
|
+
AND source_kind = ? AND source_id = ?
|
|
112
|
+
AND lifecycle IN ('draft','active','archived')
|
|
113
|
+
`;
|
|
114
|
+
const SQL_COUNT_STARTS_WORKFLOW_FOR_TARGET = `
|
|
115
|
+
SELECT COUNT(*) AS n FROM relationships
|
|
116
|
+
WHERE workspace_scope_id = ? AND type = 'starts-workflow'
|
|
117
|
+
AND target_kind = ? AND target_id = ?
|
|
118
|
+
AND lifecycle IN ('draft','active','archived')
|
|
119
|
+
`;
|
|
120
|
+
const SQL_INSERT_HISTORY = `
|
|
121
|
+
INSERT INTO relationship_lifecycle_history(id, relationship_id, from_state, to_state, occurred_at, summary)
|
|
122
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
123
|
+
`;
|
|
124
|
+
const SQL_LIST_HISTORY = `
|
|
125
|
+
SELECT relationship_id, from_state, to_state, occurred_at, summary
|
|
126
|
+
FROM relationship_lifecycle_history
|
|
127
|
+
WHERE relationship_id = ?
|
|
128
|
+
ORDER BY occurred_at DESC
|
|
129
|
+
LIMIT ?
|
|
130
|
+
`;
|
|
131
|
+
const SQL_FIND_BY_SOURCE = `
|
|
132
|
+
SELECT id, schema_version, workspace_scope_id, scope_kind, scope_coordinate, type,
|
|
133
|
+
source_kind, source_id, target_kind, target_id, lifecycle,
|
|
134
|
+
created_at, updated_at, etag, confidence, summary
|
|
135
|
+
FROM relationships
|
|
136
|
+
WHERE workspace_scope_id = ? AND source_kind = ? AND source_id = ?
|
|
137
|
+
ORDER BY updated_at DESC, id ASC
|
|
138
|
+
LIMIT ?
|
|
139
|
+
`;
|
|
140
|
+
const SQL_FIND_BY_TARGET = `
|
|
141
|
+
SELECT id, schema_version, workspace_scope_id, scope_kind, scope_coordinate, type,
|
|
142
|
+
source_kind, source_id, target_kind, target_id, lifecycle,
|
|
143
|
+
created_at, updated_at, etag, confidence, summary
|
|
144
|
+
FROM relationships
|
|
145
|
+
WHERE workspace_scope_id = ? AND target_kind = ? AND target_id = ?
|
|
146
|
+
ORDER BY updated_at DESC, id ASC
|
|
147
|
+
LIMIT ?
|
|
148
|
+
`;
|
|
149
|
+
const SQL_HEALTH_COUNTS = `
|
|
150
|
+
SELECT lifecycle, COUNT(*) AS n FROM relationships
|
|
151
|
+
WHERE workspace_scope_id = ?
|
|
152
|
+
GROUP BY lifecycle
|
|
153
|
+
`;
|
|
154
|
+
// Stale / blocked / revoked / invalid SQL — each is workspace-scoped, hard-capped, and
|
|
155
|
+
// deterministically ordered by (lifecycle, source_kind, source_id, target_kind, target_id).
|
|
156
|
+
// The limit is bound at execute-time; no user input enters SQL text.
|
|
157
|
+
const SQL_HEALTH_FINDINGS_BY_LIFECYCLE = `
|
|
158
|
+
SELECT id, type, source_kind, source_id, target_kind, target_id, lifecycle
|
|
159
|
+
FROM relationships
|
|
160
|
+
WHERE workspace_scope_id = ? AND lifecycle = ?
|
|
161
|
+
ORDER BY source_kind ASC, source_id ASC, target_kind ASC, target_id ASC, id ASC
|
|
162
|
+
LIMIT ?
|
|
163
|
+
`;
|
|
164
|
+
// Active relationships used for cycle detection + invalid-reference detection. Bounded by
|
|
165
|
+
// the same cap; we cap both the scan input and the result list at MAX_RELATIONSHIPS_PER_QUERY
|
|
166
|
+
// so an oversized graph degrades gracefully via `cycleScanTruncated = true`.
|
|
167
|
+
const SQL_HEALTH_ACTIVE_RELATIONSHIPS = `
|
|
168
|
+
SELECT id, type, source_kind, source_id, target_kind, target_id, lifecycle
|
|
169
|
+
FROM relationships
|
|
170
|
+
WHERE workspace_scope_id = ? AND lifecycle IN ('draft','active','archived')
|
|
171
|
+
ORDER BY source_kind ASC, source_id ASC, target_kind ASC, target_id ASC, id ASC
|
|
172
|
+
LIMIT ?
|
|
173
|
+
`;
|
|
174
|
+
// Endpoint participation count. Used to detect orphans: an endpoint that previously
|
|
175
|
+
// participated in a relationship (so it appears in the relationships table) but is no
|
|
176
|
+
// longer referenced by any active relationship. Bounded.
|
|
177
|
+
const SQL_HEALTH_ENDPOINT_PARTICIPATION = `
|
|
178
|
+
SELECT kind, id, SUM(active_count) AS active_total, SUM(any_count) AS any_total FROM (
|
|
179
|
+
SELECT source_kind AS kind, source_id AS id,
|
|
180
|
+
CASE WHEN lifecycle IN ('draft','active','archived') THEN 1 ELSE 0 END AS active_count,
|
|
181
|
+
1 AS any_count
|
|
182
|
+
FROM relationships WHERE workspace_scope_id = ?
|
|
183
|
+
UNION ALL
|
|
184
|
+
SELECT target_kind AS kind, target_id AS id,
|
|
185
|
+
CASE WHEN lifecycle IN ('draft','active','archived') THEN 1 ELSE 0 END AS active_count,
|
|
186
|
+
1 AS any_count
|
|
187
|
+
FROM relationships WHERE workspace_scope_id = ?
|
|
188
|
+
)
|
|
189
|
+
GROUP BY kind, id
|
|
190
|
+
ORDER BY kind ASC, id ASC
|
|
191
|
+
LIMIT ?
|
|
192
|
+
`;
|
|
193
|
+
// ─── Public mutating + reading API ────────────────────────────────────────────
|
|
194
|
+
export function insertRelationship(db, rel) {
|
|
195
|
+
// Validator runs at the API layer before this is reached (storage.md §4); the store is the
|
|
196
|
+
// structural barrier. CHECK constraints + the partial unique indexes catch the rest.
|
|
197
|
+
const scopeCoordinate = relationshipScopeCoordinate(rel.scope);
|
|
198
|
+
try {
|
|
199
|
+
db.prepare(SQL_INSERT).run(rel.id, RELATIONSHIP_SCHEMA_VERSION, rel.workspaceId, rel.scope.kind, scopeCoordinate, rel.type, rel.source.kind, rel.source.id, rel.target.kind, rel.target.id, rel.lifecycleState, rel.createdAt, rel.updatedAt, rel.etag, rel.confidence ?? null, rel.summary ?? null);
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
if (error instanceof Error && /UNIQUE/i.test(error.message)) {
|
|
203
|
+
throw new UiStoreError("invalid_request", "Cardinality constraint violated.", 409);
|
|
204
|
+
}
|
|
205
|
+
throw error;
|
|
206
|
+
}
|
|
207
|
+
// History row for the initial state (draft → active is one common case; for any other
|
|
208
|
+
// initial lifecycle the row records draft → <initial> per lifecycle.md §3). The PK pairs the
|
|
209
|
+
// relationship id with a `randomUUID` suffix so re-creation under a recycled id (or two events
|
|
210
|
+
// arriving at the same `Date.now()` millisecond) cannot collide on the history PK.
|
|
211
|
+
insertHistoryRow(db, {
|
|
212
|
+
id: nextHistoryRowId(rel.id),
|
|
213
|
+
relationshipId: rel.id,
|
|
214
|
+
fromState: "draft",
|
|
215
|
+
toState: rel.lifecycleState,
|
|
216
|
+
occurredAt: rel.createdAt,
|
|
217
|
+
summary: rel.summary,
|
|
218
|
+
});
|
|
219
|
+
const row = db.prepare(SQL_GET).get(rel.id, rel.workspaceId);
|
|
220
|
+
if (row === undefined) {
|
|
221
|
+
throw new UiStoreError("internal", "Insert returned no row.", 500);
|
|
222
|
+
}
|
|
223
|
+
return rowToRelationship(row);
|
|
224
|
+
}
|
|
225
|
+
export function getRelationship(db, id, workspaceId) {
|
|
226
|
+
const row = db.prepare(SQL_GET).get(id, workspaceId);
|
|
227
|
+
return row === undefined ? undefined : rowToRelationship(row);
|
|
228
|
+
}
|
|
229
|
+
export function getRelationshipEtag(db, id, workspaceId) {
|
|
230
|
+
const row = db.prepare(SQL_GET_ETAG_SCOPED).get(id, workspaceId);
|
|
231
|
+
return row?.etag;
|
|
232
|
+
}
|
|
233
|
+
export function updateRelationshipLifecycle(db, args) {
|
|
234
|
+
const info = db
|
|
235
|
+
.prepare(SQL_UPDATE_LIFECYCLE)
|
|
236
|
+
.run(args.to, args.updatedAt, args.newEtag, args.summary ?? null, args.id, args.workspaceId);
|
|
237
|
+
if (info.changes === 0)
|
|
238
|
+
throw notFound("Relationship");
|
|
239
|
+
// Issue #539 audit: a deterministic `${id}-h-${updatedAt}` PK collides when two transitions
|
|
240
|
+
// share a `Date.now()` millisecond (frequent in tests with a pinned clock; possible in
|
|
241
|
+
// production at fast clock ticks). UNIQUE-violation would roll back the entire UPDATE — a
|
|
242
|
+
// silent lifecycle revert. The randomUUID-suffixed PK below stays unique within a workspace.
|
|
243
|
+
insertHistoryRow(db, {
|
|
244
|
+
id: nextHistoryRowId(args.id),
|
|
245
|
+
relationshipId: args.id,
|
|
246
|
+
fromState: args.previous,
|
|
247
|
+
toState: args.to,
|
|
248
|
+
occurredAt: args.updatedAt,
|
|
249
|
+
summary: args.summary,
|
|
250
|
+
});
|
|
251
|
+
const row = db.prepare(SQL_GET).get(args.id, args.workspaceId);
|
|
252
|
+
if (row === undefined)
|
|
253
|
+
throw notFound("Relationship");
|
|
254
|
+
return rowToRelationship(row);
|
|
255
|
+
}
|
|
256
|
+
export function reconnectRelationship(db, args) {
|
|
257
|
+
const info = db
|
|
258
|
+
.prepare(SQL_UPDATE_RECONNECT)
|
|
259
|
+
.run(args.target.kind, args.target.id, args.updatedAt, args.newEtag, args.summary ?? null, args.id, args.workspaceId);
|
|
260
|
+
if (info.changes === 0)
|
|
261
|
+
throw notFound("Relationship");
|
|
262
|
+
const row = db.prepare(SQL_GET).get(args.id, args.workspaceId);
|
|
263
|
+
if (row === undefined)
|
|
264
|
+
throw notFound("Relationship");
|
|
265
|
+
return rowToRelationship(row);
|
|
266
|
+
}
|
|
267
|
+
export function relationshipCardinalitySnapshot(db, workspaceId, source, target) {
|
|
268
|
+
const sourceCount = db.prepare(SQL_COUNT_PRODUCES_EVIDENCE_FOR_SOURCE).get(workspaceId, source.kind, source.id).n;
|
|
269
|
+
const targetCount = db.prepare(SQL_COUNT_STARTS_WORKFLOW_FOR_TARGET).get(workspaceId, target.kind, target.id).n;
|
|
270
|
+
return {
|
|
271
|
+
producesEvidenceForSource: sourceCount,
|
|
272
|
+
startsWorkflowForTarget: targetCount,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
// Closed-set predicate fragments. Each fragment is a STATIC `column = ?` string;
|
|
276
|
+
// only the parameter values are bound at execution time. No user input enters SQL text.
|
|
277
|
+
const LIST_FILTER_FRAGMENTS = [
|
|
278
|
+
{ key: "sourceKind", clause: "source_kind = ?" },
|
|
279
|
+
{ key: "sourceId", clause: "source_id = ?" },
|
|
280
|
+
{ key: "targetKind", clause: "target_kind = ?" },
|
|
281
|
+
{ key: "targetId", clause: "target_id = ?" },
|
|
282
|
+
{ key: "type", clause: "type = ?" },
|
|
283
|
+
{ key: "lifecycle", clause: "lifecycle = ?" },
|
|
284
|
+
{ key: "afterEtag", clause: "etag < ?" },
|
|
285
|
+
];
|
|
286
|
+
function buildListClauses(q) {
|
|
287
|
+
const clauses = ["workspace_scope_id = ?"];
|
|
288
|
+
const params = [q.workspaceId];
|
|
289
|
+
for (const fragment of LIST_FILTER_FRAGMENTS) {
|
|
290
|
+
const value = q[fragment.key];
|
|
291
|
+
if (value !== undefined) {
|
|
292
|
+
clauses.push(fragment.clause);
|
|
293
|
+
params.push(value);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return { clauses, params };
|
|
297
|
+
}
|
|
298
|
+
export function listRelationships(db, q) {
|
|
299
|
+
if (q.limit <= 0 || q.limit > MAX_LIST_LIMIT) {
|
|
300
|
+
throw invalidRequest("Limit out of bounds.");
|
|
301
|
+
}
|
|
302
|
+
const { clauses, params: filterParams } = buildListClauses(q);
|
|
303
|
+
const params = [...filterParams, q.limit + 1];
|
|
304
|
+
const sql = "SELECT id, schema_version, workspace_scope_id, scope_kind, scope_coordinate, type," +
|
|
305
|
+
" source_kind, source_id, target_kind, target_id, lifecycle, created_at, updated_at," +
|
|
306
|
+
" etag, confidence, summary FROM relationships WHERE " +
|
|
307
|
+
clauses.join(" AND ") +
|
|
308
|
+
" ORDER BY etag DESC, id ASC LIMIT ?";
|
|
309
|
+
const rows = db.prepare(sql).all(...params);
|
|
310
|
+
const truncated = rows.length > q.limit;
|
|
311
|
+
const slice = truncated ? rows.slice(0, q.limit) : rows;
|
|
312
|
+
const entries = slice.map(rowToRelationship);
|
|
313
|
+
const last = slice[slice.length - 1];
|
|
314
|
+
const nextCursor = truncated && last !== undefined ? last.etag : undefined;
|
|
315
|
+
// Always return undefined when not truncated; `nextCursor` is `string | undefined` so
|
|
316
|
+
// exactOptionalPropertyTypes-safe.
|
|
317
|
+
return nextCursor === undefined
|
|
318
|
+
? { entries, truncated, nextCursor: undefined }
|
|
319
|
+
: { entries, truncated, nextCursor };
|
|
320
|
+
}
|
|
321
|
+
export function findRelationshipsBySource(db, workspaceId, source, limit) {
|
|
322
|
+
if (limit <= 0 || limit > MAX_LIST_LIMIT)
|
|
323
|
+
throw invalidRequest("Limit out of bounds.");
|
|
324
|
+
const rows = db
|
|
325
|
+
.prepare(SQL_FIND_BY_SOURCE)
|
|
326
|
+
.all(workspaceId, source.kind, source.id, limit);
|
|
327
|
+
return rows.map(rowToRelationship);
|
|
328
|
+
}
|
|
329
|
+
export function findRelationshipsByTarget(db, workspaceId, target, limit) {
|
|
330
|
+
if (limit <= 0 || limit > MAX_LIST_LIMIT)
|
|
331
|
+
throw invalidRequest("Limit out of bounds.");
|
|
332
|
+
const rows = db
|
|
333
|
+
.prepare(SQL_FIND_BY_TARGET)
|
|
334
|
+
.all(workspaceId, target.kind, target.id, limit);
|
|
335
|
+
return rows.map(rowToRelationship);
|
|
336
|
+
}
|
|
337
|
+
export function listRelationshipLifecycleHistory(db, relationshipId, limit = LIFECYCLE_HISTORY_RETAIN) {
|
|
338
|
+
if (limit <= 0 || limit > LIFECYCLE_HISTORY_RETAIN) {
|
|
339
|
+
throw invalidRequest("History limit out of bounds.");
|
|
340
|
+
}
|
|
341
|
+
const rows = db.prepare(SQL_LIST_HISTORY).all(relationshipId, limit);
|
|
342
|
+
return rows.map((r) => {
|
|
343
|
+
const base = {
|
|
344
|
+
relationshipId: r.relationship_id,
|
|
345
|
+
fromState: r.from_state,
|
|
346
|
+
toState: r.to_state,
|
|
347
|
+
occurredAt: r.occurred_at,
|
|
348
|
+
...(r.summary === null ? {} : { summary: r.summary }),
|
|
349
|
+
};
|
|
350
|
+
return base;
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
export function walkDependencies(db, options) {
|
|
354
|
+
validateWalkBounds(options);
|
|
355
|
+
const origin = getRelationship(db, options.originId, options.workspaceId);
|
|
356
|
+
if (origin === undefined)
|
|
357
|
+
throw notFound("Relationship");
|
|
358
|
+
return runWalkFromOrigin(db, origin, options);
|
|
359
|
+
}
|
|
360
|
+
export function computeImpact(db, options) {
|
|
361
|
+
validateWalkBounds(options);
|
|
362
|
+
return runWalk(db, options.workspaceId, [options.endpoint], options);
|
|
363
|
+
}
|
|
364
|
+
export function graphHealth(db, workspaceId) {
|
|
365
|
+
const totals = computeLifecycleTotals(db, workspaceId);
|
|
366
|
+
const findings = computeHealthFindings(db, workspaceId);
|
|
367
|
+
const truncated = findings.orphanedEndpointsTruncated ||
|
|
368
|
+
findings.staleRelationshipsTruncated ||
|
|
369
|
+
findings.blockedRelationshipsTruncated ||
|
|
370
|
+
findings.failedRelationshipsTruncated ||
|
|
371
|
+
findings.invalidReferencesTruncated ||
|
|
372
|
+
findings.cycleScanTruncated;
|
|
373
|
+
return { checkedAt: Date.now(), totals, truncated, findings };
|
|
374
|
+
}
|
|
375
|
+
function computeLifecycleTotals(db, workspaceId) {
|
|
376
|
+
const rows = db.prepare(SQL_HEALTH_COUNTS).all(workspaceId);
|
|
377
|
+
const totals = {
|
|
378
|
+
draft: 0,
|
|
379
|
+
active: 0,
|
|
380
|
+
archived: 0,
|
|
381
|
+
superseded: 0,
|
|
382
|
+
revoked: 0,
|
|
383
|
+
blocked: 0,
|
|
384
|
+
stale: 0,
|
|
385
|
+
};
|
|
386
|
+
for (const r of rows) {
|
|
387
|
+
if (r.lifecycle in totals) {
|
|
388
|
+
totals[r.lifecycle] = r.n;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
return totals;
|
|
392
|
+
}
|
|
393
|
+
function rawRowToRef(r) {
|
|
394
|
+
return {
|
|
395
|
+
id: r.id,
|
|
396
|
+
type: r.type,
|
|
397
|
+
source: { kind: r.source_kind, id: r.source_id },
|
|
398
|
+
target: { kind: r.target_kind, id: r.target_id },
|
|
399
|
+
lifecycle: r.lifecycle,
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
function selectFindingsByLifecycle(db, workspaceId, lifecycle) {
|
|
403
|
+
// Over-fetch by one to detect truncation without a separate COUNT round-trip.
|
|
404
|
+
const cap = MAX_RELATIONSHIPS_PER_QUERY;
|
|
405
|
+
const raw = db
|
|
406
|
+
.prepare(SQL_HEALTH_FINDINGS_BY_LIFECYCLE)
|
|
407
|
+
.all(workspaceId, lifecycle, cap + 1);
|
|
408
|
+
const truncated = raw.length > cap;
|
|
409
|
+
const sliced = truncated ? raw.slice(0, cap) : raw;
|
|
410
|
+
return { rows: sliced.map(rawRowToRef), truncated };
|
|
411
|
+
}
|
|
412
|
+
const SUPPORTED_OBJECT_KIND_SET = new Set(RELATIONSHIP_SUPPORTED_OBJECT_KINDS);
|
|
413
|
+
function selectInvalidReferences(db, workspaceId) {
|
|
414
|
+
const cap = MAX_RELATIONSHIPS_PER_QUERY;
|
|
415
|
+
// Scan up to cap+1 rows; an endpoint kind not in the supported set marks the
|
|
416
|
+
// relationship as referencing an unsupported object kind.
|
|
417
|
+
const raw = db
|
|
418
|
+
.prepare(SQL_HEALTH_ACTIVE_RELATIONSHIPS)
|
|
419
|
+
.all(workspaceId, cap + 1);
|
|
420
|
+
const scanTruncated = raw.length > cap;
|
|
421
|
+
const sliced = scanTruncated ? raw.slice(0, cap) : raw;
|
|
422
|
+
const out = [];
|
|
423
|
+
for (const row of sliced) {
|
|
424
|
+
if (!SUPPORTED_OBJECT_KIND_SET.has(row.source_kind) ||
|
|
425
|
+
!SUPPORTED_OBJECT_KIND_SET.has(row.target_kind)) {
|
|
426
|
+
out.push(rawRowToRef(row));
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return { rows: out, truncated: scanTruncated };
|
|
430
|
+
}
|
|
431
|
+
function selectCycleParticipants(db, workspaceId) {
|
|
432
|
+
const cap = MAX_RELATIONSHIPS_PER_QUERY;
|
|
433
|
+
const raw = db
|
|
434
|
+
.prepare(SQL_HEALTH_ACTIVE_RELATIONSHIPS)
|
|
435
|
+
.all(workspaceId, cap + 1);
|
|
436
|
+
const scanTruncated = raw.length > cap;
|
|
437
|
+
const sliced = scanTruncated ? raw.slice(0, cap) : raw;
|
|
438
|
+
// Build adjacency keyed by endpoint, then DFS to find back-edges. Detection is
|
|
439
|
+
// O(V + E) over the bounded scan input.
|
|
440
|
+
const refs = sliced.map(rawRowToRef);
|
|
441
|
+
const participantIds = detectCycleParticipantIds(refs);
|
|
442
|
+
const rows = refs.filter((r) => participantIds.has(r.id));
|
|
443
|
+
return { rows, scanTruncated };
|
|
444
|
+
}
|
|
445
|
+
function detectCycleParticipantIds(refs) {
|
|
446
|
+
// Adjacency: node-key -> list of {nextNodeKey, relId}. Self-loops are cycles too.
|
|
447
|
+
const adjacency = new Map();
|
|
448
|
+
for (const ref of refs) {
|
|
449
|
+
const fromKey = nodeKey(ref.source);
|
|
450
|
+
const toKey = nodeKey(ref.target);
|
|
451
|
+
const list = adjacency.get(fromKey) ?? [];
|
|
452
|
+
list.push({ nextKey: toKey, relId: ref.id });
|
|
453
|
+
adjacency.set(fromKey, list);
|
|
454
|
+
}
|
|
455
|
+
const participants = new Set();
|
|
456
|
+
const state = new Map();
|
|
457
|
+
// Iterative DFS to avoid stack blow-up on deep graphs.
|
|
458
|
+
for (const startKey of adjacency.keys()) {
|
|
459
|
+
if (state.get(startKey) !== undefined)
|
|
460
|
+
continue;
|
|
461
|
+
dfsFromStart(startKey, adjacency, state, participants);
|
|
462
|
+
}
|
|
463
|
+
return participants;
|
|
464
|
+
}
|
|
465
|
+
function dfsFromStart(startKey, adjacency, state, participants) {
|
|
466
|
+
const stack = [{ key: startKey, index: 0, viaRelId: null }];
|
|
467
|
+
state.set(startKey, "visiting");
|
|
468
|
+
while (stack.length > 0) {
|
|
469
|
+
const frame = stack[stack.length - 1];
|
|
470
|
+
if (frame === undefined)
|
|
471
|
+
break;
|
|
472
|
+
const neighbours = adjacency.get(frame.key) ?? [];
|
|
473
|
+
if (frame.index >= neighbours.length) {
|
|
474
|
+
state.set(frame.key, "done");
|
|
475
|
+
stack.pop();
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
const edge = neighbours[frame.index];
|
|
479
|
+
frame.index += 1;
|
|
480
|
+
if (edge === undefined)
|
|
481
|
+
continue;
|
|
482
|
+
const neighbourState = state.get(edge.nextKey);
|
|
483
|
+
if (neighbourState === "visiting") {
|
|
484
|
+
// Back-edge: every relationship on the current path from edge.nextKey down
|
|
485
|
+
// is a cycle participant.
|
|
486
|
+
markBackEdgeParticipants(stack, edge, participants);
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
if (neighbourState === "done")
|
|
490
|
+
continue;
|
|
491
|
+
state.set(edge.nextKey, "visiting");
|
|
492
|
+
stack.push({ key: edge.nextKey, index: 0, viaRelId: edge.relId });
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
function markBackEdgeParticipants(stack, closingEdge, participants) {
|
|
496
|
+
// Walk the stack from top down to the frame whose key matches closingEdge.nextKey,
|
|
497
|
+
// marking the relationship that brought us into each frame as a participant.
|
|
498
|
+
participants.add(closingEdge.relId);
|
|
499
|
+
for (let i = stack.length - 1; i >= 0; i--) {
|
|
500
|
+
const frame = stack[i];
|
|
501
|
+
if (frame === undefined)
|
|
502
|
+
break;
|
|
503
|
+
if (frame.viaRelId !== null)
|
|
504
|
+
participants.add(frame.viaRelId);
|
|
505
|
+
if (frame.key === closingEdge.nextKey)
|
|
506
|
+
break;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
function selectOrphanedEndpoints(db, workspaceId) {
|
|
510
|
+
const cap = MAX_RELATIONSHIPS_PER_QUERY;
|
|
511
|
+
const raw = db
|
|
512
|
+
.prepare(SQL_HEALTH_ENDPOINT_PARTICIPATION)
|
|
513
|
+
.all(workspaceId, workspaceId, cap + 1);
|
|
514
|
+
const truncated = raw.length > cap;
|
|
515
|
+
const sliced = truncated ? raw.slice(0, cap) : raw;
|
|
516
|
+
const out = [];
|
|
517
|
+
for (const r of sliced) {
|
|
518
|
+
if (r.active_total === 0 && r.any_total > 0) {
|
|
519
|
+
out.push({ kind: r.kind, id: r.id });
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
return { rows: out, truncated };
|
|
523
|
+
}
|
|
524
|
+
function computeHealthFindings(db, workspaceId) {
|
|
525
|
+
const stale = selectFindingsByLifecycle(db, workspaceId, "stale");
|
|
526
|
+
const blocked = selectFindingsByLifecycle(db, workspaceId, "blocked");
|
|
527
|
+
// `failedRelationships` is the public field name on the health response; the underlying query
|
|
528
|
+
// selects `lifecycle='revoked'` rows (the lifecycle.md "failure" terminal state). The field
|
|
529
|
+
// alias is intentional — the public health surface speaks "failed" / the lifecycle state is
|
|
530
|
+
// `revoked`.
|
|
531
|
+
const failed = selectFindingsByLifecycle(db, workspaceId, "revoked");
|
|
532
|
+
const invalid = selectInvalidReferences(db, workspaceId);
|
|
533
|
+
const cycle = selectCycleParticipants(db, workspaceId);
|
|
534
|
+
const orphan = selectOrphanedEndpoints(db, workspaceId);
|
|
535
|
+
return {
|
|
536
|
+
orphanedEndpoints: orphan.rows,
|
|
537
|
+
orphanedEndpointsTruncated: orphan.truncated,
|
|
538
|
+
staleRelationships: stale.rows,
|
|
539
|
+
staleRelationshipsTruncated: stale.truncated,
|
|
540
|
+
blockedRelationships: blocked.rows,
|
|
541
|
+
blockedRelationshipsTruncated: blocked.truncated,
|
|
542
|
+
failedRelationships: failed.rows,
|
|
543
|
+
failedRelationshipsTruncated: failed.truncated,
|
|
544
|
+
invalidReferences: invalid.rows,
|
|
545
|
+
invalidReferencesTruncated: invalid.truncated,
|
|
546
|
+
cycleParticipants: cycle.rows,
|
|
547
|
+
cycleScanTruncated: cycle.scanTruncated,
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
// ─── Internal helpers ─────────────────────────────────────────────────────────
|
|
551
|
+
function relationshipScopeCoordinate(scope) {
|
|
552
|
+
switch (scope.kind) {
|
|
553
|
+
case "user":
|
|
554
|
+
return scope.userId;
|
|
555
|
+
case "workspace":
|
|
556
|
+
return scope.workspaceId;
|
|
557
|
+
case "project":
|
|
558
|
+
return scope.projectId;
|
|
559
|
+
case "workflow":
|
|
560
|
+
return scope.workflowDefinitionId;
|
|
561
|
+
case "global":
|
|
562
|
+
return "global";
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
function insertHistoryRow(db, row) {
|
|
566
|
+
db.prepare(SQL_INSERT_HISTORY).run(row.id, row.relationshipId, row.fromState, row.toState, row.occurredAt, row.summary ?? null);
|
|
567
|
+
}
|
|
568
|
+
// Stable history-row PK builder: `<relationshipId>-h-<8 hex chars>`. The relationship id is the
|
|
569
|
+
// owning row; the random suffix avoids same-millisecond collisions that a deterministic clock
|
|
570
|
+
// (test fixtures, fast production clock ticks) would otherwise produce.
|
|
571
|
+
function nextHistoryRowId(relationshipId) {
|
|
572
|
+
return `${relationshipId}-h-${randomUUID().replace(/-/g, "").slice(0, 8)}`;
|
|
573
|
+
}
|
|
574
|
+
function validateWalkBounds(o) {
|
|
575
|
+
if (o.maxDepth <= 0 || o.maxDepth > MAX_IMPACT_DEPTH) {
|
|
576
|
+
throw invalidRequest("maxDepth out of bounds.");
|
|
577
|
+
}
|
|
578
|
+
if (o.maxNodes <= 0 || o.maxNodes > MAX_IMPACT_NODES) {
|
|
579
|
+
throw invalidRequest("maxNodes out of bounds.");
|
|
580
|
+
}
|
|
581
|
+
if (o.maxRelationships <= 0 || o.maxRelationships > MAX_IMPACT_RELATIONSHIPS) {
|
|
582
|
+
throw invalidRequest("maxRelationships out of bounds.");
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
function runWalkFromOrigin(db, origin, options) {
|
|
586
|
+
// Seed from the relationship's endpoints — the walk includes the origin row itself plus
|
|
587
|
+
// its neighbours, expanding by `direction` per hop.
|
|
588
|
+
const seedEndpoints = [];
|
|
589
|
+
if (options.direction !== "incoming") {
|
|
590
|
+
seedEndpoints.push({ kind: origin.target.kind, id: origin.target.id });
|
|
591
|
+
}
|
|
592
|
+
if (options.direction !== "outgoing") {
|
|
593
|
+
seedEndpoints.push({ kind: origin.source.kind, id: origin.source.id });
|
|
594
|
+
}
|
|
595
|
+
const walkResult = runWalk(db, options.workspaceId, seedEndpoints, {
|
|
596
|
+
direction: options.direction,
|
|
597
|
+
maxDepth: options.maxDepth,
|
|
598
|
+
maxNodes: options.maxNodes,
|
|
599
|
+
maxRelationships: options.maxRelationships,
|
|
600
|
+
});
|
|
601
|
+
// Always include the origin relationship + endpoints.
|
|
602
|
+
const seenRels = new Set(walkResult.relationships.map((r) => r.id));
|
|
603
|
+
const relationships = seenRels.has(origin.id)
|
|
604
|
+
? [...walkResult.relationships]
|
|
605
|
+
: [origin, ...walkResult.relationships];
|
|
606
|
+
const seenNodes = new Set(walkResult.nodes.map(nodeKey));
|
|
607
|
+
const nodes = [...walkResult.nodes];
|
|
608
|
+
for (const n of [origin.source, origin.target]) {
|
|
609
|
+
if (!seenNodes.has(nodeKey(n))) {
|
|
610
|
+
seenNodes.add(nodeKey(n));
|
|
611
|
+
nodes.push({ kind: n.kind, id: n.id });
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
return {
|
|
615
|
+
relationships,
|
|
616
|
+
nodes,
|
|
617
|
+
truncated: walkResult.truncated,
|
|
618
|
+
truncationReason: walkResult.truncationReason,
|
|
619
|
+
depthReached: walkResult.depthReached,
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
function nodeKey(n) {
|
|
623
|
+
return `${n.kind}/${n.id}`;
|
|
624
|
+
}
|
|
625
|
+
function admitEndpoint(state, endpoint, nextFrontier) {
|
|
626
|
+
const key = nodeKey(endpoint);
|
|
627
|
+
if (state.visitedNodes.has(key))
|
|
628
|
+
return true;
|
|
629
|
+
if (state.collectedNodes.length >= state.maxNodes) {
|
|
630
|
+
state.truncationReason = "max-nodes";
|
|
631
|
+
return false;
|
|
632
|
+
}
|
|
633
|
+
state.visitedNodes.add(key);
|
|
634
|
+
const admitted = { kind: endpoint.kind, id: endpoint.id };
|
|
635
|
+
state.collectedNodes.push(admitted);
|
|
636
|
+
nextFrontier.push(admitted);
|
|
637
|
+
return true;
|
|
638
|
+
}
|
|
639
|
+
function admitRelationship(state, rel) {
|
|
640
|
+
if (state.seenRelationships.has(rel.id))
|
|
641
|
+
return true;
|
|
642
|
+
if (state.seenRelationships.size >= state.maxRelationships) {
|
|
643
|
+
state.truncationReason = "max-relationships";
|
|
644
|
+
return false;
|
|
645
|
+
}
|
|
646
|
+
state.seenRelationships.set(rel.id, rel);
|
|
647
|
+
return true;
|
|
648
|
+
}
|
|
649
|
+
function expandFrontier(outgoingStatement, incomingStatement, workspaceId, frontier, direction, state) {
|
|
650
|
+
const nextFrontier = [];
|
|
651
|
+
for (const node of frontier) {
|
|
652
|
+
const neighbours = expandNeighbours(outgoingStatement, incomingStatement, workspaceId, node, direction);
|
|
653
|
+
for (const rel of neighbours) {
|
|
654
|
+
if (!admitRelationship(state, rel))
|
|
655
|
+
return nextFrontier;
|
|
656
|
+
if (!admitEndpoint(state, rel.source, nextFrontier))
|
|
657
|
+
return nextFrontier;
|
|
658
|
+
if (!admitEndpoint(state, rel.target, nextFrontier))
|
|
659
|
+
return nextFrontier;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
return nextFrontier;
|
|
663
|
+
}
|
|
664
|
+
function seedWalkState(seed, options) {
|
|
665
|
+
const state = {
|
|
666
|
+
visitedNodes: new Set(),
|
|
667
|
+
collectedNodes: [],
|
|
668
|
+
seenRelationships: new Map(),
|
|
669
|
+
maxNodes: options.maxNodes,
|
|
670
|
+
maxRelationships: options.maxRelationships,
|
|
671
|
+
truncationReason: null,
|
|
672
|
+
};
|
|
673
|
+
for (const s of seed) {
|
|
674
|
+
if (!state.visitedNodes.has(nodeKey(s))) {
|
|
675
|
+
state.visitedNodes.add(nodeKey(s));
|
|
676
|
+
state.collectedNodes.push({ kind: s.kind, id: s.id });
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
return state;
|
|
680
|
+
}
|
|
681
|
+
function walkResult(state, depthReached, truncationReason) {
|
|
682
|
+
return {
|
|
683
|
+
relationships: [...state.seenRelationships.values()],
|
|
684
|
+
nodes: state.collectedNodes,
|
|
685
|
+
truncated: truncationReason !== null,
|
|
686
|
+
truncationReason,
|
|
687
|
+
depthReached,
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
function runWalk(db, workspaceId, seed, options) {
|
|
691
|
+
const state = seedWalkState(seed, options);
|
|
692
|
+
const outgoingStatement = options.direction === "incoming" ? undefined : db.prepare(SQL_FIND_BY_SOURCE);
|
|
693
|
+
const incomingStatement = options.direction === "outgoing" ? undefined : db.prepare(SQL_FIND_BY_TARGET);
|
|
694
|
+
let depthReached = 0;
|
|
695
|
+
let frontier = [...state.collectedNodes];
|
|
696
|
+
for (let depth = 0; depth < options.maxDepth; depth++) {
|
|
697
|
+
depthReached = depth + 1;
|
|
698
|
+
const nextFrontier = expandFrontier(outgoingStatement, incomingStatement, workspaceId, frontier, options.direction, state);
|
|
699
|
+
if (state.truncationReason !== null) {
|
|
700
|
+
return walkResult(state, depthReached, state.truncationReason);
|
|
701
|
+
}
|
|
702
|
+
if (nextFrontier.length === 0)
|
|
703
|
+
break;
|
|
704
|
+
frontier = nextFrontier;
|
|
705
|
+
}
|
|
706
|
+
// depth-bounded normal completion is not a truncation per se; we only mark `max-depth`
|
|
707
|
+
// when the frontier was non-empty after the last hop (more work to do).
|
|
708
|
+
const exhaustedDepth = frontier.length > 0 && depthReached === options.maxDepth;
|
|
709
|
+
return walkResult(state, depthReached, exhaustedDepth ? "max-depth" : null);
|
|
710
|
+
}
|
|
711
|
+
function expandNeighbours(outgoingStatement, incomingStatement, workspaceId, node, direction) {
|
|
712
|
+
const out = [];
|
|
713
|
+
if (direction !== "incoming") {
|
|
714
|
+
const rows = outgoingStatement?.all(workspaceId, node.kind, node.id, MAX_LIST_LIMIT);
|
|
715
|
+
for (const row of rows ?? [])
|
|
716
|
+
out.push(rowToRelationship(row));
|
|
717
|
+
}
|
|
718
|
+
if (direction !== "outgoing") {
|
|
719
|
+
const rows = incomingStatement?.all(workspaceId, node.kind, node.id, MAX_LIST_LIMIT);
|
|
720
|
+
for (const row of rows ?? [])
|
|
721
|
+
out.push(rowToRelationship(row));
|
|
722
|
+
}
|
|
723
|
+
return out;
|
|
724
|
+
}
|