brainlayer 1.5.43__tar.gz → 1.5.45__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.43 → brainlayer-1.5.45}/.gitignore +2 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/PKG-INFO +10 -1
- {brainlayer-1.5.43 → brainlayer-1.5.45}/README.md +6 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/pyproject.toml +5 -1
- {brainlayer-1.5.43 → brainlayer-1.5.45}/scripts/launchd/install.sh +1 -1
- {brainlayer-1.5.43 → brainlayer-1.5.45}/server.json +2 -2
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/__init__.py +1 -1
- brainlayer-1.5.45/src/brainlayer/_build.py +1 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/backup_daily.py +68 -62
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/brainbar_hybrid_helper.py +14 -2
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/cli/__init__.py +41 -8
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/content_class.py +9 -7
- brainlayer-1.5.45/src/brainlayer/drive_credentials.py +313 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/eval/abcde_variants.yaml +26 -26
- brainlayer-1.5.45/src/brainlayer/eval/benchmark.py +406 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/eval/phoenix_gate/phoenix_client.py +3 -2
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/health_check.py +6 -0
- brainlayer-1.5.45/src/brainlayer/job_alerts.py +91 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/jsonl_backup.py +40 -1
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/kg_promotion.py +29 -3
- brainlayer-1.5.45/src/brainlayer/lexical_defense_dictionary.json +321 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/maintenance.py +272 -6
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/mcp/__init__.py +1 -1
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/mcp/search_handler.py +2 -2
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/observability_backup.py +9 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/batch_extraction.py +21 -13
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/enrichment.py +2 -2
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/entity_extraction.py +5 -5
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/kg_extraction_groq.py +2 -2
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/tag_entity_promotion.py +8 -12
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/runtime_store.py +21 -0
- brainlayer-1.5.43/src/brainlayer/_build.py +0 -1
- brainlayer-1.5.43/src/brainlayer/eval/benchmark.py +0 -469
- brainlayer-1.5.43/src/brainlayer/lexical_defense_dictionary.json +0 -246
- {brainlayer-1.5.43 → brainlayer-1.5.45}/LICENSE +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/scripts/hotlane_brainbar_daemon.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/scripts/launchd/backup-daily.sh +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/scripts/launchd/brainlayer-env-run.sh +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/scripts/launchd/brainlayer.env.example +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/scripts/launchd/com.brainlayer.backup-daily.plist +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/scripts/launchd/com.brainlayer.decay.plist +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/scripts/launchd/com.brainlayer.drain.plist +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/scripts/launchd/com.brainlayer.enrichment.plist +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/scripts/launchd/com.brainlayer.health-check.plist +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/scripts/launchd/com.brainlayer.hotlane-brainbar.plist +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/scripts/launchd/com.brainlayer.index.plist +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/scripts/launchd/com.brainlayer.jsonl-backup.plist +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/scripts/launchd/com.brainlayer.log-cap.plist +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/scripts/launchd/com.brainlayer.maintenance-nightly.plist +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/scripts/launchd/com.brainlayer.maintenance-weekly.plist +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/scripts/launchd/com.brainlayer.observability.plist +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/scripts/launchd/com.brainlayer.p0-counter.plist +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/scripts/launchd/com.brainlayer.repair-fts.plist +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/scripts/launchd/com.brainlayer.t3-ingest.plist +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/scripts/launchd/com.brainlayer.throughput-watchdog.plist +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/scripts/launchd/com.brainlayer.tier0-watchdog.plist +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/scripts/launchd/com.brainlayer.wal-checkpoint.plist +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/scripts/launchd/com.brainlayer.watch.plist +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/scripts/launchd/com.etanhey.brainlayer-fleet-watchdog.plist +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/scripts/launchd/fleet-watchdog.sh +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/scripts/launchd/jsonl-backup.sh +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/scripts/launchd/throughput-watchdog.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/scripts/release-verify-signatures.sh +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/scripts/tier0-watchdog.sh +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/__main__.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/_helpers.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/agent_profiles.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/agent_provenance.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/alarm.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/archive_collapse.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/archive_lifecycle.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/backup_retention_invariant.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/badge_state.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/bitemporal.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/cachedelete_purge.swift +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/calibrate.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/chunk_origin.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/chunk_origin_backfill.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/chunk_origin_wipe.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/chunk_write.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/classify.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/claude_paths.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/cli/wizard.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/cli_new.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/cloud_backfill.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/clustering.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/config.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/correction_judge.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/data/agents_registry.yaml +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/data/sandbox_seeds/skill-eval-baseline.json +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/db_shrink.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/decay.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/decay_backfill.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/decay_job.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/dedupe.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/dedupe_merge.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/deploy_drift.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/doctor.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/drain.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/drain_liveness.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/embeddings.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/engine.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/enrichment_controller.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/eval/__init__.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/eval/abcde_enrich_runner.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/eval/abcde_variants.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/eval/enrichment_gold.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/eval/enrichment_graders.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/eval/enrichment_judge.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/eval/enrichment_llm_judge.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/eval/enrichment_quality_benchmark.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/eval/experiment_store.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/eval/fixtures/README.md +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/eval/labeling/enrichment-labeler.html +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/eval/phoenix_gate/__init__.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/eval/phoenix_gate/__main__.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/eval/phoenix_gate/baseline_store.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/eval/phoenix_gate/cli.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/eval/phoenix_gate/models.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/eval/phoenix_gate/regression_gate.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/eval/phoenix_gate/triggers.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/fallback_replay.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/git_learning.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/hook_python.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/hooks/__init__.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/hooks/indexer.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/import_sweep.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/index_completeness.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/index_new.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/index_watchdog.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/ingest/__init__.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/ingest/codex.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/ingest/t3.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/ingest_denylist.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/ingest_guard.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/isolation_proof.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/job_lifecycle_health.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/jobs.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/kg/__init__.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/kg_cleanup.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/kg_judge.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/kg_repo.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/kg_review_session.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/kg_session_finish.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/kg_session_harvest.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/launchd_primitive.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/lexical_defense.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/log_cap.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/mcp/_format.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/mcp/_shared.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/mcp/enrich_handler.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/mcp/entity_handler.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/mcp/palette.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/mcp/store_handler.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/mcp/tags_handler.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/mcp_socket_config.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/mcp_stdio_bridge.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/memory_types.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/migrate.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/notification_policy.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/observability_surface.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/p0_longitudinal_count.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/parent_death.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/paths.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pause.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/phonetic.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/__init__.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/agent_enrichment.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/analyze_communication.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/brain_graph.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/chat_tags.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/chunk.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/classify.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/cloud_scrub.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/cluster_sampling.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/code_intelligence.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/correction_detection.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/correction_mining.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/digest.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/enrichment_tiers.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/entity_resolution.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/extract.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/extract_claude_desktop.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/extract_corrections.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/extract_markdown.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/extract_whatsapp.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/frustration_mining.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/git_overlay.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/groq.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/kg_extraction.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/longitudinal_analyzer.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/obsidian_export.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/operation_grouping.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/plan_linking.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/rate_limiter.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/relation_backfill.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/sanitize.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/secret_scrub.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/semantic_style.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/sentiment.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/session_enrichment.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/style_embed.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/style_index.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/temporal_chains.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/time_batcher.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/unified_timeline.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/pipeline/write_queue.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/provenance.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/provenance_autosupersede.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/provenance_integration.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/queue_io.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/queue_merge.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/reembed_backfill.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/sandbox_db.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/scoping.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/search_fanout.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/search_profile.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/search_repo.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/session_repo.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/setup.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/socket_hygiene.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/source_class_migration.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/spotlight.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/storage.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/store.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/system_prompt_guard.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/t3_provenance.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/tag_normalization.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/taxonomy.json +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/telemetry.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/timestamp_iso.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/types.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/vector_store.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/wal_checkpoint.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/watcher.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/src/brainlayer/watcher_bridge.py +0 -0
- {brainlayer-1.5.43 → brainlayer-1.5.45}/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.45
|
|
4
4
|
Summary: Persistent memory MCP server for AI agents — 17 tools, semantic search, knowledge graph, on-device SQLite
|
|
5
5
|
Project-URL: Homepage, https://brainlayer.etanheyman.com
|
|
6
6
|
Project-URL: Repository, https://github.com/EtanHey/brainlayer
|
|
@@ -23,6 +23,7 @@ Requires-Dist: abydos>=0.5.0
|
|
|
23
23
|
Requires-Dist: apsw>=3.45.0
|
|
24
24
|
Requires-Dist: deprecation>=2.1.0
|
|
25
25
|
Requires-Dist: google-api-python-client>=2.0.0
|
|
26
|
+
Requires-Dist: google-auth-oauthlib>=1.1.0
|
|
26
27
|
Requires-Dist: google-auth>=2.0.0
|
|
27
28
|
Requires-Dist: idna<4,>=2.5
|
|
28
29
|
Requires-Dist: jsonschema>=4.20.0
|
|
@@ -53,10 +54,12 @@ Requires-Dist: deepchecks>=0.19.1; extra == 'dev'
|
|
|
53
54
|
Requires-Dist: google-genai>=1.0.0; extra == 'dev'
|
|
54
55
|
Requires-Dist: numpy<2; extra == 'dev'
|
|
55
56
|
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
|
|
57
|
+
Requires-Dist: pytest-timeout>=2.3.1; extra == 'dev'
|
|
56
58
|
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
57
59
|
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
58
60
|
Requires-Dist: scikit-learn<1.6; extra == 'dev'
|
|
59
61
|
Requires-Dist: setuptools<81,>=70.0.0; extra == 'dev'
|
|
62
|
+
Requires-Dist: threadpoolctl>=3.0.0; extra == 'dev'
|
|
60
63
|
Requires-Dist: tiktoken>=0.7.0; extra == 'dev'
|
|
61
64
|
Requires-Dist: umap-learn>=0.5.0; extra == 'dev'
|
|
62
65
|
Provides-Extra: docs
|
|
@@ -368,6 +371,12 @@ BRAINLAYER_ENRICH_BACKEND=groq brainlayer enrich # Force Groq
|
|
|
368
371
|
|
|
369
372
|
## CLI Reference
|
|
370
373
|
|
|
374
|
+
### Backups
|
|
375
|
+
|
|
376
|
+
Run `brainlayer backup auth` once to authorize BrainLayer's Drive backups in your browser.
|
|
377
|
+
The private token lives at `~/.config/brainlayer/drive-tokens.json`; `BRAINLAYER_DRIVE_TOKEN_PATH` overrides it.
|
|
378
|
+
BrainBar uses `brainlayer backup auth --status --json` to check and `brainlayer backup auth --json` to authorize.
|
|
379
|
+
|
|
371
380
|
```bash
|
|
372
381
|
brainlayer setup # Create ~/.config/brainlayer/brainlayer.env
|
|
373
382
|
brainlayer setup --launchd # Create config and install launchd agents
|
|
@@ -293,6 +293,12 @@ BRAINLAYER_ENRICH_BACKEND=groq brainlayer enrich # Force Groq
|
|
|
293
293
|
|
|
294
294
|
## CLI Reference
|
|
295
295
|
|
|
296
|
+
### Backups
|
|
297
|
+
|
|
298
|
+
Run `brainlayer backup auth` once to authorize BrainLayer's Drive backups in your browser.
|
|
299
|
+
The private token lives at `~/.config/brainlayer/drive-tokens.json`; `BRAINLAYER_DRIVE_TOKEN_PATH` overrides it.
|
|
300
|
+
BrainBar uses `brainlayer backup auth --status --json` to check and `brainlayer backup auth --json` to authorize.
|
|
301
|
+
|
|
296
302
|
```bash
|
|
297
303
|
brainlayer setup # Create ~/.config/brainlayer/brainlayer.env
|
|
298
304
|
brainlayer setup --launchd # Create config and install launchd agents
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "brainlayer"
|
|
3
|
-
version = "1.5.
|
|
3
|
+
version = "1.5.45"
|
|
4
4
|
description = "Persistent memory MCP server for AI agents — 17 tools, semantic search, knowledge graph, on-device SQLite"
|
|
5
5
|
license = {text = "Apache-2.0"}
|
|
6
6
|
readme = "README.md"
|
|
@@ -34,6 +34,7 @@ dependencies = [
|
|
|
34
34
|
"idna>=2.5,<4", # Imported through requests by backup and hook entrypoints
|
|
35
35
|
"google-api-python-client>=2.0.0", # Daily DB backup upload to Google Drive
|
|
36
36
|
"google-auth>=2.0.0", # OAuth refresh for Google Drive backups
|
|
37
|
+
"google-auth-oauthlib>=1.1.0", # Installed-app Drive consent for BrainLayer-owned backup tokens
|
|
37
38
|
"ranx>=0.3.20", # IR evaluation metrics + significance testing
|
|
38
39
|
"abydos>=0.5.0", # Beider-Morse phonetic matching for cross-script entity aliases
|
|
39
40
|
"deprecation>=2.1.0", # Imported by abydos phonetic modules used by prompt hooks
|
|
@@ -82,10 +83,12 @@ dev = [
|
|
|
82
83
|
"numpy<2",
|
|
83
84
|
"pytest>=7.0.0",
|
|
84
85
|
"pytest-asyncio>=0.21.0",
|
|
86
|
+
"pytest-timeout>=2.3.1",
|
|
85
87
|
"ruff>=0.1.0",
|
|
86
88
|
"scikit-learn<1.6",
|
|
87
89
|
"setuptools>=70.0.0,<81",
|
|
88
90
|
"tiktoken>=0.7.0",
|
|
91
|
+
"threadpoolctl>=3.0.0",
|
|
89
92
|
"umap-learn>=0.5.0",
|
|
90
93
|
]
|
|
91
94
|
docs = [
|
|
@@ -121,6 +124,7 @@ pythonpath = ["src"]
|
|
|
121
124
|
markers = [
|
|
122
125
|
"engine: pure-library engine tests (excludes CLI, dashboard, BrainBar, launchd, and root orchestration surfaces)",
|
|
123
126
|
"embedding_model: tests that deliberately load a real embedding model (deselected by scripts/run_tests.sh)",
|
|
127
|
+
"timeout: maximum duration for an individual test",
|
|
124
128
|
"integration: tests that need the real production DB (268K+ chunks)",
|
|
125
129
|
"live: tests that need the live production DB or mutable local hooks state",
|
|
126
130
|
"slow: tests that load ML models or take >30s",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
# ./scripts/launchd/install.sh load enrichment
|
|
12
12
|
# ./scripts/launchd/install.sh unload enrichment
|
|
13
13
|
# ./scripts/launchd/install.sh checkpoint # Install WAL checkpoint only
|
|
14
|
-
# ./scripts/launchd/install.sh repair-fts # Install weekly
|
|
14
|
+
# ./scripts/launchd/install.sh repair-fts # Install weekly read-only FTS check
|
|
15
15
|
# ./scripts/launchd/install.sh backup # Install daily DB backup only
|
|
16
16
|
# ./scripts/launchd/install.sh jsonl-backup # Install daily JSONL backup only
|
|
17
17
|
# ./scripts/launchd/install.sh maintenance # Install recurring maintenance jobs
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "io.github.etanhey/brainlayer",
|
|
4
4
|
"title": "BrainLayer",
|
|
5
5
|
"description": "Persistent memory for AI agents \u2014 local-first semantic search, knowledge graph, and 17 MCP tools with ToolAnnotations. One SQLite file, no cloud, no API keys.",
|
|
6
|
-
"version": "1.5.
|
|
6
|
+
"version": "1.5.45",
|
|
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.45",
|
|
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.45"
|
|
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 = "d3d00b64768a53e7c534ee82dc9f9938aab65f81"
|
|
@@ -36,13 +36,14 @@ from typing import Any
|
|
|
36
36
|
|
|
37
37
|
import requests
|
|
38
38
|
|
|
39
|
+
from . import drive_credentials
|
|
39
40
|
from .paths import get_db_path
|
|
40
41
|
from .socket_hygiene import refuse_production_brainbar_socket
|
|
41
42
|
|
|
42
43
|
_sleep = time.sleep
|
|
43
44
|
|
|
44
|
-
DEFAULT_TOKEN_PATH =
|
|
45
|
-
DEFAULT_CLIENT_PATH =
|
|
45
|
+
DEFAULT_TOKEN_PATH = drive_credentials.DEFAULT_TOKEN_PATH
|
|
46
|
+
DEFAULT_CLIENT_PATH = drive_credentials.DEFAULT_CLIENT_PATH
|
|
46
47
|
CANONICAL_MACHINE_ID = "MacBook-Pro"
|
|
47
48
|
CANONICAL_FOLDER_PARTS = ["Brain Drive", "06_ARCHIVE", "backups", "brainlayer-db"]
|
|
48
49
|
# Compatibility alias for callers that explicitly target the legacy M4 folder.
|
|
@@ -61,6 +62,7 @@ BACKUP_ATTEMPT_MAX_AGE_ENV = "BRAINLAYER_BACKUP_ATTEMPT_MAX_AGE_SECONDS"
|
|
|
61
62
|
BACKUP_FULL_VERIFY_ENV = "BRAINLAYER_BACKUP_FULL_VERIFY"
|
|
62
63
|
BACKUP_LOG_PATH_ENV = "BRAINLAYER_BACKUP_LOG_PATH"
|
|
63
64
|
BACKUP_LOG_PROVENANCE_ENV = "BRAINLAYER_BACKUP_LOG_PROVENANCE"
|
|
65
|
+
BACKUP_REUSE_VERIFIED_MAX_AGE_HOURS_ENV = "BRAINLAYER_BACKUP_REUSE_VERIFIED_MAX_AGE_HOURS"
|
|
64
66
|
BACKUP_SUPERVISED_CHILD_ENV = "BRAINLAYER_BACKUP_SUPERVISED_CHILD"
|
|
65
67
|
BACKUP_SQLITE_CHECK_TIMEOUT_ENV = "BRAINLAYER_BACKUP_SQLITE_CHECK_TIMEOUT_SECONDS"
|
|
66
68
|
BACKUP_DRIVE_RETENTION_ENV = "BRAINLAYER_BACKUP_DRIVE_RETENTION"
|
|
@@ -68,7 +70,7 @@ DRIVE_UPLOAD_DEADLINE_FLOOR_ENV = "BRAINLAYER_DRIVE_UPLOAD_DEADLINE_FLOOR_SECOND
|
|
|
68
70
|
DRIVE_UPLOAD_MIN_BYTES_PER_SECOND_ENV = "BRAINLAYER_DRIVE_UPLOAD_MIN_BYTES_PER_SECOND"
|
|
69
71
|
DRIVE_UPLOAD_STALL_MAX_ATTEMPTS_ENV = "BRAINLAYER_DRIVE_UPLOAD_STALL_MAX_ATTEMPTS"
|
|
70
72
|
DRIVE_FOLDER_MIME = "application/vnd.google-apps.folder"
|
|
71
|
-
DRIVE_SCOPES =
|
|
73
|
+
DRIVE_SCOPES = drive_credentials.DRIVE_SCOPES
|
|
72
74
|
DEFAULT_LOCAL_COMPRESSED_KEEP = 3
|
|
73
75
|
DEFAULT_LOCAL_UNCOMPRESSED_KEEP = 1
|
|
74
76
|
DEFAULT_DRIVE_KEEP = 7
|
|
@@ -101,6 +103,10 @@ DAILY_RETENTION = DriveRetentionPolicy(keep_latest=DEFAULT_DRIVE_KEEP)
|
|
|
101
103
|
WEEKLY_RETENTION = DriveRetentionPolicy(keep_latest=DEFAULT_DRIVE_KEEP)
|
|
102
104
|
|
|
103
105
|
|
|
106
|
+
class BackupAlreadyRunningError(RuntimeError):
|
|
107
|
+
"""The shared backup staging lock is held by another backup."""
|
|
108
|
+
|
|
109
|
+
|
|
104
110
|
@dataclass(frozen=True)
|
|
105
111
|
class SQLiteBackupArtifact:
|
|
106
112
|
gzip_path: Path
|
|
@@ -981,65 +987,12 @@ def _send_brainbar_json_request(
|
|
|
981
987
|
return json.loads(data.decode("utf-8"))
|
|
982
988
|
|
|
983
989
|
|
|
984
|
-
def
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
temp_path.write_text(content)
|
|
988
|
-
os.replace(temp_path, path)
|
|
989
|
-
finally:
|
|
990
|
-
temp_path.unlink(missing_ok=True)
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
def get_drive_credentials(token_path: Path = DEFAULT_TOKEN_PATH, client_path: Path = DEFAULT_CLIENT_PATH):
|
|
994
|
-
"""Load and refresh Google Drive OAuth credentials from the existing MCP auth files."""
|
|
995
|
-
from google.auth.transport.requests import Request
|
|
996
|
-
from google.oauth2.credentials import Credentials
|
|
997
|
-
|
|
998
|
-
token_path = Path(token_path).expanduser()
|
|
999
|
-
client_path = Path(client_path).expanduser()
|
|
1000
|
-
if not token_path.exists():
|
|
1001
|
-
raise FileNotFoundError(f"Google Drive token file not found: {token_path}")
|
|
1002
|
-
if not client_path.exists():
|
|
1003
|
-
raise FileNotFoundError(f"Google OAuth client file not found: {client_path}")
|
|
1004
|
-
|
|
1005
|
-
lock_path = token_path.with_suffix(token_path.suffix + ".lock")
|
|
1006
|
-
with lock_path.open("w") as lock_file:
|
|
1007
|
-
fcntl.flock(lock_file, fcntl.LOCK_EX)
|
|
1008
|
-
token_data = json.loads(token_path.read_text())
|
|
1009
|
-
client_data = json.loads(client_path.read_text())["installed"]
|
|
1010
|
-
|
|
1011
|
-
expiry = token_data.get("expiry")
|
|
1012
|
-
if not expiry and token_data.get("expiry_date"):
|
|
1013
|
-
expiry = dt.datetime.fromtimestamp(int(token_data["expiry_date"]) / 1000, tz=dt.UTC).isoformat()
|
|
1014
|
-
|
|
1015
|
-
parsed_expiry = dt.datetime.fromisoformat(expiry.replace("Z", "+00:00")) if expiry else None
|
|
1016
|
-
if parsed_expiry and parsed_expiry.tzinfo:
|
|
1017
|
-
parsed_expiry = parsed_expiry.astimezone(dt.UTC).replace(tzinfo=None)
|
|
1018
|
-
elif parsed_expiry:
|
|
1019
|
-
parsed_expiry = parsed_expiry.replace(tzinfo=None)
|
|
1020
|
-
|
|
1021
|
-
creds = Credentials(
|
|
1022
|
-
token=token_data.get("access_token"),
|
|
1023
|
-
refresh_token=token_data.get("refresh_token"),
|
|
1024
|
-
token_uri=client_data["token_uri"],
|
|
1025
|
-
client_id=client_data["client_id"],
|
|
1026
|
-
client_secret=client_data["client_secret"],
|
|
1027
|
-
scopes=token_data.get("scope", " ".join(DRIVE_SCOPES)).split(),
|
|
1028
|
-
expiry=parsed_expiry,
|
|
1029
|
-
)
|
|
1030
|
-
|
|
1031
|
-
# google-auth Credentials.expired compares against a naive UTC helper, so keep expiry comparisons naive UTC.
|
|
1032
|
-
refresh_before = dt.datetime.now(dt.UTC).replace(tzinfo=None) + dt.timedelta(hours=2)
|
|
1033
|
-
if creds.expired or not creds.valid or (creds.expiry and creds.expiry < refresh_before):
|
|
1034
|
-
creds.refresh(Request())
|
|
1035
|
-
token_data["access_token"] = creds.token
|
|
1036
|
-
token_data["expiry"] = creds.expiry.isoformat() if creds.expiry else None
|
|
1037
|
-
_atomic_write_text(token_path, json.dumps(token_data, indent=2, sort_keys=True) + "\n")
|
|
1038
|
-
|
|
1039
|
-
return creds
|
|
990
|
+
def get_drive_credentials(token_path: Path | None = None, client_path: Path | None = None):
|
|
991
|
+
"""Use BrainLayer-owned credentials for both database and JSONL backups."""
|
|
992
|
+
return drive_credentials.load_credentials(token_path=token_path, client_path=client_path)
|
|
1040
993
|
|
|
1041
994
|
|
|
1042
|
-
def build_drive_service(token_path: Path =
|
|
995
|
+
def build_drive_service(token_path: Path | None = None, client_path: Path | None = None):
|
|
1043
996
|
from googleapiclient.discovery import build
|
|
1044
997
|
|
|
1045
998
|
return build("drive", "v3", credentials=get_drive_credentials(token_path, client_path))
|
|
@@ -1568,6 +1521,37 @@ def prune_drive_backups(
|
|
|
1568
1521
|
return trashed
|
|
1569
1522
|
|
|
1570
1523
|
|
|
1524
|
+
def _recent_verified_backup_for_reuse(
|
|
1525
|
+
log_path: Path, db_path: Path, *, now: dt.datetime, max_age_hours: float
|
|
1526
|
+
) -> dict[str, Any] | None:
|
|
1527
|
+
try:
|
|
1528
|
+
lines = log_path.read_text(encoding="utf-8").splitlines()
|
|
1529
|
+
except FileNotFoundError:
|
|
1530
|
+
return None
|
|
1531
|
+
now = now.astimezone(dt.UTC)
|
|
1532
|
+
for line in reversed(lines):
|
|
1533
|
+
try:
|
|
1534
|
+
receipt = json.loads(line)
|
|
1535
|
+
attempted = dt.datetime.fromisoformat(receipt["attempted_at"])
|
|
1536
|
+
except (ValueError, KeyError, TypeError):
|
|
1537
|
+
continue
|
|
1538
|
+
if attempted.tzinfo is None:
|
|
1539
|
+
continue
|
|
1540
|
+
age = now - attempted.astimezone(dt.UTC)
|
|
1541
|
+
drive_file = receipt.get("drive_file")
|
|
1542
|
+
if (
|
|
1543
|
+
receipt.get("db") == str(db_path)
|
|
1544
|
+
and receipt.get("uploaded") is True
|
|
1545
|
+
and receipt.get("verified") is True
|
|
1546
|
+
and "error_type" not in receipt
|
|
1547
|
+
and isinstance(drive_file, dict)
|
|
1548
|
+
and isinstance(drive_file.get("id"), str)
|
|
1549
|
+
and dt.timedelta(0) <= age <= dt.timedelta(hours=max_age_hours)
|
|
1550
|
+
):
|
|
1551
|
+
return receipt
|
|
1552
|
+
return None
|
|
1553
|
+
|
|
1554
|
+
|
|
1571
1555
|
def _serialized_backup_run(func: Callable[..., dict[str, Any]]) -> Callable[..., dict[str, Any]]:
|
|
1572
1556
|
@functools.wraps(func)
|
|
1573
1557
|
def wrapped(*args: Any, **kwargs: Any) -> dict[str, Any]:
|
|
@@ -1592,8 +1576,22 @@ def _serialized_backup_run(func: Callable[..., dict[str, Any]]) -> Callable[...,
|
|
|
1592
1576
|
"error": error,
|
|
1593
1577
|
},
|
|
1594
1578
|
)
|
|
1595
|
-
raise
|
|
1579
|
+
raise BackupAlreadyRunningError(error) from exc
|
|
1596
1580
|
try:
|
|
1581
|
+
# Weekly maintenance's first receipt check precedes this lock. A daily
|
|
1582
|
+
# backup can finish in that gap, so recheck before copying the DB.
|
|
1583
|
+
reuse_age = os.environ.get(BACKUP_REUSE_VERIFIED_MAX_AGE_HOURS_ENV)
|
|
1584
|
+
if reuse_age is not None:
|
|
1585
|
+
hours = float(reuse_age)
|
|
1586
|
+
if not math.isfinite(hours) or hours < 0:
|
|
1587
|
+
raise ValueError(f"{BACKUP_REUSE_VERIFIED_MAX_AGE_HOURS_ENV} must be finite and nonnegative")
|
|
1588
|
+
db_path = Path(bound.arguments.get("db_path") or get_db_path())
|
|
1589
|
+
log_path = _backup_log_path(bound.arguments.get("log_path"), db_path=db_path)
|
|
1590
|
+
receipt = _recent_verified_backup_for_reuse(
|
|
1591
|
+
log_path, db_path, now=dt.datetime.now(dt.UTC), max_age_hours=hours
|
|
1592
|
+
)
|
|
1593
|
+
if receipt is not None:
|
|
1594
|
+
return receipt
|
|
1597
1595
|
return func(*args, **kwargs)
|
|
1598
1596
|
finally:
|
|
1599
1597
|
fcntl.flock(lock, fcntl.LOCK_UN)
|
|
@@ -1904,7 +1902,15 @@ def main() -> int:
|
|
|
1904
1902
|
timeout_seconds = _configured_backup_timeout_seconds()
|
|
1905
1903
|
if _env_flag_enabled(BACKUP_SUPERVISED_CHILD_ENV):
|
|
1906
1904
|
return _run_backup_process(timeout_seconds)
|
|
1907
|
-
|
|
1905
|
+
from .job_alerts import report
|
|
1906
|
+
|
|
1907
|
+
try:
|
|
1908
|
+
code = _supervise_backup_process(timeout_seconds)
|
|
1909
|
+
except Exception:
|
|
1910
|
+
report("backup-daily", "Database backup failed; check the backup log")
|
|
1911
|
+
raise
|
|
1912
|
+
report("backup-daily", None if code == 0 else "Database backup failed; check the backup log")
|
|
1913
|
+
return code
|
|
1908
1914
|
|
|
1909
1915
|
|
|
1910
1916
|
if __name__ == "__main__":
|
|
@@ -13,6 +13,7 @@ import hashlib
|
|
|
13
13
|
import json
|
|
14
14
|
import logging
|
|
15
15
|
import os
|
|
16
|
+
import re
|
|
16
17
|
import signal
|
|
17
18
|
import socket
|
|
18
19
|
import sys
|
|
@@ -23,6 +24,9 @@ from typing import Any, Callable
|
|
|
23
24
|
|
|
24
25
|
from . import search_profile
|
|
25
26
|
|
|
27
|
+
# A profile id is only ever the shape search_profile.new_query_id() (and BrainBar's
|
|
28
|
+
# SearchProfileLogger.newQueryID()) generates; anything else is client-chosen text.
|
|
29
|
+
_PROFILE_QUERY_ID_RE = re.compile(r"q-[0-9A-Fa-f]{12}")
|
|
26
30
|
_ACCEPT_TIMEOUT_SECONDS = 0.25
|
|
27
31
|
_CONNECTION_TIMEOUT_SECONDS = 5.0
|
|
28
32
|
|
|
@@ -217,7 +221,9 @@ class HybridSearchHelper:
|
|
|
217
221
|
request = json.loads(raw.decode("utf-8"))
|
|
218
222
|
response = self._dispatch_with_deadline(request)
|
|
219
223
|
except Exception as exc:
|
|
220
|
-
|
|
224
|
+
# The class name only: BrainBar logs this field, and an exception message
|
|
225
|
+
# can carry the query or file contents (PR #993 review, B3).
|
|
226
|
+
response = {"ok": False, "error": type(exc).__name__}
|
|
221
227
|
|
|
222
228
|
payload = json.dumps(_json_safe(response), separators=(",", ":")).encode("utf-8") + b"\n"
|
|
223
229
|
try:
|
|
@@ -225,6 +231,12 @@ class HybridSearchHelper:
|
|
|
225
231
|
except OSError:
|
|
226
232
|
return
|
|
227
233
|
|
|
234
|
+
@staticmethod
|
|
235
|
+
def _accepted_profile_query_id(raw: object) -> str | None:
|
|
236
|
+
if isinstance(raw, str) and _PROFILE_QUERY_ID_RE.fullmatch(raw):
|
|
237
|
+
return raw
|
|
238
|
+
return None
|
|
239
|
+
|
|
228
240
|
@staticmethod
|
|
229
241
|
def _read_line(conn: socket.socket) -> bytes:
|
|
230
242
|
chunks: list[bytes] = []
|
|
@@ -269,7 +281,7 @@ class HybridSearchHelper:
|
|
|
269
281
|
async def _search(self, arguments: dict[str, Any]) -> tuple[str, dict[str, Any] | None, bool]:
|
|
270
282
|
from brainlayer.mcp.search_handler import _brain_search
|
|
271
283
|
|
|
272
|
-
query_id =
|
|
284
|
+
query_id = self._accepted_profile_query_id(arguments.get("_profile_query_id"))
|
|
273
285
|
if search_profile.enabled() and query_id is None:
|
|
274
286
|
query_id = search_profile.new_query_id()
|
|
275
287
|
source = arguments.get("source")
|
|
@@ -50,6 +50,25 @@ sandbox_app = typer.Typer(help="Manage isolated sandbox BrainLayer databases")
|
|
|
50
50
|
app.add_typer(sandbox_app, name="sandbox")
|
|
51
51
|
jobs_app = typer.Typer(help="Inspect and restart installed BrainLayer LaunchAgents")
|
|
52
52
|
app.add_typer(jobs_app, name="jobs")
|
|
53
|
+
backup_app = typer.Typer(help="Manage BrainLayer backup authorization")
|
|
54
|
+
app.add_typer(backup_app, name="backup")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@backup_app.command("auth")
|
|
58
|
+
def backup_auth_command(
|
|
59
|
+
status: bool = typer.Option(False, "--status", help="Check authorization without changing the token."),
|
|
60
|
+
json_output: bool = typer.Option(False, "--json", help="Print one JSON result for BrainBar."),
|
|
61
|
+
) -> None:
|
|
62
|
+
"""Authorize the BrainLayer-owned Google Drive token in the system browser."""
|
|
63
|
+
from .. import drive_credentials
|
|
64
|
+
|
|
65
|
+
result = drive_credentials.credential_status() if status else drive_credentials.authorize()
|
|
66
|
+
if json_output:
|
|
67
|
+
typer.echo(json.dumps(result, sort_keys=True))
|
|
68
|
+
else:
|
|
69
|
+
typer.echo(result["reason"])
|
|
70
|
+
if not status and result["status"] != "ok":
|
|
71
|
+
raise typer.Exit(1)
|
|
53
72
|
|
|
54
73
|
|
|
55
74
|
@jobs_app.command("restart")
|
|
@@ -2804,6 +2823,7 @@ def wal_checkpoint(
|
|
|
2804
2823
|
quiet: bool = typer.Option(False, "--quiet", help="Silent unless error"),
|
|
2805
2824
|
) -> None:
|
|
2806
2825
|
"""Run a SQLite WAL checkpoint through the BrainLayer CLI."""
|
|
2826
|
+
from ..job_alerts import report as report_job_alert
|
|
2807
2827
|
from ..wal_checkpoint import run_wal_checkpoint
|
|
2808
2828
|
|
|
2809
2829
|
normalized_mode = mode.upper()
|
|
@@ -2815,12 +2835,14 @@ def wal_checkpoint(
|
|
|
2815
2835
|
run_wal_checkpoint(normalized_mode, retry_busy=True) if retry_busy else run_wal_checkpoint(normalized_mode)
|
|
2816
2836
|
)
|
|
2817
2837
|
except FileNotFoundError as e:
|
|
2838
|
+
report_job_alert("wal-checkpoint", "BrainLayer WAL checkpoint failed; check the job log")
|
|
2818
2839
|
if json_output:
|
|
2819
2840
|
console.print_json(data={"error": str(e)})
|
|
2820
2841
|
elif not quiet:
|
|
2821
2842
|
rprint(f"[bold red]Error:[/] {e}")
|
|
2822
2843
|
raise typer.Exit(1)
|
|
2823
2844
|
except Exception as e:
|
|
2845
|
+
report_job_alert("wal-checkpoint", "BrainLayer WAL checkpoint failed; check the job log")
|
|
2824
2846
|
if json_output:
|
|
2825
2847
|
console.print_json(data={"error": str(e)})
|
|
2826
2848
|
elif not quiet:
|
|
@@ -2838,25 +2860,36 @@ def wal_checkpoint(
|
|
|
2838
2860
|
)
|
|
2839
2861
|
|
|
2840
2862
|
if result["busy"]:
|
|
2863
|
+
report_job_alert("wal-checkpoint", "BrainLayer WAL checkpoint was busy; check the job log")
|
|
2841
2864
|
raise typer.Exit(1)
|
|
2865
|
+
report_job_alert("wal-checkpoint", None)
|
|
2842
2866
|
|
|
2843
2867
|
|
|
2844
2868
|
@app.command("repair-fts")
|
|
2845
2869
|
def repair_fts(
|
|
2846
|
-
db_path: Path = typer.Argument(
|
|
2870
|
+
db_path: Path | None = typer.Argument(None, help="Offline database copy; omitted means read-only FTS count check."),
|
|
2847
2871
|
) -> None:
|
|
2848
|
-
"""
|
|
2849
|
-
from ..
|
|
2872
|
+
"""Check live FTS counts read-only, or rebuild an explicit offline copy."""
|
|
2873
|
+
from ..job_alerts import report as report_job_alert
|
|
2874
|
+
from ..runtime_store import OfflineMigrator, check_fts_counts
|
|
2850
2875
|
|
|
2851
2876
|
try:
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2877
|
+
if db_path is None:
|
|
2878
|
+
result = check_fts_counts(get_db_path())
|
|
2879
|
+
else:
|
|
2880
|
+
store = OfflineMigrator(db_path)
|
|
2881
|
+
try:
|
|
2882
|
+
result = store.repair_fts(rebuild_trigram=True)
|
|
2883
|
+
finally:
|
|
2884
|
+
store.close()
|
|
2857
2885
|
except PermissionError as exc:
|
|
2886
|
+
report_job_alert("repair-fts", "BrainLayer FTS repair failed; check the job log")
|
|
2858
2887
|
rprint(f"[bold red]Repair refused:[/] {exc}")
|
|
2859
2888
|
raise typer.Exit(1) from exc
|
|
2889
|
+
except Exception:
|
|
2890
|
+
report_job_alert("repair-fts", "BrainLayer FTS repair failed; check the job log")
|
|
2891
|
+
raise
|
|
2892
|
+
report_job_alert("repair-fts", None)
|
|
2860
2893
|
console.print_json(data=result)
|
|
2861
2894
|
|
|
2862
2895
|
|
|
@@ -68,12 +68,14 @@ _PERSONAL_RE = re.compile(
|
|
|
68
68
|
r")",
|
|
69
69
|
re.IGNORECASE,
|
|
70
70
|
)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
re.IGNORECASE,
|
|
71
|
+
_NAME_WORD = r"[A-Z][a-z]{1,30}(?:[-'][A-Z][a-z]{1,30})?"
|
|
72
|
+
_CONTEXTUAL_PERSON_NAME_RE = re.compile(
|
|
73
|
+
rf"\b(?i:met|spoke with|talked to|called|contacted|introduced|client|friend|person named)\s+"
|
|
74
|
+
rf"{_NAME_WORD}\s+{_NAME_WORD}\b"
|
|
76
75
|
)
|
|
76
|
+
_EMAIL_ADDRESS_RE = re.compile(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b")
|
|
77
|
+
|
|
78
|
+
|
|
77
79
|
_PERSONAL_RISK_RE = re.compile(
|
|
78
80
|
r"("
|
|
79
81
|
r"\betan\b|etan@heyman\.net|heyman\.net|\bjournal\b|\bpersonal\b|\bmy life\b|\bfamily\b|"
|
|
@@ -145,8 +147,8 @@ def has_personal_risk_signal(content: str | None) -> bool:
|
|
|
145
147
|
|
|
146
148
|
|
|
147
149
|
def has_person_name_signal(content: str | None) -> bool:
|
|
148
|
-
"""
|
|
149
|
-
return bool(content and
|
|
150
|
+
"""Recognize a contextual full name or email without bundled identities."""
|
|
151
|
+
return bool(content and (_CONTEXTUAL_PERSON_NAME_RE.search(content) or _EMAIL_ADDRESS_RE.search(content)))
|
|
150
152
|
|
|
151
153
|
|
|
152
154
|
def has_hebrew_signal(content: str | None) -> bool:
|