marvisx-cli 0.2.0__tar.gz → 0.2.1__tar.gz

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.
Files changed (603) hide show
  1. {marvisx_cli-0.2.0/marvisx_cli.egg-info → marvisx_cli-0.2.1}/PKG-INFO +3 -1
  2. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/rbac.py +8 -2
  3. marvisx_cli-0.2.1/core/api/tests/test_require_scope_empty_deny.py +65 -0
  4. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/tests/test_safety_bridge.py +57 -0
  5. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/cli/marvis_init.py +12 -5
  6. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/scripts/safety_bridge.py +11 -0
  7. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/telemetry/client.py +1 -1
  8. marvisx_cli-0.2.1/core/wizard/byok_vault.py +384 -0
  9. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1/marvisx_cli.egg-info}/PKG-INFO +3 -1
  10. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/marvisx_cli.egg-info/SOURCES.txt +3 -0
  11. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/marvisx_cli.egg-info/requires.txt +3 -0
  12. marvisx_cli-0.2.1/migrations/145_audit_log_immutable.sql +23 -0
  13. marvisx_cli-0.2.1/migrations/145_audit_log_immutable_down.sql +4 -0
  14. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/pyproject.toml +6 -1
  15. marvisx_cli-0.2.0/core/wizard/byok_vault.py +0 -147
  16. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/LICENSE +0 -0
  17. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/MANIFEST.in +0 -0
  18. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/README.md +0 -0
  19. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/__init__.py +0 -0
  20. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/agents/__init__.py +0 -0
  21. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/agents/session_health.py +0 -0
  22. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/agents/session_manager.py +0 -0
  23. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/bin/marvisx-state-hook.py +0 -0
  24. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/config.py +0 -0
  25. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/db.py +0 -0
  26. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/dependencies/__init__.py +0 -0
  27. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/dependencies/tenant.py +0 -0
  28. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/main.py +0 -0
  29. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/mcp/__init__.py +0 -0
  30. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/mcp/_adapter.py +0 -0
  31. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/mcp/server.py +0 -0
  32. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/mcp/tools/__init__.py +0 -0
  33. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/mcp/tools/brain.py +0 -0
  34. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/mcp/tools/graph.py +0 -0
  35. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/mcp/tools/handoffs.py +0 -0
  36. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/mcp/tools/ingest.py +0 -0
  37. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/mcp/tools/learnings.py +0 -0
  38. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/mcp/tools/projects.py +0 -0
  39. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/mcp/tools/pull_requests.py +0 -0
  40. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/mcp/tools/safety.py +0 -0
  41. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/mcp/tools/search.py +0 -0
  42. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/mcp/tools/tasks.py +0 -0
  43. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/middleware/__init__.py +0 -0
  44. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/middleware/tool_call_audit.py +0 -0
  45. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/models/__init__.py +0 -0
  46. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/models/auth.py +0 -0
  47. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/models/brain.py +0 -0
  48. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/models/common.py +0 -0
  49. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/models/costs.py +0 -0
  50. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/models/graph.py +0 -0
  51. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/models/graph_cosmo.py +0 -0
  52. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/models/graph_pr_impact.py +0 -0
  53. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/models/graph_ux.py +0 -0
  54. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/models/inbox.py +0 -0
  55. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/models/ingest_keys.py +0 -0
  56. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/models/kg.py +0 -0
  57. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/models/llm_config.py +0 -0
  58. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/models/monitoring.py +0 -0
  59. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/models/projects.py +0 -0
  60. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/models/search.py +0 -0
  61. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/models/sessions.py +0 -0
  62. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/models/tasks.py +0 -0
  63. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/models/teams.py +0 -0
  64. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/models/users.py +0 -0
  65. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/observability/__init__.py +0 -0
  66. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/observability/tracing.py +0 -0
  67. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/paths.py +0 -0
  68. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/rate_limit.py +0 -0
  69. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/__init__.py +0 -0
  70. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/_adapter.py +0 -0
  71. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/admin_pr_impact.py +0 -0
  72. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/admin_settings.py +0 -0
  73. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/agent.py +0 -0
  74. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/agent_tokens.py +0 -0
  75. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/app_settings.py +0 -0
  76. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/audit.py +0 -0
  77. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/auth.py +0 -0
  78. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/bench.py +0 -0
  79. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/brain.py +0 -0
  80. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/brain_directions.py +0 -0
  81. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/ci_checks.py +0 -0
  82. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/comments.py +0 -0
  83. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/costs.py +0 -0
  84. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/docs_coverage.py +0 -0
  85. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/docs_governance.py +0 -0
  86. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/documents.py +0 -0
  87. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/files.py +0 -0
  88. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/finder.py +0 -0
  89. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/graph.py +0 -0
  90. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/handoffs.py +0 -0
  91. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/inbox.py +0 -0
  92. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/ingest_api_keys.py +0 -0
  93. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/ingest_triage.py +0 -0
  94. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/judge.py +0 -0
  95. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/kg.py +0 -0
  96. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/learnings.py +0 -0
  97. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/llm_config.py +0 -0
  98. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/monitoring.py +0 -0
  99. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/notifications.py +0 -0
  100. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/pr_impact.py +0 -0
  101. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/projects.py +0 -0
  102. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/pull_requests.py +0 -0
  103. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/push.py +0 -0
  104. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/raci.py +0 -0
  105. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/search.py +0 -0
  106. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/sessions.py +0 -0
  107. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/settings.py +0 -0
  108. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/share_repo.py +0 -0
  109. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/status_updates.py +0 -0
  110. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/tags.py +0 -0
  111. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/tasks.py +0 -0
  112. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/teams.py +0 -0
  113. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/terminal.py +0 -0
  114. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/users.py +0 -0
  115. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/routers/webhooks.py +0 -0
  116. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/runtime_settings.py +0 -0
  117. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/security.py +0 -0
  118. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/__init__.py +0 -0
  119. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/audit.py +0 -0
  120. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/auto_approval.py +0 -0
  121. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/__init__.py +0 -0
  122. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/baseline.py +0 -0
  123. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/capabilities.py +0 -0
  124. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/cascade_rollup.py +0 -0
  125. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/compound_bridge.py +0 -0
  126. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/cycle.py +0 -0
  127. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/cycle_snapshot.py +0 -0
  128. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/digest_collector.py +0 -0
  129. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/direction.py +0 -0
  130. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/drift.py +0 -0
  131. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/drift_router.py +0 -0
  132. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/edge_metrics.py +0 -0
  133. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/events_reader.py +0 -0
  134. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/findings.py +0 -0
  135. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/findings_reader.py +0 -0
  136. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/jobs.py +0 -0
  137. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/journal.py +0 -0
  138. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/knowledge_forms.py +0 -0
  139. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/llm/__init__.py +0 -0
  140. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/llm/_runner.py +0 -0
  141. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/llm/base.py +0 -0
  142. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/llm/cache.py +0 -0
  143. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/llm/constants.py +0 -0
  144. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/llm/direction_alignment.py +0 -0
  145. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/llm/factory.py +0 -0
  146. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/llm/finding_reasoning.py +0 -0
  147. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/llm/finding_summary.py +0 -0
  148. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/llm/grounding.py +0 -0
  149. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/llm/journal_polish.py +0 -0
  150. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/llm/local_gateway.py +0 -0
  151. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/llm/parsers.py +0 -0
  152. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/llm/router_glue.py +0 -0
  153. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/memory_ops.py +0 -0
  154. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/models.py +0 -0
  155. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/owner_hint.py +0 -0
  156. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/recap.py +0 -0
  157. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/rules/__init__.py +0 -0
  158. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/rules/_signals.py +0 -0
  159. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/rules/dr1_activity_without_status.py +0 -0
  160. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/rules/dr2_decision_without_adr.py +0 -0
  161. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/rules/dr3_stale_open_loop.py +0 -0
  162. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/rules/dr4_docs_governance_drift.py +0 -0
  163. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/rules/dr5_playbook_changed.py +0 -0
  164. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/rules/dr6_external_update_unpropagated.py +0 -0
  165. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/rules/dr7_claimed_decision_gap.py +0 -0
  166. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/rules/dr8_direction_misalignment.py +0 -0
  167. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/runs_reader.py +0 -0
  168. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/scope.py +0 -0
  169. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/sources/__init__.py +0 -0
  170. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/sources/base.py +0 -0
  171. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/sources/git_kg.py +0 -0
  172. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/sources/handoffs.py +0 -0
  173. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/sources/ingestor.py +0 -0
  174. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/sources/learnings.py +0 -0
  175. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/sources/pir_tasks.py +0 -0
  176. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/watermarks.py +0 -0
  177. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/brain/ws_emitter.py +0 -0
  178. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/cc_tasks_reader.py +0 -0
  179. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ci_service.py +0 -0
  180. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/claude_metrics.py +0 -0
  181. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/codex_metrics.py +0 -0
  182. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/conversation_reader.py +0 -0
  183. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/cost_service.py +0 -0
  184. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/crypto.py +0 -0
  185. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/docs_governance/__init__.py +0 -0
  186. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/docs_governance/confidence.py +0 -0
  187. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/docs_governance/config.py +0 -0
  188. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/docs_governance/enrichment.py +0 -0
  189. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/docs_governance/frontmatter_validator.py +0 -0
  190. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/docs_governance/hard_gates.py +0 -0
  191. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/docs_governance/triage_orchestrator.py +0 -0
  192. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/embedding_internal.py +0 -0
  193. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/embedding_service.py +0 -0
  194. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/event_dispatcher.py +0 -0
  195. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/events.py +0 -0
  196. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/git_ops.py +0 -0
  197. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/graph_cosmo_service.py +0 -0
  198. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/graph_ranker.py +0 -0
  199. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/graph_service.py +0 -0
  200. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/inbox.py +0 -0
  201. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/inbox_digest.py +0 -0
  202. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/inbox_digest_deep_research.py +0 -0
  203. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/inbox_digest_jobs.py +0 -0
  204. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/inbox_gmail_sync.py +0 -0
  205. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/inbox_llm_classifier.py +0 -0
  206. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/inbox_source_identity.py +0 -0
  207. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/inbox_sources.py +0 -0
  208. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/inbox_taxonomy.py +0 -0
  209. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/inbox_tldr.py +0 -0
  210. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/inbox_triage.py +0 -0
  211. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/__init__.py +0 -0
  212. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/api_key_auth.py +0 -0
  213. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/auto_approve.py +0 -0
  214. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/classifier.py +0 -0
  215. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/confidence.py +0 -0
  216. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/dispatch.py +0 -0
  217. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/embedding_router.py +0 -0
  218. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/events.py +0 -0
  219. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/ignore_patterns.py +0 -0
  220. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/image_probe.py +0 -0
  221. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/ingress.py +0 -0
  222. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/insert_saga.py +0 -0
  223. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/llm/__init__.py +0 -0
  224. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/llm/anthropic_haiku.py +0 -0
  225. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/llm/base.py +0 -0
  226. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/llm/byok_provider.py +0 -0
  227. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/llm/classification_context.py +0 -0
  228. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/llm/config_store.py +0 -0
  229. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/llm/factory.py +0 -0
  230. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/llm/kg_enricher.py +0 -0
  231. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/llm/local_gateway.py +0 -0
  232. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/llm/local_vllm.py +0 -0
  233. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/llm/openai_nano.py +0 -0
  234. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/lock_advisory.py +0 -0
  235. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/parser_router.py +0 -0
  236. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/parsers/__init__.py +0 -0
  237. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/parsers/docling_parser.py +0 -0
  238. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/parsers/docparse_gateway.py +0 -0
  239. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/parsers/docx_parser.py +0 -0
  240. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/parsers/folder_unpacker.py +0 -0
  241. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/parsers/gateway_aux.py +0 -0
  242. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/parsers/image_parser.py +0 -0
  243. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/parsers/internal_markdown.py +0 -0
  244. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/parsers/ocr_gateway.py +0 -0
  245. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/parsers/ocr_pdf_parser.py +0 -0
  246. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/parsers/pdf_types.py +0 -0
  247. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/parsers/transcript_parser.py +0 -0
  248. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/parsers/vision_gateway.py +0 -0
  249. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/parsers/xlsx_parser.py +0 -0
  250. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/parsers/zip_unpacker.py +0 -0
  251. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/preflight.py +0 -0
  252. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/retry_voyage.py +0 -0
  253. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/routing_policy.py +0 -0
  254. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/serializers/__init__.py +0 -0
  255. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/serializers/xlsx_to_markdown.py +0 -0
  256. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/skip_log.py +0 -0
  257. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/ingest/watcher.py +0 -0
  258. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/kg/__init__.py +0 -0
  259. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/kg/audit.py +0 -0
  260. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/kg/hybrid_search.py +0 -0
  261. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/kg/lens.py +0 -0
  262. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/kg/pr_impact.py +0 -0
  263. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/kg/queries.py +0 -0
  264. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/kg/ranking.py +0 -0
  265. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/kg/rrf.py +0 -0
  266. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/kg_watcher_control.py +0 -0
  267. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/local_llm/__init__.py +0 -0
  268. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/local_llm/async_client.py +0 -0
  269. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/local_llm/client.py +0 -0
  270. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/local_llm/url_validator.py +0 -0
  271. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/metrics_collector.py +0 -0
  272. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/metrics_providers.py +0 -0
  273. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/model_registry.py +0 -0
  274. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/model_router.py +0 -0
  275. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/n8n_client.py +0 -0
  276. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/newsletter_llm_gateway.py +0 -0
  277. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/notification_service.py +0 -0
  278. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/openai_responses.py +0 -0
  279. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/opencode_metrics.py +0 -0
  280. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/opencode_sessions.py +0 -0
  281. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/pii_redactor.py +0 -0
  282. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/pr_impact_pipeline/__init__.py +0 -0
  283. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/pr_impact_pipeline/differ.py +0 -0
  284. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/pr_impact_pipeline/dispatcher.py +0 -0
  285. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/pr_impact_pipeline/gc.py +0 -0
  286. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/pr_impact_pipeline/languages.py +0 -0
  287. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/pr_impact_pipeline/parser.py +0 -0
  288. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/pr_impact_pipeline/writer.py +0 -0
  289. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/pr_service.py +0 -0
  290. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/project_paths.py +0 -0
  291. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/project_status_updates.py +0 -0
  292. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/providers.py +0 -0
  293. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/push_service.py +0 -0
  294. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/reminder_service.py +0 -0
  295. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/runas.py +0 -0
  296. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/salience_service.py +0 -0
  297. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/security_collector.py +0 -0
  298. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/session_catalog.py +0 -0
  299. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/session_metrics_service.py +0 -0
  300. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/session_ops.py +0 -0
  301. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/session_state.py +0 -0
  302. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/share_links.py +0 -0
  303. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/task_transitions.py +0 -0
  304. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/terminal_metrics.py +0 -0
  305. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/terminal_metrics_dump.py +0 -0
  306. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/tmux.py +0 -0
  307. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/webhook_service.py +0 -0
  308. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/services/workspace_sync.py +0 -0
  309. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/templates/__init__.py +0 -0
  310. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/templates/markdown_share.py +0 -0
  311. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/terminal.py +0 -0
  312. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/tests/__init__.py +0 -0
  313. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/tests/test_agent_facing_auth_dependencies.py +0 -0
  314. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/tests/test_audit_permissions.py +0 -0
  315. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/tests/test_backfill_session_conversations.py +0 -0
  316. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/tests/test_backfill_working_seconds_msg.py +0 -0
  317. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/tests/test_claude_metrics.py +0 -0
  318. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/tests/test_codex_metrics.py +0 -0
  319. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/tests/test_finder_paths.py +0 -0
  320. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/tests/test_git_ops_merge.py +0 -0
  321. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/tests/test_learnings_check_search.py +0 -0
  322. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/tests/test_metrics_providers.py +0 -0
  323. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/tests/test_migration_087.py +0 -0
  324. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/tests/test_migration_088.py +0 -0
  325. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/tests/test_migration_089.py +0 -0
  326. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/tests/test_openai_responses.py +0 -0
  327. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/tests/test_opencode_metrics.py +0 -0
  328. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/tests/test_opencode_sessions.py +0 -0
  329. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/tests/test_pr_workflow_e2e.py +0 -0
  330. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/tests/test_projects_handoffs.py +0 -0
  331. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/tests/test_providers.py +0 -0
  332. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/tests/test_session_catalog.py +0 -0
  333. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/tests/test_session_conversations.py +0 -0
  334. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/tests/test_session_metrics_service.py +0 -0
  335. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/tests/test_session_resume_paths.py +0 -0
  336. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/tests/test_session_theme_mode_migration.py +0 -0
  337. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/tests/test_sessions_rbac.py +0 -0
  338. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/tests/test_share_edit.py +0 -0
  339. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/tests/test_share_repo.py +0 -0
  340. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/tests/test_terminal_session_manager.py +0 -0
  341. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/tests/test_terminal_upload.py +0 -0
  342. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/tests/test_tmux.py +0 -0
  343. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/tests/test_workspace_sync.py +0 -0
  344. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/tests/test_ws_ticket_in_memory.py +0 -0
  345. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/use_cases/__init__.py +0 -0
  346. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/use_cases/_context.py +0 -0
  347. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/use_cases/_errors.py +0 -0
  348. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/use_cases/_roles.py +0 -0
  349. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/use_cases/audit.py +0 -0
  350. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/use_cases/brain.py +0 -0
  351. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/use_cases/costs.py +0 -0
  352. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/use_cases/graph.py +0 -0
  353. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/use_cases/handoffs.py +0 -0
  354. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/use_cases/ingest_triage.py +0 -0
  355. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/use_cases/learnings.py +0 -0
  356. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/use_cases/projects.py +0 -0
  357. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/use_cases/pull_requests.py +0 -0
  358. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/use_cases/search.py +0 -0
  359. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/use_cases/tasks.py +0 -0
  360. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/api/visibility.py +0 -0
  361. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/cli/README.md +0 -0
  362. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/cli/__init__.py +0 -0
  363. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/cli/_index_source.py +0 -0
  364. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/cli/_runtime_ctx.py +0 -0
  365. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/cli/_transmute.py +0 -0
  366. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/cli/marvis_account.py +0 -0
  367. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/cli/marvis_doctor.py +0 -0
  368. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/cli/marvis_feedback.py +0 -0
  369. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/cli/marvis_governance.py +0 -0
  370. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/cli/marvis_hooks.py +0 -0
  371. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/cli/marvis_mcp.py +0 -0
  372. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/cli/marvis_runtime.py +0 -0
  373. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/cli/marvis_telemetry.py +0 -0
  374. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/scripts/_drift_check.py +0 -0
  375. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/scripts/_frontmatter.py +0 -0
  376. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/scripts/_graph_writer.py +0 -0
  377. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/scripts/ast_parser.py +0 -0
  378. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/scripts/install_hooks/__init__.py +0 -0
  379. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/scripts/install_hooks/_config.sh +0 -0
  380. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/scripts/install_hooks/block-dangerous-bash.sh +0 -0
  381. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/scripts/install_hooks/block-db-direct-write.sh +0 -0
  382. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/scripts/install_hooks/block-push-no-task.sh +0 -0
  383. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/scripts/install_hooks/block-staging-to-prod.sh +0 -0
  384. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/scripts/install_hooks/block-subtree-push.sh +0 -0
  385. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/scripts/install_hooks/config.json +0 -0
  386. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/scripts/install_hooks/enforce-no-merge-main.sh +0 -0
  387. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/scripts/install_hooks/enforce-worktree.sh +0 -0
  388. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/scripts/install_hooks/quality-gate.sh +0 -0
  389. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/scripts/install_hooks/safety_bridge.py +0 -0
  390. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/scripts/install_hooks/secret-scan.sh +0 -0
  391. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/scripts/migrate_spike_node_ids.py +0 -0
  392. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/scripts/populate_artifacts.py +0 -0
  393. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/scripts/populate_cross_project.py +0 -0
  394. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/scripts/populate_inbox_nodes.py +0 -0
  395. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/scripts/populate_pr_impact.py +0 -0
  396. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/scripts/populate_project_nodes.py +0 -0
  397. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/scripts/populate_touch_counter.py +0 -0
  398. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/scripts/reparse_failed.py +0 -0
  399. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/telemetry/__init__.py +0 -0
  400. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/telemetry/entitlements.py +0 -0
  401. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/telemetry/rollup.py +0 -0
  402. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/telemetry/schema.py +0 -0
  403. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/telemetry/sender.py +0 -0
  404. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/wizard/__init__.py +0 -0
  405. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/wizard/defaults.py +0 -0
  406. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/wizard/state.py +0 -0
  407. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/wizard/steps.py +0 -0
  408. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/core/wizard/validation.py +0 -0
  409. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/marvisx_cli.egg-info/dependency_links.txt +0 -0
  410. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/marvisx_cli.egg-info/entry_points.txt +0 -0
  411. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/marvisx_cli.egg-info/top_level.txt +0 -0
  412. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/001_initial.sql +0 -0
  413. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/002_tasks.sql +0 -0
  414. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/003_session_management.sql +0 -0
  415. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/004_projects_comments.sql +0 -0
  416. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/005_session_intelligence.sql +0 -0
  417. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/006_settings.sql +0 -0
  418. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/007_task_scoring.sql +0 -0
  419. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/008_cost_tracking.sql +0 -0
  420. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/009_session_card_metrics.sql +0 -0
  421. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/010_monitoring.sql +0 -0
  422. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/012_agent_api.sql +0 -0
  423. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/013_session_complete.sql +0 -0
  424. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/015_pull_requests.sql +0 -0
  425. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/015_pull_requests_down.sql +0 -0
  426. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/016_users_raci.sql +0 -0
  427. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/017_task_cost_entries.sql +0 -0
  428. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/018_agents.sql +0 -0
  429. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/018_agents_down.sql +0 -0
  430. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/019_review_feedback.sql +0 -0
  431. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/020_pr_commit_sha.sql +0 -0
  432. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/021_webhook_events.sql +0 -0
  433. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/022_devx_agent_managed.sql +0 -0
  434. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/022_devx_agent_managed_down.sql +0 -0
  435. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/023_devx_p1_gate.sql +0 -0
  436. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/023_devx_p1_gate_down.sql +0 -0
  437. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/024_chat_messages.sql +0 -0
  438. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/024_pr_conversation_id.sql +0 -0
  439. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/024_task_indexes.sql +0 -0
  440. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/024_task_indexes_down.sql +0 -0
  441. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/025_audit_log.sql +0 -0
  442. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/026_agent_tokens.sql +0 -0
  443. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/027_teams_auth_phase_b.sql +0 -0
  444. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/028_learnings.sql +0 -0
  445. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/029_team_roles.sql +0 -0
  446. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/030_finder_pins.sql +0 -0
  447. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/031_pr_deploy_status.sql +0 -0
  448. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/032_task_reminders.sql +0 -0
  449. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/033_events_retry_count.sql +0 -0
  450. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/033_session_owner.sql +0 -0
  451. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/034_notifications.sql +0 -0
  452. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/035_shared_links.sql +0 -0
  453. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/036_session_index_upgrade.sql +0 -0
  454. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/037_pr_approval.sql +0 -0
  455. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/038_pr_submitted_by.sql +0 -0
  456. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/039_push_subscriptions.sql +0 -0
  457. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/040_semantic_search.sql +0 -0
  458. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/041_workspaces.sql +0 -0
  459. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/042_oidc_providers.sql +0 -0
  460. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/043_ci_checks.sql +0 -0
  461. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/044_agent_metrics.sql +0 -0
  462. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/045_documents_doc_type.sql +0 -0
  463. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/046_salience.sql +0 -0
  464. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/047_seed_missing_agents.sql +0 -0
  465. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/048_fix_agent_paths_roles.sql +0 -0
  466. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/049_agent_role_and_learnings_schema.sql +0 -0
  467. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/050_session_provider.sql +0 -0
  468. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/051_session_launch_profile.sql +0 -0
  469. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/052_session_theme_mode.sql +0 -0
  470. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/052_task_kind.sql +0 -0
  471. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/053_inbox_items.sql +0 -0
  472. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/054_inbox_triage_contract.sql +0 -0
  473. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/055_inbox_topic_treatment.sql +0 -0
  474. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/056_inbox_treatment_read_save.sql +0 -0
  475. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/057_session_theme_mode_backfill.sql +0 -0
  476. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/058_inbox_item_status_lifecycle.sql +0 -0
  477. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/059_inbox_tldr_and_source_scores.sql +0 -0
  478. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/060_newsletter.sql +0 -0
  479. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/061_inbox_redesign.sql +0 -0
  480. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/062_fix_inbox_sources_backfill.sql +0 -0
  481. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/063_task_completion_mode.sql +0 -0
  482. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/064_judge_mode_setting.sql +0 -0
  483. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/065_knowledge_graph_spike.sql +0 -0
  484. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/066_digest_ranking_inputs.sql +0 -0
  485. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/066_kg_artifact_nodes.sql +0 -0
  486. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/067_inbox_digest_selections.sql +0 -0
  487. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/067_kg_temporal.sql +0 -0
  488. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/068_inbox_digest_app_settings.sql +0 -0
  489. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/068_kg_touch_counter.sql +0 -0
  490. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/069_kg_doc_types.sql +0 -0
  491. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/070_digest_ranking_inputs_recovery.sql +0 -0
  492. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/071_inbox_digest_selections_recovery.sql +0 -0
  493. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/072_inbox_digest_app_settings_recovery.sql +0 -0
  494. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/073_kg_cross_project.sql +0 -0
  495. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/073_kg_cross_project_down.sql +0 -0
  496. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/074_kg_infra_types.sql +0 -0
  497. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/074_kg_infra_types_down.sql +0 -0
  498. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/075_kg_file_state_recovery.sql +0 -0
  499. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/075_kg_file_state_recovery_down.sql +0 -0
  500. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/076_kg_watcher_state.sql +0 -0
  501. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/076_kg_watcher_state_down.sql +0 -0
  502. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/077_kg_doc_types_extend.sql +0 -0
  503. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/077_kg_doc_types_extend_down.sql +0 -0
  504. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/078_kg_fts5.sql +0 -0
  505. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/078_kg_fts5_down.sql +0 -0
  506. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/079_kg_missing_indexes.sql +0 -0
  507. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/079_kg_missing_indexes_down.sql +0 -0
  508. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/080_kg_fts5_extended.sql +0 -0
  509. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/080_kg_fts5_extended_down.sql +0 -0
  510. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/081_kg_lens_indexes.sql +0 -0
  511. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/081_kg_lens_indexes_down.sql +0 -0
  512. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/082_kg_pins.sql +0 -0
  513. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/082_kg_pins_down.sql +0 -0
  514. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/083_kg_graph_nodes_degree.sql +0 -0
  515. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/083_kg_graph_nodes_degree_down.sql +0 -0
  516. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/084_drop_legacy_scheduler_tables.sql +0 -0
  517. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/084_drop_legacy_scheduler_tables_down.sql +0 -0
  518. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/085_kg_edge_resolves_to.sql +0 -0
  519. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/085_kg_edge_resolves_to_down.sql +0 -0
  520. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/086_project_status_updates_feed.sql +0 -0
  521. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/086_project_status_updates_feed_down.sql +0 -0
  522. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/087_session_metrics_dual.sql +0 -0
  523. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/087_session_metrics_dual_down.sql +0 -0
  524. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/088_rename_context_pct_legacy.sql +0 -0
  525. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/088_rename_context_pct_legacy_down.sql +0 -0
  526. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/089_session_metrics_equivalent_cost.sql +0 -0
  527. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/089_session_metrics_equivalent_cost_down.sql +0 -0
  528. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/090_kg_inbox_node_type.sql +0 -0
  529. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/090_kg_inbox_node_type_down.sql +0 -0
  530. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/091_kg_inbox_node_type_check.sql +0 -0
  531. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/091_kg_inbox_node_type_check_down.sql +0 -0
  532. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/092_sessions_activity_state_ts.sql +0 -0
  533. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/092_sessions_activity_state_ts_down.sql +0 -0
  534. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/093_sessions_activity_state_column.sql +0 -0
  535. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/093_sessions_activity_state_column_down.sql +0 -0
  536. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/094_ingest_pending.sql +0 -0
  537. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/094_ingest_pending_down.sql +0 -0
  538. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/095_kg_intent_first.sql +0 -0
  539. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/095_kg_intent_first_down.sql +0 -0
  540. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/096_kg_xlsx_artifact_prefix.sql +0 -0
  541. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/096_kg_xlsx_artifact_prefix_down.sql +0 -0
  542. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/097_ingest_change_history.sql +0 -0
  543. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/097_ingest_change_history_down.sql +0 -0
  544. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/098_kg_node_type_business.sql +0 -0
  545. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/098_kg_node_type_business_down.sql +0 -0
  546. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/099_kg_edges_restore_weight.sql +0 -0
  547. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/099_kg_edges_restore_weight_down.sql +0 -0
  548. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/100_kg_enriched_at.sql +0 -0
  549. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/100_kg_enriched_at_down.sql +0 -0
  550. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/101_local_llm_shadow_comparisons.sql +0 -0
  551. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/101_local_llm_shadow_comparisons_down.sql +0 -0
  552. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/102_promote_llm_costs.sql +0 -0
  553. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/102_promote_llm_costs_down.sql +0 -0
  554. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/103_ingest_skipped_log.sql +0 -0
  555. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/103_ingest_skipped_log_down.sql +0 -0
  556. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/120_docs_governance.sql +0 -0
  557. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/120_docs_governance_down.sql +0 -0
  558. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/121_notification_event_fk_cleanup.sql +0 -0
  559. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/121_notification_event_fk_cleanup_down.sql +0 -0
  560. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/122_docs_drift_history.sql +0 -0
  561. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/122_docs_drift_history_down.sql +0 -0
  562. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/123_ingest_parser_waiting_status.sql +0 -0
  563. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/123_ingest_parser_waiting_status_down.sql +0 -0
  564. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/124_heypocket_recordings.sql +0 -0
  565. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/124_heypocket_recordings_down.sql +0 -0
  566. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/125_kg_node_type_record.sql +0 -0
  567. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/125_kg_node_type_record_down.sql +0 -0
  568. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/126_ingest_terminal_upload_source_kind.sql +0 -0
  569. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/126_ingest_terminal_upload_source_kind_down.sql +0 -0
  570. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/127_brain_v1_substrate.sql +0 -0
  571. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/127_brain_v1_substrate_down.sql +0 -0
  572. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/128_brain_drift_signals.sql +0 -0
  573. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/128_brain_drift_signals_down.sql +0 -0
  574. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/129_brain_memory_operations.sql +0 -0
  575. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/129_brain_memory_operations_down.sql +0 -0
  576. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/130_brain_findings.sql +0 -0
  577. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/130_brain_findings_down.sql +0 -0
  578. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/132_kg_pr_modifies.sql +0 -0
  579. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/132_kg_pr_modifies_down.sql +0 -0
  580. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/133_brain_v1_2_direction_schema.sql +0 -0
  581. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/133_brain_v1_2_direction_schema_down.sql +0 -0
  582. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/134_brain_journal_narrative_polished.sql +0 -0
  583. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/134_brain_journal_narrative_polished_down.sql +0 -0
  584. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/135_kg_edges_provider.sql +0 -0
  585. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/136_documents_fts.sql +0 -0
  586. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/137_promote_llm_costs.sql +0 -0
  587. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/137_promote_llm_costs_down.sql +0 -0
  588. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/138_ingest_api_keys.sql +0 -0
  589. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/138_ingest_api_keys_down.sql +0 -0
  590. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/139_ingest_pending_ingress.sql +0 -0
  591. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/139_ingest_pending_ingress_down.sql +0 -0
  592. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/140_ingest_idempotency_quota.sql +0 -0
  593. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/140_ingest_idempotency_quota_down.sql +0 -0
  594. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/141_ingest_pending_metadata.sql +0 -0
  595. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/141_ingest_pending_metadata_down.sql +0 -0
  596. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/142_llm_function_config.sql +0 -0
  597. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/142_llm_function_config_down.sql +0 -0
  598. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/143_kg_code_embeddings.sql +0 -0
  599. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/143_kg_code_embeddings_down.sql +0 -0
  600. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/144_notifications_pending_sync_index.sql +0 -0
  601. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/migrations/__init__.py +0 -0
  602. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/projects/_template/project.yaml +0 -0
  603. {marvisx_cli-0.2.0 → marvisx_cli-0.2.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: marvisx-cli
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: MarvisX OSS — agent-native company-brain CLI runtime (init, status, brief, triage)
5
5
  Author: MarvisX maintainers
6
6
  License: BSL-1.1
@@ -72,6 +72,8 @@ Requires-Dist: sqlite-vec>=0.1.6
72
72
  Provides-Extra: dev
73
73
  Requires-Dist: pytest>=8; extra == "dev"
74
74
  Requires-Dist: pytest-cov>=5; extra == "dev"
75
+ Provides-Extra: keyring
76
+ Requires-Dist: keyring>=24; extra == "keyring"
75
77
  Dynamic: license-file
76
78
 
77
79
  # marvis init
@@ -80,9 +80,15 @@ def require_scope(*required_scopes: str) -> Callable[..., Awaitable[UserInfo]]:
80
80
  if not user.username.startswith("agent:") and user.username not in agent_usernames:
81
81
  return user
82
82
 
83
- # Agent with no scopes defined = unrestricted (backward compat with existing tokens)
83
+ # Agent with no scopes (empty/null) = NO permission for scope-gated ops.
84
+ # Empty MUST deny, not allow-all: an unscoped agent token previously
85
+ # passed every scope check (authz bypass — empty list satisfied
86
+ # `not user.scopes`). Scope-gated endpoints require an explicit grant.
84
87
  if not user.scopes:
85
- return user
88
+ raise HTTPException(
89
+ status_code=403,
90
+ detail=f"Token missing required scopes: {list(required_scopes)}",
91
+ )
86
92
 
87
93
  # Check that all required scopes are present
88
94
  missing = [s for s in required_scopes if s not in user.scopes]
@@ -0,0 +1,65 @@
1
+ """Regression: require_scope must DENY agent tokens with empty/null scopes.
2
+
3
+ Audit S4: an agent token with scopes=[] previously satisfied `not user.scopes`
4
+ and was treated as unrestricted (authz bypass — a scope-gated PATCH succeeded
5
+ with an empty-scope token). Empty scopes must grant NO permission.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import pytest
10
+ from fastapi import HTTPException
11
+
12
+ from core.api.models.auth import UserInfo
13
+ from core.api.rbac import require_scope
14
+
15
+
16
+ def _agent(scopes: list[str]) -> UserInfo:
17
+ # "agent:" prefix forces the agent path (not the human cookie bypass).
18
+ return UserInfo(
19
+ username="agent:tester",
20
+ user_id="tok-1",
21
+ system_role="operator",
22
+ user_type="agent",
23
+ scopes=scopes,
24
+ )
25
+
26
+
27
+ def _human(scopes: list[str]) -> UserInfo:
28
+ return UserInfo(
29
+ username="emilio",
30
+ user_id="user-1",
31
+ system_role="operator",
32
+ user_type="human",
33
+ scopes=scopes,
34
+ )
35
+
36
+
37
+ @pytest.mark.asyncio
38
+ async def test_empty_scopes_agent_denied():
39
+ check = require_scope("write")
40
+ with pytest.raises(HTTPException) as exc:
41
+ await check(user=_agent([]))
42
+ assert exc.value.status_code == 403
43
+
44
+
45
+ @pytest.mark.asyncio
46
+ async def test_matching_scope_agent_allowed():
47
+ check = require_scope("write")
48
+ user = await check(user=_agent(["read", "write"]))
49
+ assert user.username == "agent:tester"
50
+
51
+
52
+ @pytest.mark.asyncio
53
+ async def test_missing_scope_agent_denied():
54
+ check = require_scope("write")
55
+ with pytest.raises(HTTPException) as exc:
56
+ await check(user=_agent(["read"]))
57
+ assert exc.value.status_code == 403
58
+
59
+
60
+ @pytest.mark.asyncio
61
+ async def test_human_bypasses_scope_check_even_with_empty_scopes():
62
+ # Cookie/JWT humans are gated by role, not scopes — empty scopes is fine.
63
+ check = require_scope("write")
64
+ user = await check(user=_human([]))
65
+ assert user.username == "emilio"
@@ -345,3 +345,60 @@ def test_bash_action_returns_first_matching_rule(monkeypatch):
345
345
 
346
346
  assert decision.allowed is False
347
347
  assert decision.rule == "bash-merge"
348
+
349
+
350
+ def test_secret_scan_runs_outside_git_repo(monkeypatch):
351
+ """S5a: secret scan must NOT fail open when cwd is not a git repo.
352
+
353
+ Previously resolve_repo_root() returning None silently allowed, so a
354
+ `git commit` of an API_KEY=sk-live-... outside a repo slipped through.
355
+ """
356
+ bridge = _load_bridge_module()
357
+ monkeypatch.setattr(
358
+ bridge, "resolve_repo_root", lambda file_path, cwd=None: None
359
+ )
360
+
361
+ decision = bridge.check_secret_scan(
362
+ "git commit -m 'add config' && export API_KEY=sk-live-abcd1234efgh5678ijkl",
363
+ "/tmp/not-a-repo",
364
+ _config(),
365
+ )
366
+
367
+ assert decision.allowed is False
368
+ assert decision.rule == "secret-scan"
369
+
370
+
371
+ def test_secret_scan_outside_repo_allows_clean_command(monkeypatch):
372
+ """Outside a git repo, a clean commit command (no secret) is still allowed."""
373
+ bridge = _load_bridge_module()
374
+ monkeypatch.setattr(
375
+ bridge, "resolve_repo_root", lambda file_path, cwd=None: None
376
+ )
377
+
378
+ decision = bridge.check_secret_scan(
379
+ "git commit -m 'docs: tidy readme'",
380
+ "/tmp/not-a-repo",
381
+ _config(),
382
+ )
383
+
384
+ assert decision.allowed is True
385
+
386
+
387
+ def test_secret_scan_blocks_secret_in_staged_diff(monkeypatch):
388
+ """In-repo path (unchanged behaviour): secret in staged diff is blocked."""
389
+ bridge = _load_bridge_module()
390
+ monkeypatch.setattr(
391
+ bridge, "resolve_repo_root", lambda file_path, cwd=None: "/repo"
392
+ )
393
+ monkeypatch.setattr(
394
+ bridge,
395
+ "_git_output",
396
+ lambda args, cwd: "+API_KEY=sk-live-abcd1234efgh5678ijkl",
397
+ )
398
+
399
+ decision = bridge.check_secret_scan(
400
+ "git commit -m 'add config'", "/repo", _config()
401
+ )
402
+
403
+ assert decision.allowed is False
404
+ assert decision.rule == "secret-scan"
@@ -7,7 +7,9 @@ answers.
7
7
 
8
8
  Outputs (default ~/.marvis):
9
9
  - settings.yaml → workspace + storage + llm choice (no secrets)
10
- - master.key → Fernet key (chmod 600)
10
+ - master.key.enc + master.key.salt → Fernet key wrapped with a passphrase-derived
11
+ KEK, written when MARVIS_MASTER_PASSPHRASE (or an OS keyring / TTY prompt) is
12
+ available; otherwise a cleartext master.key (chmod 600) + a one-time warning.
11
13
  - byok.vault → encrypted API key store
12
14
  - <projects_root>/<slug>/project.yaml → first project seed
13
15
  """
@@ -60,7 +62,7 @@ from core.wizard.defaults import (
60
62
  )
61
63
 
62
64
  DEFAULT_SETTINGS_FILENAME = "settings.yaml"
63
- CLI_VERSION = "marvis-init 0.2.0"
65
+ CLI_VERSION = "marvis-init 0.2.1"
64
66
 
65
67
 
66
68
  def _default_vault_dir() -> Path:
@@ -458,14 +460,19 @@ def _bootstrap_schema(state: WizardState) -> str | None:
458
460
  os.environ["PIR_PASSWORD"] = prev_pwd
459
461
 
460
462
 
461
- def _store_byok(state: WizardState, vault_dir: Path) -> Path | None:
463
+ def _store_byok(
464
+ state: WizardState, vault_dir: Path, *, allow_prompt: bool = False
465
+ ) -> Path | None:
462
466
  if (
463
467
  not state.llm_provider
464
468
  or not state.llm_provider.provider
465
469
  or not state.llm_provider.api_key
466
470
  ):
467
471
  return None
468
- ensure_master_key(vault_dir, create=True)
472
+ # When a passphrase source is available (env / keyring / TTY prompt), the
473
+ # master key is written encrypted from the start; otherwise cleartext + a
474
+ # one-time warning. Init never blocks on this.
475
+ ensure_master_key(vault_dir, create=True, allow_prompt=allow_prompt)
469
476
  store_provider_key(
470
477
  state.llm_provider.provider.value,
471
478
  state.llm_provider.api_key,
@@ -699,7 +706,7 @@ def init(
699
706
  raise typer.Exit(code=0)
700
707
 
701
708
  _write_settings(settings_destination, settings_payload)
702
- vault_file = _store_byok(state, vault_dir)
709
+ vault_file = _store_byok(state, vault_dir, allow_prompt=interactive)
703
710
  project_yaml = _seed_project(state)
704
711
  db_file = _bootstrap_schema(state)
705
712
 
@@ -552,6 +552,17 @@ def check_secret_scan(command: str | None, cwd: str | None, _config: dict[str, A
552
552
 
553
553
  repo_root = resolve_repo_root(None, cwd)
554
554
  if not repo_root:
555
+ # Outside a git repo there is no staged diff to inspect, but the scan must
556
+ # NOT fail open (previously it silently allowed, so `git commit` of an
557
+ # `API_KEY=sk-live-...` outside a repo slipped through). Scan the command
558
+ # text itself so the secret-scan rule runs regardless of git context.
559
+ if command and SECRET_ASSIGNMENT_RE.search(command):
560
+ return _deny(
561
+ "Blocked: possible secret in commit command. "
562
+ "Reason: secret-scan safety rule protects credentials in every governance profile. "
563
+ "Fix: remove the secret from the command and use the BYOK vault or environment configuration instead.",
564
+ "secret-scan",
565
+ )
555
566
  return _allow()
556
567
 
557
568
  diff = _git_output(["diff", "--cached", "--unified=0", "--no-ext-diff"], repo_root)
@@ -220,7 +220,7 @@ def _install_id() -> str:
220
220
 
221
221
  def _marvis_version() -> str:
222
222
  """The CLI version (kept in lockstep with ``marvis version``)."""
223
- return "0.2.0"
223
+ return "0.2.1"
224
224
 
225
225
 
226
226
  def _os_name() -> str:
@@ -0,0 +1,384 @@
1
+ """BYOK vault — Fernet AES-128-CBC + HMAC-SHA256 file store.
2
+
3
+ OSS single-user. The vault file (`~/.marvis/byok.vault`) is encrypted with a
4
+ Fernet "master key". That master key is itself protected at rest:
5
+
6
+ - **Encrypted (preferred)** — when a passphrase source is available the master
7
+ key is wrapped with a KEK (key-encryption key) derived from the passphrase via
8
+ scrypt (memory-hard KDF) and stored as `master.key.enc` next to a random
9
+ `master.key.salt`. The raw Fernet key never touches disk.
10
+ - **Cleartext (legacy / headless fallback)** — older installs (and headless
11
+ servers with no passphrase source) keep a cleartext `master.key`. It still
12
+ works; we just emit a one-time WARNING so the operator can opt into wrapping by
13
+ setting ``MARVIS_MASTER_PASSPHRASE``.
14
+
15
+ Passphrase source precedence: ``MARVIS_MASTER_PASSPHRASE`` env → OS keyring
16
+ (optional, soft dependency) → interactive prompt (only on a TTY). No source +
17
+ no TTY => cleartext + warning, never a lockout.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import base64
23
+ import getpass
24
+ import json
25
+ import logging
26
+ import os
27
+ import sys
28
+ from datetime import datetime, timezone
29
+ from pathlib import Path
30
+ from typing import Any
31
+
32
+ from cryptography.fernet import Fernet, InvalidToken
33
+ from cryptography.hazmat.primitives.kdf.scrypt import Scrypt
34
+
35
+ logger = logging.getLogger(__name__)
36
+
37
+ DEFAULT_VAULT_DIR = Path.home() / ".marvis"
38
+ MASTER_KEY_FILENAME = "master.key"
39
+ MASTER_KEY_ENC_FILENAME = "master.key.enc"
40
+ MASTER_KEY_SALT_FILENAME = "master.key.salt"
41
+ VAULT_FILENAME = "byok.vault"
42
+ VAULT_VERSION = 1
43
+
44
+ # Passphrase source for the KEK that wraps the master key.
45
+ PASSPHRASE_ENV = "MARVIS_MASTER_PASSPHRASE"
46
+ # OS keyring coordinates (only used when the optional `keyring` backend exists).
47
+ _KEYRING_SERVICE = "marvisx-byok"
48
+ _KEYRING_USERNAME = "master-passphrase"
49
+
50
+ # scrypt parameters (memory-hard KDF). n=2**15 ~= 32 MiB working set: strong for a
51
+ # local single-user secret, light enough to derive once per process on a small box.
52
+ _SCRYPT_N = 2**15
53
+ _SCRYPT_R = 8
54
+ _SCRYPT_P = 1
55
+ _SCRYPT_KEYLEN = 32
56
+ _SALT_BYTES = 16
57
+
58
+ _KNOWN_PROVIDERS = ("anthropic", "openai", "mac_gateway", "bedrock")
59
+
60
+ # Module-level latch so the "your master.key is unencrypted" notice fires once per
61
+ # process instead of on every vault read/write.
62
+ _cleartext_warning_emitted = False
63
+
64
+
65
+ class VaultError(Exception):
66
+ pass
67
+
68
+
69
+ class MasterKeyMissing(VaultError):
70
+ pass
71
+
72
+
73
+ class WrongPassphrase(VaultError):
74
+ """Raised when an encrypted master.key exists but the passphrase is wrong."""
75
+
76
+
77
+ def _master_key_path(vault_dir: Path) -> Path:
78
+ return vault_dir / MASTER_KEY_FILENAME
79
+
80
+
81
+ def _master_key_enc_path(vault_dir: Path) -> Path:
82
+ return vault_dir / MASTER_KEY_ENC_FILENAME
83
+
84
+
85
+ def _master_key_salt_path(vault_dir: Path) -> Path:
86
+ return vault_dir / MASTER_KEY_SALT_FILENAME
87
+
88
+
89
+ def _vault_path(vault_dir: Path) -> Path:
90
+ return vault_dir / VAULT_FILENAME
91
+
92
+
93
+ def _resolve_dir(vault_dir: Path | None) -> Path:
94
+ base = vault_dir or DEFAULT_VAULT_DIR
95
+ return Path(base).expanduser()
96
+
97
+
98
+ # --------------------------------------------------------------------------- #
99
+ # Passphrase resolution + KEK derivation
100
+ # --------------------------------------------------------------------------- #
101
+
102
+
103
+ def _keyring_passphrase() -> str | None:
104
+ """Read the passphrase from the OS keyring.
105
+
106
+ `keyring` is an OPTIONAL soft dependency: a headless server has no backend, so
107
+ we import inside the function and swallow any import/backend error. Never a
108
+ hard runtime requirement.
109
+ """
110
+ try:
111
+ import keyring # type: ignore[import-not-found]
112
+ except Exception: # noqa: BLE001 — no keyring installed at all
113
+ return None
114
+ try:
115
+ value = keyring.get_password(_KEYRING_SERVICE, _KEYRING_USERNAME)
116
+ except Exception: # noqa: BLE001 — no usable backend (headless), locked, etc.
117
+ return None
118
+ return value or None
119
+
120
+
121
+ def _is_interactive() -> bool:
122
+ try:
123
+ return sys.stdin.isatty() and sys.stdout.isatty()
124
+ except Exception: # noqa: BLE001 — stdin/stdout replaced in some hosts
125
+ return False
126
+
127
+
128
+ def resolve_passphrase(*, allow_prompt: bool = False) -> str | None:
129
+ """Resolve the master passphrase, or None when no source is available.
130
+
131
+ Precedence: ``MARVIS_MASTER_PASSPHRASE`` env → OS keyring (optional) →
132
+ interactive prompt (only when ``allow_prompt`` and a TTY is present).
133
+ """
134
+ env = os.environ.get(PASSPHRASE_ENV)
135
+ if env:
136
+ return env
137
+
138
+ from_keyring = _keyring_passphrase()
139
+ if from_keyring:
140
+ return from_keyring
141
+
142
+ if allow_prompt and _is_interactive():
143
+ try:
144
+ entered = getpass.getpass(
145
+ "Master passphrase (protects your BYOK API keys): "
146
+ )
147
+ except Exception: # noqa: BLE001 — non-interactive surprise, EOF, etc.
148
+ return None
149
+ return entered or None
150
+
151
+ return None
152
+
153
+
154
+ def _derive_kek(passphrase: str, salt: bytes) -> bytes:
155
+ """Derive a 32-byte Fernet-compatible KEK from the passphrase via scrypt."""
156
+ kdf = Scrypt(
157
+ salt=salt,
158
+ length=_SCRYPT_KEYLEN,
159
+ n=_SCRYPT_N,
160
+ r=_SCRYPT_R,
161
+ p=_SCRYPT_P,
162
+ )
163
+ raw = kdf.derive(passphrase.encode("utf-8"))
164
+ return base64.urlsafe_b64encode(raw)
165
+
166
+
167
+ def _chmod_600(path: Path) -> None:
168
+ try:
169
+ os.chmod(path, 0o600)
170
+ except OSError:
171
+ pass
172
+
173
+
174
+ # --------------------------------------------------------------------------- #
175
+ # Wrap / unwrap the master key
176
+ # --------------------------------------------------------------------------- #
177
+
178
+
179
+ def _write_encrypted_master_key(
180
+ vault_dir: Path, fernet_key: bytes, passphrase: str
181
+ ) -> None:
182
+ """Wrap `fernet_key` with a passphrase-derived KEK and write the .enc + .salt."""
183
+ salt = os.urandom(_SALT_BYTES)
184
+ kek = _derive_kek(passphrase, salt)
185
+ wrapped = Fernet(kek).encrypt(fernet_key)
186
+
187
+ salt_file = _master_key_salt_path(vault_dir)
188
+ salt_tmp = salt_file.with_suffix(salt_file.suffix + ".tmp")
189
+ salt_tmp.write_bytes(salt)
190
+ _chmod_600(salt_tmp)
191
+ salt_tmp.replace(salt_file)
192
+
193
+ enc_file = _master_key_enc_path(vault_dir)
194
+ enc_tmp = enc_file.with_suffix(enc_file.suffix + ".tmp")
195
+ enc_tmp.write_bytes(wrapped)
196
+ _chmod_600(enc_tmp)
197
+ enc_tmp.replace(enc_file)
198
+
199
+
200
+ def _read_encrypted_master_key(vault_dir: Path, passphrase: str) -> bytes:
201
+ """Unwrap and return the raw Fernet key from the .enc + .salt files."""
202
+ salt = _master_key_salt_path(vault_dir).read_bytes()
203
+ wrapped = _master_key_enc_path(vault_dir).read_bytes().strip()
204
+ if not salt or not wrapped:
205
+ raise VaultError("Encrypted master key files are empty/corrupt")
206
+ kek = _derive_kek(passphrase, salt)
207
+ try:
208
+ return Fernet(kek).decrypt(wrapped)
209
+ except InvalidToken as exc:
210
+ raise WrongPassphrase(
211
+ f"wrong {PASSPHRASE_ENV} (cannot unwrap the master key)"
212
+ ) from exc
213
+
214
+
215
+ def _warn_cleartext_once(key_file: Path) -> None:
216
+ global _cleartext_warning_emitted
217
+ if _cleartext_warning_emitted:
218
+ return
219
+ _cleartext_warning_emitted = True
220
+ logger.warning(
221
+ "%s is unencrypted; set %s to protect your BYOK API keys at rest "
222
+ "(it will be migrated to an encrypted master.key.enc automatically).",
223
+ key_file,
224
+ PASSPHRASE_ENV,
225
+ )
226
+
227
+
228
+ # --------------------------------------------------------------------------- #
229
+ # Public master-key entrypoint
230
+ # --------------------------------------------------------------------------- #
231
+
232
+
233
+ def ensure_master_key(
234
+ vault_dir: Path | None = None,
235
+ *,
236
+ create: bool = True,
237
+ allow_prompt: bool = False,
238
+ ) -> bytes:
239
+ """Read or create the master key. Returns the raw Fernet key bytes.
240
+
241
+ Resolution order (no lockout, ever):
242
+
243
+ 1. **Encrypted on disk** (`master.key.enc` + `master.key.salt`) → unwrap with
244
+ the resolved passphrase. Missing passphrase → ``MasterKeyMissing``; wrong
245
+ passphrase → ``WrongPassphrase`` (clean error, no corruption).
246
+ 2. **Cleartext on disk** (`master.key`, legacy) → use it. If a passphrase is
247
+ available, transparently MIGRATE it (write .enc/.salt, remove cleartext).
248
+ If not, keep using cleartext + emit a one-time WARNING.
249
+ 3. **Nothing on disk** + ``create`` → generate a fresh key. If a passphrase is
250
+ available, write it encrypted from the start (never cleartext). Otherwise
251
+ write cleartext + warning. ``create=False`` → ``MasterKeyMissing``.
252
+ """
253
+ resolved = _resolve_dir(vault_dir)
254
+ resolved.mkdir(parents=True, exist_ok=True)
255
+ try:
256
+ os.chmod(resolved, 0o700)
257
+ except OSError:
258
+ pass
259
+
260
+ enc_file = _master_key_enc_path(resolved)
261
+ salt_file = _master_key_salt_path(resolved)
262
+ key_file = _master_key_path(resolved)
263
+
264
+ # 1. Encrypted form present → it is the source of truth.
265
+ if enc_file.exists() and salt_file.exists():
266
+ passphrase = resolve_passphrase(allow_prompt=allow_prompt)
267
+ if not passphrase:
268
+ raise MasterKeyMissing(
269
+ f"Encrypted master key found at {enc_file} but no passphrase "
270
+ f"available; set {PASSPHRASE_ENV} to unlock the BYOK vault."
271
+ )
272
+ return _read_encrypted_master_key(resolved, passphrase)
273
+
274
+ # 2. Cleartext form present (legacy / headless) → keep working, migrate if able.
275
+ if key_file.exists():
276
+ key = key_file.read_bytes().strip()
277
+ if not key:
278
+ raise VaultError(f"Master key file empty: {key_file}")
279
+ passphrase = resolve_passphrase(allow_prompt=allow_prompt)
280
+ if passphrase:
281
+ # Transparent migration: wrap, then drop the cleartext copy.
282
+ _write_encrypted_master_key(resolved, key, passphrase)
283
+ try:
284
+ key_file.unlink()
285
+ except OSError:
286
+ logger.warning(
287
+ "Migrated master key to %s but could not remove the "
288
+ "cleartext %s; delete it manually.",
289
+ enc_file,
290
+ key_file,
291
+ )
292
+ logger.info(
293
+ "Migrated %s to an encrypted master.key.enc (protected by %s).",
294
+ key_file,
295
+ PASSPHRASE_ENV,
296
+ )
297
+ else:
298
+ _warn_cleartext_once(key_file)
299
+ return key
300
+
301
+ # 3. Nothing on disk.
302
+ if not create:
303
+ raise MasterKeyMissing(f"Master key not found at {key_file}")
304
+
305
+ key = Fernet.generate_key()
306
+ passphrase = resolve_passphrase(allow_prompt=allow_prompt)
307
+ if passphrase:
308
+ _write_encrypted_master_key(resolved, key, passphrase)
309
+ else:
310
+ key_file.write_bytes(key)
311
+ _chmod_600(key_file)
312
+ _warn_cleartext_once(key_file)
313
+ return key
314
+
315
+
316
+ def _empty_vault() -> dict[str, Any]:
317
+ return {
318
+ "version": VAULT_VERSION,
319
+ "providers": {provider: None for provider in _KNOWN_PROVIDERS},
320
+ }
321
+
322
+
323
+ def load_vault(vault_dir: Path | None = None) -> dict[str, Any]:
324
+ """Decrypt + return the vault. Returns an empty vault if file missing."""
325
+ resolved = _resolve_dir(vault_dir)
326
+ vault_file = _vault_path(resolved)
327
+ if not vault_file.exists():
328
+ return _empty_vault()
329
+
330
+ master = ensure_master_key(resolved, create=False)
331
+ fernet = Fernet(master)
332
+ token = vault_file.read_bytes()
333
+ try:
334
+ plaintext = fernet.decrypt(token)
335
+ except InvalidToken as exc:
336
+ raise VaultError("Vault corrupted or master key mismatch") from exc
337
+ return json.loads(plaintext.decode("utf-8"))
338
+
339
+
340
+ def save_vault(
341
+ vault: dict[str, Any], vault_dir: Path | None = None
342
+ ) -> Path:
343
+ """Encrypt + atomically write the vault. Returns the absolute path."""
344
+ resolved = _resolve_dir(vault_dir)
345
+ master = ensure_master_key(resolved, create=True)
346
+ fernet = Fernet(master)
347
+ payload = json.dumps(vault, sort_keys=True).encode("utf-8")
348
+ token = fernet.encrypt(payload)
349
+
350
+ vault_file = _vault_path(resolved)
351
+ tmp_file = vault_file.with_suffix(".tmp")
352
+ tmp_file.write_bytes(token)
353
+ _chmod_600(tmp_file)
354
+ tmp_file.replace(vault_file)
355
+ return vault_file
356
+
357
+
358
+ def store_provider_key(
359
+ provider: str,
360
+ api_key: str,
361
+ *,
362
+ base_url: str | None = None,
363
+ vault_dir: Path | None = None,
364
+ ) -> None:
365
+ if provider not in _KNOWN_PROVIDERS:
366
+ raise VaultError(f"Unknown provider: {provider}")
367
+ vault = load_vault(vault_dir)
368
+ entry: dict[str, Any] = {
369
+ "api_key": api_key,
370
+ "added_at": datetime.now(timezone.utc).isoformat(),
371
+ }
372
+ if base_url is not None:
373
+ entry["base_url"] = base_url
374
+ vault["providers"][provider] = entry
375
+ save_vault(vault, vault_dir)
376
+
377
+
378
+ def mask_api_key(api_key: str | None) -> str:
379
+ """First 6 + last 4. Used for recap display + audit log lines."""
380
+ if not api_key:
381
+ return ""
382
+ if len(api_key) <= 10:
383
+ return "***"
384
+ return f"{api_key[:6]}***{api_key[-4:]}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: marvisx-cli
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: MarvisX OSS — agent-native company-brain CLI runtime (init, status, brief, triage)
5
5
  Author: MarvisX maintainers
6
6
  License: BSL-1.1
@@ -72,6 +72,8 @@ Requires-Dist: sqlite-vec>=0.1.6
72
72
  Provides-Extra: dev
73
73
  Requires-Dist: pytest>=8; extra == "dev"
74
74
  Requires-Dist: pytest-cov>=5; extra == "dev"
75
+ Provides-Extra: keyring
76
+ Requires-Dist: keyring>=24; extra == "keyring"
75
77
  Dynamic: license-file
76
78
 
77
79
  # marvis init
@@ -317,6 +317,7 @@ core/api/tests/test_opencode_sessions.py
317
317
  core/api/tests/test_pr_workflow_e2e.py
318
318
  core/api/tests/test_projects_handoffs.py
319
319
  core/api/tests/test_providers.py
320
+ core/api/tests/test_require_scope_empty_deny.py
320
321
  core/api/tests/test_safety_bridge.py
321
322
  core/api/tests/test_session_catalog.py
322
323
  core/api/tests/test_session_conversations.py
@@ -593,5 +594,7 @@ migrations/142_llm_function_config_down.sql
593
594
  migrations/143_kg_code_embeddings.sql
594
595
  migrations/143_kg_code_embeddings_down.sql
595
596
  migrations/144_notifications_pending_sync_index.sql
597
+ migrations/145_audit_log_immutable.sql
598
+ migrations/145_audit_log_immutable_down.sql
596
599
  migrations/__init__.py
597
600
  projects/_template/project.yaml
@@ -46,3 +46,6 @@ sqlite-vec>=0.1.6
46
46
  [dev]
47
47
  pytest>=8
48
48
  pytest-cov>=5
49
+
50
+ [keyring]
51
+ keyring>=24
@@ -0,0 +1,23 @@
1
+ -- 145: make audit_log append-only via ABORT triggers.
2
+ --
3
+ -- audit_log is the cross-system action trail (get_audit_log). It must be
4
+ -- tamper-evident: rows are only ever INSERTed by the application; the codebase
5
+ -- never issues UPDATE or DELETE against it (verified: no UPDATE/DELETE FROM
6
+ -- audit_log anywhere in core/). These triggers enforce that invariant at the
7
+ -- storage layer so a compromised path (or a stray manual query) cannot rewrite
8
+ -- or erase history. INSERT is intentionally left unguarded.
9
+ -- Reversibile: see 145_audit_log_immutable_down.sql
10
+
11
+ CREATE TRIGGER IF NOT EXISTS audit_log_no_update
12
+ BEFORE UPDATE ON audit_log
13
+ BEGIN
14
+ SELECT RAISE(ABORT, 'audit_log is append-only');
15
+ END;
16
+
17
+ CREATE TRIGGER IF NOT EXISTS audit_log_no_delete
18
+ BEFORE DELETE ON audit_log
19
+ BEGIN
20
+ SELECT RAISE(ABORT, 'audit_log is append-only');
21
+ END;
22
+
23
+ INSERT OR IGNORE INTO schema_versions (version) VALUES (145);