brainiac-cli 0.20.46__tar.gz → 0.20.48__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 (362) hide show
  1. {brainiac_cli-0.20.46/src/brainiac_cli.egg-info → brainiac_cli-0.20.48}/PKG-INFO +1 -1
  2. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/pyproject.toml +1 -1
  3. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/_assets/AGENTS.md +11 -4
  4. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/_assets/scripts/brain-synthesis.sh +13 -1
  5. brainiac_cli-0.20.48/src/brain/_assets/scripts/shorten_long_note_ids.py +100 -0
  6. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/_version.py +1 -1
  7. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/alerts.py +5 -5
  8. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/alerts_staging.py +36 -0
  9. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/cli_cmds/lifecycle_parser.py +1 -1
  10. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/ingest/handlers/concealment.py +32 -9
  11. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/ingest/handlers/concealment_office.py +3 -3
  12. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/injection_scan.py +32 -3
  13. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/mcp_housekeeping_verbs.py +1 -1
  14. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/remediation.py +12 -83
  15. brainiac_cli-0.20.48/src/brain/remediation_invariants.py +93 -0
  16. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48/src/brainiac_cli.egg-info}/PKG-INFO +1 -1
  17. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brainiac_cli.egg-info/SOURCES.txt +2 -0
  18. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/LICENSE +0 -0
  19. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/MANIFEST.in +0 -0
  20. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/README.md +0 -0
  21. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/setup.cfg +0 -0
  22. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/__init__.py +0 -0
  23. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/__main__.py +0 -0
  24. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/_assets/assets/graph-explorer-template.html +0 -0
  25. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/_assets/overlay/template/brand/brand-guide.md +0 -0
  26. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/_assets/overlay/template/keywords/glossary.md +0 -0
  27. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/_assets/overlay/template/people/roster.md +0 -0
  28. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/_assets/overlay/template/voice/voice-profile.md +0 -0
  29. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/_assets/routines/manifest.json +0 -0
  30. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/_assets/scripts/brain-brief-mac.plist +0 -0
  31. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/_assets/scripts/brain-brief.sh +0 -0
  32. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/_assets/scripts/brain-synthesis-mac.plist +0 -0
  33. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/_assets/scripts/brain_synthesis_codex.py +0 -0
  34. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/_assets/scripts/brainiac-alerts.sh +0 -0
  35. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/_assets/scripts/brainiac-egress-guard.sh +0 -0
  36. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/_assets/scripts/install-brief-mac.sh +0 -0
  37. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/_assets/scripts/install-brief-windows.ps1 +0 -0
  38. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/_assets/scripts/register_tasks.py +0 -0
  39. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/_assets/scripts/vm-boundary-probe.sh +0 -0
  40. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/_assets/scripts/vm-selftest.sh +0 -0
  41. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/_assets/templates/company.md +0 -0
  42. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/_assets/templates/concept.md +0 -0
  43. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/_assets/templates/daily.md +0 -0
  44. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/_assets/templates/decision.md +0 -0
  45. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/_assets/templates/deliverable.md +0 -0
  46. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/_assets/templates/meeting.md +0 -0
  47. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/_assets/templates/person.md +0 -0
  48. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/_assets/templates/project.md +0 -0
  49. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/_assets/templates/state-moc.md +0 -0
  50. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/_optional.py +0 -0
  51. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/alerts_render.py +0 -0
  52. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/anchor.py +0 -0
  53. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/atomic_temp.py +0 -0
  54. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/attestation.py +0 -0
  55. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/audit.py +0 -0
  56. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/audit_chain.py +0 -0
  57. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/audit_drift.py +0 -0
  58. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/autolink.py +0 -0
  59. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/backup.py +0 -0
  60. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/brief.py +0 -0
  61. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/brief_render.py +0 -0
  62. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/capture.py +0 -0
  63. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/chunk.py +0 -0
  64. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/classification.py +0 -0
  65. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/cli.py +0 -0
  66. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/cli_cmds/__init__.py +0 -0
  67. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/cli_cmds/common_parser.py +0 -0
  68. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/cli_cmds/connect.py +0 -0
  69. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/cli_cmds/connect_parser.py +0 -0
  70. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/cli_cmds/eval.py +0 -0
  71. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/cli_cmds/eval_parser.py +0 -0
  72. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/cli_cmds/index_storage.py +0 -0
  73. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/cli_cmds/index_storage_parser.py +0 -0
  74. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/cli_cmds/ingest_storage.py +0 -0
  75. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/cli_cmds/ingest_storage_parser.py +0 -0
  76. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/cli_cmds/lifecycle.py +0 -0
  77. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/cli_cmds/maintenance.py +0 -0
  78. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/cli_cmds/maintenance_parser.py +0 -0
  79. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/cli_cmds/navigation.py +0 -0
  80. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/cli_cmds/navigation_parser.py +0 -0
  81. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/cli_cmds/owner_workflow.py +0 -0
  82. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/cli_cmds/owner_workflow_parser.py +0 -0
  83. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/cli_cmds/presentation.py +0 -0
  84. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/cli_cmds/presentation_parser.py +0 -0
  85. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/cli_cmds/retrieval.py +0 -0
  86. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/cli_cmds/retrieval_parser.py +0 -0
  87. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/cli_cmds/update.py +0 -0
  88. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/cli_cmds/update_parser.py +0 -0
  89. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/cli_cmds/versioning.py +0 -0
  90. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/cli_cmds/versioning_parser.py +0 -0
  91. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/cli_help.py +0 -0
  92. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/cli_read_record.py +0 -0
  93. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/cli_render.py +0 -0
  94. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/config.py +0 -0
  95. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/config_hostpaths.py +0 -0
  96. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/connect.py +0 -0
  97. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/context.py +0 -0
  98. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/conventions.py +0 -0
  99. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/conventions_scan.py +0 -0
  100. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/core/__init__.py +0 -0
  101. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/core/_audit.py +0 -0
  102. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/core/_briefing.py +0 -0
  103. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/core/_capture.py +0 -0
  104. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/core/_capture_staging.py +0 -0
  105. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/core/_durability.py +0 -0
  106. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/core/_health.py +0 -0
  107. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/core/_maintenance.py +0 -0
  108. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/core/_maintenance_state.py +0 -0
  109. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/core/_retrieval.py +0 -0
  110. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/core/_shared.py +0 -0
  111. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/core/_supersede_batch.py +0 -0
  112. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/core/_supersession.py +0 -0
  113. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/core/_supersession_journal.py +0 -0
  114. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/cowork_leak_artifact.py +0 -0
  115. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/cowork_leak_databases.py +0 -0
  116. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/cowork_leak_notes.py +0 -0
  117. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/cowork_leak_scan.py +0 -0
  118. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/cowork_snapshot_guard.py +0 -0
  119. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/cowork_staging.py +0 -0
  120. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/dbretry.py +0 -0
  121. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/deliverables_absorb.py +0 -0
  122. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/deliverables_apply.py +0 -0
  123. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/deliverables_ledger.py +0 -0
  124. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/deliverables_materialize.py +0 -0
  125. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/deliverables_previous.py +0 -0
  126. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/deliverables_readme.py +0 -0
  127. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/deliverables_shelf.py +0 -0
  128. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/deliverables_sync.py +0 -0
  129. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/doctor.py +0 -0
  130. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/doctor_checks.py +0 -0
  131. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/doctor_context.py +0 -0
  132. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/doctor_desktop.py +0 -0
  133. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/doctor_health.py +0 -0
  134. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/doctor_mount_leak.py +0 -0
  135. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/doctor_plugins.py +0 -0
  136. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/doctor_staging.py +0 -0
  137. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/doctor_vendor.py +0 -0
  138. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/doctor_vm.py +0 -0
  139. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/doctor_wiring.py +0 -0
  140. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/draft_drain.py +0 -0
  141. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/draft_update.py +0 -0
  142. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/egress.py +0 -0
  143. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/egress_terms.py +0 -0
  144. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/embed.py +0 -0
  145. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/embed_backends.py +0 -0
  146. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/embed_catalog.py +0 -0
  147. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/embed_onnx.py +0 -0
  148. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/embed_qwen.py +0 -0
  149. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/encryption.py +0 -0
  150. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/exceptions_cli.py +0 -0
  151. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/exceptions_page.py +0 -0
  152. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/exceptions_render.py +0 -0
  153. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/exceptions_tray.py +0 -0
  154. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/exceptions_verify.py +0 -0
  155. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/folds/__init__.py +0 -0
  156. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/folds/context.py +0 -0
  157. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/folds/daily.py +0 -0
  158. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/folds/deliverables.py +0 -0
  159. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/folds/golden.py +0 -0
  160. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/folds/graph.py +0 -0
  161. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/folds/intake.py +0 -0
  162. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/folds/invariants.py +0 -0
  163. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/folds/orchestrator.py +0 -0
  164. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/folds/organization.py +0 -0
  165. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/folds/preflight.py +0 -0
  166. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/folds/publish.py +0 -0
  167. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/folds/remediation.py +0 -0
  168. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/folds/remediation_bookkeeping.py +0 -0
  169. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/folds/reporting.py +0 -0
  170. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/folds/retention.py +0 -0
  171. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/folds/watchdogs.py +0 -0
  172. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/folds/weekly.py +0 -0
  173. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/frontmatter.py +0 -0
  174. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/golden_ops.py +0 -0
  175. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/golden_probe.py +0 -0
  176. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/golden_probe_cases.py +0 -0
  177. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/graph.py +0 -0
  178. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/graph_hygiene.py +0 -0
  179. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/graph_ops.py +0 -0
  180. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/graphify.py +0 -0
  181. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/graphify_edges.py +0 -0
  182. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/graphreport.py +0 -0
  183. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/graphreport_sections.py +0 -0
  184. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/graphreport_sections_2.py +0 -0
  185. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/guard_settings.py +0 -0
  186. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/healthreport.py +0 -0
  187. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/healthreport_sections.py +0 -0
  188. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/healthreport_unsigned.py +0 -0
  189. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/inbox.py +0 -0
  190. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/index/__init__.py +0 -0
  191. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/index/_connection.py +0 -0
  192. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/index/_families.py +0 -0
  193. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/index/_graph.py +0 -0
  194. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/index/_identity.py +0 -0
  195. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/index/_lifecycle.py +0 -0
  196. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/index/_models.py +0 -0
  197. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/index/_planning.py +0 -0
  198. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/index/_records.py +0 -0
  199. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/index/_retirement.py +0 -0
  200. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/index/_schema.py +0 -0
  201. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/index/_search.py +0 -0
  202. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/index/_settings.py +0 -0
  203. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/index/_shared.py +0 -0
  204. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/index/_tools.py +0 -0
  205. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/index/_zones.py +0 -0
  206. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/index_stages/__init__.py +0 -0
  207. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/index_stages/connection.py +0 -0
  208. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/index_stages/diagnostics.py +0 -0
  209. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/index_stages/duplicate_families.py +0 -0
  210. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/index_stages/near_duplicates.py +0 -0
  211. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/index_stages/rebuild.py +0 -0
  212. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/index_stages/reranking.py +0 -0
  213. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/index_stages/search.py +0 -0
  214. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/index_stages/search_finalization.py +0 -0
  215. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/index_stages/search_identity.py +0 -0
  216. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/index_stages/search_ranking.py +0 -0
  217. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/index_stages/sync.py +0 -0
  218. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/ingest/__init__.py +0 -0
  219. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/ingest/deliverables.py +0 -0
  220. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/ingest/handlers/__init__.py +0 -0
  221. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/ingest/handlers/base.py +0 -0
  222. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/ingest/handlers/concealment_gate.py +0 -0
  223. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/ingest/handlers/concealment_html.py +0 -0
  224. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/ingest/handlers/concealment_ledger.py +0 -0
  225. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/ingest/handlers/concealment_pdf.py +0 -0
  226. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/ingest/handlers/docx.py +0 -0
  227. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/ingest/handlers/email.py +0 -0
  228. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/ingest/handlers/html.py +0 -0
  229. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/ingest/handlers/image.py +0 -0
  230. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/ingest/handlers/ole.py +0 -0
  231. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/ingest/handlers/pdf.py +0 -0
  232. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/ingest/handlers/pptx.py +0 -0
  233. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/ingest/handlers/tables.py +0 -0
  234. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/ingest/handlers/text.py +0 -0
  235. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/ingest/handlers/xlsx.py +0 -0
  236. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/ingest/handlers/zip.py +0 -0
  237. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/ingest/pipeline.py +0 -0
  238. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/ingest/pipeline_duplicates.py +0 -0
  239. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/ingest/pipeline_files.py +0 -0
  240. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/ingest/pipeline_injection.py +0 -0
  241. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/ingest/pipeline_nested.py +0 -0
  242. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/ingest/pipeline_run.py +0 -0
  243. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/ingest/pipeline_stages.py +0 -0
  244. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/ingest/tierguard.py +0 -0
  245. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/ingest/tierguard_verdict.py +0 -0
  246. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/ingest/transcript.py +0 -0
  247. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/init.py +0 -0
  248. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/init_import.py +0 -0
  249. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/init_samples.py +0 -0
  250. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/init_seed.py +0 -0
  251. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/init_steps.py +0 -0
  252. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/injection_fold.py +0 -0
  253. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/interview.py +0 -0
  254. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/interview_apply.py +0 -0
  255. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/interview_detect.py +0 -0
  256. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/invariant_coverage.py +0 -0
  257. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/invariant_crosstier.py +0 -0
  258. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/invariant_deliverables.py +0 -0
  259. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/invariant_floors.py +0 -0
  260. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/invariant_ingest_guard.py +0 -0
  261. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/invariant_lane_health.py +0 -0
  262. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/invariant_reachability.py +0 -0
  263. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/invariant_shared.py +0 -0
  264. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/invariant_unsigned_notes.py +0 -0
  265. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/invariants.py +0 -0
  266. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/invariants_metrics.py +0 -0
  267. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/language.py +0 -0
  268. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/link_targets.py +0 -0
  269. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/lock.py +0 -0
  270. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/maintenance.py +0 -0
  271. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/maintenance_escalation.py +0 -0
  272. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/maintenance_folds.py +0 -0
  273. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/maintenance_folds_2.py +0 -0
  274. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/maintenance_folds_3.py +0 -0
  275. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/maintenance_folds_4.py +0 -0
  276. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/maintenance_golden.py +0 -0
  277. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/maintenance_graphfolds.py +0 -0
  278. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/maintenance_healthlog.py +0 -0
  279. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/maintenance_hotmd.py +0 -0
  280. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/maintenance_invariant_fields.py +0 -0
  281. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/maintenance_notify.py +0 -0
  282. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/maintenance_outcomes.py +0 -0
  283. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/maintenance_para.py +0 -0
  284. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/maintenance_recommendations.py +0 -0
  285. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/maintenance_retention.py +0 -0
  286. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/maintenance_sweep.py +0 -0
  287. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/maintenance_watchdog.py +0 -0
  288. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/mcp_adapter.py +0 -0
  289. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/mcp_capture_verbs.py +0 -0
  290. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/mcp_mediation.py +0 -0
  291. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/mcp_supersede_verbs.py +0 -0
  292. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/mcp_tools/__init__.py +0 -0
  293. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/mcp_tools/capture.py +0 -0
  294. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/mcp_tools/core_read.py +0 -0
  295. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/mcp_tools/housekeeping.py +0 -0
  296. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/mcp_tools/retrieval.py +0 -0
  297. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/mcp_tools/supersession.py +0 -0
  298. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/mcp_verbs.py +0 -0
  299. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/model_slot.py +0 -0
  300. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/multihop.py +0 -0
  301. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/notes.py +0 -0
  302. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/originals_resolve.py +0 -0
  303. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/overlay.py +0 -0
  304. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/overlay_core.py +0 -0
  305. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/overlay_generated.py +0 -0
  306. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/overlay_keywords.py +0 -0
  307. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/overlay_rules.py +0 -0
  308. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/pathkey.py +0 -0
  309. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/progress.py +0 -0
  310. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/projection.py +0 -0
  311. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/provenance.py +0 -0
  312. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/provision.py +0 -0
  313. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/provision_mcp.py +0 -0
  314. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/provision_stage.py +0 -0
  315. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/provision_wire.py +0 -0
  316. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/querylog.py +0 -0
  317. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/querylog_digest.py +0 -0
  318. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/querylog_replay.py +0 -0
  319. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/querylog_status.py +0 -0
  320. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/read_log.py +0 -0
  321. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/reexec.py +0 -0
  322. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/remediation_answers.py +0 -0
  323. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/remediation_branches.py +0 -0
  324. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/remediation_converged.py +0 -0
  325. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/remediation_exceptions.py +0 -0
  326. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/remediation_exceptions_store.py +0 -0
  327. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/remediation_extract_retry.py +0 -0
  328. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/remediation_folds.py +0 -0
  329. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/remediation_questions.py +0 -0
  330. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/remediation_routing.py +0 -0
  331. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/remediation_schema.py +0 -0
  332. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/remediation_state.py +0 -0
  333. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/rerank.py +0 -0
  334. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/rerank_onnx.py +0 -0
  335. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/retrieval_ops.py +0 -0
  336. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/retro.py +0 -0
  337. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/session_hook.py +0 -0
  338. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/session_hook_settings.py +0 -0
  339. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/session_hook_spec.py +0 -0
  340. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/snapshot.py +0 -0
  341. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/status_ops.py +0 -0
  342. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/supersede_declared.py +0 -0
  343. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/sweepdirs.py +0 -0
  344. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/update.py +0 -0
  345. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/update_channels.py +0 -0
  346. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/update_channels_2.py +0 -0
  347. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/update_cos_guard.py +0 -0
  348. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/update_model.py +0 -0
  349. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/update_ops.py +0 -0
  350. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/update_plugins.py +0 -0
  351. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/update_state.py +0 -0
  352. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/update_venv.py +0 -0
  353. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/vectors.py +0 -0
  354. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/versionlink.py +0 -0
  355. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/versionlink_stages.py +0 -0
  356. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/vm_ceiling.py +0 -0
  357. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/vmstaging.py +0 -0
  358. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brain/workspaces.py +0 -0
  359. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brainiac_cli.egg-info/dependency_links.txt +0 -0
  360. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brainiac_cli.egg-info/entry_points.txt +0 -0
  361. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brainiac_cli.egg-info/requires.txt +0 -0
  362. {brainiac_cli-0.20.46 → brainiac_cli-0.20.48}/src/brainiac_cli.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: brainiac-cli
3
- Version: 0.20.46
3
+ Version: 0.20.48
4
4
  Summary: Brainiac — local any-LLM second-brain core engine + brain CLI (Markdown truth, derived SQLite index).
5
5
  License-Expression: Apache-2.0
6
6
  Project-URL: Homepage, https://github.com/Autopsias/brainiac
@@ -8,7 +8,7 @@ build-backend = "setuptools.build_meta"
8
8
  # check. The IMPORT package stays `brain` and the console script stays
9
9
  # `brain` — only the distribution/project name changes.
10
10
  name = "brainiac-cli"
11
- version = "0.20.46"
11
+ version = "0.20.48"
12
12
  description = "Brainiac — local any-LLM second-brain core engine + brain CLI (Markdown truth, derived SQLite index)."
13
13
  readme = "README.md"
14
14
  license = "Apache-2.0"
@@ -1028,6 +1028,12 @@ rules, in order:
1028
1028
  env-exported `$BRAIN_VM_MAX_EGRESS_TIER` is IGNORED (the session's own
1029
1029
  shell can set it; VULN-3386). Raising a VM's ceiling is a host-operator
1030
1030
  action (`brain vm-egress-tier <TIER>`, signed), never the model's own.
1031
+ That clamp binds the VM's own `brain --role vm` CLI only. A Cowork session
1032
+ that reads through the host broker (`brain-mcp`, the Desktop MCP entry)
1033
+ gets the BROKER's ceiling — the full vault unless `$BRAIN_MAX_EGRESS_TIER`
1034
+ narrows that entry — because the broker receives no caller role (owner
1035
+ ruling, `docs/security-acceptances.md` A-01; pentest VULN-3419). When
1036
+ asked for your cap, report the broker's, never the VM file's.
1031
1037
 
1032
1038
  3. **Ask it in every language the vault holds — the VARIANT CONTRACT
1033
1039
  (CON-01) — and paraphrase within your own.** The cross-language half is a
@@ -1850,8 +1856,9 @@ entry formats in `docs/session-memory.md`. Rules an agent needs at a glance:
1850
1856
  count match the host's; there is nothing enforcing that a session actually
1851
1857
  does it. When `N > 0`, do not tell the owner to go run `/brain-inbox` —
1852
1858
  open the exceptions page yourself and walk them through what is open using
1853
- `AskUserQuestion`, one at a time. The two host-home sources
1854
- (`~/.brainiac/update-state.json`, `~/.brain/synthesis-state.json`) stay out
1859
+ `AskUserQuestion`, one at a time. The three host-home sources
1860
+ (`~/.brainiac/update-state.json`, `~/.brain/synthesis-state.json`,
1861
+ `~/.brain/retrieval-watch-state.json`) stay out
1855
1862
  of the VM's reach and are listed under `unreachable` rather than skipped.
1856
1863
  Claude Code and Codex both fire this from a SessionStart hook; run it first
1857
1864
  regardless of harness.
@@ -1928,8 +1935,8 @@ summary is the one authority for open owner questions on the VM leg now.
1928
1935
  That is the whole point of the first bullet above — until 2026-08-14 this
1929
1936
  logic lived only in a Claude Code hook, so a Cowork session worked for days
1930
1937
  against a vault whose invariants had regressed with no surface that could
1931
- tell it. Only the two HOST-HOME sources (`~/.brainiac/update-state.json`,
1932
- `~/.brain/synthesis-state.json`) are out of the VM's reach, and `alerts`
1938
+ tell it. Only the three HOST-HOME sources (`~/.brainiac/update-state.json`,
1939
+ `~/.brain/synthesis-state.json`, `~/.brain/retrieval-watch-state.json`) are out of the VM's reach, and `alerts`
1933
1940
  reports those under `unreachable` rather than skipping them. The reads are
1934
1941
  file-reads of counts and two findings files; the VM still never WRITES
1935
1942
  anything here,
@@ -82,7 +82,9 @@ note and will be rejected; cite the source in the BODY as [[<bare-id>]], never
82
82
  edge, so it inflates the metric while linking nothing); put the [[raw/<id>]]
83
83
  form in the note's `source:` frontmatter as usual; set the note's
84
84
  classification to at least the HIGHEST classification among the sources it
85
- cites, read live off each source; and NEVER append these links into an
85
+ cites, read live off each source; give each new note a SHORT kebab-case
86
+ id of at most 80 characters, the same as its file name (a sentence-long id
87
+ is refused at signing and the note stays unsigned); and NEVER append these links into an
86
88
  existing hand-curated note — always a new note. Work the list in order; in
87
89
  your final summary state HOW MANY of the file's candidates you covered (the
88
90
  file's own "consumed_today" field is the fold's count — quote yours beside
@@ -335,6 +337,16 @@ doctor: $DOCTOR_DIAG"
335
337
  find "$VAULT/brain" -type f -name '*.md' \
336
338
  ! -name 'backlinks.md' ! -name 'catalog.md' \
337
339
  -newer "$SIGN_MARKER" > "$SIGN_LIST" 2>/dev/null || true
340
+ # A sentence-long note name is refused by the drain's safe-id check
341
+ # (2026-10-25: 20 of 45 notes unsigned, rc=75). Give those a short id
342
+ # first; anything it cannot shorten safely is left for the drain to refuse.
343
+ SHORT_LIST="$(mktemp -t brain-synthesis-shortlist)"
344
+ if python3 "$(dirname "$0")/shorten_long_note_ids.py" "$SIGN_LIST" \
345
+ > "$SHORT_LIST" 2>>"$LOG"; then
346
+ mv -f -- "$SHORT_LIST" "$SIGN_LIST"
347
+ else
348
+ rm -f -- "$SHORT_LIST"
349
+ fi
338
350
  SIGNED=0; SIGN_FAIL=0
339
351
  # fd 3, not stdin: `brain write` reads the note body FROM STDIN, so a
340
352
  # plain `while read` loop would feed it the file list instead.
@@ -0,0 +1,100 @@
1
+ """Give a too-long note a short id before the synthesis sign-drain signs it.
2
+
3
+ 2026-10-25: the confined session named 20 of its 45 notes after a whole
4
+ sentence (up to 208 characters). `notes.safe_slug` refuses an id over
5
+ MAX_SLUG_BYTES, so all 20 stayed unsigned and the run failed rc=75.
6
+
7
+ Reads the drain's file list (one path per line), and for each note whose
8
+ file stem is over the limit AND equals its frontmatter `id:`, moves it to
9
+ `<first words>-<8 hex of the old id>.md` and rewrites that one `id:` line.
10
+ Anything else is left exactly as it was, so the drain still refuses it.
11
+ Then rewrites `[[old-id]]` links to `[[new-id]]` in every listed note: the
12
+ same session wrote the notes that link to these, and on 2026-10-26 a rename
13
+ without this left 18 links pointing at files that no longer existed.
14
+ Prints the list back with the new paths. Stdlib only: the script runs it
15
+ with the host's `python3`, outside the engine's venv.
16
+ """
17
+ from __future__ import annotations
18
+
19
+ import hashlib
20
+ import re
21
+ import sys
22
+ from pathlib import Path
23
+
24
+ #: Same number as `brain.notes.MAX_SLUG_BYTES` (a test holds them equal).
25
+ MAX_SLUG_BYTES = 180
26
+ #: Short enough to read, long enough to keep the subject.
27
+ KEEP_BYTES = 80
28
+
29
+
30
+ def short_id(old: str) -> str:
31
+ head = old.encode("utf-8")[:KEEP_BYTES].decode("utf-8", "ignore")
32
+ if "-" in head:
33
+ head = head.rsplit("-", 1)[0]
34
+ return f"{head.rstrip('-.')}-{hashlib.sha256(old.encode('utf-8')).hexdigest()[:8]}"
35
+
36
+
37
+ def shorten(path: Path) -> Path:
38
+ old = path.stem
39
+ if len(old.encode("utf-8")) <= MAX_SLUG_BYTES:
40
+ return path
41
+ lines = path.read_text(encoding="utf-8").split("\n")
42
+ if not lines or lines[0].strip() != "---":
43
+ return path
44
+ for i, line in enumerate(lines[1:], start=1):
45
+ if line.strip() == "---":
46
+ return path # no `id:` line: leave it to the drain
47
+ if line.startswith("id:"):
48
+ if line[3:].strip().strip("\"'") != old:
49
+ return path # the id is not the file name: leave it
50
+ new = short_id(old)
51
+ target = path.with_name(f"{new}.md")
52
+ if target.exists():
53
+ return path
54
+ lines[i] = f"id: {new}"
55
+ target.write_text("\n".join(lines), encoding="utf-8")
56
+ path.unlink()
57
+ return target
58
+ return path
59
+
60
+
61
+ def relink(paths: list[str], renames: dict[str, str]) -> None:
62
+ """Point `[[old]]`, `[[old|..]]` and `[[old#..]]` at the new id."""
63
+ if not renames:
64
+ return
65
+ pattern = re.compile(r"\[\[(" + "|".join(map(re.escape, renames)) + r")(?=[\]|#])")
66
+ for raw in paths:
67
+ path = Path(raw)
68
+ try:
69
+ text = path.read_text(encoding="utf-8")
70
+ except (OSError, UnicodeDecodeError):
71
+ continue
72
+ new = pattern.sub(lambda m: "[[" + renames[m.group(1)], text)
73
+ if new != text:
74
+ path.write_text(new, encoding="utf-8")
75
+
76
+
77
+ def main(argv: list[str]) -> int:
78
+ # "\n" only, as the shell's `read` splits: splitlines() also breaks on
79
+ # \r, \x1c, \u2028 ..., so a crafted file name could add a list entry.
80
+ with open(argv[1], encoding="utf-8", newline="") as fh: # no \r translation
81
+ entries = fh.read().split("\n")
82
+ out: list[str] = []
83
+ renames: dict[str, str] = {}
84
+ for raw in entries:
85
+ if raw:
86
+ try:
87
+ new = shorten(Path(raw))
88
+ except (OSError, UnicodeDecodeError):
89
+ new = Path(raw)
90
+ if str(new) != raw:
91
+ renames[Path(raw).stem] = new.stem
92
+ out.append(str(new))
93
+ relink(out, renames)
94
+ for line in out:
95
+ print(line)
96
+ return 0
97
+
98
+
99
+ if __name__ == "__main__":
100
+ raise SystemExit(main(sys.argv))
@@ -9,4 +9,4 @@ pip-installed host, importlib.metadata stays primary; brain/__init__.py reads
9
9
  this file only as the fallback before 0.0.0+unknown.
10
10
  """
11
11
 
12
- __version__ = "0.20.46"
12
+ __version__ = "0.20.48"
@@ -12,7 +12,7 @@ identical list.
12
12
 
13
13
  Two design rules, both learned from earlier failures in this repo:
14
14
 
15
- * **Silence must never be able to mean "could not look."** Two of the five
15
+ * **Silence must never be able to mean "could not look."** Some of the
16
16
  inputs live in the HOST home (``~/.brainiac``, ``~/.brain``) and the Cowork
17
17
  VM cannot reach them. On ``role=vm`` they are reported in ``unreachable``
18
18
  rather than quietly contributing nothing — the same posture ``doctor`` takes
@@ -42,7 +42,7 @@ from .alerts_staging import ( # noqa: F401 — re-exported for callers/tests
42
42
  _untriaged,
43
43
  _untriaged_key,
44
44
  _unwrap_untriaged,
45
- host_vaults,
45
+ host_vaults, retrieval_watch_alerts, RETRIEVAL_WATCH_UNREACHABLE,
46
46
  staging_alerts,
47
47
  )
48
48
 
@@ -443,7 +443,7 @@ def collect(
443
443
  """Every alert this role can see, plus what it could not look at.
444
444
 
445
445
  ``host`` sweeps the workspace registry; ``vm`` reads only its own vault and
446
- reports the two host-home sources as unreachable."""
446
+ reports the host-home sources as unreachable."""
447
447
  home = home or Path.home()
448
448
  today = today or datetime.date.today()
449
449
  alerts: list[dict[str, str]] = []
@@ -454,7 +454,7 @@ def collect(
454
454
  "auto-update state (~/.brainiac/update-state.json) — host-only, "
455
455
  "run `brain alerts` on the host Mac",
456
456
  "weekly synthesis task health (~/.brain/synthesis-state.json) — "
457
- "host-only, run `brain alerts` on the host Mac",
457
+ "host-only, run `brain alerts` on the host Mac", RETRIEVAL_WATCH_UNREACHABLE,
458
458
  ]
459
459
  vaults = [Path(vault)] if vault else []
460
460
  if not vaults:
@@ -467,7 +467,7 @@ def collect(
467
467
  else:
468
468
  alerts += update_alerts(home, today)
469
469
  alerts += staging_alerts(home)
470
- alerts += synthesis_alerts(home, today)
470
+ alerts += synthesis_alerts(home, today) + retrieval_watch_alerts(home, today)
471
471
  alerts += wiring_alerts()
472
472
  vaults = host_vaults(home)
473
473
  if not vaults and vault:
@@ -186,3 +186,39 @@ def host_vaults(home: Path) -> list[Path]:
186
186
  if path not in seen:
187
187
  seen.append(path)
188
188
  return seen
189
+
190
+
191
+ #: The monthly retrieval watch fires on day 1; past this its silence is the news.
192
+ RETRIEVAL_WATCH_STALE_DAYS = 35
193
+
194
+
195
+ def retrieval_watch_alerts(home: Path, today: Any) -> list[dict[str, str]]:
196
+ """The monthly retrieval-quality watch (``~/.brain/retrieval-watch-state.json``,
197
+ written by ``scripts/brain-retrieval-watch.sh``). Until 2026-10-27 a FAIL
198
+ went only to ``hot.md``, a log nobody has to read, and it failed unread on
199
+ three months running against a baseline from a retired embedder."""
200
+ from .alerts import _alert, _date, _read_json
201
+
202
+ state = _read_json(home / ".brain" / "retrieval-watch-state.json")
203
+ if not isinstance(state, dict):
204
+ return [] # never installed on this host — nothing to watch
205
+ day = _date(state.get("at"))
206
+ if state.get("verdict") == "FAIL":
207
+ return [_alert(
208
+ "retrieval-watch:fail",
209
+ f"monthly retrieval check FAILED on {state.get('at')} — run /autoresearch, "
210
+ "or re-baseline if the engine or embedder changed")]
211
+ if state.get("verdict") == "ERROR":
212
+ return [_alert(
213
+ "retrieval-watch:error",
214
+ f"monthly retrieval check could not run on {state.get('at')} — "
215
+ "read ~/.brain/logs/launchd-retrievalwatch-stderr.log")]
216
+ if day is None or (today - day).days > RETRIEVAL_WATCH_STALE_DAYS:
217
+ return [_alert(
218
+ "retrieval-watch:stale",
219
+ f"monthly retrieval check has not run since {state.get('at') or 'never'}")]
220
+ return []
221
+
222
+ RETRIEVAL_WATCH_UNREACHABLE = (
223
+ "monthly retrieval check (~/.brain/retrieval-watch-state.json) — host-only, "
224
+ "run `brain alerts` on the host Mac")
@@ -95,7 +95,7 @@ def _add_doctor(sub) -> None:
95
95
  def _add_alerts(sub) -> None:
96
96
  sp = sub.add_parser(
97
97
  "alerts",
98
- help="READ-ONLY degradation digest every harness can call: auto-update state, weekly-synthesis task health, engine-feedback backlog, the owner-decision queue, and whatever is degraded RIGHT NOW per the findings feed `brain maintain` rewrites each run. Pure file reads — no index, embedder, network or key. role=vm reads only its own vault and REPORTS the two host-home sources it cannot reach, so silence never means 'could not look'.",
98
+ help="READ-ONLY degradation digest every harness can call: auto-update state, weekly-synthesis task health, engine-feedback backlog, the owner-decision queue, and whatever is degraded RIGHT NOW per the findings feed `brain maintain` rewrites each run. Pure file reads — no index, embedder, network or key. role=vm reads only its own vault and REPORTS the host-home sources it cannot reach, so silence never means 'could not look'.",
99
99
  )
100
100
  sp.add_argument("--json", action="store_true")
101
101
  sp.add_argument(
@@ -178,9 +178,9 @@ from .concealment_gate import (AUTHORED, CHROME, COVERED_SOURCES, # noqa: F401
178
178
 
179
179
  #: Every marker this module can emit. A run record carries exactly one.
180
180
  MARKERS = (
181
- "hidden_white_text", # text the same colour as the ground it sits on
181
+ "hidden_white_text", # text too close to its ground to read
182
182
  "hidden_transparent_text", # zero-alpha colour: invisible on any ground
183
- "hidden_tiny_text", # resolved font size below 2pt
183
+ "hidden_tiny_text", # resolved font size below TINY_PT
184
184
  "hidden_offscreen_text", # parked far off the page / outside the slide
185
185
  "hidden_vanish_run", # Word w:vanish / w:webHidden
186
186
  "hidden_render_mode", # PDF text render mode 3 or 7 (invisible)
@@ -195,8 +195,18 @@ MARKERS = (
195
195
  NEAR = 0xF0
196
196
  #: Per-channel distance at which text is "the same colour as its ground".
197
197
  GROUND_DELTA = 0x10
198
- #: Resolved sizes below this many CSS pixels are hidden (design: < 2pt).
199
- TINY_PX = 2 * 4 / 3
198
+ #: Below this WCAG contrast ratio against its ground, text is hidden too. A
199
+ #: coloured near-white walked past ``GROUND_DELTA`` alone: ``#ffffdd`` on
200
+ #: white is 1.02:1, fainter than the ``#f0f0f0`` (1.14:1) it caught (pentest
201
+ #: VULN-3418, 2026-10-28). Measured on 1,055 real originals that day: 0
202
+ #: convictions at 1.2, 1.3 and 1.5; new labels in 32 documents at 1.5 (mostly
203
+ #: headings and slide labels), 25 at 1.3, 10 at 1.2.
204
+ MIN_CONTRAST = 1.2
205
+ #: Text below this size is hidden. Was 2pt, and 2.7px (2.03pt) text passed it
206
+ #: (VULN-3418). 4pt added 1,805 runs of real 3-4pt slide labels; 3pt added 1.
207
+ #: One value for every lane: the Office walkers take it in points.
208
+ TINY_PT = 3
209
+ TINY_PX = TINY_PT * 4 / 3
200
210
  #: An offset at least this far negative is off the page, not a layout nudge.
201
211
  OFFSCREEN_PX = 1000.0
202
212
 
@@ -306,21 +316,34 @@ def colour_marker(colour: tuple[int, int, int, float] | None,
306
316
  """The marker a resolved text colour earns against its resolved ground.
307
317
 
308
318
  ``hidden_transparent_text`` for a zero alpha (invisible whatever is
309
- behind it) and ``hidden_white_text`` when the text is within
310
- ``GROUND_DELTA`` of its ground — white-on-white being the case this was
311
- measured on. Named for that case because that is what it catches in
312
- practice; the test is "the same colour as what is behind it".
319
+ behind it) and ``hidden_white_text`` when the text, blended over its
320
+ ground by its alpha, is within ``GROUND_DELTA`` of it on every channel or
321
+ below ``MIN_CONTRAST``. Named for white-on-white because that is what it
322
+ catches in practice; the test is "too close to what is behind it to read".
313
323
  """
314
324
  if colour is None:
315
325
  return None
316
326
  if colour[3] <= 0.0:
317
327
  return "hidden_transparent_text"
318
328
  bg = ground if ground is not None and ground[3] > 0.0 else (*WHITE, 1.0)
319
- if all(abs(colour[i] - bg[i]) <= GROUND_DELTA for i in range(3)):
329
+ a = min(colour[3], 1.0)
330
+ seen = tuple(a * colour[i] + (1 - a) * bg[i] for i in range(3))
331
+ if (all(abs(seen[i] - bg[i]) <= GROUND_DELTA for i in range(3))
332
+ or contrast_ratio(seen, bg) < MIN_CONTRAST):
320
333
  return "hidden_white_text"
321
334
  return None
322
335
 
323
336
 
337
+ def contrast_ratio(fg: tuple[float, ...], bg: tuple[float, ...]) -> float:
338
+ """WCAG 2 contrast ratio of two sRGB colours, from 1.0 (same) to 21.0."""
339
+ def luminance(rgb: tuple[float, ...]) -> float:
340
+ lin = [c / 255 / 12.92 if c / 255 <= 0.04045
341
+ else ((c / 255 + 0.055) / 1.055) ** 2.4 for c in rgb[:3]]
342
+ return 0.2126 * lin[0] + 0.7152 * lin[1] + 0.0722 * lin[2]
343
+ hi, lo = sorted((luminance(fg), luminance(bg)), reverse=True)
344
+ return (hi + 0.05) / (lo + 0.05)
345
+
346
+
324
347
  def to_px(value: str) -> float | None:
325
348
  """A CSS length in pixels, or ``None`` when the value carries no unit."""
326
349
  m = _SIZE.search(value or "")
@@ -21,7 +21,7 @@ from __future__ import annotations
21
21
 
22
22
  from typing import Any, Callable
23
23
 
24
- from .concealment import colour_marker, normalise, note_degraded, run_record
24
+ from .concealment import TINY_PT, colour_marker, normalise, note_degraded, run_record
25
25
 
26
26
  #: Theme slots that are white in every stock Office theme. ``pptx.py`` reads no
27
27
  #: theme part, so this is a NAME test, not a resolved colour — see the gap list
@@ -170,7 +170,7 @@ def docx_runs(document: Any,
170
170
  from docx.shared import Pt
171
171
  from docx.text.run import Run
172
172
 
173
- rgb_type, tiny, run_tag = MSO_COLOR_TYPE.RGB, Pt(2), qn("w:r")
173
+ rgb_type, tiny, run_tag = MSO_COLOR_TYPE.RGB, Pt(TINY_PT), qn("w:r")
174
174
  runs: list[dict[str, Any]] = []
175
175
  for index, paragraph in enumerate(_docx_paragraphs(document), start=1):
176
176
  container = f"docx:p{index}"
@@ -253,7 +253,7 @@ def pptx_runs(presentation: Any,
253
253
 
254
254
  runs: list[dict[str, Any]] = []
255
255
  width, height = presentation.slide_width, presentation.slide_height
256
- style = (MSO_COLOR_TYPE.RGB, Pt(2))
256
+ style = (MSO_COLOR_TYPE.RGB, Pt(TINY_PT))
257
257
  for si, slide in enumerate(presentation.slides, start=1):
258
258
  for k, shape in enumerate(slide.shapes):
259
259
  offscreen = _shape_offscreen(shape, width, height)
@@ -87,6 +87,35 @@ _HIDDEN_STYLE = re.compile(
87
87
  r"""color\s*:\s*#?(fff(fff)?|white)\b)""",
88
88
  re.I,
89
89
  )
90
+ #: An inline ``style`` value: double-quoted, single-quoted or bare, one group
91
+ #: each, so a quote of the OTHER kind inside the value cannot end it early.
92
+ _STYLE_ATTR = re.compile(
93
+ r"""\bstyle\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>"']+))""", re.I)
94
+
95
+
96
+ def _hidden_style_offsets(text: str) -> list[int]:
97
+ """Where hiding styling starts: the literal patterns above, plus any inline
98
+ ``style=""`` the handler walkers would call hidden — too little contrast on
99
+ a white page, or below the size floor. ``color:#ffffdd;font-size:2.7px``
100
+ matched no literal (pentest VULN-3418, 2026-10-28)."""
101
+ found = {m.start() for m in _HIDDEN_STYLE.finditer(text)}
102
+ if _STYLE_ATTR.search(text):
103
+ from .ingest.handlers import concealment as c
104
+
105
+ for m in _STYLE_ATTR.finditer(text):
106
+ if any(m.start() <= f < m.end() for f in found):
107
+ continue # a literal already marked this attribute
108
+ decl = c.declarations("".join(g or "" for g in m.groups()))
109
+ try:
110
+ hidden = bool(
111
+ c.colour_marker(c.parse_colour(decl.get("color", "")), None)
112
+ or c.is_tiny(c.to_px(decl.get("font-size", ""))))
113
+ except (ValueError, OverflowError):
114
+ hidden = True # unparseable: fail closed, the instruction check decides
115
+ if hidden:
116
+ found.add(m.start())
117
+ return sorted(found)
118
+
90
119
 
91
120
  # --------------------------------------------------------------------------
92
121
  # Instruction: imperative phrasing in the clear. Flagged, never quarantined.
@@ -243,14 +272,14 @@ def _concealment_hits(text: str) -> list[dict[str, Any]]:
243
272
  "offset": m.start(),
244
273
  "excerpt": " ".join(body.split())[:160],
245
274
  })
246
- for m in _HIDDEN_STYLE.finditer(text):
247
- window = text[m.start(): m.start() + 400]
275
+ for start in _hidden_style_offsets(text):
276
+ window = text[start: start + 400]
248
277
  found = _instruction_hits(window)
249
278
  if found:
250
279
  out.append({
251
280
  "marker": "hidden_styled_instruction",
252
281
  "revealed": found[0]["marker"],
253
- "offset": m.start(),
282
+ "offset": start,
254
283
  "excerpt": " ".join(window.split())[:160],
255
284
  })
256
285
  return out
@@ -26,7 +26,7 @@ UNCHANGED and keep real paths. Both already branch correctly on
26
26
  pinned workspace identity, schema, freshness — ``exceptions_verify.verify``,
27
27
  called from inside
28
28
  ``alerts.exceptions_alerts``/``exceptions_cli.vault_row``) before anything in
29
- it is trusted, and the two host-home sources
29
+ it is trusted, and the host-home sources
30
30
  (``~/.brainiac/update-state.json``, ``~/.brain/synthesis-state.json``) come
31
31
  back under ``unreachable`` rather than being silently skipped. That
32
32
  verification is NOT reimplemented here — it is the same call the CLI makes,
@@ -72,6 +72,8 @@ from .remediation_schema import ( # noqa: F401 (facade re-export)
72
72
  UNTRIAGED_PREFIX as UNTRIAGED_PREFIX,
73
73
  )
74
74
  from . import remediation_schema as _schema
75
+ from .remediation_invariants import ( # the invariant:* rows, split out by size
76
+ _AUTO_INVARIANTS, _OWNER_INVARIANTS, _SHELF_INVARIANTS)
75
77
 
76
78
  # ---------------------------------------------------------------------------
77
79
  # Owning branches and their cadences (in days). The scheduler reads THIS, so a
@@ -131,89 +133,6 @@ def validate_mechanical_allow_list(
131
133
  validate_mechanical_allow_list()
132
134
  MECHANICAL_QUARANTINE_REASONS = ALLOWED_MECHANICAL_QUARANTINE_REASONS
133
135
 
134
- _AUTO_INVARIANTS = {
135
- "invariant:unsigned_notes": Remedy(
136
- AUTO, "sign_repair", 3, "additive", shadow_nights=3,
137
- note="signs ONLY notes with host-side provenance of an interrupted "
138
- "host write; an unsigned note without it is a TAMPER exception"),
139
- "invariant:unguarded_ingests": Remedy(
140
- AUTO, "reguard", 3, "raising", shadow_nights=3,
141
- note="ENF-04 re-run; only ever raises, undecided fails closed high"),
142
- "invariant:subfloor_families": Remedy(
143
- AUTO, "extract_retry", 3, "additive",
144
- note="heals only when re-extraction clears the ENF-01 floor, never by "
145
- "merging stubs"),
146
- "invariant:unlinked_sources": Remedy(
147
- AUTO, "link_lane", 2, "additive",
148
- note="fix-05: keyed to BAK-04 lane health, not the raw count; "
149
- "escalate_after is 2 MISSED WEEKS at the link_lane cadence"),
150
- }
151
-
152
- _OWNER_INVARIANTS = {
153
- "invariant:cross_tier_duplicates": Remedy(
154
- OWNER, note="CUR-01-style proposal batch; re-tiering cascades"),
155
- "invariant:cross_tier_candidates": Remedy(
156
- OWNER, note="the UNDECIDED band — a guess here is the failure mode"),
157
- "invariant:cross_tier_twins": Remedy(
158
- OWNER, note="proposal batch, same store as the other two"),
159
- "invariant:unreachable_gold": Remedy(
160
- OWNER, note="usually a retirement mistake; reinstating is an owner act"),
161
- }
162
-
163
- #: DLV-05's three shelf counters. BANNER, and deliberately neither of the two
164
- #: dispositions its neighbours in this block carry.
165
- #:
166
- #: NOT ``auto``: an auto row is a promise about the REMEDIATION apparatus — an
167
- #: owning branch that writes a `_remediation.branches.<name>` row every night,
168
- #: which `branch_liveness`, `coverage` and `thrashing_targets` then read to
169
- #: decide whether to suppress the finding. The nightly fold that actually
170
- #: converges these three (`deliverables_shelf_fold`, and the drop lane's
171
- #: recovery pass at the top of every drain) is an ordinary maintain fold, not a
172
- #: remediation branch, and writes no such row — so declaring `auto` here would
173
- #: claim an escalation apparatus that does not exist and park a permanently
174
- #: empty branch on the exceptions page.
175
- #:
176
- #: NOT ``owner``: an owner row must be ONE decidable question with options and
177
- #: a stated default. A count that stayed above its floor is not that — and each
178
- #: CAUSE already has its own named, answerable surface (`shelf:move-cap`,
179
- #: `shelf:refused`, `shelf:diverged`, `shelf:sole-copy`, the five
180
- #: `deliverables:*` resolver refusals). Asking a second time here would be the
181
- #: same finding twice.
182
- #:
183
- #: So: the same "something stopped" class as those nine keys, and structurally
184
- #: visible for as long as it is true.
185
- _SHELF_INVARIANTS = {
186
- "invariant:unshelved_deliverables": Remedy(
187
- BANNER,
188
- note="marked deliverables the shelf ledger claims no copy of. The "
189
- "nightly shelf fold copies every censused deliverable, so a count "
190
- "above the floor means that fold declined or could not finish — "
191
- "and the reason it declined carries its own key"),
192
- "invariant:stale_shelf_entries": Remedy(
193
- BANNER,
194
- note="shelf ledger rows whose note has left the census. Nothing is "
195
- "lost (the shelf never deletes; displaced copies go to "
196
- "_previous/), but the shelf has stopped telling the truth about "
197
- "what the vault holds"),
198
- "invariant:unanchored_deliverable_payloads": Remedy(
199
- BANNER,
200
- note="drop-lane payloads with no anchor note. The lane's own recovery "
201
- "pass retries the anchor at the top of every drain, so a count "
202
- "that stays up means that write keeps failing — the one metric "
203
- "that can see the shelf mechanism having stopped entirely, since "
204
- "the other two read 0 of 0 when nothing is being marked at all"),
205
- "invariant:supersede_declared_failed": Remedy(
206
- BANNER,
207
- note="live notes whose `replaces:` names a note that is NOT retired "
208
- "under them (SUP-01) — the old version is still live in search. "
209
- "`brain health-report` samples the pairs; `brain supersede <old> "
210
- "<new>` on the host clears each"),
211
- "supersede_declared_failed": Remedy(
212
- BANNER,
213
- note="this run's owner-declared supersession(s) that could not be "
214
- "applied; the corpus count above keeps reporting them"),
215
- }
216
-
217
136
  _EXACT: dict[str, Remedy] = {
218
137
  **_AUTO_INVARIANTS,
219
138
  **_OWNER_INVARIANTS,
@@ -253,6 +172,16 @@ _EXACT: dict[str, Remedy] = {
253
172
  AUTO, "synthesis_retry", 1, "process",
254
173
  note="retried at most once per night — it costs real money"),
255
174
  "synthesis:stale": Remedy(AUTO, "synthesis_retry", 1, "process"),
175
+ # Monthly retrieval watch (alerts_staging.retrieval_watch_alerts). BANNER,
176
+ # all three: no branch can rerun or re-baseline it, and LOG would put it
177
+ # back in hot.md alone, where three false FAILs went unread (974cf75b).
178
+ "retrieval-watch:fail": Remedy(
179
+ BANNER, note="quality dropped below the baseline; the owner runs "
180
+ "/autoresearch, or re-baselines after an engine change"),
181
+ "retrieval-watch:error": Remedy(
182
+ BANNER, note="the watch could not run; its launchd stderr log says why"),
183
+ "retrieval-watch:stale": Remedy(
184
+ BANNER, note="no monthly run in 35 days: the job itself stopped"),
256
185
  # Mechanical quarantine: an operator action on THIS host, retryable.
257
186
  **{f"quarantine:{r}": Remedy(AUTO, "extract_retry", 3, "process")
258
187
  for r in MECHANICAL_QUARANTINE_REASONS},