@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,1157 @@
|
|
|
1
|
+
// Quality Intelligence drift re-check + targeted regeneration routes (Epic #735, Issue #743).
|
|
2
|
+
//
|
|
3
|
+
// NOTE on "current sources": The original run's START request sources are NOT persisted in the
|
|
4
|
+
// manifest. Therefore re-check and regenerate-stale both require the sources to be re-supplied
|
|
5
|
+
// by the caller. Both routes are POST to carry the sources in the body; the GET verb is avoided
|
|
6
|
+
// because sources can be arbitrarily large.
|
|
7
|
+
//
|
|
8
|
+
// POST /api/quality-intelligence/runs/:id/re-check
|
|
9
|
+
// Body: { sources: QualityIntelligenceInlineSource[] }
|
|
10
|
+
// Returns: QualityIntelligenceUiStalenessReport
|
|
11
|
+
//
|
|
12
|
+
// POST /api/quality-intelligence/runs/:id/regenerate-stale
|
|
13
|
+
// Body: { sources: QualityIntelligenceInlineSource[] }
|
|
14
|
+
// Returns: { runId: string; regeneratedCount: number; preservedCount: number }
|
|
15
|
+
//
|
|
16
|
+
// Both routes go through the central CSRF guard in server.ts (all POSTs do).
|
|
17
|
+
import { randomUUID } from "node:crypto";
|
|
18
|
+
import { isAbsolute } from "node:path";
|
|
19
|
+
import { QualityIntelligence } from "@oscharko-dev/keiko-contracts";
|
|
20
|
+
import { ALL_POLICY_PROFILES, buildAtomCoverageStatuses, buildCoverageMap, compareStaleness, deduplicateCandidates, regressionDefault, validateCandidates, } from "@oscharko-dev/keiko-quality-intelligence";
|
|
21
|
+
import { assertValidRunId, sha256Hex } from "@oscharko-dev/keiko-security";
|
|
22
|
+
import { createInMemoryQualityIntelligenceLocalStore, loadQualityIntelligenceCandidates, loadQualityIntelligenceRun, recordQualityIntelligenceCandidates, recordQualityIntelligenceRun, } from "@oscharko-dev/keiko-evidence";
|
|
23
|
+
import { QUALITY_INTELLIGENCE_DEFAULT_WORKFLOW_LIMITS, excerptsByAtomId, runQualityIntelligenceModelRoutedTestDesign, } from "@oscharko-dev/keiko-workflows";
|
|
24
|
+
import { currentRedactionSecrets } from "../deps.js";
|
|
25
|
+
import { makeCapsuleResolver } from "./capsuleAdapter.js";
|
|
26
|
+
import { makeFigmaSnapshotLoader, makeFigmaVisionHintProvider } from "./figmaSnapshotAdapter.js";
|
|
27
|
+
import { createQiGenerationPort, QiGenerationError } from "./generationPort.js";
|
|
28
|
+
import { createQiJudgePort } from "./judgePort.js";
|
|
29
|
+
import { resolveQiTestDesignSelection } from "./modelSelection.js";
|
|
30
|
+
import { ingestInlineSourcesAsync, QiIngestionError } from "./runIngestion.js";
|
|
31
|
+
import { parseFigmaSnapshotScreenIds } from "./figmaSnapshotScreenIds.js";
|
|
32
|
+
const MAX_BODY_BYTES = 2 * 1024 * 1024;
|
|
33
|
+
const REQUIREMENTS_ENVELOPE_PREFIX = "qi-src-req-";
|
|
34
|
+
const errorResult = (status, code, message) => ({
|
|
35
|
+
status,
|
|
36
|
+
body: { error: { code, message } },
|
|
37
|
+
});
|
|
38
|
+
// Validate the (already-non-empty) :id path param and map an invalid id to a 400 (mirrors the
|
|
39
|
+
// evidence read handlers) instead of letting it reach the store and surface as a generic 500 in the
|
|
40
|
+
// outer catch. assertValidRunId rejects separators / `..` / NUL, so a traversal-shaped id never
|
|
41
|
+
// reaches a filesystem path. Returns the error result to short-circuit, or null when the id is valid.
|
|
42
|
+
function invalidRunIdFormat(id) {
|
|
43
|
+
try {
|
|
44
|
+
assertValidRunId(id);
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return errorResult(400, "QI_BAD_REQUEST", "Run id is invalid.");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
52
|
+
const readBody = (req) => new Promise((resolve, reject) => {
|
|
53
|
+
const chunks = [];
|
|
54
|
+
let total = 0;
|
|
55
|
+
let capped = false;
|
|
56
|
+
req.on("data", (chunk) => {
|
|
57
|
+
total += chunk.length;
|
|
58
|
+
if (total > MAX_BODY_BYTES) {
|
|
59
|
+
if (!capped) {
|
|
60
|
+
capped = true;
|
|
61
|
+
chunks.length = 0;
|
|
62
|
+
reject(new Error("body too large"));
|
|
63
|
+
req.resume();
|
|
64
|
+
}
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
chunks.push(chunk);
|
|
68
|
+
});
|
|
69
|
+
req.on("end", () => {
|
|
70
|
+
if (!capped)
|
|
71
|
+
resolve(Buffer.concat(chunks).toString("utf8"));
|
|
72
|
+
});
|
|
73
|
+
req.on("error", reject);
|
|
74
|
+
});
|
|
75
|
+
function cancellationResult(signal) {
|
|
76
|
+
return signal.aborted
|
|
77
|
+
? errorResult(499, "QI_REQUEST_CANCELLED", "Quality Intelligence request was cancelled.")
|
|
78
|
+
: null;
|
|
79
|
+
}
|
|
80
|
+
function requestAbortSignal(ctx) {
|
|
81
|
+
const controller = new AbortController();
|
|
82
|
+
const abort = () => {
|
|
83
|
+
if (!controller.signal.aborted) {
|
|
84
|
+
controller.abort("quality intelligence request cancelled");
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
const abortOnIncompleteRequestClose = () => {
|
|
88
|
+
if (!ctx.req.complete)
|
|
89
|
+
abort();
|
|
90
|
+
};
|
|
91
|
+
const abortOnResponseClose = () => {
|
|
92
|
+
if (!ctx.res.writableEnded)
|
|
93
|
+
abort();
|
|
94
|
+
};
|
|
95
|
+
if (ctx.req.destroyed) {
|
|
96
|
+
abort();
|
|
97
|
+
return { signal: controller.signal, dispose: () => undefined };
|
|
98
|
+
}
|
|
99
|
+
ctx.req.once("aborted", abort);
|
|
100
|
+
ctx.req.once("close", abortOnIncompleteRequestClose);
|
|
101
|
+
if (typeof ctx.res.once === "function") {
|
|
102
|
+
ctx.res.once("close", abortOnResponseClose);
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
signal: controller.signal,
|
|
106
|
+
dispose: () => {
|
|
107
|
+
ctx.req.off("aborted", abort);
|
|
108
|
+
ctx.req.off("close", abortOnIncompleteRequestClose);
|
|
109
|
+
if (typeof ctx.res.off === "function") {
|
|
110
|
+
ctx.res.off("close", abortOnResponseClose);
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function validateCapsuleSource(label, raw) {
|
|
116
|
+
if (typeof raw.capsuleId !== "string" || raw.capsuleId.trim().length === 0)
|
|
117
|
+
return undefined;
|
|
118
|
+
return { kind: "capsule", label, capsuleId: raw.capsuleId };
|
|
119
|
+
}
|
|
120
|
+
function validateCapsuleSetSource(label, raw) {
|
|
121
|
+
if (typeof raw.capsuleSetId !== "string" || raw.capsuleSetId.trim().length === 0) {
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
return { kind: "capsule-set", label, capsuleSetId: raw.capsuleSetId };
|
|
125
|
+
}
|
|
126
|
+
function validateFigmaSnapshotSource(label, raw) {
|
|
127
|
+
if (typeof raw.snapshotRunId !== "string" || raw.snapshotRunId.trim().length === 0) {
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
// Shared with the start-run route so re-check accepts/rejects EXACTLY the same screenIds inputs:
|
|
131
|
+
// absent → whole snapshot; present → non-empty, bounded, canonicalised scope; empty array rejected.
|
|
132
|
+
const parsed = parseFigmaSnapshotScreenIds(raw.screenIds);
|
|
133
|
+
if (!parsed.ok) {
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
kind: "figma-snapshot",
|
|
138
|
+
label,
|
|
139
|
+
snapshotRunId: raw.snapshotRunId,
|
|
140
|
+
...(parsed.screenIds !== undefined ? { screenIds: parsed.screenIds } : {}),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function validateImageSource(label, raw) {
|
|
144
|
+
if (raw.sourceKind !== "figma-snapshot-screen")
|
|
145
|
+
return undefined;
|
|
146
|
+
if (typeof raw.snapshotRunId !== "string" || raw.snapshotRunId.trim().length === 0) {
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
if (typeof raw.screenId !== "string" || raw.screenId.trim().length === 0)
|
|
150
|
+
return undefined;
|
|
151
|
+
return {
|
|
152
|
+
kind: "image",
|
|
153
|
+
label,
|
|
154
|
+
sourceKind: "figma-snapshot-screen",
|
|
155
|
+
snapshotRunId: raw.snapshotRunId,
|
|
156
|
+
screenId: raw.screenId,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function validateConnectorSource(label, raw) {
|
|
160
|
+
if (raw.kind === "capsule") {
|
|
161
|
+
return validateCapsuleSource(label, raw);
|
|
162
|
+
}
|
|
163
|
+
if (raw.kind === "capsule-set") {
|
|
164
|
+
return validateCapsuleSetSource(label, raw);
|
|
165
|
+
}
|
|
166
|
+
if (raw.kind === "figma-snapshot") {
|
|
167
|
+
return validateFigmaSnapshotSource(label, raw);
|
|
168
|
+
}
|
|
169
|
+
if (raw.kind === "image") {
|
|
170
|
+
return validateImageSource(label, raw);
|
|
171
|
+
}
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
174
|
+
function validateSource(raw) {
|
|
175
|
+
if (!isObject(raw) || typeof raw.label !== "string")
|
|
176
|
+
return undefined;
|
|
177
|
+
const label = raw.label;
|
|
178
|
+
if (raw.kind === "requirements" && typeof raw.text === "string") {
|
|
179
|
+
return { kind: "requirements", label, text: raw.text };
|
|
180
|
+
}
|
|
181
|
+
if (raw.kind === "workspace" && typeof raw.path === "string") {
|
|
182
|
+
return { kind: "workspace", label, path: raw.path };
|
|
183
|
+
}
|
|
184
|
+
if (raw.kind === "file" && typeof raw.path === "string") {
|
|
185
|
+
return { kind: "file", label, path: raw.path };
|
|
186
|
+
}
|
|
187
|
+
return validateConnectorSource(label, raw);
|
|
188
|
+
}
|
|
189
|
+
async function parseSources(req) {
|
|
190
|
+
let raw;
|
|
191
|
+
try {
|
|
192
|
+
raw = await readBody(req);
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
return {
|
|
196
|
+
ok: false,
|
|
197
|
+
result: errorResult(413, "QI_BODY_TOO_LARGE", "Request body is too large."),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
let parsed;
|
|
201
|
+
try {
|
|
202
|
+
parsed = JSON.parse(raw);
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
return {
|
|
206
|
+
ok: false,
|
|
207
|
+
result: errorResult(400, "QI_BAD_REQUEST", "Request body is not valid JSON."),
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
if (!isObject(parsed) || !Array.isArray(parsed.sources) || parsed.sources.length === 0) {
|
|
211
|
+
return {
|
|
212
|
+
ok: false,
|
|
213
|
+
result: errorResult(400, "QI_BAD_REQUEST", "At least one source is required."),
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
const sources = [];
|
|
217
|
+
for (const raw_ of parsed.sources) {
|
|
218
|
+
const source = validateSource(raw_);
|
|
219
|
+
if (source === undefined) {
|
|
220
|
+
return {
|
|
221
|
+
ok: false,
|
|
222
|
+
result: errorResult(400, "QI_BAD_SOURCE", "A source entry is malformed."),
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
if (source.kind === "file" && !isAbsolute(source.path)) {
|
|
226
|
+
return {
|
|
227
|
+
ok: false,
|
|
228
|
+
result: errorResult(400, "QI_BAD_SOURCE", "File source paths must be absolute local paths."),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
sources.push(source);
|
|
232
|
+
}
|
|
233
|
+
return { ok: true, sources };
|
|
234
|
+
}
|
|
235
|
+
function buildJudgePortIfAvailable(deps, modelId) {
|
|
236
|
+
try {
|
|
237
|
+
return createQiJudgePort(deps, modelId);
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
return undefined;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
function resolveProfile(profileId) {
|
|
244
|
+
if (profileId === undefined || profileId.trim().length === 0)
|
|
245
|
+
return regressionDefault;
|
|
246
|
+
return ALL_POLICY_PROFILES.find((profile) => profile.id === profileId) ?? regressionDefault;
|
|
247
|
+
}
|
|
248
|
+
function mapCurrentAtomFingerprints(ingestedAtoms) {
|
|
249
|
+
return ingestedAtoms.map((entry) => ({
|
|
250
|
+
atomId: String(entry.atom.id),
|
|
251
|
+
envelopeId: String(entry.atom.sourceEnvelopeId),
|
|
252
|
+
canonicalHashSha256Hex: entry.atom.canonicalHashSha256Hex,
|
|
253
|
+
...(entry.replacementGroupId !== undefined
|
|
254
|
+
? { replacementGroupId: entry.replacementGroupId }
|
|
255
|
+
: {}),
|
|
256
|
+
...(entry.replacementOrdinal !== undefined
|
|
257
|
+
? { replacementOrdinal: entry.replacementOrdinal }
|
|
258
|
+
: {}),
|
|
259
|
+
}));
|
|
260
|
+
}
|
|
261
|
+
function mapCurrentSourceFingerprints(ingestion) {
|
|
262
|
+
return ingestion.envelopes.map((envelope) => ({
|
|
263
|
+
envelopeId: String(envelope.id),
|
|
264
|
+
integrityHashSha256Hex: envelope.provenance.integrityHashSha256Hex,
|
|
265
|
+
}));
|
|
266
|
+
}
|
|
267
|
+
function toEvidenceRefs(ingestedAtoms) {
|
|
268
|
+
return Object.freeze(ingestedAtoms.map((entry) => Object.freeze({
|
|
269
|
+
envelopeId: String(entry.atom.sourceEnvelopeId),
|
|
270
|
+
atomId: String(entry.atom.id),
|
|
271
|
+
lifecycleStatus: entry.atom.lifecycleStatus,
|
|
272
|
+
})));
|
|
273
|
+
}
|
|
274
|
+
function rowToCandidate(row, runId) {
|
|
275
|
+
return {
|
|
276
|
+
id: QualityIntelligence.asQualityIntelligenceTestCaseId(row.id),
|
|
277
|
+
runId: QualityIntelligence.asQualityIntelligenceRunId(runId),
|
|
278
|
+
derivedFromAtomIds: row.derivedFromAtomIds.map((atomId) => QualityIntelligence.asQualityIntelligenceEvidenceAtomId(atomId)),
|
|
279
|
+
title: row.title,
|
|
280
|
+
preconditions: row.preconditions,
|
|
281
|
+
steps: row.steps,
|
|
282
|
+
expectedResults: row.expectedResults,
|
|
283
|
+
priority: row.priority,
|
|
284
|
+
riskClass: row.riskClass,
|
|
285
|
+
tags: row.tags,
|
|
286
|
+
status: row.status,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
function toCandidateFindingRow(finding) {
|
|
290
|
+
return {
|
|
291
|
+
id: String(finding.id),
|
|
292
|
+
kind: finding.kind,
|
|
293
|
+
severity: finding.severity,
|
|
294
|
+
summaryRedacted: finding.summary,
|
|
295
|
+
...(finding.candidateId !== undefined ? { candidateId: String(finding.candidateId) } : {}),
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
function buildCoverageGapFindingRow(runId, atomStatus, ordinal, excerpt) {
|
|
299
|
+
const payload = ["v1-cov-gap", String(runId), String(atomStatus.atomId), String(ordinal)].join("");
|
|
300
|
+
// Mirror the initial-run severity model (modelRoutedTestDesign): a zero-coverage requirement is
|
|
301
|
+
// the headline audit gap (high); a weakly-covered one is a softer "strengthen this" signal (low).
|
|
302
|
+
const severity = atomStatus.status === "uncovered" ? "high" : "low";
|
|
303
|
+
// Mirror the initial-run summary shape (#790): name the requirement via its redacted excerpt,
|
|
304
|
+
// not just the opaque atom id, so the regenerated run's gap findings stay auditor-readable.
|
|
305
|
+
const atomLabel = excerpt === undefined
|
|
306
|
+
? `Atom ${String(atomStatus.atomId)}`
|
|
307
|
+
: `Atom ${String(atomStatus.atomId)} ("${excerpt}")`;
|
|
308
|
+
const summaryRedacted = atomStatus.status === "uncovered"
|
|
309
|
+
? `${atomLabel} hat keinen zugeordneten Test (uncovered).`
|
|
310
|
+
: `${atomLabel} ist nur schwach abgedeckt (kein dedizierter Test referenziert dieses Atom).`;
|
|
311
|
+
return Object.freeze({
|
|
312
|
+
id: `qi-finding-${sha256Hex(payload).slice(0, 32)}`,
|
|
313
|
+
kind: "coverage-gap",
|
|
314
|
+
severity,
|
|
315
|
+
summaryRedacted,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
function toCoverageMatrix(statuses, excerptByAtomId) {
|
|
319
|
+
return Object.freeze(statuses.map((status) => {
|
|
320
|
+
const excerpt = excerptByAtomId.get(String(status.atomId));
|
|
321
|
+
return Object.freeze({
|
|
322
|
+
atomId: String(status.atomId),
|
|
323
|
+
status: status.status,
|
|
324
|
+
confidence: status.confidence,
|
|
325
|
+
coveringCandidateIds: Object.freeze(status.coveringCandidateIds.map(String)),
|
|
326
|
+
...(excerpt !== undefined ? { requirementExcerptRedacted: excerpt } : {}),
|
|
327
|
+
});
|
|
328
|
+
}));
|
|
329
|
+
}
|
|
330
|
+
function filteredJudgeFindings(findings, candidateIds) {
|
|
331
|
+
return findings.filter((finding) => finding.kind === "test-quality" &&
|
|
332
|
+
finding.candidateId !== undefined &&
|
|
333
|
+
candidateIds.has(finding.candidateId));
|
|
334
|
+
}
|
|
335
|
+
function selectedQualityScore(args) {
|
|
336
|
+
const { oldManifest, regeneratedManifest, preservedCount, regeneratedCount } = args;
|
|
337
|
+
if (preservedCount > 0 && regeneratedCount > 0)
|
|
338
|
+
return null;
|
|
339
|
+
if (regeneratedCount > 0)
|
|
340
|
+
return regeneratedManifest?.qualityScore ?? null;
|
|
341
|
+
if (preservedCount > 0)
|
|
342
|
+
return oldManifest.qualityScore ?? null;
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
function loadManifestForDrift(id, evidenceDir) {
|
|
346
|
+
const manifest = loadQualityIntelligenceRun(id, { evidenceDir });
|
|
347
|
+
return manifest === undefined
|
|
348
|
+
? {
|
|
349
|
+
ok: false,
|
|
350
|
+
result: errorResult(404, "QI_NOT_FOUND", "Quality Intelligence run not found."),
|
|
351
|
+
}
|
|
352
|
+
: { ok: true, manifest };
|
|
353
|
+
}
|
|
354
|
+
async function ingestSourcesForDrift(sources, ingestRunId, deps) {
|
|
355
|
+
try {
|
|
356
|
+
return {
|
|
357
|
+
ok: true,
|
|
358
|
+
ingestion: await ingestInlineSourcesAsync({
|
|
359
|
+
request: { sources },
|
|
360
|
+
runId: ingestRunId,
|
|
361
|
+
registeredAt: new Date().toISOString(),
|
|
362
|
+
allowEmpty: true,
|
|
363
|
+
capsuleResolver: makeCapsuleResolver(deps),
|
|
364
|
+
// Drift must see the board's LATEST snapshot, not the pinned immutable record — a pinned
|
|
365
|
+
// write-once runId can never drift under its own identity (#735). Generate keeps pinning.
|
|
366
|
+
figmaSnapshotLoader: makeFigmaSnapshotLoader(deps, { resolveLatestByScope: true }),
|
|
367
|
+
figmaVision: makeFigmaVisionHintProvider(deps),
|
|
368
|
+
}),
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
catch (error) {
|
|
372
|
+
const code = error instanceof QiIngestionError ? error.code : "QI_INGESTION_FAILED";
|
|
373
|
+
const message = error instanceof QiIngestionError ? error.message : "Source ingestion failed.";
|
|
374
|
+
return { ok: false, result: errorResult(400, code, message) };
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
function buildDriftStaleness(manifest, oldCandidates, ingestion) {
|
|
378
|
+
const oldAtomFingerprints = manifest.atomFingerprints;
|
|
379
|
+
const oldFingerprints = oldAtomFingerprints === undefined ? Object.freeze([]) : (manifest.sourceFingerprints ?? []);
|
|
380
|
+
return compareStaleness({
|
|
381
|
+
// Newer runs persist atom fingerprints, which are required for exact per-test drift. Older
|
|
382
|
+
// source-only manifests cannot safely distinguish an unchanged workspace path set from edited
|
|
383
|
+
// file content, so fail closed instead of reporting "fresh" from envelope fingerprints alone.
|
|
384
|
+
oldFingerprints,
|
|
385
|
+
evidenceRefs: manifest.evidenceRefs.map((ref) => ({
|
|
386
|
+
envelopeId: ref.envelopeId,
|
|
387
|
+
atomId: ref.atomId,
|
|
388
|
+
})),
|
|
389
|
+
candidates: oldCandidates.map((candidate) => ({
|
|
390
|
+
id: candidate.id,
|
|
391
|
+
derivedFromAtomIds: candidate.derivedFromAtomIds,
|
|
392
|
+
})),
|
|
393
|
+
currentFingerprints: mapCurrentSourceFingerprints(ingestion),
|
|
394
|
+
currentAtomFingerprints: mapCurrentAtomFingerprints(ingestion.ingestedAtoms),
|
|
395
|
+
...(oldAtomFingerprints !== undefined ? { oldAtomFingerprints } : {}),
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
function buildDriftContext(sources, manifest, ingestion, oldArtifact) {
|
|
399
|
+
const oldCandidates = oldArtifact?.candidates ?? [];
|
|
400
|
+
return {
|
|
401
|
+
sources,
|
|
402
|
+
manifest,
|
|
403
|
+
ingestion,
|
|
404
|
+
staleness: buildDriftStaleness(manifest, oldCandidates, ingestion),
|
|
405
|
+
oldCandidates,
|
|
406
|
+
oldEditedRevisions: oldArtifact?.editedRevisions ?? [],
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
async function computeDrift(req, evidenceDir, id, ingestRunId, deps) {
|
|
410
|
+
const parsed = await parseSources(req);
|
|
411
|
+
if (!parsed.ok)
|
|
412
|
+
return { ok: false, result: parsed.result };
|
|
413
|
+
const loaded = loadManifestForDrift(id, evidenceDir);
|
|
414
|
+
if (!loaded.ok)
|
|
415
|
+
return { ok: false, result: loaded.result };
|
|
416
|
+
const ingested = await ingestSourcesForDrift(parsed.sources, ingestRunId, deps);
|
|
417
|
+
if (!ingested.ok)
|
|
418
|
+
return { ok: false, result: ingested.result };
|
|
419
|
+
const oldArtifact = loadQualityIntelligenceCandidates(id, { evidenceDir });
|
|
420
|
+
if (oldArtifact === undefined && loaded.manifest.totals.candidates > 0) {
|
|
421
|
+
return {
|
|
422
|
+
ok: false,
|
|
423
|
+
result: errorResult(500, "QI_CANDIDATES_MISSING", "The candidate artifact for this Quality Intelligence run is missing."),
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
return {
|
|
427
|
+
ok: true,
|
|
428
|
+
value: buildDriftContext(parsed.sources, loaded.manifest, ingested.ingestion, oldArtifact),
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
const ALIGN_INSERT_DELETE_COST = 3;
|
|
432
|
+
const ALIGN_SUBSTITUTE_COST = 4;
|
|
433
|
+
const ALIGN_CROSS_OLD_ATOM_COST = 10;
|
|
434
|
+
function collectStaleIds(staleness) {
|
|
435
|
+
return new Set([
|
|
436
|
+
...staleness.changedStale.map((reason) => reason.candidateId),
|
|
437
|
+
...staleness.orphanedStale.map((reason) => reason.candidateId),
|
|
438
|
+
]);
|
|
439
|
+
}
|
|
440
|
+
function buildPreservedState(drift, staleIds) {
|
|
441
|
+
const preservedCandidates = drift.oldCandidates.filter((candidate) => !staleIds.has(candidate.id));
|
|
442
|
+
const preservedIds = new Set(preservedCandidates.map((candidate) => candidate.id));
|
|
443
|
+
return {
|
|
444
|
+
preservedCandidates,
|
|
445
|
+
preservedEditedRevisions: drift.oldEditedRevisions.filter((revision) => preservedIds.has(revision.candidateId)),
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
function looksLikeLegacyRequirementsFallback(drift, staleIds) {
|
|
449
|
+
if (staleIds.size === 0 || drift.manifest.atomFingerprints !== undefined)
|
|
450
|
+
return false;
|
|
451
|
+
if (!(drift.sources.length > 0 && drift.sources.every((source) => source.kind === "requirements"))) {
|
|
452
|
+
return false;
|
|
453
|
+
}
|
|
454
|
+
const evidenceRefMap = new Map(drift.manifest.evidenceRefs.map((ref) => [ref.atomId, ref.envelopeId]));
|
|
455
|
+
return drift.oldCandidates.some((candidate) => staleIds.has(candidate.id) &&
|
|
456
|
+
candidate.derivedFromAtomIds.some((atomId) => evidenceRefMap.get(atomId)?.startsWith("qi-src-")));
|
|
457
|
+
}
|
|
458
|
+
function buildCurrentAtomIndexes(ingestion) {
|
|
459
|
+
const byId = new Map(ingestion.ingestedAtoms.map((entry) => [String(entry.atom.id), entry]));
|
|
460
|
+
const byEnvelope = new Map();
|
|
461
|
+
const replacementEntries = [];
|
|
462
|
+
for (const entry of ingestion.ingestedAtoms) {
|
|
463
|
+
const envelopeId = String(entry.atom.sourceEnvelopeId);
|
|
464
|
+
const current = byEnvelope.get(envelopeId);
|
|
465
|
+
if (current === undefined) {
|
|
466
|
+
byEnvelope.set(envelopeId, [entry]);
|
|
467
|
+
}
|
|
468
|
+
else {
|
|
469
|
+
current.push(entry);
|
|
470
|
+
}
|
|
471
|
+
if (entry.replacementGroupId !== undefined && entry.replacementOrdinal !== undefined) {
|
|
472
|
+
replacementEntries.push({
|
|
473
|
+
atomId: String(entry.atom.id),
|
|
474
|
+
canonicalHashSha256Hex: entry.atom.canonicalHashSha256Hex,
|
|
475
|
+
replacementGroupId: entry.replacementGroupId,
|
|
476
|
+
replacementOrdinal: entry.replacementOrdinal,
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
return {
|
|
481
|
+
byId,
|
|
482
|
+
byEnvelope,
|
|
483
|
+
replacementEntries,
|
|
484
|
+
envelopeIds: new Set(ingestion.envelopes.map((envelope) => String(envelope.id))),
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
function buildOldAtomIndexes(atomFingerprints) {
|
|
488
|
+
const byId = new Map(atomFingerprints.map((fp) => [
|
|
489
|
+
fp.atomId,
|
|
490
|
+
{
|
|
491
|
+
envelopeId: fp.envelopeId,
|
|
492
|
+
canonicalHashSha256Hex: fp.canonicalHashSha256Hex,
|
|
493
|
+
...(fp.replacementGroupId !== undefined
|
|
494
|
+
? { replacementGroupId: fp.replacementGroupId }
|
|
495
|
+
: {}),
|
|
496
|
+
...(fp.replacementOrdinal !== undefined
|
|
497
|
+
? { replacementOrdinal: fp.replacementOrdinal }
|
|
498
|
+
: {}),
|
|
499
|
+
},
|
|
500
|
+
]));
|
|
501
|
+
const idsByEnvelope = new Map();
|
|
502
|
+
const idsInEnvelope = new Map();
|
|
503
|
+
const replacementEntries = [];
|
|
504
|
+
for (const fp of atomFingerprints) {
|
|
505
|
+
const ids = idsByEnvelope.get(fp.envelopeId);
|
|
506
|
+
if (ids === undefined) {
|
|
507
|
+
idsByEnvelope.set(fp.envelopeId, new Set([fp.atomId]));
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
ids.add(fp.atomId);
|
|
511
|
+
}
|
|
512
|
+
const orderedIds = idsInEnvelope.get(fp.envelopeId);
|
|
513
|
+
if (orderedIds === undefined) {
|
|
514
|
+
idsInEnvelope.set(fp.envelopeId, [fp.atomId]);
|
|
515
|
+
}
|
|
516
|
+
else {
|
|
517
|
+
orderedIds.push(fp.atomId);
|
|
518
|
+
}
|
|
519
|
+
if (fp.replacementGroupId !== undefined && fp.replacementOrdinal !== undefined) {
|
|
520
|
+
replacementEntries.push({
|
|
521
|
+
atomId: fp.atomId,
|
|
522
|
+
canonicalHashSha256Hex: fp.canonicalHashSha256Hex,
|
|
523
|
+
replacementGroupId: fp.replacementGroupId,
|
|
524
|
+
replacementOrdinal: fp.replacementOrdinal,
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
return { byId, idsByEnvelope, idsInEnvelope, replacementEntries };
|
|
529
|
+
}
|
|
530
|
+
function replacementEntriesByGroup(entries) {
|
|
531
|
+
const groups = new Map();
|
|
532
|
+
for (const entry of entries) {
|
|
533
|
+
const group = groups.get(entry.replacementGroupId);
|
|
534
|
+
if (group === undefined) {
|
|
535
|
+
groups.set(entry.replacementGroupId, [entry]);
|
|
536
|
+
}
|
|
537
|
+
else {
|
|
538
|
+
group.push(entry);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
for (const group of groups.values()) {
|
|
542
|
+
group.sort((a, b) => a.replacementOrdinal - b.replacementOrdinal);
|
|
543
|
+
}
|
|
544
|
+
return groups;
|
|
545
|
+
}
|
|
546
|
+
function alignmentPairCost(oldEntry, currentEntry, oldAtomIds) {
|
|
547
|
+
if (oldEntry.atomId === currentEntry.atomId)
|
|
548
|
+
return 0;
|
|
549
|
+
if (currentEntry.canonicalHashSha256Hex === oldEntry.canonicalHashSha256Hex)
|
|
550
|
+
return 0;
|
|
551
|
+
return oldAtomIds.has(currentEntry.atomId) ? ALIGN_CROSS_OLD_ATOM_COST : ALIGN_SUBSTITUTE_COST;
|
|
552
|
+
}
|
|
553
|
+
function replacementIndexEntryAt(entries, index) {
|
|
554
|
+
const entry = entries[index];
|
|
555
|
+
if (entry === undefined)
|
|
556
|
+
throw new Error("Replacement alignment index out of bounds.");
|
|
557
|
+
return entry;
|
|
558
|
+
}
|
|
559
|
+
function matrixValue(matrix, row, col) {
|
|
560
|
+
const value = matrix[row]?.[col];
|
|
561
|
+
if (value === undefined)
|
|
562
|
+
throw new Error("Replacement alignment matrix index out of bounds.");
|
|
563
|
+
return value;
|
|
564
|
+
}
|
|
565
|
+
function setMatrixValue(matrix, row, col, value) {
|
|
566
|
+
const rowValues = matrix[row];
|
|
567
|
+
if (rowValues === undefined) {
|
|
568
|
+
throw new Error("Replacement alignment matrix index out of bounds.");
|
|
569
|
+
}
|
|
570
|
+
rowValues[col] = value;
|
|
571
|
+
}
|
|
572
|
+
function buildAlignmentCostMatrix(oldEntries, currentEntries, oldAtomIds) {
|
|
573
|
+
const matrix = Array.from({ length: oldEntries.length + 1 }, () => Array.from({ length: currentEntries.length + 1 }, () => 0));
|
|
574
|
+
for (let oldIndex = 1; oldIndex <= oldEntries.length; oldIndex += 1) {
|
|
575
|
+
setMatrixValue(matrix, oldIndex, 0, oldIndex * ALIGN_INSERT_DELETE_COST);
|
|
576
|
+
}
|
|
577
|
+
for (let currentIndex = 1; currentIndex <= currentEntries.length; currentIndex += 1) {
|
|
578
|
+
setMatrixValue(matrix, 0, currentIndex, currentIndex * ALIGN_INSERT_DELETE_COST);
|
|
579
|
+
}
|
|
580
|
+
for (let oldIndex = 1; oldIndex <= oldEntries.length; oldIndex += 1) {
|
|
581
|
+
for (let currentIndex = 1; currentIndex <= currentEntries.length; currentIndex += 1) {
|
|
582
|
+
const pairCost = alignmentPairCost(replacementIndexEntryAt(oldEntries, oldIndex - 1), replacementIndexEntryAt(currentEntries, currentIndex - 1), oldAtomIds);
|
|
583
|
+
const pair = matrixValue(matrix, oldIndex - 1, currentIndex - 1) + pairCost;
|
|
584
|
+
const deletion = matrixValue(matrix, oldIndex - 1, currentIndex) + ALIGN_INSERT_DELETE_COST;
|
|
585
|
+
const insertion = matrixValue(matrix, oldIndex, currentIndex - 1) + ALIGN_INSERT_DELETE_COST;
|
|
586
|
+
setMatrixValue(matrix, oldIndex, currentIndex, Math.min(pair, deletion, insertion));
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
return matrix;
|
|
590
|
+
}
|
|
591
|
+
function alignReplacementEntries(oldEntries, currentEntries) {
|
|
592
|
+
const mapping = new Map();
|
|
593
|
+
const oldAtomIds = new Set(oldEntries.map((entry) => entry.atomId));
|
|
594
|
+
const costs = buildAlignmentCostMatrix(oldEntries, currentEntries, oldAtomIds);
|
|
595
|
+
let oldIndex = oldEntries.length;
|
|
596
|
+
let currentIndex = currentEntries.length;
|
|
597
|
+
while (oldIndex > 0 && currentIndex > 0) {
|
|
598
|
+
const oldEntry = replacementIndexEntryAt(oldEntries, oldIndex - 1);
|
|
599
|
+
const currentEntry = replacementIndexEntryAt(currentEntries, currentIndex - 1);
|
|
600
|
+
const pairCost = alignmentPairCost(oldEntry, currentEntry, oldAtomIds);
|
|
601
|
+
const currentCost = matrixValue(costs, oldIndex, currentIndex);
|
|
602
|
+
const pair = matrixValue(costs, oldIndex - 1, currentIndex - 1) + pairCost;
|
|
603
|
+
const deletion = matrixValue(costs, oldIndex - 1, currentIndex) + ALIGN_INSERT_DELETE_COST;
|
|
604
|
+
const insertion = matrixValue(costs, oldIndex, currentIndex - 1) + ALIGN_INSERT_DELETE_COST;
|
|
605
|
+
if (pairCost === 0 && currentCost === pair) {
|
|
606
|
+
mapping.set(oldEntry.atomId, currentEntry.atomId);
|
|
607
|
+
oldIndex -= 1;
|
|
608
|
+
currentIndex -= 1;
|
|
609
|
+
}
|
|
610
|
+
else if (currentCost === insertion) {
|
|
611
|
+
currentIndex -= 1;
|
|
612
|
+
}
|
|
613
|
+
else if (currentCost === deletion) {
|
|
614
|
+
oldIndex -= 1;
|
|
615
|
+
}
|
|
616
|
+
else {
|
|
617
|
+
mapping.set(oldEntry.atomId, currentEntry.atomId);
|
|
618
|
+
oldIndex -= 1;
|
|
619
|
+
currentIndex -= 1;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
return mapping;
|
|
623
|
+
}
|
|
624
|
+
function buildReplacementAtomMap(oldEntries, currentEntries) {
|
|
625
|
+
const oldGroups = replacementEntriesByGroup(oldEntries);
|
|
626
|
+
const currentGroups = replacementEntriesByGroup(currentEntries);
|
|
627
|
+
const mapping = new Map();
|
|
628
|
+
for (const [groupId, oldGroup] of oldGroups) {
|
|
629
|
+
const currentGroup = currentGroups.get(groupId);
|
|
630
|
+
if (currentGroup === undefined)
|
|
631
|
+
continue;
|
|
632
|
+
for (const [oldAtomId, currentAtomId] of alignReplacementEntries(oldGroup, currentGroup)) {
|
|
633
|
+
mapping.set(oldAtomId, currentAtomId);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
return mapping;
|
|
637
|
+
}
|
|
638
|
+
function addPositionalReplacementRequirementAtom(atomId, envelopeId, current, old, atomIdsToRegenerate) {
|
|
639
|
+
const oldIds = old.idsByEnvelope.get(envelopeId) ?? new Set();
|
|
640
|
+
const oldIdsInEnvelope = old.idsInEnvelope.get(envelopeId) ?? [];
|
|
641
|
+
const currentAtomsInEnvelope = current.byEnvelope.get(envelopeId) ?? [];
|
|
642
|
+
const oldIndex = oldIdsInEnvelope.indexOf(atomId);
|
|
643
|
+
const replacement = oldIndex >= 0 ? currentAtomsInEnvelope[oldIndex] : undefined;
|
|
644
|
+
const replacementId = replacement === undefined ? undefined : String(replacement.atom.id);
|
|
645
|
+
if (replacementId !== undefined && !oldIds.has(replacementId)) {
|
|
646
|
+
atomIdsToRegenerate.add(replacementId);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
function addChangedCurrentAtom(oldAtom, currentEntry, atomIdsToRegenerate) {
|
|
650
|
+
if (currentEntry === undefined)
|
|
651
|
+
return false;
|
|
652
|
+
if (currentEntry.atom.canonicalHashSha256Hex === oldAtom.canonicalHashSha256Hex)
|
|
653
|
+
return false;
|
|
654
|
+
atomIdsToRegenerate.add(String(currentEntry.atom.id));
|
|
655
|
+
return true;
|
|
656
|
+
}
|
|
657
|
+
function addMappedReplacementAtom(atomId, current, old, replacementAtomIdsByOldAtomId, atomIdsToRegenerate) {
|
|
658
|
+
const replacementAtomId = replacementAtomIdsByOldAtomId.get(atomId);
|
|
659
|
+
if (replacementAtomId === undefined || replacementAtomId === atomId)
|
|
660
|
+
return false;
|
|
661
|
+
if (old.byId.has(replacementAtomId) || !current.byId.has(replacementAtomId))
|
|
662
|
+
return false;
|
|
663
|
+
atomIdsToRegenerate.add(replacementAtomId);
|
|
664
|
+
return true;
|
|
665
|
+
}
|
|
666
|
+
function addRegenerationAtomsForCandidate(candidate, current, old, replacementAtomIdsByOldAtomId, atomIdsToRegenerate) {
|
|
667
|
+
for (const atomId of candidate.derivedFromAtomIds) {
|
|
668
|
+
const oldAtom = old.byId.get(atomId);
|
|
669
|
+
const currentAtom = current.byId.get(atomId);
|
|
670
|
+
if (oldAtom === undefined || !current.envelopeIds.has(oldAtom.envelopeId))
|
|
671
|
+
continue;
|
|
672
|
+
if (addChangedCurrentAtom(oldAtom, currentAtom, atomIdsToRegenerate))
|
|
673
|
+
continue;
|
|
674
|
+
if (addMappedReplacementAtom(atomId, current, old, replacementAtomIdsByOldAtomId, atomIdsToRegenerate)) {
|
|
675
|
+
continue;
|
|
676
|
+
}
|
|
677
|
+
if (!oldAtom.envelopeId.startsWith(REQUIREMENTS_ENVELOPE_PREFIX))
|
|
678
|
+
continue;
|
|
679
|
+
addPositionalReplacementRequirementAtom(atomId, oldAtom.envelopeId, current, old, atomIdsToRegenerate);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
function collectAtomsToRegenerate(drift, staleIds) {
|
|
683
|
+
const current = buildCurrentAtomIndexes(drift.ingestion);
|
|
684
|
+
const old = buildOldAtomIndexes(drift.manifest.atomFingerprints ?? []);
|
|
685
|
+
const replacementAtomIdsByOldAtomId = buildReplacementAtomMap(old.replacementEntries, current.replacementEntries);
|
|
686
|
+
const atomIdsToRegenerate = new Set();
|
|
687
|
+
for (const candidate of drift.oldCandidates) {
|
|
688
|
+
if (!staleIds.has(candidate.id))
|
|
689
|
+
continue;
|
|
690
|
+
addRegenerationAtomsForCandidate(candidate, current, old, replacementAtomIdsByOldAtomId, atomIdsToRegenerate);
|
|
691
|
+
}
|
|
692
|
+
return drift.ingestion.ingestedAtoms.filter((entry) => atomIdsToRegenerate.has(String(entry.atom.id)));
|
|
693
|
+
}
|
|
694
|
+
function narrowRegeneration(drift) {
|
|
695
|
+
const staleIds = collectStaleIds(drift.staleness);
|
|
696
|
+
const preserved = buildPreservedState(drift, staleIds);
|
|
697
|
+
const legacyRequirementsFallback = looksLikeLegacyRequirementsFallback(drift, staleIds);
|
|
698
|
+
if (legacyRequirementsFallback || staleIds.size === 0) {
|
|
699
|
+
return {
|
|
700
|
+
staleIds,
|
|
701
|
+
atomsToRegenerate: Object.freeze([]),
|
|
702
|
+
preservedCandidates: preserved.preservedCandidates,
|
|
703
|
+
preservedEditedRevisions: preserved.preservedEditedRevisions,
|
|
704
|
+
legacyRequirementsFallback,
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
return {
|
|
708
|
+
staleIds,
|
|
709
|
+
atomsToRegenerate: collectAtomsToRegenerate(drift, staleIds),
|
|
710
|
+
preservedCandidates: preserved.preservedCandidates,
|
|
711
|
+
preservedEditedRevisions: preserved.preservedEditedRevisions,
|
|
712
|
+
legacyRequirementsFallback: false,
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
function regenWorkflowDeps(deps, target, evidenceStore, capture, signal) {
|
|
716
|
+
return {
|
|
717
|
+
sink: { emit: () => undefined },
|
|
718
|
+
signal,
|
|
719
|
+
evidenceStore,
|
|
720
|
+
candidatesSink: {
|
|
721
|
+
record: (cands, generatedAt) => {
|
|
722
|
+
capture(cands, generatedAt);
|
|
723
|
+
},
|
|
724
|
+
},
|
|
725
|
+
generate: createQiGenerationPort(deps, target),
|
|
726
|
+
// The regenerate-stale judge deliberately shares the auto-selected generation model id rather than
|
|
727
|
+
// resolving an independent qi:judge-logic model the way the initial run does (runExecution.ts).
|
|
728
|
+
// This is safe because the regen target comes from resolveQiTestDesignSelection(deps) with NO
|
|
729
|
+
// explicit model request: auto-selection prefers structured-output models, so whenever a judge is
|
|
730
|
+
// possible (a structured-output model is configured) the generation model already satisfies
|
|
731
|
+
// qi:judge-logic, and when only chat-only models exist both generation and the judge degrade
|
|
732
|
+
// (judge skipped via buildJudgePortIfAvailable's typed-unavailable catch). The initial-run
|
|
733
|
+
// asymmetry — an explicitly requested chat-only generation model paired with a separate
|
|
734
|
+
// structured-output judge — cannot arise here because the regen path never carries an explicit
|
|
735
|
+
// generation-model request.
|
|
736
|
+
...(target.kind === "model" ? { judge: buildJudgePortIfAvailable(deps, target.modelId) } : {}),
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
function buildScopedRegenPlan(newRunId, requestedAt) {
|
|
740
|
+
return {
|
|
741
|
+
id: QualityIntelligence.asQualityIntelligenceRunId(newRunId),
|
|
742
|
+
requestedAt,
|
|
743
|
+
plannerKind: "model-routed",
|
|
744
|
+
stages: [],
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
async function executeScopedWorkflow(args) {
|
|
748
|
+
const { deps, target, evidenceStore, capture, plan, ingestion, atomsToRegenerate, profile, signal, } = args;
|
|
749
|
+
try {
|
|
750
|
+
const summary = await runQualityIntelligenceModelRoutedTestDesign({
|
|
751
|
+
plan,
|
|
752
|
+
envelopes: ingestion.envelopes,
|
|
753
|
+
ingestedAtoms: atomsToRegenerate,
|
|
754
|
+
provenanceRefs: ingestion.provenanceRefs,
|
|
755
|
+
profile,
|
|
756
|
+
}, regenWorkflowDeps(deps, target, evidenceStore, capture, signal));
|
|
757
|
+
return summary.status === "succeeded"
|
|
758
|
+
? null
|
|
759
|
+
: errorResult(500, "QI_REGEN_FAILED", "Scoped regeneration did not succeed.");
|
|
760
|
+
}
|
|
761
|
+
catch (error) {
|
|
762
|
+
const code = error instanceof QiGenerationError ? error.code : "QI_REGEN_FAILED";
|
|
763
|
+
const message = error instanceof QiGenerationError ? error.message : "Scoped regeneration failed.";
|
|
764
|
+
return errorResult(500, code, message);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
function finalizeScopedWorkflow(evidenceStore, newRunId, generatedCandidates, generatedAt) {
|
|
768
|
+
const manifest = evidenceStore.load(newRunId);
|
|
769
|
+
if (manifest === undefined || generatedAt === undefined) {
|
|
770
|
+
return {
|
|
771
|
+
ok: false,
|
|
772
|
+
result: errorResult(500, "QI_REGEN_FAILED", "Scoped regeneration did not persist in memory."),
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
return {
|
|
776
|
+
ok: true,
|
|
777
|
+
value: { manifest, candidates: generatedCandidates, generatedAt },
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
async function runScopedEphemeral(args) {
|
|
781
|
+
const { deps, target, newRunId, requestedAt, ingestion, atomsToRegenerate, profile, signal } = args;
|
|
782
|
+
const evidenceStore = createInMemoryQualityIntelligenceLocalStore();
|
|
783
|
+
let generatedCandidates = [];
|
|
784
|
+
let generatedAt;
|
|
785
|
+
const failure = await executeScopedWorkflow({
|
|
786
|
+
deps,
|
|
787
|
+
target,
|
|
788
|
+
evidenceStore,
|
|
789
|
+
capture: (cands, ts) => {
|
|
790
|
+
generatedCandidates = [...cands];
|
|
791
|
+
generatedAt = ts;
|
|
792
|
+
},
|
|
793
|
+
plan: buildScopedRegenPlan(newRunId, requestedAt),
|
|
794
|
+
ingestion,
|
|
795
|
+
atomsToRegenerate,
|
|
796
|
+
profile,
|
|
797
|
+
signal,
|
|
798
|
+
});
|
|
799
|
+
if (failure !== null)
|
|
800
|
+
return { ok: false, result: failure };
|
|
801
|
+
return finalizeScopedWorkflow(evidenceStore, newRunId, generatedCandidates, generatedAt);
|
|
802
|
+
}
|
|
803
|
+
function buildMergedCandidates(newRunId, preservedCandidates, regeneratedCandidates) {
|
|
804
|
+
return deduplicateCandidates([
|
|
805
|
+
...preservedCandidates.map((candidate) => rowToCandidate(candidate, newRunId)),
|
|
806
|
+
...regeneratedCandidates,
|
|
807
|
+
]);
|
|
808
|
+
}
|
|
809
|
+
function assertMergedCandidateBudget(preservedCandidates, regeneratedCandidates) {
|
|
810
|
+
const limit = QUALITY_INTELLIGENCE_DEFAULT_WORKFLOW_LIMITS.maxCandidatesPerRun;
|
|
811
|
+
if (preservedCandidates.length + regeneratedCandidates.length <= limit)
|
|
812
|
+
return null;
|
|
813
|
+
return errorResult(409, "QI_REGEN_CANDIDATE_CAP_EXCEEDED", "Regenerating the stale tests would exceed the per-run candidate limit. Reduce the stale scope or start a fresh QI run against the current source.");
|
|
814
|
+
}
|
|
815
|
+
function buildCoverageArtifacts(runId, ingestion, mergedCandidates) {
|
|
816
|
+
const atoms = ingestion.ingestedAtoms.map((entry) => entry.atom);
|
|
817
|
+
const coverageMap = buildCoverageMap({ runId, atoms, candidates: mergedCandidates });
|
|
818
|
+
const atomStatuses = buildAtomCoverageStatuses(atoms, coverageMap);
|
|
819
|
+
const excerptByAtomId = excerptsByAtomId(ingestion.ingestedAtoms);
|
|
820
|
+
return {
|
|
821
|
+
coverageMatrix: toCoverageMatrix(atomStatuses, excerptByAtomId),
|
|
822
|
+
coverageGapRows: atomStatuses
|
|
823
|
+
.map((status, index) => status.status === "covered"
|
|
824
|
+
? null
|
|
825
|
+
: buildCoverageGapFindingRow(runId, status, index, excerptByAtomId.get(String(status.atomId))))
|
|
826
|
+
.filter((row) => row !== null),
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
// Order by severity (critical -> low) BEFORE truncating, then cap to the per-run limit — mirroring
|
|
830
|
+
// the initial run path (modelRoutedTestDesign). Sorting first guarantees that if the merge hits the
|
|
831
|
+
// cap, the most severe findings (including high-severity uncovered-requirement coverage gaps) survive
|
|
832
|
+
// rather than being dropped by array position. Array.prototype.sort is stable, so same-severity
|
|
833
|
+
// insertion order — coverage-gap rows first — is preserved, matching the initial path exactly.
|
|
834
|
+
function sortAndCapMergedFindings(rows, cap) {
|
|
835
|
+
return rows
|
|
836
|
+
.slice()
|
|
837
|
+
.sort((a, b) => QualityIntelligence.QUALITY_INTELLIGENCE_SEVERITY_RANK[a.severity] -
|
|
838
|
+
QualityIntelligence.QUALITY_INTELLIGENCE_SEVERITY_RANK[b.severity])
|
|
839
|
+
.slice(0, cap);
|
|
840
|
+
}
|
|
841
|
+
function buildMergedFindings(args) {
|
|
842
|
+
const preservedIds = new Set(args.preservedCandidates.map((candidate) => candidate.id));
|
|
843
|
+
const regeneratedIds = new Set(args.regeneratedCandidates.map((candidate) => String(candidate.id)));
|
|
844
|
+
const preservedJudgeRows = filteredJudgeFindings(args.oldManifest.findings, preservedIds);
|
|
845
|
+
const regeneratedJudgeRows = args.regeneratedManifest === undefined
|
|
846
|
+
? []
|
|
847
|
+
: filteredJudgeFindings(args.regeneratedManifest.findings, regeneratedIds);
|
|
848
|
+
// Source the cap from the default workflow limits: the re-check regeneration sub-run
|
|
849
|
+
// (regenWorkflowDeps) passes no custom `limits`, so it runs under these defaults — keeping the
|
|
850
|
+
// merge cap consistent with the sub-run's own maxFindingsPerRun.
|
|
851
|
+
const merged = sortAndCapMergedFindings([
|
|
852
|
+
...args.coverageGapRows,
|
|
853
|
+
...validateCandidates(args.runId, args.mergedCandidates).map(toCandidateFindingRow),
|
|
854
|
+
...preservedJudgeRows,
|
|
855
|
+
...regeneratedJudgeRows,
|
|
856
|
+
], QUALITY_INTELLIGENCE_DEFAULT_WORKFLOW_LIMITS.maxFindingsPerRun);
|
|
857
|
+
return Object.freeze(merged);
|
|
858
|
+
}
|
|
859
|
+
function buildMergedRunRecord(args) {
|
|
860
|
+
const { newRunId, requestedAt, profile, oldManifest, ingestion, preservedCandidates } = args;
|
|
861
|
+
return {
|
|
862
|
+
runId: newRunId,
|
|
863
|
+
planAt: requestedAt,
|
|
864
|
+
completedAt: args.completedAt,
|
|
865
|
+
status: "succeeded",
|
|
866
|
+
policyProfileIds: [profile.id],
|
|
867
|
+
retentionPolicyId: oldManifest.retentionPolicyId,
|
|
868
|
+
modelGatewayCallCount: args.regeneratedManifest?.modelGatewayCallCount ?? 0,
|
|
869
|
+
totals: {
|
|
870
|
+
candidates: args.preservedCandidates.length + args.regeneratedCandidates.length,
|
|
871
|
+
findings: args.findings.length,
|
|
872
|
+
exports: 0,
|
|
873
|
+
},
|
|
874
|
+
findings: args.findings,
|
|
875
|
+
exports: Object.freeze([]),
|
|
876
|
+
evidenceRefs: toEvidenceRefs(ingestion.ingestedAtoms),
|
|
877
|
+
provenanceRefs: ingestion.provenanceRefs,
|
|
878
|
+
coverageMatrix: args.coverageMatrix,
|
|
879
|
+
qualityScore: selectedQualityScore({
|
|
880
|
+
oldManifest,
|
|
881
|
+
regeneratedManifest: args.regeneratedManifest,
|
|
882
|
+
preservedCount: preservedCandidates.length,
|
|
883
|
+
regeneratedCount: args.regeneratedCandidates.length,
|
|
884
|
+
}),
|
|
885
|
+
sourceFingerprints: mapCurrentSourceFingerprints(ingestion),
|
|
886
|
+
atomFingerprints: mapCurrentAtomFingerprints(ingestion.ingestedAtoms),
|
|
887
|
+
...optionalModelFields(args.regeneratedManifest),
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
// Carry forward the regenerated manifest's optional model provenance (modelId / modelParameters /
|
|
891
|
+
// seedUsed) only when present, so the merged record omits — rather than nulls — an absent field.
|
|
892
|
+
function optionalModelFields(regeneratedManifest) {
|
|
893
|
+
if (regeneratedManifest === undefined)
|
|
894
|
+
return {};
|
|
895
|
+
return {
|
|
896
|
+
...(regeneratedManifest.modelId !== undefined ? { modelId: regeneratedManifest.modelId } : {}),
|
|
897
|
+
...(regeneratedManifest.modelParameters !== undefined
|
|
898
|
+
? { modelParameters: regeneratedManifest.modelParameters }
|
|
899
|
+
: {}),
|
|
900
|
+
...(regeneratedManifest.seedUsed !== undefined
|
|
901
|
+
? { seedUsed: regeneratedManifest.seedUsed }
|
|
902
|
+
: {}),
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
function recordMergedManifest(evidenceDir, args, additionalSecrets) {
|
|
906
|
+
// Persist-time secret scrubbing at parity with the initial-run path (runExecution.ts
|
|
907
|
+
// buildWorkflowDeps): the merged manifest carries judge rationales forwarded from the regenerated
|
|
908
|
+
// run, so the live additionalSecrets list must reach the manifest writer here too — otherwise a
|
|
909
|
+
// configured provider secret echoed in a rationale that the security-package builtin patterns do
|
|
910
|
+
// not match would survive into the on-disk merged manifest (Issue #747 defence-in-depth).
|
|
911
|
+
recordQualityIntelligenceRun(buildMergedRunRecord(args), {
|
|
912
|
+
evidenceDir,
|
|
913
|
+
redaction: { additionalSecrets },
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
function recordMergedCandidatesArtifact(args) {
|
|
917
|
+
recordQualityIntelligenceCandidates({
|
|
918
|
+
runId: args.newRunId,
|
|
919
|
+
generatedAt: args.completedAt,
|
|
920
|
+
candidates: args.mergedCandidates,
|
|
921
|
+
editedRevisions: args.preservedEditedRevisions,
|
|
922
|
+
evidenceDir: args.evidenceDir,
|
|
923
|
+
redact: args.deps.redactor,
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
function persistMergedRun(args) {
|
|
927
|
+
const mergedCandidates = buildMergedCandidates(args.newRunId, args.preservedCandidates, args.regeneratedCandidates);
|
|
928
|
+
const runId = QualityIntelligence.asQualityIntelligenceRunId(args.newRunId);
|
|
929
|
+
const coverage = buildCoverageArtifacts(runId, args.ingestion, mergedCandidates);
|
|
930
|
+
const findings = buildMergedFindings({
|
|
931
|
+
runId,
|
|
932
|
+
mergedCandidates,
|
|
933
|
+
coverageGapRows: coverage.coverageGapRows,
|
|
934
|
+
oldManifest: args.oldManifest,
|
|
935
|
+
preservedCandidates: args.preservedCandidates,
|
|
936
|
+
regeneratedCandidates: args.regeneratedCandidates,
|
|
937
|
+
regeneratedManifest: args.regeneratedManifest,
|
|
938
|
+
});
|
|
939
|
+
recordMergedCandidatesArtifact({
|
|
940
|
+
deps: args.deps,
|
|
941
|
+
evidenceDir: args.evidenceDir,
|
|
942
|
+
newRunId: args.newRunId,
|
|
943
|
+
completedAt: args.completedAt,
|
|
944
|
+
mergedCandidates,
|
|
945
|
+
preservedEditedRevisions: args.preservedEditedRevisions,
|
|
946
|
+
});
|
|
947
|
+
recordMergedManifest(args.evidenceDir, {
|
|
948
|
+
newRunId: args.newRunId,
|
|
949
|
+
requestedAt: args.requestedAt,
|
|
950
|
+
profile: args.profile,
|
|
951
|
+
oldManifest: args.oldManifest,
|
|
952
|
+
ingestion: args.ingestion,
|
|
953
|
+
preservedCandidates: args.preservedCandidates,
|
|
954
|
+
regeneratedCandidates: args.regeneratedCandidates,
|
|
955
|
+
regeneratedManifest: args.regeneratedManifest,
|
|
956
|
+
completedAt: args.completedAt,
|
|
957
|
+
findings,
|
|
958
|
+
coverageMatrix: coverage.coverageMatrix,
|
|
959
|
+
}, currentRedactionSecrets(args.deps));
|
|
960
|
+
}
|
|
961
|
+
function immediateRegenerationResult(narrowed) {
|
|
962
|
+
if (narrowed.legacyRequirementsFallback) {
|
|
963
|
+
return errorResult(409, "QI_REGEN_LEGACY_REQUIREMENTS_UNSUPPORTED", "This run predates atom-level requirements drift metadata. Start a new QI run against the current requirements sources instead.");
|
|
964
|
+
}
|
|
965
|
+
// Data-loss guard: targeted regeneration must never turn a non-empty run into an empty one. If
|
|
966
|
+
// every candidate is stale yet nothing maps to a regeneratable atom (preserved == 0 AND nothing to
|
|
967
|
+
// regenerate), persisting the merge would silently drop the entire run. This is the catastrophic
|
|
968
|
+
// shape an atom-id scheme drift would take; fail closed with an actionable error instead (Epic
|
|
969
|
+
// #735 drift correctness). The legitimate "some tests orphaned, some preserved" case keeps
|
|
970
|
+
// preserved > 0 and is unaffected.
|
|
971
|
+
if (narrowed.preservedCandidates.length === 0 && narrowed.atomsToRegenerate.length === 0) {
|
|
972
|
+
return errorResult(409, "QI_REGEN_WOULD_EMPTY", "Regenerating the stale tests would remove every test in this run because the current source no longer maps to any of them. Start a fresh QI run against the current source instead.");
|
|
973
|
+
}
|
|
974
|
+
return null;
|
|
975
|
+
}
|
|
976
|
+
function resolveScopedRegenerationTarget(deps) {
|
|
977
|
+
const selection = resolveQiTestDesignSelection(deps);
|
|
978
|
+
return selection.kind === "model"
|
|
979
|
+
? { kind: "model", modelId: selection.modelId }
|
|
980
|
+
: { kind: "baseline" };
|
|
981
|
+
}
|
|
982
|
+
async function regenerateCandidateSlice(args) {
|
|
983
|
+
if (args.narrowed.atomsToRegenerate.length === 0) {
|
|
984
|
+
return {
|
|
985
|
+
ok: true,
|
|
986
|
+
value: { manifest: undefined, candidates: [], completedAt: new Date().toISOString() },
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
const outcome = await runScopedEphemeral({
|
|
990
|
+
deps: args.deps,
|
|
991
|
+
target: resolveScopedRegenerationTarget(args.deps),
|
|
992
|
+
newRunId: args.newRunId,
|
|
993
|
+
requestedAt: args.requestedAt,
|
|
994
|
+
ingestion: args.drift.ingestion,
|
|
995
|
+
atomsToRegenerate: args.narrowed.atomsToRegenerate,
|
|
996
|
+
profile: args.profile,
|
|
997
|
+
signal: args.signal,
|
|
998
|
+
});
|
|
999
|
+
return outcome.ok
|
|
1000
|
+
? {
|
|
1001
|
+
ok: true,
|
|
1002
|
+
value: {
|
|
1003
|
+
manifest: outcome.value.manifest,
|
|
1004
|
+
candidates: outcome.value.candidates,
|
|
1005
|
+
completedAt: outcome.value.generatedAt,
|
|
1006
|
+
},
|
|
1007
|
+
}
|
|
1008
|
+
: outcome;
|
|
1009
|
+
}
|
|
1010
|
+
function persistRegenerationResult(args) {
|
|
1011
|
+
persistMergedRun({
|
|
1012
|
+
deps: args.deps,
|
|
1013
|
+
evidenceDir: args.evidenceDir,
|
|
1014
|
+
newRunId: args.newRunId,
|
|
1015
|
+
requestedAt: args.requestedAt,
|
|
1016
|
+
profile: args.profile,
|
|
1017
|
+
oldManifest: args.drift.manifest,
|
|
1018
|
+
ingestion: args.drift.ingestion,
|
|
1019
|
+
preservedCandidates: args.narrowed.preservedCandidates,
|
|
1020
|
+
preservedEditedRevisions: args.narrowed.preservedEditedRevisions,
|
|
1021
|
+
regeneratedCandidates: args.regenerated.candidates,
|
|
1022
|
+
regeneratedManifest: args.regenerated.manifest,
|
|
1023
|
+
completedAt: args.regenerated.completedAt,
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
function regenerationSuccessResult(args) {
|
|
1027
|
+
return {
|
|
1028
|
+
status: 200,
|
|
1029
|
+
body: {
|
|
1030
|
+
runId: args.newRunId,
|
|
1031
|
+
regeneratedCount: args.regeneratedCount,
|
|
1032
|
+
preservedCount: args.preservedCount,
|
|
1033
|
+
},
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
async function regenerateFromDrift(args) {
|
|
1037
|
+
const { deps, evidenceDir, newRunId, requestedAt, drift, signal } = args;
|
|
1038
|
+
const narrowed = narrowRegeneration(drift);
|
|
1039
|
+
const immediate = immediateRegenerationResult(narrowed);
|
|
1040
|
+
if (immediate !== null)
|
|
1041
|
+
return immediate;
|
|
1042
|
+
const cancelledBeforeRegeneration = cancellationResult(signal);
|
|
1043
|
+
if (cancelledBeforeRegeneration !== null)
|
|
1044
|
+
return cancelledBeforeRegeneration;
|
|
1045
|
+
const profile = resolveProfile(drift.manifest.policyProfileIds[0]);
|
|
1046
|
+
const regenerated = await regenerateCandidateSlice({
|
|
1047
|
+
deps,
|
|
1048
|
+
newRunId,
|
|
1049
|
+
requestedAt,
|
|
1050
|
+
drift,
|
|
1051
|
+
narrowed,
|
|
1052
|
+
profile,
|
|
1053
|
+
signal,
|
|
1054
|
+
});
|
|
1055
|
+
if (!regenerated.ok)
|
|
1056
|
+
return regenerated.result;
|
|
1057
|
+
const cancelledBeforePersist = cancellationResult(signal);
|
|
1058
|
+
if (cancelledBeforePersist !== null)
|
|
1059
|
+
return cancelledBeforePersist;
|
|
1060
|
+
const budgetError = assertMergedCandidateBudget(narrowed.preservedCandidates, regenerated.value.candidates);
|
|
1061
|
+
if (budgetError !== null)
|
|
1062
|
+
return budgetError;
|
|
1063
|
+
persistRegenerationResult({
|
|
1064
|
+
deps,
|
|
1065
|
+
evidenceDir,
|
|
1066
|
+
newRunId,
|
|
1067
|
+
requestedAt,
|
|
1068
|
+
drift,
|
|
1069
|
+
narrowed,
|
|
1070
|
+
profile,
|
|
1071
|
+
regenerated: regenerated.value,
|
|
1072
|
+
});
|
|
1073
|
+
return regenerationSuccessResult({
|
|
1074
|
+
newRunId,
|
|
1075
|
+
regeneratedCount: regenerated.value.candidates.length,
|
|
1076
|
+
preservedCount: narrowed.preservedCandidates.length,
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
export async function handleQiReCheck(ctx, deps) {
|
|
1080
|
+
const { id } = ctx.params;
|
|
1081
|
+
if (id === undefined || id.trim().length === 0) {
|
|
1082
|
+
return errorResult(400, "QI_BAD_REQUEST", "Run id is required.");
|
|
1083
|
+
}
|
|
1084
|
+
const invalidId = invalidRunIdFormat(id);
|
|
1085
|
+
if (invalidId !== null)
|
|
1086
|
+
return invalidId;
|
|
1087
|
+
const evidenceDir = deps.evidenceDir;
|
|
1088
|
+
if (evidenceDir === undefined) {
|
|
1089
|
+
return errorResult(500, "QI_NO_EVIDENCE_DIR", "The evidence directory is not configured.");
|
|
1090
|
+
}
|
|
1091
|
+
try {
|
|
1092
|
+
const drift = await computeDrift(ctx.req, evidenceDir, id, `qi-recheck-${id}`, deps);
|
|
1093
|
+
if (!drift.ok)
|
|
1094
|
+
return drift.result;
|
|
1095
|
+
const { staleness } = drift.value;
|
|
1096
|
+
return {
|
|
1097
|
+
status: 200,
|
|
1098
|
+
body: {
|
|
1099
|
+
runId: id,
|
|
1100
|
+
staleCount: staleness.changedStale.length + staleness.orphanedStale.length,
|
|
1101
|
+
fresh: staleness.fresh,
|
|
1102
|
+
changedStale: staleness.changedStale,
|
|
1103
|
+
orphanedStale: staleness.orphanedStale,
|
|
1104
|
+
},
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
1107
|
+
catch {
|
|
1108
|
+
return errorResult(500, "QI_RECHECK_FAILED", "Failed to inspect the current sources for drift.");
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
export async function handleQiRegenerateStale(ctx, deps) {
|
|
1112
|
+
const { id } = ctx.params;
|
|
1113
|
+
if (id === undefined || id.trim().length === 0) {
|
|
1114
|
+
return errorResult(400, "QI_BAD_REQUEST", "Run id is required.");
|
|
1115
|
+
}
|
|
1116
|
+
const invalidId = invalidRunIdFormat(id);
|
|
1117
|
+
if (invalidId !== null)
|
|
1118
|
+
return invalidId;
|
|
1119
|
+
const evidenceDir = deps.evidenceDir;
|
|
1120
|
+
if (evidenceDir === undefined) {
|
|
1121
|
+
return errorResult(500, "QI_NO_EVIDENCE_DIR", "The evidence directory is not configured.");
|
|
1122
|
+
}
|
|
1123
|
+
const newRunId = `qi-run-${randomUUID()}`;
|
|
1124
|
+
const requestedAt = new Date().toISOString();
|
|
1125
|
+
const abortScope = requestAbortSignal(ctx);
|
|
1126
|
+
try {
|
|
1127
|
+
const drift = await computeDrift(ctx.req, evidenceDir, id, newRunId, deps);
|
|
1128
|
+
if (!drift.ok)
|
|
1129
|
+
return drift.result;
|
|
1130
|
+
return await regenerateFromDrift({
|
|
1131
|
+
deps,
|
|
1132
|
+
evidenceDir,
|
|
1133
|
+
newRunId,
|
|
1134
|
+
requestedAt,
|
|
1135
|
+
drift: drift.value,
|
|
1136
|
+
signal: abortScope.signal,
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1139
|
+
catch {
|
|
1140
|
+
return errorResult(500, "QI_REGEN_FAILED", "Failed to regenerate stale candidates.");
|
|
1141
|
+
}
|
|
1142
|
+
finally {
|
|
1143
|
+
abortScope.dispose();
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
export const QI_RECHECK_ROUTE_GROUP = [
|
|
1147
|
+
{
|
|
1148
|
+
method: "POST",
|
|
1149
|
+
pattern: "/api/quality-intelligence/runs/:id/re-check",
|
|
1150
|
+
handler: handleQiReCheck,
|
|
1151
|
+
},
|
|
1152
|
+
{
|
|
1153
|
+
method: "POST",
|
|
1154
|
+
pattern: "/api/quality-intelligence/runs/:id/regenerate-stale",
|
|
1155
|
+
handler: handleQiRegenerateStale,
|
|
1156
|
+
},
|
|
1157
|
+
];
|