marvisx-cli 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.
- core/api/__init__.py +0 -0
- core/api/agents/__init__.py +0 -0
- core/api/agents/session_health.py +59 -0
- core/api/agents/session_manager.py +206 -0
- core/api/bin/marvisx-state-hook.py +182 -0
- core/api/config.py +533 -0
- core/api/db.py +1516 -0
- core/api/dependencies/__init__.py +0 -0
- core/api/dependencies/tenant.py +34 -0
- core/api/main.py +1641 -0
- core/api/mcp/__init__.py +8 -0
- core/api/mcp/_adapter.py +184 -0
- core/api/mcp/server.py +58 -0
- core/api/mcp/tools/__init__.py +59 -0
- core/api/mcp/tools/brain.py +599 -0
- core/api/mcp/tools/graph.py +380 -0
- core/api/mcp/tools/handoffs.py +112 -0
- core/api/mcp/tools/ingest.py +326 -0
- core/api/mcp/tools/learnings.py +144 -0
- core/api/mcp/tools/projects.py +99 -0
- core/api/mcp/tools/pull_requests.py +173 -0
- core/api/mcp/tools/safety.py +111 -0
- core/api/mcp/tools/search.py +79 -0
- core/api/mcp/tools/tasks.py +258 -0
- core/api/middleware/__init__.py +0 -0
- core/api/middleware/tool_call_audit.py +111 -0
- core/api/models/__init__.py +346 -0
- core/api/models/auth.py +48 -0
- core/api/models/brain.py +1006 -0
- core/api/models/common.py +76 -0
- core/api/models/costs.py +91 -0
- core/api/models/graph.py +66 -0
- core/api/models/graph_cosmo.py +125 -0
- core/api/models/graph_pr_impact.py +257 -0
- core/api/models/graph_ux.py +141 -0
- core/api/models/inbox.py +230 -0
- core/api/models/ingest_keys.py +108 -0
- core/api/models/kg.py +41 -0
- core/api/models/llm_config.py +56 -0
- core/api/models/monitoring.py +234 -0
- core/api/models/projects.py +161 -0
- core/api/models/search.py +42 -0
- core/api/models/sessions.py +322 -0
- core/api/models/tasks.py +184 -0
- core/api/models/teams.py +63 -0
- core/api/models/users.py +184 -0
- core/api/observability/__init__.py +0 -0
- core/api/observability/tracing.py +92 -0
- core/api/paths.py +26 -0
- core/api/rate_limit.py +24 -0
- core/api/rbac.py +112 -0
- core/api/routers/__init__.py +0 -0
- core/api/routers/_adapter.py +24 -0
- core/api/routers/admin_pr_impact.py +230 -0
- core/api/routers/admin_settings.py +147 -0
- core/api/routers/agent.py +1079 -0
- core/api/routers/agent_tokens.py +276 -0
- core/api/routers/app_settings.py +112 -0
- core/api/routers/audit.py +89 -0
- core/api/routers/auth.py +586 -0
- core/api/routers/bench.py +161 -0
- core/api/routers/brain.py +881 -0
- core/api/routers/brain_directions.py +527 -0
- core/api/routers/ci_checks.py +140 -0
- core/api/routers/comments.py +273 -0
- core/api/routers/costs.py +148 -0
- core/api/routers/docs_coverage.py +217 -0
- core/api/routers/docs_governance.py +63 -0
- core/api/routers/documents.py +318 -0
- core/api/routers/files.py +163 -0
- core/api/routers/finder.py +987 -0
- core/api/routers/graph.py +836 -0
- core/api/routers/handoffs.py +156 -0
- core/api/routers/inbox.py +496 -0
- core/api/routers/ingest_api_keys.py +205 -0
- core/api/routers/ingest_triage.py +1227 -0
- core/api/routers/judge.py +306 -0
- core/api/routers/kg.py +336 -0
- core/api/routers/learnings.py +253 -0
- core/api/routers/llm_config.py +130 -0
- core/api/routers/monitoring.py +347 -0
- core/api/routers/notifications.py +125 -0
- core/api/routers/pr_impact.py +315 -0
- core/api/routers/projects.py +1061 -0
- core/api/routers/pull_requests.py +312 -0
- core/api/routers/push.py +67 -0
- core/api/routers/raci.py +228 -0
- core/api/routers/search.py +125 -0
- core/api/routers/sessions.py +3100 -0
- core/api/routers/settings.py +90 -0
- core/api/routers/share_repo.py +68 -0
- core/api/routers/status_updates.py +96 -0
- core/api/routers/tags.py +45 -0
- core/api/routers/tasks.py +526 -0
- core/api/routers/teams.py +425 -0
- core/api/routers/terminal.py +105 -0
- core/api/routers/users.py +331 -0
- core/api/routers/webhooks.py +330 -0
- core/api/runtime_settings.py +84 -0
- core/api/security.py +652 -0
- core/api/services/__init__.py +0 -0
- core/api/services/audit.py +58 -0
- core/api/services/auto_approval.py +82 -0
- core/api/services/brain/__init__.py +52 -0
- core/api/services/brain/baseline.py +230 -0
- core/api/services/brain/capabilities.py +75 -0
- core/api/services/brain/cascade_rollup.py +388 -0
- core/api/services/brain/compound_bridge.py +215 -0
- core/api/services/brain/cycle.py +1242 -0
- core/api/services/brain/cycle_snapshot.py +371 -0
- core/api/services/brain/digest_collector.py +147 -0
- core/api/services/brain/direction.py +421 -0
- core/api/services/brain/drift.py +356 -0
- core/api/services/brain/drift_router.py +409 -0
- core/api/services/brain/edge_metrics.py +79 -0
- core/api/services/brain/events_reader.py +222 -0
- core/api/services/brain/findings.py +1379 -0
- core/api/services/brain/findings_reader.py +1006 -0
- core/api/services/brain/jobs.py +733 -0
- core/api/services/brain/journal.py +206 -0
- core/api/services/brain/knowledge_forms.py +92 -0
- core/api/services/brain/llm/__init__.py +37 -0
- core/api/services/brain/llm/_runner.py +62 -0
- core/api/services/brain/llm/base.py +70 -0
- core/api/services/brain/llm/cache.py +99 -0
- core/api/services/brain/llm/constants.py +46 -0
- core/api/services/brain/llm/direction_alignment.py +289 -0
- core/api/services/brain/llm/factory.py +132 -0
- core/api/services/brain/llm/finding_reasoning.py +98 -0
- core/api/services/brain/llm/finding_summary.py +92 -0
- core/api/services/brain/llm/grounding.py +46 -0
- core/api/services/brain/llm/journal_polish.py +96 -0
- core/api/services/brain/llm/local_gateway.py +426 -0
- core/api/services/brain/llm/parsers.py +71 -0
- core/api/services/brain/llm/router_glue.py +422 -0
- core/api/services/brain/memory_ops.py +1677 -0
- core/api/services/brain/models.py +140 -0
- core/api/services/brain/owner_hint.py +211 -0
- core/api/services/brain/recap.py +307 -0
- core/api/services/brain/rules/__init__.py +65 -0
- core/api/services/brain/rules/_signals.py +205 -0
- core/api/services/brain/rules/dr1_activity_without_status.py +108 -0
- core/api/services/brain/rules/dr2_decision_without_adr.py +110 -0
- core/api/services/brain/rules/dr3_stale_open_loop.py +127 -0
- core/api/services/brain/rules/dr4_docs_governance_drift.py +79 -0
- core/api/services/brain/rules/dr5_playbook_changed.py +99 -0
- core/api/services/brain/rules/dr6_external_update_unpropagated.py +100 -0
- core/api/services/brain/rules/dr7_claimed_decision_gap.py +123 -0
- core/api/services/brain/rules/dr8_direction_misalignment.py +230 -0
- core/api/services/brain/runs_reader.py +485 -0
- core/api/services/brain/scope.py +79 -0
- core/api/services/brain/sources/__init__.py +46 -0
- core/api/services/brain/sources/base.py +86 -0
- core/api/services/brain/sources/git_kg.py +393 -0
- core/api/services/brain/sources/handoffs.py +157 -0
- core/api/services/brain/sources/ingestor.py +130 -0
- core/api/services/brain/sources/learnings.py +121 -0
- core/api/services/brain/sources/pir_tasks.py +245 -0
- core/api/services/brain/watermarks.py +147 -0
- core/api/services/brain/ws_emitter.py +170 -0
- core/api/services/cc_tasks_reader.py +76 -0
- core/api/services/ci_service.py +263 -0
- core/api/services/claude_metrics.py +796 -0
- core/api/services/codex_metrics.py +364 -0
- core/api/services/conversation_reader.py +102 -0
- core/api/services/cost_service.py +243 -0
- core/api/services/crypto.py +147 -0
- core/api/services/docs_governance/__init__.py +1 -0
- core/api/services/docs_governance/confidence.py +230 -0
- core/api/services/docs_governance/config.py +87 -0
- core/api/services/docs_governance/enrichment.py +65 -0
- core/api/services/docs_governance/frontmatter_validator.py +83 -0
- core/api/services/docs_governance/hard_gates.py +221 -0
- core/api/services/docs_governance/triage_orchestrator.py +98 -0
- core/api/services/embedding_internal.py +395 -0
- core/api/services/embedding_service.py +832 -0
- core/api/services/event_dispatcher.py +167 -0
- core/api/services/events.py +70 -0
- core/api/services/git_ops.py +621 -0
- core/api/services/graph_cosmo_service.py +440 -0
- core/api/services/graph_ranker.py +306 -0
- core/api/services/graph_service.py +1589 -0
- core/api/services/inbox.py +800 -0
- core/api/services/inbox_digest.py +221 -0
- core/api/services/inbox_digest_deep_research.py +80 -0
- core/api/services/inbox_digest_jobs.py +595 -0
- core/api/services/inbox_gmail_sync.py +167 -0
- core/api/services/inbox_llm_classifier.py +906 -0
- core/api/services/inbox_source_identity.py +116 -0
- core/api/services/inbox_sources.py +456 -0
- core/api/services/inbox_taxonomy.py +195 -0
- core/api/services/inbox_tldr.py +1079 -0
- core/api/services/inbox_triage.py +899 -0
- core/api/services/ingest/__init__.py +13 -0
- core/api/services/ingest/api_key_auth.py +136 -0
- core/api/services/ingest/auto_approve.py +120 -0
- core/api/services/ingest/classifier.py +138 -0
- core/api/services/ingest/confidence.py +173 -0
- core/api/services/ingest/dispatch.py +88 -0
- core/api/services/ingest/embedding_router.py +272 -0
- core/api/services/ingest/events.py +33 -0
- core/api/services/ingest/ignore_patterns.py +79 -0
- core/api/services/ingest/image_probe.py +218 -0
- core/api/services/ingest/ingress.py +263 -0
- core/api/services/ingest/insert_saga.py +793 -0
- core/api/services/ingest/llm/__init__.py +13 -0
- core/api/services/ingest/llm/anthropic_haiku.py +23 -0
- core/api/services/ingest/llm/base.py +59 -0
- core/api/services/ingest/llm/byok_provider.py +130 -0
- core/api/services/ingest/llm/classification_context.py +301 -0
- core/api/services/ingest/llm/config_store.py +246 -0
- core/api/services/ingest/llm/factory.py +24 -0
- core/api/services/ingest/llm/kg_enricher.py +306 -0
- core/api/services/ingest/llm/local_gateway.py +821 -0
- core/api/services/ingest/llm/local_vllm.py +23 -0
- core/api/services/ingest/llm/openai_nano.py +349 -0
- core/api/services/ingest/lock_advisory.py +57 -0
- core/api/services/ingest/parser_router.py +1756 -0
- core/api/services/ingest/parsers/__init__.py +1 -0
- core/api/services/ingest/parsers/docling_parser.py +142 -0
- core/api/services/ingest/parsers/docparse_gateway.py +178 -0
- core/api/services/ingest/parsers/docx_parser.py +127 -0
- core/api/services/ingest/parsers/folder_unpacker.py +85 -0
- core/api/services/ingest/parsers/gateway_aux.py +147 -0
- core/api/services/ingest/parsers/image_parser.py +251 -0
- core/api/services/ingest/parsers/internal_markdown.py +89 -0
- core/api/services/ingest/parsers/ocr_gateway.py +117 -0
- core/api/services/ingest/parsers/ocr_pdf_parser.py +112 -0
- core/api/services/ingest/parsers/pdf_types.py +13 -0
- core/api/services/ingest/parsers/transcript_parser.py +445 -0
- core/api/services/ingest/parsers/vision_gateway.py +186 -0
- core/api/services/ingest/parsers/xlsx_parser.py +91 -0
- core/api/services/ingest/parsers/zip_unpacker.py +126 -0
- core/api/services/ingest/preflight.py +393 -0
- core/api/services/ingest/retry_voyage.py +88 -0
- core/api/services/ingest/routing_policy.py +307 -0
- core/api/services/ingest/serializers/__init__.py +1 -0
- core/api/services/ingest/serializers/xlsx_to_markdown.py +80 -0
- core/api/services/ingest/skip_log.py +74 -0
- core/api/services/ingest/watcher.py +637 -0
- core/api/services/kg/__init__.py +0 -0
- core/api/services/kg/audit.py +49 -0
- core/api/services/kg/hybrid_search.py +691 -0
- core/api/services/kg/lens.py +339 -0
- core/api/services/kg/pr_impact.py +770 -0
- core/api/services/kg/queries.py +152 -0
- core/api/services/kg/ranking.py +89 -0
- core/api/services/kg/rrf.py +143 -0
- core/api/services/kg_watcher_control.py +161 -0
- core/api/services/local_llm/__init__.py +19 -0
- core/api/services/local_llm/async_client.py +385 -0
- core/api/services/local_llm/client.py +173 -0
- core/api/services/local_llm/url_validator.py +44 -0
- core/api/services/metrics_collector.py +646 -0
- core/api/services/metrics_providers.py +65 -0
- core/api/services/model_registry.py +266 -0
- core/api/services/model_router.py +137 -0
- core/api/services/n8n_client.py +77 -0
- core/api/services/newsletter_llm_gateway.py +66 -0
- core/api/services/notification_service.py +134 -0
- core/api/services/openai_responses.py +55 -0
- core/api/services/opencode_metrics.py +375 -0
- core/api/services/opencode_sessions.py +173 -0
- core/api/services/pii_redactor.py +138 -0
- core/api/services/pr_impact_pipeline/__init__.py +21 -0
- core/api/services/pr_impact_pipeline/differ.py +421 -0
- core/api/services/pr_impact_pipeline/dispatcher.py +415 -0
- core/api/services/pr_impact_pipeline/gc.py +93 -0
- core/api/services/pr_impact_pipeline/languages.py +192 -0
- core/api/services/pr_impact_pipeline/parser.py +178 -0
- core/api/services/pr_impact_pipeline/writer.py +394 -0
- core/api/services/pr_service.py +1393 -0
- core/api/services/project_paths.py +70 -0
- core/api/services/project_status_updates.py +265 -0
- core/api/services/providers.py +276 -0
- core/api/services/push_service.py +170 -0
- core/api/services/reminder_service.py +89 -0
- core/api/services/runas.py +41 -0
- core/api/services/salience_service.py +69 -0
- core/api/services/security_collector.py +281 -0
- core/api/services/session_catalog.py +385 -0
- core/api/services/session_metrics_service.py +301 -0
- core/api/services/session_ops.py +272 -0
- core/api/services/session_state.py +173 -0
- core/api/services/share_links.py +222 -0
- core/api/services/task_transitions.py +146 -0
- core/api/services/terminal_metrics.py +462 -0
- core/api/services/terminal_metrics_dump.py +203 -0
- core/api/services/tmux.py +1205 -0
- core/api/services/webhook_service.py +422 -0
- core/api/services/workspace_sync.py +164 -0
- core/api/templates/__init__.py +1 -0
- core/api/templates/markdown_share.py +164 -0
- core/api/terminal.py +1031 -0
- core/api/tests/__init__.py +0 -0
- core/api/tests/test_agent_facing_auth_dependencies.py +132 -0
- core/api/tests/test_audit_permissions.py +133 -0
- core/api/tests/test_backfill_session_conversations.py +90 -0
- core/api/tests/test_backfill_working_seconds_msg.py +129 -0
- core/api/tests/test_claude_metrics.py +326 -0
- core/api/tests/test_codex_metrics.py +189 -0
- core/api/tests/test_finder_paths.py +74 -0
- core/api/tests/test_git_ops_merge.py +155 -0
- core/api/tests/test_learnings_check_search.py +81 -0
- core/api/tests/test_metrics_providers.py +133 -0
- core/api/tests/test_migration_087.py +164 -0
- core/api/tests/test_migration_088.py +94 -0
- core/api/tests/test_migration_089.py +116 -0
- core/api/tests/test_openai_responses.py +24 -0
- core/api/tests/test_opencode_metrics.py +740 -0
- core/api/tests/test_opencode_sessions.py +321 -0
- core/api/tests/test_pr_workflow_e2e.py +457 -0
- core/api/tests/test_projects_handoffs.py +31 -0
- core/api/tests/test_providers.py +138 -0
- core/api/tests/test_safety_bridge.py +347 -0
- core/api/tests/test_session_catalog.py +142 -0
- core/api/tests/test_session_conversations.py +512 -0
- core/api/tests/test_session_metrics_service.py +270 -0
- core/api/tests/test_session_resume_paths.py +548 -0
- core/api/tests/test_session_theme_mode_migration.py +56 -0
- core/api/tests/test_sessions_rbac.py +131 -0
- core/api/tests/test_share_edit.py +398 -0
- core/api/tests/test_share_repo.py +200 -0
- core/api/tests/test_terminal_session_manager.py +98 -0
- core/api/tests/test_terminal_upload.py +34 -0
- core/api/tests/test_tmux.py +272 -0
- core/api/tests/test_workspace_sync.py +186 -0
- core/api/tests/test_ws_ticket_in_memory.py +73 -0
- core/api/use_cases/__init__.py +11 -0
- core/api/use_cases/_context.py +89 -0
- core/api/use_cases/_errors.py +62 -0
- core/api/use_cases/_roles.py +16 -0
- core/api/use_cases/audit.py +171 -0
- core/api/use_cases/brain.py +1232 -0
- core/api/use_cases/costs.py +249 -0
- core/api/use_cases/graph.py +1153 -0
- core/api/use_cases/handoffs.py +506 -0
- core/api/use_cases/ingest_triage.py +1229 -0
- core/api/use_cases/learnings.py +538 -0
- core/api/use_cases/projects.py +705 -0
- core/api/use_cases/pull_requests.py +415 -0
- core/api/use_cases/search.py +926 -0
- core/api/use_cases/tasks.py +1495 -0
- core/api/visibility.py +141 -0
- core/cli/__init__.py +5 -0
- core/cli/_index_source.py +632 -0
- core/cli/_runtime_ctx.py +160 -0
- core/cli/_transmute.py +241 -0
- core/cli/marvis_doctor.py +704 -0
- core/cli/marvis_feedback.py +396 -0
- core/cli/marvis_governance.py +315 -0
- core/cli/marvis_hooks.py +515 -0
- core/cli/marvis_init.py +757 -0
- core/cli/marvis_mcp.py +401 -0
- core/cli/marvis_runtime.py +855 -0
- core/cli/marvis_telemetry.py +228 -0
- core/scripts/_drift_check.py +716 -0
- core/scripts/_frontmatter.py +66 -0
- core/scripts/_graph_writer.py +189 -0
- core/scripts/ast_parser.py +1553 -0
- core/scripts/install_hooks/__init__.py +1 -0
- core/scripts/install_hooks/_config.sh +109 -0
- core/scripts/install_hooks/block-dangerous-bash.sh +23 -0
- core/scripts/install_hooks/block-db-direct-write.sh +23 -0
- core/scripts/install_hooks/block-push-no-task.sh +23 -0
- core/scripts/install_hooks/block-staging-to-prod.sh +23 -0
- core/scripts/install_hooks/block-subtree-push.sh +23 -0
- core/scripts/install_hooks/config.json +53 -0
- core/scripts/install_hooks/enforce-no-merge-main.sh +23 -0
- core/scripts/install_hooks/enforce-worktree.sh +23 -0
- core/scripts/install_hooks/quality-gate.sh +170 -0
- core/scripts/install_hooks/safety_bridge.py +968 -0
- core/scripts/install_hooks/secret-scan.sh +23 -0
- core/scripts/migrate_spike_node_ids.py +122 -0
- core/scripts/populate_artifacts.py +2198 -0
- core/scripts/populate_cross_project.py +2457 -0
- core/scripts/populate_inbox_nodes.py +357 -0
- core/scripts/populate_pr_impact.py +267 -0
- core/scripts/populate_project_nodes.py +603 -0
- core/scripts/populate_touch_counter.py +337 -0
- core/scripts/reparse_failed.py +57 -0
- core/scripts/safety_bridge.py +968 -0
- core/telemetry/__init__.py +9 -0
- core/telemetry/client.py +405 -0
- core/telemetry/schema.py +122 -0
- core/wizard/__init__.py +65 -0
- core/wizard/byok_vault.py +147 -0
- core/wizard/defaults.py +58 -0
- core/wizard/state.py +117 -0
- core/wizard/steps.py +70 -0
- core/wizard/validation.py +136 -0
- marvisx_cli-0.1.0.dist-info/METADATA +201 -0
- marvisx_cli-0.1.0.dist-info/RECORD +587 -0
- marvisx_cli-0.1.0.dist-info/WHEEL +5 -0
- marvisx_cli-0.1.0.dist-info/entry_points.txt +3 -0
- marvisx_cli-0.1.0.dist-info/licenses/LICENSE +98 -0
- marvisx_cli-0.1.0.dist-info/top_level.txt +3 -0
- migrations/001_initial.sql +33 -0
- migrations/002_tasks.sql +30 -0
- migrations/003_session_management.sql +7 -0
- migrations/004_projects_comments.sql +65 -0
- migrations/005_session_intelligence.sql +15 -0
- migrations/006_settings.sql +12 -0
- migrations/007_task_scoring.sql +12 -0
- migrations/008_cost_tracking.sql +31 -0
- migrations/009_session_card_metrics.sql +3 -0
- migrations/010_monitoring.sql +55 -0
- migrations/012_agent_api.sql +21 -0
- migrations/013_session_complete.sql +8 -0
- migrations/015_pull_requests.sql +43 -0
- migrations/015_pull_requests_down.sql +5 -0
- migrations/016_users_raci.sql +116 -0
- migrations/017_task_cost_entries.sql +87 -0
- migrations/018_agents.sql +73 -0
- migrations/018_agents_down.sql +13 -0
- migrations/019_review_feedback.sql +18 -0
- migrations/020_pr_commit_sha.sql +4 -0
- migrations/021_webhook_events.sql +18 -0
- migrations/022_devx_agent_managed.sql +11 -0
- migrations/022_devx_agent_managed_down.sql +6 -0
- migrations/023_devx_p1_gate.sql +7 -0
- migrations/023_devx_p1_gate_down.sql +3 -0
- migrations/024_chat_messages.sql +16 -0
- migrations/024_pr_conversation_id.sql +8 -0
- migrations/024_task_indexes.sql +21 -0
- migrations/024_task_indexes_down.sql +7 -0
- migrations/025_audit_log.sql +17 -0
- migrations/026_agent_tokens.sql +20 -0
- migrations/027_teams_auth_phase_b.sql +35 -0
- migrations/028_learnings.sql +23 -0
- migrations/029_team_roles.sql +14 -0
- migrations/030_finder_pins.sql +10 -0
- migrations/031_pr_deploy_status.sql +9 -0
- migrations/032_task_reminders.sql +7 -0
- migrations/033_events_retry_count.sql +6 -0
- migrations/033_session_owner.sql +9 -0
- migrations/034_notifications.sql +38 -0
- migrations/035_shared_links.sql +15 -0
- migrations/036_session_index_upgrade.sql +29 -0
- migrations/037_pr_approval.sql +15 -0
- migrations/038_pr_submitted_by.sql +6 -0
- migrations/039_push_subscriptions.sql +17 -0
- migrations/040_semantic_search.sql +16 -0
- migrations/041_workspaces.sql +63 -0
- migrations/042_oidc_providers.sql +24 -0
- migrations/043_ci_checks.sql +31 -0
- migrations/044_agent_metrics.sql +30 -0
- migrations/045_documents_doc_type.sql +5 -0
- migrations/046_salience.sql +13 -0
- migrations/047_seed_missing_agents.sql +6 -0
- migrations/048_fix_agent_paths_roles.sql +5 -0
- migrations/049_agent_role_and_learnings_schema.sql +3 -0
- migrations/050_session_provider.sql +2 -0
- migrations/051_session_launch_profile.sql +4 -0
- migrations/052_session_theme_mode.sql +2 -0
- migrations/052_task_kind.sql +4 -0
- migrations/053_inbox_items.sql +31 -0
- migrations/054_inbox_triage_contract.sql +30 -0
- migrations/055_inbox_topic_treatment.sql +12 -0
- migrations/056_inbox_treatment_read_save.sql +57 -0
- migrations/057_session_theme_mode_backfill.sql +4 -0
- migrations/058_inbox_item_status_lifecycle.sql +13 -0
- migrations/059_inbox_tldr_and_source_scores.sql +18 -0
- migrations/060_newsletter.sql +16 -0
- migrations/061_inbox_redesign.sql +69 -0
- migrations/062_fix_inbox_sources_backfill.sql +37 -0
- migrations/063_task_completion_mode.sql +23 -0
- migrations/064_judge_mode_setting.sql +4 -0
- migrations/065_knowledge_graph_spike.sql +40 -0
- migrations/066_digest_ranking_inputs.sql +10 -0
- migrations/066_kg_artifact_nodes.sql +129 -0
- migrations/067_inbox_digest_selections.sql +28 -0
- migrations/067_kg_temporal.sql +53 -0
- migrations/068_inbox_digest_app_settings.sql +9 -0
- migrations/068_kg_touch_counter.sql +52 -0
- migrations/069_kg_doc_types.sql +117 -0
- migrations/070_digest_ranking_inputs_recovery.sql +3 -0
- migrations/071_inbox_digest_selections_recovery.sql +3 -0
- migrations/072_inbox_digest_app_settings_recovery.sql +3 -0
- migrations/073_kg_cross_project.sql +216 -0
- migrations/073_kg_cross_project_down.sql +77 -0
- migrations/074_kg_infra_types.sql +208 -0
- migrations/074_kg_infra_types_down.sql +80 -0
- migrations/075_kg_file_state_recovery.sql +35 -0
- migrations/075_kg_file_state_recovery_down.sql +5 -0
- migrations/076_kg_watcher_state.sql +33 -0
- migrations/076_kg_watcher_state_down.sql +3 -0
- migrations/077_kg_doc_types_extend.sql +226 -0
- migrations/077_kg_doc_types_extend_down.sql +80 -0
- migrations/078_kg_fts5.sql +102 -0
- migrations/078_kg_fts5_down.sql +14 -0
- migrations/079_kg_missing_indexes.sql +31 -0
- migrations/079_kg_missing_indexes_down.sql +10 -0
- migrations/080_kg_fts5_extended.sql +232 -0
- migrations/080_kg_fts5_extended_down.sql +25 -0
- migrations/081_kg_lens_indexes.sql +9 -0
- migrations/081_kg_lens_indexes_down.sql +3 -0
- migrations/082_kg_pins.sql +26 -0
- migrations/082_kg_pins_down.sql +14 -0
- migrations/083_kg_graph_nodes_degree.sql +20 -0
- migrations/083_kg_graph_nodes_degree_down.sql +15 -0
- migrations/084_drop_legacy_scheduler_tables.sql +58 -0
- migrations/084_drop_legacy_scheduler_tables_down.sql +112 -0
- migrations/085_kg_edge_resolves_to.sql +142 -0
- migrations/085_kg_edge_resolves_to_down.sql +66 -0
- migrations/086_project_status_updates_feed.sql +20 -0
- migrations/086_project_status_updates_feed_down.sql +36 -0
- migrations/087_session_metrics_dual.sql +50 -0
- migrations/087_session_metrics_dual_down.sql +21 -0
- migrations/088_rename_context_pct_legacy.sql +23 -0
- migrations/088_rename_context_pct_legacy_down.sql +8 -0
- migrations/089_session_metrics_equivalent_cost.sql +26 -0
- migrations/089_session_metrics_equivalent_cost_down.sql +11 -0
- migrations/090_kg_inbox_node_type.sql +26 -0
- migrations/090_kg_inbox_node_type_down.sql +20 -0
- migrations/091_kg_inbox_node_type_check.sql +265 -0
- migrations/091_kg_inbox_node_type_check_down.sql +129 -0
- migrations/092_sessions_activity_state_ts.sql +29 -0
- migrations/092_sessions_activity_state_ts_down.sql +14 -0
- migrations/093_sessions_activity_state_column.sql +29 -0
- migrations/093_sessions_activity_state_column_down.sql +10 -0
- migrations/094_ingest_pending.sql +55 -0
- migrations/094_ingest_pending_down.sql +15 -0
- migrations/095_kg_intent_first.sql +77 -0
- migrations/095_kg_intent_first_down.sql +25 -0
- migrations/096_kg_xlsx_artifact_prefix.sql +17 -0
- migrations/096_kg_xlsx_artifact_prefix_down.sql +11 -0
- migrations/097_ingest_change_history.sql +37 -0
- migrations/097_ingest_change_history_down.sql +13 -0
- migrations/098_kg_node_type_business.sql +254 -0
- migrations/098_kg_node_type_business_down.sql +195 -0
- migrations/099_kg_edges_restore_weight.sql +58 -0
- migrations/099_kg_edges_restore_weight_down.sql +12 -0
- migrations/100_kg_enriched_at.sql +25 -0
- migrations/100_kg_enriched_at_down.sql +12 -0
- migrations/101_local_llm_shadow_comparisons.sql +66 -0
- migrations/101_local_llm_shadow_comparisons_down.sql +15 -0
- migrations/102_promote_llm_costs.sql +69 -0
- migrations/102_promote_llm_costs_down.sql +19 -0
- migrations/103_ingest_skipped_log.sql +46 -0
- migrations/103_ingest_skipped_log_down.sql +15 -0
- migrations/120_docs_governance.sql +50 -0
- migrations/120_docs_governance_down.sql +11 -0
- migrations/121_notification_event_fk_cleanup.sql +21 -0
- migrations/121_notification_event_fk_cleanup_down.sql +10 -0
- migrations/122_docs_drift_history.sql +34 -0
- migrations/122_docs_drift_history_down.sql +15 -0
- migrations/123_ingest_parser_waiting_status.sql +69 -0
- migrations/123_ingest_parser_waiting_status_down.sql +69 -0
- migrations/124_heypocket_recordings.sql +63 -0
- migrations/124_heypocket_recordings_down.sql +13 -0
- migrations/125_kg_node_type_record.sql +219 -0
- migrations/125_kg_node_type_record_down.sql +205 -0
- migrations/126_ingest_terminal_upload_source_kind.sql +69 -0
- migrations/126_ingest_terminal_upload_source_kind_down.sql +69 -0
- migrations/127_brain_v1_substrate.sql +200 -0
- migrations/127_brain_v1_substrate_down.sql +32 -0
- migrations/128_brain_drift_signals.sql +157 -0
- migrations/128_brain_drift_signals_down.sql +23 -0
- migrations/129_brain_memory_operations.sql +232 -0
- migrations/129_brain_memory_operations_down.sql +27 -0
- migrations/130_brain_findings.sql +258 -0
- migrations/130_brain_findings_down.sql +29 -0
- migrations/132_kg_pr_modifies.sql +242 -0
- migrations/132_kg_pr_modifies_down.sql +99 -0
- migrations/133_brain_v1_2_direction_schema.sql +476 -0
- migrations/133_brain_v1_2_direction_schema_down.sql +273 -0
- migrations/134_brain_journal_narrative_polished.sql +8 -0
- migrations/134_brain_journal_narrative_polished_down.sql +6 -0
- migrations/135_kg_edges_provider.sql +21 -0
- migrations/136_documents_fts.sql +56 -0
- migrations/137_promote_llm_costs.sql +59 -0
- migrations/137_promote_llm_costs_down.sql +19 -0
- migrations/138_ingest_api_keys.sql +39 -0
- migrations/138_ingest_api_keys_down.sql +8 -0
- migrations/139_ingest_pending_ingress.sql +91 -0
- migrations/139_ingest_pending_ingress_down.sql +73 -0
- migrations/140_ingest_idempotency_quota.sql +45 -0
- migrations/140_ingest_idempotency_quota_down.sql +9 -0
- migrations/141_ingest_pending_metadata.sql +16 -0
- migrations/141_ingest_pending_metadata_down.sql +7 -0
- migrations/142_llm_function_config.sql +36 -0
- migrations/142_llm_function_config_down.sql +8 -0
- migrations/143_kg_code_embeddings.sql +25 -0
- migrations/143_kg_code_embeddings_down.sql +5 -0
- migrations/__init__.py +4 -0
- projects/_template/project.yaml +46 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
# v1.0.0 - 2026-05-26 - M1 CAPTURE U2 — ingress content negotiation + idempotency + rate/quota
|
|
2
|
+
"""Side logic for the unified POST /api/v1/ingest endpoint.
|
|
3
|
+
|
|
4
|
+
Deliberately imports nothing from ingest_triage (the endpoint handler lives
|
|
5
|
+
there and owns the path guards) to avoid an import cycle. This module owns:
|
|
6
|
+
- JSON content decode (text / base64; url deferred to v1.1)
|
|
7
|
+
- request fingerprinting for idempotency
|
|
8
|
+
- the claim-first idempotency protocol
|
|
9
|
+
- atomic per-key rate + daily-quota counters
|
|
10
|
+
- the ephemeral-table TTL sweep (called from the hourly _periodic_cleanup)
|
|
11
|
+
|
|
12
|
+
All writes go through the single SQLite writer (write_db / acquire_write_db),
|
|
13
|
+
learning 6130bc49. Counters are atomic check-and-increment so two concurrent
|
|
14
|
+
requests cannot both pass the gate.
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import base64
|
|
19
|
+
import hashlib
|
|
20
|
+
import json
|
|
21
|
+
import uuid
|
|
22
|
+
from datetime import datetime, timezone
|
|
23
|
+
|
|
24
|
+
import aiosqlite
|
|
25
|
+
|
|
26
|
+
from core.api.db import write_db
|
|
27
|
+
from core.api.models.ingest_keys import IngestJsonContent, IngestJsonPayload
|
|
28
|
+
from core.api.services.ingest.ignore_patterns import MAX_FILE_SIZE_BYTES
|
|
29
|
+
from core.api.use_cases._errors import ServiceError, ValidationError
|
|
30
|
+
|
|
31
|
+
# A JSON request body larger than this is rejected before parsing (raw-body cap).
|
|
32
|
+
MAX_JSON_BODY_BYTES = 32 * 1024 * 1024
|
|
33
|
+
MAX_INGRESS_FILE_COUNT = 50
|
|
34
|
+
MAX_IDEMPOTENCY_KEY_LEN = 255
|
|
35
|
+
IDEMPOTENCY_TTL_HOURS = 24
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# --------------------------------------------------------------------------- #
|
|
39
|
+
# Content decode
|
|
40
|
+
# --------------------------------------------------------------------------- #
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def decode_json_content(content: IngestJsonContent) -> tuple[bytes, str]:
|
|
44
|
+
"""Decode JSON content to (bytes, filename). Raises 4xx on bad input.
|
|
45
|
+
|
|
46
|
+
`url` is rejected (422) — server-side fetch is deferred to v1.1 (SSRF pivot).
|
|
47
|
+
Exactly one of text / base64 must be present.
|
|
48
|
+
"""
|
|
49
|
+
if content.url is not None:
|
|
50
|
+
raise ValidationError(
|
|
51
|
+
code="content_url_unsupported",
|
|
52
|
+
message=(
|
|
53
|
+
"content.url (server-side fetch) is not supported in M1 — it is an "
|
|
54
|
+
"SSRF pivot and unnecessary under the n8n-as-orchestrator model. "
|
|
55
|
+
"Fix: send the bytes inline as content.text or content.base64."
|
|
56
|
+
),
|
|
57
|
+
)
|
|
58
|
+
if content.text is not None and content.base64 is not None:
|
|
59
|
+
raise ValidationError(
|
|
60
|
+
code="content_text_base64_both",
|
|
61
|
+
message="Provide exactly one of content.text or content.base64, not both.",
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
if content.text is not None:
|
|
65
|
+
data = content.text.encode("utf-8")
|
|
66
|
+
default_ext = ".txt"
|
|
67
|
+
elif content.base64 is not None:
|
|
68
|
+
try:
|
|
69
|
+
# binascii.Error is a ValueError subclass, so this also covers bad padding.
|
|
70
|
+
data = base64.b64decode(content.base64, validate=True)
|
|
71
|
+
except (ValueError, TypeError):
|
|
72
|
+
raise ValidationError(
|
|
73
|
+
code="content_base64_invalid",
|
|
74
|
+
message="content.base64 is not valid base64.",
|
|
75
|
+
)
|
|
76
|
+
default_ext = ".bin"
|
|
77
|
+
else:
|
|
78
|
+
raise ValidationError(
|
|
79
|
+
code="content_missing",
|
|
80
|
+
message="content must provide text or base64.",
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
if not data:
|
|
84
|
+
raise ValidationError(code="content_empty", message="content is empty.")
|
|
85
|
+
# Cap the DECODED size (base64 inflates ~33%); the buffer is already in memory.
|
|
86
|
+
if len(data) > MAX_FILE_SIZE_BYTES:
|
|
87
|
+
err = ServiceError(
|
|
88
|
+
code="content_too_large",
|
|
89
|
+
message=f"content exceeds the maximum size of {MAX_FILE_SIZE_BYTES} bytes.",
|
|
90
|
+
)
|
|
91
|
+
err.http_status = 413 # preserve the original 413 status (was a 413 raise)
|
|
92
|
+
raise err
|
|
93
|
+
|
|
94
|
+
filename = content.filename or f"ingest-{uuid.uuid4().hex[:8]}{default_ext}"
|
|
95
|
+
return data, filename
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# --------------------------------------------------------------------------- #
|
|
99
|
+
# Request fingerprint (idempotency binding)
|
|
100
|
+
# --------------------------------------------------------------------------- #
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def json_request_fingerprint(payload: IngestJsonPayload) -> str:
|
|
104
|
+
canonical = json.dumps(
|
|
105
|
+
payload.model_dump(), sort_keys=True, separators=(",", ":"), ensure_ascii=False
|
|
106
|
+
)
|
|
107
|
+
return hashlib.sha256(canonical.encode("utf-8")).hexdigest()
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def multipart_request_fingerprint(project: str, files: list[tuple[str, int]]) -> str:
|
|
111
|
+
parts = [project, *sorted(f"{name}:{size}" for name, size in files)]
|
|
112
|
+
return hashlib.sha256("|".join(parts).encode("utf-8")).hexdigest()
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# --------------------------------------------------------------------------- #
|
|
116
|
+
# Idempotency (claim-first protocol)
|
|
117
|
+
# --------------------------------------------------------------------------- #
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
async def claim_idempotency(
|
|
121
|
+
key_id: str, idem_key: str, request_sha256: str
|
|
122
|
+
) -> tuple[str, str | None]:
|
|
123
|
+
"""Atomically claim an Idempotency-Key. Returns (state, response_json).
|
|
124
|
+
|
|
125
|
+
state ∈ {
|
|
126
|
+
"claimed" -> winner, proceed and finalize when done,
|
|
127
|
+
"replay" -> finished earlier, response_json is the stored snapshot,
|
|
128
|
+
"pending" -> a concurrent request holds the claim (caller should 409),
|
|
129
|
+
"mismatch" -> same key, different payload (caller should 422),
|
|
130
|
+
}
|
|
131
|
+
The INSERT is the first write of the whole request, so a duplicate cannot
|
|
132
|
+
create a second ingest_pending row (reads use the read-only pool and would
|
|
133
|
+
both miss).
|
|
134
|
+
"""
|
|
135
|
+
async with write_db(label="ingest.ingress.idem_claim") as db:
|
|
136
|
+
cur = await db.execute(
|
|
137
|
+
"""
|
|
138
|
+
INSERT INTO ingest_idempotency
|
|
139
|
+
(api_key_id, idem_key, request_sha256, status, created_at)
|
|
140
|
+
VALUES (?, ?, ?, 'pending', datetime('now'))
|
|
141
|
+
ON CONFLICT(api_key_id, idem_key) DO NOTHING
|
|
142
|
+
""",
|
|
143
|
+
(key_id, idem_key, request_sha256),
|
|
144
|
+
)
|
|
145
|
+
if cur.rowcount == 1:
|
|
146
|
+
return "claimed", None
|
|
147
|
+
|
|
148
|
+
async with db.execute(
|
|
149
|
+
"SELECT request_sha256, status, response_json FROM ingest_idempotency "
|
|
150
|
+
"WHERE api_key_id = ? AND idem_key = ?",
|
|
151
|
+
(key_id, idem_key),
|
|
152
|
+
) as c:
|
|
153
|
+
row = await c.fetchone()
|
|
154
|
+
|
|
155
|
+
if row is None:
|
|
156
|
+
# Lost the row to a concurrent TTL sweep between INSERT and SELECT — treat
|
|
157
|
+
# as a fresh claim (the duplicate-protection window already elapsed).
|
|
158
|
+
return "claimed", None
|
|
159
|
+
if row["request_sha256"] != request_sha256:
|
|
160
|
+
return "mismatch", None
|
|
161
|
+
if row["status"] == "done":
|
|
162
|
+
return "replay", row["response_json"]
|
|
163
|
+
return "pending", None
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
async def finalize_idempotency(key_id: str, idem_key: str, response_json: str) -> None:
|
|
167
|
+
async with write_db(label="ingest.ingress.idem_finalize") as db:
|
|
168
|
+
await db.execute(
|
|
169
|
+
"UPDATE ingest_idempotency SET status = 'done', response_json = ? "
|
|
170
|
+
"WHERE api_key_id = ? AND idem_key = ?",
|
|
171
|
+
(response_json, key_id, idem_key),
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
async def release_idempotency(key_id: str, idem_key: str) -> None:
|
|
176
|
+
"""Drop a still-pending claim so a failed request does not block retries."""
|
|
177
|
+
async with write_db(label="ingest.ingress.idem_release") as db:
|
|
178
|
+
await db.execute(
|
|
179
|
+
"DELETE FROM ingest_idempotency "
|
|
180
|
+
"WHERE api_key_id = ? AND idem_key = ? AND status = 'pending'",
|
|
181
|
+
(key_id, idem_key),
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
# --------------------------------------------------------------------------- #
|
|
186
|
+
# Rate + quota (atomic check-and-increment)
|
|
187
|
+
# --------------------------------------------------------------------------- #
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _utc_now() -> datetime:
|
|
191
|
+
return datetime.now(timezone.utc)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
async def check_and_increment_rate(key_id: str, limit_per_min: int) -> bool:
|
|
195
|
+
"""Per-minute rate gate. Returns True if allowed (and consumed), else False."""
|
|
196
|
+
bucket = _utc_now().strftime("%Y-%m-%dT%H:%M")
|
|
197
|
+
async with write_db(label="ingest.ingress.rate") as db:
|
|
198
|
+
await db.execute(
|
|
199
|
+
"INSERT INTO ingest_rate_usage (api_key_id, minute_bucket, count) "
|
|
200
|
+
"VALUES (?, ?, 0) ON CONFLICT(api_key_id, minute_bucket) DO NOTHING",
|
|
201
|
+
(key_id, bucket),
|
|
202
|
+
)
|
|
203
|
+
cur = await db.execute(
|
|
204
|
+
"UPDATE ingest_rate_usage SET count = count + 1 "
|
|
205
|
+
"WHERE api_key_id = ? AND minute_bucket = ? AND count < ?",
|
|
206
|
+
(key_id, bucket, limit_per_min),
|
|
207
|
+
)
|
|
208
|
+
return cur.rowcount == 1
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
async def check_and_increment_quota(key_id: str, daily_quota: int) -> bool:
|
|
212
|
+
"""Daily quota gate. Returns True if allowed (and consumed), else False."""
|
|
213
|
+
today = _utc_now().strftime("%Y-%m-%d")
|
|
214
|
+
async with write_db(label="ingest.ingress.quota") as db:
|
|
215
|
+
await db.execute(
|
|
216
|
+
"INSERT INTO ingest_quota_usage (api_key_id, usage_date, count) "
|
|
217
|
+
"VALUES (?, ?, 0) ON CONFLICT(api_key_id, usage_date) DO NOTHING",
|
|
218
|
+
(key_id, today),
|
|
219
|
+
)
|
|
220
|
+
cur = await db.execute(
|
|
221
|
+
"UPDATE ingest_quota_usage SET count = count + 1 "
|
|
222
|
+
"WHERE api_key_id = ? AND usage_date = ? AND count < ?",
|
|
223
|
+
(key_id, today, daily_quota),
|
|
224
|
+
)
|
|
225
|
+
return cur.rowcount == 1
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def seconds_to_next_minute() -> int:
|
|
229
|
+
return max(1, 60 - _utc_now().second)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def seconds_to_midnight_utc() -> int:
|
|
233
|
+
now = _utc_now()
|
|
234
|
+
secs = (23 - now.hour) * 3600 + (59 - now.minute) * 60 + (60 - now.second)
|
|
235
|
+
return max(1, secs)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
# --------------------------------------------------------------------------- #
|
|
239
|
+
# TTL sweep (called from main._periodic_cleanup — sleep-before-lock, 4d4278e4)
|
|
240
|
+
# --------------------------------------------------------------------------- #
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
async def cleanup_ingest_ephemeral(db: aiosqlite.Connection) -> tuple[int, int, int]:
|
|
244
|
+
"""Prune stale idempotency / rate / quota rows. Caller holds the writer.
|
|
245
|
+
|
|
246
|
+
Returns (idempotency_deleted, rate_deleted, quota_deleted).
|
|
247
|
+
"""
|
|
248
|
+
idem = await db.execute(
|
|
249
|
+
"DELETE FROM ingest_idempotency "
|
|
250
|
+
"WHERE created_at < datetime('now', ?)",
|
|
251
|
+
(f"-{IDEMPOTENCY_TTL_HOURS} hours",),
|
|
252
|
+
)
|
|
253
|
+
# Rate buckets only matter for the current minute; anything older is dead.
|
|
254
|
+
rate = await db.execute(
|
|
255
|
+
"DELETE FROM ingest_rate_usage "
|
|
256
|
+
"WHERE minute_bucket < strftime('%Y-%m-%dT%H:%M', datetime('now', '-2 minutes'))"
|
|
257
|
+
)
|
|
258
|
+
# Keep a week of daily quota rows for audit, then drop.
|
|
259
|
+
quota = await db.execute(
|
|
260
|
+
"DELETE FROM ingest_quota_usage "
|
|
261
|
+
"WHERE usage_date < strftime('%Y-%m-%d', datetime('now', '-7 days'))"
|
|
262
|
+
)
|
|
263
|
+
return idem.rowcount, rate.rowcount, quota.rowcount
|