brainlayer 1.5.78__tar.gz → 1.5.80__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.78 → brainlayer-1.5.80}/PKG-INFO +1 -1
- {brainlayer-1.5.78 → brainlayer-1.5.80}/pyproject.toml +1 -1
- {brainlayer-1.5.78 → brainlayer-1.5.80}/server.json +2 -2
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/__init__.py +1 -1
- brainlayer-1.5.80/src/brainlayer/_build.py +1 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/backup_daily.py +20 -7
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/brainbar_auto_activation.py +13 -4
- brainlayer-1.5.80/src/brainlayer/cli_jobs.py +108 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/decay_job.py +3 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/health_check.py +28 -11
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/job_alerts.py +10 -1
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/jsonl_backup.py +39 -22
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/maintenance.py +64 -5
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/maintenance_calendar.py +352 -32
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/observability_backup.py +3 -5
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/service_supervisor.py +3 -1
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/supervisor_adapters.py +1 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/supervisor_guard.py +72 -5
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/supervisor_jobs.py +27 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/vector_store.py +3 -24
- brainlayer-1.5.80/src/brainlayer/wal_checkpoint.py +338 -0
- brainlayer-1.5.80/src/brainlayer/writer_policy.py +31 -0
- brainlayer-1.5.78/src/brainlayer/_build.py +0 -1
- brainlayer-1.5.78/src/brainlayer/cli_jobs.py +0 -66
- brainlayer-1.5.78/src/brainlayer/wal_checkpoint.py +0 -175
- {brainlayer-1.5.78 → brainlayer-1.5.80}/.gitignore +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/LICENSE +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/README.md +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/scripts/hotlane_brainbar_daemon.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/scripts/launchd/backup-daily.sh +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/scripts/launchd/brainlayer-env-run.sh +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/scripts/launchd/brainlayer.env.example +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/scripts/launchd/com.brainlayer.backup-daily.plist +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/scripts/launchd/com.brainlayer.daemon.plist +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/scripts/launchd/com.brainlayer.decay.plist +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/scripts/launchd/com.brainlayer.drain.plist +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/scripts/launchd/com.brainlayer.health-check.plist +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/scripts/launchd/com.brainlayer.hotlane-brainbar.plist +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/scripts/launchd/com.brainlayer.index.plist +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/scripts/launchd/com.brainlayer.jsonl-backup.plist +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/scripts/launchd/com.brainlayer.log-cap.plist +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/scripts/launchd/com.brainlayer.maintenance-nightly.plist +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/scripts/launchd/com.brainlayer.maintenance-weekly.plist +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/scripts/launchd/com.brainlayer.maintenance.plist +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/scripts/launchd/com.brainlayer.observability.plist +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/scripts/launchd/com.brainlayer.p0-counter.plist +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/scripts/launchd/com.brainlayer.repair-fts.plist +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/scripts/launchd/com.brainlayer.t3-ingest.plist +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/scripts/launchd/com.brainlayer.throughput-watchdog.plist +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/scripts/launchd/com.brainlayer.tier0-watchdog.plist +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/scripts/launchd/com.brainlayer.wal-checkpoint.plist +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/scripts/launchd/com.brainlayer.watch.plist +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/scripts/launchd/com.etanhey.brainlayer-fleet-watchdog.plist +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/scripts/launchd/fleet-watchdog.sh +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/scripts/launchd/install.sh +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/scripts/launchd/jsonl-backup.sh +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/scripts/launchd/throughput-watchdog.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/scripts/release-verify-signatures.sh +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/scripts/tier0-watchdog.sh +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/__main__.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/_helpers.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/agent_profiles.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/agent_provenance.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/alarm.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/archive_collapse.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/archive_lifecycle.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/backup_retention_invariant.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/badge_state.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/bitemporal.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/brainbar_activation.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/brainbar_hybrid_helper.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/brainbar_identity.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/cachedelete_purge.swift +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/calibrate.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/chunk_origin.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/chunk_origin_backfill.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/chunk_origin_wipe.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/chunk_write.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/classify.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/claude_paths.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/cli/__init__.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/cli/wizard.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/cli_new.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/cloud_backfill.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/clustering.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/component_jobs.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/config.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/consolidation_plan.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/content_class.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/correction_judge.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/data/agents_registry.yaml +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/data/sandbox_seeds/skill-eval-baseline.json +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/db_shrink.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/decay.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/decay_backfill.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/dedupe.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/dedupe_merge.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/deploy_drift.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/disk_space.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/doctor.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/drain.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/drain_liveness.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/drive_credentials.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/embeddings.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/engine.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/enrichment_replay.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/eval/__init__.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/eval/abcde_enrich_runner.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/eval/abcde_variants.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/eval/abcde_variants.yaml +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/eval/benchmark.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/eval/enrichment_gold.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/eval/enrichment_graders.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/eval/enrichment_judge.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/eval/enrichment_llm_judge.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/eval/enrichment_quality_benchmark.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/eval/experiment_store.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/eval/fixtures/README.md +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/eval/labeling/enrichment-labeler.html +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/eval/phoenix_gate/__init__.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/eval/phoenix_gate/__main__.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/eval/phoenix_gate/baseline_store.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/eval/phoenix_gate/cli.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/eval/phoenix_gate/models.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/eval/phoenix_gate/phoenix_client.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/eval/phoenix_gate/regression_gate.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/eval/phoenix_gate/triggers.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/eval/pilot_artifact_report.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/fallback_replay.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/git_learning.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/hook_python.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/hooks/__init__.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/hooks/indexer.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/import_sweep.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/index_admission.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/index_budget.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/index_completeness.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/index_new.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/index_watchdog.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/ingest/__init__.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/ingest/codex.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/ingest/t3.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/ingest_denylist.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/ingest_guard.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/isolation_proof.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/job_lifecycle_health.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/jobs.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/kg/__init__.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/kg_cleanup.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/kg_judge.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/kg_promotion.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/kg_repo.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/kg_review_session.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/kg_session_finish.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/kg_session_harvest.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/launchd_consolidation.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/launchd_install.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/launchd_primitive.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/launchd_revert.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/lexical_defense.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/lexical_defense_dictionary.json +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/log_cap.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/maintenance_control.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/mcp/__init__.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/mcp/_format.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/mcp/_shared.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/mcp/enrich_handler.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/mcp/entity_handler.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/mcp/palette.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/mcp/search_handler.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/mcp/store_handler.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/mcp/tags_handler.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/mcp_socket_config.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/mcp_stdio_bridge.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/memory_types.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/migrate.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/migration_custody.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/migration_journal.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/migration_runtime.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/notification_policy.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/observability_surface.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/observability_transport.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/p0_longitudinal_count.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/parent_death.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/paths.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pause.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/phonetic.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/__init__.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/agent_enrichment.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/analyze_communication.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/batch_extraction.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/brain_graph.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/chat_tags.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/chunk.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/classify.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/cloud_scrub.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/cluster_sampling.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/code_intelligence.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/correction_detection.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/correction_mining.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/digest.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/enrichment_prompts.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/enrichment_results.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/enrichment_tiers.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/entity_extraction.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/entity_resolution.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/extract.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/extract_claude_desktop.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/extract_corrections.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/extract_markdown.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/extract_whatsapp.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/frustration_mining.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/git_overlay.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/kg_extraction.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/kg_extraction_groq.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/longitudinal_analyzer.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/obsidian_export.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/operation_grouping.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/plan_linking.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/rate_limiter.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/relation_backfill.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/relation_verification.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/sanitize.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/secret_scrub.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/semantic_style.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/sentiment.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/session_history.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/style_embed.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/style_index.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/tag_entity_promotion.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/temporal_chains.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/time_batcher.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/unified_timeline.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/pipeline/write_queue.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/provenance.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/provenance_autosupersede.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/provenance_integration.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/queue_io.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/queue_merge.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/reembed_backfill.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/retired_services.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/runtime_store.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/sandbox_db.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/scoping.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/scrub_at_rest.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/search_fanout.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/search_profile.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/search_repo.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/session_repo.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/setup.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/socket_hygiene.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/source_class_migration.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/spotlight.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/storage.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/store.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/supervisor_child.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/supervisor_diagnostics.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/system_prompt_guard.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/t3_linkage_alarm.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/t3_provenance.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/t3_sources.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/tag_normalization.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/taxonomy.json +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/telemetry.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/timestamp_iso.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/types.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/watcher.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/src/brainlayer/watcher_bridge.py +0 -0
- {brainlayer-1.5.78 → brainlayer-1.5.80}/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.80
|
|
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.80",
|
|
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.80",
|
|
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.80"
|
|
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 = "cc301a9f19b679f75278f9cca43c48fc49fe8d7e"
|
|
@@ -406,6 +406,10 @@ class BrainBarDatabaseIdentityError(RuntimeError):
|
|
|
406
406
|
"""BrainBar serves a different database than the one this backup was aimed at."""
|
|
407
407
|
|
|
408
408
|
|
|
409
|
+
class BrainBarDatabaseIdentityUnavailableError(BrainBarDatabaseIdentityError):
|
|
410
|
+
"""BrainBar did not confirm its open database; no difference has been established."""
|
|
411
|
+
|
|
412
|
+
|
|
409
413
|
# MCPRouter.handleBrainBackupVacuumInto refuses with this before any VACUUM INTO when the
|
|
410
414
|
# request's expected_db_path does not identify the database BrainBar serves.
|
|
411
415
|
BRAINBAR_DB_IDENTITY_MISMATCH = "BrainBar database identity mismatch"
|
|
@@ -1230,7 +1234,9 @@ def _refuse_foreign_db_on_production_socket(db_path: Path, socket_path: Path | s
|
|
|
1230
1234
|
configured_db = resolve_db_path()
|
|
1231
1235
|
if not _same_database_file(db_path, configured_db):
|
|
1232
1236
|
raise BrainBarDatabaseIdentityError(
|
|
1233
|
-
|
|
1237
|
+
"Backup blocked: BrainBar has a different database open. "
|
|
1238
|
+
"Check the backup database path and BrainBar's configured database. "
|
|
1239
|
+
f"Details: refusing to snapshot {db_path} through the production BrainBar "
|
|
1234
1240
|
f"socket {resolved_socket}, which serves the configured database {configured_db}; "
|
|
1235
1241
|
"pass an isolated socket_path to back up any other database"
|
|
1236
1242
|
)
|
|
@@ -1897,15 +1903,22 @@ def request_brainbar_vacuum_into(
|
|
|
1897
1903
|
echo = echo if isinstance(echo, dict) else {}
|
|
1898
1904
|
served_identity = (echo.get("db_dev"), echo.get("db_ino"))
|
|
1899
1905
|
expected_identity = _file_identity(resolved_expected_db)
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
#
|
|
1906
|
+
identity_valid = all(type(v) is int for v in served_identity) and served_identity[1] > 0
|
|
1907
|
+
if not identity_valid or served_identity != expected_identity:
|
|
1908
|
+
# A terminal response ends this writer. Unconfirmed and foreign snapshots
|
|
1909
|
+
# both fail closed, but absence of support proves no database difference.
|
|
1903
1910
|
attempt_path.unlink(missing_ok=True)
|
|
1904
1911
|
_backup_attempt_completion_marker(attempt_path).unlink(missing_ok=True)
|
|
1912
|
+
if not identity_valid:
|
|
1913
|
+
raise BrainBarDatabaseIdentityUnavailableError(
|
|
1914
|
+
"Backup blocked: this BrainBar version cannot confirm which database it has open. "
|
|
1915
|
+
"Upgrade BrainBar."
|
|
1916
|
+
)
|
|
1905
1917
|
raise BrainBarDatabaseIdentityError(
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
"
|
|
1918
|
+
"Backup blocked: BrainBar has a different database open. "
|
|
1919
|
+
"Check the backup database path and BrainBar's configured database. "
|
|
1920
|
+
f"Details: expected {resolved_expected_db} {expected_identity}, "
|
|
1921
|
+
f"BrainBar reported {echo.get('db_path')!r} {served_identity}; deleted the unconfirmed snapshot."
|
|
1909
1922
|
)
|
|
1910
1923
|
if not attempt_path.exists():
|
|
1911
1924
|
raise RuntimeError(f"BrainBar backup did not create snapshot: {attempt_path}")
|
|
@@ -31,12 +31,15 @@ def brew_processes():
|
|
|
31
31
|
# Include brew's Ruby entrypoint and Homebrew-owned child executables, even
|
|
32
32
|
# after reparenting. Unknown/unreadable process tables defer activation.
|
|
33
33
|
rows = process_table().splitlines()
|
|
34
|
-
matches = []
|
|
34
|
+
matches, readable = [], 0
|
|
35
35
|
for row in rows:
|
|
36
36
|
fields = row.split(None, 3)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
# A process exiting or exec-ing mid-scan can tear one row: empty args
|
|
38
|
+
# are still checked by comm, a row without a pid and comm is skipped.
|
|
39
|
+
if len(fields) < 3 or not fields[0].isascii() or not fields[0].isdecimal():
|
|
40
|
+
continue
|
|
41
|
+
readable += 1
|
|
42
|
+
tokens = fields[3].split() if len(fields) == 4 else []
|
|
40
43
|
if Path(fields[2]).name == "brew" or any(
|
|
41
44
|
Path(token).name in {"brew", "brew.rb"} or "/Homebrew/" in token or "/homebrew/Library/Homebrew/" in token
|
|
42
45
|
for token in tokens
|
|
@@ -44,6 +47,8 @@ def brew_processes():
|
|
|
44
47
|
# argv is detection-only: Homebrew curl headers can carry tokens.
|
|
45
48
|
executable = scrub_secrets(Path(fields[2]).name).text[:128]
|
|
46
49
|
matches.append(f"{fields[0]} {executable}")
|
|
50
|
+
if not readable:
|
|
51
|
+
raise ValueError("unreadable process table")
|
|
47
52
|
return matches
|
|
48
53
|
|
|
49
54
|
|
|
@@ -85,6 +90,9 @@ class AutoActivator:
|
|
|
85
90
|
processes = brew_processes()
|
|
86
91
|
except (OSError, subprocess.SubprocessError) as exc:
|
|
87
92
|
raise ProcessScanUnavailable(type(exc).__name__) from None
|
|
93
|
+
except ValueError:
|
|
94
|
+
# Counted and alerted only after 12 ticks, never an immediate refusal.
|
|
95
|
+
raise ProcessScanUnavailable("unreadable process table") from None
|
|
88
96
|
# Recover before activation can report a new failure on the same alert key.
|
|
89
97
|
self.state.with_suffix(".scan.json").unlink(missing_ok=True)
|
|
90
98
|
reason = active_alerts().get("brainbar-activation", "")
|
|
@@ -97,6 +105,7 @@ class AutoActivator:
|
|
|
97
105
|
(
|
|
98
106
|
"BrainBar activation process scan failed for >=12 ticks:",
|
|
99
107
|
"BrainBar activation monitor refused: Command '['/bin/ps',",
|
|
108
|
+
"BrainBar activation monitor refused: unreadable process table",
|
|
100
109
|
)
|
|
101
110
|
):
|
|
102
111
|
report("brainbar-activation", None)
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""Explicit consolidated launcher and component CLI commands."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import datetime as dt
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def register(app, jobs_app):
|
|
13
|
+
@app.command("maintenance-calendar")
|
|
14
|
+
def maintenance_calendar(
|
|
15
|
+
lib_dir: Path = typer.Option(Path(__file__).parent / "launchd", "--lib-dir"),
|
|
16
|
+
scheduled_date: str | None = typer.Option(None, "--date"),
|
|
17
|
+
state_dir: Path | None = typer.Option(None, "--state-dir"),
|
|
18
|
+
logs_dir: Path = typer.Option(Path.home() / "Library/Logs/brainlayer", "--logs-dir"),
|
|
19
|
+
recover_transcripts: bool = typer.Option(False, "--recover-transcripts"),
|
|
20
|
+
recover_failed: bool = typer.Option(False, "--recover-failed"),
|
|
21
|
+
status: bool = typer.Option(
|
|
22
|
+
False, "--status", help="Check failed-step recovery admission without running steps"
|
|
23
|
+
),
|
|
24
|
+
):
|
|
25
|
+
"""Run BrainLayer Maintenance for one scheduled date."""
|
|
26
|
+
from .maintenance_calendar import calendar_steps, run_calendar
|
|
27
|
+
from .paths import supervisor_socket_path
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
date = dt.date.fromisoformat(scheduled_date) if scheduled_date else dt.date.today()
|
|
31
|
+
except ValueError as exc:
|
|
32
|
+
raise typer.BadParameter("date must be ISO YYYY-MM-DD") from exc
|
|
33
|
+
if recover_failed and recover_transcripts:
|
|
34
|
+
raise typer.BadParameter("Choose --recover-failed or --recover-transcripts")
|
|
35
|
+
if status and not recover_failed:
|
|
36
|
+
raise typer.BadParameter("--status requires --recover-failed")
|
|
37
|
+
if recover_failed and scheduled_date:
|
|
38
|
+
raise typer.BadParameter("Failed-step recovery uses the latest scheduled night; omit --date")
|
|
39
|
+
result = {}
|
|
40
|
+
directory = state_dir or supervisor_socket_path().parent / "calendar"
|
|
41
|
+
if directory.resolve() == supervisor_socket_path().parent.resolve():
|
|
42
|
+
raise typer.BadParameter("calendar custody directory must differ from resident supervisor")
|
|
43
|
+
try:
|
|
44
|
+
plan = calendar_steps(sys.executable, str(Path(sys.executable).with_name("brainlayer")), lib_dir, date)
|
|
45
|
+
code = asyncio.run(
|
|
46
|
+
run_calendar(
|
|
47
|
+
plan,
|
|
48
|
+
directory,
|
|
49
|
+
logs_dir,
|
|
50
|
+
install_signals=True,
|
|
51
|
+
transcript_recovery=recover_transcripts,
|
|
52
|
+
**(
|
|
53
|
+
dict(failed_recovery=True, recovery_status=status, recovery_result=result)
|
|
54
|
+
if recover_failed
|
|
55
|
+
else {}
|
|
56
|
+
),
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
except (OSError, ValueError, RuntimeError, KeyError, TypeError) as exc:
|
|
60
|
+
if not recover_failed:
|
|
61
|
+
raise
|
|
62
|
+
from .maintenance_calendar import recovery_refusal_reason
|
|
63
|
+
|
|
64
|
+
results = result.get("results") or {}
|
|
65
|
+
if result.get("status") == "accepted" and results and all(v == "ok" for v in results.values()):
|
|
66
|
+
# The retried steps finished and were journaled; only cleanup after them failed.
|
|
67
|
+
result.update(
|
|
68
|
+
reason="Retried successfully; maintenance cleanup afterwards had a problem (see the log)."
|
|
69
|
+
)
|
|
70
|
+
code = 0
|
|
71
|
+
elif results:
|
|
72
|
+
# The steps ran and did not all succeed: keep their outcome and reason, not an
|
|
73
|
+
# availability error. A cleanup failure afterwards does not change what ran.
|
|
74
|
+
if result.get("status") != "failed":
|
|
75
|
+
result.update(reason="Retry did not finish successfully; see the step results and logs")
|
|
76
|
+
result.update(status="failed")
|
|
77
|
+
code = 1
|
|
78
|
+
else:
|
|
79
|
+
status = "failed" if result.get("status") in {"accepted", "failed"} else "refused"
|
|
80
|
+
result.update(status=status, reason=recovery_refusal_reason(exc), steps=[])
|
|
81
|
+
code = 1
|
|
82
|
+
if recover_failed:
|
|
83
|
+
typer.echo(json.dumps(result, sort_keys=True))
|
|
84
|
+
raise typer.Exit(code)
|
|
85
|
+
|
|
86
|
+
@app.command("daemon")
|
|
87
|
+
def daemon(manifest: Path = typer.Option(..., "--manifest"), socket: Path | None = typer.Option(None, "--socket")):
|
|
88
|
+
"""Run BrainLayer Daemon from a private command manifest."""
|
|
89
|
+
from .supervisor_jobs import run_daemon
|
|
90
|
+
|
|
91
|
+
run_daemon(manifest, socket)
|
|
92
|
+
|
|
93
|
+
@jobs_app.command("component")
|
|
94
|
+
def component(
|
|
95
|
+
name: str,
|
|
96
|
+
action: str,
|
|
97
|
+
manifest: Path | None = typer.Option(None, "--manifest"),
|
|
98
|
+
socket: Path | None = typer.Option(None, "--socket"),
|
|
99
|
+
):
|
|
100
|
+
"""Status/restart or configured env/log paths for one component."""
|
|
101
|
+
from .component_jobs import component_action
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
result = component_action(name, action, manifest=manifest, path=socket)
|
|
105
|
+
except (OSError, ValueError, RuntimeError, KeyError, TypeError) as exc:
|
|
106
|
+
typer.echo(json.dumps({"error": str(exc)}))
|
|
107
|
+
raise typer.Exit(1) from exc
|
|
108
|
+
typer.echo(json.dumps(result, sort_keys=True))
|
|
@@ -5,6 +5,8 @@ from typing import Any
|
|
|
5
5
|
|
|
6
6
|
import apsw
|
|
7
7
|
|
|
8
|
+
from .writer_policy import configure_writer_wal_policy
|
|
9
|
+
|
|
8
10
|
ARCHIVE_THRESHOLD = 0.1
|
|
9
11
|
|
|
10
12
|
|
|
@@ -35,6 +37,7 @@ def run_decay_job(
|
|
|
35
37
|
current_time = time.time() if now is None else now
|
|
36
38
|
connection = apsw.Connection(str(db_path))
|
|
37
39
|
connection.setbusytimeout(30_000)
|
|
40
|
+
configure_writer_wal_policy(connection)
|
|
38
41
|
cursor = connection.cursor()
|
|
39
42
|
|
|
40
43
|
last_rowid = 0
|
|
@@ -1572,14 +1572,15 @@ def _run_health_check_locked(
|
|
|
1572
1572
|
if result.missing_embedding_scan.completeness == "complete":
|
|
1573
1573
|
result.missing_vectors = result.missing_embedding_scan.missing
|
|
1574
1574
|
except sqlite3.OperationalError as exc:
|
|
1575
|
-
|
|
1575
|
+
# Stable codes for BrainBar's vector notice; the exception text stays in the issue message.
|
|
1576
|
+
result.missing_embedding_scan = MissingEmbeddingScan(reason="sql_error")
|
|
1576
1577
|
add_issue(
|
|
1577
1578
|
"missing_embeddings_count_failed",
|
|
1578
1579
|
"warning" if _text_has_sqlite_busy_signal(exc) else "critical",
|
|
1579
1580
|
f"could not count missing embeddings: {exc}",
|
|
1580
1581
|
)
|
|
1581
1582
|
except Exception as exc:
|
|
1582
|
-
result.missing_embedding_scan = MissingEmbeddingScan(reason=
|
|
1583
|
+
result.missing_embedding_scan = MissingEmbeddingScan(reason="unexpected")
|
|
1583
1584
|
add_issue(
|
|
1584
1585
|
"missing_embeddings_count_failed",
|
|
1585
1586
|
"critical",
|
|
@@ -1587,18 +1588,26 @@ def _run_health_check_locked(
|
|
|
1587
1588
|
)
|
|
1588
1589
|
scan = result.missing_embedding_scan
|
|
1589
1590
|
state["missing_embedding_scan"] = asdict(scan)
|
|
1590
|
-
|
|
1591
|
+
# Keep budget reasons aligned with BrainBarVectorScan.routinePartial in
|
|
1592
|
+
# brain-bar/Sources/BrainBar/Dashboard/ObservabilityView.swift.
|
|
1593
|
+
routine_partial = (
|
|
1594
|
+
scan.completeness == "partial"
|
|
1595
|
+
and scan.scanned > 0
|
|
1596
|
+
and scan.reason in {"instruction_limit", "time_limit", "page_limit", "window_end"}
|
|
1597
|
+
)
|
|
1598
|
+
if scan.completeness != "complete" and not routine_partial:
|
|
1591
1599
|
add_issue(
|
|
1592
1600
|
"missing_embeddings_scan_incomplete",
|
|
1593
1601
|
"warning",
|
|
1594
|
-
|
|
1602
|
+
"The vector check could not finish. The current backlog is not verified by this check.",
|
|
1603
|
+
)
|
|
1604
|
+
if scan.completeness != "complete" and scan.missing:
|
|
1605
|
+
add_issue(
|
|
1606
|
+
"missing_embeddings_backlog",
|
|
1607
|
+
"warning",
|
|
1608
|
+
f"At least {scan.missing:,} checked {'chunk is' if scan.missing == 1 else 'chunks are'} waiting for vectors. "
|
|
1609
|
+
"See Vector backlog for the latest full count.",
|
|
1595
1610
|
)
|
|
1596
|
-
if scan.missing:
|
|
1597
|
-
add_issue(
|
|
1598
|
-
"missing_embeddings_backlog",
|
|
1599
|
-
"warning",
|
|
1600
|
-
f"at least {scan.missing} missing embeddings in {scan.scanned} sampled rows",
|
|
1601
|
-
)
|
|
1602
1611
|
|
|
1603
1612
|
hotlane_sample_advanced, hotlane_progressed = _hotlane_progress_sample(state, result.missing_vectors, now)
|
|
1604
1613
|
if hotlane_progressed:
|
|
@@ -1978,7 +1987,15 @@ def _run_health_check_locked(
|
|
|
1978
1987
|
state_payload["heal_failures"] = heal_failures
|
|
1979
1988
|
if job_tick is not None:
|
|
1980
1989
|
state_payload["job_lifecycle"] = job_tick.state
|
|
1981
|
-
state_payload["heal_tripped"] =
|
|
1990
|
+
state_payload["heal_tripped"] = (
|
|
1991
|
+
sorted(heal_tripped)
|
|
1992
|
+
if result.issues
|
|
1993
|
+
else [
|
|
1994
|
+
key
|
|
1995
|
+
for key in sorted(heal_tripped)
|
|
1996
|
+
if not hotlane_sample_advanced and key == _heal_key(config.hotlane_label, "missing_embeddings_not_draining")
|
|
1997
|
+
]
|
|
1998
|
+
)
|
|
1982
1999
|
_record_queue_notice(state_payload, queue_notice_signature)
|
|
1983
2000
|
state_payload["ts"] = now.isoformat()
|
|
1984
2001
|
if result.missing_vectors is not None:
|
|
@@ -65,7 +65,12 @@ def report(
|
|
|
65
65
|
path: Path | None = None,
|
|
66
66
|
notify: Callable[[str], None] = _notify,
|
|
67
67
|
) -> bool:
|
|
68
|
-
"""Set or clear one episode. Return true only when the visible state changes.
|
|
68
|
+
"""Set or clear one episode. Return true only when the visible state changes.
|
|
69
|
+
|
|
70
|
+
Only a new failure posts a notification, and only when no other active key already
|
|
71
|
+
carries the identical message (one incident fanned out across keys posts once).
|
|
72
|
+
Recovery updates the alert file and the health log but never notifies.
|
|
73
|
+
"""
|
|
69
74
|
destination = path or alert_path()
|
|
70
75
|
with _locked(destination):
|
|
71
76
|
current = active_alerts(destination)
|
|
@@ -75,15 +80,19 @@ def report(
|
|
|
75
80
|
return False
|
|
76
81
|
current.pop(key)
|
|
77
82
|
message = f"BrainLayer {key} recovered"
|
|
83
|
+
post = False
|
|
78
84
|
else:
|
|
79
85
|
if before is not None and (before == reason or key != "drive-consent"):
|
|
80
86
|
return False
|
|
87
|
+
post = reason not in (value for other, value in current.items() if other != key)
|
|
81
88
|
current[key] = reason
|
|
82
89
|
message = reason
|
|
83
90
|
from brainlayer.drive_credentials import _atomic_write
|
|
84
91
|
|
|
85
92
|
_atomic_write(destination, current)
|
|
86
93
|
LOG.warning("health event job=%s state=%s reason=%s", key, "recovered" if reason is None else "failed", message)
|
|
94
|
+
if not post:
|
|
95
|
+
return True
|
|
87
96
|
try:
|
|
88
97
|
notify(message)
|
|
89
98
|
except (OSError, subprocess.SubprocessError) as exc:
|
|
@@ -30,6 +30,7 @@ opaque `.pb` implicit records, and per-session `.system_generated` /
|
|
|
30
30
|
from __future__ import annotations
|
|
31
31
|
|
|
32
32
|
import datetime as dt
|
|
33
|
+
import errno
|
|
33
34
|
import fcntl
|
|
34
35
|
import functools
|
|
35
36
|
import gzip
|
|
@@ -1226,7 +1227,8 @@ class _HashingReader:
|
|
|
1226
1227
|
|
|
1227
1228
|
def create_jsonl_bundle_with_digests(
|
|
1228
1229
|
candidates: list[JsonlCandidate], staging_dir: Path, *, date_stamp: str
|
|
1229
|
-
) -> tuple[Path, dict[str, str], dict[str, int]]:
|
|
1230
|
+
) -> tuple[Path | None, dict[str, str], dict[str, int]]:
|
|
1231
|
+
"""Bundle completed source snapshots; return no archive when every source vanished."""
|
|
1230
1232
|
if not candidates:
|
|
1231
1233
|
raise ValueError("create_jsonl_bundle requires at least one candidate")
|
|
1232
1234
|
staging_dir = Path(staging_dir).expanduser()
|
|
@@ -1240,30 +1242,37 @@ def create_jsonl_bundle_with_digests(
|
|
|
1240
1242
|
sizes: dict[str, int] = {}
|
|
1241
1243
|
try:
|
|
1242
1244
|
with tarfile.open(temp_path, "w:gz") as tar:
|
|
1245
|
+
tar.dereference = True
|
|
1243
1246
|
for candidate in candidates:
|
|
1244
|
-
#
|
|
1245
|
-
#
|
|
1246
|
-
#
|
|
1247
|
-
#
|
|
1248
|
-
#
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1247
|
+
# Open before fstat so unlink/rotation cannot invalidate the metadata.
|
|
1248
|
+
# Snapshot one member on disk before writing its tar header: an ENOENT
|
|
1249
|
+
# even partway through a read must leave no partial member in the gzip.
|
|
1250
|
+
# Hash exactly the snapshot bytes, at O(buffer) memory. Following links
|
|
1251
|
+
# and dereferencing hardlinks keeps every source a regular member.
|
|
1252
|
+
with tempfile.TemporaryFile(dir=staging_dir) as snapshot:
|
|
1253
|
+
try:
|
|
1254
|
+
with candidate.path.open("rb") as handle:
|
|
1255
|
+
info = tar.gettarinfo(fileobj=handle, arcname=_archive_name(candidate))
|
|
1256
|
+
reader = _HashingReader(handle)
|
|
1257
|
+
tarfile.copyfileobj(reader, snapshot, info.size)
|
|
1258
|
+
except OSError as exc:
|
|
1259
|
+
if isinstance(exc, FileNotFoundError) or exc.errno == errno.ENOENT:
|
|
1260
|
+
continue
|
|
1261
|
+
raise
|
|
1262
|
+
snapshot.seek(0)
|
|
1263
|
+
tar.addfile(info, snapshot)
|
|
1257
1264
|
digests[candidate.path.as_posix()] = reader.hexdigest()
|
|
1258
1265
|
sizes[candidate.path.as_posix()] = info.size
|
|
1266
|
+
if not digests:
|
|
1267
|
+
return None, digests, sizes
|
|
1259
1268
|
os.replace(temp_path, archive_path)
|
|
1260
1269
|
finally:
|
|
1261
1270
|
temp_path.unlink(missing_ok=True)
|
|
1262
1271
|
return archive_path, digests, sizes
|
|
1263
1272
|
|
|
1264
1273
|
|
|
1265
|
-
def create_jsonl_bundle(candidates: list[JsonlCandidate], staging_dir: Path, *, date_stamp: str) -> Path:
|
|
1266
|
-
"""
|
|
1274
|
+
def create_jsonl_bundle(candidates: list[JsonlCandidate], staging_dir: Path, *, date_stamp: str) -> Path | None:
|
|
1275
|
+
"""Return only the archive path, or None when every candidate vanished."""
|
|
1267
1276
|
archive_path, _, _ = create_jsonl_bundle_with_digests(candidates, staging_dir, date_stamp=date_stamp)
|
|
1268
1277
|
return archive_path
|
|
1269
1278
|
|
|
@@ -1626,7 +1635,18 @@ def run_backup(
|
|
|
1626
1635
|
if pending_candidates:
|
|
1627
1636
|
changed = [candidate for candidate in changed if candidate.path.as_posix() not in pending_icloud_sources]
|
|
1628
1637
|
|
|
1629
|
-
|
|
1638
|
+
archive_path = None
|
|
1639
|
+
bundle_digests: dict[str, str] = {}
|
|
1640
|
+
bundle_sizes: dict[str, int] = {}
|
|
1641
|
+
if changed:
|
|
1642
|
+
archive_path, bundle_digests, bundle_sizes = create_jsonl_bundle_with_digests(
|
|
1643
|
+
changed, staging_dir, date_stamp=date_stamp
|
|
1644
|
+
)
|
|
1645
|
+
bundled = [candidate for candidate in changed if candidate.path.as_posix() in bundle_digests]
|
|
1646
|
+
vanished += len(changed) - len(bundled)
|
|
1647
|
+
changed = bundled
|
|
1648
|
+
|
|
1649
|
+
if archive_path is None and pending_candidates:
|
|
1630
1650
|
error = (
|
|
1631
1651
|
f"iCloud coverage is pending; repair deferred while {len(pending_candidates)} source(s) finish uploading"
|
|
1632
1652
|
)
|
|
@@ -1649,7 +1669,7 @@ def run_backup(
|
|
|
1649
1669
|
_enqueue_run_summary(result, queue_dir=queue_dir)
|
|
1650
1670
|
return result
|
|
1651
1671
|
|
|
1652
|
-
if
|
|
1672
|
+
if archive_path is None and icloud_bootstrap_pending and active:
|
|
1653
1673
|
error = (
|
|
1654
1674
|
"iCloud coverage is not verified; repair deferred because "
|
|
1655
1675
|
f"{len(active)} discovered source(s) are still active"
|
|
@@ -1672,7 +1692,7 @@ def run_backup(
|
|
|
1672
1692
|
_enqueue_run_summary(result, queue_dir=queue_dir)
|
|
1673
1693
|
return result
|
|
1674
1694
|
|
|
1675
|
-
if
|
|
1695
|
+
if archive_path is None:
|
|
1676
1696
|
result: dict[str, Any] = {
|
|
1677
1697
|
"attempted_at": attempted_at,
|
|
1678
1698
|
"status": "no-op",
|
|
@@ -1689,9 +1709,6 @@ def run_backup(
|
|
|
1689
1709
|
_enqueue_run_summary(result, queue_dir=queue_dir)
|
|
1690
1710
|
return result
|
|
1691
1711
|
|
|
1692
|
-
archive_path, bundle_digests, bundle_sizes = create_jsonl_bundle_with_digests(
|
|
1693
|
-
changed, staging_dir, date_stamp=date_stamp
|
|
1694
|
-
)
|
|
1695
1712
|
archive_size = archive_path.stat().st_size
|
|
1696
1713
|
result = {
|
|
1697
1714
|
"attempted_at": attempted_at,
|
|
@@ -43,7 +43,8 @@ from .paths import get_db_path
|
|
|
43
43
|
from .pause import DEFAULT_PAUSE_SENTINEL_PATH, pause_applies_to_label, pause_sentinel_state
|
|
44
44
|
from .pipeline.code_intelligence import _pid_is_alive, _pid_start_time
|
|
45
45
|
from .queue_io import get_queue_dir
|
|
46
|
-
from .wal_checkpoint import checkpoint
|
|
46
|
+
from .wal_checkpoint import CheckpointGuardTimeout, checkpoint, get_wal_size
|
|
47
|
+
from .writer_policy import configure_writer_wal_policy
|
|
47
48
|
|
|
48
49
|
PAUSE_SENTINEL_PATH = DEFAULT_PAUSE_SENTINEL_PATH
|
|
49
50
|
|
|
@@ -64,6 +65,7 @@ _WATCHDOG_DISABLED_BEFORE = "_fleet_watchdog_disabled_before"
|
|
|
64
65
|
MAINTENANCE_LOCK_TIMEOUT_SECONDS = 4 * 60 * 60
|
|
65
66
|
# VACUUM is interrupted this long before its hold expires, so restore wins the calendar's cut.
|
|
66
67
|
HOLD_UNWIND_S = 30.0
|
|
68
|
+
POST_VACUUM_BUSY_S = 0.25
|
|
67
69
|
VACUUM_PROGRESS_OPS = 100_000 # SQLite VM steps between deadline polls during VACUUM.
|
|
68
70
|
# VACUUM's final copy-back into the main DB runs no VM steps, so the progress handler cannot stop
|
|
69
71
|
# it: a 20.7 GB copy (MBP, 2026-10-19) overran a 60 s cutoff and completed at 328 s, 15.9 s/GB.
|
|
@@ -906,6 +908,55 @@ def _checkpoint_full(db_path: Path) -> tuple[int, int, int]:
|
|
|
906
908
|
return checkpoint(str(db_path), "FULL")
|
|
907
909
|
|
|
908
910
|
|
|
911
|
+
def _reclaim_vacuum_wal(
|
|
912
|
+
config: MaintenanceConfig, *, deadline: float | None, phase: str
|
|
913
|
+
) -> tuple[int, int, int] | None:
|
|
914
|
+
"""Try once before releasing custody, preserving the VACUUM outcome.
|
|
915
|
+
|
|
916
|
+
The restore reserve is only checked before starting: ``remaining`` bounds SQLite lock waiting,
|
|
917
|
+
not the checkpoint's WAL-copy I/O once it runs. The replaced FULL checkpoint had the same limit.
|
|
918
|
+
"""
|
|
919
|
+
from .maintenance_control import hold_clock
|
|
920
|
+
|
|
921
|
+
started = time.monotonic()
|
|
922
|
+
event = dict(phase=phase, mode="TRUNCATE", wal_before_bytes=get_wal_size(str(config.db_path)))
|
|
923
|
+
result = None
|
|
924
|
+
busy = None
|
|
925
|
+
reason = ""
|
|
926
|
+
try:
|
|
927
|
+
remaining = POST_VACUUM_BUSY_S if deadline is None else deadline - HOLD_UNWIND_S - hold_clock()
|
|
928
|
+
if remaining <= 0:
|
|
929
|
+
reason = "hold-unwind-reserve"
|
|
930
|
+
else:
|
|
931
|
+
result = checkpoint(
|
|
932
|
+
str(config.db_path),
|
|
933
|
+
"TRUNCATE",
|
|
934
|
+
guard_timeout_seconds=0,
|
|
935
|
+
busy_timeout_seconds=min(POST_VACUUM_BUSY_S, remaining),
|
|
936
|
+
)
|
|
937
|
+
busy = result[0]
|
|
938
|
+
if busy:
|
|
939
|
+
reason = "reader-or-writer-busy"
|
|
940
|
+
except CheckpointGuardTimeout:
|
|
941
|
+
reason = "checkpoint-guard-busy"
|
|
942
|
+
except Exception as exc:
|
|
943
|
+
reason = f"{type(exc).__name__}: {exc}"
|
|
944
|
+
event.update(
|
|
945
|
+
wal_after_bytes=get_wal_size(str(config.db_path)),
|
|
946
|
+
duration_ms=(time.monotonic() - started) * 1000,
|
|
947
|
+
busy=busy,
|
|
948
|
+
deferred=bool(reason),
|
|
949
|
+
reason=reason,
|
|
950
|
+
checkpoint=result,
|
|
951
|
+
)
|
|
952
|
+
try:
|
|
953
|
+
_write_log(config.log_path, event)
|
|
954
|
+
except Exception as exc:
|
|
955
|
+
# A failed cleanup receipt must not replace the VACUUM failure or delay restoration.
|
|
956
|
+
print(f"maintenance WAL reclaim: {json.dumps(event)}; log failed: {exc}", file=sys.stderr)
|
|
957
|
+
return result
|
|
958
|
+
|
|
959
|
+
|
|
909
960
|
def _file_size(path: Path) -> int:
|
|
910
961
|
try:
|
|
911
962
|
return path.stat().st_size
|
|
@@ -986,6 +1037,7 @@ def _vacuum(db_path: Path, *, deadline: float | None = None) -> tuple[int, int]:
|
|
|
986
1037
|
before = db_path.stat().st_size
|
|
987
1038
|
conn = sqlite3.connect(db_path)
|
|
988
1039
|
try:
|
|
1040
|
+
configure_writer_wal_policy(conn)
|
|
989
1041
|
if cutoff is not None:
|
|
990
1042
|
# Polled on the VACUUM's own thread: no timer race, and an expiry before the statement
|
|
991
1043
|
# starts still interrupts it (sqlite3_interrupt with no running statement is a no-op).
|
|
@@ -1345,10 +1397,17 @@ def run_maintenance(mode: str, *, config: MaintenanceConfig | None = None, dry_r
|
|
|
1345
1397
|
if mode == "full" and backup is not None:
|
|
1346
1398
|
result.actions.append(f"verified_local_backup={backup.get('snapshot')}")
|
|
1347
1399
|
hold = booted_out.get("_supervisor_hold")
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1400
|
+
deadline = getattr(hold, "deadline", None)
|
|
1401
|
+
# _vacuum closes its connection before returning or raising. Never reclaim
|
|
1402
|
+
# inside its transaction, and never renew custody just to reclaim disk space.
|
|
1403
|
+
# SystemExit/KeyboardInterrupt (SIGTERM) skip the reclaim and unwind straight
|
|
1404
|
+
# to service restore: a full-size TRUNCATE there would delay it by seconds.
|
|
1405
|
+
try:
|
|
1406
|
+
result.vacuum_before_bytes, result.vacuum_after_bytes = _vacuum(config.db_path, deadline=deadline)
|
|
1407
|
+
except Exception:
|
|
1408
|
+
result.checkpoint = _reclaim_vacuum_wal(config, deadline=deadline, phase="vacuum-failure")
|
|
1409
|
+
raise
|
|
1410
|
+
result.checkpoint = _reclaim_vacuum_wal(config, deadline=deadline, phase="post-vacuum")
|
|
1352
1411
|
if mode == "burn":
|
|
1353
1412
|
result.burn = _run_burn_until_empty(config)
|
|
1354
1413
|
if result.burn.failed_files:
|