provekit 0.1.0__py3-none-any.whl
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.
- provekit/__init__.py +0 -0
- provekit/cli.py +256 -0
- provekit/config.py +77 -0
- provekit/database.py +132 -0
- provekit/main.py +115 -0
- provekit/migrations/env.py +40 -0
- provekit/migrations/script.py.mako +22 -0
- provekit/migrations/versions/323eb73d463c_user_email_verified.py +32 -0
- provekit/migrations/versions/49e8ab812556_baseline_schema.py +241 -0
- provekit/migrations/versions/819ed5ff183e_workspace_ingest_key.py +35 -0
- provekit/migrations/versions/a1b2c3d4e5f6_user_token_version.py +27 -0
- provekit/models.py +191 -0
- provekit/observability.py +154 -0
- provekit/routers/__init__.py +0 -0
- provekit/routers/auth.py +150 -0
- provekit/routers/connections.py +270 -0
- provekit/routers/deployments.py +161 -0
- provekit/routers/flows.py +188 -0
- provekit/routers/library.py +265 -0
- provekit/routers/prompts.py +72 -0
- provekit/routers/run.py +203 -0
- provekit/routers/runtime.py +103 -0
- provekit/routers/traces.py +65 -0
- provekit/routers/usage.py +42 -0
- provekit/services/__init__.py +0 -0
- provekit/services/assertions.py +135 -0
- provekit/services/auth.py +131 -0
- provekit/services/deploy.py +63 -0
- provekit/services/dispatch.py +313 -0
- provekit/services/email.py +38 -0
- provekit/services/flow.py +299 -0
- provekit/services/limits.py +109 -0
- provekit/services/masking.py +45 -0
- provekit/services/netguard.py +69 -0
- provekit/services/otel.py +146 -0
- provekit/services/promptfoo.py +92 -0
- provekit/services/providers/__init__.py +0 -0
- provekit/services/providers/a2a_client.py +110 -0
- provekit/services/providers/agent_http.py +55 -0
- provekit/services/providers/llm.py +364 -0
- provekit/services/providers/mcp_client.py +289 -0
- provekit/services/runstore.py +93 -0
- provekit/services/sealing.py +106 -0
- provekit/services/templates.py +69 -0
- provekit/services/testfile.py +122 -0
- provekit/services/tooling.py +194 -0
- provekit/services/workspace.py +91 -0
- provekit/templates/flows/_manifest.json +2594 -0
- provekit/templates/flows/banking-action-items.yaml +43 -0
- provekit/templates/flows/banking-answer.yaml +43 -0
- provekit/templates/flows/banking-classify-intent.yaml +43 -0
- provekit/templates/flows/banking-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/banking-extract-to-json.yaml +43 -0
- provekit/templates/flows/banking-faq-match-answer.yaml +60 -0
- provekit/templates/flows/banking-keyword-extract.yaml +43 -0
- provekit/templates/flows/banking-language-route.yaml +103 -0
- provekit/templates/flows/banking-moderation-gate.yaml +73 -0
- provekit/templates/flows/banking-redact-summarize.yaml +59 -0
- provekit/templates/flows/banking-score-gate.yaml +73 -0
- provekit/templates/flows/banking-sentiment-route.yaml +73 -0
- provekit/templates/flows/banking-severity-tiers.yaml +103 -0
- provekit/templates/flows/banking-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/banking-tag-enrich.yaml +43 -0
- provekit/templates/flows/banking-tone-rewrite.yaml +43 -0
- provekit/templates/flows/banking-translate-reply.yaml +58 -0
- provekit/templates/flows/banking-triage-route.yaml +73 -0
- provekit/templates/flows/community-forum-action-items.yaml +43 -0
- provekit/templates/flows/community-forum-answer.yaml +43 -0
- provekit/templates/flows/community-forum-classify-intent.yaml +43 -0
- provekit/templates/flows/community-forum-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/community-forum-extract-to-json.yaml +43 -0
- provekit/templates/flows/community-forum-faq-match-answer.yaml +60 -0
- provekit/templates/flows/community-forum-keyword-extract.yaml +43 -0
- provekit/templates/flows/community-forum-language-route.yaml +103 -0
- provekit/templates/flows/community-forum-moderation-gate.yaml +73 -0
- provekit/templates/flows/community-forum-redact-summarize.yaml +59 -0
- provekit/templates/flows/community-forum-score-gate.yaml +73 -0
- provekit/templates/flows/community-forum-sentiment-route.yaml +73 -0
- provekit/templates/flows/community-forum-severity-tiers.yaml +103 -0
- provekit/templates/flows/community-forum-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/community-forum-tag-enrich.yaml +43 -0
- provekit/templates/flows/community-forum-tone-rewrite.yaml +43 -0
- provekit/templates/flows/community-forum-translate-reply.yaml +58 -0
- provekit/templates/flows/community-forum-triage-route.yaml +73 -0
- provekit/templates/flows/content-moderation-action-items.yaml +43 -0
- provekit/templates/flows/content-moderation-answer.yaml +43 -0
- provekit/templates/flows/content-moderation-classify-intent.yaml +43 -0
- provekit/templates/flows/content-moderation-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/content-moderation-extract-to-json.yaml +43 -0
- provekit/templates/flows/content-moderation-faq-match-answer.yaml +60 -0
- provekit/templates/flows/content-moderation-keyword-extract.yaml +43 -0
- provekit/templates/flows/content-moderation-language-route.yaml +103 -0
- provekit/templates/flows/content-moderation-moderation-gate.yaml +73 -0
- provekit/templates/flows/content-moderation-redact-summarize.yaml +59 -0
- provekit/templates/flows/content-moderation-score-gate.yaml +73 -0
- provekit/templates/flows/content-moderation-sentiment-route.yaml +73 -0
- provekit/templates/flows/content-moderation-severity-tiers.yaml +103 -0
- provekit/templates/flows/content-moderation-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/content-moderation-tag-enrich.yaml +43 -0
- provekit/templates/flows/content-moderation-tone-rewrite.yaml +43 -0
- provekit/templates/flows/content-moderation-translate-reply.yaml +58 -0
- provekit/templates/flows/content-moderation-triage-route.yaml +73 -0
- provekit/templates/flows/customer-support-action-items.yaml +43 -0
- provekit/templates/flows/customer-support-answer.yaml +43 -0
- provekit/templates/flows/customer-support-classify-intent.yaml +43 -0
- provekit/templates/flows/customer-support-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/customer-support-extract-to-json.yaml +43 -0
- provekit/templates/flows/customer-support-faq-match-answer.yaml +60 -0
- provekit/templates/flows/customer-support-keyword-extract.yaml +43 -0
- provekit/templates/flows/customer-support-language-route.yaml +103 -0
- provekit/templates/flows/customer-support-moderation-gate.yaml +73 -0
- provekit/templates/flows/customer-support-redact-summarize.yaml +59 -0
- provekit/templates/flows/customer-support-score-gate.yaml +73 -0
- provekit/templates/flows/customer-support-sentiment-route.yaml +73 -0
- provekit/templates/flows/customer-support-severity-tiers.yaml +103 -0
- provekit/templates/flows/customer-support-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/customer-support-tag-enrich.yaml +43 -0
- provekit/templates/flows/customer-support-tone-rewrite.yaml +43 -0
- provekit/templates/flows/customer-support-translate-reply.yaml +58 -0
- provekit/templates/flows/customer-support-triage-route.yaml +73 -0
- provekit/templates/flows/developer-tooling-action-items.yaml +43 -0
- provekit/templates/flows/developer-tooling-answer.yaml +43 -0
- provekit/templates/flows/developer-tooling-classify-intent.yaml +43 -0
- provekit/templates/flows/developer-tooling-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/developer-tooling-extract-to-json.yaml +43 -0
- provekit/templates/flows/developer-tooling-faq-match-answer.yaml +60 -0
- provekit/templates/flows/developer-tooling-keyword-extract.yaml +43 -0
- provekit/templates/flows/developer-tooling-language-route.yaml +103 -0
- provekit/templates/flows/developer-tooling-moderation-gate.yaml +73 -0
- provekit/templates/flows/developer-tooling-redact-summarize.yaml +59 -0
- provekit/templates/flows/developer-tooling-score-gate.yaml +73 -0
- provekit/templates/flows/developer-tooling-sentiment-route.yaml +73 -0
- provekit/templates/flows/developer-tooling-severity-tiers.yaml +103 -0
- provekit/templates/flows/developer-tooling-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/developer-tooling-tag-enrich.yaml +43 -0
- provekit/templates/flows/developer-tooling-tone-rewrite.yaml +43 -0
- provekit/templates/flows/developer-tooling-translate-reply.yaml +58 -0
- provekit/templates/flows/developer-tooling-triage-route.yaml +73 -0
- provekit/templates/flows/devrel-action-items.yaml +43 -0
- provekit/templates/flows/devrel-answer.yaml +43 -0
- provekit/templates/flows/devrel-classify-intent.yaml +43 -0
- provekit/templates/flows/devrel-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/devrel-extract-to-json.yaml +43 -0
- provekit/templates/flows/devrel-faq-match-answer.yaml +60 -0
- provekit/templates/flows/devrel-keyword-extract.yaml +43 -0
- provekit/templates/flows/devrel-language-route.yaml +103 -0
- provekit/templates/flows/devrel-moderation-gate.yaml +73 -0
- provekit/templates/flows/devrel-redact-summarize.yaml +59 -0
- provekit/templates/flows/devrel-score-gate.yaml +73 -0
- provekit/templates/flows/devrel-sentiment-route.yaml +73 -0
- provekit/templates/flows/devrel-severity-tiers.yaml +103 -0
- provekit/templates/flows/devrel-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/devrel-tag-enrich.yaml +43 -0
- provekit/templates/flows/devrel-tone-rewrite.yaml +43 -0
- provekit/templates/flows/devrel-translate-reply.yaml +58 -0
- provekit/templates/flows/devrel-triage-route.yaml +73 -0
- provekit/templates/flows/e-commerce-action-items.yaml +43 -0
- provekit/templates/flows/e-commerce-answer.yaml +43 -0
- provekit/templates/flows/e-commerce-classify-intent.yaml +43 -0
- provekit/templates/flows/e-commerce-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/e-commerce-extract-to-json.yaml +43 -0
- provekit/templates/flows/e-commerce-faq-match-answer.yaml +60 -0
- provekit/templates/flows/e-commerce-keyword-extract.yaml +43 -0
- provekit/templates/flows/e-commerce-language-route.yaml +103 -0
- provekit/templates/flows/e-commerce-moderation-gate.yaml +73 -0
- provekit/templates/flows/e-commerce-redact-summarize.yaml +59 -0
- provekit/templates/flows/e-commerce-score-gate.yaml +73 -0
- provekit/templates/flows/e-commerce-sentiment-route.yaml +73 -0
- provekit/templates/flows/e-commerce-severity-tiers.yaml +103 -0
- provekit/templates/flows/e-commerce-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/e-commerce-tag-enrich.yaml +43 -0
- provekit/templates/flows/e-commerce-tone-rewrite.yaml +43 -0
- provekit/templates/flows/e-commerce-translate-reply.yaml +58 -0
- provekit/templates/flows/e-commerce-triage-route.yaml +73 -0
- provekit/templates/flows/education-action-items.yaml +43 -0
- provekit/templates/flows/education-answer.yaml +43 -0
- provekit/templates/flows/education-classify-intent.yaml +43 -0
- provekit/templates/flows/education-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/education-extract-to-json.yaml +43 -0
- provekit/templates/flows/education-faq-match-answer.yaml +60 -0
- provekit/templates/flows/education-keyword-extract.yaml +43 -0
- provekit/templates/flows/education-language-route.yaml +103 -0
- provekit/templates/flows/education-moderation-gate.yaml +73 -0
- provekit/templates/flows/education-redact-summarize.yaml +59 -0
- provekit/templates/flows/education-score-gate.yaml +73 -0
- provekit/templates/flows/education-sentiment-route.yaml +73 -0
- provekit/templates/flows/education-severity-tiers.yaml +103 -0
- provekit/templates/flows/education-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/education-tag-enrich.yaml +43 -0
- provekit/templates/flows/education-tone-rewrite.yaml +43 -0
- provekit/templates/flows/education-translate-reply.yaml +58 -0
- provekit/templates/flows/education-triage-route.yaml +73 -0
- provekit/templates/flows/finance-action-items.yaml +43 -0
- provekit/templates/flows/finance-answer.yaml +43 -0
- provekit/templates/flows/finance-classify-intent.yaml +43 -0
- provekit/templates/flows/finance-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/finance-extract-to-json.yaml +43 -0
- provekit/templates/flows/finance-faq-match-answer.yaml +60 -0
- provekit/templates/flows/finance-keyword-extract.yaml +43 -0
- provekit/templates/flows/finance-language-route.yaml +103 -0
- provekit/templates/flows/finance-moderation-gate.yaml +73 -0
- provekit/templates/flows/finance-redact-summarize.yaml +59 -0
- provekit/templates/flows/finance-score-gate.yaml +73 -0
- provekit/templates/flows/finance-sentiment-route.yaml +73 -0
- provekit/templates/flows/finance-severity-tiers.yaml +103 -0
- provekit/templates/flows/finance-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/finance-tag-enrich.yaml +43 -0
- provekit/templates/flows/finance-tone-rewrite.yaml +43 -0
- provekit/templates/flows/finance-translate-reply.yaml +58 -0
- provekit/templates/flows/finance-triage-route.yaml +73 -0
- provekit/templates/flows/food-delivery-action-items.yaml +43 -0
- provekit/templates/flows/food-delivery-answer.yaml +43 -0
- provekit/templates/flows/food-delivery-classify-intent.yaml +43 -0
- provekit/templates/flows/food-delivery-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/food-delivery-extract-to-json.yaml +43 -0
- provekit/templates/flows/food-delivery-faq-match-answer.yaml +60 -0
- provekit/templates/flows/food-delivery-keyword-extract.yaml +43 -0
- provekit/templates/flows/food-delivery-language-route.yaml +103 -0
- provekit/templates/flows/food-delivery-moderation-gate.yaml +73 -0
- provekit/templates/flows/food-delivery-redact-summarize.yaml +59 -0
- provekit/templates/flows/food-delivery-score-gate.yaml +73 -0
- provekit/templates/flows/food-delivery-sentiment-route.yaml +73 -0
- provekit/templates/flows/food-delivery-severity-tiers.yaml +103 -0
- provekit/templates/flows/food-delivery-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/food-delivery-tag-enrich.yaml +43 -0
- provekit/templates/flows/food-delivery-tone-rewrite.yaml +43 -0
- provekit/templates/flows/food-delivery-translate-reply.yaml +58 -0
- provekit/templates/flows/food-delivery-triage-route.yaml +73 -0
- provekit/templates/flows/gaming-support-action-items.yaml +43 -0
- provekit/templates/flows/gaming-support-answer.yaml +43 -0
- provekit/templates/flows/gaming-support-classify-intent.yaml +43 -0
- provekit/templates/flows/gaming-support-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/gaming-support-extract-to-json.yaml +43 -0
- provekit/templates/flows/gaming-support-faq-match-answer.yaml +60 -0
- provekit/templates/flows/gaming-support-keyword-extract.yaml +43 -0
- provekit/templates/flows/gaming-support-language-route.yaml +103 -0
- provekit/templates/flows/gaming-support-moderation-gate.yaml +73 -0
- provekit/templates/flows/gaming-support-redact-summarize.yaml +59 -0
- provekit/templates/flows/gaming-support-score-gate.yaml +73 -0
- provekit/templates/flows/gaming-support-sentiment-route.yaml +73 -0
- provekit/templates/flows/gaming-support-severity-tiers.yaml +103 -0
- provekit/templates/flows/gaming-support-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/gaming-support-tag-enrich.yaml +43 -0
- provekit/templates/flows/gaming-support-tone-rewrite.yaml +43 -0
- provekit/templates/flows/gaming-support-translate-reply.yaml +58 -0
- provekit/templates/flows/gaming-support-triage-route.yaml +73 -0
- provekit/templates/flows/healthcare-intake-action-items.yaml +43 -0
- provekit/templates/flows/healthcare-intake-answer.yaml +43 -0
- provekit/templates/flows/healthcare-intake-classify-intent.yaml +43 -0
- provekit/templates/flows/healthcare-intake-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/healthcare-intake-extract-to-json.yaml +43 -0
- provekit/templates/flows/healthcare-intake-faq-match-answer.yaml +60 -0
- provekit/templates/flows/healthcare-intake-keyword-extract.yaml +43 -0
- provekit/templates/flows/healthcare-intake-language-route.yaml +103 -0
- provekit/templates/flows/healthcare-intake-moderation-gate.yaml +73 -0
- provekit/templates/flows/healthcare-intake-redact-summarize.yaml +59 -0
- provekit/templates/flows/healthcare-intake-score-gate.yaml +73 -0
- provekit/templates/flows/healthcare-intake-sentiment-route.yaml +73 -0
- provekit/templates/flows/healthcare-intake-severity-tiers.yaml +103 -0
- provekit/templates/flows/healthcare-intake-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/healthcare-intake-tag-enrich.yaml +43 -0
- provekit/templates/flows/healthcare-intake-tone-rewrite.yaml +43 -0
- provekit/templates/flows/healthcare-intake-translate-reply.yaml +58 -0
- provekit/templates/flows/healthcare-intake-triage-route.yaml +73 -0
- provekit/templates/flows/hr-action-items.yaml +43 -0
- provekit/templates/flows/hr-answer.yaml +43 -0
- provekit/templates/flows/hr-classify-intent.yaml +43 -0
- provekit/templates/flows/hr-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/hr-extract-to-json.yaml +43 -0
- provekit/templates/flows/hr-faq-match-answer.yaml +60 -0
- provekit/templates/flows/hr-keyword-extract.yaml +43 -0
- provekit/templates/flows/hr-language-route.yaml +103 -0
- provekit/templates/flows/hr-moderation-gate.yaml +73 -0
- provekit/templates/flows/hr-redact-summarize.yaml +59 -0
- provekit/templates/flows/hr-score-gate.yaml +73 -0
- provekit/templates/flows/hr-sentiment-route.yaml +73 -0
- provekit/templates/flows/hr-severity-tiers.yaml +103 -0
- provekit/templates/flows/hr-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/hr-tag-enrich.yaml +43 -0
- provekit/templates/flows/hr-tone-rewrite.yaml +43 -0
- provekit/templates/flows/hr-translate-reply.yaml +58 -0
- provekit/templates/flows/hr-triage-route.yaml +73 -0
- provekit/templates/flows/insurance-action-items.yaml +43 -0
- provekit/templates/flows/insurance-answer.yaml +43 -0
- provekit/templates/flows/insurance-classify-intent.yaml +43 -0
- provekit/templates/flows/insurance-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/insurance-extract-to-json.yaml +43 -0
- provekit/templates/flows/insurance-faq-match-answer.yaml +60 -0
- provekit/templates/flows/insurance-keyword-extract.yaml +43 -0
- provekit/templates/flows/insurance-language-route.yaml +103 -0
- provekit/templates/flows/insurance-moderation-gate.yaml +73 -0
- provekit/templates/flows/insurance-redact-summarize.yaml +59 -0
- provekit/templates/flows/insurance-score-gate.yaml +73 -0
- provekit/templates/flows/insurance-sentiment-route.yaml +73 -0
- provekit/templates/flows/insurance-severity-tiers.yaml +103 -0
- provekit/templates/flows/insurance-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/insurance-tag-enrich.yaml +43 -0
- provekit/templates/flows/insurance-tone-rewrite.yaml +43 -0
- provekit/templates/flows/insurance-translate-reply.yaml +58 -0
- provekit/templates/flows/insurance-triage-route.yaml +73 -0
- provekit/templates/flows/it-helpdesk-action-items.yaml +43 -0
- provekit/templates/flows/it-helpdesk-answer.yaml +43 -0
- provekit/templates/flows/it-helpdesk-classify-intent.yaml +43 -0
- provekit/templates/flows/it-helpdesk-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/it-helpdesk-extract-to-json.yaml +43 -0
- provekit/templates/flows/it-helpdesk-faq-match-answer.yaml +60 -0
- provekit/templates/flows/it-helpdesk-keyword-extract.yaml +43 -0
- provekit/templates/flows/it-helpdesk-language-route.yaml +103 -0
- provekit/templates/flows/it-helpdesk-moderation-gate.yaml +73 -0
- provekit/templates/flows/it-helpdesk-redact-summarize.yaml +59 -0
- provekit/templates/flows/it-helpdesk-score-gate.yaml +73 -0
- provekit/templates/flows/it-helpdesk-sentiment-route.yaml +73 -0
- provekit/templates/flows/it-helpdesk-severity-tiers.yaml +103 -0
- provekit/templates/flows/it-helpdesk-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/it-helpdesk-tag-enrich.yaml +43 -0
- provekit/templates/flows/it-helpdesk-tone-rewrite.yaml +43 -0
- provekit/templates/flows/it-helpdesk-translate-reply.yaml +58 -0
- provekit/templates/flows/it-helpdesk-triage-route.yaml +73 -0
- provekit/templates/flows/legal-intake-action-items.yaml +43 -0
- provekit/templates/flows/legal-intake-answer.yaml +43 -0
- provekit/templates/flows/legal-intake-classify-intent.yaml +43 -0
- provekit/templates/flows/legal-intake-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/legal-intake-extract-to-json.yaml +43 -0
- provekit/templates/flows/legal-intake-faq-match-answer.yaml +60 -0
- provekit/templates/flows/legal-intake-keyword-extract.yaml +43 -0
- provekit/templates/flows/legal-intake-language-route.yaml +103 -0
- provekit/templates/flows/legal-intake-moderation-gate.yaml +73 -0
- provekit/templates/flows/legal-intake-redact-summarize.yaml +59 -0
- provekit/templates/flows/legal-intake-score-gate.yaml +73 -0
- provekit/templates/flows/legal-intake-sentiment-route.yaml +73 -0
- provekit/templates/flows/legal-intake-severity-tiers.yaml +103 -0
- provekit/templates/flows/legal-intake-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/legal-intake-tag-enrich.yaml +43 -0
- provekit/templates/flows/legal-intake-tone-rewrite.yaml +43 -0
- provekit/templates/flows/legal-intake-translate-reply.yaml +58 -0
- provekit/templates/flows/legal-intake-triage-route.yaml +73 -0
- provekit/templates/flows/logistics-action-items.yaml +43 -0
- provekit/templates/flows/logistics-answer.yaml +43 -0
- provekit/templates/flows/logistics-classify-intent.yaml +43 -0
- provekit/templates/flows/logistics-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/logistics-extract-to-json.yaml +43 -0
- provekit/templates/flows/logistics-faq-match-answer.yaml +60 -0
- provekit/templates/flows/logistics-keyword-extract.yaml +43 -0
- provekit/templates/flows/logistics-language-route.yaml +103 -0
- provekit/templates/flows/logistics-moderation-gate.yaml +73 -0
- provekit/templates/flows/logistics-redact-summarize.yaml +59 -0
- provekit/templates/flows/logistics-score-gate.yaml +73 -0
- provekit/templates/flows/logistics-sentiment-route.yaml +73 -0
- provekit/templates/flows/logistics-severity-tiers.yaml +103 -0
- provekit/templates/flows/logistics-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/logistics-tag-enrich.yaml +43 -0
- provekit/templates/flows/logistics-tone-rewrite.yaml +43 -0
- provekit/templates/flows/logistics-translate-reply.yaml +58 -0
- provekit/templates/flows/logistics-triage-route.yaml +73 -0
- provekit/templates/flows/marketing-action-items.yaml +43 -0
- provekit/templates/flows/marketing-answer.yaml +43 -0
- provekit/templates/flows/marketing-classify-intent.yaml +43 -0
- provekit/templates/flows/marketing-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/marketing-extract-to-json.yaml +43 -0
- provekit/templates/flows/marketing-faq-match-answer.yaml +60 -0
- provekit/templates/flows/marketing-keyword-extract.yaml +43 -0
- provekit/templates/flows/marketing-language-route.yaml +103 -0
- provekit/templates/flows/marketing-moderation-gate.yaml +73 -0
- provekit/templates/flows/marketing-redact-summarize.yaml +59 -0
- provekit/templates/flows/marketing-score-gate.yaml +73 -0
- provekit/templates/flows/marketing-sentiment-route.yaml +73 -0
- provekit/templates/flows/marketing-severity-tiers.yaml +103 -0
- provekit/templates/flows/marketing-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/marketing-tag-enrich.yaml +43 -0
- provekit/templates/flows/marketing-tone-rewrite.yaml +43 -0
- provekit/templates/flows/marketing-translate-reply.yaml +58 -0
- provekit/templates/flows/marketing-triage-route.yaml +73 -0
- provekit/templates/flows/nonprofit-action-items.yaml +43 -0
- provekit/templates/flows/nonprofit-answer.yaml +43 -0
- provekit/templates/flows/nonprofit-classify-intent.yaml +43 -0
- provekit/templates/flows/nonprofit-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/nonprofit-extract-to-json.yaml +43 -0
- provekit/templates/flows/nonprofit-faq-match-answer.yaml +60 -0
- provekit/templates/flows/nonprofit-keyword-extract.yaml +43 -0
- provekit/templates/flows/nonprofit-language-route.yaml +103 -0
- provekit/templates/flows/nonprofit-moderation-gate.yaml +73 -0
- provekit/templates/flows/nonprofit-redact-summarize.yaml +59 -0
- provekit/templates/flows/nonprofit-score-gate.yaml +73 -0
- provekit/templates/flows/nonprofit-sentiment-route.yaml +73 -0
- provekit/templates/flows/nonprofit-severity-tiers.yaml +103 -0
- provekit/templates/flows/nonprofit-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/nonprofit-tag-enrich.yaml +43 -0
- provekit/templates/flows/nonprofit-tone-rewrite.yaml +43 -0
- provekit/templates/flows/nonprofit-translate-reply.yaml +58 -0
- provekit/templates/flows/nonprofit-triage-route.yaml +73 -0
- provekit/templates/flows/real-estate-action-items.yaml +43 -0
- provekit/templates/flows/real-estate-answer.yaml +43 -0
- provekit/templates/flows/real-estate-classify-intent.yaml +43 -0
- provekit/templates/flows/real-estate-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/real-estate-extract-to-json.yaml +43 -0
- provekit/templates/flows/real-estate-faq-match-answer.yaml +60 -0
- provekit/templates/flows/real-estate-keyword-extract.yaml +43 -0
- provekit/templates/flows/real-estate-language-route.yaml +103 -0
- provekit/templates/flows/real-estate-moderation-gate.yaml +73 -0
- provekit/templates/flows/real-estate-redact-summarize.yaml +59 -0
- provekit/templates/flows/real-estate-score-gate.yaml +73 -0
- provekit/templates/flows/real-estate-sentiment-route.yaml +73 -0
- provekit/templates/flows/real-estate-severity-tiers.yaml +103 -0
- provekit/templates/flows/real-estate-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/real-estate-tag-enrich.yaml +43 -0
- provekit/templates/flows/real-estate-tone-rewrite.yaml +43 -0
- provekit/templates/flows/real-estate-translate-reply.yaml +58 -0
- provekit/templates/flows/real-estate-triage-route.yaml +73 -0
- provekit/templates/flows/recruiting-action-items.yaml +43 -0
- provekit/templates/flows/recruiting-answer.yaml +43 -0
- provekit/templates/flows/recruiting-classify-intent.yaml +43 -0
- provekit/templates/flows/recruiting-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/recruiting-extract-to-json.yaml +43 -0
- provekit/templates/flows/recruiting-faq-match-answer.yaml +60 -0
- provekit/templates/flows/recruiting-keyword-extract.yaml +43 -0
- provekit/templates/flows/recruiting-language-route.yaml +103 -0
- provekit/templates/flows/recruiting-moderation-gate.yaml +73 -0
- provekit/templates/flows/recruiting-redact-summarize.yaml +59 -0
- provekit/templates/flows/recruiting-score-gate.yaml +73 -0
- provekit/templates/flows/recruiting-sentiment-route.yaml +73 -0
- provekit/templates/flows/recruiting-severity-tiers.yaml +103 -0
- provekit/templates/flows/recruiting-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/recruiting-tag-enrich.yaml +43 -0
- provekit/templates/flows/recruiting-tone-rewrite.yaml +43 -0
- provekit/templates/flows/recruiting-translate-reply.yaml +58 -0
- provekit/templates/flows/recruiting-triage-route.yaml +73 -0
- provekit/templates/flows/research-action-items.yaml +43 -0
- provekit/templates/flows/research-answer.yaml +43 -0
- provekit/templates/flows/research-classify-intent.yaml +43 -0
- provekit/templates/flows/research-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/research-extract-to-json.yaml +43 -0
- provekit/templates/flows/research-faq-match-answer.yaml +60 -0
- provekit/templates/flows/research-keyword-extract.yaml +43 -0
- provekit/templates/flows/research-language-route.yaml +103 -0
- provekit/templates/flows/research-moderation-gate.yaml +73 -0
- provekit/templates/flows/research-redact-summarize.yaml +59 -0
- provekit/templates/flows/research-score-gate.yaml +73 -0
- provekit/templates/flows/research-sentiment-route.yaml +73 -0
- provekit/templates/flows/research-severity-tiers.yaml +103 -0
- provekit/templates/flows/research-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/research-tag-enrich.yaml +43 -0
- provekit/templates/flows/research-tone-rewrite.yaml +43 -0
- provekit/templates/flows/research-translate-reply.yaml +58 -0
- provekit/templates/flows/research-triage-route.yaml +73 -0
- provekit/templates/flows/sales-lead-action-items.yaml +43 -0
- provekit/templates/flows/sales-lead-answer.yaml +43 -0
- provekit/templates/flows/sales-lead-classify-intent.yaml +43 -0
- provekit/templates/flows/sales-lead-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/sales-lead-extract-to-json.yaml +43 -0
- provekit/templates/flows/sales-lead-faq-match-answer.yaml +60 -0
- provekit/templates/flows/sales-lead-keyword-extract.yaml +43 -0
- provekit/templates/flows/sales-lead-language-route.yaml +103 -0
- provekit/templates/flows/sales-lead-moderation-gate.yaml +73 -0
- provekit/templates/flows/sales-lead-redact-summarize.yaml +59 -0
- provekit/templates/flows/sales-lead-score-gate.yaml +73 -0
- provekit/templates/flows/sales-lead-sentiment-route.yaml +73 -0
- provekit/templates/flows/sales-lead-severity-tiers.yaml +103 -0
- provekit/templates/flows/sales-lead-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/sales-lead-tag-enrich.yaml +43 -0
- provekit/templates/flows/sales-lead-tone-rewrite.yaml +43 -0
- provekit/templates/flows/sales-lead-translate-reply.yaml +58 -0
- provekit/templates/flows/sales-lead-triage-route.yaml +73 -0
- provekit/templates/flows/travel-action-items.yaml +43 -0
- provekit/templates/flows/travel-answer.yaml +43 -0
- provekit/templates/flows/travel-classify-intent.yaml +43 -0
- provekit/templates/flows/travel-draft-critique-revise.yaml +75 -0
- provekit/templates/flows/travel-extract-to-json.yaml +43 -0
- provekit/templates/flows/travel-faq-match-answer.yaml +60 -0
- provekit/templates/flows/travel-keyword-extract.yaml +43 -0
- provekit/templates/flows/travel-language-route.yaml +103 -0
- provekit/templates/flows/travel-moderation-gate.yaml +73 -0
- provekit/templates/flows/travel-redact-summarize.yaml +59 -0
- provekit/templates/flows/travel-score-gate.yaml +73 -0
- provekit/templates/flows/travel-sentiment-route.yaml +73 -0
- provekit/templates/flows/travel-severity-tiers.yaml +103 -0
- provekit/templates/flows/travel-summarize-then-decide.yaml +88 -0
- provekit/templates/flows/travel-tag-enrich.yaml +43 -0
- provekit/templates/flows/travel-tone-rewrite.yaml +43 -0
- provekit/templates/flows/travel-translate-reply.yaml +58 -0
- provekit/templates/flows/travel-triage-route.yaml +73 -0
- provekit-0.1.0.dist-info/METADATA +86 -0
- provekit-0.1.0.dist-info/RECORD +485 -0
- provekit-0.1.0.dist-info/WHEEL +5 -0
- provekit-0.1.0.dist-info/entry_points.txt +2 -0
- provekit-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"""baseline schema
|
|
2
|
+
|
|
3
|
+
Revision ID: 49e8ab812556
|
|
4
|
+
Revises:
|
|
5
|
+
Create Date: 2026-07-16 23:49:32.276669
|
|
6
|
+
"""
|
|
7
|
+
from alembic import op
|
|
8
|
+
import sqlalchemy as sa
|
|
9
|
+
import provekit.models
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
revision = '49e8ab812556'
|
|
13
|
+
down_revision = None
|
|
14
|
+
branch_labels = None
|
|
15
|
+
depends_on = None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def upgrade() -> None:
|
|
19
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
|
20
|
+
op.create_table('users',
|
|
21
|
+
sa.Column('id', sa.Integer(), nullable=False),
|
|
22
|
+
sa.Column('email', sa.String(length=255), nullable=False),
|
|
23
|
+
sa.Column('name', sa.String(length=160), nullable=False),
|
|
24
|
+
sa.Column('password_hash', sa.String(length=255), nullable=False),
|
|
25
|
+
sa.Column('auth_provider', sa.String(length=32), nullable=False),
|
|
26
|
+
sa.Column('created_at', sa.DateTime(), nullable=False),
|
|
27
|
+
sa.PrimaryKeyConstraint('id', name=op.f('pk_users')),
|
|
28
|
+
sa.UniqueConstraint('email', name=op.f('uq_users_email'))
|
|
29
|
+
)
|
|
30
|
+
op.create_table('workspaces',
|
|
31
|
+
sa.Column('id', sa.Integer(), nullable=False),
|
|
32
|
+
sa.Column('name', sa.String(length=160), nullable=False),
|
|
33
|
+
sa.Column('owner_user_id', sa.Integer(), nullable=False),
|
|
34
|
+
sa.Column('created_at', sa.DateTime(), nullable=False),
|
|
35
|
+
sa.ForeignKeyConstraint(['owner_user_id'], ['users.id'], name=op.f('fk_workspaces_owner_user_id_users')),
|
|
36
|
+
sa.PrimaryKeyConstraint('id', name=op.f('pk_workspaces'))
|
|
37
|
+
)
|
|
38
|
+
with op.batch_alter_table('workspaces', schema=None) as batch_op:
|
|
39
|
+
batch_op.create_index(batch_op.f('ix_workspaces_owner_user_id'), ['owner_user_id'], unique=False)
|
|
40
|
+
|
|
41
|
+
op.create_table('collections',
|
|
42
|
+
sa.Column('id', sa.Integer(), nullable=False),
|
|
43
|
+
sa.Column('workspace_id', sa.Integer(), nullable=True),
|
|
44
|
+
sa.Column('name', sa.String(length=120), nullable=False),
|
|
45
|
+
sa.Column('created_at', sa.DateTime(), nullable=False),
|
|
46
|
+
sa.ForeignKeyConstraint(['workspace_id'], ['workspaces.id'], name=op.f('fk_collections_workspace_id_workspaces')),
|
|
47
|
+
sa.PrimaryKeyConstraint('id', name=op.f('pk_collections'))
|
|
48
|
+
)
|
|
49
|
+
with op.batch_alter_table('collections', schema=None) as batch_op:
|
|
50
|
+
batch_op.create_index(batch_op.f('ix_collections_workspace_id'), ['workspace_id'], unique=False)
|
|
51
|
+
|
|
52
|
+
op.create_table('connections',
|
|
53
|
+
sa.Column('id', sa.Integer(), nullable=False),
|
|
54
|
+
sa.Column('workspace_id', sa.Integer(), nullable=True),
|
|
55
|
+
sa.Column('name', sa.String(length=120), nullable=False),
|
|
56
|
+
sa.Column('kind', sa.String(length=16), nullable=False),
|
|
57
|
+
sa.Column('config', provekit.models.SealedJSON(), nullable=False),
|
|
58
|
+
sa.Column('created_at', sa.DateTime(), nullable=False),
|
|
59
|
+
sa.ForeignKeyConstraint(['workspace_id'], ['workspaces.id'], name=op.f('fk_connections_workspace_id_workspaces')),
|
|
60
|
+
sa.PrimaryKeyConstraint('id', name=op.f('pk_connections'))
|
|
61
|
+
)
|
|
62
|
+
with op.batch_alter_table('connections', schema=None) as batch_op:
|
|
63
|
+
batch_op.create_index(batch_op.f('ix_connections_workspace_id'), ['workspace_id'], unique=False)
|
|
64
|
+
|
|
65
|
+
op.create_table('datasets',
|
|
66
|
+
sa.Column('id', sa.Integer(), nullable=False),
|
|
67
|
+
sa.Column('workspace_id', sa.Integer(), nullable=True),
|
|
68
|
+
sa.Column('name', sa.String(length=160), nullable=False),
|
|
69
|
+
sa.Column('rows', sa.JSON(), nullable=False),
|
|
70
|
+
sa.Column('created_at', sa.DateTime(), nullable=False),
|
|
71
|
+
sa.ForeignKeyConstraint(['workspace_id'], ['workspaces.id'], name=op.f('fk_datasets_workspace_id_workspaces')),
|
|
72
|
+
sa.PrimaryKeyConstraint('id', name=op.f('pk_datasets'))
|
|
73
|
+
)
|
|
74
|
+
with op.batch_alter_table('datasets', schema=None) as batch_op:
|
|
75
|
+
batch_op.create_index(batch_op.f('ix_datasets_workspace_id'), ['workspace_id'], unique=False)
|
|
76
|
+
|
|
77
|
+
op.create_table('environments',
|
|
78
|
+
sa.Column('id', sa.Integer(), nullable=False),
|
|
79
|
+
sa.Column('workspace_id', sa.Integer(), nullable=True),
|
|
80
|
+
sa.Column('name', sa.String(length=120), nullable=False),
|
|
81
|
+
sa.Column('variables', sa.JSON(), nullable=False),
|
|
82
|
+
sa.Column('is_active', sa.Boolean(), nullable=False),
|
|
83
|
+
sa.ForeignKeyConstraint(['workspace_id'], ['workspaces.id'], name=op.f('fk_environments_workspace_id_workspaces')),
|
|
84
|
+
sa.PrimaryKeyConstraint('id', name=op.f('pk_environments'))
|
|
85
|
+
)
|
|
86
|
+
with op.batch_alter_table('environments', schema=None) as batch_op:
|
|
87
|
+
batch_op.create_index(batch_op.f('ix_environments_workspace_id'), ['workspace_id'], unique=False)
|
|
88
|
+
|
|
89
|
+
op.create_table('flows',
|
|
90
|
+
sa.Column('id', sa.Integer(), nullable=False),
|
|
91
|
+
sa.Column('workspace_id', sa.Integer(), nullable=True),
|
|
92
|
+
sa.Column('name', sa.String(length=160), nullable=False),
|
|
93
|
+
sa.Column('description', sa.Text(), nullable=False),
|
|
94
|
+
sa.Column('nodes', sa.JSON(), nullable=False),
|
|
95
|
+
sa.Column('edges', sa.JSON(), nullable=False),
|
|
96
|
+
sa.Column('updated_at', sa.DateTime(), nullable=False),
|
|
97
|
+
sa.ForeignKeyConstraint(['workspace_id'], ['workspaces.id'], name=op.f('fk_flows_workspace_id_workspaces')),
|
|
98
|
+
sa.PrimaryKeyConstraint('id', name=op.f('pk_flows'))
|
|
99
|
+
)
|
|
100
|
+
with op.batch_alter_table('flows', schema=None) as batch_op:
|
|
101
|
+
batch_op.create_index(batch_op.f('ix_flows_workspace_id'), ['workspace_id'], unique=False)
|
|
102
|
+
|
|
103
|
+
op.create_table('prompts',
|
|
104
|
+
sa.Column('id', sa.Integer(), nullable=False),
|
|
105
|
+
sa.Column('workspace_id', sa.Integer(), nullable=True),
|
|
106
|
+
sa.Column('key', sa.String(length=80), nullable=False),
|
|
107
|
+
sa.Column('name', sa.String(length=160), nullable=False),
|
|
108
|
+
sa.Column('description', sa.Text(), nullable=False),
|
|
109
|
+
sa.Column('content', sa.Text(), nullable=False),
|
|
110
|
+
sa.Column('updated_at', sa.DateTime(), nullable=False),
|
|
111
|
+
sa.ForeignKeyConstraint(['workspace_id'], ['workspaces.id'], name=op.f('fk_prompts_workspace_id_workspaces')),
|
|
112
|
+
sa.PrimaryKeyConstraint('id', name=op.f('pk_prompts')),
|
|
113
|
+
sa.UniqueConstraint('workspace_id', 'key', name='uq_prompt_key')
|
|
114
|
+
)
|
|
115
|
+
with op.batch_alter_table('prompts', schema=None) as batch_op:
|
|
116
|
+
batch_op.create_index(batch_op.f('ix_prompts_workspace_id'), ['workspace_id'], unique=False)
|
|
117
|
+
|
|
118
|
+
op.create_table('workspace_members',
|
|
119
|
+
sa.Column('id', sa.Integer(), nullable=False),
|
|
120
|
+
sa.Column('workspace_id', sa.Integer(), nullable=False),
|
|
121
|
+
sa.Column('user_id', sa.Integer(), nullable=False),
|
|
122
|
+
sa.Column('role', sa.String(length=16), nullable=False),
|
|
123
|
+
sa.ForeignKeyConstraint(['user_id'], ['users.id'], name=op.f('fk_workspace_members_user_id_users')),
|
|
124
|
+
sa.ForeignKeyConstraint(['workspace_id'], ['workspaces.id'], name=op.f('fk_workspace_members_workspace_id_workspaces')),
|
|
125
|
+
sa.PrimaryKeyConstraint('id', name=op.f('pk_workspace_members')),
|
|
126
|
+
sa.UniqueConstraint('workspace_id', 'user_id', name='uq_member')
|
|
127
|
+
)
|
|
128
|
+
with op.batch_alter_table('workspace_members', schema=None) as batch_op:
|
|
129
|
+
batch_op.create_index(batch_op.f('ix_workspace_members_user_id'), ['user_id'], unique=False)
|
|
130
|
+
batch_op.create_index(batch_op.f('ix_workspace_members_workspace_id'), ['workspace_id'], unique=False)
|
|
131
|
+
|
|
132
|
+
op.create_table('deployments',
|
|
133
|
+
sa.Column('id', sa.Integer(), nullable=False),
|
|
134
|
+
sa.Column('workspace_id', sa.Integer(), nullable=True),
|
|
135
|
+
sa.Column('flow_id', sa.Integer(), nullable=True),
|
|
136
|
+
sa.Column('slug', sa.String(length=80), nullable=False),
|
|
137
|
+
sa.Column('version', sa.Integer(), nullable=False),
|
|
138
|
+
sa.Column('name', sa.String(length=160), nullable=False),
|
|
139
|
+
sa.Column('snapshot', sa.JSON(), nullable=False),
|
|
140
|
+
sa.Column('api_key_hash', sa.String(length=128), nullable=False),
|
|
141
|
+
sa.Column('active', sa.Boolean(), nullable=False),
|
|
142
|
+
sa.Column('created_at', sa.DateTime(), nullable=False),
|
|
143
|
+
sa.ForeignKeyConstraint(['flow_id'], ['flows.id'], name=op.f('fk_deployments_flow_id_flows')),
|
|
144
|
+
sa.ForeignKeyConstraint(['workspace_id'], ['workspaces.id'], name=op.f('fk_deployments_workspace_id_workspaces')),
|
|
145
|
+
sa.PrimaryKeyConstraint('id', name=op.f('pk_deployments'))
|
|
146
|
+
)
|
|
147
|
+
with op.batch_alter_table('deployments', schema=None) as batch_op:
|
|
148
|
+
batch_op.create_index(batch_op.f('ix_deployments_active'), ['active'], unique=False)
|
|
149
|
+
batch_op.create_index(batch_op.f('ix_deployments_slug'), ['slug'], unique=False)
|
|
150
|
+
batch_op.create_index(batch_op.f('ix_deployments_workspace_id'), ['workspace_id'], unique=False)
|
|
151
|
+
|
|
152
|
+
op.create_table('requests',
|
|
153
|
+
sa.Column('id', sa.Integer(), nullable=False),
|
|
154
|
+
sa.Column('workspace_id', sa.Integer(), nullable=True),
|
|
155
|
+
sa.Column('collection_id', sa.Integer(), nullable=True),
|
|
156
|
+
sa.Column('name', sa.String(length=160), nullable=False),
|
|
157
|
+
sa.Column('type', sa.String(length=16), nullable=False),
|
|
158
|
+
sa.Column('payload', sa.JSON(), nullable=False),
|
|
159
|
+
sa.Column('updated_at', sa.DateTime(), nullable=False),
|
|
160
|
+
sa.ForeignKeyConstraint(['collection_id'], ['collections.id'], name=op.f('fk_requests_collection_id_collections')),
|
|
161
|
+
sa.ForeignKeyConstraint(['workspace_id'], ['workspaces.id'], name=op.f('fk_requests_workspace_id_workspaces')),
|
|
162
|
+
sa.PrimaryKeyConstraint('id', name=op.f('pk_requests'))
|
|
163
|
+
)
|
|
164
|
+
with op.batch_alter_table('requests', schema=None) as batch_op:
|
|
165
|
+
batch_op.create_index(batch_op.f('ix_requests_workspace_id'), ['workspace_id'], unique=False)
|
|
166
|
+
|
|
167
|
+
op.create_table('runs',
|
|
168
|
+
sa.Column('id', sa.Integer(), nullable=False),
|
|
169
|
+
sa.Column('workspace_id', sa.Integer(), nullable=True),
|
|
170
|
+
sa.Column('deployment_id', sa.Integer(), nullable=True),
|
|
171
|
+
sa.Column('type', sa.String(length=16), nullable=False),
|
|
172
|
+
sa.Column('label', sa.String(length=200), nullable=False),
|
|
173
|
+
sa.Column('request', sa.JSON(), nullable=False),
|
|
174
|
+
sa.Column('result', sa.JSON(), nullable=False),
|
|
175
|
+
sa.Column('status', sa.String(length=16), nullable=False),
|
|
176
|
+
sa.Column('duration_ms', sa.Integer(), nullable=False),
|
|
177
|
+
sa.Column('error', sa.Text(), nullable=False),
|
|
178
|
+
sa.Column('created_at', sa.DateTime(), nullable=False),
|
|
179
|
+
sa.ForeignKeyConstraint(['deployment_id'], ['deployments.id'], name=op.f('fk_runs_deployment_id_deployments')),
|
|
180
|
+
sa.ForeignKeyConstraint(['workspace_id'], ['workspaces.id'], name=op.f('fk_runs_workspace_id_workspaces')),
|
|
181
|
+
sa.PrimaryKeyConstraint('id', name=op.f('pk_runs'))
|
|
182
|
+
)
|
|
183
|
+
with op.batch_alter_table('runs', schema=None) as batch_op:
|
|
184
|
+
batch_op.create_index(batch_op.f('ix_runs_deployment_id'), ['deployment_id'], unique=False)
|
|
185
|
+
batch_op.create_index(batch_op.f('ix_runs_workspace_id'), ['workspace_id'], unique=False)
|
|
186
|
+
|
|
187
|
+
# ### end Alembic commands ###
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def downgrade() -> None:
|
|
191
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
|
192
|
+
with op.batch_alter_table('runs', schema=None) as batch_op:
|
|
193
|
+
batch_op.drop_index(batch_op.f('ix_runs_workspace_id'))
|
|
194
|
+
batch_op.drop_index(batch_op.f('ix_runs_deployment_id'))
|
|
195
|
+
|
|
196
|
+
op.drop_table('runs')
|
|
197
|
+
with op.batch_alter_table('requests', schema=None) as batch_op:
|
|
198
|
+
batch_op.drop_index(batch_op.f('ix_requests_workspace_id'))
|
|
199
|
+
|
|
200
|
+
op.drop_table('requests')
|
|
201
|
+
with op.batch_alter_table('deployments', schema=None) as batch_op:
|
|
202
|
+
batch_op.drop_index(batch_op.f('ix_deployments_workspace_id'))
|
|
203
|
+
batch_op.drop_index(batch_op.f('ix_deployments_slug'))
|
|
204
|
+
batch_op.drop_index(batch_op.f('ix_deployments_active'))
|
|
205
|
+
|
|
206
|
+
op.drop_table('deployments')
|
|
207
|
+
with op.batch_alter_table('workspace_members', schema=None) as batch_op:
|
|
208
|
+
batch_op.drop_index(batch_op.f('ix_workspace_members_workspace_id'))
|
|
209
|
+
batch_op.drop_index(batch_op.f('ix_workspace_members_user_id'))
|
|
210
|
+
|
|
211
|
+
op.drop_table('workspace_members')
|
|
212
|
+
with op.batch_alter_table('prompts', schema=None) as batch_op:
|
|
213
|
+
batch_op.drop_index(batch_op.f('ix_prompts_workspace_id'))
|
|
214
|
+
|
|
215
|
+
op.drop_table('prompts')
|
|
216
|
+
with op.batch_alter_table('flows', schema=None) as batch_op:
|
|
217
|
+
batch_op.drop_index(batch_op.f('ix_flows_workspace_id'))
|
|
218
|
+
|
|
219
|
+
op.drop_table('flows')
|
|
220
|
+
with op.batch_alter_table('environments', schema=None) as batch_op:
|
|
221
|
+
batch_op.drop_index(batch_op.f('ix_environments_workspace_id'))
|
|
222
|
+
|
|
223
|
+
op.drop_table('environments')
|
|
224
|
+
with op.batch_alter_table('datasets', schema=None) as batch_op:
|
|
225
|
+
batch_op.drop_index(batch_op.f('ix_datasets_workspace_id'))
|
|
226
|
+
|
|
227
|
+
op.drop_table('datasets')
|
|
228
|
+
with op.batch_alter_table('connections', schema=None) as batch_op:
|
|
229
|
+
batch_op.drop_index(batch_op.f('ix_connections_workspace_id'))
|
|
230
|
+
|
|
231
|
+
op.drop_table('connections')
|
|
232
|
+
with op.batch_alter_table('collections', schema=None) as batch_op:
|
|
233
|
+
batch_op.drop_index(batch_op.f('ix_collections_workspace_id'))
|
|
234
|
+
|
|
235
|
+
op.drop_table('collections')
|
|
236
|
+
with op.batch_alter_table('workspaces', schema=None) as batch_op:
|
|
237
|
+
batch_op.drop_index(batch_op.f('ix_workspaces_owner_user_id'))
|
|
238
|
+
|
|
239
|
+
op.drop_table('workspaces')
|
|
240
|
+
op.drop_table('users')
|
|
241
|
+
# ### end Alembic commands ###
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""workspace ingest key
|
|
2
|
+
|
|
3
|
+
Revision ID: 819ed5ff183e
|
|
4
|
+
Revises: 49e8ab812556
|
|
5
|
+
Create Date: 2026-07-17 00:25:59.721254
|
|
6
|
+
"""
|
|
7
|
+
from alembic import op
|
|
8
|
+
import sqlalchemy as sa
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
revision = '819ed5ff183e'
|
|
12
|
+
down_revision = '49e8ab812556'
|
|
13
|
+
branch_labels = None
|
|
14
|
+
depends_on = None
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def upgrade() -> None:
|
|
18
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
|
19
|
+
with op.batch_alter_table('workspaces', schema=None) as batch_op:
|
|
20
|
+
# server_default="" so adding this NOT NULL column succeeds on databases that
|
|
21
|
+
# already have workspace rows (otherwise the ALTER fails at upgrade time).
|
|
22
|
+
batch_op.add_column(sa.Column('ingest_key_hash', sa.String(length=128), nullable=False,
|
|
23
|
+
server_default=''))
|
|
24
|
+
batch_op.create_index(batch_op.f('ix_workspaces_ingest_key_hash'), ['ingest_key_hash'], unique=False)
|
|
25
|
+
|
|
26
|
+
# ### end Alembic commands ###
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def downgrade() -> None:
|
|
30
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
|
31
|
+
with op.batch_alter_table('workspaces', schema=None) as batch_op:
|
|
32
|
+
batch_op.drop_index(batch_op.f('ix_workspaces_ingest_key_hash'))
|
|
33
|
+
batch_op.drop_column('ingest_key_hash')
|
|
34
|
+
|
|
35
|
+
# ### end Alembic commands ###
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""user token_version
|
|
2
|
+
|
|
3
|
+
Revision ID: a1b2c3d4e5f6
|
|
4
|
+
Revises: 323eb73d463c
|
|
5
|
+
Create Date: 2026-07-17 06:10:00.000000
|
|
6
|
+
"""
|
|
7
|
+
from alembic import op
|
|
8
|
+
import sqlalchemy as sa
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
revision = 'a1b2c3d4e5f6'
|
|
12
|
+
down_revision = '323eb73d463c'
|
|
13
|
+
branch_labels = None
|
|
14
|
+
depends_on = None
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def upgrade() -> None:
|
|
18
|
+
with op.batch_alter_table('users', schema=None) as batch_op:
|
|
19
|
+
# server_default="0" so the NOT NULL column populates on existing rows; all current
|
|
20
|
+
# sessions carry v=0 (or no v, read as 0) and stay valid until the next reset.
|
|
21
|
+
batch_op.add_column(sa.Column('token_version', sa.Integer(), nullable=False,
|
|
22
|
+
server_default='0'))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def downgrade() -> None:
|
|
26
|
+
with op.batch_alter_table('users', schema=None) as batch_op:
|
|
27
|
+
batch_op.drop_column('token_version')
|
provekit/models.py
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""ProveKit data model. Everything the console persists: connections (providers),
|
|
2
|
+
collections + saved requests, environments (variables), and run history."""
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
|
|
5
|
+
from sqlalchemy import JSON, DateTime, ForeignKey, Integer, String, Text, TypeDecorator, UniqueConstraint
|
|
6
|
+
from sqlalchemy.orm import Mapped, mapped_column
|
|
7
|
+
|
|
8
|
+
from .database import Base
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _ws_fk() -> Mapped[int]:
|
|
12
|
+
"""Workspace FK shared by every tenant-scoped table (indexed for per-workspace queries)."""
|
|
13
|
+
return mapped_column(ForeignKey("workspaces.id"), index=True, nullable=True)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _now() -> datetime:
|
|
17
|
+
return datetime.now(timezone.utc)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def iso_utc(dt: datetime | None) -> str | None:
|
|
21
|
+
"""Serialize a stored timestamp as ISO-8601 with an explicit UTC offset. Timestamps
|
|
22
|
+
are written in UTC (_now), but SQLite hands them back naive — without the offset the
|
|
23
|
+
browser would parse them as local time."""
|
|
24
|
+
if dt is None:
|
|
25
|
+
return None
|
|
26
|
+
if dt.tzinfo is None:
|
|
27
|
+
dt = dt.replace(tzinfo=timezone.utc)
|
|
28
|
+
return dt.isoformat()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class SealedJSON(TypeDecorator):
|
|
32
|
+
"""JSON column whose secret fields (api_key, auth headers) are encrypted at rest.
|
|
33
|
+
Application code always sees plaintext; the database file never does."""
|
|
34
|
+
impl = JSON
|
|
35
|
+
cache_ok = True
|
|
36
|
+
|
|
37
|
+
def process_bind_param(self, value, dialect):
|
|
38
|
+
from .services.sealing import seal_config
|
|
39
|
+
return seal_config(value) if isinstance(value, dict) else value
|
|
40
|
+
|
|
41
|
+
def process_result_value(self, value, dialect):
|
|
42
|
+
from .services.sealing import unseal_config
|
|
43
|
+
return unseal_config(value) if isinstance(value, dict) else value
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class User(Base):
|
|
47
|
+
"""An account. password_hash is null for OAuth-only users."""
|
|
48
|
+
__tablename__ = "users"
|
|
49
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
50
|
+
email: Mapped[str] = mapped_column(String(255), unique=True)
|
|
51
|
+
name: Mapped[str] = mapped_column(String(160), default="")
|
|
52
|
+
password_hash: Mapped[str] = mapped_column(String(255), default="")
|
|
53
|
+
auth_provider: Mapped[str] = mapped_column(String(32), default="password") # password | github | local
|
|
54
|
+
email_verified: Mapped[bool] = mapped_column(default=False)
|
|
55
|
+
# Bumped on password reset to revoke every previously-issued token (sessions + the
|
|
56
|
+
# just-used reset link): tokens embed the version and are rejected once it moves on.
|
|
57
|
+
token_version: Mapped[int] = mapped_column(default=0)
|
|
58
|
+
created_at: Mapped[datetime] = mapped_column(DateTime, default=_now)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class Workspace(Base):
|
|
62
|
+
"""A tenant boundary. Every user gets a default workspace; teams share one later."""
|
|
63
|
+
__tablename__ = "workspaces"
|
|
64
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
65
|
+
name: Mapped[str] = mapped_column(String(160), default="My workspace")
|
|
66
|
+
owner_user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), index=True)
|
|
67
|
+
# Bearer token for unattended OTLP trace ingest (exporters can't send cookies).
|
|
68
|
+
ingest_key_hash: Mapped[str] = mapped_column(String(128), default="", index=True)
|
|
69
|
+
created_at: Mapped[datetime] = mapped_column(DateTime, default=_now)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class WorkspaceMember(Base):
|
|
73
|
+
__tablename__ = "workspace_members"
|
|
74
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
75
|
+
workspace_id: Mapped[int] = mapped_column(ForeignKey("workspaces.id"), index=True)
|
|
76
|
+
user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), index=True)
|
|
77
|
+
role: Mapped[str] = mapped_column(String(16), default="member") # owner | member
|
|
78
|
+
__table_args__ = (UniqueConstraint("workspace_id", "user_id", name="uq_member"),)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class Connection(Base):
|
|
82
|
+
"""A provider you can run against. kind = llm | mcp | agent."""
|
|
83
|
+
__tablename__ = "connections"
|
|
84
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
85
|
+
workspace_id: Mapped[int] = _ws_fk()
|
|
86
|
+
name: Mapped[str] = mapped_column(String(120))
|
|
87
|
+
kind: Mapped[str] = mapped_column(String(16)) # llm | mcp | agent
|
|
88
|
+
# config shape by kind:
|
|
89
|
+
# llm -> {provider: openai|anthropic|compatible, base_url, api_key, models: [str]}
|
|
90
|
+
# mcp -> {url}
|
|
91
|
+
# agent -> {base_url, headers: {}}
|
|
92
|
+
config: Mapped[dict] = mapped_column(SealedJSON, default=dict)
|
|
93
|
+
created_at: Mapped[datetime] = mapped_column(DateTime, default=_now)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class Collection(Base):
|
|
97
|
+
__tablename__ = "collections"
|
|
98
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
99
|
+
workspace_id: Mapped[int] = _ws_fk()
|
|
100
|
+
name: Mapped[str] = mapped_column(String(120))
|
|
101
|
+
created_at: Mapped[datetime] = mapped_column(DateTime, default=_now)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class Request(Base):
|
|
105
|
+
"""A saved request. type = prompt | tool | agent. payload holds the type-specific body."""
|
|
106
|
+
__tablename__ = "requests"
|
|
107
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
108
|
+
workspace_id: Mapped[int] = _ws_fk()
|
|
109
|
+
collection_id: Mapped[int | None] = mapped_column(ForeignKey("collections.id"), nullable=True)
|
|
110
|
+
name: Mapped[str] = mapped_column(String(160))
|
|
111
|
+
type: Mapped[str] = mapped_column(String(16))
|
|
112
|
+
payload: Mapped[dict] = mapped_column(JSON, default=dict)
|
|
113
|
+
updated_at: Mapped[datetime] = mapped_column(DateTime, default=_now, onupdate=_now)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class Environment(Base):
|
|
117
|
+
__tablename__ = "environments"
|
|
118
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
119
|
+
workspace_id: Mapped[int] = _ws_fk()
|
|
120
|
+
name: Mapped[str] = mapped_column(String(120))
|
|
121
|
+
variables: Mapped[dict] = mapped_column(JSON, default=dict)
|
|
122
|
+
is_active: Mapped[bool] = mapped_column(default=False)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class Prompt(Base):
|
|
126
|
+
"""A managed, reusable prompt (the generic Prompt Registry)."""
|
|
127
|
+
__tablename__ = "prompts"
|
|
128
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
129
|
+
workspace_id: Mapped[int] = _ws_fk()
|
|
130
|
+
key: Mapped[str] = mapped_column(String(80))
|
|
131
|
+
name: Mapped[str] = mapped_column(String(160))
|
|
132
|
+
description: Mapped[str] = mapped_column(Text, default="")
|
|
133
|
+
content: Mapped[str] = mapped_column(Text, default="")
|
|
134
|
+
updated_at: Mapped[datetime] = mapped_column(DateTime, default=_now, onupdate=_now)
|
|
135
|
+
__table_args__ = (UniqueConstraint("workspace_id", "key", name="uq_prompt_key"),)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class Flow(Base):
|
|
139
|
+
"""A visual agent workflow: a graph of nodes + edges executed by the flow engine."""
|
|
140
|
+
__tablename__ = "flows"
|
|
141
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
142
|
+
workspace_id: Mapped[int] = _ws_fk()
|
|
143
|
+
name: Mapped[str] = mapped_column(String(160))
|
|
144
|
+
description: Mapped[str] = mapped_column(Text, default="")
|
|
145
|
+
nodes: Mapped[list] = mapped_column(JSON, default=list)
|
|
146
|
+
edges: Mapped[list] = mapped_column(JSON, default=list)
|
|
147
|
+
updated_at: Mapped[datetime] = mapped_column(DateTime, default=_now, onupdate=_now)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class Dataset(Base):
|
|
151
|
+
"""A reusable, named set of input rows — run any request over it. rows =
|
|
152
|
+
[{name, variables}]. Decoupled from requests, like a shared data file."""
|
|
153
|
+
__tablename__ = "datasets"
|
|
154
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
155
|
+
workspace_id: Mapped[int] = _ws_fk()
|
|
156
|
+
name: Mapped[str] = mapped_column(String(160))
|
|
157
|
+
rows: Mapped[list] = mapped_column(JSON, default=list)
|
|
158
|
+
created_at: Mapped[datetime] = mapped_column(DateTime, default=_now)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class Deployment(Base):
|
|
162
|
+
"""A published flow snapshot, callable as a hosted endpoint. Rows share a `slug`
|
|
163
|
+
across versions; exactly one version per slug is active. The graph is frozen in
|
|
164
|
+
`snapshot` at deploy time so editing the flow never changes production."""
|
|
165
|
+
__tablename__ = "deployments"
|
|
166
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
167
|
+
workspace_id: Mapped[int] = _ws_fk()
|
|
168
|
+
flow_id: Mapped[int | None] = mapped_column(ForeignKey("flows.id"), nullable=True)
|
|
169
|
+
slug: Mapped[str] = mapped_column(String(80), index=True)
|
|
170
|
+
version: Mapped[int] = mapped_column(Integer, default=1)
|
|
171
|
+
name: Mapped[str] = mapped_column(String(160), default="")
|
|
172
|
+
snapshot: Mapped[dict] = mapped_column(JSON, default=dict) # {nodes, edges}
|
|
173
|
+
api_key_hash: Mapped[str] = mapped_column(String(128))
|
|
174
|
+
active: Mapped[bool] = mapped_column(default=True, index=True)
|
|
175
|
+
created_at: Mapped[datetime] = mapped_column(DateTime, default=_now)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class Run(Base):
|
|
179
|
+
"""A single execution + its result (for history/replay)."""
|
|
180
|
+
__tablename__ = "runs"
|
|
181
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
182
|
+
workspace_id: Mapped[int] = _ws_fk()
|
|
183
|
+
deployment_id: Mapped[int | None] = mapped_column(ForeignKey("deployments.id"), index=True, nullable=True)
|
|
184
|
+
type: Mapped[str] = mapped_column(String(16))
|
|
185
|
+
label: Mapped[str] = mapped_column(String(200), default="")
|
|
186
|
+
request: Mapped[dict] = mapped_column(JSON, default=dict) # the sent request
|
|
187
|
+
result: Mapped[dict] = mapped_column(JSON, default=dict) # {output, meta, events?}
|
|
188
|
+
status: Mapped[str] = mapped_column(String(16), default="completed")
|
|
189
|
+
duration_ms: Mapped[int] = mapped_column(Integer, default=0)
|
|
190
|
+
error: Mapped[str] = mapped_column(Text, default="")
|
|
191
|
+
created_at: Mapped[datetime] = mapped_column(DateTime, default=_now)
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""Observability: request IDs, structured JSON logs (hosted), optional Sentry, /healthz.
|
|
2
|
+
|
|
3
|
+
Logs never include prompt/response bodies or secrets — only run lifecycle metadata.
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import logging
|
|
9
|
+
import uuid
|
|
10
|
+
from contextvars import ContextVar
|
|
11
|
+
|
|
12
|
+
from fastapi import Request
|
|
13
|
+
from fastapi.responses import JSONResponse
|
|
14
|
+
from sqlalchemy import text
|
|
15
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
16
|
+
|
|
17
|
+
from .config import get_settings
|
|
18
|
+
from .database import SessionLocal
|
|
19
|
+
|
|
20
|
+
request_id: ContextVar[str] = ContextVar("request_id", default="-")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class _JSONFormatter(logging.Formatter):
|
|
24
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
25
|
+
payload = {"level": record.levelname, "logger": record.name,
|
|
26
|
+
"msg": record.getMessage(), "request_id": request_id.get()}
|
|
27
|
+
if record.exc_info:
|
|
28
|
+
payload["exc"] = self.formatException(record.exc_info)
|
|
29
|
+
return json.dumps(payload)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def setup_logging() -> None:
|
|
33
|
+
s = get_settings()
|
|
34
|
+
root = logging.getLogger()
|
|
35
|
+
root.setLevel(logging.INFO)
|
|
36
|
+
if s.hosted: # machine-parseable logs in production
|
|
37
|
+
for h in root.handlers:
|
|
38
|
+
h.setFormatter(_JSONFormatter())
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def init_sentry() -> None:
|
|
42
|
+
dsn = get_settings().sentry_dsn
|
|
43
|
+
if not dsn:
|
|
44
|
+
return
|
|
45
|
+
try:
|
|
46
|
+
import sentry_sdk
|
|
47
|
+
sentry_sdk.init(dsn=dsn, traces_sample_rate=0.1)
|
|
48
|
+
logging.getLogger("provekit").info("sentry enabled")
|
|
49
|
+
except ImportError:
|
|
50
|
+
logging.getLogger("provekit").warning("SENTRY_DSN set but sentry-sdk not installed")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class RequestIDMiddleware(BaseHTTPMiddleware):
|
|
54
|
+
"""Assign/propagate X-Request-ID and bind it to logs for the duration of the request."""
|
|
55
|
+
async def dispatch(self, request: Request, call_next):
|
|
56
|
+
rid = request.headers.get("x-request-id") or uuid.uuid4().hex[:16]
|
|
57
|
+
token = request_id.set(rid)
|
|
58
|
+
try:
|
|
59
|
+
response = await call_next(request)
|
|
60
|
+
finally:
|
|
61
|
+
request_id.reset(token)
|
|
62
|
+
response.headers["X-Request-ID"] = rid
|
|
63
|
+
return response
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class SecurityHeadersMiddleware(BaseHTTPMiddleware):
|
|
67
|
+
"""Baseline hardening headers on every response; HSTS only in hosted (TLS) mode."""
|
|
68
|
+
async def dispatch(self, request: Request, call_next):
|
|
69
|
+
response = await call_next(request)
|
|
70
|
+
response.headers.setdefault("X-Content-Type-Options", "nosniff")
|
|
71
|
+
response.headers.setdefault("X-Frame-Options", "DENY")
|
|
72
|
+
response.headers.setdefault("Referrer-Policy", "strict-origin-when-cross-origin")
|
|
73
|
+
if get_settings().hosted:
|
|
74
|
+
response.headers.setdefault("Strict-Transport-Security",
|
|
75
|
+
"max-age=31536000; includeSubDomains")
|
|
76
|
+
return response
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class BodySizeLimitMiddleware:
|
|
80
|
+
"""Reject oversized request bodies with 413. Pure ASGI so the cap holds for chunked /
|
|
81
|
+
Content-Length-less requests too: the body is buffered up to the limit and then replayed
|
|
82
|
+
to the app (this API takes small JSON bodies, so buffering is cheap)."""
|
|
83
|
+
def __init__(self, app):
|
|
84
|
+
self.app = app
|
|
85
|
+
|
|
86
|
+
async def __call__(self, scope, receive, send):
|
|
87
|
+
if scope["type"] != "http":
|
|
88
|
+
return await self.app(scope, receive, send)
|
|
89
|
+
max_bytes = get_settings().max_body_bytes
|
|
90
|
+
headers = dict(scope.get("headers") or [])
|
|
91
|
+
cl = headers.get(b"content-length")
|
|
92
|
+
if cl is not None and cl.isdigit() and int(cl) > max_bytes:
|
|
93
|
+
return await self._reject(send)
|
|
94
|
+
body = bytearray()
|
|
95
|
+
while True:
|
|
96
|
+
message = await receive()
|
|
97
|
+
if message["type"] == "http.disconnect":
|
|
98
|
+
# The client vanished mid-upload. Returning bare would satisfy a pure-ASGI
|
|
99
|
+
# parent, but this runs inside BaseHTTPMiddleware (RequestID/SecurityHeaders),
|
|
100
|
+
# whose call_next raises "No response returned." if nothing is sent — turning
|
|
101
|
+
# every cancelled upload into a 500 + Sentry event. Nobody reads this response.
|
|
102
|
+
return await self._respond(send, 499, "Client disconnected")
|
|
103
|
+
body.extend(message.get("body", b""))
|
|
104
|
+
if len(body) > max_bytes:
|
|
105
|
+
return await self._reject(send)
|
|
106
|
+
if not message.get("more_body", False):
|
|
107
|
+
break
|
|
108
|
+
buffered = bytes(body)
|
|
109
|
+
done = False
|
|
110
|
+
|
|
111
|
+
async def replay():
|
|
112
|
+
nonlocal done
|
|
113
|
+
if not done:
|
|
114
|
+
done = True
|
|
115
|
+
return {"type": "http.request", "body": buffered, "more_body": False}
|
|
116
|
+
# Delegate later reads to the real channel so a StreamingResponse's disconnect
|
|
117
|
+
# listener gets genuine http.disconnect events (returning a fake one truncates SSE).
|
|
118
|
+
return await receive()
|
|
119
|
+
|
|
120
|
+
await self.app(scope, replay, send)
|
|
121
|
+
|
|
122
|
+
@classmethod
|
|
123
|
+
async def _reject(cls, send):
|
|
124
|
+
return await cls._respond(send, 413, "Request body too large")
|
|
125
|
+
|
|
126
|
+
@staticmethod
|
|
127
|
+
async def _respond(send, status: int, detail: str):
|
|
128
|
+
body = json.dumps({"detail": detail}).encode()
|
|
129
|
+
await send({"type": "http.response.start", "status": status,
|
|
130
|
+
"headers": [(b"content-type", b"application/json"),
|
|
131
|
+
(b"content-length", str(len(body)).encode())]})
|
|
132
|
+
await send({"type": "http.response.body", "body": body})
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def healthz() -> JSONResponse:
|
|
136
|
+
"""Liveness+readiness: DB reachable, and Redis if configured."""
|
|
137
|
+
checks = {"db": False, "redis": None}
|
|
138
|
+
try:
|
|
139
|
+
db = SessionLocal()
|
|
140
|
+
db.execute(text("SELECT 1"))
|
|
141
|
+
db.close()
|
|
142
|
+
checks["db"] = True
|
|
143
|
+
except Exception:
|
|
144
|
+
pass
|
|
145
|
+
if get_settings().redis_url:
|
|
146
|
+
checks["redis"] = False
|
|
147
|
+
try:
|
|
148
|
+
from .services.runstore import _store
|
|
149
|
+
_store()._r.ping()
|
|
150
|
+
checks["redis"] = True
|
|
151
|
+
except Exception:
|
|
152
|
+
pass
|
|
153
|
+
ok = checks["db"] and (checks["redis"] is not False)
|
|
154
|
+
return JSONResponse({"ok": ok, "checks": checks}, status_code=200 if ok else 503)
|
|
File without changes
|