@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,293 @@
|
|
|
1
|
+
// Quality Intelligence inline-edit BFF route (Epic #712, Issue #726).
|
|
2
|
+
//
|
|
3
|
+
// * POST /api/quality-intelligence/runs/:id/edit — edit a generated candidate's fields inline
|
|
4
|
+
//
|
|
5
|
+
// Updates the candidate row in place inside the MUTABLE candidates artifact (redacted, with
|
|
6
|
+
// provenance appended to `editedRevisions[]`) and records an `edit` audit entry. The IMMUTABLE run
|
|
7
|
+
// manifest (`<runId>.qi.json`) is never touched. CSRF is enforced by the dispatch layer for POST
|
|
8
|
+
// (mirrors reviewRoutes); the body is capped at 16KB and every field is validated before persist.
|
|
9
|
+
import { applyQualityIntelligenceCandidateEdit, loadQualityIntelligenceRun, } from "@oscharko-dev/keiko-evidence";
|
|
10
|
+
import { QualityIntelligence, } from "@oscharko-dev/keiko-contracts";
|
|
11
|
+
import { normaliseCandidateText } from "@oscharko-dev/keiko-quality-intelligence";
|
|
12
|
+
import { appendEditAudit, candidateReviewStateOf, loadRunReviewState } from "./reviewStore.js";
|
|
13
|
+
const MAX_BODY_BYTES = 16 * 1024;
|
|
14
|
+
const MAX_TITLE_LEN = 256;
|
|
15
|
+
const MAX_LIST_ITEMS = 100;
|
|
16
|
+
const MAX_LABEL_LEN = 80;
|
|
17
|
+
const PRIORITIES = new Set(QualityIntelligence.QUALITY_INTELLIGENCE_PRIORITIES);
|
|
18
|
+
const RISK_CLASSES = new Set(QualityIntelligence.QUALITY_INTELLIGENCE_RISK_CLASSES);
|
|
19
|
+
const readBody = (req) => new Promise((resolve, reject) => {
|
|
20
|
+
const chunks = [];
|
|
21
|
+
let total = 0;
|
|
22
|
+
let capped = false;
|
|
23
|
+
req.on("data", (chunk) => {
|
|
24
|
+
total += chunk.length;
|
|
25
|
+
if (total > MAX_BODY_BYTES) {
|
|
26
|
+
if (!capped) {
|
|
27
|
+
capped = true;
|
|
28
|
+
chunks.length = 0;
|
|
29
|
+
reject(new Error("body too large"));
|
|
30
|
+
req.resume();
|
|
31
|
+
}
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
chunks.push(chunk);
|
|
35
|
+
});
|
|
36
|
+
req.on("end", () => {
|
|
37
|
+
if (!capped)
|
|
38
|
+
resolve(Buffer.concat(chunks).toString("utf8"));
|
|
39
|
+
});
|
|
40
|
+
req.on("error", reject);
|
|
41
|
+
});
|
|
42
|
+
const errorResult = (status, code, message) => ({
|
|
43
|
+
status,
|
|
44
|
+
body: { error: { code, message } },
|
|
45
|
+
});
|
|
46
|
+
const isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
47
|
+
// A list field is an array (≤ MAX_LIST_ITEMS) of non-blank strings. `minItems` separates the two
|
|
48
|
+
// domain shapes: steps and expected results are the load-bearing body of a test case and must carry
|
|
49
|
+
// at least one entry (clearing them produces an empty, meaningless test case → reject); preconditions
|
|
50
|
+
// and tags are legitimately optional and may be cleared. Blank ("") items are rejected for every list
|
|
51
|
+
// field (a blank step / tag is never meaningful), keeping the persisted body clean.
|
|
52
|
+
const isListField = (value, minItems) => Array.isArray(value) &&
|
|
53
|
+
value.length >= minItems &&
|
|
54
|
+
value.length <= MAX_LIST_ITEMS &&
|
|
55
|
+
value.every((item) => typeof item === "string" && item.length > 0);
|
|
56
|
+
const isValidTitle = (value) => typeof value === "string" && value.length >= 1 && value.length <= MAX_TITLE_LEN;
|
|
57
|
+
// Per-field validators keyed by field name. Keeping the branching in a data table (not a switch)
|
|
58
|
+
// holds the validator helper's cyclomatic complexity low while still rejecting any malformed value.
|
|
59
|
+
const FIELD_VALIDATORS = {
|
|
60
|
+
title: isValidTitle,
|
|
61
|
+
preconditions: (value) => isListField(value, 0),
|
|
62
|
+
steps: (value) => isListField(value, 1),
|
|
63
|
+
expectedResults: (value) => isListField(value, 1),
|
|
64
|
+
priority: (value) => typeof value === "string" && PRIORITIES.has(value),
|
|
65
|
+
riskClass: (value) => typeof value === "string" && RISK_CLASSES.has(value),
|
|
66
|
+
tags: (value) => isListField(value, 0),
|
|
67
|
+
};
|
|
68
|
+
// Validate one editable field. Returns `false` for a malformed value so the caller rejects the whole
|
|
69
|
+
// edit (no partial persist). Unknown keys are ignored by the caller (only EDITABLE_KEYS are read).
|
|
70
|
+
function isValidField(key, value) {
|
|
71
|
+
return FIELD_VALIDATORS[key]?.(value) ?? false;
|
|
72
|
+
}
|
|
73
|
+
const EDITABLE_KEYS = [
|
|
74
|
+
"title",
|
|
75
|
+
"preconditions",
|
|
76
|
+
"steps",
|
|
77
|
+
"expectedResults",
|
|
78
|
+
"priority",
|
|
79
|
+
"riskClass",
|
|
80
|
+
"tags",
|
|
81
|
+
];
|
|
82
|
+
function parseEditorLabel(value) {
|
|
83
|
+
if (typeof value !== "string")
|
|
84
|
+
return undefined;
|
|
85
|
+
// Strip bidi/zero-width/control spoofing code points (+ NFKC + trim) before length-capping so the
|
|
86
|
+
// persisted provenance `editorLabel` and the `.review.json` `reviewerLabel` (both derived from this
|
|
87
|
+
// one value) get the same content-safety treatment as generated candidate text. A label of only
|
|
88
|
+
// format characters normalises to "" and is rejected as a missing reviewer label.
|
|
89
|
+
const cleaned = normaliseCandidateText(value);
|
|
90
|
+
return cleaned.length > 0 ? cleaned.slice(0, MAX_LABEL_LEN) : undefined;
|
|
91
|
+
}
|
|
92
|
+
// The editable fields whose value is an array of candidate body-text lines. `title` (single string)
|
|
93
|
+
// and the closed enums (priority / riskClass) are handled separately in normaliseEditedValue.
|
|
94
|
+
const TEXT_LIST_KEYS = new Set([
|
|
95
|
+
"preconditions",
|
|
96
|
+
"steps",
|
|
97
|
+
"expectedResults",
|
|
98
|
+
"tags",
|
|
99
|
+
]);
|
|
100
|
+
// Strip bidi / zero-width / control spoofing code points (+ NFKC + trim) from every human-supplied
|
|
101
|
+
// text field BEFORE validation, so an inline edit persists through the SAME candidate-text
|
|
102
|
+
// normalisation as the generation path (parseGeneratedCandidates → normaliseCandidateText — the
|
|
103
|
+
// documented single chokepoint for persisted candidate text). Closed enums (priority / riskClass) and
|
|
104
|
+
// any non-string shape pass through untouched; their validators reject anything off-list. A title or
|
|
105
|
+
// list item whose only content was format characters normalises to "" and is then rejected by the
|
|
106
|
+
// non-blank validators (an actionable 400) rather than persisted as junk. Normalising first also means
|
|
107
|
+
// MAX_TITLE_LEN is measured on the cleaned text, so padding a title with zero-width marks cannot
|
|
108
|
+
// smuggle it past the length cap.
|
|
109
|
+
function normaliseEditedValue(key, value) {
|
|
110
|
+
if (key === "title") {
|
|
111
|
+
return typeof value === "string" ? normaliseCandidateText(value) : value;
|
|
112
|
+
}
|
|
113
|
+
if (TEXT_LIST_KEYS.has(key) && Array.isArray(value)) {
|
|
114
|
+
// `Array.isArray` narrows `unknown` to `any[]`; widen back to `unknown[]` so each item is typed
|
|
115
|
+
// (no unsafe `any`). Non-string items pass through for the list validator to reject.
|
|
116
|
+
return value.map((item) => typeof item === "string" ? normaliseCandidateText(item) : item);
|
|
117
|
+
}
|
|
118
|
+
return value;
|
|
119
|
+
}
|
|
120
|
+
function collectEditedFields(edited) {
|
|
121
|
+
const collected = {};
|
|
122
|
+
let fieldCount = 0;
|
|
123
|
+
for (const key of EDITABLE_KEYS) {
|
|
124
|
+
const raw = edited[key];
|
|
125
|
+
if (raw === undefined)
|
|
126
|
+
continue;
|
|
127
|
+
const value = normaliseEditedValue(key, raw);
|
|
128
|
+
if (!isValidField(key, value))
|
|
129
|
+
return { ok: false, invalidField: key };
|
|
130
|
+
collected[key] = value;
|
|
131
|
+
fieldCount += 1;
|
|
132
|
+
}
|
|
133
|
+
return fieldCount > 0
|
|
134
|
+
? { ok: true, fields: collected }
|
|
135
|
+
: { ok: false, invalidField: undefined };
|
|
136
|
+
}
|
|
137
|
+
// Returns the parsed edit, or a field-specific rejection message so the reviewer learns WHICH field
|
|
138
|
+
// is invalid (a generic "edit is invalid" is not actionable). Any failing field rejects the whole
|
|
139
|
+
// edit (no partial persist).
|
|
140
|
+
function parseEdit(body) {
|
|
141
|
+
const candidateId = body.candidateId;
|
|
142
|
+
if (typeof candidateId !== "string" || candidateId.trim().length === 0) {
|
|
143
|
+
return { ok: false, message: "A candidate id is required." };
|
|
144
|
+
}
|
|
145
|
+
const editorLabel = parseEditorLabel(body.editorLabel);
|
|
146
|
+
if (editorLabel === undefined) {
|
|
147
|
+
return { ok: false, message: "A non-empty reviewer label is required." };
|
|
148
|
+
}
|
|
149
|
+
const edited = body.edited;
|
|
150
|
+
if (!isObject(edited)) {
|
|
151
|
+
return { ok: false, message: "An `edited` object with the changed fields is required." };
|
|
152
|
+
}
|
|
153
|
+
const collected = collectEditedFields(edited);
|
|
154
|
+
if (!collected.ok) {
|
|
155
|
+
return {
|
|
156
|
+
ok: false,
|
|
157
|
+
message: collected.invalidField === undefined
|
|
158
|
+
? "At least one editable field must be supplied."
|
|
159
|
+
: `The "${collected.invalidField}" field is empty or invalid.`,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
return { ok: true, edit: { candidateId, edited: collected.fields, editorLabel } };
|
|
163
|
+
}
|
|
164
|
+
async function readEdit(req) {
|
|
165
|
+
let raw;
|
|
166
|
+
try {
|
|
167
|
+
raw = await readBody(req);
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
return { ok: false, result: errorResult(413, "QI_BODY_TOO_LARGE", "Edit body is too large.") };
|
|
171
|
+
}
|
|
172
|
+
let parsed;
|
|
173
|
+
try {
|
|
174
|
+
parsed = JSON.parse(raw);
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
return {
|
|
178
|
+
ok: false,
|
|
179
|
+
result: errorResult(400, "QI_BAD_REQUEST", "Edit body is not valid JSON."),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
if (!isObject(parsed)) {
|
|
183
|
+
return {
|
|
184
|
+
ok: false,
|
|
185
|
+
result: errorResult(400, "QI_BAD_REQUEST", "Edit body must be an object."),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
const edit = parseEdit(parsed);
|
|
189
|
+
if (!edit.ok) {
|
|
190
|
+
return { ok: false, result: errorResult(400, "QI_BAD_EDIT", edit.message) };
|
|
191
|
+
}
|
|
192
|
+
return { ok: true, edit: edit.edit };
|
|
193
|
+
}
|
|
194
|
+
// Project the edited row for the immediate POST response. This intentionally omits `weakTestFlag`
|
|
195
|
+
// (the adversarial-judge badge): that flag is derived from the run MANIFEST findings, not the
|
|
196
|
+
// candidate row, and recomputing it here would duplicate the run-detail GET's manifest read
|
|
197
|
+
// (uiRoutes.ts) and risk drift. AC1 scopes "reflected" to the next run-detail fetch, where the GET
|
|
198
|
+
// projection re-attaches `weakTestFlag`; clients refresh the badge from that fetch.
|
|
199
|
+
function projectCandidate(row, evidenceDir, runId) {
|
|
200
|
+
const reviewArtifact = loadRunReviewState(runId, evidenceDir);
|
|
201
|
+
return {
|
|
202
|
+
id: row.id,
|
|
203
|
+
title: row.title,
|
|
204
|
+
preconditions: row.preconditions,
|
|
205
|
+
steps: row.steps,
|
|
206
|
+
expectedResults: row.expectedResults,
|
|
207
|
+
priority: row.priority,
|
|
208
|
+
riskClass: row.riskClass,
|
|
209
|
+
tags: row.tags,
|
|
210
|
+
status: row.status,
|
|
211
|
+
reviewState: candidateReviewStateOf(reviewArtifact, row.id),
|
|
212
|
+
derivedFromAtomIds: row.derivedFromAtomIds,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
// Apply a validated edit (redacted, in-place), append the audit entry, and project the updated
|
|
216
|
+
// candidate for the wire. Split out of the handler to keep the handler within the line budget.
|
|
217
|
+
function recordEdit(runId, edit, evidenceDir, deps) {
|
|
218
|
+
const result = applyQualityIntelligenceCandidateEdit({
|
|
219
|
+
runId,
|
|
220
|
+
candidateId: edit.candidateId,
|
|
221
|
+
editedFields: edit.edited,
|
|
222
|
+
provenance: {
|
|
223
|
+
editedAt: new Date().toISOString(),
|
|
224
|
+
editedBy: "human",
|
|
225
|
+
editorLabel: edit.editorLabel,
|
|
226
|
+
},
|
|
227
|
+
evidenceDir,
|
|
228
|
+
redact: deps.redactor,
|
|
229
|
+
});
|
|
230
|
+
if (!result.ok) {
|
|
231
|
+
if (result.reason === "no-edited-fields") {
|
|
232
|
+
return errorResult(400, "QI_BAD_EDIT", "At least one editable field must be supplied.");
|
|
233
|
+
}
|
|
234
|
+
if (result.reason === "artifact-not-found") {
|
|
235
|
+
// The run manifest exists but its candidates companion is missing — a distinct, observable
|
|
236
|
+
// failure (corruption / partial state) rather than an unknown candidate id.
|
|
237
|
+
return errorResult(404, "QI_CANDIDATES_NOT_FOUND", "No candidates artifact exists for this run.");
|
|
238
|
+
}
|
|
239
|
+
return errorResult(404, "QI_NOT_FOUND", "Candidate not found for this run.");
|
|
240
|
+
}
|
|
241
|
+
if (result.changed) {
|
|
242
|
+
// Append the consolidated `.review.json` audit entry. The atomic, per-edit record is the
|
|
243
|
+
// `editedRevisions[]` provenance just written WITH the candidate change (a single candidates.json
|
|
244
|
+
// write inside applyQualityIntelligenceCandidateEdit); this is a second, separate companion write.
|
|
245
|
+
// If it throws (e.g. disk failure) the outer handler returns 500, but the edit is still recorded
|
|
246
|
+
// in editedRevisions[] provenance — an edit is never fully unaudited. The trade-off: the
|
|
247
|
+
// consolidated audit log can lag the per-artifact provenance on a partial-write failure, and a
|
|
248
|
+
// retry with identical fields is a no-op (changed === false) that won't backfill it. Cross-artifact
|
|
249
|
+
// write atomicity is a tracked follow-up; it is not introduced here to avoid touching the shared
|
|
250
|
+
// companion store used by every QI route.
|
|
251
|
+
appendEditAudit({
|
|
252
|
+
runId,
|
|
253
|
+
evidenceDir,
|
|
254
|
+
candidateId: edit.candidateId,
|
|
255
|
+
reviewerLabel: edit.editorLabel,
|
|
256
|
+
now: new Date().toISOString(),
|
|
257
|
+
redact: deps.redactor,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
return {
|
|
261
|
+
status: 200,
|
|
262
|
+
body: { candidate: projectCandidate(result.candidate, evidenceDir, runId) },
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
export async function handleQiEditCandidate(ctx, deps) {
|
|
266
|
+
const { id } = ctx.params;
|
|
267
|
+
if (id === undefined || id.trim().length === 0) {
|
|
268
|
+
return errorResult(400, "QI_BAD_REQUEST", "Run id is required.");
|
|
269
|
+
}
|
|
270
|
+
const evidenceDir = deps.evidenceDir;
|
|
271
|
+
if (evidenceDir === undefined) {
|
|
272
|
+
return errorResult(500, "QI_NO_EVIDENCE_DIR", "The evidence directory is not configured.");
|
|
273
|
+
}
|
|
274
|
+
const parsed = await readEdit(ctx.req);
|
|
275
|
+
if (!parsed.ok)
|
|
276
|
+
return parsed.result;
|
|
277
|
+
try {
|
|
278
|
+
if (loadQualityIntelligenceRun(id, { evidenceDir }) === undefined) {
|
|
279
|
+
return errorResult(404, "QI_RUN_NOT_FOUND", "Quality Intelligence run not found.");
|
|
280
|
+
}
|
|
281
|
+
return recordEdit(id, parsed.edit, evidenceDir, deps);
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
return errorResult(500, "QI_EDIT_FAILED", "Failed to record the candidate edit.");
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
export const QI_EDIT_ROUTE_GROUP = [
|
|
288
|
+
{
|
|
289
|
+
method: "POST",
|
|
290
|
+
pattern: "/api/quality-intelligence/runs/:id/edit",
|
|
291
|
+
handler: handleQiEditCandidate,
|
|
292
|
+
},
|
|
293
|
+
];
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reduce an entry name to a contained, single-segment file name. Defense-in-depth: the run id is
|
|
3
|
+
* already brand-validated (no "/", "\\", ".." — see keiko-contracts ids.ts) before it reaches this
|
|
4
|
+
* helper, but the ZIP writer must not depend on its caller for containment. Strips any directory
|
|
5
|
+
* component, collapses traversal dot-runs, and rejects an empty result.
|
|
6
|
+
*/
|
|
7
|
+
export declare function safeZipEntryName(name: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* Assembles a deterministic STORE (no-compression) ZIP from the given entries.
|
|
10
|
+
* DOS date/time fields are zeroed for byte-stable output across runs. Entry names are reduced to a
|
|
11
|
+
* contained basename so a bundle can never write outside its own archive root.
|
|
12
|
+
*/
|
|
13
|
+
export declare function assembleZipBundle(entries: readonly {
|
|
14
|
+
readonly name: string;
|
|
15
|
+
readonly bytes: Uint8Array;
|
|
16
|
+
}[]): Uint8Array;
|
|
17
|
+
/**
|
|
18
|
+
* Assembles a deterministic, multi-page, text-only PDF from the given plain-text body. The entire
|
|
19
|
+
* body is rendered across as many pages as needed (no truncation). Byte-stable across identical runs.
|
|
20
|
+
*/
|
|
21
|
+
export declare function assemblePdf(body: string, runId: string): Uint8Array;
|
|
22
|
+
//# sourceMappingURL=exportAssembly.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exportAssembly.d.ts","sourceRoot":"","sources":["../../src/qualityIntelligence/exportAssembly.ts"],"names":[],"mappings":"AA6FA;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKrD;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,SAAS;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAA;CAAE,EAAE,GACxE,UAAU,CA+BZ;AA+KD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,UAAU,CAmDnE"}
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
// Binary export assembly helpers (Epic #711).
|
|
2
|
+
//
|
|
3
|
+
// Produces deterministic binary artifacts — a minimal text-only PDF and a STORE
|
|
4
|
+
// (no-compression) ZIP — with zero new runtime dependencies. Both formats are
|
|
5
|
+
// assembled by hand to guarantee byte-stable output: no /CreationDate, no UUIDs,
|
|
6
|
+
// no timestamps, no Math.random.
|
|
7
|
+
//
|
|
8
|
+
// PDF determinism: fixed object offsets, static trailer, no metadata objects.
|
|
9
|
+
// ZIP determinism: DOS date/time zeroed, STORE method (no compression), CRC32
|
|
10
|
+
// computed from content bytes, central directory built from the same metadata.
|
|
11
|
+
// ─── Shared ───────────────────────────────────────────────────────────────────
|
|
12
|
+
const TEXT_ENC = new TextEncoder();
|
|
13
|
+
// ─── CRC32 ────────────────────────────────────────────────────────────────────
|
|
14
|
+
const CRC32_TABLE = (() => {
|
|
15
|
+
const t = new Uint32Array(256);
|
|
16
|
+
for (let i = 0; i < 256; i++) {
|
|
17
|
+
let c = i;
|
|
18
|
+
for (let k = 0; k < 8; k++) {
|
|
19
|
+
c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
|
|
20
|
+
}
|
|
21
|
+
t[i] = c >>> 0;
|
|
22
|
+
}
|
|
23
|
+
return t;
|
|
24
|
+
})();
|
|
25
|
+
function crc32(bytes) {
|
|
26
|
+
let c = 0xffffffff;
|
|
27
|
+
for (const byte of bytes) {
|
|
28
|
+
c = (CRC32_TABLE[(c ^ byte) & 0xff] ?? 0) ^ (c >>> 8);
|
|
29
|
+
}
|
|
30
|
+
return (c ^ 0xffffffff) >>> 0;
|
|
31
|
+
}
|
|
32
|
+
// ─── Little-endian integer helpers ───────────────────────────────────────────
|
|
33
|
+
function u16le(n) {
|
|
34
|
+
return [n & 0xff, (n >>> 8) & 0xff];
|
|
35
|
+
}
|
|
36
|
+
function u32le(n) {
|
|
37
|
+
return [n & 0xff, (n >>> 8) & 0xff, (n >>> 16) & 0xff, (n >>> 24) & 0xff];
|
|
38
|
+
}
|
|
39
|
+
// ─── ZIP assembly ─────────────────────────────────────────────────────────────
|
|
40
|
+
const ZIP_LOCAL_HEADER_SIG = [0x50, 0x4b, 0x03, 0x04];
|
|
41
|
+
const ZIP_CENTRAL_SIG = [0x50, 0x4b, 0x01, 0x02];
|
|
42
|
+
const ZIP_EOCD_SIG = [0x50, 0x4b, 0x05, 0x06];
|
|
43
|
+
function buildLocalHeader(name, size, crc) {
|
|
44
|
+
return [
|
|
45
|
+
...ZIP_LOCAL_HEADER_SIG,
|
|
46
|
+
...u16le(20), // version needed
|
|
47
|
+
...u16le(0), // flags
|
|
48
|
+
...u16le(0), // STORE method
|
|
49
|
+
...u16le(0), // last mod time (zeroed)
|
|
50
|
+
...u16le(0), // last mod date (zeroed)
|
|
51
|
+
...u32le(crc),
|
|
52
|
+
...u32le(size), // compressed size
|
|
53
|
+
...u32le(size), // uncompressed size
|
|
54
|
+
...u16le(name.length),
|
|
55
|
+
...u16le(0), // extra field length
|
|
56
|
+
...name,
|
|
57
|
+
];
|
|
58
|
+
}
|
|
59
|
+
function buildCentralEntry(name, size, crc, offset) {
|
|
60
|
+
return [
|
|
61
|
+
...ZIP_CENTRAL_SIG,
|
|
62
|
+
...u16le(20), // version made by
|
|
63
|
+
...u16le(20), // version needed
|
|
64
|
+
...u16le(0), // flags
|
|
65
|
+
...u16le(0), // STORE method
|
|
66
|
+
...u16le(0), // last mod time (zeroed)
|
|
67
|
+
...u16le(0), // last mod date (zeroed)
|
|
68
|
+
...u32le(crc),
|
|
69
|
+
...u32le(size), // compressed size
|
|
70
|
+
...u32le(size), // uncompressed size
|
|
71
|
+
...u16le(name.length),
|
|
72
|
+
...u16le(0), // extra field length
|
|
73
|
+
...u16le(0), // file comment length
|
|
74
|
+
...u16le(0), // disk number start
|
|
75
|
+
...u16le(0), // internal attributes
|
|
76
|
+
...u32le(0), // external attributes
|
|
77
|
+
...u32le(offset),
|
|
78
|
+
...name,
|
|
79
|
+
];
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Reduce an entry name to a contained, single-segment file name. Defense-in-depth: the run id is
|
|
83
|
+
* already brand-validated (no "/", "\\", ".." — see keiko-contracts ids.ts) before it reaches this
|
|
84
|
+
* helper, but the ZIP writer must not depend on its caller for containment. Strips any directory
|
|
85
|
+
* component, collapses traversal dot-runs, and rejects an empty result.
|
|
86
|
+
*/
|
|
87
|
+
export function safeZipEntryName(name) {
|
|
88
|
+
const base = name
|
|
89
|
+
.replace(/^.*[\\/]/u, "") // drop any directory prefix (POSIX or Windows separators)
|
|
90
|
+
.replace(/\.{2,}/gu, "."); // collapse ".." (and longer dot-runs) so no traversal token survives
|
|
91
|
+
return base.length > 0 ? base : "export.bin";
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Assembles a deterministic STORE (no-compression) ZIP from the given entries.
|
|
95
|
+
* DOS date/time fields are zeroed for byte-stable output across runs. Entry names are reduced to a
|
|
96
|
+
* contained basename so a bundle can never write outside its own archive root.
|
|
97
|
+
*/
|
|
98
|
+
export function assembleZipBundle(entries) {
|
|
99
|
+
const localHeaders = [];
|
|
100
|
+
const centralEntries = [];
|
|
101
|
+
const dataChunks = [];
|
|
102
|
+
const offsets = [];
|
|
103
|
+
let offset = 0;
|
|
104
|
+
for (const entry of entries) {
|
|
105
|
+
const nameBytes = TEXT_ENC.encode(safeZipEntryName(entry.name));
|
|
106
|
+
const crc = crc32(entry.bytes);
|
|
107
|
+
const local = buildLocalHeader(nameBytes, entry.bytes.length, crc);
|
|
108
|
+
offsets.push(offset);
|
|
109
|
+
localHeaders.push(local);
|
|
110
|
+
dataChunks.push(entry.bytes);
|
|
111
|
+
offset += local.length + entry.bytes.length;
|
|
112
|
+
centralEntries.push(buildCentralEntry(nameBytes, entry.bytes.length, crc, offsets.at(-1) ?? 0));
|
|
113
|
+
}
|
|
114
|
+
const centralStart = offset;
|
|
115
|
+
const centralFlat = centralEntries.flatMap((e) => e);
|
|
116
|
+
const eocd = [
|
|
117
|
+
...ZIP_EOCD_SIG,
|
|
118
|
+
...u16le(0), // disk number
|
|
119
|
+
...u16le(0), // central dir start disk
|
|
120
|
+
...u16le(entries.length), // entries on disk
|
|
121
|
+
...u16le(entries.length), // total entries
|
|
122
|
+
...u32le(centralFlat.length),
|
|
123
|
+
...u32le(centralStart),
|
|
124
|
+
...u16le(0), // comment length
|
|
125
|
+
];
|
|
126
|
+
return concatZipBytes(localHeaders, dataChunks, centralFlat, eocd);
|
|
127
|
+
}
|
|
128
|
+
// Interleave [localHeader, data] pairs, then the central directory and EOCD, into one buffer.
|
|
129
|
+
function concatZipBytes(localHeaders, dataChunks, centralFlat, eocd) {
|
|
130
|
+
const totalLen = localHeaders.reduce((s, h) => s + h.length, 0) +
|
|
131
|
+
dataChunks.reduce((s, d) => s + d.length, 0) +
|
|
132
|
+
centralFlat.length +
|
|
133
|
+
eocd.length;
|
|
134
|
+
const out = new Uint8Array(totalLen);
|
|
135
|
+
let pos = 0;
|
|
136
|
+
for (let i = 0; i < localHeaders.length; i++) {
|
|
137
|
+
const hdr = localHeaders[i];
|
|
138
|
+
const dat = dataChunks[i];
|
|
139
|
+
if (hdr === undefined || dat === undefined)
|
|
140
|
+
continue;
|
|
141
|
+
out.set(hdr, pos);
|
|
142
|
+
pos += hdr.length;
|
|
143
|
+
out.set(dat, pos);
|
|
144
|
+
pos += dat.length;
|
|
145
|
+
}
|
|
146
|
+
out.set(centralFlat, pos);
|
|
147
|
+
pos += centralFlat.length;
|
|
148
|
+
out.set(eocd, pos);
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
151
|
+
// ─── PDF assembly ─────────────────────────────────────────────────────────────
|
|
152
|
+
//
|
|
153
|
+
// A minimal, dependency-free, multi-page text PDF. The whole candidate body is rendered (no
|
|
154
|
+
// arbitrary truncation): lines are wrapped to the page width and paginated across as many A4 pages
|
|
155
|
+
// as needed. Text is encoded as single-byte WinAnsi (CP1252) and the Courier font declares
|
|
156
|
+
// /Encoding /WinAnsiEncoding, so German umlauts (ä ö ü Ä Ö Ü ß), the euro sign, and common
|
|
157
|
+
// typographic punctuation render correctly instead of as UTF-8 mojibake. No /CreationDate,
|
|
158
|
+
// no Producer, no random — byte-stable across identical runs.
|
|
159
|
+
const PDF_FONT_SIZE = 10;
|
|
160
|
+
const PDF_LEADING = 14;
|
|
161
|
+
const PDF_MARGIN_X = 50;
|
|
162
|
+
const PDF_TOP_Y = 792; // 842 page height − 50 top margin
|
|
163
|
+
const PDF_BOTTOM_Y = 56; // keep the last baseline above the bottom margin
|
|
164
|
+
const PDF_MAX_CHARS = 80; // Courier @10pt is 6pt/glyph → ~82 glyphs fit in 495pt; 80 leaves a margin
|
|
165
|
+
const PDF_LINES_PER_PAGE = Math.floor((PDF_TOP_Y - PDF_BOTTOM_Y) / PDF_LEADING); // 52
|
|
166
|
+
function pdfStr(s) {
|
|
167
|
+
return TEXT_ENC.encode(s);
|
|
168
|
+
}
|
|
169
|
+
function escapePdf(s) {
|
|
170
|
+
return s.replace(/\\/g, "\\\\").replace(/\(/g, "\\(").replace(/\)/g, "\\)");
|
|
171
|
+
}
|
|
172
|
+
// WinAnsi (CP1252) bytes for the code points that differ from Latin-1 in the 0x80–0x9F window.
|
|
173
|
+
const CP1252_HIGH = {
|
|
174
|
+
0x20ac: 0x80,
|
|
175
|
+
0x201a: 0x82,
|
|
176
|
+
0x0192: 0x83,
|
|
177
|
+
0x201e: 0x84,
|
|
178
|
+
0x2026: 0x85,
|
|
179
|
+
0x2020: 0x86,
|
|
180
|
+
0x2021: 0x87,
|
|
181
|
+
0x02c6: 0x88,
|
|
182
|
+
0x2030: 0x89,
|
|
183
|
+
0x0160: 0x8a,
|
|
184
|
+
0x2039: 0x8b,
|
|
185
|
+
0x0152: 0x8c,
|
|
186
|
+
0x017d: 0x8e,
|
|
187
|
+
0x2018: 0x91,
|
|
188
|
+
0x2019: 0x92,
|
|
189
|
+
0x201c: 0x93,
|
|
190
|
+
0x201d: 0x94,
|
|
191
|
+
0x2022: 0x95,
|
|
192
|
+
0x2013: 0x96,
|
|
193
|
+
0x2014: 0x97,
|
|
194
|
+
0x02dc: 0x98,
|
|
195
|
+
0x2122: 0x99,
|
|
196
|
+
0x0161: 0x9a,
|
|
197
|
+
0x203a: 0x9b,
|
|
198
|
+
0x0153: 0x9c,
|
|
199
|
+
0x017e: 0x9e,
|
|
200
|
+
0x0178: 0x9f,
|
|
201
|
+
};
|
|
202
|
+
// Best-effort ASCII fold for common punctuation outside CP1252, so they degrade legibly (not to '?').
|
|
203
|
+
const PDF_ASCII_FOLD = {
|
|
204
|
+
0x2010: 0x2d,
|
|
205
|
+
0x2011: 0x2d,
|
|
206
|
+
0x2012: 0x2d, // hyphen/figure/non-breaking dashes → '-'
|
|
207
|
+
0x00a0: 0x20,
|
|
208
|
+
0x2007: 0x20,
|
|
209
|
+
0x202f: 0x20, // no-break / figure / narrow spaces → ' '
|
|
210
|
+
};
|
|
211
|
+
function winAnsiByte(cp) {
|
|
212
|
+
if (cp === 0x09)
|
|
213
|
+
return 0x20; // tab → space
|
|
214
|
+
if (cp < 0x20)
|
|
215
|
+
return 0x3f; // other C0 controls → '?'
|
|
216
|
+
if (cp <= 0x7e)
|
|
217
|
+
return cp; // printable ASCII
|
|
218
|
+
if (cp === 0x7f)
|
|
219
|
+
return 0x3f; // DEL
|
|
220
|
+
if (cp >= 0xa0 && cp <= 0xff)
|
|
221
|
+
return cp; // Latin-1 printable (umlauts, ß, accented letters)
|
|
222
|
+
return CP1252_HIGH[cp] ?? PDF_ASCII_FOLD[cp] ?? 0x3f;
|
|
223
|
+
}
|
|
224
|
+
function winAnsiEncode(text) {
|
|
225
|
+
const codePoints = Array.from(text); // iterate by code point (surrogate-safe)
|
|
226
|
+
const out = new Uint8Array(codePoints.length);
|
|
227
|
+
for (let i = 0; i < codePoints.length; i++) {
|
|
228
|
+
out[i] = winAnsiByte(codePoints[i]?.codePointAt(0) ?? 0x3f);
|
|
229
|
+
}
|
|
230
|
+
return out;
|
|
231
|
+
}
|
|
232
|
+
// Greedy word-aware wrap to `maxChars`; a single over-long token is hard-broken. Preserves blanks.
|
|
233
|
+
function wrapText(line, maxChars) {
|
|
234
|
+
if (line.length <= maxChars)
|
|
235
|
+
return [line];
|
|
236
|
+
const out = [];
|
|
237
|
+
let current = "";
|
|
238
|
+
for (const word of line.split(" ")) {
|
|
239
|
+
if (word.length > maxChars) {
|
|
240
|
+
if (current.length > 0)
|
|
241
|
+
out.push(current);
|
|
242
|
+
let rest = word;
|
|
243
|
+
while (rest.length > maxChars) {
|
|
244
|
+
out.push(rest.slice(0, maxChars));
|
|
245
|
+
rest = rest.slice(maxChars);
|
|
246
|
+
}
|
|
247
|
+
current = rest;
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
const candidate = current.length === 0 ? word : `${current} ${word}`;
|
|
251
|
+
if (candidate.length > maxChars) {
|
|
252
|
+
out.push(current);
|
|
253
|
+
current = word;
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
current = candidate;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (current.length > 0)
|
|
260
|
+
out.push(current);
|
|
261
|
+
return out.length > 0 ? out : [""];
|
|
262
|
+
}
|
|
263
|
+
function wrapBody(body) {
|
|
264
|
+
const out = [];
|
|
265
|
+
for (const line of body.split(/\r\n|\r|\n/u)) {
|
|
266
|
+
for (const wrapped of wrapText(line, PDF_MAX_CHARS))
|
|
267
|
+
out.push(wrapped);
|
|
268
|
+
}
|
|
269
|
+
return out;
|
|
270
|
+
}
|
|
271
|
+
function paginate(lines, perPage) {
|
|
272
|
+
const pages = [];
|
|
273
|
+
for (let i = 0; i < lines.length; i += perPage) {
|
|
274
|
+
pages.push(lines.slice(i, i + perPage));
|
|
275
|
+
}
|
|
276
|
+
return pages.length > 0 ? pages : [[""]];
|
|
277
|
+
}
|
|
278
|
+
// Build one page's content stream: position the text cursor, then emit each (escaped, WinAnsi) line.
|
|
279
|
+
function buildPageContent(lines) {
|
|
280
|
+
const chunks = [
|
|
281
|
+
pdfStr(`BT\n/F1 ${String(PDF_FONT_SIZE)} Tf\n${String(PDF_LEADING)} TL\n`),
|
|
282
|
+
pdfStr(`${String(PDF_MARGIN_X)} ${String(PDF_TOP_Y)} Td\n`),
|
|
283
|
+
];
|
|
284
|
+
let first = true;
|
|
285
|
+
for (const line of lines) {
|
|
286
|
+
chunks.push(pdfStr(first ? "(" : "T* ("));
|
|
287
|
+
chunks.push(winAnsiEncode(escapePdf(line)));
|
|
288
|
+
chunks.push(pdfStr(") Tj\n"));
|
|
289
|
+
first = false;
|
|
290
|
+
}
|
|
291
|
+
chunks.push(pdfStr("ET"));
|
|
292
|
+
return concatBytes(chunks);
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Assembles a deterministic, multi-page, text-only PDF from the given plain-text body. The entire
|
|
296
|
+
* body is rendered across as many pages as needed (no truncation). Byte-stable across identical runs.
|
|
297
|
+
*/
|
|
298
|
+
export function assemblePdf(body, runId) {
|
|
299
|
+
const rawLines = [`Run: ${runId}`, "", ...wrapBody(body)];
|
|
300
|
+
const pages = paginate(rawLines, PDF_LINES_PER_PAGE);
|
|
301
|
+
const pageObjNums = pages.map((_, i) => 4 + 2 * i);
|
|
302
|
+
const maxObjNum = 3 + 2 * pages.length;
|
|
303
|
+
const objects = [
|
|
304
|
+
pdfStr("1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n"),
|
|
305
|
+
pdfStr(`2 0 obj\n<< /Type /Pages /Kids [${pageObjNums.map((n) => `${String(n)} 0 R`).join(" ")}]` +
|
|
306
|
+
` /Count ${String(pages.length)} >>\nendobj\n`),
|
|
307
|
+
pdfStr("3 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Courier" +
|
|
308
|
+
" /Encoding /WinAnsiEncoding >>\nendobj\n"),
|
|
309
|
+
];
|
|
310
|
+
pages.forEach((pageLines, i) => {
|
|
311
|
+
const pageNum = 4 + 2 * i;
|
|
312
|
+
const contentNum = pageNum + 1;
|
|
313
|
+
objects.push(pdfStr(`${String(pageNum)} 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842]` +
|
|
314
|
+
` /Contents ${String(contentNum)} 0 R` +
|
|
315
|
+
` /Resources << /Font << /F1 3 0 R >> >> >>\nendobj\n`));
|
|
316
|
+
const content = buildPageContent(pageLines);
|
|
317
|
+
objects.push(concatBytes([
|
|
318
|
+
pdfStr(`${String(contentNum)} 0 obj\n<< /Length ${String(content.length)} >>\nstream\n`),
|
|
319
|
+
content,
|
|
320
|
+
pdfStr("\nendstream\nendobj\n"),
|
|
321
|
+
]));
|
|
322
|
+
});
|
|
323
|
+
const header = pdfStr("%PDF-1.4\n");
|
|
324
|
+
const offsets = [];
|
|
325
|
+
let pos = header.length;
|
|
326
|
+
for (const obj of objects) {
|
|
327
|
+
offsets.push(pos);
|
|
328
|
+
pos += obj.length;
|
|
329
|
+
}
|
|
330
|
+
const xrefOffset = pos;
|
|
331
|
+
const xref = buildPdfXref(offsets);
|
|
332
|
+
const trailer = pdfStr(`trailer\n<< /Size ${String(maxObjNum + 1)} /Root 1 0 R >>\n` +
|
|
333
|
+
`startxref\n${String(xrefOffset)}\n%%EOF\n`);
|
|
334
|
+
return concatBytes([header, ...objects, xref, trailer]);
|
|
335
|
+
}
|
|
336
|
+
// Build the PDF cross-reference table for the given object byte-offsets (object 0 is the free head).
|
|
337
|
+
function buildPdfXref(offsets) {
|
|
338
|
+
const rows = offsets.map((o) => `${String(o).padStart(10, "0")} 00000 n `);
|
|
339
|
+
const lines = ["xref", `0 ${String(offsets.length + 1)}`, "0000000000 65535 f ", ...rows];
|
|
340
|
+
return pdfStr(lines.join("\n") + "\n");
|
|
341
|
+
}
|
|
342
|
+
// Concatenate byte chunks into a single buffer.
|
|
343
|
+
function concatBytes(chunks) {
|
|
344
|
+
const totalLen = chunks.reduce((s, c) => s + c.length, 0);
|
|
345
|
+
const out = new Uint8Array(totalLen);
|
|
346
|
+
let pos = 0;
|
|
347
|
+
for (const chunk of chunks) {
|
|
348
|
+
out.set(chunk, pos);
|
|
349
|
+
pos += chunk.length;
|
|
350
|
+
}
|
|
351
|
+
return out;
|
|
352
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { RouteContext, RouteResult, RouteDefinition } from "../routes.js";
|
|
2
|
+
import type { UiHandlerDeps } from "../deps.js";
|
|
3
|
+
export declare function handleQiExport(ctx: RouteContext, deps: UiHandlerDeps): Promise<RouteResult>;
|
|
4
|
+
export declare const QI_EXPORT_ROUTE_GROUP: readonly RouteDefinition[];
|
|
5
|
+
//# sourceMappingURL=exportRoutes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exportRoutes.d.ts","sourceRoot":"","sources":["../../src/qualityIntelligence/exportRoutes.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AA8NhD,wBAAsB,cAAc,CAAC,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,WAAW,CAAC,CA2BjG;AA+HD,eAAO,MAAM,qBAAqB,EAAE,SAAS,eAAe,EAE3D,CAAC"}
|