brainlayer 1.5.63__tar.gz → 1.5.65__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 (263) hide show
  1. {brainlayer-1.5.63 → brainlayer-1.5.65}/PKG-INFO +1 -1
  2. {brainlayer-1.5.63 → brainlayer-1.5.65}/pyproject.toml +1 -1
  3. {brainlayer-1.5.63 → brainlayer-1.5.65}/server.json +2 -2
  4. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/__init__.py +1 -1
  5. brainlayer-1.5.65/src/brainlayer/_build.py +1 -0
  6. brainlayer-1.5.65/src/brainlayer/brainbar_activation.py +202 -0
  7. brainlayer-1.5.65/src/brainlayer/brainbar_auto_activation.py +210 -0
  8. brainlayer-1.5.65/src/brainlayer/brainbar_identity.py +91 -0
  9. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/cli/__init__.py +48 -0
  10. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/supervisor_jobs.py +29 -0
  11. brainlayer-1.5.65/src/brainlayer/t3_linkage_alarm.py +110 -0
  12. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/t3_provenance.py +4 -6
  13. brainlayer-1.5.63/src/brainlayer/_build.py +0 -1
  14. {brainlayer-1.5.63 → brainlayer-1.5.65}/.gitignore +0 -0
  15. {brainlayer-1.5.63 → brainlayer-1.5.65}/LICENSE +0 -0
  16. {brainlayer-1.5.63 → brainlayer-1.5.65}/README.md +0 -0
  17. {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/hotlane_brainbar_daemon.py +0 -0
  18. {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/backup-daily.sh +0 -0
  19. {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/brainlayer-env-run.sh +0 -0
  20. {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/brainlayer.env.example +0 -0
  21. {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/com.brainlayer.backup-daily.plist +0 -0
  22. {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/com.brainlayer.daemon.plist +0 -0
  23. {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/com.brainlayer.decay.plist +0 -0
  24. {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/com.brainlayer.drain.plist +0 -0
  25. {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/com.brainlayer.health-check.plist +0 -0
  26. {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/com.brainlayer.hotlane-brainbar.plist +0 -0
  27. {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/com.brainlayer.index.plist +0 -0
  28. {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/com.brainlayer.jsonl-backup.plist +0 -0
  29. {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/com.brainlayer.log-cap.plist +0 -0
  30. {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/com.brainlayer.maintenance-nightly.plist +0 -0
  31. {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/com.brainlayer.maintenance-weekly.plist +0 -0
  32. {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/com.brainlayer.maintenance.plist +0 -0
  33. {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/com.brainlayer.observability.plist +0 -0
  34. {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/com.brainlayer.p0-counter.plist +0 -0
  35. {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/com.brainlayer.repair-fts.plist +0 -0
  36. {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/com.brainlayer.t3-ingest.plist +0 -0
  37. {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/com.brainlayer.throughput-watchdog.plist +0 -0
  38. {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/com.brainlayer.tier0-watchdog.plist +0 -0
  39. {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/com.brainlayer.wal-checkpoint.plist +0 -0
  40. {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/com.brainlayer.watch.plist +0 -0
  41. {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/com.etanhey.brainlayer-fleet-watchdog.plist +0 -0
  42. {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/fleet-watchdog.sh +0 -0
  43. {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/install.sh +0 -0
  44. {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/jsonl-backup.sh +0 -0
  45. {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/throughput-watchdog.py +0 -0
  46. {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/release-verify-signatures.sh +0 -0
  47. {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/tier0-watchdog.sh +0 -0
  48. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/__main__.py +0 -0
  49. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/_helpers.py +0 -0
  50. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/agent_profiles.py +0 -0
  51. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/agent_provenance.py +0 -0
  52. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/alarm.py +0 -0
  53. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/archive_collapse.py +0 -0
  54. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/archive_lifecycle.py +0 -0
  55. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/backup_daily.py +0 -0
  56. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/backup_retention_invariant.py +0 -0
  57. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/badge_state.py +0 -0
  58. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/bitemporal.py +0 -0
  59. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/brainbar_hybrid_helper.py +0 -0
  60. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/cachedelete_purge.swift +0 -0
  61. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/calibrate.py +0 -0
  62. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/chunk_origin.py +0 -0
  63. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/chunk_origin_backfill.py +0 -0
  64. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/chunk_origin_wipe.py +0 -0
  65. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/chunk_write.py +0 -0
  66. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/classify.py +0 -0
  67. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/claude_paths.py +0 -0
  68. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/cli/wizard.py +0 -0
  69. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/cli_jobs.py +0 -0
  70. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/cli_new.py +0 -0
  71. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/cloud_backfill.py +0 -0
  72. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/clustering.py +0 -0
  73. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/component_jobs.py +0 -0
  74. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/config.py +0 -0
  75. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/consolidation_plan.py +0 -0
  76. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/content_class.py +0 -0
  77. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/correction_judge.py +0 -0
  78. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/data/agents_registry.yaml +0 -0
  79. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/data/sandbox_seeds/skill-eval-baseline.json +0 -0
  80. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/db_shrink.py +0 -0
  81. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/decay.py +0 -0
  82. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/decay_backfill.py +0 -0
  83. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/decay_job.py +0 -0
  84. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/dedupe.py +0 -0
  85. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/dedupe_merge.py +0 -0
  86. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/deploy_drift.py +0 -0
  87. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/doctor.py +0 -0
  88. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/drain.py +0 -0
  89. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/drain_liveness.py +0 -0
  90. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/drive_credentials.py +0 -0
  91. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/embeddings.py +0 -0
  92. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/engine.py +0 -0
  93. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/enrichment_replay.py +0 -0
  94. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/eval/__init__.py +0 -0
  95. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/eval/abcde_enrich_runner.py +0 -0
  96. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/eval/abcde_variants.py +0 -0
  97. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/eval/abcde_variants.yaml +0 -0
  98. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/eval/benchmark.py +0 -0
  99. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/eval/enrichment_gold.py +0 -0
  100. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/eval/enrichment_graders.py +0 -0
  101. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/eval/enrichment_judge.py +0 -0
  102. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/eval/enrichment_llm_judge.py +0 -0
  103. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/eval/enrichment_quality_benchmark.py +0 -0
  104. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/eval/experiment_store.py +0 -0
  105. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/eval/fixtures/README.md +0 -0
  106. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/eval/labeling/enrichment-labeler.html +0 -0
  107. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/eval/phoenix_gate/__init__.py +0 -0
  108. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/eval/phoenix_gate/__main__.py +0 -0
  109. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/eval/phoenix_gate/baseline_store.py +0 -0
  110. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/eval/phoenix_gate/cli.py +0 -0
  111. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/eval/phoenix_gate/models.py +0 -0
  112. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/eval/phoenix_gate/phoenix_client.py +0 -0
  113. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/eval/phoenix_gate/regression_gate.py +0 -0
  114. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/eval/phoenix_gate/triggers.py +0 -0
  115. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/eval/pilot_artifact_report.py +0 -0
  116. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/fallback_replay.py +0 -0
  117. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/git_learning.py +0 -0
  118. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/health_check.py +0 -0
  119. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/hook_python.py +0 -0
  120. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/hooks/__init__.py +0 -0
  121. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/hooks/indexer.py +0 -0
  122. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/import_sweep.py +0 -0
  123. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/index_budget.py +0 -0
  124. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/index_completeness.py +0 -0
  125. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/index_new.py +0 -0
  126. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/index_watchdog.py +0 -0
  127. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/ingest/__init__.py +0 -0
  128. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/ingest/codex.py +0 -0
  129. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/ingest/t3.py +0 -0
  130. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/ingest_denylist.py +0 -0
  131. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/ingest_guard.py +0 -0
  132. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/isolation_proof.py +0 -0
  133. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/job_alerts.py +0 -0
  134. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/job_lifecycle_health.py +0 -0
  135. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/jobs.py +0 -0
  136. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/jsonl_backup.py +0 -0
  137. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/kg/__init__.py +0 -0
  138. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/kg_cleanup.py +0 -0
  139. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/kg_judge.py +0 -0
  140. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/kg_promotion.py +0 -0
  141. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/kg_repo.py +0 -0
  142. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/kg_review_session.py +0 -0
  143. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/kg_session_finish.py +0 -0
  144. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/kg_session_harvest.py +0 -0
  145. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/launchd_consolidation.py +0 -0
  146. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/launchd_install.py +0 -0
  147. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/launchd_primitive.py +0 -0
  148. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/launchd_revert.py +0 -0
  149. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/lexical_defense.py +0 -0
  150. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/lexical_defense_dictionary.json +0 -0
  151. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/log_cap.py +0 -0
  152. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/maintenance.py +0 -0
  153. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/maintenance_calendar.py +0 -0
  154. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/maintenance_control.py +0 -0
  155. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/mcp/__init__.py +0 -0
  156. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/mcp/_format.py +0 -0
  157. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/mcp/_shared.py +0 -0
  158. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/mcp/enrich_handler.py +0 -0
  159. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/mcp/entity_handler.py +0 -0
  160. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/mcp/palette.py +0 -0
  161. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/mcp/search_handler.py +0 -0
  162. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/mcp/store_handler.py +0 -0
  163. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/mcp/tags_handler.py +0 -0
  164. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/mcp_socket_config.py +0 -0
  165. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/mcp_stdio_bridge.py +0 -0
  166. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/memory_types.py +0 -0
  167. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/migrate.py +0 -0
  168. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/migration_custody.py +0 -0
  169. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/migration_journal.py +0 -0
  170. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/migration_runtime.py +0 -0
  171. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/notification_policy.py +0 -0
  172. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/observability_backup.py +0 -0
  173. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/observability_surface.py +0 -0
  174. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/observability_transport.py +0 -0
  175. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/p0_longitudinal_count.py +0 -0
  176. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/parent_death.py +0 -0
  177. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/paths.py +0 -0
  178. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pause.py +0 -0
  179. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/phonetic.py +0 -0
  180. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/__init__.py +0 -0
  181. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/agent_enrichment.py +0 -0
  182. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/analyze_communication.py +0 -0
  183. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/batch_extraction.py +0 -0
  184. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/brain_graph.py +0 -0
  185. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/chat_tags.py +0 -0
  186. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/chunk.py +0 -0
  187. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/classify.py +0 -0
  188. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/cloud_scrub.py +0 -0
  189. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/cluster_sampling.py +0 -0
  190. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/code_intelligence.py +0 -0
  191. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/correction_detection.py +0 -0
  192. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/correction_mining.py +0 -0
  193. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/digest.py +0 -0
  194. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/enrichment_prompts.py +0 -0
  195. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/enrichment_results.py +0 -0
  196. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/enrichment_tiers.py +0 -0
  197. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/entity_extraction.py +0 -0
  198. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/entity_resolution.py +0 -0
  199. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/extract.py +0 -0
  200. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/extract_claude_desktop.py +0 -0
  201. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/extract_corrections.py +0 -0
  202. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/extract_markdown.py +0 -0
  203. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/extract_whatsapp.py +0 -0
  204. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/frustration_mining.py +0 -0
  205. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/git_overlay.py +0 -0
  206. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/kg_extraction.py +0 -0
  207. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/kg_extraction_groq.py +0 -0
  208. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/longitudinal_analyzer.py +0 -0
  209. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/obsidian_export.py +0 -0
  210. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/operation_grouping.py +0 -0
  211. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/plan_linking.py +0 -0
  212. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/rate_limiter.py +0 -0
  213. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/relation_backfill.py +0 -0
  214. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/relation_verification.py +0 -0
  215. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/sanitize.py +0 -0
  216. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/secret_scrub.py +0 -0
  217. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/semantic_style.py +0 -0
  218. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/sentiment.py +0 -0
  219. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/session_history.py +0 -0
  220. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/style_embed.py +0 -0
  221. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/style_index.py +0 -0
  222. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/tag_entity_promotion.py +0 -0
  223. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/temporal_chains.py +0 -0
  224. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/time_batcher.py +0 -0
  225. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/unified_timeline.py +0 -0
  226. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/write_queue.py +0 -0
  227. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/provenance.py +0 -0
  228. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/provenance_autosupersede.py +0 -0
  229. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/provenance_integration.py +0 -0
  230. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/queue_io.py +0 -0
  231. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/queue_merge.py +0 -0
  232. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/reembed_backfill.py +0 -0
  233. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/retired_services.py +0 -0
  234. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/runtime_store.py +0 -0
  235. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/sandbox_db.py +0 -0
  236. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/scoping.py +0 -0
  237. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/scrub_at_rest.py +0 -0
  238. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/search_fanout.py +0 -0
  239. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/search_profile.py +0 -0
  240. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/search_repo.py +0 -0
  241. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/service_supervisor.py +0 -0
  242. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/session_repo.py +0 -0
  243. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/setup.py +0 -0
  244. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/socket_hygiene.py +0 -0
  245. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/source_class_migration.py +0 -0
  246. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/spotlight.py +0 -0
  247. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/storage.py +0 -0
  248. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/store.py +0 -0
  249. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/supervisor_adapters.py +0 -0
  250. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/supervisor_child.py +0 -0
  251. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/supervisor_guard.py +0 -0
  252. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/system_prompt_guard.py +0 -0
  253. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/t3_sources.py +0 -0
  254. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/tag_normalization.py +0 -0
  255. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/taxonomy.json +0 -0
  256. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/telemetry.py +0 -0
  257. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/timestamp_iso.py +0 -0
  258. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/types.py +0 -0
  259. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/vector_store.py +0 -0
  260. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/wal_checkpoint.py +0 -0
  261. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/watcher.py +0 -0
  262. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/watcher_bridge.py +0 -0
  263. {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/writer_telemetry.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: brainlayer
3
- Version: 1.5.63
3
+ Version: 1.5.65
4
4
  Summary: Persistent local memory for AI agents — 16 BrainBar MCP tools, semantic search, knowledge graph, SQLite
5
5
  Project-URL: Homepage, https://brainlayer.etanheyman.com
6
6
  Project-URL: Repository, https://github.com/EtanHey/brainlayer
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "brainlayer"
3
- version = "1.5.63"
3
+ version = "1.5.65"
4
4
  description = "Persistent local memory for AI agents — 16 BrainBar MCP tools, semantic search, knowledge graph, SQLite"
5
5
  license = {text = "Apache-2.0"}
6
6
  readme = "README.md"
@@ -3,7 +3,7 @@
3
3
  "name": "io.github.etanhey/brainlayer",
4
4
  "title": "BrainLayer",
5
5
  "description": "Persistent local memory for AI agents \u2014 semantic search, knowledge graph, and 16 BrainBar MCP tools with ToolAnnotations over SQLite.",
6
- "version": "1.5.63",
6
+ "version": "1.5.65",
7
7
  "websiteUrl": "https://brainlayer.etanheyman.com",
8
8
  "repository": {
9
9
  "url": "https://github.com/EtanHey/brainlayer",
@@ -14,7 +14,7 @@
14
14
  "registryType": "pypi",
15
15
  "registryBaseUrl": "https://pypi.org",
16
16
  "identifier": "brainlayer",
17
- "version": "1.5.63",
17
+ "version": "1.5.65",
18
18
  "runtimeHint": "pipx",
19
19
  "transport": {
20
20
  "type": "stdio"
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- __version__ = "1.5.63"
5
+ __version__ = "1.5.65"
6
6
  # Git sha the distributed package was built from; stamped by the release build, None in a source tree.
7
7
  # scripts/sprint_gate.py reads it in "keg" mode to prove the served package is the code under test.
8
8
  # No runtime `str | None` annotation: launchd/install.sh imports this under /usr/bin/python3 (3.9).
@@ -0,0 +1 @@
1
+ BUILD_SHA = "c8981703b52a14f33ca46b861c245478d628e72c"
@@ -0,0 +1,202 @@
1
+ """Explicit, single-attempt cask activation after Homebrew returns."""
2
+
3
+ import fcntl
4
+ import math
5
+ import os
6
+ import plistlib
7
+ import re
8
+ import subprocess
9
+ import sys
10
+ import time
11
+ from pathlib import Path
12
+ from xml.parsers.expat import ExpatError
13
+
14
+ from .brainbar_identity import JOBS, running_bundle_matches
15
+ from .setup import get_current_mcp_bridge_bin, verify_mcp_transport
16
+
17
+
18
+ def _activate(agents: Path, *, timeout_seconds: float = 90.0, verify: bool = True, automatic: bool = False) -> dict:
19
+ """Replace only the cask's two labels; unload attempts on failure, never relaunch."""
20
+ attempted = []
21
+ mutated = False
22
+ stage = "preflight"
23
+ report = {"ok": False, "service_ready": False, "rollback_attempted": False, "cleanup_errors": []}
24
+
25
+ def launchctl(*args, timeout=10.0):
26
+ return subprocess.run(["/bin/launchctl", *args], capture_output=True, text=True, timeout=timeout, check=False)
27
+
28
+ try:
29
+ deadline = time.monotonic() + timeout_seconds
30
+ if sys.platform != "darwin":
31
+ raise ValueError("activation requires macOS")
32
+ if not math.isfinite(timeout_seconds) or timeout_seconds <= 0:
33
+ raise ValueError("activation timeout must be finite and positive")
34
+ socket = Path("/tmp/brainbar.sock")
35
+ for key in ("BRAINLAYER_MCP_SOCKET", "MCPLAYER_BRAINLAYER_FRONT"):
36
+ if os.environ.get(key) and os.environ[key] != str(socket):
37
+ raise ValueError(f"cask activation requires {key} to be unset or {socket}")
38
+ bridge = get_current_mcp_bridge_bin()
39
+ if verify and not bridge:
40
+ raise ValueError("installed brainlayer-mcp-stdio-bridge is missing")
41
+ bundles = set()
42
+ for label, binary in JOBS:
43
+ with (agents / f"{label}.plist").open("rb") as stream:
44
+ plist = plistlib.load(stream)
45
+ if not isinstance(plist, dict):
46
+ raise ValueError(f"invalid installed cask plist: {label}")
47
+ arguments = plist.get("ProgramArguments", [])
48
+ executable = (
49
+ Path(arguments[0])
50
+ if isinstance(arguments, list) and len(arguments) == 1 and isinstance(arguments[0], str)
51
+ else Path()
52
+ )
53
+ if (
54
+ plist.get("Label") != label
55
+ or not executable.is_absolute()
56
+ or executable.name != binary
57
+ or executable.parent.name != "MacOS"
58
+ or executable.parent.parent.name != "Contents"
59
+ or executable.parents[2].name != "BrainBar.app"
60
+ or not os.access(executable, os.X_OK)
61
+ ):
62
+ raise ValueError(f"invalid installed cask plist: {label}")
63
+ bundles.add(executable.parents[2])
64
+ if len(bundles) != 1:
65
+ raise ValueError("BrainBar labels must use the same installed app")
66
+ domain = f"gui/{os.getuid()}"
67
+
68
+ def remaining():
69
+ seconds = deadline - time.monotonic()
70
+ if seconds <= 0:
71
+ raise TimeoutError("activation deadline exceeded")
72
+ return seconds
73
+
74
+ stage = "disabled"
75
+ disabled = launchctl("print-disabled", domain, timeout=min(10, remaining()))
76
+ if disabled.returncode:
77
+ raise RuntimeError("could not inspect disabled launchd labels")
78
+ for label, _ in JOBS:
79
+ if re.search(r'"' + re.escape(label) + r'"\s*=>\s*true', disabled.stdout):
80
+ raise RuntimeError(f"launchd label explicitly disabled: {label}")
81
+ stage = "running_probe"
82
+ if running_bundle_matches(next(iter(bundles)), timeout=min(10, remaining())):
83
+ # A failed probe of this already-running bundle never authorizes stopping it.
84
+ tool_count = verify_mcp_transport(bridge_command=bridge, timeout_seconds=remaining()) if verify else None
85
+ return {
86
+ **report,
87
+ "ok": True,
88
+ "service_ready": verify,
89
+ "stage": "already_running",
90
+ "reason": "",
91
+ "tool_count": tool_count,
92
+ }
93
+
94
+ def brew_guard():
95
+ if automatic:
96
+ from .brainbar_auto_activation import brew_active
97
+
98
+ if brew_active():
99
+ raise RuntimeError("active Homebrew run; automatic activation deferred")
100
+
101
+ brew_guard()
102
+ for label, _ in JOBS:
103
+ brew_guard()
104
+ stage = f"launch:{label}"
105
+ target = f"{domain}/{label}"
106
+ current = launchctl("print", target, timeout=min(10, remaining()))
107
+ if current.returncode not in (0, 113):
108
+ raise RuntimeError(f"could not inspect {label}: {current.returncode}")
109
+ if current.returncode == 0:
110
+ mutated = True
111
+ if launchctl("bootout", target, timeout=min(10, remaining())).returncode != 0:
112
+ raise RuntimeError(f"could not unload {label}")
113
+ while True:
114
+ state = launchctl("print", target, timeout=min(10, remaining()))
115
+ if state.returncode == 113:
116
+ break
117
+ if state.returncode != 0:
118
+ raise RuntimeError(f"could not observe teardown: {label}")
119
+ time.sleep(min(0.1, remaining()))
120
+ brew_guard()
121
+ attempted.append(target)
122
+ mutated = True
123
+ result = launchctl("bootstrap", domain, str(agents / f"{label}.plist"), timeout=min(10, remaining()))
124
+ if result.returncode:
125
+ if launchctl("print", target, timeout=min(10, remaining())).returncode != 0:
126
+ raise RuntimeError(f"could not bootstrap {label}: {result.returncode}")
127
+ stage = "socket"
128
+ if verify:
129
+ while not socket.is_socket():
130
+ time.sleep(min(0.1, remaining()))
131
+ stage = "mcp"
132
+ tool_count = verify_mcp_transport(bridge_command=bridge, timeout_seconds=remaining()) if verify else None
133
+ if verify:
134
+ stage = "running_identity"
135
+ if not running_bundle_matches(next(iter(bundles)), timeout=min(10, remaining())):
136
+ raise RuntimeError("MCP responded but running executables do not match the installed bundle")
137
+ return {
138
+ **report,
139
+ "ok": True,
140
+ "service_ready": verify,
141
+ "stage": "verified" if verify else "started_unverified",
142
+ "reason": "",
143
+ "tool_count": tool_count,
144
+ }
145
+ except (
146
+ OSError,
147
+ ValueError,
148
+ RuntimeError,
149
+ subprocess.SubprocessError,
150
+ plistlib.InvalidFileException,
151
+ ExpatError,
152
+ ) as exc:
153
+ if automatic and not mutated and "active Homebrew run" in str(exc):
154
+ return {**report, "stage": "deferred", "reason": str(exc)}
155
+ # KeepAlive jobs must not keep retrying after failed activation. No automatic
156
+ # old-bundle rollback, config rewrite, or second bootstrap is attempted.
157
+ for target in reversed(attempted):
158
+ try:
159
+ if launchctl("bootout", target).returncode != 0:
160
+ report["cleanup_errors"].append(target)
161
+ except (OSError, subprocess.SubprocessError):
162
+ report["cleanup_errors"].append(target)
163
+ return {**report, "stage": stage, "reason": str(exc)}
164
+
165
+
166
+ def activate(agents: Path, *, timeout_seconds: float = 90.0, verify: bool = True, automatic: bool = False) -> dict:
167
+ try:
168
+ if not math.isfinite(timeout_seconds) or timeout_seconds <= 0:
169
+ raise ValueError("activation timeout must be finite and positive")
170
+ deadline = time.monotonic() + timeout_seconds
171
+ if os.environ.get("BRAINLAYER_FORBID_BRAINBAR_SOCKET") and agents == Path.home() / "Library/LaunchAgents":
172
+ raise ValueError("test guard forbids installed BrainBar activation")
173
+ # Shared by manual commands and the supervisor child; no concurrent restart.
174
+ with (agents / ".brainbar-activation.lock").open("a") as lock:
175
+ while True:
176
+ seconds = deadline - time.monotonic()
177
+ if seconds <= 0:
178
+ raise TimeoutError("activation deadline exceeded waiting for activation lock")
179
+ try:
180
+ fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
181
+ break
182
+ except BlockingIOError:
183
+ if automatic:
184
+ return {
185
+ "ok": False,
186
+ "stage": "deferred",
187
+ "reason": "activation already in progress",
188
+ "service_ready": False,
189
+ "rollback_attempted": False,
190
+ "cleanup_errors": [],
191
+ }
192
+ time.sleep(min(0.1, seconds))
193
+ return _activate(agents, timeout_seconds=deadline - time.monotonic(), verify=verify, automatic=automatic)
194
+ except (OSError, ValueError) as exc:
195
+ return {
196
+ "ok": False,
197
+ "stage": "activation_lock",
198
+ "reason": str(exc),
199
+ "service_ready": False,
200
+ "rollback_attempted": False,
201
+ "cleanup_errors": [],
202
+ }
@@ -0,0 +1,210 @@
1
+ """One bounded supervisor pass; durable per-build suppression survives daemon restarts."""
2
+
3
+ import argparse
4
+ import fcntl
5
+ import json
6
+ import os
7
+ import plistlib
8
+ import subprocess
9
+ import sys
10
+ import time
11
+ from pathlib import Path
12
+
13
+ from .brainbar_identity import JOBS, app_identity, loaded, running_bundle_matches
14
+ from .deploy_drift import _atomic_write_json
15
+ from .job_alerts import active_alerts, report
16
+ from .pipeline.secret_scrub import scrub_secrets
17
+
18
+
19
+ class ProcessScanUnavailable(Exception):
20
+ """Detection is unknown; only the safe exception class name may escape."""
21
+
22
+
23
+ def process_table():
24
+ result = subprocess.run(
25
+ ["/bin/ps", "-axo", "pid=,ppid=,comm=,args="], capture_output=True, text=True, check=True, timeout=5
26
+ )
27
+ return result.stdout
28
+
29
+
30
+ def brew_processes():
31
+ # Include brew's Ruby entrypoint and Homebrew-owned child executables, even
32
+ # after reparenting. Unknown/unreadable process tables defer activation.
33
+ rows = process_table().splitlines()
34
+ matches = []
35
+ for row in rows:
36
+ fields = row.split(None, 3)
37
+ if len(fields) != 4 or not fields[0].isascii() or not fields[0].isdecimal():
38
+ raise ValueError("unreadable process table")
39
+ tokens = fields[3].split()
40
+ if Path(fields[2]).name == "brew" or any(
41
+ Path(token).name in {"brew", "brew.rb"} or "/Homebrew/" in token or "/homebrew/Library/Homebrew/" in token
42
+ for token in tokens
43
+ ):
44
+ # argv is detection-only: Homebrew curl headers can carry tokens.
45
+ executable = scrub_secrets(Path(fields[2]).name).text[:128]
46
+ matches.append(f"{fields[0]} {executable}")
47
+ return matches
48
+
49
+
50
+ def brew_active():
51
+ return bool(brew_processes())
52
+
53
+
54
+ class AutoActivator:
55
+ def __init__(self, agents: Path, state: Path):
56
+ self.agents, self.state = agents, state
57
+
58
+ def tick(self):
59
+ self.state.parent.mkdir(parents=True, exist_ok=True)
60
+ with self.state.with_suffix(".lock").open("a") as lock:
61
+ try:
62
+ fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
63
+ except BlockingIOError:
64
+ return
65
+ failures = self.state.with_suffix(".scan.json")
66
+ try:
67
+ self._tick()
68
+ except ProcessScanUnavailable as exc:
69
+ prior = json.loads(failures.read_text()) if failures.exists() else {}
70
+ count = min(prior.get("failures", 0) + 1, 12)
71
+ _atomic_write_json(failures, {"failures": count})
72
+ if count >= 12:
73
+ try:
74
+ absent = all(loaded(label).returncode == 113 for label, _ in JOBS)
75
+ except (OSError, subprocess.SubprocessError, RuntimeError):
76
+ return # Unknown launchd state is not evidence BrainBar is down.
77
+ if absent:
78
+ report("brainbar-activation", f"BrainBar activation process scan failed for >=12 ticks: {exc}")
79
+
80
+ def _defer_for_brew(self):
81
+ # Separate from the per-build attempt: brew may have removed both plists.
82
+ path = self.state.with_suffix(".brew.json")
83
+ prior = json.loads(path.read_text()) if path.exists() else {}
84
+ try:
85
+ processes = brew_processes()
86
+ except (OSError, subprocess.SubprocessError) as exc:
87
+ raise ProcessScanUnavailable(type(exc).__name__) from None
88
+ # Recover before activation can report a new failure on the same alert key.
89
+ self.state.with_suffix(".scan.json").unlink(missing_ok=True)
90
+ reason = active_alerts().get("brainbar-activation", "")
91
+ migration = self.state.with_suffix(".scan-migration.json")
92
+ migrate_legacy = not migration.exists()
93
+ if migrate_legacy:
94
+ # Consume even without a legacy alert, before later work can raise an OSError.
95
+ _atomic_write_json(migration, {"completed": True})
96
+ if (migrate_legacy and reason.startswith("BrainBar activation monitor refused: [Errno")) or reason.startswith(
97
+ (
98
+ "BrainBar activation process scan failed for >=12 ticks:",
99
+ "BrainBar activation monitor refused: Command '['/bin/ps',",
100
+ )
101
+ ):
102
+ report("brainbar-activation", None)
103
+ absent = bool(processes) and all(loaded(label).returncode == 113 for label, _ in JOBS)
104
+ if not absent:
105
+ if prior:
106
+ path.unlink()
107
+ if prior.get("alerted"):
108
+ report("brainbar-activation-brew", None)
109
+ else:
110
+ now = time.time()
111
+ since = prior.get("since", now)
112
+ if since > now:
113
+ since, prior = now, {}
114
+ alerted = prior.get("alerted", False)
115
+ if now - since > 600 and not alerted:
116
+ names = processes[:5]
117
+ if len(processes) > 5:
118
+ names.append(f"+{len(processes) - 5} more")
119
+ report(
120
+ "brainbar-activation-brew",
121
+ "BrainBar activation deferred by Homebrew for >10 minutes: " + "; ".join(names),
122
+ )
123
+ alerted = True
124
+ _atomic_write_json(path, {"since": since, "alerted": alerted})
125
+ return bool(processes)
126
+
127
+ def _tick(self):
128
+ if self._defer_for_brew():
129
+ return
130
+ apps = set()
131
+ for label, _ in JOBS:
132
+ path = self.agents / f"{label}.plist"
133
+ if not path.is_file():
134
+ return
135
+ with path.open("rb") as stream:
136
+ plist = plistlib.load(stream)
137
+ apps.add(Path(plist["ProgramArguments"][0]).parents[2])
138
+ if len(apps) != 1:
139
+ raise ValueError("cask plists use different bundles")
140
+ app = apps.pop()
141
+ if not (app / "Contents/Info.plist").is_file():
142
+ return
143
+ identity = {"app": str(app), **app_identity(app)}
144
+ prior = json.loads(self.state.read_text()) if self.state.exists() else {}
145
+ if prior.get("identity") == identity and prior.get("stage") == "ready":
146
+ return
147
+ states = [loaded(label).returncode for label, _ in JOBS]
148
+ if all(code == 0 for code in states) and running_bundle_matches(app):
149
+ _atomic_write_json(self.state, {"identity": identity, "stage": "ready"})
150
+ report("brainbar-activation", None)
151
+ return
152
+ if prior.get("identity") == identity:
153
+ if prior.get("stage") == "attempted":
154
+ reason = "BrainBar activation was interrupted; run brainlayer brainbar activate"
155
+ report("brainbar-activation", reason)
156
+ _atomic_write_json(self.state, {"identity": identity, "stage": "failed", "reason": reason})
157
+ return
158
+ if any(code == 0 for code in states) or self._defer_for_brew():
159
+ return
160
+ # Write before launching: crash/restart is never permission to repeat.
161
+ _atomic_write_json(self.state, {"identity": identity, "stage": "attempted"})
162
+ try:
163
+ result = subprocess.run(
164
+ [
165
+ sys.executable,
166
+ "-m",
167
+ "brainlayer",
168
+ "brainbar",
169
+ "activate",
170
+ "--verify",
171
+ "--automatic",
172
+ "--timeout",
173
+ "90",
174
+ ],
175
+ capture_output=True,
176
+ text=True,
177
+ timeout=110,
178
+ check=False,
179
+ )
180
+ receipt = json.loads(result.stdout)
181
+ if receipt.get("stage") == "deferred":
182
+ self.state.unlink()
183
+ return
184
+ ok = result.returncode == 0 and receipt.get("ok") is True
185
+ reason = (
186
+ None
187
+ if ok
188
+ else f"BrainBar activation failed: {receipt.get('reason', 'no successful receipt')}; run brainlayer brainbar activate"
189
+ )
190
+ except (OSError, ValueError, subprocess.SubprocessError) as exc:
191
+ ok, reason = False, f"BrainBar activation failed: {exc}; run brainlayer brainbar activate"
192
+ _atomic_write_json(self.state, {"identity": identity, "stage": "ready" if ok else "failed", "reason": reason})
193
+ report("brainbar-activation", reason)
194
+
195
+
196
+ def main():
197
+ parser = argparse.ArgumentParser(description=__doc__)
198
+ parser.add_argument("--state", type=Path, required=True)
199
+ args = parser.parse_args()
200
+ if os.environ.get("BRAINLAYER_FORBID_BRAINBAR_SOCKET"):
201
+ raise SystemExit("test guard forbids automatic installed BrainBar activation")
202
+ try:
203
+ AutoActivator(Path.home() / "Library/LaunchAgents", args.state).tick()
204
+ except Exception as exc:
205
+ report("brainbar-activation", f"BrainBar activation monitor refused: {exc}")
206
+ raise SystemExit(1) from exc
207
+
208
+
209
+ if __name__ == "__main__":
210
+ main()
@@ -0,0 +1,91 @@
1
+ """Bind loaded cask PIDs to the executable vnodes in the installed bundle."""
2
+
3
+ import os
4
+ import plistlib
5
+ import re
6
+ import subprocess
7
+ import time
8
+ from pathlib import Path
9
+
10
+ JOBS = (("com.brainlayer.brainbar-daemon", "BrainBarDaemon"), ("com.brainlayer.brainbar", "BrainBar"))
11
+
12
+
13
+ def app_identity(app: Path) -> dict:
14
+ with (app / "Contents/Info.plist").open("rb") as stream:
15
+ info = plistlib.load(stream)
16
+ if not isinstance(info, dict):
17
+ raise ValueError("installed BrainBar identity is not a dictionary")
18
+ version = info.get("CFBundleShortVersionString")
19
+ commit = info.get("GitCommit", "")
20
+ if not isinstance(version, str) or not version or not isinstance(commit, str):
21
+ raise ValueError("installed BrainBar identity is missing")
22
+ executable = (app / "Contents/MacOS/BrainBar").stat()
23
+ return {
24
+ "version": version,
25
+ "git_commit": commit,
26
+ "executable_dev": executable.st_dev,
27
+ "executable_ino": executable.st_ino,
28
+ }
29
+
30
+
31
+ def executable_snapshot(info):
32
+ return (info.st_dev, info.st_ino, info.st_size, info.st_mtime_ns)
33
+
34
+
35
+ def run(args, timeout=10):
36
+ return subprocess.run(args, capture_output=True, text=True, check=False, timeout=timeout)
37
+
38
+
39
+ def loaded(label, timeout=10):
40
+ result = run(["/bin/launchctl", "print", f"gui/{os.getuid()}/{label}"], timeout)
41
+ if result.returncode not in (0, 113):
42
+ raise RuntimeError(f"launchctl print failed for {label}: {result.returncode}")
43
+ return result
44
+
45
+
46
+ def running_bundle_matches(app: Path, *, timeout=10) -> bool:
47
+ deadline = time.monotonic() + timeout
48
+
49
+ def remaining():
50
+ value = deadline - time.monotonic()
51
+ if value <= 0:
52
+ raise TimeoutError("running-bundle inspection deadline exceeded")
53
+ return value
54
+
55
+ for label, binary in JOBS:
56
+ state = loaded(label, remaining())
57
+ pid = re.search(r"(?m)^\s*pid\s*=\s*(\d+)\s*$", state.stdout)
58
+ executable = app / "Contents/MacOS" / binary
59
+ program = re.search(r"(?m)^\s*program\s*=\s*(.*?)\s*$", state.stdout)
60
+ if state.returncode or not pid or not program or program[1].strip('"') != str(executable):
61
+ return False
62
+ result = run(["/usr/sbin/lsof", "-a", "-p", pid[1], "-d", "txt", "-F", "Din"], remaining())
63
+ if result.returncode:
64
+ return False
65
+ device = inode = None
66
+ matched = False
67
+ info = executable.stat()
68
+ for line in result.stdout.splitlines():
69
+ if line.startswith("f"):
70
+ device = inode = None
71
+ elif line.startswith("D"):
72
+ device = int(line[1:], 16)
73
+ elif line.startswith("i"):
74
+ inode = int(line[1:])
75
+ elif line == f"n{executable}":
76
+ matched = device == info.st_dev and inode == info.st_ino
77
+ if matched:
78
+ break
79
+ if not matched:
80
+ return False
81
+ # Refuse PID/label replacement while the vnode snapshot was inspected.
82
+ final = loaded(label, remaining())
83
+ final_pid = re.search(r"(?m)^\s*pid\s*=\s*(\d+)\s*$", final.stdout)
84
+ if (
85
+ final.returncode
86
+ or not final_pid
87
+ or final_pid[1] != pid[1]
88
+ or executable_snapshot(executable.stat()) != executable_snapshot(info)
89
+ ):
90
+ return False
91
+ return True
@@ -76,6 +76,54 @@ def backup_auth_command(
76
76
  raise typer.Exit(1)
77
77
 
78
78
 
79
+ brainbar_app = typer.Typer(help="Activate the installed BrainBar cask after Homebrew returns")
80
+ app.add_typer(brainbar_app, name="brainbar")
81
+
82
+
83
+ @brainbar_app.command("activate")
84
+ def brainbar_activate_command(
85
+ verify: bool = typer.Option(True, "--verify/--no-verify", help="Require real MCP verification after activation."),
86
+ automatic: bool = typer.Option(False, "--automatic", hidden=True),
87
+ timeout: float = typer.Option(
88
+ 90.0,
89
+ "--timeout",
90
+ envvar="BRAINLAYER_BRAINBAR_ACTIVATION_TIMEOUT_S",
91
+ help="Startup and MCP deadline in seconds; separate from setup's verification deadline.",
92
+ ),
93
+ ) -> None:
94
+ """Activate the cask's two LaunchAgents once and emit a JSON receipt."""
95
+ from ..brainbar_activation import activate
96
+
97
+ report = activate(
98
+ Path.home() / "Library" / "LaunchAgents", timeout_seconds=timeout, verify=verify, automatic=automatic
99
+ )
100
+ if report["ok"] and verify and not automatic:
101
+ from ..job_alerts import report as report_alert
102
+
103
+ report_alert("brainbar-activation", None)
104
+ typer.echo(json.dumps(report, sort_keys=True))
105
+ if not report["ok"]:
106
+ raise typer.Exit(1)
107
+
108
+
109
+ @brainbar_app.command("verify-running")
110
+ def brainbar_verify_running_command(
111
+ app_path: Path = typer.Option(Path("/Applications/BrainBar.app"), "--app"),
112
+ ) -> None:
113
+ """Read-only check that both running executables belong to this installed build."""
114
+ from ..brainbar_identity import app_identity, running_bundle_matches
115
+
116
+ try:
117
+ identity = app_identity(app_path)
118
+ ok = running_bundle_matches(app_path)
119
+ report = {"ok": ok, **identity, "reason": "" if ok else "running_bundle_mismatch"}
120
+ except (OSError, ValueError, RuntimeError, subprocess.SubprocessError) as exc:
121
+ report = {"ok": False, "reason": str(exc)}
122
+ typer.echo(json.dumps(report, sort_keys=True))
123
+ if not report["ok"]:
124
+ raise typer.Exit(1)
125
+
126
+
79
127
  @jobs_app.command("restart")
80
128
  def jobs_restart_command(
81
129
  all_jobs: bool = typer.Option(False, "--all", help="Select loaded BrainLayer jobs."),
@@ -12,6 +12,7 @@ import json
12
12
  import math
13
13
  import os
14
14
  import stat
15
+ import sys
15
16
  import time
16
17
  from pathlib import Path
17
18
 
@@ -123,6 +124,34 @@ def load_components(path: Path, socket_path: Path) -> dict[str, Component]:
123
124
  if interval and not run_at_load:
124
125
  child.retry_at = time.monotonic() + interval
125
126
  components[ALIASES[label]] = child
127
+ return with_brainbar_monitor(components, socket_path)
128
+
129
+
130
+ def with_brainbar_monitor(components, path):
131
+ if sys.platform == "darwin":
132
+ observability_env = components.get("observability", Component(("unused",))).env
133
+ monitor_env = {
134
+ key: observability_env[key]
135
+ for key in ("BRAINLAYER_DB", "BRAINLAYER_JOB_ALERT_PATH")
136
+ if key in observability_env
137
+ }
138
+ if "BRAINLAYER_FORBID_BRAINBAR_SOCKET" in os.environ:
139
+ monitor_env["BRAINLAYER_FORBID_BRAINBAR_SOCKET"] = os.environ["BRAINLAYER_FORBID_BRAINBAR_SOCKET"]
140
+ components["brainbar-activation"] = Component(
141
+ (
142
+ sys.executable,
143
+ "-m",
144
+ "brainlayer.brainbar_auto_activation",
145
+ "--state",
146
+ str(path.with_name("brainbar-activation.json")),
147
+ ),
148
+ env=monitor_env,
149
+ interval=5,
150
+ run_timeout=180,
151
+ exit_timeout=2,
152
+ stdout=path.with_name("brainbar-activation.log"),
153
+ stderr=path.with_name("brainbar-activation.err.log"),
154
+ )
126
155
  return components
127
156
 
128
157