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,99 @@
|
|
|
1
|
+
# DR5 — Procedure / Playbook Changed (sub-02 §5 DR5, CE4 axis=context).
|
|
2
|
+
# If a handoff_changed/learning_changed event's summary matches the compiled
|
|
3
|
+
# procedure regex AND no playbook/guide artifact updated in the same cycle,
|
|
4
|
+
# emit `playbook_changed`.
|
|
5
|
+
#
|
|
6
|
+
# CycleSnapshot pre-filters procedure_keyword_hits — scan ONLY summary text
|
|
7
|
+
# of digest events. NEVER read handoff files from disk (anti-pattern).
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
|
|
12
|
+
from core.api.models.brain import DriftSignal
|
|
13
|
+
from core.api.services.brain.cycle_snapshot import CycleSnapshot
|
|
14
|
+
from core.api.services.brain.rules._signals import build_signal
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
_ELIGIBLE_EVENT_TYPES = frozenset({"handoff_changed", "learning_changed"})
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _has_playbook_artifact_updated(snapshot: CycleSnapshot) -> bool:
|
|
21
|
+
"""Check if any doc_changed event in this cycle is a playbook/guide."""
|
|
22
|
+
for ev in snapshot.by_event_type.get("doc_changed", []):
|
|
23
|
+
ref = ev.source_ref.lower()
|
|
24
|
+
if "playbook" in ref or "guide" in ref:
|
|
25
|
+
return True
|
|
26
|
+
if isinstance(ev.evidence, dict):
|
|
27
|
+
kind = ev.evidence.get("kind")
|
|
28
|
+
if isinstance(kind, str) and kind in {"playbook", "guide"}:
|
|
29
|
+
return True
|
|
30
|
+
return False
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
async def build_signals(
|
|
34
|
+
snapshot: CycleSnapshot, *, run_id: str, now: datetime
|
|
35
|
+
) -> list[DriftSignal]:
|
|
36
|
+
signals: list[DriftSignal] = []
|
|
37
|
+
if not snapshot.procedure_keyword_hits:
|
|
38
|
+
return signals
|
|
39
|
+
playbook_updated = _has_playbook_artifact_updated(snapshot)
|
|
40
|
+
if playbook_updated:
|
|
41
|
+
return signals
|
|
42
|
+
|
|
43
|
+
seen_refs: set[str] = set()
|
|
44
|
+
for event in snapshot.procedure_keyword_hits:
|
|
45
|
+
if event.event_type not in _ELIGIBLE_EVENT_TYPES:
|
|
46
|
+
continue
|
|
47
|
+
if event.source_ref in seen_refs:
|
|
48
|
+
continue
|
|
49
|
+
seen_refs.add(event.source_ref)
|
|
50
|
+
|
|
51
|
+
if event.source_project:
|
|
52
|
+
scope_type, scope_key = "project", event.source_project
|
|
53
|
+
elif event.program_key:
|
|
54
|
+
scope_type, scope_key = "program", event.program_key
|
|
55
|
+
else:
|
|
56
|
+
scope_type, scope_key = "company", "__company__"
|
|
57
|
+
|
|
58
|
+
involved_projects = sorted(
|
|
59
|
+
{
|
|
60
|
+
p
|
|
61
|
+
for p in (event.source_project, event.target_project)
|
|
62
|
+
if isinstance(p, str) and p
|
|
63
|
+
}
|
|
64
|
+
)
|
|
65
|
+
modifiers = 0
|
|
66
|
+
if len(involved_projects) > 1:
|
|
67
|
+
modifiers += 1
|
|
68
|
+
|
|
69
|
+
observed_ref = f"event:{event.event_id}"
|
|
70
|
+
observed_delta = (
|
|
71
|
+
f"Procedure-change summary in {event.source_ref} ({event.event_type}) "
|
|
72
|
+
f"without matching playbook/guide update."
|
|
73
|
+
)
|
|
74
|
+
signals.append(
|
|
75
|
+
build_signal(
|
|
76
|
+
run_id=run_id,
|
|
77
|
+
cycle_key=snapshot.cycle_key,
|
|
78
|
+
detected_at=now,
|
|
79
|
+
rule_id="DR5",
|
|
80
|
+
scope_type=scope_type, # type: ignore[arg-type]
|
|
81
|
+
scope_key=scope_key,
|
|
82
|
+
program_key=event.program_key,
|
|
83
|
+
signal_type="playbook_changed",
|
|
84
|
+
expected_direction_source="doc",
|
|
85
|
+
expected_direction_ref=None,
|
|
86
|
+
observed_direction_ref=observed_ref,
|
|
87
|
+
observed_delta=observed_delta,
|
|
88
|
+
evidence_refs=[observed_ref],
|
|
89
|
+
severity_base="high",
|
|
90
|
+
drift_axis="context",
|
|
91
|
+
involved_projects=involved_projects,
|
|
92
|
+
severity_modifiers=modifiers,
|
|
93
|
+
observed_event=event,
|
|
94
|
+
)
|
|
95
|
+
)
|
|
96
|
+
return signals
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
__all__ = ["build_signals"]
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# DR6 — External Update Unpropagated (sub-02 §5 DR6, CE4 axis=context).
|
|
2
|
+
# If `external_update_seen` event has no linked internal artifact within
|
|
3
|
+
# `brain_drift_propagation_window_cycles` (default 3), emit
|
|
4
|
+
# `external_update_unpropagated`. Severity escalates per missed cycle.
|
|
5
|
+
#
|
|
6
|
+
# v1.01 note: external_update_seen producer is deferred (sub-01 §3). DR6 will
|
|
7
|
+
# be a no-op until that producer ships — tests treat it accordingly.
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from core.api.models.brain import DriftSignal, Severity
|
|
14
|
+
from core.api.services.brain.cycle_snapshot import CycleSnapshot, DigestEventRow
|
|
15
|
+
from core.api.services.brain.rules._signals import build_signal
|
|
16
|
+
|
|
17
|
+
# Severity ladder: medium → high @ 2 missed cycles → critical @ 3+ (max).
|
|
18
|
+
def _severity_for_missed(missed: int) -> Severity:
|
|
19
|
+
if missed >= 3:
|
|
20
|
+
return "critical"
|
|
21
|
+
if missed >= 2:
|
|
22
|
+
return "high"
|
|
23
|
+
return "medium"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _propagation_linked(event: DigestEventRow) -> bool:
|
|
27
|
+
if not isinstance(event.evidence, dict):
|
|
28
|
+
return False
|
|
29
|
+
if event.evidence.get("propagation_link_ref"):
|
|
30
|
+
return True
|
|
31
|
+
linked = event.evidence.get("linked_artifacts")
|
|
32
|
+
if isinstance(linked, list) and linked:
|
|
33
|
+
return True
|
|
34
|
+
return False
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _scope(event: DigestEventRow) -> tuple[str, str]:
|
|
38
|
+
if event.source_project:
|
|
39
|
+
return ("project", event.source_project)
|
|
40
|
+
if event.program_key:
|
|
41
|
+
return ("program", event.program_key)
|
|
42
|
+
return ("company", "__company__")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
async def build_signals(
|
|
46
|
+
snapshot: CycleSnapshot, *, run_id: str, now: datetime
|
|
47
|
+
) -> list[DriftSignal]:
|
|
48
|
+
signals: list[DriftSignal] = []
|
|
49
|
+
seen_refs: set[str] = set()
|
|
50
|
+
for event in snapshot.external_update_events:
|
|
51
|
+
if _propagation_linked(event):
|
|
52
|
+
continue
|
|
53
|
+
if event.source_ref in seen_refs:
|
|
54
|
+
continue
|
|
55
|
+
seen_refs.add(event.source_ref)
|
|
56
|
+
|
|
57
|
+
scope_type, scope_key = _scope(event)
|
|
58
|
+
observed_ref = f"event:{event.event_id}"
|
|
59
|
+
# Missed cycles: derived from evidence if available, else default 1.
|
|
60
|
+
missed = 1
|
|
61
|
+
if isinstance(event.evidence, dict):
|
|
62
|
+
raw = event.evidence.get("missed_cycles")
|
|
63
|
+
if isinstance(raw, int) and raw > 0:
|
|
64
|
+
missed = min(raw, 5)
|
|
65
|
+
severity_base = _severity_for_missed(missed)
|
|
66
|
+
observed_delta = (
|
|
67
|
+
f"External update {event.source_ref} observed without internal "
|
|
68
|
+
f"propagation (missed {missed} cycle(s))."
|
|
69
|
+
)
|
|
70
|
+
signals.append(
|
|
71
|
+
build_signal(
|
|
72
|
+
run_id=run_id,
|
|
73
|
+
cycle_key=snapshot.cycle_key,
|
|
74
|
+
detected_at=now,
|
|
75
|
+
rule_id="DR6",
|
|
76
|
+
scope_type=scope_type, # type: ignore[arg-type]
|
|
77
|
+
scope_key=scope_key,
|
|
78
|
+
program_key=event.program_key,
|
|
79
|
+
signal_type="external_update_unpropagated",
|
|
80
|
+
expected_direction_source="none",
|
|
81
|
+
expected_direction_ref=None,
|
|
82
|
+
observed_direction_ref=observed_ref,
|
|
83
|
+
observed_delta=observed_delta,
|
|
84
|
+
evidence_refs=[observed_ref],
|
|
85
|
+
severity_base=severity_base,
|
|
86
|
+
drift_axis="context",
|
|
87
|
+
involved_projects=sorted(
|
|
88
|
+
{
|
|
89
|
+
p
|
|
90
|
+
for p in (event.source_project, event.target_project)
|
|
91
|
+
if isinstance(p, str) and p
|
|
92
|
+
}
|
|
93
|
+
),
|
|
94
|
+
observed_event=event,
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
return signals
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
__all__ = ["build_signals"]
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# DR7 — Claimed Decision Gap (sub-02 §5 DR7, CE4 axis=intent).
|
|
2
|
+
# If an event has decision_marker in {approved, signed_off} AND no matching
|
|
3
|
+
# audit_log row with actor_user_id IS NOT NULL referencing the same artifact,
|
|
4
|
+
# emit `claimed_decision_gap`. Severity high; critical if claim is external.
|
|
5
|
+
#
|
|
6
|
+
# Cross-reference: rogue-agent-creates-task-and-PR learning (2026-05-12).
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
|
|
12
|
+
from core.api.db import acquire_db
|
|
13
|
+
from core.api.models.brain import DriftSignal
|
|
14
|
+
from core.api.services.brain.cycle_snapshot import CycleSnapshot, DigestEventRow
|
|
15
|
+
from core.api.services.brain.rules._signals import build_signal
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
_CLAIM_MARKERS = frozenset({"approved", "signed_off"})
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
async def _audit_lookup(source_refs: list[str]) -> set[str]:
|
|
23
|
+
"""Return the subset of source_refs that have a human-actor audit row."""
|
|
24
|
+
if not source_refs:
|
|
25
|
+
return set()
|
|
26
|
+
placeholders = ",".join("?" for _ in source_refs)
|
|
27
|
+
try:
|
|
28
|
+
async with acquire_db() as db:
|
|
29
|
+
cursor = await db.execute(
|
|
30
|
+
f"SELECT DISTINCT entity_ref FROM audit_log "
|
|
31
|
+
f"WHERE entity_ref IN ({placeholders}) "
|
|
32
|
+
f"AND actor_user_id IS NOT NULL",
|
|
33
|
+
source_refs,
|
|
34
|
+
)
|
|
35
|
+
rows = await cursor.fetchall()
|
|
36
|
+
except Exception as exc: # noqa: BLE001 — table may not exist
|
|
37
|
+
logger.debug("DR7: audit_log lookup skipped (%s)", exc)
|
|
38
|
+
return set()
|
|
39
|
+
return {row[0] for row in rows}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _scope(event: DigestEventRow) -> tuple[str, str]:
|
|
43
|
+
if event.source_project:
|
|
44
|
+
return ("project", event.source_project)
|
|
45
|
+
if event.program_key:
|
|
46
|
+
return ("program", event.program_key)
|
|
47
|
+
return ("company", "__company__")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
async def build_signals(
|
|
51
|
+
snapshot: CycleSnapshot, *, run_id: str, now: datetime
|
|
52
|
+
) -> list[DriftSignal]:
|
|
53
|
+
candidates: list[tuple[DigestEventRow, str]] = []
|
|
54
|
+
for event in snapshot.decision_marker_events:
|
|
55
|
+
marker = event.evidence.get("decision_marker") if isinstance(event.evidence, dict) else None
|
|
56
|
+
if not isinstance(marker, str) or marker not in _CLAIM_MARKERS:
|
|
57
|
+
continue
|
|
58
|
+
candidates.append((event, event.source_ref))
|
|
59
|
+
if not candidates:
|
|
60
|
+
return []
|
|
61
|
+
|
|
62
|
+
# Batched lookup (chunked IN, max 500 per chunk — §5 DR7).
|
|
63
|
+
refs = [src_ref for _, src_ref in candidates]
|
|
64
|
+
matched: set[str] = set()
|
|
65
|
+
chunk = 500
|
|
66
|
+
for i in range(0, len(refs), chunk):
|
|
67
|
+
matched |= await _audit_lookup(refs[i : i + chunk])
|
|
68
|
+
|
|
69
|
+
signals: list[DriftSignal] = []
|
|
70
|
+
seen: set[str] = set()
|
|
71
|
+
for event, src_ref in candidates:
|
|
72
|
+
if src_ref in matched:
|
|
73
|
+
continue
|
|
74
|
+
if src_ref in seen:
|
|
75
|
+
continue
|
|
76
|
+
seen.add(src_ref)
|
|
77
|
+
scope_type, scope_key = _scope(event)
|
|
78
|
+
observed_ref = f"event:{event.event_id}"
|
|
79
|
+
marker = (
|
|
80
|
+
event.evidence.get("decision_marker")
|
|
81
|
+
if isinstance(event.evidence, dict)
|
|
82
|
+
else "approved"
|
|
83
|
+
)
|
|
84
|
+
observed_delta = (
|
|
85
|
+
f"Decision claim '{marker}' on {src_ref} with no human actor in audit_log."
|
|
86
|
+
)
|
|
87
|
+
external = bool(
|
|
88
|
+
isinstance(event.evidence, dict)
|
|
89
|
+
and event.evidence.get("claimed_by_external")
|
|
90
|
+
)
|
|
91
|
+
severity_base = "critical" if external else "high"
|
|
92
|
+
signals.append(
|
|
93
|
+
build_signal(
|
|
94
|
+
run_id=run_id,
|
|
95
|
+
cycle_key=snapshot.cycle_key,
|
|
96
|
+
detected_at=now,
|
|
97
|
+
rule_id="DR7",
|
|
98
|
+
scope_type=scope_type, # type: ignore[arg-type]
|
|
99
|
+
scope_key=scope_key,
|
|
100
|
+
program_key=event.program_key,
|
|
101
|
+
signal_type="claimed_decision_gap",
|
|
102
|
+
expected_direction_source="none",
|
|
103
|
+
expected_direction_ref=None,
|
|
104
|
+
observed_direction_ref=observed_ref,
|
|
105
|
+
observed_delta=observed_delta,
|
|
106
|
+
evidence_refs=[observed_ref, f"audit_query:{src_ref}"],
|
|
107
|
+
severity_base=severity_base,
|
|
108
|
+
drift_axis="intent",
|
|
109
|
+
involved_projects=sorted(
|
|
110
|
+
{
|
|
111
|
+
p
|
|
112
|
+
for p in (event.source_project, event.target_project)
|
|
113
|
+
if isinstance(p, str) and p
|
|
114
|
+
}
|
|
115
|
+
),
|
|
116
|
+
observed_event=event,
|
|
117
|
+
decision_marker=marker if isinstance(marker, str) else None,
|
|
118
|
+
)
|
|
119
|
+
)
|
|
120
|
+
return signals
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
__all__ = ["build_signals"]
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# DR8 — Direction Misalignment (Brain v1.2, signal_type=direction_misalignment,
|
|
2
|
+
# axis=intent).
|
|
3
|
+
#
|
|
4
|
+
# Compares the project's declared direction (project_directions.summary +
|
|
5
|
+
# out_of_scope) against the events observed in the cycle. Emits a drift signal
|
|
6
|
+
# when the LLM classifier (tier-fast Gemma 4 E4B) reports status != aligned
|
|
7
|
+
# with confidence >= 0.85.
|
|
8
|
+
#
|
|
9
|
+
# If no direction record is present for the project, the rule is a no-op
|
|
10
|
+
# (returns []). Bootstrap phase populates direction via the bootstrap-apply
|
|
11
|
+
# script + Console Triage approval workflow.
|
|
12
|
+
#
|
|
13
|
+
# Pragmatic v1 implementation (without live tier-fast call):
|
|
14
|
+
# * Hard-codes a deterministic heuristic for tests: emit a high-confidence
|
|
15
|
+
# `direction_misalignment` signal when `events_count >= ACTIVITY_MIN` AND
|
|
16
|
+
# the project has a direction row. The orchestrator then derives the
|
|
17
|
+
# observed_delta + proposed_payload via the upstream LLM call when
|
|
18
|
+
# enabled (env BRAIN_DR8_LLM_ENABLED=true).
|
|
19
|
+
# * LLM polish for proposed_direction_update is invoked asynchronously
|
|
20
|
+
# via api.services.brain.llm.* (existing tier-write provider). If LLM
|
|
21
|
+
# unavailable, the signal is still emitted with a deterministic
|
|
22
|
+
# observed_delta and no proposed_payload.
|
|
23
|
+
#
|
|
24
|
+
# Decisione brainstorm §6: NO banned-words retry policy on the LLM prompt.
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import logging
|
|
29
|
+
import os
|
|
30
|
+
from datetime import datetime
|
|
31
|
+
from typing import Any
|
|
32
|
+
|
|
33
|
+
import aiosqlite
|
|
34
|
+
|
|
35
|
+
from core.api.db import acquire_db
|
|
36
|
+
from core.api.models.brain import DriftSignal
|
|
37
|
+
from core.api.services.brain.cycle_snapshot import CycleSnapshot, DigestEventRow
|
|
38
|
+
from core.api.services.brain.rules._signals import build_signal
|
|
39
|
+
|
|
40
|
+
logger = logging.getLogger(__name__)
|
|
41
|
+
|
|
42
|
+
ACTIVITY_MIN = 1 # any event vs declared direction triggers DR8 evaluation
|
|
43
|
+
CONFIDENCE_EMIT_THRESHOLD = 0.85
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
async def _fetch_directions(
|
|
47
|
+
*, db: aiosqlite.Connection
|
|
48
|
+
) -> dict[str, dict[str, Any]]:
|
|
49
|
+
"""Fetch every project_directions row keyed by project_slug."""
|
|
50
|
+
cur = await db.execute(
|
|
51
|
+
"SELECT project_slug, summary, out_of_scope, last_updated_at"
|
|
52
|
+
" FROM project_directions"
|
|
53
|
+
)
|
|
54
|
+
rows = await cur.fetchall()
|
|
55
|
+
await cur.close()
|
|
56
|
+
out: dict[str, dict[str, Any]] = {}
|
|
57
|
+
for row in rows:
|
|
58
|
+
slug = row[0]
|
|
59
|
+
out[slug] = {
|
|
60
|
+
"summary": row[1],
|
|
61
|
+
"out_of_scope": row[2],
|
|
62
|
+
"last_updated_at": row[3],
|
|
63
|
+
}
|
|
64
|
+
return out
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _events_evidence_refs(events: list[DigestEventRow], cap: int = 5) -> list[str]:
|
|
68
|
+
"""Pick up to `cap` event refs (commits, tasks, prs, handoffs) as evidence."""
|
|
69
|
+
refs: list[str] = []
|
|
70
|
+
for ev in events[:cap]:
|
|
71
|
+
if ev.source_ref:
|
|
72
|
+
refs.append(f"{ev.source_system or 'event'}:{ev.source_ref}")
|
|
73
|
+
return refs
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _summarise_events(events: list[DigestEventRow]) -> str:
|
|
77
|
+
"""Build a short observed-delta string from events."""
|
|
78
|
+
if not events:
|
|
79
|
+
return "no events in cycle window"
|
|
80
|
+
counters: dict[str, int] = {}
|
|
81
|
+
for ev in events:
|
|
82
|
+
kind = ev.event_type or "event"
|
|
83
|
+
counters[kind] = counters.get(kind, 0) + 1
|
|
84
|
+
parts = sorted(counters.items(), key=lambda kv: -kv[1])[:5]
|
|
85
|
+
return "events_in_cycle: " + ", ".join(f"{k}={v}" for k, v in parts)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
async def build_signals(
|
|
89
|
+
snapshot: CycleSnapshot, *, run_id: str, now: datetime
|
|
90
|
+
) -> list[DriftSignal]:
|
|
91
|
+
"""Emit DR8 direction_misalignment signals for projects with declared direction.
|
|
92
|
+
|
|
93
|
+
Strategy:
|
|
94
|
+
1. Load all project_directions rows (DB cache, fast SQL).
|
|
95
|
+
2. For each (project_scope, scope_key=slug) in CycleSnapshot.by_scope,
|
|
96
|
+
if a direction is present AND events_count >= ACTIVITY_MIN:
|
|
97
|
+
- Build deterministic observed_delta from events summary.
|
|
98
|
+
- Confidence defaults to 0.6 (heuristic). When LLM enabled (env
|
|
99
|
+
BRAIN_DR8_LLM_ENABLED=true), tier-fast classifier may bump to
|
|
100
|
+
>= 0.85 → emit. Without LLM, signals stay below threshold but
|
|
101
|
+
are still recorded (`state='open'`) so the orchestrator can
|
|
102
|
+
surface them for later promotion.
|
|
103
|
+
3. The CONFIDENCE_FLOOR enforcement happens upstream; we only need to
|
|
104
|
+
emit signals with confidence >= CONFIDENCE_EMIT_THRESHOLD to drive
|
|
105
|
+
L5 finding creation.
|
|
106
|
+
|
|
107
|
+
Returns drift signals (empty list when no direction or no events).
|
|
108
|
+
"""
|
|
109
|
+
signals: list[DriftSignal] = []
|
|
110
|
+
|
|
111
|
+
async with acquire_db() as db:
|
|
112
|
+
directions = await _fetch_directions(db=db)
|
|
113
|
+
|
|
114
|
+
if not directions:
|
|
115
|
+
logger.debug("DR8: no project_directions rows — skipping")
|
|
116
|
+
return signals
|
|
117
|
+
|
|
118
|
+
llm_enabled = os.environ.get("BRAIN_DR8_LLM_ENABLED", "").lower() in (
|
|
119
|
+
"1", "true", "yes",
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
for (scope_type, scope_key), events in snapshot.by_scope.items():
|
|
123
|
+
if scope_type != "project":
|
|
124
|
+
continue
|
|
125
|
+
direction = directions.get(scope_key)
|
|
126
|
+
if direction is None:
|
|
127
|
+
continue
|
|
128
|
+
if len(events) < ACTIVITY_MIN:
|
|
129
|
+
continue
|
|
130
|
+
|
|
131
|
+
observed_delta = _summarise_events(events)
|
|
132
|
+
evidence_refs = _events_evidence_refs(events)
|
|
133
|
+
expected_ref = f"project_directions:{scope_key}"
|
|
134
|
+
observed_ref = f"events_window:{scope_key}:{snapshot.cycle_key}"
|
|
135
|
+
|
|
136
|
+
# Confidence: deterministic baseline = 0.55 (medium).
|
|
137
|
+
# LLM-enabled path bumps to 0.9 when classifier returns status != aligned.
|
|
138
|
+
# Per brainstorm §6 we do NOT run a banned-words retry; the LLM result
|
|
139
|
+
# is accepted as-is.
|
|
140
|
+
confidence_estimate = 0.55
|
|
141
|
+
proposed_payload: dict[str, Any] | None = None
|
|
142
|
+
|
|
143
|
+
if llm_enabled:
|
|
144
|
+
# Lazy import — keep rule importable without LLM stack in tests.
|
|
145
|
+
try:
|
|
146
|
+
from core.api.services.brain.llm import get_brain_llm_service
|
|
147
|
+
# `get_brain_llm_service` is a sync singleton accessor — do NOT
|
|
148
|
+
# await it. The async surface is `llm.classify_direction_alignment`.
|
|
149
|
+
llm = get_brain_llm_service()
|
|
150
|
+
# Build prompt context — keep it compact (~2k tokens)
|
|
151
|
+
prompt_payload = {
|
|
152
|
+
"project_slug": scope_key,
|
|
153
|
+
"direction_summary": direction["summary"],
|
|
154
|
+
"direction_out_of_scope": direction["out_of_scope"],
|
|
155
|
+
"events_observed": observed_delta,
|
|
156
|
+
"events_refs": evidence_refs,
|
|
157
|
+
}
|
|
158
|
+
# Reuse the polish surface — caller wraps result in PolishResult.
|
|
159
|
+
# If grounding fails we fall back to deterministic confidence.
|
|
160
|
+
result = await llm.classify_direction_alignment(prompt_payload) # type: ignore[attr-defined]
|
|
161
|
+
if result is not None:
|
|
162
|
+
confidence_estimate = float(result.get("confidence", 0.55))
|
|
163
|
+
if result.get("status") and result.get("status") != "aligned":
|
|
164
|
+
proposed_payload = {
|
|
165
|
+
"status": result.get("status"),
|
|
166
|
+
"rationale": result.get("rationale", ""),
|
|
167
|
+
"proposed_summary": result.get("proposed_summary"),
|
|
168
|
+
"proposed_out_of_scope": result.get("proposed_out_of_scope"),
|
|
169
|
+
}
|
|
170
|
+
except Exception as exc: # noqa: BLE001 — LLM is best-effort
|
|
171
|
+
logger.warning(
|
|
172
|
+
"DR8: LLM classify failed for %s: %s — fallback heuristic",
|
|
173
|
+
scope_key, exc,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Emit signal regardless of confidence — L5 findings layer filters
|
|
177
|
+
# by >= 0.85; signals < 0.85 stay as audit-only rows.
|
|
178
|
+
program_key = None
|
|
179
|
+
try:
|
|
180
|
+
project_meta = snapshot.project_meta.get(scope_key, {}) # type: ignore[attr-defined]
|
|
181
|
+
program_key = project_meta.get("program")
|
|
182
|
+
except (AttributeError, Exception): # pragma: no cover
|
|
183
|
+
program_key = None
|
|
184
|
+
|
|
185
|
+
signal = build_signal(
|
|
186
|
+
run_id=run_id,
|
|
187
|
+
cycle_key=snapshot.cycle_key,
|
|
188
|
+
detected_at=now,
|
|
189
|
+
rule_id="DR8",
|
|
190
|
+
scope_type="project",
|
|
191
|
+
scope_key=scope_key,
|
|
192
|
+
program_key=program_key,
|
|
193
|
+
signal_type="direction_misalignment",
|
|
194
|
+
expected_direction_source="doc",
|
|
195
|
+
expected_direction_ref=expected_ref,
|
|
196
|
+
observed_direction_ref=observed_ref,
|
|
197
|
+
observed_delta=observed_delta,
|
|
198
|
+
evidence_refs=evidence_refs or [expected_ref],
|
|
199
|
+
severity_base="medium",
|
|
200
|
+
drift_axis="intent",
|
|
201
|
+
involved_projects=[scope_key],
|
|
202
|
+
)
|
|
203
|
+
# Override the heuristic confidence with our LLM-aware estimate.
|
|
204
|
+
# `build_signal` already clamps to [FLOOR, 1.0].
|
|
205
|
+
signal_dict = signal.model_dump()
|
|
206
|
+
signal_dict["confidence"] = max(0.3, min(1.0, confidence_estimate))
|
|
207
|
+
signal = type(signal)(**signal_dict)
|
|
208
|
+
|
|
209
|
+
# Attach proposed_payload via evidence_json passthrough — orchestrator
|
|
210
|
+
# writes it to brain_drift_signals.evidence_json so L5 can read it.
|
|
211
|
+
if proposed_payload is not None:
|
|
212
|
+
try:
|
|
213
|
+
import json as _json
|
|
214
|
+
# Mutate the signal's evidence to include the proposed payload
|
|
215
|
+
# under a reserved key the L5 layer recognises. This stays
|
|
216
|
+
# backward-compatible (older rules emit list[str] evidence).
|
|
217
|
+
signal_dict = signal.model_dump()
|
|
218
|
+
ev = list(signal_dict.get("evidence", []))
|
|
219
|
+
ev.append(f"proposed_payload_json:{_json.dumps(proposed_payload, sort_keys=True)}")
|
|
220
|
+
signal_dict["evidence"] = ev
|
|
221
|
+
signal = type(signal)(**signal_dict)
|
|
222
|
+
except Exception: # pragma: no cover
|
|
223
|
+
pass
|
|
224
|
+
|
|
225
|
+
signals.append(signal)
|
|
226
|
+
|
|
227
|
+
return signals
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
__all__ = ["build_signals", "ACTIVITY_MIN", "CONFIDENCE_EMIT_THRESHOLD"]
|