brainlayer 1.5.76__tar.gz → 1.5.77__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.76 → brainlayer-1.5.77}/PKG-INFO +1 -1
- {brainlayer-1.5.76 → brainlayer-1.5.77}/pyproject.toml +1 -1
- {brainlayer-1.5.76 → brainlayer-1.5.77}/server.json +2 -2
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/__init__.py +1 -1
- brainlayer-1.5.77/src/brainlayer/_build.py +1 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/backup_daily.py +135 -20
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/health_check.py +18 -17
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/observability_backup.py +29 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/supervisor_adapters.py +81 -4
- brainlayer-1.5.76/src/brainlayer/_build.py +0 -1
- {brainlayer-1.5.76 → brainlayer-1.5.77}/.gitignore +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/LICENSE +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/README.md +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/scripts/hotlane_brainbar_daemon.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/scripts/launchd/backup-daily.sh +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/scripts/launchd/brainlayer-env-run.sh +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/scripts/launchd/brainlayer.env.example +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/scripts/launchd/com.brainlayer.backup-daily.plist +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/scripts/launchd/com.brainlayer.daemon.plist +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/scripts/launchd/com.brainlayer.decay.plist +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/scripts/launchd/com.brainlayer.drain.plist +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/scripts/launchd/com.brainlayer.health-check.plist +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/scripts/launchd/com.brainlayer.hotlane-brainbar.plist +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/scripts/launchd/com.brainlayer.index.plist +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/scripts/launchd/com.brainlayer.jsonl-backup.plist +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/scripts/launchd/com.brainlayer.log-cap.plist +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/scripts/launchd/com.brainlayer.maintenance-nightly.plist +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/scripts/launchd/com.brainlayer.maintenance-weekly.plist +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/scripts/launchd/com.brainlayer.maintenance.plist +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/scripts/launchd/com.brainlayer.observability.plist +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/scripts/launchd/com.brainlayer.p0-counter.plist +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/scripts/launchd/com.brainlayer.repair-fts.plist +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/scripts/launchd/com.brainlayer.t3-ingest.plist +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/scripts/launchd/com.brainlayer.throughput-watchdog.plist +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/scripts/launchd/com.brainlayer.tier0-watchdog.plist +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/scripts/launchd/com.brainlayer.wal-checkpoint.plist +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/scripts/launchd/com.brainlayer.watch.plist +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/scripts/launchd/com.etanhey.brainlayer-fleet-watchdog.plist +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/scripts/launchd/fleet-watchdog.sh +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/scripts/launchd/install.sh +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/scripts/launchd/jsonl-backup.sh +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/scripts/launchd/throughput-watchdog.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/scripts/release-verify-signatures.sh +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/scripts/tier0-watchdog.sh +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/__main__.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/_helpers.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/agent_profiles.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/agent_provenance.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/alarm.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/archive_collapse.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/archive_lifecycle.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/backup_retention_invariant.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/badge_state.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/bitemporal.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/brainbar_activation.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/brainbar_auto_activation.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/brainbar_hybrid_helper.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/brainbar_identity.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/cachedelete_purge.swift +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/calibrate.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/chunk_origin.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/chunk_origin_backfill.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/chunk_origin_wipe.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/chunk_write.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/classify.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/claude_paths.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/cli/__init__.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/cli/wizard.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/cli_jobs.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/cli_new.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/cloud_backfill.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/clustering.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/component_jobs.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/config.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/consolidation_plan.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/content_class.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/correction_judge.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/data/agents_registry.yaml +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/data/sandbox_seeds/skill-eval-baseline.json +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/db_shrink.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/decay.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/decay_backfill.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/decay_job.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/dedupe.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/dedupe_merge.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/deploy_drift.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/disk_space.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/doctor.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/drain.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/drain_liveness.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/drive_credentials.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/embeddings.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/engine.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/enrichment_replay.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/eval/__init__.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/eval/abcde_enrich_runner.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/eval/abcde_variants.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/eval/abcde_variants.yaml +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/eval/benchmark.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/eval/enrichment_gold.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/eval/enrichment_graders.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/eval/enrichment_judge.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/eval/enrichment_llm_judge.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/eval/enrichment_quality_benchmark.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/eval/experiment_store.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/eval/fixtures/README.md +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/eval/labeling/enrichment-labeler.html +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/eval/phoenix_gate/__init__.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/eval/phoenix_gate/__main__.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/eval/phoenix_gate/baseline_store.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/eval/phoenix_gate/cli.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/eval/phoenix_gate/models.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/eval/phoenix_gate/phoenix_client.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/eval/phoenix_gate/regression_gate.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/eval/phoenix_gate/triggers.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/eval/pilot_artifact_report.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/fallback_replay.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/git_learning.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/hook_python.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/hooks/__init__.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/hooks/indexer.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/import_sweep.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/index_admission.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/index_budget.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/index_completeness.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/index_new.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/index_watchdog.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/ingest/__init__.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/ingest/codex.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/ingest/t3.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/ingest_denylist.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/ingest_guard.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/isolation_proof.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/job_alerts.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/job_lifecycle_health.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/jobs.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/jsonl_backup.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/kg/__init__.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/kg_cleanup.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/kg_judge.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/kg_promotion.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/kg_repo.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/kg_review_session.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/kg_session_finish.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/kg_session_harvest.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/launchd_consolidation.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/launchd_install.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/launchd_primitive.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/launchd_revert.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/lexical_defense.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/lexical_defense_dictionary.json +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/log_cap.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/maintenance.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/maintenance_calendar.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/maintenance_control.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/mcp/__init__.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/mcp/_format.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/mcp/_shared.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/mcp/enrich_handler.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/mcp/entity_handler.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/mcp/palette.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/mcp/search_handler.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/mcp/store_handler.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/mcp/tags_handler.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/mcp_socket_config.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/mcp_stdio_bridge.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/memory_types.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/migrate.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/migration_custody.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/migration_journal.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/migration_runtime.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/notification_policy.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/observability_surface.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/observability_transport.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/p0_longitudinal_count.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/parent_death.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/paths.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pause.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/phonetic.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/__init__.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/agent_enrichment.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/analyze_communication.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/batch_extraction.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/brain_graph.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/chat_tags.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/chunk.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/classify.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/cloud_scrub.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/cluster_sampling.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/code_intelligence.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/correction_detection.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/correction_mining.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/digest.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/enrichment_prompts.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/enrichment_results.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/enrichment_tiers.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/entity_extraction.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/entity_resolution.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/extract.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/extract_claude_desktop.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/extract_corrections.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/extract_markdown.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/extract_whatsapp.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/frustration_mining.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/git_overlay.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/kg_extraction.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/kg_extraction_groq.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/longitudinal_analyzer.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/obsidian_export.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/operation_grouping.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/plan_linking.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/rate_limiter.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/relation_backfill.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/relation_verification.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/sanitize.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/secret_scrub.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/semantic_style.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/sentiment.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/session_history.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/style_embed.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/style_index.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/tag_entity_promotion.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/temporal_chains.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/time_batcher.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/unified_timeline.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/pipeline/write_queue.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/provenance.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/provenance_autosupersede.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/provenance_integration.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/queue_io.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/queue_merge.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/reembed_backfill.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/retired_services.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/runtime_store.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/sandbox_db.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/scoping.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/scrub_at_rest.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/search_fanout.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/search_profile.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/search_repo.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/service_supervisor.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/session_repo.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/setup.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/socket_hygiene.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/source_class_migration.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/spotlight.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/storage.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/store.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/supervisor_child.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/supervisor_guard.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/supervisor_jobs.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/system_prompt_guard.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/t3_linkage_alarm.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/t3_provenance.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/t3_sources.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/tag_normalization.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/taxonomy.json +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/telemetry.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/timestamp_iso.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/types.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/vector_store.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/wal_checkpoint.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/watcher.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/src/brainlayer/watcher_bridge.py +0 -0
- {brainlayer-1.5.76 → brainlayer-1.5.77}/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.77
|
|
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.77",
|
|
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.77",
|
|
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.77"
|
|
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 = "54c580ca6849c2d32628f9ad38d924c54109f055"
|
|
@@ -2763,6 +2763,8 @@ def _verified_drive_file_ids(log_path: Path) -> set[str] | None:
|
|
|
2763
2763
|
return None
|
|
2764
2764
|
latest: dict[str, Mapping[str, Any]] = {}
|
|
2765
2765
|
for receipt in receipts:
|
|
2766
|
+
if receipt.get("event") == "local_sweep":
|
|
2767
|
+
continue
|
|
2766
2768
|
drive_file = receipt.get("drive_file")
|
|
2767
2769
|
file_id = drive_file.get("id") if isinstance(drive_file, dict) else None
|
|
2768
2770
|
if isinstance(file_id, str) and file_id:
|
|
@@ -2876,17 +2878,116 @@ def _recent_local_verified_backup(
|
|
|
2876
2878
|
return None
|
|
2877
2879
|
|
|
2878
2880
|
|
|
2879
|
-
def
|
|
2881
|
+
def _weekly_pin_protection(staging_dir: Path) -> tuple[set[str], str | None]:
|
|
2882
|
+
"""No snapshot-bound completion receipt exists; weekly custody expires the next day."""
|
|
2880
2883
|
pin = staging_dir / ".weekly-backup-pin.json"
|
|
2881
2884
|
try:
|
|
2882
2885
|
payload = json.loads(pin.read_text())
|
|
2883
|
-
if payload["date"] == local_backup_date()
|
|
2884
|
-
return {Path(payload["snapshot"]).name}
|
|
2886
|
+
if dt.date.fromisoformat(payload["date"]) == local_backup_date():
|
|
2887
|
+
return {Path(payload["snapshot"]).name}, "weekly_pin_pending"
|
|
2885
2888
|
except FileNotFoundError:
|
|
2886
2889
|
pass
|
|
2887
2890
|
except (OSError, ValueError, KeyError, TypeError):
|
|
2888
|
-
return {
|
|
2889
|
-
|
|
2891
|
+
return {
|
|
2892
|
+
p.name for p in staging_dir.iterdir() if _parse_snapshot_date(p.name) == local_backup_date()
|
|
2893
|
+
}, "pin_unreadable"
|
|
2894
|
+
return set(), None
|
|
2895
|
+
|
|
2896
|
+
|
|
2897
|
+
def _protected_snapshot_names(staging_dir: Path) -> set[str]:
|
|
2898
|
+
return _weekly_pin_protection(staging_dir)[0]
|
|
2899
|
+
|
|
2900
|
+
|
|
2901
|
+
def _archive_drive_md5_matches(receipt: Mapping[str, Any]) -> bool:
|
|
2902
|
+
"""Local deletion needs both recorded checksums, including Drive's stored-byte MD5."""
|
|
2903
|
+
drive = receipt.get("drive_file")
|
|
2904
|
+
local_md5 = receipt.get("local_md5")
|
|
2905
|
+
return (
|
|
2906
|
+
isinstance(local_md5, str)
|
|
2907
|
+
and bool(local_md5)
|
|
2908
|
+
and isinstance(drive, dict)
|
|
2909
|
+
and drive.get("md5Checksum") == local_md5
|
|
2910
|
+
)
|
|
2911
|
+
|
|
2912
|
+
|
|
2913
|
+
def _sweep_verified_local_archives(
|
|
2914
|
+
log_path: Path, staging_dir: Path, context: Mapping[str, Any], *, remove_local: bool
|
|
2915
|
+
) -> list[str]:
|
|
2916
|
+
"""Remove older archives only with latest, matching Drive proof, under the job's lock."""
|
|
2917
|
+
if not log_path.exists():
|
|
2918
|
+
return []
|
|
2919
|
+
try:
|
|
2920
|
+
receipts = _read_backup_receipts(log_path, strict=True)
|
|
2921
|
+
except (OSError, ValueError):
|
|
2922
|
+
print("Cannot validate Drive receipts; retaining local archives", flush=True)
|
|
2923
|
+
return []
|
|
2924
|
+
latest = {
|
|
2925
|
+
r["snapshot"]: r for r in receipts if r.get("event") != "local_sweep" and isinstance(r.get("snapshot"), str)
|
|
2926
|
+
}
|
|
2927
|
+
last_sweep = {
|
|
2928
|
+
r["archive"]: r for r in receipts if r.get("event") == "local_sweep" and isinstance(r.get("archive"), str)
|
|
2929
|
+
}
|
|
2930
|
+
verified_ids = _verified_drive_file_ids(log_path) or set()
|
|
2931
|
+
protected, pin_reason = _weekly_pin_protection(staging_dir)
|
|
2932
|
+
removed = []
|
|
2933
|
+
for receipt in latest.values():
|
|
2934
|
+
path = Path(receipt["snapshot"])
|
|
2935
|
+
day = _parse_snapshot_date(path.name)
|
|
2936
|
+
if (
|
|
2937
|
+
receipt.get("db") != context.get("db")
|
|
2938
|
+
or path.parent.resolve() != staging_dir.resolve()
|
|
2939
|
+
or day is None
|
|
2940
|
+
or path.name != f"{day.isoformat()}{_snapshot_suffix(path.name)}"
|
|
2941
|
+
or day >= local_backup_date()
|
|
2942
|
+
or path.is_symlink()
|
|
2943
|
+
or not path.is_file()
|
|
2944
|
+
or path.resolve() == Path(context["db"]).resolve()
|
|
2945
|
+
):
|
|
2946
|
+
continue
|
|
2947
|
+
drive = receipt.get("drive_file")
|
|
2948
|
+
verified = (
|
|
2949
|
+
receipt.get("uploaded") is True
|
|
2950
|
+
and receipt.get("verified") is True
|
|
2951
|
+
and isinstance(drive, dict)
|
|
2952
|
+
and isinstance(drive.get("id"), str)
|
|
2953
|
+
and drive.get("id") in verified_ids
|
|
2954
|
+
and drive.get("name") == path.name
|
|
2955
|
+
and str(drive.get("size")) == str(path.stat().st_size)
|
|
2956
|
+
)
|
|
2957
|
+
reason = (
|
|
2958
|
+
"not_verified"
|
|
2959
|
+
if not verified
|
|
2960
|
+
else "md5_unverified"
|
|
2961
|
+
if not _archive_drive_md5_matches(receipt)
|
|
2962
|
+
else "opt_out"
|
|
2963
|
+
if not remove_local
|
|
2964
|
+
else pin_reason
|
|
2965
|
+
if path.name in protected
|
|
2966
|
+
else None
|
|
2967
|
+
)
|
|
2968
|
+
if reason is None:
|
|
2969
|
+
try:
|
|
2970
|
+
if _md5_file(path) != drive["md5Checksum"]:
|
|
2971
|
+
reason = "md5_current_mismatch"
|
|
2972
|
+
else:
|
|
2973
|
+
path.unlink()
|
|
2974
|
+
except OSError as exc:
|
|
2975
|
+
print(f"Cannot delete local archive {path}: {exc}", flush=True)
|
|
2976
|
+
reason = "delete_failed"
|
|
2977
|
+
else:
|
|
2978
|
+
if reason is None:
|
|
2979
|
+
removed.append(path.name)
|
|
2980
|
+
# Local housekeeping cannot grant/revoke Drive verification or refresh last-backup status.
|
|
2981
|
+
update = {
|
|
2982
|
+
"event": "local_sweep",
|
|
2983
|
+
"archive": str(path),
|
|
2984
|
+
"at": dt.datetime.now(dt.UTC).isoformat(),
|
|
2985
|
+
"local_removed": reason is None,
|
|
2986
|
+
"local_removed_skip_reason": reason,
|
|
2987
|
+
}
|
|
2988
|
+
if reason is None or last_sweep.get(str(path), {}).get("local_removed_skip_reason") != reason:
|
|
2989
|
+
_append_json_log(log_path, update)
|
|
2990
|
+
return removed
|
|
2890
2991
|
|
|
2891
2992
|
|
|
2892
2993
|
def _reclaim_superseded_local(log_path: Path, result: dict[str, Any], staging_dir: Path) -> None:
|
|
@@ -3191,6 +3292,7 @@ def run_backup(
|
|
|
3191
3292
|
"db": str(resolved_db_path),
|
|
3192
3293
|
"uploaded": False,
|
|
3193
3294
|
"local_removed": False,
|
|
3295
|
+
"local_removed_skip_reason": "not_verified",
|
|
3194
3296
|
"verified": False,
|
|
3195
3297
|
"backup_log_provenance": _backup_log_provenance(),
|
|
3196
3298
|
"attempt_reclamation": "unknown",
|
|
@@ -3232,6 +3334,9 @@ def run_backup(
|
|
|
3232
3334
|
"machine_id": resolved_machine_id,
|
|
3233
3335
|
}
|
|
3234
3336
|
)
|
|
3337
|
+
result["local_gzip_retention_deleted"] = _sweep_verified_local_archives(
|
|
3338
|
+
resolved_log_path, Path(staging_dir), result, remove_local=remove_local_after_upload
|
|
3339
|
+
)
|
|
3235
3340
|
result["consumed_raw_crash_leftovers_deleted"] = _cleanup_consumed_raw_remnants(Path(staging_dir))
|
|
3236
3341
|
if resolved_log_path.exists():
|
|
3237
3342
|
try:
|
|
@@ -3533,6 +3638,16 @@ def run_backup(
|
|
|
3533
3638
|
)
|
|
3534
3639
|
result["upload_state"] = "verified" if result["verified"] else "failed"
|
|
3535
3640
|
if result["verified"]:
|
|
3641
|
+
protected, pin_reason = _weekly_pin_protection(snapshot.parent)
|
|
3642
|
+
result["local_removed_skip_reason"] = (
|
|
3643
|
+
"md5_unverified"
|
|
3644
|
+
if not _archive_drive_md5_matches(result)
|
|
3645
|
+
else "opt_out"
|
|
3646
|
+
if not remove_local_after_upload
|
|
3647
|
+
else pin_reason
|
|
3648
|
+
if snapshot.name in protected
|
|
3649
|
+
else None
|
|
3650
|
+
)
|
|
3536
3651
|
_append_json_log(resolved_log_path, result) # remote proof precedes reclamation
|
|
3537
3652
|
_reclaim_superseded_local(resolved_log_path, result, snapshot.parent)
|
|
3538
3653
|
from . import job_alerts
|
|
@@ -3543,23 +3658,16 @@ def run_backup(
|
|
|
3543
3658
|
result["temp_dirs_removed_after_verification"] = _prune_owned_temps_after_verified(
|
|
3544
3659
|
snapshot.parent, snapshot
|
|
3545
3660
|
)
|
|
3546
|
-
if
|
|
3547
|
-
|
|
3548
|
-
|
|
3661
|
+
if result["local_removed_skip_reason"] is None:
|
|
3662
|
+
try:
|
|
3663
|
+
snapshot.unlink()
|
|
3664
|
+
except OSError as exc:
|
|
3665
|
+
print(f"Cannot delete local archive {snapshot}: {exc}", flush=True)
|
|
3666
|
+
result["local_removed_skip_reason"] = "delete_failed"
|
|
3667
|
+
else:
|
|
3668
|
+
result["local_removed"] = True
|
|
3549
3669
|
if resume is None:
|
|
3550
3670
|
_remove_superseded_stalls(stalled, result, snapshot.name[:10])
|
|
3551
|
-
verified_drive_names = _verified_snapshot_names_from_log(
|
|
3552
|
-
resolved_log_path, db_path=Path(resolved_db_path)
|
|
3553
|
-
)
|
|
3554
|
-
verified_drive_names.add(snapshot.name)
|
|
3555
|
-
result["local_gzip_retention_deleted"] = prune_local_compressed_snapshots(
|
|
3556
|
-
snapshot.parent,
|
|
3557
|
-
verified_drive_names=verified_drive_names,
|
|
3558
|
-
protected_names=_protected_snapshot_names(snapshot.parent)
|
|
3559
|
-
| {
|
|
3560
|
-
name for name in _compressed_snapshot_names(snapshot.parent) if name not in verified_drive_names
|
|
3561
|
-
},
|
|
3562
|
-
)
|
|
3563
3671
|
if retention_enabled:
|
|
3564
3672
|
try:
|
|
3565
3673
|
result["retention_deleted"] = prune_drive_backups(
|
|
@@ -3620,6 +3728,13 @@ def run_backup(
|
|
|
3620
3728
|
result.setdefault("error_code", "drive_upload_interrupted")
|
|
3621
3729
|
if not nothing_pending:
|
|
3622
3730
|
_append_json_log(resolved_log_path, result)
|
|
3731
|
+
swept = _sweep_verified_local_archives(
|
|
3732
|
+
resolved_log_path, Path(staging_dir), result, remove_local=remove_local_after_upload
|
|
3733
|
+
)
|
|
3734
|
+
if swept:
|
|
3735
|
+
result["local_gzip_retention_deleted"].extend(swept)
|
|
3736
|
+
if not nothing_pending:
|
|
3737
|
+
_append_json_log(resolved_log_path, result)
|
|
3623
3738
|
return result
|
|
3624
3739
|
|
|
3625
3740
|
|
|
@@ -1415,23 +1415,24 @@ def _run_health_check_locked(
|
|
|
1415
1415
|
}
|
|
1416
1416
|
)
|
|
1417
1417
|
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
command_runner
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1418
|
+
from .supervisor_adapters import adapt_commands
|
|
1419
|
+
|
|
1420
|
+
command_runner = adapt_commands(
|
|
1421
|
+
command_runner,
|
|
1422
|
+
config.pause_sentinel_path,
|
|
1423
|
+
required=(
|
|
1424
|
+
config.watch_label,
|
|
1425
|
+
config.drain_label,
|
|
1426
|
+
config.hotlane_label,
|
|
1427
|
+
config.health_check_label,
|
|
1428
|
+
config.observability_label,
|
|
1429
|
+
),
|
|
1430
|
+
tolerate_unavailable=True,
|
|
1431
|
+
discover_ownership=True,
|
|
1432
|
+
)
|
|
1433
|
+
if error := getattr(command_runner, "control_error", ""):
|
|
1434
|
+
add_issue("supervisor_control_unavailable", "critical", f"supervisor control unavailable: {error}")
|
|
1435
|
+
if config.heal:
|
|
1435
1436
|
result.actions.append("heal_failed:supervisor_control_unavailable")
|
|
1436
1437
|
|
|
1437
1438
|
if state.get("_state_corrupt_now"):
|
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import json
|
|
4
4
|
import math
|
|
5
5
|
import os
|
|
6
|
+
import plistlib
|
|
6
7
|
import re
|
|
7
8
|
import subprocess
|
|
8
9
|
from datetime import UTC, datetime
|
|
@@ -301,6 +302,27 @@ def _unmeasurable(reason: str, inputs: list[dict[str, Any]]) -> dict[str, Any]:
|
|
|
301
302
|
return {"state": "unmeasurable", "reason": reason, "inputs": inputs}
|
|
302
303
|
|
|
303
304
|
|
|
305
|
+
def _maintenance_schedule_error(path: Path) -> str | None:
|
|
306
|
+
"""A loaded parent is usable schedule evidence only with a readable Hour/Minute calendar."""
|
|
307
|
+
try:
|
|
308
|
+
with path.open("rb") as handle:
|
|
309
|
+
definition = plistlib.load(handle)
|
|
310
|
+
except Exception as exc:
|
|
311
|
+
reason = f"cannot read plist ({type(exc).__name__})"
|
|
312
|
+
else:
|
|
313
|
+
interval = definition.get("StartCalendarInterval") if isinstance(definition, dict) else None
|
|
314
|
+
if (
|
|
315
|
+
isinstance(interval, dict)
|
|
316
|
+
and type(interval.get("Hour")) is int
|
|
317
|
+
and 0 <= interval["Hour"] <= 23
|
|
318
|
+
and type(interval.get("Minute")) is int
|
|
319
|
+
and 0 <= interval["Minute"] <= 59
|
|
320
|
+
):
|
|
321
|
+
return None
|
|
322
|
+
reason = "expected plist dictionary with StartCalendarInterval Hour (0-23) and Minute (0-59)"
|
|
323
|
+
return f"maintenance_schedule_unavailable: {path}: {reason}"
|
|
324
|
+
|
|
325
|
+
|
|
304
326
|
def build_backups_section(
|
|
305
327
|
*,
|
|
306
328
|
env: Mapping[str, str],
|
|
@@ -321,6 +343,7 @@ def build_backups_section(
|
|
|
321
343
|
launchd_override = env.get("BRAINLAYER_OBSERVABILITY_LAUNCHD_OUTPUT")
|
|
322
344
|
observed_label, extra_inputs = LABEL, []
|
|
323
345
|
launchd_not_loaded = False
|
|
346
|
+
schedule_error: str | None = None
|
|
324
347
|
command_failure: str | None = None
|
|
325
348
|
if launchd_override:
|
|
326
349
|
launchd_path = Path(launchd_override).expanduser()
|
|
@@ -368,6 +391,7 @@ def build_backups_section(
|
|
|
368
391
|
)
|
|
369
392
|
)
|
|
370
393
|
observed_label = "com.brainlayer.maintenance"
|
|
394
|
+
schedule_error = _maintenance_schedule_error(agents / f"{observed_label}.plist")
|
|
371
395
|
argv = ["launchctl", "print", f"gui/{os.getuid()}/{observed_label}"]
|
|
372
396
|
completed = subprocess.run(argv, capture_output=True, text=False, timeout=5, check=False, shell=False)
|
|
373
397
|
exit_code = completed.returncode
|
|
@@ -491,6 +515,11 @@ def build_backups_section(
|
|
|
491
515
|
if error_type is None and db_reason is not None:
|
|
492
516
|
error_type = db_reason
|
|
493
517
|
bootstrapped = not launchd_not_loaded and observed_label in launchd_text
|
|
518
|
+
if schedule_error is not None:
|
|
519
|
+
bootstrapped = False
|
|
520
|
+
# Schedule uncertainty must not discard dated receipts or mask an existing failure.
|
|
521
|
+
if not error_type:
|
|
522
|
+
error_type = schedule_error
|
|
494
523
|
|
|
495
524
|
# Optional evidence, never synthesized from a missing upload or a zero archive count.
|
|
496
525
|
evidence: dict[str, Any] = {}
|
|
@@ -50,6 +50,48 @@ ARCHIVED = {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
|
|
53
|
+
def supervisor_owns_jobs() -> bool:
|
|
54
|
+
"""Read the canonical migration owner with BrainBarJobOwnership's custody rules."""
|
|
55
|
+
path = Path.home() / ".local/share/brainlayer/supervisor/migration.json"
|
|
56
|
+
limit = 4 * 1024 * 1024
|
|
57
|
+
fd = os.open("/", os.O_RDONLY | os.O_DIRECTORY | os.O_CLOEXEC)
|
|
58
|
+
try:
|
|
59
|
+
parts = path.parts[1:]
|
|
60
|
+
for index, part in enumerate(parts):
|
|
61
|
+
if part in {".", ".."}:
|
|
62
|
+
raise ValueError("unsafe migration journal path")
|
|
63
|
+
flags = os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK | os.O_CLOEXEC
|
|
64
|
+
if index < len(parts) - 1:
|
|
65
|
+
flags |= os.O_DIRECTORY
|
|
66
|
+
next_fd = os.open(part, flags, dir_fd=fd)
|
|
67
|
+
os.close(fd)
|
|
68
|
+
fd = next_fd
|
|
69
|
+
info = os.fstat(fd)
|
|
70
|
+
if (
|
|
71
|
+
not stat.S_ISREG(info.st_mode)
|
|
72
|
+
or info.st_uid != os.getuid()
|
|
73
|
+
or info.st_nlink != 1
|
|
74
|
+
or info.st_mode & 0o077
|
|
75
|
+
or info.st_size > limit
|
|
76
|
+
):
|
|
77
|
+
raise PermissionError("migration journal is untrusted")
|
|
78
|
+
with os.fdopen(os.dup(fd), "rb") as stream:
|
|
79
|
+
raw = stream.read(limit + 1)
|
|
80
|
+
if len(raw) > limit:
|
|
81
|
+
raise ValueError("migration journal exceeds read limit")
|
|
82
|
+
row = json.loads(raw)
|
|
83
|
+
phase = row.get("phase") if isinstance(row, dict) else None
|
|
84
|
+
if isinstance(phase, str) and phase in {"migrating", "migrated", "reverting"}:
|
|
85
|
+
return True
|
|
86
|
+
if isinstance(phase, str) and phase in {"reverted", "rolled_back"}:
|
|
87
|
+
return False
|
|
88
|
+
raise ValueError("migration journal has unknown phase")
|
|
89
|
+
except FileNotFoundError:
|
|
90
|
+
return False
|
|
91
|
+
finally:
|
|
92
|
+
os.close(fd)
|
|
93
|
+
|
|
94
|
+
|
|
53
95
|
class SupervisorCommands:
|
|
54
96
|
"""Adapt legacy *policy*, never launch archived component plists.
|
|
55
97
|
|
|
@@ -99,6 +141,15 @@ class SupervisorCommands:
|
|
|
99
141
|
or type(value["exit_timeout"]) not in {int, float}
|
|
100
142
|
or not math.isfinite(value["exit_timeout"])
|
|
101
143
|
or value["exit_timeout"] < 0
|
|
144
|
+
or type(value.get("interval", 0)) not in {int, float}
|
|
145
|
+
or not math.isfinite(value.get("interval", 0))
|
|
146
|
+
or value.get("interval", 0) < 0
|
|
147
|
+
or value.get("next_run_at") is not None
|
|
148
|
+
and (
|
|
149
|
+
type(value["next_run_at"]) not in {int, float}
|
|
150
|
+
or not math.isfinite(value["next_run_at"])
|
|
151
|
+
or value["next_run_at"] < 0
|
|
152
|
+
)
|
|
102
153
|
or value["pid"] is not None
|
|
103
154
|
and (type(value["pid"]) is not int or value["pid"] <= 1)
|
|
104
155
|
):
|
|
@@ -168,15 +219,30 @@ class SupervisorCommands:
|
|
|
168
219
|
raise PermissionError("wrong launchd target")
|
|
169
220
|
if verb == "print":
|
|
170
221
|
state = states[name]
|
|
222
|
+
# An interval job remains loaded between runs, including a health
|
|
223
|
+
# check that exits 1 to report issues. A dead worker with no next
|
|
224
|
+
# scheduled run must remain unhealthy.
|
|
225
|
+
down = (
|
|
226
|
+
state["configured"]
|
|
227
|
+
and not state["paused"]
|
|
228
|
+
and not state["pid"]
|
|
229
|
+
and not (state.get("interval") and state.get("next_run_at") is not None)
|
|
230
|
+
)
|
|
171
231
|
return result(
|
|
232
|
+
code=113 if down else 0,
|
|
172
233
|
out=f"state = {'running' if state['pid'] else 'waiting'}\nruns = {state['starts']}\n"
|
|
173
|
-
+ (f"pid = {state['pid']}\n" if state["pid"] else "")
|
|
234
|
+
+ (f"pid = {state['pid']}\n" if state["pid"] else ""),
|
|
174
235
|
)
|
|
175
236
|
if verb == "bootstrap":
|
|
176
237
|
if args[2] != self.domain:
|
|
177
238
|
raise PermissionError("wrong launchd domain")
|
|
178
|
-
|
|
239
|
+
state = states[name]
|
|
240
|
+
# Resume only selects paused children. Heal an already-unpaused
|
|
241
|
+
# dead worker directly, without creating an owner-less pause.
|
|
242
|
+
action = "restart" if state["configured"] and not state["paused"] and not state["pid"] else "resume"
|
|
179
243
|
elif verb == "bootout":
|
|
244
|
+
if not states[name]["pid"]:
|
|
245
|
+
return result()
|
|
180
246
|
action = "pause"
|
|
181
247
|
elif verb == "kickstart":
|
|
182
248
|
action = "restart"
|
|
@@ -201,9 +267,18 @@ class SupervisorCommands:
|
|
|
201
267
|
return result(127, error=f"supervisor control failed: {exc}")
|
|
202
268
|
|
|
203
269
|
|
|
204
|
-
def adapt_commands(runner, pause_path=None, *, required=(), tolerate_unavailable=False):
|
|
270
|
+
def adapt_commands(runner, pause_path=None, *, required=(), tolerate_unavailable=False, discover_ownership=False):
|
|
205
271
|
path = os.environ.get("BRAINLAYER_SUPERVISOR_SOCKET")
|
|
206
|
-
if
|
|
272
|
+
if isinstance(runner, SupervisorCommands):
|
|
273
|
+
return runner
|
|
274
|
+
ownership_error = ""
|
|
275
|
+
if discover_ownership:
|
|
276
|
+
try:
|
|
277
|
+
if not supervisor_owns_jobs():
|
|
278
|
+
return runner
|
|
279
|
+
except (OSError, ValueError) as exc:
|
|
280
|
+
ownership_error = f"can't determine job owner: {exc}"
|
|
281
|
+
elif not path:
|
|
207
282
|
return runner
|
|
208
283
|
backend = SupervisorCommands(
|
|
209
284
|
supervisor_socket_path(),
|
|
@@ -211,6 +286,8 @@ def adapt_commands(runner, pause_path=None, *, required=(), tolerate_unavailable
|
|
|
211
286
|
pause_path=pause_path or Path(os.environ.get("BRAINLAYER_PAUSE_SENTINEL", str(DEFAULT_PAUSE_SENTINEL_PATH))),
|
|
212
287
|
)
|
|
213
288
|
try:
|
|
289
|
+
if ownership_error:
|
|
290
|
+
raise RuntimeError(ownership_error)
|
|
214
291
|
states = backend.request()
|
|
215
292
|
if any(backend.aliases[label] not in states for label in required if label in backend.aliases):
|
|
216
293
|
raise RuntimeError("required supervisor component is unavailable")
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
BUILD_SHA = "184525f889a9552f6f08a5f3fe499b41f3fbc6dc"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{brainlayer-1.5.76 → brainlayer-1.5.77}/scripts/launchd/com.brainlayer.hotlane-brainbar.plist
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{brainlayer-1.5.76 → brainlayer-1.5.77}/scripts/launchd/com.brainlayer.maintenance-nightly.plist
RENAMED
|
File without changes
|
{brainlayer-1.5.76 → brainlayer-1.5.77}/scripts/launchd/com.brainlayer.maintenance-weekly.plist
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{brainlayer-1.5.76 → brainlayer-1.5.77}/scripts/launchd/com.brainlayer.throughput-watchdog.plist
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{brainlayer-1.5.76 → brainlayer-1.5.77}/scripts/launchd/com.etanhey.brainlayer-fleet-watchdog.plist
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|