brainiac-cli 0.20.32__tar.gz → 0.20.34__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 (462) hide show
  1. {brainiac_cli-0.20.32/src/brainiac_cli.egg-info → brainiac_cli-0.20.34}/PKG-INFO +1 -1
  2. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/pyproject.toml +1 -1
  3. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/AGENTS.md +36 -6
  4. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/scripts/install-brief-mac.sh +119 -12
  5. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/scripts/vm-boundary-probe.sh +67 -16
  6. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_version.py +1 -1
  7. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/alerts.py +61 -0
  8. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/alerts_staging.py +34 -4
  9. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cli.py +24 -24
  10. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cli_cmds/index_storage_parser.py +1 -1
  11. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cli_cmds/lifecycle.py +46 -0
  12. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cli_cmds/lifecycle_parser.py +32 -0
  13. brainiac_cli-0.20.34/src/brain/cli_read_record.py +124 -0
  14. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/config.py +31 -0
  15. brainiac_cli-0.20.34/src/brain/cowork_leak_artifact.py +24 -0
  16. brainiac_cli-0.20.34/src/brain/cowork_leak_databases.py +133 -0
  17. brainiac_cli-0.20.34/src/brain/cowork_leak_notes.py +138 -0
  18. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cowork_leak_scan.py +41 -164
  19. brainiac_cli-0.20.34/src/brain/cowork_snapshot_guard.py +59 -0
  20. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/doctor.py +6 -5
  21. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/doctor_checks.py +11 -1
  22. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/doctor_context.py +16 -0
  23. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/doctor_mount_leak.py +75 -8
  24. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/doctor_staging.py +16 -97
  25. brainiac_cli-0.20.34/src/brain/doctor_wiring.py +247 -0
  26. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/draft_drain.py +67 -4
  27. brainiac_cli-0.20.34/src/brain/draft_update.py +106 -0
  28. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/egress.py +11 -0
  29. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/graph.py +48 -6
  30. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/index/_tools.py +12 -2
  31. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/mcp_mediation.py +21 -13
  32. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/mcp_tools/core_read.py +2 -2
  33. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/mcp_verbs.py +35 -18
  34. brainiac_cli-0.20.34/src/brain/pathkey.py +143 -0
  35. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/provision.py +138 -77
  36. brainiac_cli-0.20.34/src/brain/provision_mcp.py +113 -0
  37. brainiac_cli-0.20.34/src/brain/provision_stage.py +128 -0
  38. brainiac_cli-0.20.34/src/brain/provision_wire.py +461 -0
  39. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/read_log.py +74 -11
  40. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/remediation.py +21 -0
  41. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/snapshot.py +48 -2
  42. brainiac_cli-0.20.34/src/brain/sweepdirs.py +294 -0
  43. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/update_channels.py +51 -40
  44. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/workspaces.py +68 -6
  45. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34/src/brainiac_cli.egg-info}/PKG-INFO +1 -1
  46. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brainiac_cli.egg-info/SOURCES.txt +12 -0
  47. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/LICENSE +0 -0
  48. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/MANIFEST.in +0 -0
  49. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/README.md +0 -0
  50. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/setup.cfg +0 -0
  51. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/__init__.py +0 -0
  52. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/__main__.py +0 -0
  53. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/assets/graph-explorer-template.html +0 -0
  54. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/overlay/template/brand/brand-guide.md +0 -0
  55. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/overlay/template/cos/auto-archive.md +0 -0
  56. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/overlay/template/cos/browser-lane.md +0 -0
  57. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/overlay/template/cos/drafts.md +0 -0
  58. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/overlay/template/cos/ingest.md +0 -0
  59. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/overlay/template/cos/priorities.md +0 -0
  60. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/overlay/template/cos/tenant-domains.md +0 -0
  61. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/overlay/template/keywords/glossary.md +0 -0
  62. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/overlay/template/people/roster.md +0 -0
  63. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/overlay/template/voice/voice-profile.md +0 -0
  64. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/routines/manifest.json +0 -0
  65. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/scripts/brain-brief-mac.plist +0 -0
  66. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/scripts/brain-brief.sh +0 -0
  67. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/scripts/brain-synthesis-mac.plist +0 -0
  68. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/scripts/brain-synthesis.sh +0 -0
  69. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/scripts/brainiac-alerts.sh +0 -0
  70. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/scripts/install-brief-windows.ps1 +0 -0
  71. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/scripts/register_tasks.py +0 -0
  72. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/scripts/vm-selftest.sh +0 -0
  73. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/templates/company.md +0 -0
  74. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/templates/concept.md +0 -0
  75. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/templates/daily.md +0 -0
  76. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/templates/decision.md +0 -0
  77. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/templates/deliverable.md +0 -0
  78. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/templates/meeting.md +0 -0
  79. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/templates/person.md +0 -0
  80. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/templates/project.md +0 -0
  81. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/templates/state-moc.md +0 -0
  82. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/tools/cos_browser_scan.mjs +0 -0
  83. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/tools/cos_cdp_capture.py +0 -0
  84. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/tools/cos_contract.py +0 -0
  85. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/tools/cos_contract_criteria.py +0 -0
  86. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/tools/cos_contract_criteria_2.py +0 -0
  87. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/tools/cos_contract_ledger_scan.py +0 -0
  88. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/tools/cos_contract_provenance.py +0 -0
  89. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/tools/cos_contract_snapshot_shape.py +0 -0
  90. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/tools/cos_contract_verdict_clauses.py +0 -0
  91. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/tools/cos_driver.py +0 -0
  92. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/tools/cos_driver_accounting.py +0 -0
  93. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/tools/cos_driver_capture.py +0 -0
  94. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/tools/cos_driver_categories.py +0 -0
  95. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/tools/cos_driver_cli.py +0 -0
  96. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/tools/cos_driver_completeness.py +0 -0
  97. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/tools/cos_driver_draw.py +0 -0
  98. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/tools/cos_driver_enumeration.py +0 -0
  99. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/tools/cos_driver_gate.py +0 -0
  100. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/tools/cos_driver_night_records.py +0 -0
  101. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/tools/cos_driver_selfcheck.py +0 -0
  102. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/tools/cos_driver_transport.py +0 -0
  103. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/tools/cos_mutate.py +0 -0
  104. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/tools/cos_mutate_apply.py +0 -0
  105. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/tools/cos_mutate_bridge.py +0 -0
  106. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/tools/cos_mutate_canary.py +0 -0
  107. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/tools/cos_mutate_cli.py +0 -0
  108. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/tools/cos_mutate_evidence.py +0 -0
  109. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/tools/cos_mutate_gates.py +0 -0
  110. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/tools/cos_mutate_ledger.py +0 -0
  111. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/tools/cos_mutate_passes.py +0 -0
  112. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/tools/cos_mutate_plan.py +0 -0
  113. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/tools/cos_mutate_plan_aged.py +0 -0
  114. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/tools/cos_mutate_policy.py +0 -0
  115. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/tools/cos_mutate_rehearsal.py +0 -0
  116. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/tools/cos_mutate_selfcheck.py +0 -0
  117. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/tools/cos_mutate_shapes.py +0 -0
  118. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/tools/cos_mutate_shapestore.py +0 -0
  119. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/tools/cos_reconcile_append.py +0 -0
  120. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/tools/cos_reconcile_guard.py +0 -0
  121. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/tools/cos_reconcile_metrics.py +0 -0
  122. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/tools/cos_reconcile_rows.py +0 -0
  123. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/tools/cos_reconcile_steps.py +0 -0
  124. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/tools/cos_retro.py +0 -0
  125. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/_assets/tools/cos_retro_scanners.py +0 -0
  126. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/anchor.py +0 -0
  127. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/atomic_temp.py +0 -0
  128. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/attestation.py +0 -0
  129. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/audit.py +0 -0
  130. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/audit_chain.py +0 -0
  131. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/audit_drift.py +0 -0
  132. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/autolink.py +0 -0
  133. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/backup.py +0 -0
  134. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/brief.py +0 -0
  135. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/brief_render.py +0 -0
  136. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/capture.py +0 -0
  137. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/chunk.py +0 -0
  138. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/classification.py +0 -0
  139. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cli_cmds/__init__.py +0 -0
  140. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cli_cmds/common_parser.py +0 -0
  141. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cli_cmds/connect.py +0 -0
  142. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cli_cmds/connect_parser.py +0 -0
  143. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cli_cmds/cos_capture.py +0 -0
  144. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cli_cmds/cos_capture_parser.py +0 -0
  145. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cli_cmds/cos_operations.py +0 -0
  146. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cli_cmds/cos_operations_parser.py +0 -0
  147. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cli_cmds/eval.py +0 -0
  148. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cli_cmds/eval_parser.py +0 -0
  149. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cli_cmds/index_storage.py +0 -0
  150. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cli_cmds/ingest_storage.py +0 -0
  151. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cli_cmds/ingest_storage_parser.py +0 -0
  152. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cli_cmds/maintenance.py +0 -0
  153. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cli_cmds/maintenance_parser.py +0 -0
  154. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cli_cmds/navigation.py +0 -0
  155. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cli_cmds/navigation_parser.py +0 -0
  156. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cli_cmds/owner_workflow.py +0 -0
  157. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cli_cmds/owner_workflow_parser.py +0 -0
  158. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cli_cmds/presentation.py +0 -0
  159. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cli_cmds/presentation_parser.py +0 -0
  160. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cli_cmds/retrieval.py +0 -0
  161. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cli_cmds/retrieval_parser.py +0 -0
  162. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cli_cmds/update.py +0 -0
  163. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cli_cmds/update_parser.py +0 -0
  164. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cli_cmds/versioning.py +0 -0
  165. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cli_cmds/versioning_parser.py +0 -0
  166. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cli_help.py +0 -0
  167. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cli_render.py +0 -0
  168. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/config_hostpaths.py +0 -0
  169. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/connect.py +0 -0
  170. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/context.py +0 -0
  171. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/conventions.py +0 -0
  172. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/conventions_scan.py +0 -0
  173. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/core/__init__.py +0 -0
  174. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/core/_audit.py +0 -0
  175. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/core/_briefing.py +0 -0
  176. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/core/_capture.py +0 -0
  177. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/core/_capture_staging.py +0 -0
  178. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/core/_cos_facade.py +0 -0
  179. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/core/_durability.py +0 -0
  180. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/core/_health.py +0 -0
  181. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/core/_maintenance.py +0 -0
  182. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/core/_maintenance_state.py +0 -0
  183. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/core/_retrieval.py +0 -0
  184. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/core/_shared.py +0 -0
  185. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/core/_supersession.py +0 -0
  186. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/core/_supersession_journal.py +0 -0
  187. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/__init__.py +0 -0
  188. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_answer_consume.py +0 -0
  189. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_approval.py +0 -0
  190. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_approval_cleanup.py +0 -0
  191. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_attachment_acceptance.py +0 -0
  192. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_attachment_anchors.py +0 -0
  193. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_attachment_store.py +0 -0
  194. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_autocap.py +0 -0
  195. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_batch_apply.py +0 -0
  196. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_batches.py +0 -0
  197. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_behavior.py +0 -0
  198. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_claim_binding.py +0 -0
  199. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_claim_evaluation.py +0 -0
  200. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_claim_sweep.py +0 -0
  201. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_claims_state.py +0 -0
  202. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_constants.py +0 -0
  203. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_consumption.py +0 -0
  204. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_corrections.py +0 -0
  205. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_criteria.py +0 -0
  206. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_evidence.py +0 -0
  207. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_facade.py +0 -0
  208. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_guards.py +0 -0
  209. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_hold_release.py +0 -0
  210. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_hold_store.py +0 -0
  211. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_hold_undo.py +0 -0
  212. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_ingest_claim.py +0 -0
  213. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_ingest_sweep.py +0 -0
  214. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_ingress.py +0 -0
  215. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_io.py +0 -0
  216. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_layout.py +0 -0
  217. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_layout_setup.py +0 -0
  218. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_learning_config.py +0 -0
  219. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_learning_ledger.py +0 -0
  220. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_ledger_join.py +0 -0
  221. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_priority.py +0 -0
  222. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_proposal_maintenance.py +0 -0
  223. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_proposal_state.py +0 -0
  224. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_routing.py +0 -0
  225. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_run_manifest_write.py +0 -0
  226. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_run_migration.py +0 -0
  227. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_runs.py +0 -0
  228. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_shared.py +0 -0
  229. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_spine.py +0 -0
  230. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_standing_approval.py +0 -0
  231. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_status.py +0 -0
  232. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_taxonomy.py +0 -0
  233. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_version_apply.py +0 -0
  234. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos/_version_links.py +0 -0
  235. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos_chips.py +0 -0
  236. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos_corpus.py +0 -0
  237. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos_corpus_retention.py +0 -0
  238. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos_corpus_write.py +0 -0
  239. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos_deploy.py +0 -0
  240. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos_echecks.py +0 -0
  241. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos_echecks_answers.py +0 -0
  242. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos_echecks_answers_2.py +0 -0
  243. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos_echecks_delivery.py +0 -0
  244. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos_echecks_runs.py +0 -0
  245. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos_runverify.py +0 -0
  246. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos_runverify_alerts.py +0 -0
  247. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos_runverify_checks.py +0 -0
  248. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos_runverify_chipdraw.py +0 -0
  249. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos_runverify_chips.py +0 -0
  250. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos_runverify_contract.py +0 -0
  251. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos_runverify_corpus.py +0 -0
  252. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos_runverify_identity.py +0 -0
  253. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos_runverify_io.py +0 -0
  254. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos_runverify_join.py +0 -0
  255. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos_runverify_ledger.py +0 -0
  256. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos_runverify_metrics.py +0 -0
  257. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos_runverify_planbinding.py +0 -0
  258. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos_runverify_preamble.py +0 -0
  259. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos_runverify_selfeval.py +0 -0
  260. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos_runverify_stamps.py +0 -0
  261. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cos_runverify_touch.py +0 -0
  262. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/cowork_staging.py +0 -0
  263. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/dbretry.py +0 -0
  264. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/deliverables_absorb.py +0 -0
  265. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/deliverables_apply.py +0 -0
  266. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/deliverables_ledger.py +0 -0
  267. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/deliverables_materialize.py +0 -0
  268. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/deliverables_previous.py +0 -0
  269. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/deliverables_readme.py +0 -0
  270. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/deliverables_shelf.py +0 -0
  271. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/deliverables_sync.py +0 -0
  272. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/doctor_desktop.py +0 -0
  273. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/doctor_health.py +0 -0
  274. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/doctor_plugins.py +0 -0
  275. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/doctor_vendor.py +0 -0
  276. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/doctor_vm.py +0 -0
  277. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/embed.py +0 -0
  278. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/embed_backends.py +0 -0
  279. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/embed_catalog.py +0 -0
  280. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/embed_onnx.py +0 -0
  281. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/embed_qwen.py +0 -0
  282. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/encryption.py +0 -0
  283. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/exceptions_cli.py +0 -0
  284. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/exceptions_page.py +0 -0
  285. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/exceptions_render.py +0 -0
  286. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/exceptions_tray.py +0 -0
  287. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/exceptions_verify.py +0 -0
  288. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/folds/__init__.py +0 -0
  289. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/folds/context.py +0 -0
  290. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/folds/cos.py +0 -0
  291. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/folds/daily.py +0 -0
  292. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/folds/deliverables.py +0 -0
  293. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/folds/golden.py +0 -0
  294. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/folds/graph.py +0 -0
  295. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/folds/intake.py +0 -0
  296. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/folds/invariants.py +0 -0
  297. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/folds/orchestrator.py +0 -0
  298. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/folds/organization.py +0 -0
  299. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/folds/preflight.py +0 -0
  300. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/folds/publish.py +0 -0
  301. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/folds/remediation.py +0 -0
  302. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/folds/remediation_bookkeeping.py +0 -0
  303. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/folds/reporting.py +0 -0
  304. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/folds/retention.py +0 -0
  305. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/folds/watchdogs.py +0 -0
  306. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/folds/weekly.py +0 -0
  307. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/frontmatter.py +0 -0
  308. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/golden_ops.py +0 -0
  309. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/golden_probe.py +0 -0
  310. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/golden_probe_cases.py +0 -0
  311. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/graph_hygiene.py +0 -0
  312. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/graph_ops.py +0 -0
  313. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/graphify.py +0 -0
  314. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/graphify_edges.py +0 -0
  315. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/graphreport.py +0 -0
  316. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/graphreport_sections.py +0 -0
  317. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/graphreport_sections_2.py +0 -0
  318. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/healthreport.py +0 -0
  319. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/healthreport_sections.py +0 -0
  320. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/healthreport_unsigned.py +0 -0
  321. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/inbox.py +0 -0
  322. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/index/__init__.py +0 -0
  323. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/index/_connection.py +0 -0
  324. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/index/_families.py +0 -0
  325. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/index/_graph.py +0 -0
  326. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/index/_identity.py +0 -0
  327. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/index/_lifecycle.py +0 -0
  328. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/index/_models.py +0 -0
  329. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/index/_planning.py +0 -0
  330. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/index/_records.py +0 -0
  331. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/index/_retirement.py +0 -0
  332. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/index/_schema.py +0 -0
  333. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/index/_search.py +0 -0
  334. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/index/_settings.py +0 -0
  335. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/index/_shared.py +0 -0
  336. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/index/_zones.py +0 -0
  337. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/index_stages/__init__.py +0 -0
  338. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/index_stages/connection.py +0 -0
  339. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/index_stages/diagnostics.py +0 -0
  340. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/index_stages/duplicate_families.py +0 -0
  341. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/index_stages/near_duplicates.py +0 -0
  342. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/index_stages/rebuild.py +0 -0
  343. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/index_stages/reranking.py +0 -0
  344. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/index_stages/search.py +0 -0
  345. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/index_stages/search_finalization.py +0 -0
  346. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/index_stages/search_identity.py +0 -0
  347. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/index_stages/search_ranking.py +0 -0
  348. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/index_stages/sync.py +0 -0
  349. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/ingest/__init__.py +0 -0
  350. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/ingest/deliverables.py +0 -0
  351. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/ingest/handlers/__init__.py +0 -0
  352. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/ingest/handlers/base.py +0 -0
  353. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/ingest/handlers/docx.py +0 -0
  354. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/ingest/handlers/email.py +0 -0
  355. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/ingest/handlers/html.py +0 -0
  356. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/ingest/handlers/image.py +0 -0
  357. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/ingest/handlers/pdf.py +0 -0
  358. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/ingest/handlers/pptx.py +0 -0
  359. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/ingest/handlers/tables.py +0 -0
  360. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/ingest/handlers/text.py +0 -0
  361. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/ingest/handlers/xlsx.py +0 -0
  362. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/ingest/handlers/zip.py +0 -0
  363. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/ingest/pipeline.py +0 -0
  364. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/ingest/pipeline_duplicates.py +0 -0
  365. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/ingest/pipeline_files.py +0 -0
  366. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/ingest/pipeline_injection.py +0 -0
  367. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/ingest/pipeline_nested.py +0 -0
  368. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/ingest/pipeline_run.py +0 -0
  369. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/ingest/pipeline_stages.py +0 -0
  370. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/ingest/tierguard.py +0 -0
  371. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/ingest/tierguard_verdict.py +0 -0
  372. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/ingest/transcript.py +0 -0
  373. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/init.py +0 -0
  374. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/init_import.py +0 -0
  375. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/init_samples.py +0 -0
  376. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/init_seed.py +0 -0
  377. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/init_steps.py +0 -0
  378. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/injection_scan.py +0 -0
  379. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/invariant_coverage.py +0 -0
  380. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/invariant_crosstier.py +0 -0
  381. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/invariant_deliverables.py +0 -0
  382. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/invariant_floors.py +0 -0
  383. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/invariant_ingest_guard.py +0 -0
  384. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/invariant_lane_health.py +0 -0
  385. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/invariant_reachability.py +0 -0
  386. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/invariant_shared.py +0 -0
  387. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/invariant_unsigned_notes.py +0 -0
  388. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/invariants.py +0 -0
  389. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/invariants_metrics.py +0 -0
  390. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/language.py +0 -0
  391. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/link_targets.py +0 -0
  392. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/lock.py +0 -0
  393. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/maintenance.py +0 -0
  394. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/maintenance_escalation.py +0 -0
  395. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/maintenance_folds.py +0 -0
  396. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/maintenance_folds_2.py +0 -0
  397. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/maintenance_folds_3.py +0 -0
  398. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/maintenance_folds_4.py +0 -0
  399. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/maintenance_golden.py +0 -0
  400. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/maintenance_graphfolds.py +0 -0
  401. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/maintenance_healthlog.py +0 -0
  402. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/maintenance_hotmd.py +0 -0
  403. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/maintenance_invariant_fields.py +0 -0
  404. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/maintenance_notify.py +0 -0
  405. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/maintenance_outcomes.py +0 -0
  406. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/maintenance_recommendations.py +0 -0
  407. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/maintenance_retention.py +0 -0
  408. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/maintenance_sweep.py +0 -0
  409. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/maintenance_watchdog.py +0 -0
  410. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/mcp_adapter.py +0 -0
  411. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/mcp_capture_verbs.py +0 -0
  412. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/mcp_housekeeping_verbs.py +0 -0
  413. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/mcp_tools/__init__.py +0 -0
  414. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/mcp_tools/capture.py +0 -0
  415. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/mcp_tools/housekeeping.py +0 -0
  416. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/mcp_tools/retrieval.py +0 -0
  417. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/multihop.py +0 -0
  418. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/notes.py +0 -0
  419. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/originals_resolve.py +0 -0
  420. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/overlay.py +0 -0
  421. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/overlay_rules.py +0 -0
  422. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/progress.py +0 -0
  423. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/projection.py +0 -0
  424. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/provenance.py +0 -0
  425. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/querylog.py +0 -0
  426. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/querylog_digest.py +0 -0
  427. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/querylog_replay.py +0 -0
  428. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/querylog_status.py +0 -0
  429. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/reexec.py +0 -0
  430. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/remediation_answers.py +0 -0
  431. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/remediation_branches.py +0 -0
  432. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/remediation_exceptions.py +0 -0
  433. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/remediation_exceptions_store.py +0 -0
  434. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/remediation_extract_retry.py +0 -0
  435. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/remediation_folds.py +0 -0
  436. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/remediation_questions.py +0 -0
  437. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/remediation_routing.py +0 -0
  438. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/remediation_schema.py +0 -0
  439. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/remediation_state.py +0 -0
  440. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/rerank.py +0 -0
  441. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/rerank_onnx.py +0 -0
  442. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/retrieval_ops.py +0 -0
  443. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/retro.py +0 -0
  444. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/session_hook.py +0 -0
  445. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/spine.py +0 -0
  446. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/spine_render.py +0 -0
  447. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/status_ops.py +0 -0
  448. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/update.py +0 -0
  449. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/update_channels_2.py +0 -0
  450. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/update_model.py +0 -0
  451. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/update_ops.py +0 -0
  452. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/update_plugins.py +0 -0
  453. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/update_state.py +0 -0
  454. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/vectors.py +0 -0
  455. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/versionlink.py +0 -0
  456. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/versionlink_stages.py +0 -0
  457. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/vm_ceiling.py +0 -0
  458. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brain/vmstaging.py +0 -0
  459. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brainiac_cli.egg-info/dependency_links.txt +0 -0
  460. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brainiac_cli.egg-info/entry_points.txt +0 -0
  461. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/src/brainiac_cli.egg-info/requires.txt +0 -0
  462. {brainiac_cli-0.20.32 → brainiac_cli-0.20.34}/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.32
3
+ Version: 0.20.34
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
  Requires-Python: >=3.9
@@ -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.32"
11
+ version = "0.20.34"
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"
@@ -65,7 +65,9 @@ profile-a-brain/
65
65
  inbox, routines copy (gitignored). The live
66
66
  index.sqlite + audit chain live in the per-user
67
67
  app-data dir (`config.index_dir()`); override
68
- with $BRAIN_INDEX_DIR per vault.
68
+ with $BRAIN_INDEX_DIR per vault, or move the
69
+ whole app-data base with $BRAIN_APP_DATA_DIR
70
+ (which keeps the per-vault nesting).
69
71
  ├── memory/ ← session memory: handoff.md,
70
72
  │ hot.md, lessons.md, archive/
71
73
  │ (host-only, never indexed — §9)
@@ -1072,11 +1074,23 @@ Obsidian "five-step retrieval cascade" rule for any harness reading this file.
1072
1074
  > `brain-mcp` adapter. Full table: `docs/harness-wiring.md`.
1073
1075
  >
1074
1076
  > **Cowork-Windows VM (PRIMARY surface):** Cowork is Claude Desktop's Linux VM
1075
- > sandbox execution mode (`docs/glossary.md`). Run `brain --role vm` (or
1076
- > `export BRAIN_ROLE=vm`). The VM is **read + draft only** — it reads ONLY the
1077
- > published read-only snapshot in `.brain/snapshot/` (never WAL), captures via
1078
- > `brain draft-capture` into `.brain/capture-inbox/`, and never resolves a signing
1079
- > key; the host drains + signs + indexes + republishes the snapshot. Install +
1077
+ > sandbox execution mode (`docs/glossary.md`). **Two shapes exist, and which
1078
+ > one a given workspace runs depends on whether it has been through Closed
1079
+ > Stacks s07's cutover (2026-08-27 → 2026-08-30, VULN-3385).** Pre-cutover /
1080
+ > co-located: run `brain --role vm` (or `export BRAIN_ROLE=vm`) — the VM is
1081
+ > **read + draft only**, reading ONLY the published read-only snapshot in
1082
+ > `.brain/snapshot/` (never WAL), capturing via `brain draft-capture` into
1083
+ > `.brain/capture-inbox/`, never resolving a signing key; the host drains +
1084
+ > signs + indexes + republishes the snapshot. **Post-cutover (a relocated
1085
+ > workspace):** the vault is not attached to the VM's mount at all — `brain`
1086
+ > is not on the session's `PATH`, and there is no local snapshot to read —
1087
+ > every read reaches the vault only through the host `brain-mcp` broker,
1088
+ > which applies the same classification filter and now writes a fail-closed
1089
+ > audit record before returning content. The broker's ceiling for this leg is
1090
+ > still the same full-vault default the host itself uses (no per-caller tier
1091
+ > narrowing is configured), so a relocated workspace is a MITIGATION of
1092
+ > VULN-3385, not a closure — full posture and residual:
1093
+ > `docs/install/cowork.md`, `docs/security/vuln-3385-risk-reduction.md`. Install +
1080
1094
  > per-session PATH/model re-export: `docs/cowork-windows-install.md`.
1081
1095
  >
1082
1096
  > **Where the kernel skills live per client:** the ten
@@ -1217,6 +1231,22 @@ The VM is a **read + draft** surface only; the host is the **only writer**.
1217
1231
  atomically republishes the read-only, generation-stamped snapshot into
1218
1232
  `.brain/snapshot/`. Only now is the note retrievable from the VM.
1219
1233
 
1234
+ **In-place updates (UPD-01, 2026-08-30).** A duplicate id used to be a dead
1235
+ end: the drain skipped it forever, silently — a Cowork session's rule update
1236
+ sat refused in `capture-inbox/` for 11 days while the session believed it had
1237
+ applied. A draft may now declare an IN-PLACE update of an existing note with
1238
+ two transport-only frontmatter keys: `updates: <id>` (must equal the draft's
1239
+ own id) and `base_sha256: <sha>` (the `sha256` that `brain get` returned for
1240
+ the version the author read). The drain applies it through the same signed
1241
+ `write_note` path ONLY when the live note is untrusted-lane-authored
1242
+ (`provenance.trust: untrusted`) and under `brain/` — the untrusted leg may
1243
+ rewrite what the untrusted lane wrote, never a host-authored note (those skip
1244
+ `update-needs-owner`; apply on the host with `brain write`) and never `raw/`
1245
+ (append-only). A base-hash mismatch skips as `stale-base` instead of
1246
+ clobbering a newer write. The transport keys are dropped before signing, and
1247
+ `brain alerts` now reports any draft still in the inbox after 48h
1248
+ (`stuck-drafts`) — a refusal is never silent again.
1249
+
1220
1250
  ### VM-request → host-drain vault provisioning (PRV-10, 2026-08-17)
1221
1251
 
1222
1252
  A Cowork session can also create a whole NEW vault, with the same trust
@@ -40,9 +40,14 @@ if [ -z "$LABEL" ]; then
40
40
  LABEL="com.brainiac.nightly.$_h"
41
41
  fi
42
42
 
43
+ # THE SAME override brain.config.nightly_plist_path reads. A test (or a
44
+ # sandboxed install) that redirects one must redirect both, or a test that
45
+ # merges a sweep dir reads, backs up and rewrites the developer's REAL plist --
46
+ # $HOME is not enough, because launchctl talks to the login domain by uid.
47
+ LAUNCH_AGENTS_DIR="${BRAIN_LAUNCH_AGENTS_DIR:-$HOME/Library/LaunchAgents}"
43
48
  LEGACY_LABEL="com.profile-a-brain.daily-brief"
44
- LEGACY_PLIST="$HOME/Library/LaunchAgents/$LEGACY_LABEL.plist"
45
- PLIST_DST="$HOME/Library/LaunchAgents/$LABEL.plist"
49
+ LEGACY_PLIST="$LAUNCH_AGENTS_DIR/$LEGACY_LABEL.plist"
50
+ PLIST_DST="$LAUNCH_AGENTS_DIR/$LABEL.plist"
46
51
  LOGDIR="${BRAIN_LOG_DIR:-$HOME/.brain/logs}"
47
52
 
48
53
  # Audit signing key: the drain resolves it from the macOS Keychain AT RUNTIME
@@ -53,6 +58,12 @@ LOGDIR="${BRAIN_LOG_DIR:-$HOME/.brain/logs}"
53
58
  # injected BRAIN_AUDIT_KEY_PEM (custom custody / unattended box) — and the
54
59
  # plist is chmod 600 below either way.
55
60
  KEYCHAIN_SERVICE="${BRAIN_AUDIT_KEYCHAIN_SERVICE:-profile-a-brain-audit-key}"
61
+ # `set -u` + an unset $USER is a hard abort, not a fallback: launchd, cron and
62
+ # any `env -i` caller hand this script an environment without USER, and both
63
+ # uses below took the script down at line 75 with "USER: unbound variable"
64
+ # BEFORE it could write a plist. Measured 2026-08-30 running
65
+ # `brain provision-local` end to end: wire 1 reported `init: exit 1`.
66
+ KEY_ACCOUNT="${USER:-$(id -un)}"
56
67
  AUDIT_KEY="${BRAIN_AUDIT_KEY_PEM:-}"
57
68
 
58
69
  # Managed endpoints (BRAIN_MANAGED=1) resolve the key ONLY from the OS keystore
@@ -67,14 +78,65 @@ if [ -n "${BRAIN_MANAGED:-}" ] && [ "${BRAIN_MANAGED}" != "0" ] && [ -n "$AUDIT_
67
78
  fi
68
79
 
69
80
  if [ -z "$AUDIT_KEY" ]; then
70
- if ! security find-generic-password -s "$KEYCHAIN_SERVICE" -a "$USER" >/dev/null 2>&1; then
81
+ if ! security find-generic-password -s "$KEYCHAIN_SERVICE" -a "$KEY_ACCOUNT" >/dev/null 2>&1; then
71
82
  echo "WARNING: No Keychain entry for '$KEYCHAIN_SERVICE'." >&2
72
83
  echo " Captures will not be signed (drain fails closed). Run 'brain audit-key'" >&2
73
84
  echo " (create-if-absent, never rotates) — no reinstall needed after." >&2
74
85
  fi
75
86
  fi
76
87
 
77
- mkdir -p "$LOGDIR" "$HOME/Library/LaunchAgents"
88
+ mkdir -p "$LOGDIR" "$LAUNCH_AGENTS_DIR"
89
+
90
+ # THE PLIST IS THE DURABLE HOME OF THE SWEEP LIST, for every caller.
91
+ # This script re-renders the whole body on every run and reinstalls when it
92
+ # differs, so an UNSET $BRAIN_WORKSPACE_SWEEP_DIRS used to bake an empty string
93
+ # -- and any `brain init --full --apply` from a bare shell (a reinstall, a
94
+ # second vault, an old doc, `brain update`) silently wiped a list somebody had
95
+ # configured. Unset now means "keep what is installed"; an explicitly SET value
96
+ # still wins, including an explicit empty one (the operator's intent).
97
+ if [ -n "${BRAIN_WORKSPACE_SWEEP_DIRS+set}" ]; then
98
+ SWEEP_DIRS="$BRAIN_WORKSPACE_SWEEP_DIRS"
99
+ elif [ -f "$PLIST_DST" ]; then
100
+ SWEEP_DIRS="$(/usr/libexec/PlistBuddy -c \
101
+ "Print :EnvironmentVariables:BRAIN_WORKSPACE_SWEEP_DIRS" \
102
+ "$PLIST_DST" 2>/dev/null || true)"
103
+ else
104
+ SWEEP_DIRS=""
105
+ fi
106
+ # EVERY value substituted into the plist below goes through this, because each
107
+ # one is escaped TWICE over and both stages are load-bearing.
108
+ #
109
+ # 1. The value lands inside an XML <string>, so `&` and `<` in a folder name
110
+ # would render a plist nothing can parse -- and the PlistBuddy round-trip
111
+ # above reads the value back out unescaped, so it would corrupt a little
112
+ # more on every run.
113
+ # 2. ...and THEN it becomes a sed REPLACEMENT, where `\`, `&` and the `|`
114
+ # delimiter are metacharacters of their own. A bare `&` in a replacement
115
+ # means "the whole match", so the `&` stage 1 just wrote as `&amp;`
116
+ # re-inserts the literal placeholder text: probed 2026-08-30,
117
+ # BRAIN_WORKSPACE_SWEEP_DIRS=/tmp/A&B<C rendered as
118
+ # `/tmp/ASWEEP_DIRS_PLACEHOLDERamp;BSWEEP_DIRS_PLACEHOLDERlt;C`, and
119
+ # VAULT=/tmp/A&B<C rendered `<string>/tmp/AVAULT_PATHB<C</string>` --- an
120
+ # unparseable plist from an ORDINARY path (`R&D`, `Smith & Co`).
121
+ #
122
+ # It is a function because doing it for the sweep list alone left the two
123
+ # substitutions directly below it raw (probed 2026-08-30). $AUDIT_KEY needs no
124
+ # metacharacter escaping -- base64 and the PEM header contain none of \ & | < --
125
+ # and $LABEL is `com.brainiac.nightly.<hex8>` by construction. It is NOT used
126
+ # on $AUDIT_KEY, though: a real PEM carries embedded NEWLINES (multi-line,
127
+ # `-----BEGIN...-----\n<body>\n-----END...-----\n`), and a `sed -e` REPLACEMENT
128
+ # cannot hold a raw newline without a `\`-continuation before each one -- an
129
+ # unescaped one is a parse error, not silent corruption. AUDIT_KEY gets its own
130
+ # newline-safe substitution below instead of going through this sed pipeline.
131
+ xml_sed_escape() {
132
+ printf '%s' "$1" | sed -e 's/&/\&amp;/g' -e 's/</\&lt;/g' \
133
+ | sed -e 's/[\\&|]/\\&/g'
134
+ }
135
+
136
+ SWEEP_DIRS_SED="$(xml_sed_escape "$SWEEP_DIRS")"
137
+ VAULT_SED="$(xml_sed_escape "$VAULT")"
138
+ HOME_SED="$(xml_sed_escape "$HOME")"
139
+ SCRIPTS_SED="$(xml_sed_escape "$SCRIPTS")"
78
140
 
79
141
  # Render the plist to a scratch file first so we can diff against whatever is
80
142
  # already installed — the nightly-diff contract (docs/install/plugin-distribution.md
@@ -84,18 +146,60 @@ PLIST_NEW="$(mktemp "${TMPDIR:-/tmp}/brain-brief-mac.plist.XXXXXX")"
84
146
  trap 'rm -f "$PLIST_NEW"' EXIT
85
147
 
86
148
  sed -e "s|LABEL_PLACEHOLDER|$LABEL|g" \
87
- -e "s|SCRIPTS_DIR|$SCRIPTS|g" \
88
- -e "s|VAULT_PATH|$VAULT|g" \
89
- -e "s|HOME_DIR|$HOME|g" \
90
- -e "s|AUDIT_KEY_PEM_PLACEHOLDER|$AUDIT_KEY|g" \
91
- -e "s|SWEEP_DIRS_PLACEHOLDER|${BRAIN_WORKSPACE_SWEEP_DIRS:-}|g" \
149
+ -e "s|SCRIPTS_DIR|$SCRIPTS_SED|g" \
150
+ -e "s|VAULT_PATH|$VAULT_SED|g" \
151
+ -e "s|HOME_DIR|$HOME_SED|g" \
152
+ -e "s|SWEEP_DIRS_PLACEHOLDER|$SWEEP_DIRS_SED|g" \
92
153
  "$PLIST_SRC" > "$PLIST_NEW"
93
154
 
155
+ # AUDIT_KEY_PEM_PLACEHOLDER: substituted OUTSIDE the sed pipeline above with
156
+ # awk, whose replacement text is an ordinary string (no per-`-e`-argument
157
+ # newline restriction) rather than a sed script fragment --- the same
158
+ # multi-line value that breaks `sed -e "s|X|$AUDIT_KEY|g"` (probed 2026-08-31,
159
+ # running `provision-local` end to end with a synthetic Ed25519 test key:
160
+ # "sed: 1: ...: unescaped newline inside substitute pattern") assigns straight
161
+ # through `ENVIRON["AUDIT_KEY"]` here. Trailing newline(s) are stripped by the
162
+ # `$( )` around printf below (command substitution always strips them),
163
+ # matching what the OLD single-line sed substitution effectively produced too.
164
+ # NOT guarded on a non-empty $AUDIT_KEY. Managed mode (line 74) and a box with
165
+ # no key both set it to "", and the placeholder MUST still be replaced --- by
166
+ # nothing. A guard here skipped the awk and left the literal string
167
+ # `AUDIT_KEY_PEM_PLACEHOLDER` baked into the plist as BRAIN_AUDIT_KEY_PEM
168
+ # (probed 2026-08-31 against the pre-change script, which rendered
169
+ # `<string></string>`), handing every managed endpoint a garbage PEM instead of
170
+ # the empty value that makes the runtime fall back to the Keychain. `split("")`
171
+ # yields no fields, so `pem[1]` is "" and the placeholder collapses to nothing.
172
+ AUDIT_KEY_TRIMMED="$(printf '%s' "$AUDIT_KEY")"
173
+ AUDIT_KEY="$AUDIT_KEY_TRIMMED" awk '
174
+ function xml_awk_escape(s) {
175
+ # Same two-char XML entity escape as xml_sed_escape above --- `&`
176
+ # first, so `<` does not re-encode the `&` that step just wrote.
177
+ # No sub()/gsub()-replacement-side "&" concern here: this gsub
178
+ # RUNS the escape, it is not used as another calls REPLACEMENT
179
+ # argument (that is the substr()-based split below, precisely to
180
+ # avoid needing a second round of escaping on top of this one).
181
+ gsub(/&/, "\\&amp;", s); gsub(/</, "\\&lt;", s); return s
182
+ }
183
+ {
184
+ pos = index($0, "AUDIT_KEY_PEM_PLACEHOLDER")
185
+ if (!pos) { print; next }
186
+ n = split(ENVIRON["AUDIT_KEY"], pem, "\n")
187
+ prefix = substr($0, 1, pos - 1)
188
+ suffix = substr($0, pos + length("AUDIT_KEY_PEM_PLACEHOLDER"))
189
+ # substr()/printf concatenation only, below --- never sub()/gsub()
190
+ # with pem[i] as the REPLACEMENT argument, which would reinterpret
191
+ # any literal "&" the escape above just wrote as "insert match".
192
+ printf "%s%s", prefix, xml_awk_escape(pem[1])
193
+ for (i = 2; i <= n; i++) printf "\n%s", xml_awk_escape(pem[i])
194
+ print suffix
195
+ }
196
+ ' "$PLIST_NEW" > "$PLIST_NEW.awk" && mv "$PLIST_NEW.awk" "$PLIST_NEW"
197
+
94
198
  # ponytail: real launchctl talks to the login launchd domain by uid, not by
95
199
  # $HOME — a sandboxed HOME does NOT sandbox launchctl. BRAIN_LAUNCHD_DRY_RUN=1
96
200
  # (or a non-default HOME, detected below) skips the real bootstrap/load calls
97
201
  # so test/sandbox runs never touch the real user's launchd domain.
98
- if [ -z "${BRAIN_LAUNCHD_DRY_RUN:-}" ] && [ "$HOME" != "$(eval echo ~"$USER")" ]; then
202
+ if [ -z "${BRAIN_LAUNCHD_DRY_RUN:-}" ] && [ "$HOME" != "$(eval echo ~"$KEY_ACCOUNT")" ]; then
99
203
  BRAIN_LAUNCHD_DRY_RUN=1
100
204
  fi
101
205
 
@@ -140,10 +244,13 @@ echo "✓ Installed: $LABEL (hourly umbrella, vault: $VAULT)"
140
244
  # vault in ~/.brainiac/workspaces.json, so re-running this installer for a
141
245
  # second vault just refreshes the same host-level agent. Weekly Sun 08:00.
142
246
  SYNTH_SRC="$SCRIPTS/brain-synthesis-mac.plist"
143
- SYNTH_DST="$HOME/Library/LaunchAgents/com.brainiac.synthesis.plist"
247
+ SYNTH_DST="$LAUNCH_AGENTS_DIR/com.brainiac.synthesis.plist"
144
248
  if [ -f "$SYNTH_SRC" ]; then
145
249
  SYNTH_NEW="$(mktemp "${TMPDIR:-/tmp}/brain-synthesis-mac.plist.XXXXXX")"
146
- sed -e "s|SCRIPTS_DIR|$SCRIPTS|g" -e "s|HOME_DIR|$HOME|g" \
250
+ # Same two-stage escaping as the nightly block above: a raw $SCRIPTS or
251
+ # $HOME carrying `&`, `<` or `|` corrupts this plist too (see the worked
252
+ # example at the top of the nightly substitution).
253
+ sed -e "s|SCRIPTS_DIR|$SCRIPTS_SED|g" -e "s|HOME_DIR|$HOME_SED|g" \
147
254
  "$SYNTH_SRC" > "$SYNTH_NEW"
148
255
  if ! cmp -s "$SYNTH_NEW" "$SYNTH_DST" 2>/dev/null; then
149
256
  cp "$SYNTH_NEW" "$SYNTH_DST"
@@ -38,7 +38,13 @@ export PATH="$BRAIN_RUNTIME_DIR:$PATH"
38
38
  # refusal, whatever the exit code says.
39
39
  REFUSAL_RE='is host-broker only|RoleError|role=vm is read\+draft'
40
40
 
41
- BREACHED=0 # the boundary itself failed
41
+ BREACHED=0 # the boundary itself failed — the VM did something it must not
42
+ DEGRADED=0 # section D only: the VM cannot do something it legitimately may.
43
+ # Counted SEPARATELY and never printed as BREACHED. These are
44
+ # opposite failures with opposite remediations, and summing them
45
+ # is how a real breach eventually gets ignored (2026-08-30: a
46
+ # cut-over workspace with no local snapshot printed BREACHED
47
+ # while all 27 containment probes passed).
42
48
  INVALID=0 # this script asked the wrong question
43
49
  OBSERVED=0 # true-but-expected conditions (see section B)
44
50
  say(){ echo "$@"; }
@@ -266,16 +272,53 @@ case "$KEY_OUT" in
266
272
  esac
267
273
  say ""
268
274
 
269
- say "### D. the two things a VM legitimately CAN do must still work"
275
+ say "### D. the things a VM legitimately CAN do must still work"
270
276
  say "-- snapshot read"
277
+ # A LOCAL snapshot is no longer the VM's read surface. s06b moved all 14 Cowork
278
+ # skills onto the broker's MCP tools, and s07 ruled `snapshot/` GOES because it
279
+ # carries 3105 non-empty MNPI note bodies — publishing one back onto the mount
280
+ # would re-open the very finding the cutover closed. So its ABSENCE is the fix
281
+ # working, and this probe must not read as a capability loss. Where no snapshot
282
+ # is present the read surface is the broker, which this script cannot exercise.
271
283
  # --no-rerank (RK-02 caller policy, 2026-08-04): a liveness check on the
272
284
  # snapshot read path — it discards stdout entirely, so paying for BR-03's
273
285
  # default-on cross-encoder here buys literally nothing.
274
- if brain search "${VM_PROBE_TERM:-project}" --no-rerank --json >/dev/null 2>&1; then
286
+ # ASK `brain status` ONCE, AND KEEP ITS EXIT CODE. Until 2026-08-30 this line
287
+ # read `brain status` inside the test itself, so a status that FAILED --- no
288
+ # engine on PATH, a role refusal, a broken index --- produced empty output and
289
+ # was scored identically to a real absence. That is the "clean because the
290
+ # input was empty" shape the rest of this change set exists to remove, sitting
291
+ # inside the script that reports it.
292
+ _status_out="$(brain status 2>/dev/null)" && _status_rc=0 || _status_rc=$?
293
+ _snap_line="$(printf '%s\n' "$_status_out" | sed -n 's/^snapshot:[[:space:]]*//p')"
294
+ # THREE WAYS TO KNOW NOTHING, AND ONLY ONE OF THEM IS THE DESIGN WORKING.
295
+ # A status that FAILED, a status that answered but never mentions a snapshot,
296
+ # and a status that says `absent` all produced the same empty string once. Only
297
+ # the third is the post-cutover N/A. The first two are the probe failing to
298
+ # ask, and reporting them as N/A is the "clean because the input was empty"
299
+ # all-clear this script exists to catch elsewhere. Both earlier shapes were
300
+ # reproduced against stub interpreters before this was written.
301
+ if [ "$_status_rc" -ne 0 ] || [ -z "$_status_out" ]; then
302
+ say " INCONCLUSIVE — \`brain status\` did not answer (rc=$_status_rc), so"
303
+ say " whether a snapshot is present was never established."
304
+ say " This is NOT the post-cutover N/A below; it is the"
305
+ say " probe failing to ask."
306
+ INVALID=$((INVALID+1))
307
+ elif [ -z "$_snap_line" ]; then
308
+ say " INCONCLUSIVE — \`brain status\` answered but reported no \`snapshot:\`"
309
+ say " line at all, so this probe learned nothing. An older"
310
+ say " or differently-configured engine reads this way; it is"
311
+ say " not evidence that no snapshot exists."
312
+ INVALID=$((INVALID+1))
313
+ elif [ -z "$(printf '%s\n' "$_snap_line" | grep -v '^absent')" ]; then
314
+ say " N/A — no local snapshot on this mount, which is the post-cutover"
315
+ say " design (it carries MNPI bodies). The VM reads through the"
316
+ say " broker's MCP tools instead; exercise that from the session."
317
+ elif brain search "${VM_PROBE_TERM:-project}" --no-rerank --json >/dev/null 2>&1; then
275
318
  say " PASS — snapshot search returned"
276
319
  else
277
- say " FAIL — the VM cannot read the snapshot (boundary is over-tight)"
278
- BREACHED=$((BREACHED+1))
320
+ say " DEGRADED — a snapshot is present but the VM cannot read it"
321
+ DEGRADED=$((DEGRADED+1))
279
322
  fi
280
323
  say "-- proposal drop (unsigned, into a dir \`sync\` never reads)"
281
324
  # This leaves a real file in the drop dir for as long as it takes to delete it.
@@ -288,7 +331,7 @@ DROP_OUT="$(printf -- '---\nid: %s\ntitle: "VM boundary probe — safe to reject
288
331
  printf '%s\n' "$DROP_OUT" | sed 's/^/ | /'
289
332
  case "$DROP_OUT" in
290
333
  *dropped*) say " PASS — unsigned drop accepted, nothing signed";;
291
- *) say " FAIL — the VM cannot even drop a proposal"; BREACHED=$((BREACHED+1));;
334
+ *) say " DEGRADED — the VM cannot even drop a proposal"; DEGRADED=$((DEGRADED+1));;
292
335
  esac
293
336
  # Delete the file the engine actually reported, not one this script guesses at
294
337
  # — a guessed path that has drifted cleans up nothing and says it did.
@@ -296,25 +339,33 @@ PROBE_FILE="$(printf '%s' "$DROP_OUT" | sed -n 's/.*"proposal"[[:space:]]*:[[:sp
296
339
  [ -n "$PROBE_FILE" ] || PROBE_FILE="$BRAIN_RUNTIME_DIR/cos/drop/proposal-drop/$PROBE_ID.md"
297
340
  rm -f "$PROBE_FILE" 2>/dev/null
298
341
  if [ -e "$PROBE_FILE" ]; then
299
- say " FAIL — could not clean up $PROBE_FILE; delete it before the next"
300
- say " broker fold or it becomes an owner-batch question"
301
- BREACHED=$((BREACHED+1))
342
+ say " DEGRADED — could not clean up $PROBE_FILE; delete it before the next"
343
+ say " broker fold or it becomes an owner-batch question."
344
+ say " (A Cowork shell has deletion blocked by policy, so this"
345
+ say " is expected there — remove it from the host instead.)"
346
+ DEGRADED=$((DEGRADED+1))
302
347
  else
303
348
  say " cleanup: probe drop removed ($PROBE_FILE)"
304
349
  fi
305
350
  say ""
306
351
 
307
352
  say "=== SUMMARY ==="
308
- say " boundary failures : $BREACHED"
353
+ say " boundary failures : $BREACHED (the VM did something it must not)"
354
+ say " degraded : $DEGRADED (section D: the VM cannot do something it may)"
309
355
  say " invalid probes : $INVALID (this script asked the wrong question)"
310
356
  say " observations : $OBSERVED (host-private bytes readable over the mount)"
311
- if [ $BREACHED -eq 0 ] && [ $INVALID -eq 0 ]; then
312
- say "=== VERDICT: BOUNDARY INTACT ==="
313
- exit 0
357
+ if [ $BREACHED -gt 0 ]; then
358
+ say "=== VERDICT: BREACHED — $BREACHED probe(s) failed ==="
359
+ exit 1
314
360
  fi
315
- if [ $BREACHED -eq 0 ]; then
361
+ if [ $INVALID -gt 0 ]; then
316
362
  say "=== VERDICT: INCONCLUSIVE — $INVALID probe(s) never reached the gate ==="
317
363
  exit 2
318
364
  fi
319
- say "=== VERDICT: BREACHED — $BREACHED probe(s) failed ==="
320
- exit 1
365
+ if [ $DEGRADED -gt 0 ]; then
366
+ say "=== VERDICT: BOUNDARY INTACT, DEGRADED — $DEGRADED capability probe(s) failed ==="
367
+ say " The containment held. Something the VM is ALLOWED to do did not work."
368
+ exit 3
369
+ fi
370
+ say "=== VERDICT: BOUNDARY INTACT ==="
371
+ exit 0
@@ -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.32"
12
+ __version__ = "0.20.34"
@@ -69,6 +69,14 @@ UPDATE_STATE_MAX_AGE_DAYS = 7
69
69
  # A weekly task that has not succeeded in longer than this has missed a run.
70
70
  SYNTHESIS_STALE_DAYS = 8
71
71
 
72
+ # The hourly nightly drains capture drafts on every firing, so a draft still
73
+ # sitting in the inbox after this many hours has been REFUSED by the drain
74
+ # (duplicate-id, stale-base, update-needs-owner, ...) and will never promote
75
+ # on its own. Until 2026-08-30 nothing reported this: a 19-Aug rule update
76
+ # sat refused for 11 days while the session that staged it believed it had
77
+ # applied (UPD-01).
78
+ STUCK_DRAFT_HOURS = 48
79
+
72
80
  # The feed's `findings[].key` vocabulary is closed (see maintenance_notify.py:
73
81
  # "blocked", "synthesis-watchdog", "trend:<metric>", "invariant:<metric>",
74
82
  # "branch-escalate:<branch>", "ingest_quarantine*", ...). The dir is on the
@@ -181,6 +189,22 @@ def synthesis_alerts(home: Path, today: datetime.date) -> list[dict[str, str]]:
181
189
  return out
182
190
 
183
191
 
192
+ def wiring_alerts() -> list[dict[str, str]]:
193
+ """The ONE push channel for ``doctor``'s non-gating "Vault wiring" row
194
+ (DOC-01): ``brain doctor`` reports it, but nothing was watching it
195
+ between session starts before this. ``check_vault_wiring`` resolves the
196
+ registry from ``$BRAINIAC_HOME`` itself and stays at the same file-reads
197
+ budget (no ``maintain*``/``notify-sent`` file touched), so this needs no
198
+ vault list handed in and covers every registered vault in one line."""
199
+ from . import doctor as _doctor
200
+ from .doctor_wiring import check_vault_wiring
201
+
202
+ warn = sum(1 for row in check_vault_wiring() if row["status"] == _doctor.WARN)
203
+ if not warn:
204
+ return []
205
+ return [_alert("vault-wiring", f"vault wiring: {warn} warn — run `brain doctor`")]
206
+
207
+
184
208
  # ---------------------------------------------------------------------------
185
209
  # Per-vault sources — on the shared mount, so BOTH roles read them
186
210
  # ---------------------------------------------------------------------------
@@ -201,9 +225,45 @@ def vault_alerts(
201
225
 
202
226
  out += exceptions_alerts(vault, today, name, role=role)
203
227
  out += degradation_alerts(vault, today, name)
228
+ if role != "vm":
229
+ # Host-only: the capture inbox lives on the host's filesystem
230
+ # (closed-stacks), and the fix the alert names (`brain sync`,
231
+ # `brain write`) is a host action anyway. The VM leg does not read
232
+ # this, and a VM zero would be a fabricated one.
233
+ out += stuck_draft_alerts(vault, name)
204
234
  return out
205
235
 
206
236
 
237
+ def stuck_draft_alerts(vault: Path, name: str) -> list[dict[str, str]]:
238
+ """Capture drafts the drain keeps refusing (UPD-01). Pure mtime reads."""
239
+ import time
240
+
241
+ cutoff = time.time() - STUCK_DRAFT_HOURS * 3600
242
+ stuck: list[str] = []
243
+ for d in (_config.capture_inbox_dir(vault), vault / ".brain" / "drafts"):
244
+ try:
245
+ entries = sorted(d.glob("*.md"))
246
+ except OSError:
247
+ continue
248
+ for f in entries:
249
+ try:
250
+ if f.stat().st_mtime < cutoff:
251
+ # Same posture as _FINDING_KEY_RE: a draft filename is
252
+ # untrusted-leg-authored and this line is rendered into
253
+ # the host SessionStart context — never verbatim.
254
+ stuck.append(re.sub(r"[^A-Za-z0-9._-]", "_", f.name)[:64])
255
+ except OSError:
256
+ continue
257
+ if not stuck:
258
+ return []
259
+ shown = ", ".join(stuck[:3]) + ("…" if len(stuck) > 3 else "")
260
+ return [_alert(
261
+ "stuck-drafts",
262
+ f"{len(stuck)} capture draft(s) stuck >{STUCK_DRAFT_HOURS}h ({shown}) — "
263
+ "run `brain sync` and read the skip reasons; an `update-needs-owner` "
264
+ "refusal applies with `brain write`", name)]
265
+
266
+
207
267
  def exceptions_alerts(
208
268
  vault: Path, today: datetime.date, name: str, *, role: str,
209
269
  ) -> list[dict[str, str]]:
@@ -384,6 +444,7 @@ def collect(
384
444
  alerts += update_alerts(home, today)
385
445
  alerts += staging_alerts(home)
386
446
  alerts += synthesis_alerts(home, today)
447
+ alerts += wiring_alerts()
387
448
  vaults = host_vaults(home)
388
449
  if not vaults and vault:
389
450
  vaults = [Path(vault)]
@@ -10,6 +10,8 @@ from __future__ import annotations
10
10
 
11
11
  import re
12
12
  from pathlib import Path
13
+
14
+ from .cowork_staging import staging_root
13
15
  from typing import Any
14
16
 
15
17
  #: What an undeclared key renders as when the registry cannot be loaded AT
@@ -69,16 +71,44 @@ def _untriaged(key: str) -> bool:
69
71
  return True
70
72
 
71
73
 
72
- def _staged_versions(vault: Path) -> set[str]:
74
+ def _root_for(entry: dict, raw: str) -> Path:
75
+ """The `.brain` staging root this registry row actually stages into.
76
+
77
+ RELOCATION-AWARE since 2026-08-31. This read derived the root as
78
+ `<vault>/.brain`, which is right only while the vault sits inside the
79
+ workspace. On a RELOCATED vault the engine is staged at
80
+ `<workspace>/vault/.brain`, so the old derivation read the vault-side
81
+ LEFTOVER --- frozen at whatever version was current when the vault moved
82
+ --- and reported the workspace permanently stale. `brain update` could
83
+ never clear it: it writes the workspace side.
84
+
85
+ Measured 2026-08-31, minutes after the 0.20.33 restage: the staged engine
86
+ on disk and `brain doctor` both read 0.20.33 while this digest cried
87
+ 0.20.32 for both live vaults. Sixth relocation-unaware surface, and the
88
+ first on the digest every session is told to read FIRST. The three doctor
89
+ rows before it went SILENT, which is the milder failure; a permanent false
90
+ alarm teaches sessions to ignore the channel.
91
+
92
+ Never raises: `alerts` reporting nothing is the one outcome this module may
93
+ never produce, so an unresolvable path falls back to the co-located root.
94
+ """
95
+ try:
96
+ return staging_root(raw, entry.get("workspace_path") or None)
97
+ except (OSError, ValueError):
98
+ return Path(raw) / ".brain"
99
+
100
+
101
+ def _staged_versions(root: Path) -> set[str]:
73
102
  """Every version stamp a staged Cowork workspace carries, as file reads.
74
103
 
104
+ Takes the `.brain` STAGING ROOT, not the vault --- see :func:`_root_for`.
75
105
  Two stamp kinds exist: the staged engine's ``_version.py`` and the
76
106
  ``brain-linux-*.version`` sidecars beside the VM ELFs. A missing stamp
77
107
  contributes nothing — absence means "never staged", which is not
78
108
  staleness."""
79
109
  found: set[str] = set()
80
110
  try:
81
- text = (vault / ".brain" / "engine" / "brain" / "_version.py").read_text(
111
+ text = (root / "engine" / "brain" / "_version.py").read_text(
82
112
  encoding="utf-8")
83
113
  m = re.search(r'__version__\s*=\s*"([^"]+)"', text)
84
114
  if m:
@@ -86,7 +116,7 @@ def _staged_versions(vault: Path) -> set[str]:
86
116
  except OSError:
87
117
  pass
88
118
  try:
89
- for stamp in sorted((vault / ".brain" / "bin").glob("brain-linux-*.version")):
119
+ for stamp in sorted((root / "bin").glob("brain-linux-*.version")):
90
120
  v = stamp.read_text(encoding="utf-8").strip()
91
121
  if v:
92
122
  found.add(v)
@@ -128,7 +158,7 @@ def staging_alerts(home: Path) -> list[dict[str, str]]:
128
158
  if not raw or raw in seen:
129
159
  continue
130
160
  seen.add(raw)
131
- stale = sorted(_staged_versions(Path(raw)) - {running})
161
+ stale = sorted(_staged_versions(_root_for(entry, raw)) - {running})
132
162
  if stale:
133
163
  out.append(_alert(
134
164
  "staging:stale",
@@ -118,10 +118,15 @@ def _json_default(o: Any) -> Any:
118
118
 
119
119
  def _emit(obj: Any, as_json: bool, human: str | None = None) -> None:
120
120
  if as_json:
121
- json.dump(obj, sys.stdout, ensure_ascii=False, indent=2, default=_json_default)
122
- sys.stdout.write("\n")
121
+ text = json.dumps(obj, ensure_ascii=False, indent=2, default=_json_default) + "\n"
123
122
  else:
124
- sys.stdout.write((human if human is not None else str(obj)) + "\n")
123
+ text = (human if human is not None else str(obj)) + "\n"
124
+ # Gated content is HELD until its SEC-06 record is written; everything else
125
+ # streams as before. See brain.cli_read_record for why.
126
+ from . import cli_read_record as _crr
127
+
128
+ if not _crr.hold(text):
129
+ sys.stdout.write(text)
125
130
 
126
131
 
127
132
  def _excluded_note(res: dict[str, Any]) -> str:
@@ -440,29 +445,24 @@ def _main(argv: list[str] | None = None) -> int:
440
445
  # a stale count would be attributed to this command. One-shot in the CLI,
441
446
  # load-bearing in the long-lived MCP server.
442
447
  _egress_reset.take_tally()
448
+ from . import cli_read_record as _crr
449
+
450
+ _crr.begin()
443
451
  started = _time.perf_counter()
444
452
  try:
445
- return group.run(args, ctx)
446
- finally:
447
- # SEC-06: one access record per invocation, from the counts the egress
448
- # chokepoint accumulated. Hooked HERE rather than in each handler so a
449
- # content verb added later is logged the day it routes through the gate
450
- # — the same argument that put the gate itself at one chokepoint.
451
- # In `finally` because a command that raised still read what it read.
452
- try:
453
- from . import egress as _egress, read_log as _read_log
454
-
455
- _read_log.record_from_tally(
456
- vault=getattr(core, "vault", None),
457
- role=role,
458
- cmd=args.cmd,
459
- max_tier=getattr(args, "max_tier", None),
460
- tally=_egress.take_tally(),
461
- latency_ms=(_time.perf_counter() - started) * 1000.0,
462
- )
463
- except Exception: # never let logging fail a read
464
- pass
465
-
453
+ rc = group.run(args, ctx)
454
+ except BaseException:
455
+ # A command that raised still read what it read, so the record is still
456
+ # owed. Its held output is dropped: the caller above renders the error.
457
+ _crr.settle_for(core, role, args, started)
458
+ raise
459
+ withhold, held = _crr.settle_for(core, role, args, started)
460
+ if withhold:
461
+ print(_crr.refusal(args.cmd), file=sys.stderr)
462
+ return _crr.EXIT_RECORD_FAILED
463
+ for chunk in held:
464
+ sys.stdout.write(chunk)
465
+ return rc
466
466
 
467
467
 
468
468
  # The --help epilog text lives in cli_help.py and the read-surface render
@@ -8,7 +8,7 @@ from .. import classification as cls
8
8
  def _add_draft_capture(sub) -> None:
9
9
  sp = sub.add_parser(
10
10
  "draft-capture",
11
- help="VM-side capture: stage a candidate note as a plain DRAFT (no sign, no index, no WAL) for the host to drain later",
11
+ help="VM-side capture: stage a candidate note as a plain DRAFT (no sign, no index, no WAL) for the host to drain later. To UPDATE an existing note in place (UPD-01), give the draft the SAME id plus two frontmatter keys: `updates: <id>` and `base_sha256: <the sha256 `brain get` returned for the version you read>` — accepted only when the live note is untrusted-lane-authored and under brain/; a host-authored target needs the owner (`brain write`), and a base mismatch refuses as stale-base",
12
12
  )
13
13
  sp.add_argument(
14
14
  "--id", default=None, help="note id (default: from frontmatter or content hash)"