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.
- {brainlayer-1.5.63 → brainlayer-1.5.65}/PKG-INFO +1 -1
- {brainlayer-1.5.63 → brainlayer-1.5.65}/pyproject.toml +1 -1
- {brainlayer-1.5.63 → brainlayer-1.5.65}/server.json +2 -2
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/__init__.py +1 -1
- brainlayer-1.5.65/src/brainlayer/_build.py +1 -0
- brainlayer-1.5.65/src/brainlayer/brainbar_activation.py +202 -0
- brainlayer-1.5.65/src/brainlayer/brainbar_auto_activation.py +210 -0
- brainlayer-1.5.65/src/brainlayer/brainbar_identity.py +91 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/cli/__init__.py +48 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/supervisor_jobs.py +29 -0
- brainlayer-1.5.65/src/brainlayer/t3_linkage_alarm.py +110 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/t3_provenance.py +4 -6
- brainlayer-1.5.63/src/brainlayer/_build.py +0 -1
- {brainlayer-1.5.63 → brainlayer-1.5.65}/.gitignore +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/LICENSE +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/README.md +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/hotlane_brainbar_daemon.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/backup-daily.sh +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/brainlayer-env-run.sh +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/brainlayer.env.example +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/com.brainlayer.backup-daily.plist +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/com.brainlayer.daemon.plist +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/com.brainlayer.decay.plist +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/com.brainlayer.drain.plist +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/com.brainlayer.health-check.plist +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/com.brainlayer.hotlane-brainbar.plist +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/com.brainlayer.index.plist +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/com.brainlayer.jsonl-backup.plist +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/com.brainlayer.log-cap.plist +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/com.brainlayer.maintenance-nightly.plist +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/com.brainlayer.maintenance-weekly.plist +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/com.brainlayer.maintenance.plist +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/com.brainlayer.observability.plist +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/com.brainlayer.p0-counter.plist +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/com.brainlayer.repair-fts.plist +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/com.brainlayer.t3-ingest.plist +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/com.brainlayer.throughput-watchdog.plist +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/com.brainlayer.tier0-watchdog.plist +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/com.brainlayer.wal-checkpoint.plist +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/com.brainlayer.watch.plist +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/com.etanhey.brainlayer-fleet-watchdog.plist +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/fleet-watchdog.sh +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/install.sh +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/jsonl-backup.sh +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/launchd/throughput-watchdog.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/release-verify-signatures.sh +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/scripts/tier0-watchdog.sh +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/__main__.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/_helpers.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/agent_profiles.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/agent_provenance.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/alarm.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/archive_collapse.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/archive_lifecycle.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/backup_daily.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/backup_retention_invariant.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/badge_state.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/bitemporal.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/brainbar_hybrid_helper.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/cachedelete_purge.swift +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/calibrate.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/chunk_origin.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/chunk_origin_backfill.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/chunk_origin_wipe.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/chunk_write.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/classify.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/claude_paths.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/cli/wizard.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/cli_jobs.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/cli_new.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/cloud_backfill.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/clustering.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/component_jobs.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/config.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/consolidation_plan.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/content_class.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/correction_judge.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/data/agents_registry.yaml +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/data/sandbox_seeds/skill-eval-baseline.json +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/db_shrink.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/decay.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/decay_backfill.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/decay_job.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/dedupe.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/dedupe_merge.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/deploy_drift.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/doctor.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/drain.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/drain_liveness.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/drive_credentials.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/embeddings.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/engine.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/enrichment_replay.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/eval/__init__.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/eval/abcde_enrich_runner.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/eval/abcde_variants.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/eval/abcde_variants.yaml +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/eval/benchmark.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/eval/enrichment_gold.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/eval/enrichment_graders.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/eval/enrichment_judge.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/eval/enrichment_llm_judge.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/eval/enrichment_quality_benchmark.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/eval/experiment_store.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/eval/fixtures/README.md +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/eval/labeling/enrichment-labeler.html +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/eval/phoenix_gate/__init__.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/eval/phoenix_gate/__main__.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/eval/phoenix_gate/baseline_store.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/eval/phoenix_gate/cli.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/eval/phoenix_gate/models.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/eval/phoenix_gate/phoenix_client.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/eval/phoenix_gate/regression_gate.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/eval/phoenix_gate/triggers.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/eval/pilot_artifact_report.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/fallback_replay.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/git_learning.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/health_check.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/hook_python.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/hooks/__init__.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/hooks/indexer.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/import_sweep.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/index_budget.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/index_completeness.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/index_new.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/index_watchdog.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/ingest/__init__.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/ingest/codex.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/ingest/t3.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/ingest_denylist.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/ingest_guard.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/isolation_proof.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/job_alerts.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/job_lifecycle_health.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/jobs.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/jsonl_backup.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/kg/__init__.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/kg_cleanup.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/kg_judge.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/kg_promotion.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/kg_repo.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/kg_review_session.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/kg_session_finish.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/kg_session_harvest.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/launchd_consolidation.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/launchd_install.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/launchd_primitive.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/launchd_revert.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/lexical_defense.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/lexical_defense_dictionary.json +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/log_cap.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/maintenance.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/maintenance_calendar.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/maintenance_control.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/mcp/__init__.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/mcp/_format.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/mcp/_shared.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/mcp/enrich_handler.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/mcp/entity_handler.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/mcp/palette.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/mcp/search_handler.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/mcp/store_handler.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/mcp/tags_handler.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/mcp_socket_config.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/mcp_stdio_bridge.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/memory_types.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/migrate.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/migration_custody.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/migration_journal.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/migration_runtime.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/notification_policy.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/observability_backup.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/observability_surface.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/observability_transport.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/p0_longitudinal_count.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/parent_death.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/paths.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pause.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/phonetic.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/__init__.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/agent_enrichment.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/analyze_communication.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/batch_extraction.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/brain_graph.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/chat_tags.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/chunk.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/classify.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/cloud_scrub.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/cluster_sampling.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/code_intelligence.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/correction_detection.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/correction_mining.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/digest.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/enrichment_prompts.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/enrichment_results.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/enrichment_tiers.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/entity_extraction.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/entity_resolution.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/extract.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/extract_claude_desktop.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/extract_corrections.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/extract_markdown.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/extract_whatsapp.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/frustration_mining.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/git_overlay.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/kg_extraction.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/kg_extraction_groq.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/longitudinal_analyzer.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/obsidian_export.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/operation_grouping.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/plan_linking.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/rate_limiter.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/relation_backfill.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/relation_verification.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/sanitize.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/secret_scrub.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/semantic_style.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/sentiment.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/session_history.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/style_embed.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/style_index.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/tag_entity_promotion.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/temporal_chains.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/time_batcher.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/unified_timeline.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/pipeline/write_queue.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/provenance.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/provenance_autosupersede.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/provenance_integration.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/queue_io.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/queue_merge.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/reembed_backfill.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/retired_services.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/runtime_store.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/sandbox_db.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/scoping.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/scrub_at_rest.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/search_fanout.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/search_profile.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/search_repo.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/service_supervisor.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/session_repo.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/setup.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/socket_hygiene.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/source_class_migration.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/spotlight.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/storage.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/store.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/supervisor_adapters.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/supervisor_child.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/supervisor_guard.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/system_prompt_guard.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/t3_sources.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/tag_normalization.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/taxonomy.json +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/telemetry.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/timestamp_iso.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/types.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/vector_store.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/wal_checkpoint.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/watcher.py +0 -0
- {brainlayer-1.5.63 → brainlayer-1.5.65}/src/brainlayer/watcher_bridge.py +0 -0
- {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.
|
|
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
|
|
@@ -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.
|
|
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.
|
|
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.
|
|
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
|
|