@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,314 @@
|
|
|
1
|
+
// Figma Snapshot render orchestration + assembly (Epic #750, Issue #753).
|
|
2
|
+
//
|
|
3
|
+
// The SECOND and final Figma egress of the snapshot-build. For each detected screen FRAME id
|
|
4
|
+
// (from #752's ScreenIrResult — NEVER the canvas root, which times out on `/v1/images`), it:
|
|
5
|
+
// 1. requests an ephemeral render url from `GET /v1/images` (batched, minimal — heavy backoff is
|
|
6
|
+
// #759), authenticated with the read-only PAT in the `X-Figma-Token` header;
|
|
7
|
+
// 2. downloads the PNG bytes from that ephemeral url via the injectable render port (no auth
|
|
8
|
+
// header — the url is pre-signed);
|
|
9
|
+
// 3. assembles the immutable Snapshot value: per-screen IR + image + deterministic integrity
|
|
10
|
+
// hashes + provenance, with a `skippedScreens` list plus structural-only IR rows for any
|
|
11
|
+
// screen that failed to render.
|
|
12
|
+
//
|
|
13
|
+
// Once this returns, the Snapshot is the communication boundary: nothing downstream contacts
|
|
14
|
+
// Figma. The token flows ONLY into the images-call header; it never reaches the snapshot value,
|
|
15
|
+
// the render-url download, an error, or a log.
|
|
16
|
+
import { FigmaConnectorError } from "./figmaConnectorErrors.js";
|
|
17
|
+
import { mapWithConcurrency } from "./figmaConcurrency.js";
|
|
18
|
+
import { classifyTokenFailure } from "./figmaTokenSource.js";
|
|
19
|
+
import { DEFAULT_FIGMA_RETRY_POLICY, fetchWithBackoff, realFigmaRetrySleep, } from "./figmaRetry.js";
|
|
20
|
+
import { hashBytes, hashScreen, hashSnapshot, hashStructuralScreen } from "./figmaSnapshotHash.js";
|
|
21
|
+
import { DEFAULT_SCOPED_PAGINATION_LIMITS, SCOPED_PAGINATION_LIMIT_CEILINGS, } from "./figmaScopedPagination.js";
|
|
22
|
+
const FIGMA_API_ORIGIN = "https://api.figma.com";
|
|
23
|
+
const SNAPSHOT_SCHEMA_VERSION = 1;
|
|
24
|
+
const DEFAULT_RENDER_BATCH_SIZE = 20;
|
|
25
|
+
const MAX_RENDER_BATCH_SIZE = 100;
|
|
26
|
+
const DEFAULT_MAX_IMAGE_BYTES = 25 * 1024 * 1024;
|
|
27
|
+
const DEFAULT_RENDER_SCALE = 1;
|
|
28
|
+
const DEFAULT_DOWNLOAD_CONCURRENCY = 4;
|
|
29
|
+
// Bounded retries for a TRANSIENT malformed `/v1/images` body (a 2xx without an `images` map). Figma's
|
|
30
|
+
// render endpoint returns this intermittently when overloaded by back-to-back whole-page builds; an
|
|
31
|
+
// isolated retry recovers it. After this many retries the batch degrades to a skip rather than failing
|
|
32
|
+
// the whole build (F8).
|
|
33
|
+
const MAX_RENDER_BODY_RETRIES = 3;
|
|
34
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
35
|
+
const chunk = (items, size) => {
|
|
36
|
+
const safeSize = Number.isInteger(size) && size >= 1 ? size : DEFAULT_RENDER_BATCH_SIZE;
|
|
37
|
+
const out = [];
|
|
38
|
+
for (let i = 0; i < items.length; i += safeSize)
|
|
39
|
+
out.push(items.slice(i, i + safeSize));
|
|
40
|
+
return out;
|
|
41
|
+
};
|
|
42
|
+
const boundedPositiveInt = (value, fallback, ceiling) => {
|
|
43
|
+
if (value === undefined || !Number.isInteger(value) || value < 1)
|
|
44
|
+
return fallback;
|
|
45
|
+
return Math.min(value, ceiling);
|
|
46
|
+
};
|
|
47
|
+
// Returns true when the hostname is an IP literal or a local/loopback name that must be blocked.
|
|
48
|
+
const isBlockedHostname = (h) => h.length === 0 ||
|
|
49
|
+
/^\d+\.\d+\.\d+\.\d+$/.test(h) ||
|
|
50
|
+
h.startsWith("[") ||
|
|
51
|
+
h === "localhost" ||
|
|
52
|
+
h.endsWith(".localhost") ||
|
|
53
|
+
h.endsWith(".local");
|
|
54
|
+
// Validates that a render URL returned by the Figma API is safe to follow. Blocks http:
|
|
55
|
+
// (plaintext) and IP-literal hostnames (RFC-1918, loopback, link-local) to prevent SSRF via
|
|
56
|
+
// a compromised or MITM'd API response. A broad IP-block is preferred over a tight CDN
|
|
57
|
+
// allowlist because Figma's pre-signed render URLs span multiple AWS S3 regions/CDN fronts.
|
|
58
|
+
const isRenderUrlSafe = (raw) => {
|
|
59
|
+
let url;
|
|
60
|
+
try {
|
|
61
|
+
url = new URL(raw);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
if (url.protocol !== "https:")
|
|
67
|
+
return false;
|
|
68
|
+
// Normalise: lowercase + strip exactly one trailing dot so "localhost." == "localhost".
|
|
69
|
+
const h = url.hostname.toLowerCase().replace(/\.$/, "");
|
|
70
|
+
if (isBlockedHostname(h))
|
|
71
|
+
return false;
|
|
72
|
+
if (!isAllowedFigmaRenderUrl(url, h))
|
|
73
|
+
return false;
|
|
74
|
+
// Only allow the default HTTPS port (443 or implicit) — non-standard ports indicate an
|
|
75
|
+
// internal service, not a CDN. url.port is "" when the port is the scheme default.
|
|
76
|
+
if (url.port !== "" && url.port !== "443")
|
|
77
|
+
return false;
|
|
78
|
+
return true;
|
|
79
|
+
};
|
|
80
|
+
const isAllowedFigmaRenderUrl = (url, host) => {
|
|
81
|
+
if (host === "s3-alpha-sig.figma.com")
|
|
82
|
+
return true;
|
|
83
|
+
if (host === "cdn.figma.com" || host === "static.figma.com")
|
|
84
|
+
return true;
|
|
85
|
+
if (host.endsWith(".figma.com"))
|
|
86
|
+
return true;
|
|
87
|
+
// Multi-region S3 bucket allowlist: accepts any globally-unique bucket whose NAME starts with
|
|
88
|
+
// "figma" (e.g. figma-prod.s3.eu-west-1.amazonaws.com). The residual risk is that an attacker
|
|
89
|
+
// who can create an S3 bucket named figma-anything could serve a crafted render response. In
|
|
90
|
+
// practice the risk is low: (a) render URLs are returned by the authenticated Figma API over
|
|
91
|
+
// TLS, so a MITM or API compromise is a prerequisite; (b) the URLs are short-lived/pre-signed
|
|
92
|
+
// and carry no Figma auth header (the token is injected only into the /v1/images API call);
|
|
93
|
+
// (c) the byte-cap + oversized-skip guard limits the blast radius of a poisoned response.
|
|
94
|
+
// Tightening to a fixed bucket list would break legitimate Figma render fronts that span many
|
|
95
|
+
// regions; revisit if Figma publishes an authoritative set of render origins.
|
|
96
|
+
if (/^figma[-a-z0-9]*\.s3[.-][a-z0-9-]+\.amazonaws\.com$/u.test(host))
|
|
97
|
+
return true;
|
|
98
|
+
if ((host === "s3-us-west-2.amazonaws.com" || host === "s3.us-west-2.amazonaws.com") &&
|
|
99
|
+
/^\/figma[-_/a-z0-9]*/iu.test(url.pathname)) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
return false;
|
|
103
|
+
};
|
|
104
|
+
const buildImagesUrl = (fileKey, ids, version) => {
|
|
105
|
+
const url = new URL(`${FIGMA_API_ORIGIN}/v1/images/${encodeURIComponent(fileKey)}`);
|
|
106
|
+
url.searchParams.set("ids", ids.join(","));
|
|
107
|
+
url.searchParams.set("format", "png");
|
|
108
|
+
url.searchParams.set("scale", String(DEFAULT_RENDER_SCALE));
|
|
109
|
+
if (version !== undefined && version.length > 0)
|
|
110
|
+
url.searchParams.set("version", version);
|
|
111
|
+
return url.toString();
|
|
112
|
+
};
|
|
113
|
+
const extractFigmaReason = (body) => {
|
|
114
|
+
if (!isRecord(body))
|
|
115
|
+
return undefined;
|
|
116
|
+
const reason = body.err ?? body.message;
|
|
117
|
+
return typeof reason === "string" ? reason : undefined;
|
|
118
|
+
};
|
|
119
|
+
const statusToError = (status, body) => classifyTokenFailure(status, extractFigmaReason(body));
|
|
120
|
+
// Extracts the `{ images: { id: url|null } }` map from one `/v1/images` response, or `null` when the
|
|
121
|
+
// body is a non-standard 2xx WITHOUT an `images` map. Figma's render endpoint returns such a body
|
|
122
|
+
// intermittently when it is overloaded by back-to-back whole-page builds (#750 F8 — observed live).
|
|
123
|
+
// `null` is a TRANSIENT, retriable signal — the caller retries the batch, then degrades the affected
|
|
124
|
+
// screens to a skip (coverage notice) if it persists, rather than failing the whole build.
|
|
125
|
+
const extractImageUrls = (json) => {
|
|
126
|
+
if (!isRecord(json) || !isRecord(json.images))
|
|
127
|
+
return null;
|
|
128
|
+
const out = {};
|
|
129
|
+
for (const [id, value] of Object.entries(json.images)) {
|
|
130
|
+
out[id] = typeof value === "string" && value.length > 0 ? value : null;
|
|
131
|
+
}
|
|
132
|
+
return out;
|
|
133
|
+
};
|
|
134
|
+
// Figma's render endpoint can reject a subset of otherwise readable screen node ids with a client
|
|
135
|
+
// status (observed on large boards where some detected screen frames cannot be rendered). That is a
|
|
136
|
+
// renderability problem, not an auth/egress/build integrity problem: keep the snapshot usable by
|
|
137
|
+
// returning an empty render-url map for that batch so the affected screens become structural-only.
|
|
138
|
+
// Auth/scope/proxy/rate-limit/upstream failures remain hard coded errors below.
|
|
139
|
+
const isRenderEndpointSoftFailure = (status) => status === 400 || status === 404 || status === 422;
|
|
140
|
+
// Resolve one `/v1/images` batch to its ephemeral-url map. The fetch is wrapped in the deterministic
|
|
141
|
+
// 429 backoff; auth/scope/upstream non-2xx responses are hard coded errors, while renderability
|
|
142
|
+
// client failures degrade this batch to structural-only. A 2xx with a malformed body is a transient
|
|
143
|
+
// render-overload signal that is retried up to MAX_RENDER_BODY_RETRIES, then degrades to an empty map
|
|
144
|
+
// so the affected screens skip (render-url-missing) instead of aborting the build (F8).
|
|
145
|
+
const resolveRenderBatch = async (input, batch, policy, sleep) => {
|
|
146
|
+
const requestUrl = buildImagesUrl(input.provenance.fileKey, batch, input.provenance.version);
|
|
147
|
+
for (let attempt = 0;; attempt += 1) {
|
|
148
|
+
const response = await fetchWithBackoff(() => input.imagesPort({ url: requestUrl, headers: { "X-Figma-Token": input.token } }), policy, sleep);
|
|
149
|
+
if (response.status < 200 || response.status >= 300) {
|
|
150
|
+
if (isRenderEndpointSoftFailure(response.status))
|
|
151
|
+
return {};
|
|
152
|
+
throw statusToError(response.status, response.json);
|
|
153
|
+
}
|
|
154
|
+
const map = extractImageUrls(response.json);
|
|
155
|
+
if (map !== null)
|
|
156
|
+
return map;
|
|
157
|
+
if (attempt >= MAX_RENDER_BODY_RETRIES)
|
|
158
|
+
return {};
|
|
159
|
+
await sleep(Math.min(policy.baseDelayMs * 2 ** attempt, policy.maxDelayMs));
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
// Calls `/v1/images` in bounded batches and merges the ephemeral-url map. The token authenticates
|
|
163
|
+
// each batch via the header only. Each batch is 429-backoff retried AND malformed-body retried
|
|
164
|
+
// (resolveRenderBatch), so a rate-limited or transiently-overloaded huge-board render survives.
|
|
165
|
+
const requestRenderUrls = async (input, screenIds) => {
|
|
166
|
+
const policy = input.retryPolicy ?? DEFAULT_FIGMA_RETRY_POLICY;
|
|
167
|
+
const sleep = input.sleep ?? realFigmaRetrySleep;
|
|
168
|
+
const batchSize = boundedPositiveInt(input.batchSize, DEFAULT_RENDER_BATCH_SIZE, MAX_RENDER_BATCH_SIZE);
|
|
169
|
+
const urls = new Map();
|
|
170
|
+
for (const batch of chunk(screenIds, batchSize)) {
|
|
171
|
+
const map = await resolveRenderBatch(input, batch, policy, sleep);
|
|
172
|
+
for (const id of batch)
|
|
173
|
+
urls.set(id, map[id] ?? null);
|
|
174
|
+
}
|
|
175
|
+
return urls;
|
|
176
|
+
};
|
|
177
|
+
const structuralOnly = (ir, reason) => ({
|
|
178
|
+
skipped: { screenId: ir.id, reason },
|
|
179
|
+
structuralScreen: {
|
|
180
|
+
screenId: ir.id,
|
|
181
|
+
ir,
|
|
182
|
+
reason,
|
|
183
|
+
integrityHash: hashStructuralScreen(ir.id, ir),
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
// Classifies a finished render download into a skip reason, or null when the bytes are usable.
|
|
187
|
+
const renderSkipReason = (response, maxBytes) => {
|
|
188
|
+
if (response === null)
|
|
189
|
+
return "render-fetch-failed";
|
|
190
|
+
if (response.status < 200 || response.status >= 300)
|
|
191
|
+
return "render-fetch-failed";
|
|
192
|
+
if (response.bytes.length === 0)
|
|
193
|
+
return "render-empty";
|
|
194
|
+
if (response.bytes.length > maxBytes)
|
|
195
|
+
return "render-oversized";
|
|
196
|
+
return null;
|
|
197
|
+
};
|
|
198
|
+
// Hard-egress codes for the render-byte download host (S3/CDN — a DIFFERENT host than
|
|
199
|
+
// api.figma.com). If the render port throws a FigmaConnectorError in this family, it means
|
|
200
|
+
// TLS or proxy is misconfigured for the render CDN; swallowing it would yield a "successful"
|
|
201
|
+
// snapshot with every screen skipped while hiding the root cause. Re-throw to abort the build,
|
|
202
|
+
// consistent with the DEEP_FETCH_ABORT_CODES discipline in figmaScopedPagination.ts.
|
|
203
|
+
// Using ReadonlySet<string> so this set is forward-compatible with the new codes
|
|
204
|
+
// (FIGMA_PROXY_AUTH_REQUIRED, FIGMA_PROXY_BLOCKED_BY_POLICY) landing in a parallel change to
|
|
205
|
+
// figmaConnectorErrors.ts without requiring a re-touch of this file.
|
|
206
|
+
const RENDER_EGRESS_ABORT_CODES = new Set([
|
|
207
|
+
"FIGMA_TLS_CA_FAILURE",
|
|
208
|
+
"FIGMA_PROXY_UNREACHABLE",
|
|
209
|
+
"FIGMA_PROXY_EGRESS_FAILED",
|
|
210
|
+
"FIGMA_PROXY_AUTH_REQUIRED",
|
|
211
|
+
"FIGMA_PROXY_BLOCKED_BY_POLICY",
|
|
212
|
+
]);
|
|
213
|
+
// Classifies a render-port throw into a re-throw (hard egress abort) or a coded skip outcome.
|
|
214
|
+
// Hard-egress errors abort the whole build — they affect every screen on the same host so
|
|
215
|
+
// swallowing them would silently produce an empty snapshot.
|
|
216
|
+
const classifyRenderError = (screenId, err) => {
|
|
217
|
+
if (err instanceof FigmaConnectorError) {
|
|
218
|
+
if (RENDER_EGRESS_ABORT_CODES.has(err.code))
|
|
219
|
+
throw err;
|
|
220
|
+
return { skipped: { screenId, reason: `render-fetch-failed:${err.code}` } };
|
|
221
|
+
}
|
|
222
|
+
return { skipped: { screenId, reason: "render-fetch-failed" } };
|
|
223
|
+
};
|
|
224
|
+
// Downloads one screen's render bytes and classifies the result into a kept screen or a skip.
|
|
225
|
+
// A single screen failing after retries degrades to a skip (partial render), never aborting.
|
|
226
|
+
// Exception: hard egress errors (TLS/proxy misconfiguration) abort via classifyRenderError.
|
|
227
|
+
const resolveScreen = async (input, ir, renderUrl) => {
|
|
228
|
+
if (renderUrl === null)
|
|
229
|
+
return structuralOnly(ir, "render-url-missing");
|
|
230
|
+
if (!isRenderUrlSafe(renderUrl))
|
|
231
|
+
return structuralOnly(ir, "render-url-blocked");
|
|
232
|
+
const maxBytes = input.maxImageBytes ?? DEFAULT_MAX_IMAGE_BYTES;
|
|
233
|
+
const policy = input.retryPolicy ?? DEFAULT_FIGMA_RETRY_POLICY;
|
|
234
|
+
const sleep = input.sleep ?? realFigmaRetrySleep;
|
|
235
|
+
let response;
|
|
236
|
+
try {
|
|
237
|
+
response = await fetchWithBackoff(() => input.renderPort({ url: renderUrl, headers: {} }), policy, sleep);
|
|
238
|
+
}
|
|
239
|
+
catch (err) {
|
|
240
|
+
const classified = classifyRenderError(ir.id, err);
|
|
241
|
+
if (classified.skipped !== undefined) {
|
|
242
|
+
return structuralOnly(ir, classified.skipped.reason);
|
|
243
|
+
}
|
|
244
|
+
return classified;
|
|
245
|
+
}
|
|
246
|
+
const reason = renderSkipReason(response, maxBytes);
|
|
247
|
+
if (reason !== null)
|
|
248
|
+
return structuralOnly(ir, reason);
|
|
249
|
+
const sha256 = hashBytes(response.bytes);
|
|
250
|
+
const screen = {
|
|
251
|
+
screenId: ir.id,
|
|
252
|
+
ir,
|
|
253
|
+
image: {
|
|
254
|
+
mimeType: "image/png",
|
|
255
|
+
bytes: response.bytes,
|
|
256
|
+
byteLength: response.bytes.length,
|
|
257
|
+
sha256,
|
|
258
|
+
},
|
|
259
|
+
integrityHash: hashScreen(ir.id, ir, sha256),
|
|
260
|
+
};
|
|
261
|
+
return { screen };
|
|
262
|
+
};
|
|
263
|
+
function collectSnapshotRows(outcomes, cappedScreens) {
|
|
264
|
+
const screens = [];
|
|
265
|
+
const skippedScreens = [];
|
|
266
|
+
const structuralScreens = [];
|
|
267
|
+
for (const outcome of outcomes) {
|
|
268
|
+
if (outcome.screen !== undefined)
|
|
269
|
+
screens.push(outcome.screen);
|
|
270
|
+
if (outcome.skipped !== undefined)
|
|
271
|
+
skippedScreens.push(outcome.skipped);
|
|
272
|
+
if (outcome.structuralScreen !== undefined)
|
|
273
|
+
structuralScreens.push(outcome.structuralScreen);
|
|
274
|
+
}
|
|
275
|
+
for (const ir of cappedScreens) {
|
|
276
|
+
const outcome = structuralOnly(ir, "render-screen-cap-exceeded");
|
|
277
|
+
if (outcome.skipped !== undefined)
|
|
278
|
+
skippedScreens.push(outcome.skipped);
|
|
279
|
+
if (outcome.structuralScreen !== undefined)
|
|
280
|
+
structuralScreens.push(outcome.structuralScreen);
|
|
281
|
+
}
|
|
282
|
+
return { screens, skippedScreens, structuralScreens };
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Render the screens and assemble the immutable Figma Snapshot. Render failures degrade to a
|
|
286
|
+
* `skippedScreens` entry plus structural-only IR (partial render); a non-2xx `/v1/images` call is
|
|
287
|
+
* a hard coded error.
|
|
288
|
+
*/
|
|
289
|
+
export const buildFigmaSnapshot = async (input) => {
|
|
290
|
+
const maxScreensRendered = boundedPositiveInt(input.maxScreensRendered, DEFAULT_SCOPED_PAGINATION_LIMITS.maxScreensDeep, SCOPED_PAGINATION_LIMIT_CEILINGS.maxScreensDeep);
|
|
291
|
+
const renderableScreens = input.ir.screens.slice(0, maxScreensRendered);
|
|
292
|
+
const cappedScreens = input.ir.screens.slice(maxScreensRendered);
|
|
293
|
+
const screenIds = renderableScreens.map((screen) => screen.id);
|
|
294
|
+
const renderUrls = screenIds.length === 0
|
|
295
|
+
? new Map()
|
|
296
|
+
: await requestRenderUrls(input, screenIds);
|
|
297
|
+
const concurrency = boundedPositiveInt(input.downloadConcurrency, DEFAULT_DOWNLOAD_CONCURRENCY, Number.MAX_SAFE_INTEGER);
|
|
298
|
+
const outcomes = await mapWithConcurrency(renderableScreens, concurrency, (ir) => resolveScreen(input, ir, renderUrls.get(ir.id) ?? null));
|
|
299
|
+
const { screens, skippedScreens, structuralScreens } = collectSnapshotRows(outcomes, cappedScreens);
|
|
300
|
+
return {
|
|
301
|
+
snapshotSchemaVersion: SNAPSHOT_SCHEMA_VERSION,
|
|
302
|
+
provenance: input.provenance,
|
|
303
|
+
screens,
|
|
304
|
+
skippedScreens,
|
|
305
|
+
structuralScreens,
|
|
306
|
+
// Carried verbatim from the Screen-IR (#752) for the navigation/flow graph (#811). NOT folded
|
|
307
|
+
// into the integrity hash below — `links` is non-identity metadata, so drift (#735) is stable.
|
|
308
|
+
links: input.ir.links,
|
|
309
|
+
integrityHash: hashSnapshot(SNAPSHOT_SCHEMA_VERSION, input.provenance.version, [
|
|
310
|
+
...screens,
|
|
311
|
+
...structuralScreens,
|
|
312
|
+
]),
|
|
313
|
+
};
|
|
314
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { QualityIntelligenceFigma } from "@oscharko-dev/keiko-quality-intelligence";
|
|
2
|
+
type ScreenIr = QualityIntelligenceFigma.ScreenIr;
|
|
3
|
+
/** sha256 over the canonical per-screen identity: {screenId, ir, imageSha256}. */
|
|
4
|
+
export declare const hashScreen: (screenId: string, ir: ScreenIr, imageSha256: string) => string;
|
|
5
|
+
/** sha256 over a structural-only screen identity: {screenId, ir, structuralOnly:true}. */
|
|
6
|
+
export declare const hashStructuralScreen: (screenId: string, ir: ScreenIr) => string;
|
|
7
|
+
/**
|
|
8
|
+
* sha256 over the canonical snapshot identity: schema version + pinned Figma version + the
|
|
9
|
+
* per-screen hashes sorted by screenId. EXCLUDES `fetchedAt` so the hash is drift-stable across
|
|
10
|
+
* re-fetches of the same unchanged design.
|
|
11
|
+
*/
|
|
12
|
+
export declare const hashSnapshot: (snapshotSchemaVersion: number, version: string | undefined, perScreen: readonly {
|
|
13
|
+
readonly screenId: string;
|
|
14
|
+
readonly integrityHash: string;
|
|
15
|
+
}[]) => string;
|
|
16
|
+
export declare const hashBytes: (bytes: Uint8Array) => string;
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=figmaSnapshotHash.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"figmaSnapshotHash.d.ts","sourceRoot":"","sources":["../../../src/qualityIntelligence/figma/figmaSnapshotHash.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,0CAA0C,CAAC;AAEzF,KAAK,QAAQ,GAAG,wBAAwB,CAAC,QAAQ,CAAC;AAwDlD,kFAAkF;AAClF,eAAO,MAAM,UAAU,GAAI,UAAU,MAAM,EAAE,IAAI,QAAQ,EAAE,aAAa,MAAM,KAAG,MACV,CAAC;AAExE,0FAA0F;AAC1F,eAAO,MAAM,oBAAoB,GAAI,UAAU,MAAM,EAAE,IAAI,QAAQ,KAAG,MACU,CAAC;AAEjF;;;;GAIG;AACH,eAAO,MAAM,YAAY,GACvB,uBAAuB,MAAM,EAC7B,SAAS,MAAM,GAAG,SAAS,EAC3B,WAAW,SAAS;IAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;CAAE,EAAE,KAClF,MAKF,CAAC;AAEF,eAAO,MAAM,SAAS,GAAI,OAAO,UAAU,KAAG,MACI,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Deterministic integrity hashing for the Figma Snapshot (Epic #750, Issue #753, drift #735).
|
|
2
|
+
//
|
|
3
|
+
// The hash is the drift identity: same unchanged design ⇒ byte-identical hash, regardless of when
|
|
4
|
+
// it was fetched. We therefore canonicalise to a stable key order and EXCLUDE the wall-clock
|
|
5
|
+
// `fetchedAt`. The snapshot identity is the pinned Figma `version` + the sorted per-screen
|
|
6
|
+
// identities (each = screenId + structural IR + image content sha256, or a structural-only marker
|
|
7
|
+
// for screens whose PNG render was deliberately skipped/could not be fetched). `version` and the IR
|
|
8
|
+
// are deterministic outputs of #751/#752; the image sha is included so an unexpected render-byte
|
|
9
|
+
// change surfaces as drift too, while structural-only screens still participate in drift detection.
|
|
10
|
+
import { createHash } from "node:crypto";
|
|
11
|
+
const sha256Hex = (input) => createHash("sha256").update(input, "utf8").digest("hex");
|
|
12
|
+
// Stable stringify: object keys are emitted in sorted order at every depth so the serialisation is
|
|
13
|
+
// independent of insertion order. Arrays keep their (already deterministic) order from #752.
|
|
14
|
+
const canonical = (value) => {
|
|
15
|
+
if (value === undefined)
|
|
16
|
+
return "null";
|
|
17
|
+
if (value === null || typeof value !== "object")
|
|
18
|
+
return JSON.stringify(value);
|
|
19
|
+
if (Array.isArray(value))
|
|
20
|
+
return `[${value.map(canonical).join(",")}]`;
|
|
21
|
+
const record = value;
|
|
22
|
+
const entries = Object.keys(record)
|
|
23
|
+
.sort()
|
|
24
|
+
.map((key) => `${JSON.stringify(key)}:${canonical(record[key])}`);
|
|
25
|
+
return `{${entries.join(",")}}`;
|
|
26
|
+
};
|
|
27
|
+
// Keys excluded from the integrity hash. Using a static Set + Object.entries filter avoids both
|
|
28
|
+
// dynamic-delete (no-dynamic-delete) and unused-var issues while keeping the whitelist explicit.
|
|
29
|
+
const HASH_NEUTRAL_KEYS = new Set([
|
|
30
|
+
"textColor",
|
|
31
|
+
"backgroundColor",
|
|
32
|
+
"layout",
|
|
33
|
+
"sizing",
|
|
34
|
+
"cornerRadius",
|
|
35
|
+
"typography",
|
|
36
|
+
]);
|
|
37
|
+
const stripHashNeutralFields = (node) => {
|
|
38
|
+
const entries = Object.entries(node).filter(([key]) => key !== "children" && !HASH_NEUTRAL_KEYS.has(key));
|
|
39
|
+
return {
|
|
40
|
+
...Object.fromEntries(entries),
|
|
41
|
+
children: node.children.map(stripHashNeutralFields),
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
const hashStableIr = (ir) => ({
|
|
45
|
+
...ir,
|
|
46
|
+
root: stripHashNeutralFields(ir.root),
|
|
47
|
+
});
|
|
48
|
+
/** sha256 over the canonical per-screen identity: {screenId, ir, imageSha256}. */
|
|
49
|
+
export const hashScreen = (screenId, ir, imageSha256) => sha256Hex(canonical({ imageSha256, ir: hashStableIr(ir), screenId }));
|
|
50
|
+
/** sha256 over a structural-only screen identity: {screenId, ir, structuralOnly:true}. */
|
|
51
|
+
export const hashStructuralScreen = (screenId, ir) => sha256Hex(canonical({ ir: hashStableIr(ir), screenId, structuralOnly: true }));
|
|
52
|
+
/**
|
|
53
|
+
* sha256 over the canonical snapshot identity: schema version + pinned Figma version + the
|
|
54
|
+
* per-screen hashes sorted by screenId. EXCLUDES `fetchedAt` so the hash is drift-stable across
|
|
55
|
+
* re-fetches of the same unchanged design.
|
|
56
|
+
*/
|
|
57
|
+
export const hashSnapshot = (snapshotSchemaVersion, version, perScreen) => {
|
|
58
|
+
const screens = [...perScreen]
|
|
59
|
+
.sort((a, b) => (a.screenId < b.screenId ? -1 : a.screenId > b.screenId ? 1 : 0))
|
|
60
|
+
.map((s) => ({ integrityHash: s.integrityHash, screenId: s.screenId }));
|
|
61
|
+
return sha256Hex(canonical({ screens, snapshotSchemaVersion, version: version ?? null }));
|
|
62
|
+
};
|
|
63
|
+
export const hashBytes = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { QualityIntelligenceFigma } from "@oscharko-dev/keiko-quality-intelligence";
|
|
2
|
+
import type { FigmaProvenance } from "./figmaConnector.js";
|
|
3
|
+
type ScreenIr = QualityIntelligenceFigma.ScreenIr;
|
|
4
|
+
/** The rendered image for one screen — raw bytes plus their content hash (tamper-evidence). */
|
|
5
|
+
export interface FigmaRenderedImage {
|
|
6
|
+
readonly mimeType: "image/png";
|
|
7
|
+
readonly bytes: Uint8Array;
|
|
8
|
+
readonly byteLength: number;
|
|
9
|
+
readonly sha256: string;
|
|
10
|
+
}
|
|
11
|
+
/** One assembled screen: the structural IR plus its render. */
|
|
12
|
+
export interface FigmaSnapshotScreen {
|
|
13
|
+
readonly screenId: string;
|
|
14
|
+
readonly ir: ScreenIr;
|
|
15
|
+
readonly image: FigmaRenderedImage;
|
|
16
|
+
/** sha256 over the canonical {screenId, ir, imageSha256} body — the per-screen drift identity. */
|
|
17
|
+
readonly integrityHash: string;
|
|
18
|
+
}
|
|
19
|
+
/** One screen whose structural IR is captured but no PNG side-file exists. */
|
|
20
|
+
export interface FigmaSnapshotStructuralScreen {
|
|
21
|
+
readonly screenId: string;
|
|
22
|
+
readonly ir: ScreenIr;
|
|
23
|
+
readonly reason: FigmaSkippedScreenReason;
|
|
24
|
+
/** sha256 over the canonical {screenId, ir, structuralOnly:true} body. */
|
|
25
|
+
readonly integrityHash: string;
|
|
26
|
+
}
|
|
27
|
+
/** Why a detected screen produced no render and was excluded from `screens` (partial-render).
|
|
28
|
+
* The `render-fetch-failed` member also appears as `render-fetch-failed:<CODE>` when the fetch
|
|
29
|
+
* threw a FigmaConnectorError — the code suffix lets metrics distinguish an egress
|
|
30
|
+
* misconfiguration (e.g. `FIGMA_EGRESS_TIMEOUT`) from an unclassified network flake.
|
|
31
|
+
*/
|
|
32
|
+
export type FigmaSkippedScreenReason = "render-url-missing" | "render-url-blocked" | "render-screen-cap-exceeded" | "render-fetch-failed" | `render-fetch-failed:${string}` | "render-empty" | "render-oversized";
|
|
33
|
+
export interface FigmaSkippedScreen {
|
|
34
|
+
readonly screenId: string;
|
|
35
|
+
readonly reason: FigmaSkippedScreenReason;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The immutable Figma Snapshot value. `integrityHash` is DETERMINISTIC for drift detection
|
|
39
|
+
* (#735): it is keyed on the pinned Figma `version` + the sorted per-screen identities, and
|
|
40
|
+
* deliberately EXCLUDES the wall-clock `fetchedAt` so two snapshots of the same unchanged design
|
|
41
|
+
* at different times hash identically. `provenance.fetchedAt` is retained for audit only.
|
|
42
|
+
*/
|
|
43
|
+
export interface FigmaSnapshot {
|
|
44
|
+
readonly snapshotSchemaVersion: 1;
|
|
45
|
+
readonly provenance: FigmaProvenance;
|
|
46
|
+
readonly screens: readonly FigmaSnapshotScreen[];
|
|
47
|
+
readonly skippedScreens: readonly FigmaSkippedScreen[];
|
|
48
|
+
/**
|
|
49
|
+
* Structural IR for screens that did not produce a PNG render. This keeps QI/code consumers from
|
|
50
|
+
* losing JSON evidence when render fan-out is capped or a single render fetch degrades. Optional
|
|
51
|
+
* for older snapshots/builders; new builds emit it for every skipped screen that has IR.
|
|
52
|
+
*/
|
|
53
|
+
readonly structuralScreens?: readonly FigmaSnapshotStructuralScreen[];
|
|
54
|
+
/**
|
|
55
|
+
* Raw inter-screen transitions carried from the Screen-IR (#752) for the navigation/flow graph
|
|
56
|
+
* (#811). OPTIONAL and additive — a constructor that omits it (e.g. an older builder or a test
|
|
57
|
+
* fixture) yields a valid snapshot, and the navigation derivation degrades to zero nav items. NOT
|
|
58
|
+
* part of `integrityHash` — non-identity design metadata, so it never affects drift (#735).
|
|
59
|
+
* Downstream persistence stores it on the snapshot record additively.
|
|
60
|
+
*/
|
|
61
|
+
readonly links?: readonly QualityIntelligenceFigma.InterScreenLink[];
|
|
62
|
+
readonly integrityHash: string;
|
|
63
|
+
}
|
|
64
|
+
export {};
|
|
65
|
+
//# sourceMappingURL=figmaSnapshotTypes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"figmaSnapshotTypes.d.ts","sourceRoot":"","sources":["../../../src/qualityIntelligence/figma/figmaSnapshotTypes.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,0CAA0C,CAAC;AACzF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE3D,KAAK,QAAQ,GAAG,wBAAwB,CAAC,QAAQ,CAAC;AAElD,+FAA+F;AAC/F,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,+DAA+D;AAC/D,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,kBAAkB,CAAC;IACnC,kGAAkG;IAClG,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC;AAED,8EAA8E;AAC9E,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,wBAAwB,CAAC;IAC1C,0EAA0E;IAC1E,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC;AAED;;;;GAIG;AACH,MAAM,MAAM,wBAAwB,GAChC,oBAAoB,GACpB,oBAAoB,GACpB,4BAA4B,GAC5B,qBAAqB,GACrB,uBAAuB,MAAM,EAAE,GAC/B,cAAc,GACd,kBAAkB,CAAC;AAEvB,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,wBAAwB,CAAC;CAC3C;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,qBAAqB,EAAE,CAAC,CAAC;IAClC,QAAQ,CAAC,UAAU,EAAE,eAAe,CAAC;IACrC,QAAQ,CAAC,OAAO,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACjD,QAAQ,CAAC,cAAc,EAAE,SAAS,kBAAkB,EAAE,CAAC;IACvD;;;;OAIG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,SAAS,6BAA6B,EAAE,CAAC;IACtE;;;;;;OAMG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,wBAAwB,CAAC,eAAe,EAAE,CAAC;IACrE,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Figma Snapshot value types (Epic #750, Issue #753).
|
|
2
|
+
//
|
|
3
|
+
// The Snapshot is the immutable, self-contained artifact assembled at the end of the bounded
|
|
4
|
+
// snapshot-build: per-screen IR (from #752) + the rendered PNG bytes + token-free provenance
|
|
5
|
+
// (from #751) + per-screen and snapshot integrity hashes. Once assembled it is the communication
|
|
6
|
+
// boundary — every downstream stage (#754 QI source, #755 codegen) reads ONLY this value and
|
|
7
|
+
// NEVER re-contacts Figma.
|
|
8
|
+
//
|
|
9
|
+
// These are the IN-MEMORY assembly types produced by the builder. The persisted, redacted,
|
|
10
|
+
// side-file-backed evidence shape lives in keiko-evidence (figmaSnapshot/schema.ts); the store
|
|
11
|
+
// strips the inline bytes onto disk and records a side-file ref. NO token is present on any of
|
|
12
|
+
// these types by construction.
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { FigmaConnectorError } from "./figmaConnectorErrors.js";
|
|
2
|
+
export interface FigmaTokenSources {
|
|
3
|
+
readonly vaultToken?: string | undefined;
|
|
4
|
+
readonly configToken?: string | undefined;
|
|
5
|
+
readonly envToken?: string | undefined;
|
|
6
|
+
}
|
|
7
|
+
export declare function resolveFigmaToken(sources: FigmaTokenSources): string;
|
|
8
|
+
export declare function classifyTokenFailure(status: number, reason?: string): FigmaConnectorError;
|
|
9
|
+
//# sourceMappingURL=figmaTokenSource.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"figmaTokenSource.d.ts","sourceRoot":"","sources":["../../../src/qualityIntelligence/figma/figmaTokenSource.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,mBAAmB,EAAgC,MAAM,2BAA2B,CAAC;AAE9F,MAAM,WAAW,iBAAiB;IAGhC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACxC;AAUD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAIpE;AA6BD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,mBAAmB,CAGzF"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Figma token-resolution seam + token-failure taxonomy (Epic #750, Issue #758).
|
|
2
|
+
//
|
|
3
|
+
// resolveFigmaToken is the single point the connector consults for the read-only PAT. Precedence is
|
|
4
|
+
// vault > config > env, so the encrypted vault entry (#758) takes priority while the
|
|
5
|
+
// FIGMA_ACCESS_TOKEN env var (#751) stays the dev default whenever no vault entry exists. The
|
|
6
|
+
// resolved token is returned for use at the transport boundary only; the resolver itself never logs
|
|
7
|
+
// it and never echoes any candidate value in the FIGMA_TOKEN_MISSING error.
|
|
8
|
+
//
|
|
9
|
+
// classifyTokenFailure maps a failing Figma/proxy HTTP response to a coded, user-actionable error.
|
|
10
|
+
// It is PURE and STRUCTURAL: it keys off the HTTP status plus a small set of generic Figma reason
|
|
11
|
+
// keywords (expired / revoked / scope), and defaults any unrecognised 403 to the safe
|
|
12
|
+
// FIGMA_TOKEN_INVALID rather than guessing. No board-, file-, or message-specific tuning.
|
|
13
|
+
import { FigmaConnectorError } from "./figmaConnectorErrors.js";
|
|
14
|
+
const firstNonEmpty = (...candidates) => {
|
|
15
|
+
for (const candidate of candidates) {
|
|
16
|
+
const trimmed = (candidate ?? "").trim();
|
|
17
|
+
if (trimmed.length > 0)
|
|
18
|
+
return trimmed;
|
|
19
|
+
}
|
|
20
|
+
return undefined;
|
|
21
|
+
};
|
|
22
|
+
export function resolveFigmaToken(sources) {
|
|
23
|
+
const token = firstNonEmpty(sources.vaultToken, sources.configToken, sources.envToken);
|
|
24
|
+
if (token === undefined)
|
|
25
|
+
throw new FigmaConnectorError("FIGMA_TOKEN_MISSING");
|
|
26
|
+
return token;
|
|
27
|
+
}
|
|
28
|
+
const includesAny = (haystack, needles) => needles.some((needle) => haystack.includes(needle));
|
|
29
|
+
// Structural reason buckets. Each is a small set of generic substrings that Figma uses across
|
|
30
|
+
// files; none is tied to a specific board, file, or customer-supplied message.
|
|
31
|
+
function classifyForbidden(reason) {
|
|
32
|
+
if (includesAny(reason, ["expired", "expire"]))
|
|
33
|
+
return "FIGMA_TOKEN_EXPIRED";
|
|
34
|
+
if (includesAny(reason, ["revoked", "revoke"]))
|
|
35
|
+
return "FIGMA_TOKEN_REVOKED";
|
|
36
|
+
if (includesAny(reason, ["scope", "permission", "not allowed", "forbidden access"])) {
|
|
37
|
+
return "FIGMA_INSUFFICIENT_SCOPE";
|
|
38
|
+
}
|
|
39
|
+
// Unknown 403 → safe default, never a guess.
|
|
40
|
+
return "FIGMA_TOKEN_INVALID";
|
|
41
|
+
}
|
|
42
|
+
function codeForStatus(status, reason) {
|
|
43
|
+
if (status === 401)
|
|
44
|
+
return "FIGMA_TOKEN_INVALID";
|
|
45
|
+
if (status === 403)
|
|
46
|
+
return classifyForbidden(reason);
|
|
47
|
+
if (status === 404)
|
|
48
|
+
return "FIGMA_NOT_FOUND";
|
|
49
|
+
// Forward-proxy auth is visible as 407. Other proxy failures arrive as OutboundHttpEgressError
|
|
50
|
+
// before this status classifier runs; raw upstream 502/504 responses must not be blamed on proxy
|
|
51
|
+
// egress because that sends operators to the wrong remediation path.
|
|
52
|
+
if (status === 407)
|
|
53
|
+
return "FIGMA_PROXY_EGRESS_FAILED";
|
|
54
|
+
if (status >= 500)
|
|
55
|
+
return "FIGMA_UPSTREAM_UNAVAILABLE";
|
|
56
|
+
return "FIGMA_INTERNAL";
|
|
57
|
+
}
|
|
58
|
+
export function classifyTokenFailure(status, reason) {
|
|
59
|
+
const normalised = (reason ?? "").toLowerCase();
|
|
60
|
+
return new FigmaConnectorError(codeForStatus(status, normalised));
|
|
61
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type FigmaVaultKeySource = "env" | "keychain" | "keyfile";
|
|
2
|
+
export interface ResolvedFigmaVaultKey {
|
|
3
|
+
readonly key: Buffer;
|
|
4
|
+
readonly source: FigmaVaultKeySource;
|
|
5
|
+
}
|
|
6
|
+
export type FigmaKeychainAccess = () => Buffer | undefined;
|
|
7
|
+
export interface FigmaTokenStore {
|
|
8
|
+
readonly store: (token: string) => void;
|
|
9
|
+
readonly read: () => string | undefined;
|
|
10
|
+
readonly revoke: () => void;
|
|
11
|
+
}
|
|
12
|
+
export interface FigmaTokenStoreDeps {
|
|
13
|
+
readonly key: Buffer;
|
|
14
|
+
readonly storePath: string;
|
|
15
|
+
}
|
|
16
|
+
export declare const NO_FIGMA_KEYCHAIN: FigmaKeychainAccess;
|
|
17
|
+
export declare function resolveFigmaVaultKey(env: Readonly<Record<string, string | undefined>>, vaultDir: string, keychainAccess?: FigmaKeychainAccess): ResolvedFigmaVaultKey;
|
|
18
|
+
export declare function createFigmaTokenStore(deps: FigmaTokenStoreDeps): FigmaTokenStore;
|
|
19
|
+
//# sourceMappingURL=figmaTokenStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"figmaTokenStore.d.ts","sourceRoot":"","sources":["../../../src/qualityIntelligence/figma/figmaTokenStore.ts"],"names":[],"mappings":"AAwCA,MAAM,MAAM,mBAAmB,GAAG,KAAK,GAAG,UAAU,GAAG,SAAS,CAAC;AAEjE,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAC;CACtC;AAID,MAAM,MAAM,mBAAmB,GAAG,MAAM,MAAM,GAAG,SAAS,CAAC;AAE3D,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAqGD,eAAO,MAAM,iBAAiB,EAAE,mBAAqC,CAAC;AAEtE,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,EACjD,QAAQ,EAAE,MAAM,EAChB,cAAc,GAAE,mBAAqC,GACpD,qBAAqB,CAMvB;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,mBAAmB,GAAG,eAAe,CA0BhF"}
|