@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,1204 @@
|
|
|
1
|
+
// MemoriaViva BFF route handlers (Issue #211 / Epic #204).
|
|
2
|
+
//
|
|
3
|
+
// These handlers wire the /api/memory/* routes to the three memory packages:
|
|
4
|
+
// - @oscharko-dev/keiko-memory-vault → persistence (list / get / update / delete)
|
|
5
|
+
// - @oscharko-dev/keiko-memory-governance → pure operation builders (pin, archive, forget, correct)
|
|
6
|
+
// - @oscharko-dev/keiko-memory-retrieval → not called here (retrieval is for model context)
|
|
7
|
+
//
|
|
8
|
+
// ADR-0019 direction rule 6a: keiko-server may import memory-vault, memory-governance, and
|
|
9
|
+
// memory-retrieval. Rule 8: the browser tier (keiko-ui) imports only keiko-contracts types
|
|
10
|
+
// via the BFF wire — never the domain packages directly.
|
|
11
|
+
//
|
|
12
|
+
// CSRF is enforced for all state-changing methods (POST/PATCH/DELETE) by the server dispatch
|
|
13
|
+
// layer in server.ts — handlers do NOT need to re-check.
|
|
14
|
+
//
|
|
15
|
+
// Every response is redacted through `deps.redactor` before serialisation to honour D9.
|
|
16
|
+
import { randomUUID } from "node:crypto";
|
|
17
|
+
import { createMemoryVault, MemoryStorageError, } from "@oscharko-dev/keiko-memory-vault";
|
|
18
|
+
import { GovernanceError, buildArchiveOperation, buildConflictTransitions, buildCorrection, buildForgetOperations, buildPinOperation, buildUnpinOperation, detectConflictPair, selectMemoriesForForget, supersededValidity, } from "@oscharko-dev/keiko-memory-governance";
|
|
19
|
+
import { checkStatusTransition, MEMORY_SCOPE_KINDS, MEMORY_STATUSES, MEMORY_TYPES, MEMORY_SENSITIVITIES, validateMemoryScope, } from "@oscharko-dev/keiko-contracts";
|
|
20
|
+
import { errorBody } from "./routes.js";
|
|
21
|
+
import { auditRunIdFor, recordMemoryAudit } from "./memory-audit-handler.js";
|
|
22
|
+
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
23
|
+
const MAX_MEMORY_BODY_BYTES = 64_000;
|
|
24
|
+
const DEFAULT_REVIEWER_ID = "memoriaviva-ui";
|
|
25
|
+
const MAX_LIST_LIMIT = 200;
|
|
26
|
+
const DEFAULT_LIST_LIMIT = 50;
|
|
27
|
+
const REVIEW_QUEUE_STATUSES = ["proposed", "conflicted", "expired"];
|
|
28
|
+
// ─── Type guards / helpers ─────────────────────────────────────────────────────
|
|
29
|
+
// Sanitise GovernanceError into a code-keyed safe response body. GovernanceError.message
|
|
30
|
+
// is composed as `GovernanceError(${code}): ${detail}` and can embed memory UUIDs from
|
|
31
|
+
// the inner detail string; the public surface should only expose the stable enum `code`.
|
|
32
|
+
function governanceErrorBody(err) {
|
|
33
|
+
return errorBody("GOVERNANCE_ERROR", `Governance constraint violated (${err.code}).`);
|
|
34
|
+
}
|
|
35
|
+
function isRecord(value) {
|
|
36
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
37
|
+
}
|
|
38
|
+
function isMemoryScopeKind(value) {
|
|
39
|
+
return typeof value === "string" && MEMORY_SCOPE_KINDS.includes(value);
|
|
40
|
+
}
|
|
41
|
+
function isMemoryStatus(value) {
|
|
42
|
+
return typeof value === "string" && MEMORY_STATUSES.includes(value);
|
|
43
|
+
}
|
|
44
|
+
function isMemoryType(value) {
|
|
45
|
+
return typeof value === "string" && MEMORY_TYPES.includes(value);
|
|
46
|
+
}
|
|
47
|
+
function isMemorySensitivity(value) {
|
|
48
|
+
return typeof value === "string" && MEMORY_SENSITIVITIES.includes(value);
|
|
49
|
+
}
|
|
50
|
+
function isScopeKindArray(value) {
|
|
51
|
+
return Array.isArray(value) && value.every(isMemoryScopeKind);
|
|
52
|
+
}
|
|
53
|
+
function isStatusArray(value) {
|
|
54
|
+
return Array.isArray(value) && value.every(isMemoryStatus);
|
|
55
|
+
}
|
|
56
|
+
function isTypeArray(value) {
|
|
57
|
+
return Array.isArray(value) && value.every(isMemoryType);
|
|
58
|
+
}
|
|
59
|
+
function isSensitivityArray(value) {
|
|
60
|
+
return Array.isArray(value) && value.every(isMemorySensitivity);
|
|
61
|
+
}
|
|
62
|
+
function parseIntQuery(raw, defaultValue, max) {
|
|
63
|
+
if (raw === null)
|
|
64
|
+
return defaultValue;
|
|
65
|
+
const n = parseInt(raw, 10);
|
|
66
|
+
if (!Number.isFinite(n) || n < 1)
|
|
67
|
+
return defaultValue;
|
|
68
|
+
return Math.min(n, max);
|
|
69
|
+
}
|
|
70
|
+
function splitComma(raw) {
|
|
71
|
+
if (raw === null || raw.trim().length === 0)
|
|
72
|
+
return [];
|
|
73
|
+
return raw
|
|
74
|
+
.split(",")
|
|
75
|
+
.map((s) => s.trim())
|
|
76
|
+
.filter((s) => s.length > 0);
|
|
77
|
+
}
|
|
78
|
+
function parseScope(raw) {
|
|
79
|
+
if (!isRecord(raw)) {
|
|
80
|
+
return { status: 400, body: errorBody("BAD_REQUEST", "selector.scope must be an object.") };
|
|
81
|
+
}
|
|
82
|
+
if (!validateMemoryScope(raw).ok) {
|
|
83
|
+
return {
|
|
84
|
+
status: 400,
|
|
85
|
+
body: errorBody("BAD_REQUEST", "selector.scope must be a valid memory scope."),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return raw;
|
|
89
|
+
}
|
|
90
|
+
// ─── Body reading ──────────────────────────────────────────────────────────────
|
|
91
|
+
class BodyTooLargeError extends Error {
|
|
92
|
+
constructor() {
|
|
93
|
+
super("request body too large");
|
|
94
|
+
this.name = "BodyTooLargeError";
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function readBody(req) {
|
|
98
|
+
return new Promise((resolve, reject) => {
|
|
99
|
+
const chunks = [];
|
|
100
|
+
let total = 0;
|
|
101
|
+
let capped = false;
|
|
102
|
+
req.on("data", (chunk) => {
|
|
103
|
+
total += chunk.length;
|
|
104
|
+
if (total > MAX_MEMORY_BODY_BYTES) {
|
|
105
|
+
if (!capped) {
|
|
106
|
+
capped = true;
|
|
107
|
+
chunks.length = 0;
|
|
108
|
+
reject(new BodyTooLargeError());
|
|
109
|
+
req.resume();
|
|
110
|
+
}
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
chunks.push(chunk);
|
|
114
|
+
});
|
|
115
|
+
req.on("end", () => {
|
|
116
|
+
if (!capped)
|
|
117
|
+
resolve(Buffer.concat(chunks).toString("utf8"));
|
|
118
|
+
});
|
|
119
|
+
req.on("error", reject);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
async function readJsonBody(req) {
|
|
123
|
+
let raw;
|
|
124
|
+
try {
|
|
125
|
+
raw = await readBody(req);
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
if (err instanceof BodyTooLargeError) {
|
|
129
|
+
return { status: 413, body: errorBody("PAYLOAD_TOO_LARGE", "Request body too large.") };
|
|
130
|
+
}
|
|
131
|
+
throw err;
|
|
132
|
+
}
|
|
133
|
+
let parsed;
|
|
134
|
+
try {
|
|
135
|
+
parsed = raw.length === 0 ? {} : JSON.parse(raw);
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return { status: 400, body: errorBody("BAD_REQUEST", "Request body is not valid JSON.") };
|
|
139
|
+
}
|
|
140
|
+
if (!isRecord(parsed)) {
|
|
141
|
+
return { status: 400, body: errorBody("BAD_REQUEST", "Request body must be a JSON object.") };
|
|
142
|
+
}
|
|
143
|
+
return parsed;
|
|
144
|
+
}
|
|
145
|
+
function isRouteResult(v) {
|
|
146
|
+
return isRecord(v) && typeof v.status === "number";
|
|
147
|
+
}
|
|
148
|
+
// ─── Vault access ──────────────────────────────────────────────────────────────
|
|
149
|
+
// The memory vault is optional in UiHandlerDeps (tests that do not exercise memory routes
|
|
150
|
+
// do not need it). At runtime it is created lazily inside the BFF server process.
|
|
151
|
+
// Handlers resolve it here; if absent we return 503 so integration tests that run with a
|
|
152
|
+
// minimal deps fixture still get a predictable status code rather than a crash.
|
|
153
|
+
function resolveVault(deps) {
|
|
154
|
+
if (deps.memoryVault === undefined) {
|
|
155
|
+
return {
|
|
156
|
+
status: 503,
|
|
157
|
+
body: errorBody("MEMORY_UNAVAILABLE", "Memory vault is not configured."),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
return deps.memoryVault;
|
|
161
|
+
}
|
|
162
|
+
// ─── Redaction helper ──────────────────────────────────────────────────────────
|
|
163
|
+
function redactMemory(deps, record) {
|
|
164
|
+
return deps.redactor(record);
|
|
165
|
+
}
|
|
166
|
+
function redactMemories(deps, records) {
|
|
167
|
+
return deps.redactor(records);
|
|
168
|
+
}
|
|
169
|
+
function sortMemories(records) {
|
|
170
|
+
return [...records].sort((a, b) => {
|
|
171
|
+
if (b.createdAt !== a.createdAt)
|
|
172
|
+
return b.createdAt - a.createdAt;
|
|
173
|
+
if (b.updatedAt !== a.updatedAt)
|
|
174
|
+
return b.updatedAt - a.updatedAt;
|
|
175
|
+
return a.id.localeCompare(b.id);
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
function listMemoriesAcrossScopes(vault, options) {
|
|
179
|
+
const records = vault.listMemories({
|
|
180
|
+
...(options.types !== undefined && options.types.length > 0 ? { type: options.types } : {}),
|
|
181
|
+
...(options.statuses !== undefined && options.statuses.length > 0
|
|
182
|
+
? { status: options.statuses }
|
|
183
|
+
: {}),
|
|
184
|
+
includeExpired: true,
|
|
185
|
+
});
|
|
186
|
+
const filtered = records.filter((record) => {
|
|
187
|
+
if (options.scopeKinds !== undefined &&
|
|
188
|
+
options.scopeKinds.length > 0 &&
|
|
189
|
+
!options.scopeKinds.includes(record.scope.kind)) {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
if (options.sensitivities !== undefined &&
|
|
193
|
+
options.sensitivities.length > 0 &&
|
|
194
|
+
!options.sensitivities.includes(record.provenance.sensitivity)) {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
return true;
|
|
198
|
+
});
|
|
199
|
+
return sortMemories(filtered);
|
|
200
|
+
}
|
|
201
|
+
function isStaleReviewCandidate(record) {
|
|
202
|
+
return (record.staleReason !== undefined &&
|
|
203
|
+
record.status !== "proposed" &&
|
|
204
|
+
record.status !== "conflicted" &&
|
|
205
|
+
record.status !== "expired" &&
|
|
206
|
+
record.status !== "rejected" &&
|
|
207
|
+
record.status !== "archived" &&
|
|
208
|
+
record.status !== "forgotten");
|
|
209
|
+
}
|
|
210
|
+
function listReviewQueueMemories(vault) {
|
|
211
|
+
const byStatus = listMemoriesAcrossScopes(vault, {
|
|
212
|
+
statuses: REVIEW_QUEUE_STATUSES,
|
|
213
|
+
});
|
|
214
|
+
const stale = listMemoriesAcrossScopes(vault, {}).filter(isStaleReviewCandidate);
|
|
215
|
+
const byId = new Map();
|
|
216
|
+
for (const record of [...byStatus, ...stale]) {
|
|
217
|
+
byId.set(record.id, record);
|
|
218
|
+
}
|
|
219
|
+
return sortMemories([...byId.values()]);
|
|
220
|
+
}
|
|
221
|
+
function parseListParams(ctx) {
|
|
222
|
+
const scopeKinds = splitComma(ctx.url.searchParams.get("scope"));
|
|
223
|
+
const types = splitComma(ctx.url.searchParams.get("type"));
|
|
224
|
+
const statuses = splitComma(ctx.url.searchParams.get("status"));
|
|
225
|
+
const sensitivities = splitComma(ctx.url.searchParams.get("sensitivity"));
|
|
226
|
+
if (scopeKinds.length > 0 && !isScopeKindArray(scopeKinds)) {
|
|
227
|
+
return {
|
|
228
|
+
status: 400,
|
|
229
|
+
body: errorBody("BAD_REQUEST", `scope must be a comma-separated list of valid scope kinds.`),
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
if (types.length > 0 && !isTypeArray(types)) {
|
|
233
|
+
return {
|
|
234
|
+
status: 400,
|
|
235
|
+
body: errorBody("BAD_REQUEST", `type must be a comma-separated list of valid memory types.`),
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
if (statuses.length > 0 && !isStatusArray(statuses)) {
|
|
239
|
+
return {
|
|
240
|
+
status: 400,
|
|
241
|
+
body: errorBody("BAD_REQUEST", `status must be a comma-separated list of valid memory statuses.`),
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
if (sensitivities.length > 0 && !isSensitivityArray(sensitivities)) {
|
|
245
|
+
return {
|
|
246
|
+
status: 400,
|
|
247
|
+
body: errorBody("BAD_REQUEST", `sensitivity must be a comma-separated list of valid sensitivity values.`),
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
return {
|
|
251
|
+
scopeKinds,
|
|
252
|
+
types,
|
|
253
|
+
statuses,
|
|
254
|
+
sensitivities,
|
|
255
|
+
limit: parseIntQuery(ctx.url.searchParams.get("limit"), DEFAULT_LIST_LIMIT, MAX_LIST_LIMIT),
|
|
256
|
+
offset: parseIntQuery(ctx.url.searchParams.get("offset"), 0, Number.MAX_SAFE_INTEGER),
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
export function handleListMemories(ctx, deps) {
|
|
260
|
+
const vault = resolveVault(deps);
|
|
261
|
+
if (isRouteResult(vault))
|
|
262
|
+
return vault;
|
|
263
|
+
const params = parseListParams(ctx);
|
|
264
|
+
if (isRouteResult(params))
|
|
265
|
+
return params;
|
|
266
|
+
const { scopeKinds, types, statuses, sensitivities, limit, offset } = params;
|
|
267
|
+
try {
|
|
268
|
+
const filtered = listMemoriesAcrossScopes(vault, {
|
|
269
|
+
...(scopeKinds.length > 0 ? { scopeKinds: scopeKinds } : {}),
|
|
270
|
+
...(types.length > 0 ? { types: types } : {}),
|
|
271
|
+
...(statuses.length > 0 ? { statuses: statuses } : {}),
|
|
272
|
+
...(sensitivities.length > 0
|
|
273
|
+
? { sensitivities: sensitivities }
|
|
274
|
+
: {}),
|
|
275
|
+
});
|
|
276
|
+
const page = filtered.slice(offset, offset + limit);
|
|
277
|
+
return {
|
|
278
|
+
status: 200,
|
|
279
|
+
body: { memories: redactMemories(deps, page), total: filtered.length, limit, offset },
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
catch (err) {
|
|
283
|
+
if (err instanceof MemoryStorageError) {
|
|
284
|
+
return { status: 500, body: errorBody("MEMORY_ERROR", "Failed to list memories.") };
|
|
285
|
+
}
|
|
286
|
+
throw err;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
// ─── Handler: GET /api/memory/review-queue ────────────────────────────────────
|
|
290
|
+
export function handleMemoryReviewQueue(_ctx, deps) {
|
|
291
|
+
const vault = resolveVault(deps);
|
|
292
|
+
if (isRouteResult(vault))
|
|
293
|
+
return vault;
|
|
294
|
+
try {
|
|
295
|
+
const proposed = listReviewQueueMemories(vault);
|
|
296
|
+
return {
|
|
297
|
+
status: 200,
|
|
298
|
+
body: {
|
|
299
|
+
memories: redactMemories(deps, proposed),
|
|
300
|
+
total: proposed.length,
|
|
301
|
+
},
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
catch (err) {
|
|
305
|
+
if (err instanceof MemoryStorageError) {
|
|
306
|
+
return { status: 500, body: errorBody("MEMORY_ERROR", "Failed to load review queue.") };
|
|
307
|
+
}
|
|
308
|
+
throw err;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
// ─── Handler: GET /api/memory/:id ─────────────────────────────────────────────
|
|
312
|
+
export function handleGetMemory(ctx, deps) {
|
|
313
|
+
const vault = resolveVault(deps);
|
|
314
|
+
if (isRouteResult(vault))
|
|
315
|
+
return vault;
|
|
316
|
+
const { id } = ctx.params;
|
|
317
|
+
if (id === undefined || id.length === 0) {
|
|
318
|
+
return { status: 400, body: errorBody("BAD_REQUEST", "Memory id is required.") };
|
|
319
|
+
}
|
|
320
|
+
try {
|
|
321
|
+
const record = vault.getMemory(id);
|
|
322
|
+
if (record === undefined) {
|
|
323
|
+
return { status: 404, body: errorBody("NOT_FOUND", "Memory not found.") };
|
|
324
|
+
}
|
|
325
|
+
return { status: 200, body: { memory: redactMemory(deps, record) } };
|
|
326
|
+
}
|
|
327
|
+
catch (err) {
|
|
328
|
+
if (err instanceof MemoryStorageError) {
|
|
329
|
+
return { status: 500, body: errorBody("MEMORY_ERROR", "Failed to read memory.") };
|
|
330
|
+
}
|
|
331
|
+
throw err;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
function parseEditInput(raw) {
|
|
335
|
+
const { body: newBody, tags, sensitivity } = raw;
|
|
336
|
+
if (newBody === undefined && tags === undefined && sensitivity === undefined) {
|
|
337
|
+
return {
|
|
338
|
+
status: 400,
|
|
339
|
+
body: errorBody("BAD_REQUEST", "At least one of body, tags, or sensitivity must be provided."),
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
if (newBody !== undefined && (typeof newBody !== "string" || newBody.trim().length === 0)) {
|
|
343
|
+
return { status: 400, body: errorBody("BAD_REQUEST", "body must be a non-empty string.") };
|
|
344
|
+
}
|
|
345
|
+
if (sensitivity !== undefined && !isMemorySensitivity(sensitivity)) {
|
|
346
|
+
return {
|
|
347
|
+
status: 400,
|
|
348
|
+
body: errorBody("BAD_REQUEST", `sensitivity must be one of: ${MEMORY_SENSITIVITIES.join(", ")}.`),
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
return {
|
|
352
|
+
newBody: typeof newBody === "string" ? newBody : undefined,
|
|
353
|
+
tags,
|
|
354
|
+
sensitivity,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
function buildEditPatch(input, existing) {
|
|
358
|
+
const { newBody, tags, sensitivity } = input;
|
|
359
|
+
const patch = {};
|
|
360
|
+
if (typeof newBody === "string" && newBody.trim().length > 0) {
|
|
361
|
+
patch.body = newBody.trim();
|
|
362
|
+
}
|
|
363
|
+
if (Array.isArray(tags)) {
|
|
364
|
+
patch.tags = tags.filter((t) => typeof t === "string");
|
|
365
|
+
}
|
|
366
|
+
if (isMemorySensitivity(sensitivity)) {
|
|
367
|
+
patch.provenance = { ...existing.provenance, sensitivity };
|
|
368
|
+
}
|
|
369
|
+
return patch;
|
|
370
|
+
}
|
|
371
|
+
export async function handleEditMemory(ctx, deps) {
|
|
372
|
+
const vault = resolveVault(deps);
|
|
373
|
+
if (isRouteResult(vault))
|
|
374
|
+
return vault;
|
|
375
|
+
const { id } = ctx.params;
|
|
376
|
+
if (id === undefined || id.length === 0) {
|
|
377
|
+
return { status: 400, body: errorBody("BAD_REQUEST", "Memory id is required.") };
|
|
378
|
+
}
|
|
379
|
+
const body = await readJsonBody(ctx.req);
|
|
380
|
+
if (isRouteResult(body))
|
|
381
|
+
return body;
|
|
382
|
+
const input = parseEditInput(body);
|
|
383
|
+
if (isRouteResult(input))
|
|
384
|
+
return input;
|
|
385
|
+
try {
|
|
386
|
+
const existing = vault.getMemory(id);
|
|
387
|
+
if (existing === undefined) {
|
|
388
|
+
return { status: 404, body: errorBody("NOT_FOUND", "Memory not found.") };
|
|
389
|
+
}
|
|
390
|
+
const updated = vault.updateMemory(id, buildEditPatch(input, existing), Date.now());
|
|
391
|
+
return { status: 200, body: { memory: redactMemory(deps, updated) } };
|
|
392
|
+
}
|
|
393
|
+
catch (err) {
|
|
394
|
+
if (err instanceof MemoryStorageError) {
|
|
395
|
+
return {
|
|
396
|
+
status: err.code === "not-found" ? 404 : 500,
|
|
397
|
+
body: errorBody("MEMORY_ERROR", "Failed to update memory."),
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
throw err;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
// ─── Handler: POST /api/memory/:id/pin ────────────────────────────────────────
|
|
404
|
+
export function handlePinMemory(ctx, deps) {
|
|
405
|
+
const vault = resolveVault(deps);
|
|
406
|
+
if (isRouteResult(vault))
|
|
407
|
+
return vault;
|
|
408
|
+
const { id } = ctx.params;
|
|
409
|
+
if (id === undefined || id.length === 0) {
|
|
410
|
+
return { status: 400, body: errorBody("BAD_REQUEST", "Memory id is required.") };
|
|
411
|
+
}
|
|
412
|
+
try {
|
|
413
|
+
const record = vault.getMemory(id);
|
|
414
|
+
if (record === undefined) {
|
|
415
|
+
return { status: 404, body: errorBody("NOT_FOUND", "Memory not found.") };
|
|
416
|
+
}
|
|
417
|
+
void buildPinOperation(record, { reviewerId: DEFAULT_REVIEWER_ID, nowMs: Date.now() });
|
|
418
|
+
const updated = vault.updateMemory(id, { pinned: true }, Date.now());
|
|
419
|
+
return { status: 200, body: { memory: redactMemory(deps, updated) } };
|
|
420
|
+
}
|
|
421
|
+
catch (err) {
|
|
422
|
+
if (err instanceof GovernanceError) {
|
|
423
|
+
return {
|
|
424
|
+
status: err.code === "idempotent-noop" ? 409 : 400,
|
|
425
|
+
body: governanceErrorBody(err),
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
if (err instanceof MemoryStorageError) {
|
|
429
|
+
return { status: 500, body: errorBody("MEMORY_ERROR", "Failed to pin memory.") };
|
|
430
|
+
}
|
|
431
|
+
throw err;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
// ─── Handler: POST /api/memory/:id/unpin ──────────────────────────────────────
|
|
435
|
+
export function handleUnpinMemory(ctx, deps) {
|
|
436
|
+
const vault = resolveVault(deps);
|
|
437
|
+
if (isRouteResult(vault))
|
|
438
|
+
return vault;
|
|
439
|
+
const { id } = ctx.params;
|
|
440
|
+
if (id === undefined || id.length === 0) {
|
|
441
|
+
return { status: 400, body: errorBody("BAD_REQUEST", "Memory id is required.") };
|
|
442
|
+
}
|
|
443
|
+
try {
|
|
444
|
+
const record = vault.getMemory(id);
|
|
445
|
+
if (record === undefined) {
|
|
446
|
+
return { status: 404, body: errorBody("NOT_FOUND", "Memory not found.") };
|
|
447
|
+
}
|
|
448
|
+
void buildUnpinOperation(record, { reviewerId: DEFAULT_REVIEWER_ID, nowMs: Date.now() });
|
|
449
|
+
const updated = vault.updateMemory(id, { pinned: false }, Date.now());
|
|
450
|
+
return { status: 200, body: { memory: redactMemory(deps, updated) } };
|
|
451
|
+
}
|
|
452
|
+
catch (err) {
|
|
453
|
+
if (err instanceof GovernanceError) {
|
|
454
|
+
return {
|
|
455
|
+
status: err.code === "idempotent-noop" ? 409 : 400,
|
|
456
|
+
body: governanceErrorBody(err),
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
if (err instanceof MemoryStorageError) {
|
|
460
|
+
return { status: 500, body: errorBody("MEMORY_ERROR", "Failed to unpin memory.") };
|
|
461
|
+
}
|
|
462
|
+
throw err;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
// ─── Handler: POST /api/memory/:id/archive ────────────────────────────────────
|
|
466
|
+
export async function handleArchiveMemory(ctx, deps) {
|
|
467
|
+
const vault = resolveVault(deps);
|
|
468
|
+
if (isRouteResult(vault))
|
|
469
|
+
return vault;
|
|
470
|
+
const { id } = ctx.params;
|
|
471
|
+
if (id === undefined || id.length === 0) {
|
|
472
|
+
return { status: 400, body: errorBody("BAD_REQUEST", "Memory id is required.") };
|
|
473
|
+
}
|
|
474
|
+
const body = await readJsonBody(ctx.req);
|
|
475
|
+
if (isRouteResult(body))
|
|
476
|
+
return body;
|
|
477
|
+
const reason = typeof body.reason === "string" ? body.reason.trim() : undefined;
|
|
478
|
+
try {
|
|
479
|
+
const record = vault.getMemory(id);
|
|
480
|
+
if (record === undefined) {
|
|
481
|
+
return { status: 404, body: errorBody("NOT_FOUND", "Memory not found.") };
|
|
482
|
+
}
|
|
483
|
+
void buildArchiveOperation(record, { reviewerId: DEFAULT_REVIEWER_ID, nowMs: Date.now() }, reason);
|
|
484
|
+
const updated = vault.updateMemory(id, { status: "archived" }, Date.now());
|
|
485
|
+
return { status: 200, body: { memory: redactMemory(deps, updated) } };
|
|
486
|
+
}
|
|
487
|
+
catch (err) {
|
|
488
|
+
if (err instanceof GovernanceError) {
|
|
489
|
+
return {
|
|
490
|
+
status: 400,
|
|
491
|
+
body: governanceErrorBody(err),
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
if (err instanceof MemoryStorageError) {
|
|
495
|
+
return { status: 500, body: errorBody("MEMORY_ERROR", "Failed to archive memory.") };
|
|
496
|
+
}
|
|
497
|
+
throw err;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
function parseDestructiveInput(raw, defaultReason) {
|
|
501
|
+
if (raw.acknowledged !== true) {
|
|
502
|
+
return {
|
|
503
|
+
status: 400,
|
|
504
|
+
body: errorBody("BAD_REQUEST", "acknowledged must be true to confirm the destructive operation."),
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
const reason = typeof raw.reason === "string" && raw.reason.trim().length > 0
|
|
508
|
+
? raw.reason.trim()
|
|
509
|
+
: defaultReason;
|
|
510
|
+
return { reason };
|
|
511
|
+
}
|
|
512
|
+
function parseByIdForgetSelector(raw) {
|
|
513
|
+
if (typeof raw.memoryId !== "string" || raw.memoryId.trim().length === 0) {
|
|
514
|
+
return {
|
|
515
|
+
status: 400,
|
|
516
|
+
body: errorBody("BAD_REQUEST", "selector.memoryId must be a non-empty string."),
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
return { kind: "by-id", memoryId: raw.memoryId };
|
|
520
|
+
}
|
|
521
|
+
function parseByScopeForgetSelector(raw) {
|
|
522
|
+
const scope = parseScope(raw.scope);
|
|
523
|
+
if (isRouteResult(scope))
|
|
524
|
+
return scope;
|
|
525
|
+
return { kind: "by-scope", scope };
|
|
526
|
+
}
|
|
527
|
+
function parseByTypeForgetSelector(raw) {
|
|
528
|
+
const scope = parseScope(raw.scope);
|
|
529
|
+
if (isRouteResult(scope))
|
|
530
|
+
return scope;
|
|
531
|
+
if (!isMemoryType(raw.type)) {
|
|
532
|
+
return {
|
|
533
|
+
status: 400,
|
|
534
|
+
body: errorBody("BAD_REQUEST", "selector.type must be a valid memory type."),
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
return { kind: "by-type", scope, type: raw.type };
|
|
538
|
+
}
|
|
539
|
+
function parseBySourceConversationForgetSelector(raw) {
|
|
540
|
+
const scope = parseScope(raw.scope);
|
|
541
|
+
if (isRouteResult(scope))
|
|
542
|
+
return scope;
|
|
543
|
+
if (typeof raw.sourceConversationId !== "string" ||
|
|
544
|
+
raw.sourceConversationId.trim().length === 0) {
|
|
545
|
+
return {
|
|
546
|
+
status: 400,
|
|
547
|
+
body: errorBody("BAD_REQUEST", "selector.sourceConversationId must be a non-empty string."),
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
return {
|
|
551
|
+
kind: "by-source-conversation",
|
|
552
|
+
scope,
|
|
553
|
+
sourceConversationId: raw.sourceConversationId,
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
function parseByTimeWindowForgetSelector(raw) {
|
|
557
|
+
const scope = parseScope(raw.scope);
|
|
558
|
+
if (isRouteResult(scope))
|
|
559
|
+
return scope;
|
|
560
|
+
if (typeof raw.olderThanMs !== "number" ||
|
|
561
|
+
!Number.isFinite(raw.olderThanMs) ||
|
|
562
|
+
raw.olderThanMs < 0) {
|
|
563
|
+
return {
|
|
564
|
+
status: 400,
|
|
565
|
+
body: errorBody("BAD_REQUEST", "selector.olderThanMs must be a finite non-negative number."),
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
return { kind: "by-time-window", scope, olderThanMs: raw.olderThanMs };
|
|
569
|
+
}
|
|
570
|
+
function parseForgetSelector(raw) {
|
|
571
|
+
if (!isRecord(raw)) {
|
|
572
|
+
return { status: 400, body: errorBody("BAD_REQUEST", "selector must be an object.") };
|
|
573
|
+
}
|
|
574
|
+
switch (raw.kind) {
|
|
575
|
+
case "by-id":
|
|
576
|
+
return parseByIdForgetSelector(raw);
|
|
577
|
+
case "by-scope":
|
|
578
|
+
return parseByScopeForgetSelector(raw);
|
|
579
|
+
case "by-type":
|
|
580
|
+
return parseByTypeForgetSelector(raw);
|
|
581
|
+
case "by-source-conversation":
|
|
582
|
+
return parseBySourceConversationForgetSelector(raw);
|
|
583
|
+
case "by-time-window":
|
|
584
|
+
return parseByTimeWindowForgetSelector(raw);
|
|
585
|
+
default:
|
|
586
|
+
return {
|
|
587
|
+
status: 400,
|
|
588
|
+
body: errorBody("BAD_REQUEST", "selector.kind is not supported."),
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
function parseForgetSelectionInput(raw) {
|
|
593
|
+
const destructive = parseDestructiveInput(raw, "user-initiated selective forget from MemoriaViva");
|
|
594
|
+
if (isRouteResult(destructive))
|
|
595
|
+
return destructive;
|
|
596
|
+
const selector = parseForgetSelector(raw.selector);
|
|
597
|
+
if (isRouteResult(selector))
|
|
598
|
+
return selector;
|
|
599
|
+
return { ...destructive, selector };
|
|
600
|
+
}
|
|
601
|
+
function listForgetCandidates(vault, selector) {
|
|
602
|
+
if (selector.kind === "by-id") {
|
|
603
|
+
const record = vault.getMemory(selector.memoryId);
|
|
604
|
+
if (record === undefined) {
|
|
605
|
+
return { status: 404, body: errorBody("NOT_FOUND", "Memory not found.") };
|
|
606
|
+
}
|
|
607
|
+
return [record];
|
|
608
|
+
}
|
|
609
|
+
return sortMemories(vault.listMemoriesByScope(selector.scope, { includeExpired: true }));
|
|
610
|
+
}
|
|
611
|
+
function executeForgetSelection(vault, selector, reason) {
|
|
612
|
+
const nowMs = Date.now();
|
|
613
|
+
const records = listForgetCandidates(vault, selector);
|
|
614
|
+
if (isRouteResult(records))
|
|
615
|
+
return records;
|
|
616
|
+
const candidates = selectMemoriesForForget(records, selector, { nowMs });
|
|
617
|
+
if (candidates.length === 0) {
|
|
618
|
+
return {
|
|
619
|
+
status: 409,
|
|
620
|
+
body: errorBody("GOVERNANCE_ERROR", "No matching memories can be forgotten."),
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
const operations = buildForgetOperations(candidates, { reviewerId: DEFAULT_REVIEWER_ID, nowMs }, { reason, writeTombstone: true });
|
|
624
|
+
vault.deleteMemories(operations.map((operation) => ({
|
|
625
|
+
id: operation.memoryId,
|
|
626
|
+
options: {
|
|
627
|
+
tombstone: true,
|
|
628
|
+
reviewerId: operation.reviewerId,
|
|
629
|
+
reason: operation.reason,
|
|
630
|
+
forgetterSurface: "memory-center",
|
|
631
|
+
nowMs: operation.forgottenAt,
|
|
632
|
+
},
|
|
633
|
+
})));
|
|
634
|
+
return { memoryIds: operations.map((operation) => operation.memoryId) };
|
|
635
|
+
}
|
|
636
|
+
function formatForgetBody(memoryIds) {
|
|
637
|
+
return {
|
|
638
|
+
forgotten: true,
|
|
639
|
+
memoryIds,
|
|
640
|
+
count: memoryIds.length,
|
|
641
|
+
...(memoryIds.length === 1 ? { memoryId: memoryIds[0] } : {}),
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
function memoryMutationErrorBody(err, fallbackMessage) {
|
|
645
|
+
if (err instanceof GovernanceError) {
|
|
646
|
+
return { status: 400, body: governanceErrorBody(err) };
|
|
647
|
+
}
|
|
648
|
+
if (err instanceof MemoryStorageError) {
|
|
649
|
+
return {
|
|
650
|
+
status: err.code === "not-found" ? 404 : 500,
|
|
651
|
+
body: errorBody("MEMORY_ERROR", fallbackMessage),
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
throw err;
|
|
655
|
+
}
|
|
656
|
+
export async function handleForgetMemory(ctx, deps) {
|
|
657
|
+
const vault = resolveVault(deps);
|
|
658
|
+
if (isRouteResult(vault))
|
|
659
|
+
return vault;
|
|
660
|
+
const { id } = ctx.params;
|
|
661
|
+
if (id === undefined || id.length === 0) {
|
|
662
|
+
return { status: 400, body: errorBody("BAD_REQUEST", "Memory id is required.") };
|
|
663
|
+
}
|
|
664
|
+
const body = await readJsonBody(ctx.req);
|
|
665
|
+
if (isRouteResult(body))
|
|
666
|
+
return body;
|
|
667
|
+
const input = parseDestructiveInput(body, "user-initiated forget from MemoriaViva");
|
|
668
|
+
if (isRouteResult(input))
|
|
669
|
+
return input;
|
|
670
|
+
try {
|
|
671
|
+
const result = executeForgetSelection(vault, { kind: "by-id", memoryId: id }, input.reason);
|
|
672
|
+
if (isRouteResult(result))
|
|
673
|
+
return result;
|
|
674
|
+
return { status: 200, body: formatForgetBody(result.memoryIds) };
|
|
675
|
+
}
|
|
676
|
+
catch (err) {
|
|
677
|
+
return memoryMutationErrorBody(err, "Failed to forget memory.");
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
export async function handleForgetMemories(ctx, deps) {
|
|
681
|
+
const vault = resolveVault(deps);
|
|
682
|
+
if (isRouteResult(vault))
|
|
683
|
+
return vault;
|
|
684
|
+
const body = await readJsonBody(ctx.req);
|
|
685
|
+
if (isRouteResult(body))
|
|
686
|
+
return body;
|
|
687
|
+
const input = parseForgetSelectionInput(body);
|
|
688
|
+
if (isRouteResult(input))
|
|
689
|
+
return input;
|
|
690
|
+
try {
|
|
691
|
+
const result = executeForgetSelection(vault, input.selector, input.reason);
|
|
692
|
+
if (isRouteResult(result))
|
|
693
|
+
return result;
|
|
694
|
+
return { status: 200, body: formatForgetBody(result.memoryIds) };
|
|
695
|
+
}
|
|
696
|
+
catch (err) {
|
|
697
|
+
return memoryMutationErrorBody(err, "Failed to forget memories.");
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
// ─── Handler: DELETE /api/memory/:id ──────────────────────────────────────────
|
|
701
|
+
// DELETE is a convenience alias for governed, tombstoned deletion. It does not expose hard delete.
|
|
702
|
+
export async function handleDeleteMemory(ctx, deps) {
|
|
703
|
+
const vault = resolveVault(deps);
|
|
704
|
+
if (isRouteResult(vault))
|
|
705
|
+
return vault;
|
|
706
|
+
const { id } = ctx.params;
|
|
707
|
+
if (id === undefined || id.length === 0) {
|
|
708
|
+
return { status: 400, body: errorBody("BAD_REQUEST", "Memory id is required.") };
|
|
709
|
+
}
|
|
710
|
+
const body = await readJsonBody(ctx.req);
|
|
711
|
+
if (isRouteResult(body))
|
|
712
|
+
return body;
|
|
713
|
+
const input = parseDestructiveInput(body, "user-initiated delete from MemoriaViva");
|
|
714
|
+
if (isRouteResult(input))
|
|
715
|
+
return input;
|
|
716
|
+
try {
|
|
717
|
+
const result = executeForgetSelection(vault, { kind: "by-id", memoryId: id }, input.reason);
|
|
718
|
+
if (isRouteResult(result))
|
|
719
|
+
return result;
|
|
720
|
+
return {
|
|
721
|
+
status: 200,
|
|
722
|
+
body: {
|
|
723
|
+
deleted: true,
|
|
724
|
+
memoryId: id,
|
|
725
|
+
memoryIds: result.memoryIds,
|
|
726
|
+
count: result.memoryIds.length,
|
|
727
|
+
},
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
catch (err) {
|
|
731
|
+
return memoryMutationErrorBody(err, "Failed to delete memory.");
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
function uniqueIds(ids) {
|
|
735
|
+
return ids.filter((id, index) => ids.indexOf(id) === index);
|
|
736
|
+
}
|
|
737
|
+
function parseConflictResolutionInput(raw) {
|
|
738
|
+
if (typeof raw.winner !== "string" || raw.winner.trim().length === 0) {
|
|
739
|
+
return { status: 400, body: errorBody("BAD_REQUEST", "winner must be a non-empty string.") };
|
|
740
|
+
}
|
|
741
|
+
if (!Array.isArray(raw.losers) ||
|
|
742
|
+
raw.losers.length === 0 ||
|
|
743
|
+
!raw.losers.every((id) => typeof id === "string" && id.trim().length > 0)) {
|
|
744
|
+
return {
|
|
745
|
+
status: 400,
|
|
746
|
+
body: errorBody("BAD_REQUEST", "losers must be a non-empty string array."),
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
const reason = typeof raw.reason === "string" && raw.reason.trim().length > 0
|
|
750
|
+
? raw.reason.trim()
|
|
751
|
+
: "conflict resolved from MemoriaViva";
|
|
752
|
+
const winner = raw.winner;
|
|
753
|
+
const losers = raw.losers.map((id) => id);
|
|
754
|
+
if (uniqueIds([winner, ...losers]).length !== 1 + losers.length) {
|
|
755
|
+
return {
|
|
756
|
+
status: 400,
|
|
757
|
+
body: errorBody("BAD_REQUEST", "winner and losers must be unique memory ids."),
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
return {
|
|
761
|
+
winner,
|
|
762
|
+
losers,
|
|
763
|
+
reason,
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
function loadConflictMemories(vault, input) {
|
|
767
|
+
const records = [];
|
|
768
|
+
for (const id of uniqueIds([input.winner, ...input.losers])) {
|
|
769
|
+
const record = vault.getMemory(id);
|
|
770
|
+
if (record === undefined) {
|
|
771
|
+
return { status: 404, body: errorBody("NOT_FOUND", "Memory not found.") };
|
|
772
|
+
}
|
|
773
|
+
records.push(record);
|
|
774
|
+
}
|
|
775
|
+
return records;
|
|
776
|
+
}
|
|
777
|
+
function scopeKey(scope) {
|
|
778
|
+
switch (scope.kind) {
|
|
779
|
+
case "user":
|
|
780
|
+
return `user:${scope.userId}`;
|
|
781
|
+
case "workspace":
|
|
782
|
+
return `workspace:${scope.workspaceId}`;
|
|
783
|
+
case "project":
|
|
784
|
+
return `project:${scope.projectId}`;
|
|
785
|
+
case "workflow":
|
|
786
|
+
return `workflow:${scope.workflowDefinitionId}`;
|
|
787
|
+
case "global":
|
|
788
|
+
return "global";
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
function validateConflictPairForResolution(winner, loser) {
|
|
792
|
+
if (winner.type !== loser.type || scopeKey(winner.scope) !== scopeKey(loser.scope)) {
|
|
793
|
+
throw new GovernanceError("invalid-resolution", "conflict resolution requires memories with the same scope and type");
|
|
794
|
+
}
|
|
795
|
+
const conflict = detectConflictPair(winner, loser);
|
|
796
|
+
if (!conflict.hasConflict) {
|
|
797
|
+
throw new GovernanceError("invalid-resolution", "conflict resolution requires an actual detected conflict");
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
function validateConflictResolutionMemories(memories, input) {
|
|
801
|
+
const winner = findMemoryById(memories, input.winner);
|
|
802
|
+
if (winner === undefined) {
|
|
803
|
+
throw new GovernanceError("invalid-resolution", "winner is not loaded");
|
|
804
|
+
}
|
|
805
|
+
for (const loserId of input.losers) {
|
|
806
|
+
const loser = findMemoryById(memories, loserId);
|
|
807
|
+
if (loser === undefined) {
|
|
808
|
+
throw new GovernanceError("invalid-resolution", "loser is not loaded");
|
|
809
|
+
}
|
|
810
|
+
validateConflictPairForResolution(winner, loser);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
function buildEdgeFromSupersession(supersession) {
|
|
814
|
+
return {
|
|
815
|
+
id: randomUUID(),
|
|
816
|
+
schemaVersion: "1",
|
|
817
|
+
fromMemoryId: supersession.oldMemoryId,
|
|
818
|
+
toMemoryId: supersession.newMemoryId,
|
|
819
|
+
kind: supersession.edgeKind,
|
|
820
|
+
createdAt: supersession.supersededAt,
|
|
821
|
+
provenanceSummary: supersession.reason,
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
function findMemoryById(memories, id) {
|
|
825
|
+
return memories.find((memory) => memory.id === id);
|
|
826
|
+
}
|
|
827
|
+
function persistConflictTransitions(vault, resolution, reason) {
|
|
828
|
+
for (const transition of resolution.statusTransitions) {
|
|
829
|
+
// Bi-temporal-lite (#204, C1): a record losing a conflict and being SUPERSEDED gets its belief
|
|
830
|
+
// window closed at the transition time, same as the correction path. Other transitions (e.g.
|
|
831
|
+
// the winner re-accepted) leave validity untouched.
|
|
832
|
+
const existing = transition.to === "superseded" ? vault.getMemory(transition.memoryId) : undefined;
|
|
833
|
+
const validity = existing !== undefined ? supersededValidity(existing, transition.transitionedAt) : null;
|
|
834
|
+
vault.updateMemory(transition.memoryId, { status: transition.to, staleReason: reason, ...(validity !== null ? { validity } : {}) }, transition.transitionedAt);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
function persistConflictSupersessions(vault, deps, memories, supersessions, nowMs) {
|
|
838
|
+
const edgeIds = [];
|
|
839
|
+
for (const supersession of supersessions) {
|
|
840
|
+
const edge = vault.insertEdge(buildEdgeFromSupersession(supersession));
|
|
841
|
+
edgeIds.push(edge.id);
|
|
842
|
+
const loser = findMemoryById(memories, supersession.oldMemoryId);
|
|
843
|
+
const winner = findMemoryById(memories, supersession.newMemoryId);
|
|
844
|
+
if (loser === undefined || winner === undefined)
|
|
845
|
+
continue;
|
|
846
|
+
recordSupersessionAudit(deps, loser, winner, nowMs, "Conflict resolution linked losing memory to the selected winner.");
|
|
847
|
+
}
|
|
848
|
+
return edgeIds;
|
|
849
|
+
}
|
|
850
|
+
function executeConflictResolution(vault, deps, input) {
|
|
851
|
+
const memories = loadConflictMemories(vault, input);
|
|
852
|
+
if (isRouteResult(memories))
|
|
853
|
+
return memories;
|
|
854
|
+
validateConflictResolutionMemories(memories, input);
|
|
855
|
+
const nowMs = Date.now();
|
|
856
|
+
const resolution = buildConflictTransitions(memories, { winner: input.winner, losers: input.losers }, { reviewerId: DEFAULT_REVIEWER_ID, nowMs });
|
|
857
|
+
persistConflictTransitions(vault, resolution, input.reason);
|
|
858
|
+
const edgeIds = persistConflictSupersessions(vault, deps, memories, resolution.supersessions, nowMs);
|
|
859
|
+
// Outcome-driven forgetting (#204, O-V1): the winner proved more correct (utility 1), the losers
|
|
860
|
+
// proved wrong (utility 0). These bias the maintenance utility factor toward keeping the winner
|
|
861
|
+
// and forgetting the superseded losers.
|
|
862
|
+
vault.recordOutcome([input.winner], 1, nowMs);
|
|
863
|
+
vault.recordOutcome(input.losers, 0, nowMs);
|
|
864
|
+
return {
|
|
865
|
+
resolved: true,
|
|
866
|
+
winner: input.winner,
|
|
867
|
+
losers: input.losers,
|
|
868
|
+
supersessionEdgeIds: edgeIds,
|
|
869
|
+
transitions: resolution.statusTransitions,
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
export async function handleResolveMemoryConflict(ctx, deps) {
|
|
873
|
+
const vault = resolveVault(deps);
|
|
874
|
+
if (isRouteResult(vault))
|
|
875
|
+
return vault;
|
|
876
|
+
const body = await readJsonBody(ctx.req);
|
|
877
|
+
if (isRouteResult(body))
|
|
878
|
+
return body;
|
|
879
|
+
const input = parseConflictResolutionInput(body);
|
|
880
|
+
if (isRouteResult(input))
|
|
881
|
+
return input;
|
|
882
|
+
try {
|
|
883
|
+
const result = executeConflictResolution(vault, deps, input);
|
|
884
|
+
if (isRouteResult(result))
|
|
885
|
+
return result;
|
|
886
|
+
return { status: 200, body: result };
|
|
887
|
+
}
|
|
888
|
+
catch (err) {
|
|
889
|
+
return memoryMutationErrorBody(err, "Failed to resolve conflict.");
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
// ─── Handler: POST /api/memory/:id/correct ────────────────────────────────────
|
|
893
|
+
// A correction creates a new "proposed" correction-type memory and links it to the old one.
|
|
894
|
+
function parseCorrectInput(raw) {
|
|
895
|
+
const correctedBody = typeof raw.body === "string" ? raw.body.trim() : "";
|
|
896
|
+
if (correctedBody.length === 0) {
|
|
897
|
+
return {
|
|
898
|
+
status: 400,
|
|
899
|
+
body: errorBody("BAD_REQUEST", "body must be a non-empty string for the corrected memory."),
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
return { correctedBody };
|
|
903
|
+
}
|
|
904
|
+
function buildCorrectionRecord(proposal, id, nowMs) {
|
|
905
|
+
// Note: exactOptionalPropertyTypes is on — omit staleReason rather than assigning undefined.
|
|
906
|
+
const base = {
|
|
907
|
+
id,
|
|
908
|
+
schemaVersion: "1",
|
|
909
|
+
scope: proposal.scope,
|
|
910
|
+
type: proposal.type,
|
|
911
|
+
body: proposal.body,
|
|
912
|
+
provenance: proposal.provenance,
|
|
913
|
+
validity: proposal.validity,
|
|
914
|
+
status: proposal.initialStatus,
|
|
915
|
+
pinned: false,
|
|
916
|
+
tags: proposal.tags,
|
|
917
|
+
createdAt: nowMs,
|
|
918
|
+
updatedAt: nowMs,
|
|
919
|
+
};
|
|
920
|
+
return {
|
|
921
|
+
...base,
|
|
922
|
+
...(proposal.payload === undefined ? {} : { payload: proposal.payload }),
|
|
923
|
+
...(proposal.retentionHint === undefined ? {} : { retentionHint: proposal.retentionHint }),
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
function redactString(deps, value) {
|
|
927
|
+
const redacted = deps.redactor(value);
|
|
928
|
+
return typeof redacted === "string" ? redacted : value;
|
|
929
|
+
}
|
|
930
|
+
function recordSupersessionAudit(deps, oldMemory, newMemory, nowMs, summary) {
|
|
931
|
+
const event = {
|
|
932
|
+
schemaVersion: "1",
|
|
933
|
+
kind: "memory:superseded",
|
|
934
|
+
eventId: randomUUID(),
|
|
935
|
+
occurredAt: nowMs,
|
|
936
|
+
initiatorSurface: "memory-center",
|
|
937
|
+
summary,
|
|
938
|
+
oldMemoryId: oldMemory.id,
|
|
939
|
+
newMemoryId: newMemory.id,
|
|
940
|
+
scope: oldMemory.scope,
|
|
941
|
+
};
|
|
942
|
+
recordMemoryAudit({ evidenceStore: deps.evidenceStore, redactString: (value) => redactString(deps, value) }, event);
|
|
943
|
+
}
|
|
944
|
+
function auditEventCountForDay(deps, nowMs) {
|
|
945
|
+
const json = deps.evidenceStore.get(auditRunIdFor(nowMs));
|
|
946
|
+
if (json === undefined) {
|
|
947
|
+
return 0;
|
|
948
|
+
}
|
|
949
|
+
try {
|
|
950
|
+
const parsed = JSON.parse(json);
|
|
951
|
+
return Array.isArray(parsed) ? parsed.length : 0;
|
|
952
|
+
}
|
|
953
|
+
catch {
|
|
954
|
+
return 0;
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
function recordCorrectionProposalAuditIfNeeded(deps, inserted, nowMs, countBeforeInsert) {
|
|
958
|
+
if (auditEventCountForDay(deps, nowMs) > countBeforeInsert) {
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
const event = {
|
|
962
|
+
schemaVersion: "1",
|
|
963
|
+
kind: "memory:proposed",
|
|
964
|
+
eventId: randomUUID(),
|
|
965
|
+
occurredAt: nowMs,
|
|
966
|
+
initiatorSurface: "memory-center",
|
|
967
|
+
summary: `memory ${inserted.id} correction proposed`,
|
|
968
|
+
memoryId: inserted.id,
|
|
969
|
+
scope: inserted.scope,
|
|
970
|
+
};
|
|
971
|
+
recordMemoryAudit({ evidenceStore: deps.evidenceStore, redactString: (value) => redactString(deps, value) }, event);
|
|
972
|
+
}
|
|
973
|
+
export async function handleCorrectMemory(ctx, deps) {
|
|
974
|
+
const vault = resolveVault(deps);
|
|
975
|
+
if (isRouteResult(vault))
|
|
976
|
+
return vault;
|
|
977
|
+
const { id } = ctx.params;
|
|
978
|
+
if (id === undefined || id.length === 0) {
|
|
979
|
+
return { status: 400, body: errorBody("BAD_REQUEST", "Memory id is required.") };
|
|
980
|
+
}
|
|
981
|
+
const body = await readJsonBody(ctx.req);
|
|
982
|
+
if (isRouteResult(body))
|
|
983
|
+
return body;
|
|
984
|
+
const input = parseCorrectInput(body);
|
|
985
|
+
if (isRouteResult(input))
|
|
986
|
+
return input;
|
|
987
|
+
try {
|
|
988
|
+
const existing = vault.getMemory(id);
|
|
989
|
+
if (existing === undefined) {
|
|
990
|
+
return { status: 404, body: errorBody("NOT_FOUND", "Memory not found.") };
|
|
991
|
+
}
|
|
992
|
+
const nowMs = Date.now();
|
|
993
|
+
const correctionId = randomUUID();
|
|
994
|
+
const { proposal, supersession } = buildCorrection({
|
|
995
|
+
olderMemory: existing,
|
|
996
|
+
correctedBody: input.correctedBody,
|
|
997
|
+
context: { reviewerId: DEFAULT_REVIEWER_ID, nowMs },
|
|
998
|
+
newProposalId: randomUUID(),
|
|
999
|
+
newMemoryId: correctionId,
|
|
1000
|
+
});
|
|
1001
|
+
const auditCountBeforeInsert = auditEventCountForDay(deps, nowMs);
|
|
1002
|
+
const inserted = vault.insertMemory(buildCorrectionRecord(proposal, correctionId, nowMs));
|
|
1003
|
+
recordCorrectionProposalAuditIfNeeded(deps, inserted, nowMs, auditCountBeforeInsert);
|
|
1004
|
+
vault.insertEdge(buildEdgeFromSupersession(supersession));
|
|
1005
|
+
return {
|
|
1006
|
+
status: 201,
|
|
1007
|
+
body: { correction: redactMemory(deps, inserted), originalMemoryId: id },
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
catch (err) {
|
|
1011
|
+
if (err instanceof GovernanceError) {
|
|
1012
|
+
return { status: 400, body: governanceErrorBody(err) };
|
|
1013
|
+
}
|
|
1014
|
+
if (err instanceof MemoryStorageError) {
|
|
1015
|
+
return { status: 500, body: errorBody("MEMORY_ERROR", "Failed to create correction.") };
|
|
1016
|
+
}
|
|
1017
|
+
throw err;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
// ─── Handler: POST /api/memory/proposals/:id/accept ───────────────────────────
|
|
1021
|
+
function assertSupersedable(memory) {
|
|
1022
|
+
const check = checkStatusTransition(memory.status, "superseded");
|
|
1023
|
+
if (!check.ok) {
|
|
1024
|
+
throw new GovernanceError("illegal-status-transition", check.reason ?? `illegal transition: ${memory.status} -> superseded`);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
function loadCorrectionSupersessionOrigins(vault, proposal) {
|
|
1028
|
+
if (proposal.type !== "correction")
|
|
1029
|
+
return [];
|
|
1030
|
+
const incomingSupersessions = vault
|
|
1031
|
+
.listIncomingEdges(proposal.id)
|
|
1032
|
+
.filter((edge) => edge.kind === "supersedes");
|
|
1033
|
+
if (incomingSupersessions.length === 0) {
|
|
1034
|
+
throw new GovernanceError("invalid-resolution", "correction proposal requires a supersession origin");
|
|
1035
|
+
}
|
|
1036
|
+
return incomingSupersessions.map((edge) => {
|
|
1037
|
+
const original = vault.getMemory(edge.fromMemoryId);
|
|
1038
|
+
if (original === undefined) {
|
|
1039
|
+
throw new GovernanceError("invalid-resolution", "correction origin memory is missing");
|
|
1040
|
+
}
|
|
1041
|
+
return { edge, original };
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
function acceptedCorrectionType(origins) {
|
|
1045
|
+
if (origins.length === 0)
|
|
1046
|
+
return undefined;
|
|
1047
|
+
const first = origins[0]?.original.type;
|
|
1048
|
+
if (first === undefined)
|
|
1049
|
+
return undefined;
|
|
1050
|
+
for (const origin of origins) {
|
|
1051
|
+
if (origin.original.type !== first) {
|
|
1052
|
+
throw new GovernanceError("invalid-resolution", "correction origins must have the same memory type");
|
|
1053
|
+
}
|
|
1054
|
+
assertSupersedable(origin.original);
|
|
1055
|
+
}
|
|
1056
|
+
return first;
|
|
1057
|
+
}
|
|
1058
|
+
function buildAcceptProposalPatch(origins) {
|
|
1059
|
+
const correctionType = acceptedCorrectionType(origins);
|
|
1060
|
+
return correctionType === undefined
|
|
1061
|
+
? { status: "accepted" }
|
|
1062
|
+
: { status: "accepted", type: correctionType };
|
|
1063
|
+
}
|
|
1064
|
+
function buildCorrectionAcceptanceUpdates(proposalId, acceptPatch, origins, nowMs) {
|
|
1065
|
+
return [
|
|
1066
|
+
{ id: proposalId, patch: acceptPatch, nowMs },
|
|
1067
|
+
...origins.map(({ edge, original }) => {
|
|
1068
|
+
// Bi-temporal-lite (#204, C1): close the superseded fact's belief window at acceptance time so
|
|
1069
|
+
// it drops out of default retrieval and "as of date T" stays answerable. Additive — only when
|
|
1070
|
+
// it forms a valid, non-extending interval.
|
|
1071
|
+
const validity = supersededValidity(original, nowMs);
|
|
1072
|
+
return {
|
|
1073
|
+
id: original.id,
|
|
1074
|
+
patch: {
|
|
1075
|
+
status: "superseded",
|
|
1076
|
+
staleReason: edge.provenanceSummary ?? "accepted correction",
|
|
1077
|
+
...(validity !== null ? { validity } : {}),
|
|
1078
|
+
},
|
|
1079
|
+
nowMs,
|
|
1080
|
+
};
|
|
1081
|
+
}),
|
|
1082
|
+
];
|
|
1083
|
+
}
|
|
1084
|
+
function recordCorrectionSupersessionAudits(deps, acceptedCorrection, origins, nowMs) {
|
|
1085
|
+
for (const { original } of origins) {
|
|
1086
|
+
recordSupersessionAudit(deps, original, acceptedCorrection, nowMs, "Accepted correction superseded the original memory.");
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
function ensureProposedMemory(existing) {
|
|
1090
|
+
if (existing === undefined) {
|
|
1091
|
+
return { status: 404, body: errorBody("NOT_FOUND", "Memory proposal not found.") };
|
|
1092
|
+
}
|
|
1093
|
+
if (existing.status !== "proposed") {
|
|
1094
|
+
return {
|
|
1095
|
+
status: 409,
|
|
1096
|
+
body: errorBody("CONFLICT", "Memory is not in proposed status."),
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
return existing;
|
|
1100
|
+
}
|
|
1101
|
+
function acceptMemoryProposal(vault, deps, id) {
|
|
1102
|
+
const existing = ensureProposedMemory(vault.getMemory(id));
|
|
1103
|
+
if (isRouteResult(existing))
|
|
1104
|
+
return existing;
|
|
1105
|
+
const nowMs = Date.now();
|
|
1106
|
+
const origins = loadCorrectionSupersessionOrigins(vault, existing);
|
|
1107
|
+
const acceptPatch = buildAcceptProposalPatch(origins);
|
|
1108
|
+
const updates = buildCorrectionAcceptanceUpdates(id, acceptPatch, origins, nowMs);
|
|
1109
|
+
const [updated] = vault.updateMemories(updates);
|
|
1110
|
+
if (updated === undefined) {
|
|
1111
|
+
throw new GovernanceError("invalid-resolution", "acceptance update produced no records");
|
|
1112
|
+
}
|
|
1113
|
+
// Outcome-driven forgetting (#204, O-V1): acceptance is a positive retention outcome for the
|
|
1114
|
+
// proposal; any origin it supersedes proved wrong (utility 0). Both feed the maintenance utility
|
|
1115
|
+
// factor so the kept memory resists disuse decay and the corrected-away origin fades sooner.
|
|
1116
|
+
vault.recordOutcome([id], 1, nowMs);
|
|
1117
|
+
const supersededOriginIds = origins.map((origin) => origin.original.id);
|
|
1118
|
+
if (supersededOriginIds.length > 0) {
|
|
1119
|
+
vault.recordOutcome(supersededOriginIds, 0, nowMs);
|
|
1120
|
+
}
|
|
1121
|
+
recordCorrectionSupersessionAudits(deps, updated, origins, nowMs);
|
|
1122
|
+
return { status: 200, body: { memory: redactMemory(deps, updated) } };
|
|
1123
|
+
}
|
|
1124
|
+
export function handleAcceptMemoryProposal(ctx, deps) {
|
|
1125
|
+
const vault = resolveVault(deps);
|
|
1126
|
+
if (isRouteResult(vault))
|
|
1127
|
+
return vault;
|
|
1128
|
+
const { id } = ctx.params;
|
|
1129
|
+
if (id === undefined || id.length === 0) {
|
|
1130
|
+
return { status: 400, body: errorBody("BAD_REQUEST", "Memory id is required.") };
|
|
1131
|
+
}
|
|
1132
|
+
try {
|
|
1133
|
+
return acceptMemoryProposal(vault, deps, id);
|
|
1134
|
+
}
|
|
1135
|
+
catch (err) {
|
|
1136
|
+
return memoryMutationErrorBody(err, "Failed to accept proposal.");
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
// ─── Handler: POST /api/memory/proposals/:id/reject ───────────────────────────
|
|
1140
|
+
function parseRejectInput(raw) {
|
|
1141
|
+
const reason = typeof raw.reason === "string" && raw.reason.trim().length > 0
|
|
1142
|
+
? raw.reason.trim()
|
|
1143
|
+
: "rejected by user";
|
|
1144
|
+
return { reason };
|
|
1145
|
+
}
|
|
1146
|
+
function ensureRejectableMemory(existing) {
|
|
1147
|
+
if (existing === undefined) {
|
|
1148
|
+
return { status: 404, body: errorBody("NOT_FOUND", "Memory proposal not found.") };
|
|
1149
|
+
}
|
|
1150
|
+
if (existing.status !== "proposed" && existing.status !== "conflicted") {
|
|
1151
|
+
return {
|
|
1152
|
+
status: 409,
|
|
1153
|
+
body: errorBody("CONFLICT", "Memory is not in proposed or conflicted status."),
|
|
1154
|
+
};
|
|
1155
|
+
}
|
|
1156
|
+
return existing;
|
|
1157
|
+
}
|
|
1158
|
+
export async function handleRejectMemoryProposal(ctx, deps) {
|
|
1159
|
+
const vault = resolveVault(deps);
|
|
1160
|
+
if (isRouteResult(vault))
|
|
1161
|
+
return vault;
|
|
1162
|
+
const { id } = ctx.params;
|
|
1163
|
+
if (id === undefined || id.length === 0) {
|
|
1164
|
+
return { status: 400, body: errorBody("BAD_REQUEST", "Memory id is required.") };
|
|
1165
|
+
}
|
|
1166
|
+
const body = await readJsonBody(ctx.req);
|
|
1167
|
+
if (isRouteResult(body))
|
|
1168
|
+
return body;
|
|
1169
|
+
const { reason } = parseRejectInput(body);
|
|
1170
|
+
try {
|
|
1171
|
+
const existing = ensureRejectableMemory(vault.getMemory(id));
|
|
1172
|
+
if (isRouteResult(existing))
|
|
1173
|
+
return existing;
|
|
1174
|
+
const nowMs = Date.now();
|
|
1175
|
+
const updated = vault.updateMemory(id, { status: "rejected", staleReason: reason }, nowMs);
|
|
1176
|
+
// Outcome-driven forgetting (#204, O-V1): a user rejection is a negative retention outcome
|
|
1177
|
+
// (utility 0), so the rejected memory fades faster under maintenance instead of lingering.
|
|
1178
|
+
vault.recordOutcome([id], 0, nowMs);
|
|
1179
|
+
return { status: 200, body: { memory: redactMemory(deps, updated) } };
|
|
1180
|
+
}
|
|
1181
|
+
catch (err) {
|
|
1182
|
+
if (err instanceof MemoryStorageError) {
|
|
1183
|
+
return {
|
|
1184
|
+
status: err.code === "not-found" ? 404 : 500,
|
|
1185
|
+
body: errorBody("MEMORY_ERROR", "Failed to reject proposal."),
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
throw err;
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
// ─── Vault factory helper (called by buildUiHandlerDeps) ──────────────────────
|
|
1192
|
+
// Exported so deps.ts can wire it without importing the vault directly. The `redactString`
|
|
1193
|
+
// is the same closure used by the audit redactor; re-using it here keeps redaction in one
|
|
1194
|
+
// place (D9).
|
|
1195
|
+
export function createBffMemoryVault(redactString, onMemoryEvent, env) {
|
|
1196
|
+
// Optional onMemoryEvent (#214) wires every successful vault mutation into the audit
|
|
1197
|
+
// ledger. When undefined, the vault still fires its internal NOOP sink, so the absence
|
|
1198
|
+
// of an audit hook is fully backward-compatible with the pre-#214 BFF wiring.
|
|
1199
|
+
return createMemoryVault({
|
|
1200
|
+
redactString,
|
|
1201
|
+
...(onMemoryEvent === undefined ? {} : { onMemoryEvent }),
|
|
1202
|
+
...(env === undefined ? {} : { env }),
|
|
1203
|
+
});
|
|
1204
|
+
}
|