brainlayer 1.5.45__tar.gz → 1.5.47__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. {brainlayer-1.5.45 → brainlayer-1.5.47}/PKG-INFO +1 -1
  2. {brainlayer-1.5.45 → brainlayer-1.5.47}/pyproject.toml +1 -1
  3. {brainlayer-1.5.45 → brainlayer-1.5.47}/server.json +2 -2
  4. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/__init__.py +1 -1
  5. brainlayer-1.5.47/src/brainlayer/_build.py +1 -0
  6. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/chunk_write.py +63 -1
  7. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/cli/__init__.py +46 -0
  8. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/doctor.py +87 -0
  9. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/hooks/indexer.py +4 -0
  10. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/jsonl_backup.py +17 -15
  11. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/maintenance.py +102 -21
  12. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/cloud_scrub.py +44 -5
  13. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/secret_scrub.py +33 -10
  14. brainlayer-1.5.47/src/brainlayer/scrub_at_rest.py +432 -0
  15. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/store.py +8 -2
  16. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/vector_store.py +77 -7
  17. brainlayer-1.5.45/src/brainlayer/_build.py +0 -1
  18. {brainlayer-1.5.45 → brainlayer-1.5.47}/.gitignore +0 -0
  19. {brainlayer-1.5.45 → brainlayer-1.5.47}/LICENSE +0 -0
  20. {brainlayer-1.5.45 → brainlayer-1.5.47}/README.md +0 -0
  21. {brainlayer-1.5.45 → brainlayer-1.5.47}/scripts/hotlane_brainbar_daemon.py +0 -0
  22. {brainlayer-1.5.45 → brainlayer-1.5.47}/scripts/launchd/backup-daily.sh +0 -0
  23. {brainlayer-1.5.45 → brainlayer-1.5.47}/scripts/launchd/brainlayer-env-run.sh +0 -0
  24. {brainlayer-1.5.45 → brainlayer-1.5.47}/scripts/launchd/brainlayer.env.example +0 -0
  25. {brainlayer-1.5.45 → brainlayer-1.5.47}/scripts/launchd/com.brainlayer.backup-daily.plist +0 -0
  26. {brainlayer-1.5.45 → brainlayer-1.5.47}/scripts/launchd/com.brainlayer.decay.plist +0 -0
  27. {brainlayer-1.5.45 → brainlayer-1.5.47}/scripts/launchd/com.brainlayer.drain.plist +0 -0
  28. {brainlayer-1.5.45 → brainlayer-1.5.47}/scripts/launchd/com.brainlayer.enrichment.plist +0 -0
  29. {brainlayer-1.5.45 → brainlayer-1.5.47}/scripts/launchd/com.brainlayer.health-check.plist +0 -0
  30. {brainlayer-1.5.45 → brainlayer-1.5.47}/scripts/launchd/com.brainlayer.hotlane-brainbar.plist +0 -0
  31. {brainlayer-1.5.45 → brainlayer-1.5.47}/scripts/launchd/com.brainlayer.index.plist +0 -0
  32. {brainlayer-1.5.45 → brainlayer-1.5.47}/scripts/launchd/com.brainlayer.jsonl-backup.plist +0 -0
  33. {brainlayer-1.5.45 → brainlayer-1.5.47}/scripts/launchd/com.brainlayer.log-cap.plist +0 -0
  34. {brainlayer-1.5.45 → brainlayer-1.5.47}/scripts/launchd/com.brainlayer.maintenance-nightly.plist +0 -0
  35. {brainlayer-1.5.45 → brainlayer-1.5.47}/scripts/launchd/com.brainlayer.maintenance-weekly.plist +0 -0
  36. {brainlayer-1.5.45 → brainlayer-1.5.47}/scripts/launchd/com.brainlayer.observability.plist +0 -0
  37. {brainlayer-1.5.45 → brainlayer-1.5.47}/scripts/launchd/com.brainlayer.p0-counter.plist +0 -0
  38. {brainlayer-1.5.45 → brainlayer-1.5.47}/scripts/launchd/com.brainlayer.repair-fts.plist +0 -0
  39. {brainlayer-1.5.45 → brainlayer-1.5.47}/scripts/launchd/com.brainlayer.t3-ingest.plist +0 -0
  40. {brainlayer-1.5.45 → brainlayer-1.5.47}/scripts/launchd/com.brainlayer.throughput-watchdog.plist +0 -0
  41. {brainlayer-1.5.45 → brainlayer-1.5.47}/scripts/launchd/com.brainlayer.tier0-watchdog.plist +0 -0
  42. {brainlayer-1.5.45 → brainlayer-1.5.47}/scripts/launchd/com.brainlayer.wal-checkpoint.plist +0 -0
  43. {brainlayer-1.5.45 → brainlayer-1.5.47}/scripts/launchd/com.brainlayer.watch.plist +0 -0
  44. {brainlayer-1.5.45 → brainlayer-1.5.47}/scripts/launchd/com.etanhey.brainlayer-fleet-watchdog.plist +0 -0
  45. {brainlayer-1.5.45 → brainlayer-1.5.47}/scripts/launchd/fleet-watchdog.sh +0 -0
  46. {brainlayer-1.5.45 → brainlayer-1.5.47}/scripts/launchd/install.sh +0 -0
  47. {brainlayer-1.5.45 → brainlayer-1.5.47}/scripts/launchd/jsonl-backup.sh +0 -0
  48. {brainlayer-1.5.45 → brainlayer-1.5.47}/scripts/launchd/throughput-watchdog.py +0 -0
  49. {brainlayer-1.5.45 → brainlayer-1.5.47}/scripts/release-verify-signatures.sh +0 -0
  50. {brainlayer-1.5.45 → brainlayer-1.5.47}/scripts/tier0-watchdog.sh +0 -0
  51. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/__main__.py +0 -0
  52. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/_helpers.py +0 -0
  53. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/agent_profiles.py +0 -0
  54. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/agent_provenance.py +0 -0
  55. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/alarm.py +0 -0
  56. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/archive_collapse.py +0 -0
  57. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/archive_lifecycle.py +0 -0
  58. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/backup_daily.py +0 -0
  59. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/backup_retention_invariant.py +0 -0
  60. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/badge_state.py +0 -0
  61. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/bitemporal.py +0 -0
  62. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/brainbar_hybrid_helper.py +0 -0
  63. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/cachedelete_purge.swift +0 -0
  64. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/calibrate.py +0 -0
  65. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/chunk_origin.py +0 -0
  66. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/chunk_origin_backfill.py +0 -0
  67. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/chunk_origin_wipe.py +0 -0
  68. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/classify.py +0 -0
  69. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/claude_paths.py +0 -0
  70. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/cli/wizard.py +0 -0
  71. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/cli_new.py +0 -0
  72. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/cloud_backfill.py +0 -0
  73. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/clustering.py +0 -0
  74. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/config.py +0 -0
  75. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/content_class.py +0 -0
  76. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/correction_judge.py +0 -0
  77. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/data/agents_registry.yaml +0 -0
  78. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/data/sandbox_seeds/skill-eval-baseline.json +0 -0
  79. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/db_shrink.py +0 -0
  80. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/decay.py +0 -0
  81. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/decay_backfill.py +0 -0
  82. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/decay_job.py +0 -0
  83. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/dedupe.py +0 -0
  84. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/dedupe_merge.py +0 -0
  85. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/deploy_drift.py +0 -0
  86. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/drain.py +0 -0
  87. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/drain_liveness.py +0 -0
  88. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/drive_credentials.py +0 -0
  89. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/embeddings.py +0 -0
  90. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/engine.py +0 -0
  91. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/enrichment_controller.py +0 -0
  92. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/eval/__init__.py +0 -0
  93. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/eval/abcde_enrich_runner.py +0 -0
  94. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/eval/abcde_variants.py +0 -0
  95. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/eval/abcde_variants.yaml +0 -0
  96. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/eval/benchmark.py +0 -0
  97. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/eval/enrichment_gold.py +0 -0
  98. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/eval/enrichment_graders.py +0 -0
  99. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/eval/enrichment_judge.py +0 -0
  100. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/eval/enrichment_llm_judge.py +0 -0
  101. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/eval/enrichment_quality_benchmark.py +0 -0
  102. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/eval/experiment_store.py +0 -0
  103. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/eval/fixtures/README.md +0 -0
  104. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/eval/labeling/enrichment-labeler.html +0 -0
  105. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/eval/phoenix_gate/__init__.py +0 -0
  106. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/eval/phoenix_gate/__main__.py +0 -0
  107. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/eval/phoenix_gate/baseline_store.py +0 -0
  108. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/eval/phoenix_gate/cli.py +0 -0
  109. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/eval/phoenix_gate/models.py +0 -0
  110. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/eval/phoenix_gate/phoenix_client.py +0 -0
  111. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/eval/phoenix_gate/regression_gate.py +0 -0
  112. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/eval/phoenix_gate/triggers.py +0 -0
  113. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/fallback_replay.py +0 -0
  114. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/git_learning.py +0 -0
  115. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/health_check.py +0 -0
  116. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/hook_python.py +0 -0
  117. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/hooks/__init__.py +0 -0
  118. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/import_sweep.py +0 -0
  119. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/index_completeness.py +0 -0
  120. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/index_new.py +0 -0
  121. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/index_watchdog.py +0 -0
  122. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/ingest/__init__.py +0 -0
  123. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/ingest/codex.py +0 -0
  124. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/ingest/t3.py +0 -0
  125. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/ingest_denylist.py +0 -0
  126. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/ingest_guard.py +0 -0
  127. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/isolation_proof.py +0 -0
  128. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/job_alerts.py +0 -0
  129. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/job_lifecycle_health.py +0 -0
  130. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/jobs.py +0 -0
  131. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/kg/__init__.py +0 -0
  132. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/kg_cleanup.py +0 -0
  133. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/kg_judge.py +0 -0
  134. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/kg_promotion.py +0 -0
  135. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/kg_repo.py +0 -0
  136. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/kg_review_session.py +0 -0
  137. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/kg_session_finish.py +0 -0
  138. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/kg_session_harvest.py +0 -0
  139. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/launchd_primitive.py +0 -0
  140. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/lexical_defense.py +0 -0
  141. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/lexical_defense_dictionary.json +0 -0
  142. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/log_cap.py +0 -0
  143. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/mcp/__init__.py +0 -0
  144. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/mcp/_format.py +0 -0
  145. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/mcp/_shared.py +0 -0
  146. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/mcp/enrich_handler.py +0 -0
  147. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/mcp/entity_handler.py +0 -0
  148. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/mcp/palette.py +0 -0
  149. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/mcp/search_handler.py +0 -0
  150. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/mcp/store_handler.py +0 -0
  151. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/mcp/tags_handler.py +0 -0
  152. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/mcp_socket_config.py +0 -0
  153. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/mcp_stdio_bridge.py +0 -0
  154. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/memory_types.py +0 -0
  155. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/migrate.py +0 -0
  156. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/notification_policy.py +0 -0
  157. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/observability_backup.py +0 -0
  158. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/observability_surface.py +0 -0
  159. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/p0_longitudinal_count.py +0 -0
  160. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/parent_death.py +0 -0
  161. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/paths.py +0 -0
  162. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pause.py +0 -0
  163. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/phonetic.py +0 -0
  164. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/__init__.py +0 -0
  165. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/agent_enrichment.py +0 -0
  166. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/analyze_communication.py +0 -0
  167. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/batch_extraction.py +0 -0
  168. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/brain_graph.py +0 -0
  169. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/chat_tags.py +0 -0
  170. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/chunk.py +0 -0
  171. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/classify.py +0 -0
  172. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/cluster_sampling.py +0 -0
  173. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/code_intelligence.py +0 -0
  174. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/correction_detection.py +0 -0
  175. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/correction_mining.py +0 -0
  176. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/digest.py +0 -0
  177. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/enrichment.py +0 -0
  178. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/enrichment_tiers.py +0 -0
  179. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/entity_extraction.py +0 -0
  180. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/entity_resolution.py +0 -0
  181. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/extract.py +0 -0
  182. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/extract_claude_desktop.py +0 -0
  183. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/extract_corrections.py +0 -0
  184. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/extract_markdown.py +0 -0
  185. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/extract_whatsapp.py +0 -0
  186. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/frustration_mining.py +0 -0
  187. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/git_overlay.py +0 -0
  188. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/groq.py +0 -0
  189. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/kg_extraction.py +0 -0
  190. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/kg_extraction_groq.py +0 -0
  191. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/longitudinal_analyzer.py +0 -0
  192. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/obsidian_export.py +0 -0
  193. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/operation_grouping.py +0 -0
  194. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/plan_linking.py +0 -0
  195. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/rate_limiter.py +0 -0
  196. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/relation_backfill.py +0 -0
  197. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/sanitize.py +0 -0
  198. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/semantic_style.py +0 -0
  199. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/sentiment.py +0 -0
  200. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/session_enrichment.py +0 -0
  201. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/style_embed.py +0 -0
  202. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/style_index.py +0 -0
  203. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/tag_entity_promotion.py +0 -0
  204. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/temporal_chains.py +0 -0
  205. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/time_batcher.py +0 -0
  206. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/unified_timeline.py +0 -0
  207. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/pipeline/write_queue.py +0 -0
  208. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/provenance.py +0 -0
  209. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/provenance_autosupersede.py +0 -0
  210. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/provenance_integration.py +0 -0
  211. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/queue_io.py +0 -0
  212. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/queue_merge.py +0 -0
  213. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/reembed_backfill.py +0 -0
  214. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/runtime_store.py +0 -0
  215. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/sandbox_db.py +0 -0
  216. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/scoping.py +0 -0
  217. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/search_fanout.py +0 -0
  218. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/search_profile.py +0 -0
  219. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/search_repo.py +0 -0
  220. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/session_repo.py +0 -0
  221. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/setup.py +0 -0
  222. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/socket_hygiene.py +0 -0
  223. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/source_class_migration.py +0 -0
  224. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/spotlight.py +0 -0
  225. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/storage.py +0 -0
  226. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/system_prompt_guard.py +0 -0
  227. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/t3_provenance.py +0 -0
  228. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/tag_normalization.py +0 -0
  229. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/taxonomy.json +0 -0
  230. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/telemetry.py +0 -0
  231. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/timestamp_iso.py +0 -0
  232. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/types.py +0 -0
  233. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/wal_checkpoint.py +0 -0
  234. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/watcher.py +0 -0
  235. {brainlayer-1.5.45 → brainlayer-1.5.47}/src/brainlayer/watcher_bridge.py +0 -0
  236. {brainlayer-1.5.45 → brainlayer-1.5.47}/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.45
3
+ Version: 1.5.47
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "brainlayer"
3
- version = "1.5.45"
3
+ version = "1.5.47"
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"
@@ -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.45",
6
+ "version": "1.5.47",
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.45",
17
+ "version": "1.5.47",
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.45"
5
+ __version__ = "1.5.47"
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 = "38521b5296eddaaa7d2cdc722119f2622f3456b2"
@@ -148,6 +148,62 @@ def _table_columns(conn: Any, table: str) -> set[str]:
148
148
  return {row[1] for row in rows}
149
149
 
150
150
 
151
+ def scrub_chunk_values(values: Mapping[str, Any]) -> dict[str, Any]:
152
+ """Scrub stored text before deriving hashes, previews or merge inputs.
153
+
154
+ JSON fields are decoded/normalized before scanning so escaped credentials
155
+ and mapping keys cannot bypass the at-rest policy. Quarantine remains
156
+ metadata only. Already-scrubbed callers retain their findings.
157
+ """
158
+ from .pipeline.cloud_scrub import normalize_json_strings
159
+ from .pipeline.secret_scrub import merge_scrub_metadata, scrub_for_storage
160
+
161
+ clean = dict(values)
162
+ identity_fields = {"id", "brick_id", "conversation_id", "superseded_by", "aggregated_into"}
163
+ findings: dict[str, Any] = {}
164
+ body_findings: dict[str, Any] = {}
165
+ for key, value in clean.items():
166
+ structured = isinstance(value, (dict, list, tuple))
167
+ if structured:
168
+ value = json.dumps(value, ensure_ascii=False)
169
+ if not isinstance(value, str):
170
+ continue
171
+ if key != "content" and key not in identity_fields:
172
+ value = normalize_json_strings(value)
173
+ text, found = scrub_for_storage(value)
174
+ if key in identity_fields and text != value:
175
+ raise ValueError("chunk identifier requires redaction; refusing to change identity")
176
+ clean[key] = json.loads(text) if structured else text
177
+ # Quarantine count describes the body, not keys/derived placeholders.
178
+ findings = merge_scrub_metadata(
179
+ findings, {k: v for k, v in found.items() if k != "secret_scrub_quarantine_count"}
180
+ )
181
+ if key == "content":
182
+ body_findings = found
183
+ if findings or body_findings:
184
+ metadata = clean.get("metadata")
185
+ if metadata is None:
186
+ metadata = {}
187
+ if isinstance(metadata, str):
188
+ try:
189
+ metadata = json.loads(metadata)
190
+ except ValueError:
191
+ metadata = {"value": metadata}
192
+ if not isinstance(metadata, dict):
193
+ metadata = {"value": metadata}
194
+ if clean.get("content") == values.get("content") and (
195
+ "secret_scrub_quarantine_count" in metadata or "secret_scrub_redactions" in metadata
196
+ ):
197
+ body_findings.pop("secret_scrub_quarantine_count", None)
198
+ clean["metadata"] = merge_scrub_metadata(merge_scrub_metadata(metadata, findings), body_findings)
199
+ if clean.get("content") != values.get("content"):
200
+ clean["char_count"] = len(str(clean.get("content") or ""))
201
+ # Caller-supplied dedupe fields describe the pre-scrub text. Recompute.
202
+ for key in ("dedupe_hash", "simhash", *(f"simhash_band_{i}" for i in range(4))):
203
+ clean.pop(key, None)
204
+ return clean
205
+
206
+
151
207
  def prepare_canonical_insert(
152
208
  values: Mapping[str, Any],
153
209
  *,
@@ -158,6 +214,7 @@ def prepare_canonical_insert(
158
214
  """Fill required insert columns. Does not write."""
159
215
  from .timestamp_iso import normalize_timestamp
160
216
 
217
+ values = scrub_chunk_values(values)
161
218
  content = str(values.get("content") or "")
162
219
  if not content.strip():
163
220
  raise ValueError("content must be non-empty")
@@ -271,7 +328,12 @@ def insert_canonical_chunk(
271
328
  on_conflict: str = "ignore",
272
329
  now: datetime | None = None,
273
330
  ) -> dict[str, Any]:
274
- """Insert one chunk row using the canonical column set."""
331
+ """Insert a scrubbed chunk; hashes and dedupe fields describe stored text.
332
+
333
+ Tokens of the same provider collapse to the same placeholder, so dedupe
334
+ treats otherwise identical scrubbed memories as the same content. Existing
335
+ unsanitized rows are not rewritten by this go-forward boundary.
336
+ """
275
337
  columns = _table_columns(conn, "chunks")
276
338
  row = prepare_canonical_insert(values, now=now, columns=columns)
277
339
  names = [key for key in row if key in columns]
@@ -1285,6 +1285,8 @@ def doctor_command(
1285
1285
  f"[bold]{status}[/] chunks={result.chunk_count} "
1286
1286
  f"recent_unvectored={result.recent_unvectored_chunks} queue={result.queue_count}"
1287
1287
  )
1288
+ if result.cli_path_shadow and result.cli_path_shadow["state"] == "skipped":
1289
+ rprint(f"CLI PATH check skipped: {result.cli_path_shadow['reason']}")
1288
1290
  for issue in result.issues:
1289
1291
  color = "red" if issue.severity == "fatal" else "yellow"
1290
1292
  rprint(f"[{color}]{issue.severity.upper()}[/] {issue.code}: {issue.message}")
@@ -2865,6 +2867,50 @@ def wal_checkpoint(
2865
2867
  report_job_alert("wal-checkpoint", None)
2866
2868
 
2867
2869
 
2870
+ @app.command("scrub-at-rest")
2871
+ def scrub_at_rest_command(
2872
+ db_path: Path = typer.Option(..., "--db", help="Explicit database path; live apply requires guarded opt-in."),
2873
+ providers: str = typer.Option(
2874
+ "google_oauth",
2875
+ "--providers",
2876
+ help="Provider mode: google_oauth, context7, or exa_labeled (EXA key labels only).",
2877
+ ),
2878
+ dry_run: bool = typer.Option(False, "--dry-run", help="Count matches without writing."),
2879
+ batch_size: int = typer.Option(100, "--batch-size", min=1, max=1000),
2880
+ allow_live_db: bool = typer.Option(
2881
+ False, "--allow-live-db", help="Apply with maintenance safety gates and writer quiescence."
2882
+ ),
2883
+ expect_rows: int | None = typer.Option(
2884
+ None, "--expect-rows", min=0, help="Required guarded apply total from the preceding dry run."
2885
+ ),
2886
+ ) -> None:
2887
+ """Redact provider credentials on a copy or a guarded runtime DB; print counts only."""
2888
+ from ..scrub_at_rest import scrub_at_rest
2889
+
2890
+ try:
2891
+ result = scrub_at_rest(
2892
+ db_path,
2893
+ providers=providers,
2894
+ dry_run=dry_run,
2895
+ batch_size=batch_size,
2896
+ allow_live_db=allow_live_db,
2897
+ expect_rows=expect_rows,
2898
+ )
2899
+ except Exception as exc:
2900
+ typer.echo(
2901
+ json.dumps(
2902
+ {
2903
+ "error": "at-rest scrub refused or failed",
2904
+ "error_type": type(exc).__name__,
2905
+ "reason": getattr(exc, "reason", "scrub-failed"),
2906
+ "detail": getattr(exc, "detail", None),
2907
+ }
2908
+ )
2909
+ )
2910
+ raise typer.Exit(1) from None
2911
+ typer.echo(json.dumps(result, sort_keys=True))
2912
+
2913
+
2868
2914
  @app.command("repair-fts")
2869
2915
  def repair_fts(
2870
2916
  db_path: Path | None = typer.Argument(None, help="Offline database copy; omitted means read-only FTS count check."),
@@ -8,6 +8,9 @@ indexes.
8
8
  from __future__ import annotations
9
9
 
10
10
  import json
11
+ import os
12
+ import re
13
+ import shlex
11
14
  import sqlite3
12
15
  import sys
13
16
  import time
@@ -250,11 +253,93 @@ class DoctorResult:
250
253
  hotlane_running: bool = False
251
254
  roundtrip_latency_seconds: float | None = None
252
255
  fts5_health: dict[str, Any] | None = None
256
+ cli_path_shadow: dict[str, Any] | None = None
253
257
 
254
258
  def to_dict(self) -> dict[str, Any]:
255
259
  return asdict(self)
256
260
 
257
261
 
262
+ def _check_cli_path_shadow(result: DoctorResult) -> None:
263
+ """Keep an advisory PATH probe from aborting the other doctor checks."""
264
+ try:
265
+ _probe_cli_path_shadow(result)
266
+ except Exception as exc:
267
+ reason = f"{type(exc).__name__}: {exc}"
268
+ result.cli_path_shadow = {"state": "warning", "reason": reason}
269
+ result.issues.append(DoctorIssue("cli_path_shadow_check_failed", "warning", reason))
270
+
271
+
272
+ def _probe_cli_path_shadow(result: DoctorResult) -> None:
273
+ prefix = Path(sys.prefix).resolve()
274
+ keg = next(
275
+ (p for p in (prefix, *prefix.parents) if p.parent.name == "brainlayer" and p.parent.parent.name == "Cellar"),
276
+ None,
277
+ )
278
+ result.cli_path_shadow = {"state": "skipped", "reason": "running interpreter is not a BrainLayer keg install"}
279
+ if keg is None:
280
+ return
281
+ paths: list[Path] = []
282
+ seen: set[Path] = set()
283
+ probe_failed = False
284
+ for directory in os.environ.get("PATH", "").split(os.pathsep):
285
+ candidate = Path(directory) / "brainlayer"
286
+ try:
287
+ if candidate.is_file() and os.access(candidate, os.X_OK):
288
+ resolved = candidate.resolve()
289
+ if resolved not in seen:
290
+ paths.append(candidate.absolute())
291
+ seen.add(resolved)
292
+ except OSError as exc:
293
+ probe_failed = True
294
+ result.issues.append(DoctorIssue("cli_path_shadow_probe_failed", "warning", f"Skipped {candidate}: {exc}"))
295
+ severity = "fatal" if paths and not paths[0].resolve().is_relative_to(keg) else "warning"
296
+ duplicates = [p for p in paths if not p.resolve().is_relative_to(keg)]
297
+ result.cli_path_shadow = {
298
+ "state": severity if duplicates or not paths or probe_failed else "pass",
299
+ "paths": [str(p) for p in paths],
300
+ "prefix": str(keg),
301
+ }
302
+ if not paths:
303
+ result.issues.append(DoctorIssue("cli_path_shadow", "warning", "No brainlayer executable on PATH"))
304
+ for path in duplicates:
305
+ severity = "fatal" if path == paths[0] else "warning"
306
+ kind, remedy = "plain script", "put the running BrainLayer keg bin directory first on PATH"
307
+ try:
308
+ with path.open() as stream:
309
+ shebang = stream.readline(4096).strip()
310
+ interpreter = Path(shebang[2:].split()[0]) if shebang.startswith("#!/") else None
311
+ if interpreter and interpreter.name.startswith("python"):
312
+ install = interpreter.parent.parent
313
+ metadata = list(install.glob("lib/python*/site-packages/brainlayer-*.dist-info/direct_url.json"))
314
+ version_match = re.search(r"python(\d+\.\d+)$", interpreter.resolve().name)
315
+ version = version_match.group(1) if version_match else None
316
+ user_metadata = []
317
+ if version:
318
+ user_metadata = list(
319
+ (Path.home() / "Library/Python" / version / "lib/python/site-packages").glob(
320
+ "brainlayer-*.dist-info"
321
+ )
322
+ )
323
+ user_metadata += list(
324
+ (Path.home() / ".local/lib" / f"python{version}/site-packages").glob("brainlayer-*.dist-info")
325
+ )
326
+ if any(json.loads(p.read_text()).get("dir_info", {}).get("editable") is True for p in metadata):
327
+ kind = "editable install"
328
+ env = "PYTHONNOUSERSITE=1 " if user_metadata else ""
329
+ remedy = f"sudo {env}{shlex.quote(str(interpreter.parent / 'pip3'))} uninstall brainlayer"
330
+ if path.is_relative_to(Path.home() / ".local/bin") or path.is_relative_to(
331
+ Path.home() / "Library/Python"
332
+ ):
333
+ remedy = f"{shlex.quote(str(interpreter))} -m pip uninstall brainlayer"
334
+ except (OSError, ValueError, TypeError, AttributeError):
335
+ pass
336
+ result.issues.append(
337
+ DoctorIssue(
338
+ "cli_path_shadow", severity, f"{path}: {kind}; {remedy}", {"path": str(path), "remediation": remedy}
339
+ )
340
+ )
341
+
342
+
258
343
  def _ro_conn(db_path: Path) -> sqlite3.Connection:
259
344
  uri = f"file:{db_path.expanduser()}?mode=ro"
260
345
  conn = sqlite3.connect(uri, uri=True, timeout=5)
@@ -581,6 +666,8 @@ def run_doctor(
581
666
  def warning(code: str, message: str, **details: Any) -> None:
582
667
  result.issues.append(DoctorIssue(code, "warning", message, details))
583
668
 
669
+ _check_cli_path_shadow(result)
670
+
584
671
  db_config_error = get_brainlayer_db_config_error()
585
672
  if db_config_error is not None:
586
673
  fatal(
@@ -181,6 +181,10 @@ class RealtimeIndexer:
181
181
  next_index = row[0] if row else 0
182
182
 
183
183
  try:
184
+ from ..pipeline.secret_scrub import scrub_secrets
185
+
186
+ compact_summary = scrub_secrets(compact_summary).text
187
+ trigger = scrub_secrets(trigger).text
184
188
  cursor = self._db.execute(
185
189
  """INSERT INTO chapters (session_id, chapter_index, compact_summary, trigger)
186
190
  VALUES (?, ?, ?, ?)""",
@@ -50,6 +50,8 @@ import traceback
50
50
  import uuid
51
51
  from dataclasses import dataclass
52
52
  from pathlib import Path
53
+ from time import monotonic as _monotonic
54
+ from time import sleep as _sleep
53
55
  from typing import Any
54
56
 
55
57
  from . import backup_daily
@@ -190,7 +192,7 @@ def _icloud_deadline_for_archive(
190
192
  """Set an archive-sized budget when the iCloud copy operation begins."""
191
193
  archive_size = Path(archive_path).expanduser().stat().st_size
192
194
  configured_timeout = _configured_icloud_timeout_seconds() if timeout_seconds is None else timeout_seconds
193
- return time.monotonic() + max(configured_timeout, archive_size / ICLOUD_MIN_BYTES_PER_SECOND)
195
+ return _monotonic() + max(configured_timeout, archive_size / ICLOUD_MIN_BYTES_PER_SECOND)
194
196
 
195
197
 
196
198
  def _load_state(path: Path) -> dict[str, Any]:
@@ -454,7 +456,7 @@ class ICloudProbeError(RuntimeError):
454
456
 
455
457
 
456
458
  def _check_icloud_deadline(deadline: float | None, phase: str) -> None:
457
- if deadline is not None and time.monotonic() >= deadline:
459
+ if deadline is not None and _monotonic() >= deadline:
458
460
  raise ICloudDeadlineExceeded(f"iCloud operation deadline exceeded during {phase}")
459
461
 
460
462
 
@@ -663,7 +665,7 @@ def copy_archive_to_icloud(
663
665
  )
664
666
 
665
667
  def remaining_seconds() -> float:
666
- return max(deadline - time.monotonic(), 0.001)
668
+ return max(deadline - _monotonic(), 0.001)
667
669
 
668
670
  icloud_dir.mkdir(parents=True, exist_ok=True)
669
671
  _check_icloud_deadline(deadline, "preparing the iCloud directory")
@@ -738,14 +740,14 @@ def copy_archive_to_icloud(
738
740
  result = receipt(reused=True)
739
741
  _trash_stale_unverified_icloud_items(icloud_dir, logical_sha256)
740
742
  return result
741
- if time.monotonic() >= deadline:
743
+ if _monotonic() >= deadline:
742
744
  if state.get("is_uploading") is True and destination.is_file():
743
745
  return pending_receipt()
744
746
  raise ICloudDeadlineExceeded(
745
747
  "existing iCloud copy was not uploaded and materialized before its deadline: "
746
748
  f"path={destination} state={state!r}"
747
749
  )
748
- time.sleep(min(poll_interval_seconds, remaining_seconds()))
750
+ _sleep(min(poll_interval_seconds, remaining_seconds()))
749
751
  status_path = placeholder if not destination.exists() and placeholder.exists() else destination
750
752
  state = _icloud_item_state(
751
753
  status_path,
@@ -767,7 +769,7 @@ def copy_archive_to_icloud(
767
769
  raise
768
770
  _quarantine_unverified_icloud_item(destination)
769
771
  _quarantine_unverified_icloud_item(placeholder)
770
- if time.monotonic() >= deadline:
772
+ if _monotonic() >= deadline:
771
773
  raise
772
774
 
773
775
  temp_path = icloud_dir / f".{destination.name}.{os.getpid()}.partial"
@@ -800,10 +802,10 @@ def copy_archive_to_icloud(
800
802
  # The local copy/hash budget is complete. Network status gets a fresh phase
801
803
  # budget, so an expired disk-scaled deadline cannot become a 0.001-second
802
804
  # osascript timeout on the upload probe.
803
- network_deadline = time.monotonic() + configured_timeout
805
+ network_deadline = _monotonic() + configured_timeout
804
806
 
805
807
  def network_remaining_seconds() -> float:
806
- return max(network_deadline - time.monotonic(), 0.001)
808
+ return max(network_deadline - _monotonic(), 0.001)
807
809
 
808
810
  verified = False
809
811
  pending = False
@@ -841,10 +843,10 @@ def copy_archive_to_icloud(
841
843
  )
842
844
  _trash_stale_unverified_icloud_items(icloud_dir, logical_sha256)
843
845
  return result
844
- if time.monotonic() >= deadline and state.get("is_uploading") is True and destination.is_file():
846
+ if _monotonic() >= deadline and state.get("is_uploading") is True and destination.is_file():
845
847
  pending = True
846
848
  return pending_receipt()
847
- if time.monotonic() >= network_deadline:
849
+ if _monotonic() >= network_deadline:
848
850
  if state.get("is_uploading") is True and destination.is_file():
849
851
  pending = True
850
852
  return pending_receipt()
@@ -854,7 +856,7 @@ def copy_archive_to_icloud(
854
856
  "iCloud copy was not uploaded and materialized before its deadline: "
855
857
  f"path={destination} materialization={materialization} state={state!r}"
856
858
  )
857
- time.sleep(min(poll_interval_seconds, network_remaining_seconds()))
859
+ _sleep(min(poll_interval_seconds, network_remaining_seconds()))
858
860
  placeholder = destination.with_name(f".{destination.name}.icloud")
859
861
  status_path = placeholder if not destination.exists() and placeholder.exists() else destination
860
862
  state = _icloud_item_state(status_path, request_download=True, timeout_seconds=network_remaining_seconds())
@@ -1042,7 +1044,7 @@ def _icloud_inventory_is_verified(
1042
1044
  return False
1043
1045
 
1044
1046
  configured_timeout = _configured_icloud_timeout_seconds() if timeout_seconds is None else timeout_seconds
1045
- deadline = deadline if deadline is not None else time.monotonic() + configured_timeout
1047
+ deadline = deadline if deadline is not None else _monotonic() + configured_timeout
1046
1048
  for archive_name in sorted(referenced_by_sources):
1047
1049
  try:
1048
1050
  _check_icloud_deadline(deadline, f"validating iCloud inventory archive {archive_name}")
@@ -1086,7 +1088,7 @@ def _icloud_inventory_is_verified(
1086
1088
  archive_valid = True
1087
1089
  while True:
1088
1090
  _check_icloud_deadline(deadline, f"probing iCloud inventory archive {archive_name}")
1089
- remaining = max(deadline - time.monotonic(), 0.001)
1091
+ remaining = max(deadline - _monotonic(), 0.001)
1090
1092
  status_path = placeholder if not destination.exists() and placeholder.exists() else destination
1091
1093
  item_state = _icloud_item_state(
1092
1094
  status_path,
@@ -1117,11 +1119,11 @@ def _icloud_inventory_is_verified(
1117
1119
  elif validated_sources is not None:
1118
1120
  validated_sources.update(referenced_by_sources[archive_name])
1119
1121
  break
1120
- if time.monotonic() >= deadline:
1122
+ if _monotonic() >= deadline:
1121
1123
  raise ICloudDeadlineExceeded(
1122
1124
  f"iCloud operation deadline exceeded while materializing inventory archive {archive_name}"
1123
1125
  )
1124
- time.sleep(min(poll_interval_seconds, remaining))
1126
+ _sleep(min(poll_interval_seconds, remaining))
1125
1127
  if not archive_valid:
1126
1128
  complete = False
1127
1129
  except (OSError, RuntimeError, subprocess.TimeoutExpired) as exc: