brainiac-cli 0.20.44__tar.gz → 0.20.46__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.
- {brainiac_cli-0.20.44/src/brainiac_cli.egg-info → brainiac_cli-0.20.46}/PKG-INFO +7 -5
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/pyproject.toml +24 -4
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/scripts/brain-brief.sh +15 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_version.py +1 -1
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/audit_drift.py +2 -1
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/core/_maintenance_state.py +2 -1
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/embed.py +7 -4
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/embed_onnx.py +8 -4
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/exceptions_page.py +3 -2
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/exceptions_verify.py +3 -2
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/folds/intake.py +5 -5
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/graph_ops.py +3 -2
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index/_search.py +15 -10
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/deliverables.py +2 -1
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/handlers/concealment_pdf.py +4 -1
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/pipeline_files.py +8 -3
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/interview.py +2 -1
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/invariant_coverage.py +2 -1
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/maintenance_notify.py +2 -1
- brainiac_cli-0.20.46/src/brain/model_slot.py +110 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/overlay_generated.py +2 -1
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/provision.py +2 -2
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/remediation_exceptions_store.py +2 -1
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/remediation_state.py +2 -1
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/rerank.py +7 -4
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/rerank_onnx.py +28 -4
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/session_hook_settings.py +2 -1
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/update_channels.py +18 -12
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/vmstaging.py +22 -6
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/workspaces.py +2 -1
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46/src/brainiac_cli.egg-info}/PKG-INFO +7 -5
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brainiac_cli.egg-info/SOURCES.txt +1 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brainiac_cli.egg-info/requires.txt +12 -2
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/LICENSE +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/MANIFEST.in +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/README.md +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/setup.cfg +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/__init__.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/__main__.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/AGENTS.md +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/assets/graph-explorer-template.html +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/overlay/template/brand/brand-guide.md +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/overlay/template/keywords/glossary.md +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/overlay/template/people/roster.md +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/overlay/template/voice/voice-profile.md +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/routines/manifest.json +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/scripts/brain-brief-mac.plist +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/scripts/brain-synthesis-mac.plist +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/scripts/brain-synthesis.sh +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/scripts/brain_synthesis_codex.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/scripts/brainiac-alerts.sh +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/scripts/brainiac-egress-guard.sh +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/scripts/install-brief-mac.sh +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/scripts/install-brief-windows.ps1 +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/scripts/register_tasks.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/scripts/vm-boundary-probe.sh +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/scripts/vm-selftest.sh +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/templates/company.md +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/templates/concept.md +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/templates/daily.md +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/templates/decision.md +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/templates/deliverable.md +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/templates/meeting.md +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/templates/person.md +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/templates/project.md +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_assets/templates/state-moc.md +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/_optional.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/alerts.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/alerts_render.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/alerts_staging.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/anchor.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/atomic_temp.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/attestation.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/audit.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/audit_chain.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/autolink.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/backup.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/brief.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/brief_render.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/capture.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/chunk.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/classification.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/__init__.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/common_parser.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/connect.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/connect_parser.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/eval.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/eval_parser.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/index_storage.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/index_storage_parser.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/ingest_storage.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/ingest_storage_parser.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/lifecycle.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/lifecycle_parser.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/maintenance.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/maintenance_parser.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/navigation.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/navigation_parser.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/owner_workflow.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/owner_workflow_parser.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/presentation.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/presentation_parser.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/retrieval.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/retrieval_parser.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/update.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/update_parser.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/versioning.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_cmds/versioning_parser.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_help.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_read_record.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cli_render.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/config.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/config_hostpaths.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/connect.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/context.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/conventions.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/conventions_scan.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/core/__init__.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/core/_audit.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/core/_briefing.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/core/_capture.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/core/_capture_staging.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/core/_durability.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/core/_health.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/core/_maintenance.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/core/_retrieval.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/core/_shared.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/core/_supersede_batch.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/core/_supersession.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/core/_supersession_journal.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cowork_leak_artifact.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cowork_leak_databases.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cowork_leak_notes.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cowork_leak_scan.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cowork_snapshot_guard.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/cowork_staging.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/dbretry.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/deliverables_absorb.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/deliverables_apply.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/deliverables_ledger.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/deliverables_materialize.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/deliverables_previous.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/deliverables_readme.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/deliverables_shelf.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/deliverables_sync.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/doctor.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/doctor_checks.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/doctor_context.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/doctor_desktop.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/doctor_health.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/doctor_mount_leak.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/doctor_plugins.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/doctor_staging.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/doctor_vendor.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/doctor_vm.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/doctor_wiring.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/draft_drain.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/draft_update.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/egress.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/egress_terms.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/embed_backends.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/embed_catalog.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/embed_qwen.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/encryption.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/exceptions_cli.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/exceptions_render.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/exceptions_tray.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/folds/__init__.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/folds/context.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/folds/daily.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/folds/deliverables.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/folds/golden.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/folds/graph.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/folds/invariants.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/folds/orchestrator.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/folds/organization.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/folds/preflight.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/folds/publish.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/folds/remediation.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/folds/remediation_bookkeeping.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/folds/reporting.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/folds/retention.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/folds/watchdogs.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/folds/weekly.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/frontmatter.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/golden_ops.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/golden_probe.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/golden_probe_cases.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/graph.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/graph_hygiene.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/graphify.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/graphify_edges.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/graphreport.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/graphreport_sections.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/graphreport_sections_2.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/guard_settings.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/healthreport.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/healthreport_sections.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/healthreport_unsigned.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/inbox.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index/__init__.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index/_connection.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index/_families.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index/_graph.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index/_identity.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index/_lifecycle.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index/_models.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index/_planning.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index/_records.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index/_retirement.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index/_schema.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index/_settings.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index/_shared.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index/_tools.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index/_zones.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index_stages/__init__.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index_stages/connection.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index_stages/diagnostics.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index_stages/duplicate_families.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index_stages/near_duplicates.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index_stages/rebuild.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index_stages/reranking.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index_stages/search.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index_stages/search_finalization.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index_stages/search_identity.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index_stages/search_ranking.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/index_stages/sync.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/__init__.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/handlers/__init__.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/handlers/base.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/handlers/concealment.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/handlers/concealment_gate.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/handlers/concealment_html.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/handlers/concealment_ledger.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/handlers/concealment_office.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/handlers/docx.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/handlers/email.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/handlers/html.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/handlers/image.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/handlers/ole.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/handlers/pdf.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/handlers/pptx.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/handlers/tables.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/handlers/text.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/handlers/xlsx.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/handlers/zip.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/pipeline.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/pipeline_duplicates.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/pipeline_injection.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/pipeline_nested.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/pipeline_run.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/pipeline_stages.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/tierguard.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/tierguard_verdict.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/ingest/transcript.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/init.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/init_import.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/init_samples.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/init_seed.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/init_steps.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/injection_fold.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/injection_scan.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/interview_apply.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/interview_detect.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/invariant_crosstier.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/invariant_deliverables.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/invariant_floors.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/invariant_ingest_guard.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/invariant_lane_health.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/invariant_reachability.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/invariant_shared.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/invariant_unsigned_notes.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/invariants.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/invariants_metrics.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/language.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/link_targets.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/lock.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/maintenance.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/maintenance_escalation.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/maintenance_folds.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/maintenance_folds_2.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/maintenance_folds_3.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/maintenance_folds_4.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/maintenance_golden.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/maintenance_graphfolds.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/maintenance_healthlog.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/maintenance_hotmd.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/maintenance_invariant_fields.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/maintenance_outcomes.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/maintenance_para.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/maintenance_recommendations.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/maintenance_retention.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/maintenance_sweep.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/maintenance_watchdog.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/mcp_adapter.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/mcp_capture_verbs.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/mcp_housekeeping_verbs.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/mcp_mediation.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/mcp_supersede_verbs.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/mcp_tools/__init__.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/mcp_tools/capture.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/mcp_tools/core_read.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/mcp_tools/housekeeping.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/mcp_tools/retrieval.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/mcp_tools/supersession.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/mcp_verbs.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/multihop.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/notes.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/originals_resolve.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/overlay.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/overlay_core.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/overlay_keywords.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/overlay_rules.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/pathkey.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/progress.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/projection.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/provenance.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/provision_mcp.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/provision_stage.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/provision_wire.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/querylog.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/querylog_digest.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/querylog_replay.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/querylog_status.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/read_log.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/reexec.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/remediation.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/remediation_answers.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/remediation_branches.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/remediation_converged.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/remediation_exceptions.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/remediation_extract_retry.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/remediation_folds.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/remediation_questions.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/remediation_routing.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/remediation_schema.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/retrieval_ops.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/retro.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/session_hook.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/session_hook_spec.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/snapshot.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/status_ops.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/supersede_declared.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/sweepdirs.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/update.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/update_channels_2.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/update_cos_guard.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/update_model.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/update_ops.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/update_plugins.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/update_state.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/update_venv.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/vectors.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/versionlink.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/versionlink_stages.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brain/vm_ceiling.py +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brainiac_cli.egg-info/dependency_links.txt +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brainiac_cli.egg-info/entry_points.txt +0 -0
- {brainiac_cli-0.20.44 → brainiac_cli-0.20.46}/src/brainiac_cli.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: brainiac-cli
|
|
3
|
-
Version: 0.20.
|
|
3
|
+
Version: 0.20.46
|
|
4
4
|
Summary: Brainiac — local any-LLM second-brain core engine + brain CLI (Markdown truth, derived SQLite index).
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Project-URL: Homepage, https://github.com/Autopsias/brainiac
|
|
@@ -19,11 +19,12 @@ Requires-Dist: cryptography>=49
|
|
|
19
19
|
Requires-Dist: PyYAML>=6
|
|
20
20
|
Requires-Dist: regex>=2023.0.0
|
|
21
21
|
Requires-Dist: keyring>=24; sys_platform == "win32"
|
|
22
|
-
Requires-Dist: pypdf>=6.
|
|
22
|
+
Requires-Dist: pypdf>=6.21.2
|
|
23
23
|
Requires-Dist: python-docx>=1.1
|
|
24
24
|
Requires-Dist: python-pptx>=0.6.23
|
|
25
25
|
Requires-Dist: openpyxl>=3.1
|
|
26
|
-
Requires-Dist: Pillow>=
|
|
26
|
+
Requires-Dist: Pillow>=13.0.0; python_version >= "3.11"
|
|
27
|
+
Requires-Dist: Pillow>=12.3.0; python_version < "3.11"
|
|
27
28
|
Provides-Extra: vec
|
|
28
29
|
Requires-Dist: sqlite-vec>=0.1.6; extra == "vec"
|
|
29
30
|
Provides-Extra: audit
|
|
@@ -43,11 +44,12 @@ Requires-Dist: PyYAML>=6; extra == "corporate"
|
|
|
43
44
|
Provides-Extra: yaml
|
|
44
45
|
Requires-Dist: PyYAML>=6; extra == "yaml"
|
|
45
46
|
Provides-Extra: ingest
|
|
46
|
-
Requires-Dist: pypdf>=6.
|
|
47
|
+
Requires-Dist: pypdf>=6.21.2; extra == "ingest"
|
|
47
48
|
Requires-Dist: python-docx>=1.1; extra == "ingest"
|
|
48
49
|
Requires-Dist: python-pptx>=0.6.23; extra == "ingest"
|
|
49
50
|
Requires-Dist: openpyxl>=3.1; extra == "ingest"
|
|
50
|
-
Requires-Dist: Pillow>=
|
|
51
|
+
Requires-Dist: Pillow>=13.0.0; python_version >= "3.11" and extra == "ingest"
|
|
52
|
+
Requires-Dist: Pillow>=12.3.0; python_version < "3.11" and extra == "ingest"
|
|
51
53
|
Provides-Extra: dev
|
|
52
54
|
Requires-Dist: pytest>=7; extra == "dev"
|
|
53
55
|
Requires-Dist: build>=1; extra == "dev"
|
|
@@ -8,7 +8,7 @@ build-backend = "setuptools.build_meta"
|
|
|
8
8
|
# check. The IMPORT package stays `brain` and the console script stays
|
|
9
9
|
# `brain` — only the distribution/project name changes.
|
|
10
10
|
name = "brainiac-cli"
|
|
11
|
-
version = "0.20.
|
|
11
|
+
version = "0.20.46"
|
|
12
12
|
description = "Brainiac — local any-LLM second-brain core engine + brain CLI (Markdown truth, derived SQLite index)."
|
|
13
13
|
readme = "README.md"
|
|
14
14
|
license = "Apache-2.0"
|
|
@@ -42,7 +42,7 @@ dependencies = [
|
|
|
42
42
|
# because requirements.lock is CI/audit-only -- a plain
|
|
43
43
|
# `pip install brainiac-cli` reads THIS constraint, and >=4.0 let the
|
|
44
44
|
# vulnerable 6.14.2 survive an upgrade to 0.20.5 on a real host.
|
|
45
|
-
"pypdf>=6.
|
|
45
|
+
"pypdf>=6.21.2",
|
|
46
46
|
"python-docx>=1.1",
|
|
47
47
|
"python-pptx>=0.6.23",
|
|
48
48
|
"openpyxl>=3.1",
|
|
@@ -50,7 +50,10 @@ dependencies = [
|
|
|
50
50
|
# binary is present — the pytesseract binding itself stays a fully
|
|
51
51
|
# OPTIONAL, NOT-a-pip-dependency probe: OCR degrades gracefully absent
|
|
52
52
|
# it, matching the ADR's "system binaries are never pip deps" rule).
|
|
53
|
-
|
|
53
|
+
# >=13.0.0 fixes 37 advisories (one a FlashPix heap overflow); 13 needs
|
|
54
|
+
# Python 3.11+, and no fixed 12.x exists, so 3.10 keeps the old floor.
|
|
55
|
+
"Pillow>=13.0.0; python_version >= '3.11'",
|
|
56
|
+
"Pillow>=12.3.0; python_version < '3.11'",
|
|
54
57
|
]
|
|
55
58
|
|
|
56
59
|
[project.optional-dependencies]
|
|
@@ -94,7 +97,7 @@ yaml = ["PyYAML>=6"]
|
|
|
94
97
|
# message hints valid even though a default install already has these. Absent,
|
|
95
98
|
# `brain ingest` degrades per-handler (reports which extensions are
|
|
96
99
|
# unavailable) rather than crashing.
|
|
97
|
-
ingest = ["pypdf>=6.
|
|
100
|
+
ingest = ["pypdf>=6.21.2", "python-docx>=1.1", "python-pptx>=0.6.23", "openpyxl>=3.1", "Pillow>=13.0.0; python_version >= '3.11'", "Pillow>=12.3.0; python_version < '3.11'"]
|
|
98
101
|
# pytest-xdist/-timeout back the documented parallel suite run (AGENTS.md §8,
|
|
99
102
|
# "Running the test suite"): ~15 min sequential -> ~6 min at `-n 8`. Both are
|
|
100
103
|
# pytest-dev org, actively maintained. Declared here so a fresh checkout can
|
|
@@ -296,3 +299,20 @@ python-platform = "linux"
|
|
|
296
299
|
# `_assets` is the generated mirror of tools/ + scripts/ inside src/brain/.
|
|
297
300
|
include = ["src", "tools", "eval", "scripts", "packaging"]
|
|
298
301
|
exclude = ["**/_plans", "**/_archive", "**/_evidence", "**/_assets"]
|
|
302
|
+
|
|
303
|
+
[tool.uv]
|
|
304
|
+
# Wait 7 days before resolving a newly published version (a new release can be
|
|
305
|
+
# malicious or broken). shortcut: pypdf 6.21.2, filelock 4.2.4 and certifi
|
|
306
|
+
# 2026.10.23 are security fixes under 7 days old on 2026-10-24, so they are
|
|
307
|
+
# exempt; remove the exemptions after 2026-10-31.
|
|
308
|
+
exclude-newer = "7 days"
|
|
309
|
+
exclude-newer-package = { pypdf = false, filelock = false, certifi = false }
|
|
310
|
+
# fastembed (the optional `embed` extra, not installed by default) caps
|
|
311
|
+
# pillow<13 in every release up to 0.9.0, but pillow 13.0.0 is the first
|
|
312
|
+
# release fixing 37 advisories (one a FlashPix heap overflow). fastembed uses
|
|
313
|
+
# pillow only for IMAGE embeddings; this repo embeds text only.
|
|
314
|
+
# shortcut: drop this once a fastembed release allows pillow>=13.
|
|
315
|
+
override-dependencies = [
|
|
316
|
+
"pillow>=13.0.0; python_version >= '3.11'",
|
|
317
|
+
"pillow>=12.3.0; python_version < '3.11'",
|
|
318
|
+
]
|
|
@@ -61,6 +61,21 @@ if [ -z "$BRAIN_BIN" ]; then
|
|
|
61
61
|
exit 1
|
|
62
62
|
fi
|
|
63
63
|
|
|
64
|
+
# YIELD TO THE NIGHTLY MAIL RUN (2026-10-19). Both do model work, and the
|
|
65
|
+
# machine runs at most $BRAIN_MODEL_SLOTS (2) model processes at once. On
|
|
66
|
+
# run374 an hourly maintain ran 02:06-03:04 across the run's grounding window
|
|
67
|
+
# and 58 of 72 sender lookups timed out (0 of 122 the night before). The night
|
|
68
|
+
# runs its own broker after its child exits, so skipping an hour loses
|
|
69
|
+
# nothing: the next hourly run catches up. A hung night also stops this job;
|
|
70
|
+
# the WAT-01 stale-feed alert names it after 2 days.
|
|
71
|
+
# ponytail: process-name match, a pid lock if the night ever runs under
|
|
72
|
+
# another name.
|
|
73
|
+
YIELD_TO="${BRAIN_BRIEF_YIELD_TO:-tools/cos_nightly.sh}"
|
|
74
|
+
if pgrep -f "$YIELD_TO" >/dev/null 2>&1; then
|
|
75
|
+
echo "=== brain-daily-brief / brain-nightly $(date -u +%Y-%m-%dT%H:%M:%SZ) === SKIP: the nightly mail run is running ($YIELD_TO); next hour catches up" >> "$LOG"
|
|
76
|
+
exit 0
|
|
77
|
+
fi
|
|
78
|
+
|
|
64
79
|
# The RUN VALIDATOR needs the mutation toolchain, and the wheel does not carry
|
|
65
80
|
# it (2026-08-22). `brain.cos_runverify.check_plan_binding` imports
|
|
66
81
|
# `cos_mutate.load_frozen_plan` to re-hash a run's frozen plan — one definition
|
|
@@ -6,6 +6,7 @@ import os
|
|
|
6
6
|
from datetime import date
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from typing import TYPE_CHECKING
|
|
9
|
+
from .atomic_temp import atomic_temp_path
|
|
9
10
|
|
|
10
11
|
if TYPE_CHECKING: # annotation-only; a runtime import here would be a cycle
|
|
11
12
|
from .audit_chain import AuditChain
|
|
@@ -32,7 +33,7 @@ def _write_dispositions(path: Path, payload: dict) -> None:
|
|
|
32
33
|
turn every explained finding back into an unexplained one. `os.replace` is
|
|
33
34
|
atomic within a filesystem, so a reader sees the old file or the new one.
|
|
34
35
|
"""
|
|
35
|
-
tmp =
|
|
36
|
+
tmp = atomic_temp_path(path)
|
|
36
37
|
tmp.write_text(json.dumps(payload, indent=2), encoding="utf-8")
|
|
37
38
|
os.replace(tmp, path)
|
|
38
39
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Maintenance state methods for BrainCore."""
|
|
2
2
|
from __future__ import annotations
|
|
3
|
+
from ..atomic_temp import atomic_temp_path
|
|
3
4
|
|
|
4
5
|
from ._shared import (
|
|
5
6
|
Any,
|
|
@@ -130,7 +131,7 @@ class _MaintenanceStateMixin:
|
|
|
130
131
|
|
|
131
132
|
path = config.maintain_state_path(self.vault)
|
|
132
133
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
133
|
-
tmp =
|
|
134
|
+
tmp = atomic_temp_path(path)
|
|
134
135
|
tmp.write_text(_json.dumps(state, indent=2, sort_keys=True), encoding="utf-8")
|
|
135
136
|
_os.replace(tmp, path)
|
|
136
137
|
def _note_graphify_build_outcome(
|
|
@@ -50,18 +50,21 @@ ARCTIC_FULL_DIM = 768
|
|
|
50
50
|
MRL_DIM = 256
|
|
51
51
|
|
|
52
52
|
|
|
53
|
+
# Measured 2026-10-19: 4 threads match the all-cores wall time at ~40% of the CPU.
|
|
54
|
+
_DEFAULT_THREADS = 4
|
|
55
|
+
|
|
56
|
+
|
|
53
57
|
def _ort_threads() -> int | None:
|
|
54
58
|
"""Intra-op thread count for the ONNX session (S11 speed fix).
|
|
55
59
|
|
|
56
|
-
Default:
|
|
57
|
-
|
|
58
|
-
across cores is the biggest ORT knob for bulk embedding throughput.
|
|
60
|
+
Default: 4 threads (capped at the core count). More threads spin without
|
|
61
|
+
cutting wall time; raise it for bulk work.
|
|
59
62
|
Override via ``$BRAIN_EMBED_THREADS``."""
|
|
60
63
|
raw = os.environ.get("BRAIN_EMBED_THREADS")
|
|
61
64
|
if raw and raw.strip().isdigit():
|
|
62
65
|
return int(raw)
|
|
63
66
|
try:
|
|
64
|
-
return os.cpu_count() or
|
|
67
|
+
return min(_DEFAULT_THREADS, os.cpu_count() or _DEFAULT_THREADS)
|
|
65
68
|
except Exception:
|
|
66
69
|
return None
|
|
67
70
|
|
|
@@ -8,6 +8,8 @@ import sys as sys
|
|
|
8
8
|
from dataclasses import dataclass
|
|
9
9
|
from typing import Sequence
|
|
10
10
|
|
|
11
|
+
from .model_slot import model_slot
|
|
12
|
+
|
|
11
13
|
# --- Direct-ONNX models (DIST-01: eliminates fastembed) ---------------------
|
|
12
14
|
# Each model owns its pooling, prefixes, dimension and exact file. Keeping the
|
|
13
15
|
# contract here prevents a future model swap from inheriting another model's
|
|
@@ -255,9 +257,10 @@ class OnnxEmbedder:
|
|
|
255
257
|
t = _ort_threads()
|
|
256
258
|
if t:
|
|
257
259
|
so.intra_op_num_threads = t
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
260
|
+
with model_slot(): # loading peaks well above the weights' size
|
|
261
|
+
self._sess = ort.InferenceSession(
|
|
262
|
+
onnx_path, sess_options=so, providers=_ort_providers()
|
|
263
|
+
)
|
|
261
264
|
self._in_names = [i.name for i in self._sess.get_inputs()]
|
|
262
265
|
self._tok = Tokenizer.from_file(os.path.join(base, "tokenizer.json"))
|
|
263
266
|
# Truncate to the engine's calibrated chunk window BEFORE padding.
|
|
@@ -387,7 +390,8 @@ class OnnxEmbedder:
|
|
|
387
390
|
feed[nm] = am
|
|
388
391
|
elif "token_type" in low:
|
|
389
392
|
feed[nm] = np.zeros_like(ii)
|
|
390
|
-
|
|
393
|
+
with model_slot():
|
|
394
|
+
hidden = self._sess.run(None, feed)[0] # [batch, seq, dim] float32
|
|
391
395
|
if self._pooling == "cls":
|
|
392
396
|
pooled = hidden[:, 0, :]
|
|
393
397
|
else:
|
|
@@ -60,6 +60,7 @@ from . import maintenance as _maintenance
|
|
|
60
60
|
from . import maintenance_notify as _notify
|
|
61
61
|
from . import remediation as _rem
|
|
62
62
|
from . import remediation_exceptions as _rex
|
|
63
|
+
from .atomic_temp import atomic_temp_path
|
|
63
64
|
from .exceptions_render import render_page # re-exported: the page's one
|
|
64
65
|
# renderer, split out on 2026-08-22 when this file hit the 500-line bound.
|
|
65
66
|
|
|
@@ -287,7 +288,7 @@ def maybe_ping(vault: Path, keys: list[str], full_path: Path) -> str | None:
|
|
|
287
288
|
if not str(out or "").startswith("skipped"): # a SKIP defers, never spends
|
|
288
289
|
try:
|
|
289
290
|
state_path.parent.mkdir(parents=True, exist_ok=True)
|
|
290
|
-
tmp =
|
|
291
|
+
tmp = atomic_temp_path(state_path)
|
|
291
292
|
tmp.write_text(json.dumps({"hash": current_hash}), encoding="utf-8")
|
|
292
293
|
os.replace(tmp, state_path)
|
|
293
294
|
except OSError:
|
|
@@ -364,6 +365,6 @@ def generate(core: Any, today: datetime.date | None = None) -> dict[str, Any]:
|
|
|
364
365
|
|
|
365
366
|
|
|
366
367
|
def _atomic_write(path: Path, text: str) -> None:
|
|
367
|
-
tmp =
|
|
368
|
+
tmp = atomic_temp_path(path)
|
|
368
369
|
tmp.write_text(text, encoding="utf-8")
|
|
369
370
|
os.replace(tmp, path)
|
|
@@ -78,6 +78,7 @@ from pathlib import Path
|
|
|
78
78
|
from typing import Any
|
|
79
79
|
|
|
80
80
|
from . import config as _config
|
|
81
|
+
from .atomic_temp import atomic_temp_path
|
|
81
82
|
|
|
82
83
|
PINNED_FILENAME = "pinned-verify.json"
|
|
83
84
|
MOUNT_HTML_FILENAME = "exceptions.html"
|
|
@@ -372,7 +373,7 @@ def stage_pin(vault: Path, workspace: Path | None = None) -> dict[str, Any]:
|
|
|
372
373
|
data = {"vault_id": vid, "public_key_pem": pem}
|
|
373
374
|
path = staging_root(vault, workspace) / PINNED_FILENAME
|
|
374
375
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
375
|
-
tmp =
|
|
376
|
+
tmp = atomic_temp_path(path)
|
|
376
377
|
tmp.write_text(json.dumps(data, indent=1, sort_keys=True), encoding="utf-8")
|
|
377
378
|
os.replace(tmp, path)
|
|
378
379
|
return data
|
|
@@ -413,6 +414,6 @@ def publish_summary(vault: Path, workspace: Path | None = None) -> None:
|
|
|
413
414
|
if not src.exists():
|
|
414
415
|
continue
|
|
415
416
|
dest = dest_dir / filename
|
|
416
|
-
tmp =
|
|
417
|
+
tmp = atomic_temp_path(dest)
|
|
417
418
|
tmp.write_bytes(src.read_bytes())
|
|
418
419
|
os.replace(tmp, dest)
|
|
@@ -240,12 +240,12 @@ class IntakeFoldsMixin:
|
|
|
240
240
|
from .. import cos # noqa: PLC0415
|
|
241
241
|
|
|
242
242
|
ops = cos.run_ops_dir(self.vault)
|
|
243
|
-
ledgers = sorted(ops.glob(
|
|
243
|
+
ledgers = sorted(ops.glob(cos.LEDGER_GLOB),
|
|
244
244
|
key=self._ledger_order)
|
|
245
245
|
if not ledgers:
|
|
246
246
|
return
|
|
247
247
|
latest = ledgers[-1]
|
|
248
|
-
run_id =
|
|
248
|
+
run_id = cos.ledger_run_id(latest)
|
|
249
249
|
rows, seen = [], set()
|
|
250
250
|
for line in latest.read_text(encoding="utf-8").splitlines():
|
|
251
251
|
if not line.strip():
|
|
@@ -268,7 +268,7 @@ class IntakeFoldsMixin:
|
|
|
268
268
|
"""Sort key for `_cos_ingestion_ledger_<date>-run<N>.jsonl`: the date
|
|
269
269
|
as text, then the run number as an INT. A missing or unparseable run
|
|
270
270
|
number sorts first, which keeps a malformed name from winning."""
|
|
271
|
-
stem = path.stem[len("_cos_ingestion_ledger_"):]
|
|
271
|
+
stem = path.stem[len("_cos_ingestion_ledger_"):] # no COS import: this runs unguarded
|
|
272
272
|
date, _, tail = stem.partition("-run")
|
|
273
273
|
return (date, int(tail) if tail.isdigit() else -1)
|
|
274
274
|
|
|
@@ -355,12 +355,12 @@ class IntakeFoldsMixin:
|
|
|
355
355
|
from .. import cos # noqa: PLC0415
|
|
356
356
|
|
|
357
357
|
ops = cos.run_ops_dir(self.vault)
|
|
358
|
-
ledgers = sorted(ops.glob(
|
|
358
|
+
ledgers = sorted(ops.glob(cos.LEDGER_GLOB),
|
|
359
359
|
key=self._ledger_order)
|
|
360
360
|
if not ledgers:
|
|
361
361
|
return
|
|
362
362
|
latest = ledgers[-1]
|
|
363
|
-
run_id =
|
|
363
|
+
run_id = cos.ledger_run_id(latest)
|
|
364
364
|
stuck, seen = {}, set()
|
|
365
365
|
for line in latest.read_text(encoding="utf-8").splitlines():
|
|
366
366
|
if not line.strip():
|
|
@@ -14,6 +14,7 @@ from . import classification, config, egress
|
|
|
14
14
|
from . import graphify as graphify_model
|
|
15
15
|
from .graph import build_graph
|
|
16
16
|
from .progress import progress_note
|
|
17
|
+
from .atomic_temp import atomic_temp_path
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
@dataclass(frozen=True)
|
|
@@ -186,7 +187,7 @@ def _publish_graph_artifact(
|
|
|
186
187
|
day: dt.date,
|
|
187
188
|
) -> None:
|
|
188
189
|
"""Atomically publish validated graph and manifest files."""
|
|
189
|
-
temporary_graph =
|
|
190
|
+
temporary_graph = atomic_temp_path(paths.graph)
|
|
190
191
|
temporary_graph.write_text(
|
|
191
192
|
json.dumps(artifact, indent=2, sort_keys=True), encoding="utf-8"
|
|
192
193
|
)
|
|
@@ -197,7 +198,7 @@ def _publish_graph_artifact(
|
|
|
197
198
|
"built_at": day.isoformat(),
|
|
198
199
|
"notes": manifest,
|
|
199
200
|
}
|
|
200
|
-
temporary_manifest =
|
|
201
|
+
temporary_manifest = atomic_temp_path(paths.manifest)
|
|
201
202
|
temporary_manifest.write_text(
|
|
202
203
|
json.dumps(new_state, indent=2, sort_keys=True), encoding="utf-8"
|
|
203
204
|
)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
from ._shared import * # noqa: F401,F403
|
|
5
|
+
from ..model_slot import model_slot
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
class _SearchMixin:
|
|
@@ -150,11 +151,14 @@ class _SearchMixin:
|
|
|
150
151
|
|
|
151
152
|
Retired versions (``is_latest_version: false``) are hidden unless
|
|
152
153
|
``include_retired`` — see ``search_ranking.generate_candidates``."""
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
154
|
+
# The slot is taken HERE, before the reranker's 30 s budget starts, so
|
|
155
|
+
# a queued search waits instead of silently losing its rerank.
|
|
156
|
+
with model_slot():
|
|
157
|
+
return self._hybrid_search_impl(
|
|
158
|
+
query, k=k, rrf_k=rrf_k, candidate_factor=candidate_factor,
|
|
159
|
+
rerank=rerank, reranker=reranker, rerank_top=rerank_top,
|
|
160
|
+
rerank_gate=rerank_gate, trace=None, include_retired=include_retired,
|
|
161
|
+
)
|
|
158
162
|
|
|
159
163
|
def hybrid_search_with_trace(
|
|
160
164
|
self,
|
|
@@ -187,11 +191,12 @@ class _SearchMixin:
|
|
|
187
191
|
candidate_limit=n,
|
|
188
192
|
result_limit=max(0, k),
|
|
189
193
|
)
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
194
|
+
with model_slot(): # see hybrid_search
|
|
195
|
+
hits = self._hybrid_search_impl(
|
|
196
|
+
query, k=k, rrf_k=rrf_k, candidate_factor=candidate_factor,
|
|
197
|
+
rerank=rerank, reranker=reranker, rerank_top=rerank_top,
|
|
198
|
+
rerank_gate=rerank_gate, trace=trace, include_retired=include_retired,
|
|
199
|
+
)
|
|
195
200
|
return hits, trace
|
|
196
201
|
|
|
197
202
|
def _hybrid_search_impl(
|
|
@@ -49,6 +49,7 @@ from typing import TYPE_CHECKING, Any
|
|
|
49
49
|
|
|
50
50
|
from .. import classification as CLS
|
|
51
51
|
from .. import supersede_declared as SUP
|
|
52
|
+
from ..atomic_temp import atomic_temp_path
|
|
52
53
|
|
|
53
54
|
if TYPE_CHECKING: # the stage record; a runtime import here would be a cycle
|
|
54
55
|
from .pipeline_stages import ClaimRecord
|
|
@@ -423,7 +424,7 @@ def _store(vault: Any, pending: dict[str, dict[str, Any]]) -> None:
|
|
|
423
424
|
path = journal_path(vault)
|
|
424
425
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
425
426
|
config.secure_file_permissions(path.parent, 0o700)
|
|
426
|
-
tmp =
|
|
427
|
+
tmp = atomic_temp_path(path)
|
|
427
428
|
tmp.write_text(json.dumps(pending, indent=2, sort_keys=True), encoding="utf-8")
|
|
428
429
|
config.secure_file_permissions(tmp, 0o600)
|
|
429
430
|
tmp.replace(path)
|
|
@@ -62,7 +62,10 @@ def page_fonts(page: Any) -> dict[str, Any]:
|
|
|
62
62
|
"""
|
|
63
63
|
fonts: dict[str, Any] = {}
|
|
64
64
|
try:
|
|
65
|
-
|
|
65
|
+
try:
|
|
66
|
+
from pypdf.generic._font import Font # pypdf >= 6.21
|
|
67
|
+
except ImportError: # coverage-audit: older pypdf keeps Font here
|
|
68
|
+
from pypdf._font import Font
|
|
66
69
|
|
|
67
70
|
resources = page.get_inherited("/Resources", None)
|
|
68
71
|
table = resources.get("/Font") if resources else None
|
|
@@ -10,6 +10,7 @@ import shutil
|
|
|
10
10
|
import tempfile
|
|
11
11
|
from pathlib import Path
|
|
12
12
|
from typing import Any
|
|
13
|
+
from ..atomic_temp import atomic_temp_path
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
def manifest_path(vault: Path) -> Path:
|
|
@@ -32,7 +33,7 @@ def load_manifest(vault: Path) -> dict[str, str]:
|
|
|
32
33
|
def save_manifest(vault: Path, manifest: dict[str, str]) -> None:
|
|
33
34
|
path = manifest_path(vault)
|
|
34
35
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
35
|
-
tmp = path
|
|
36
|
+
tmp = atomic_temp_path(path)
|
|
36
37
|
tmp.write_text(json.dumps(manifest, indent=2, sort_keys=True), encoding="utf-8")
|
|
37
38
|
os.replace(tmp, path)
|
|
38
39
|
|
|
@@ -57,7 +58,7 @@ def load_failures(vault: Path) -> dict[str, int]:
|
|
|
57
58
|
def save_failures(vault: Path, failures: dict[str, int]) -> None:
|
|
58
59
|
path = failures_path(vault)
|
|
59
60
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
60
|
-
tmp = path
|
|
61
|
+
tmp = atomic_temp_path(path)
|
|
61
62
|
tmp.write_text(json.dumps(failures, indent=2, sort_keys=True), encoding="utf-8")
|
|
62
63
|
os.replace(tmp, path)
|
|
63
64
|
|
|
@@ -88,7 +89,11 @@ def read_nofollow(path: Path, *, max_bytes: int | None = None) -> bytes:
|
|
|
88
89
|
|
|
89
90
|
if path.is_symlink():
|
|
90
91
|
raise ReadRefused(f"{path.name} is a symlink — refused")
|
|
91
|
-
|
|
92
|
+
# O_NONBLOCK: a FIFO planted at the name would otherwise block the open
|
|
93
|
+
# until a writer arrives, before the S_ISREG check below can refuse it
|
|
94
|
+
# (same guard as ``cos._io``). It changes nothing for a regular file.
|
|
95
|
+
flags = (os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0) | getattr(os, "O_BINARY", 0)
|
|
96
|
+
| getattr(os, "O_NONBLOCK", 0))
|
|
92
97
|
try:
|
|
93
98
|
fd = os.open(path, flags)
|
|
94
99
|
except OSError as exc:
|
|
@@ -27,6 +27,7 @@ import os
|
|
|
27
27
|
import re
|
|
28
28
|
from pathlib import Path
|
|
29
29
|
from typing import Any
|
|
30
|
+
from .atomic_temp import atomic_temp_path
|
|
30
31
|
|
|
31
32
|
SCHEMA = "brain-interview/1"
|
|
32
33
|
STATE_RELPATH = ".brain/interview/state.json"
|
|
@@ -103,7 +104,7 @@ def read_state(vault: Any) -> dict[str, Any]:
|
|
|
103
104
|
def write_state(vault: Any, state: dict[str, Any]) -> Path:
|
|
104
105
|
p = state_path(vault)
|
|
105
106
|
p.parent.mkdir(parents=True, exist_ok=True)
|
|
106
|
-
tmp = p
|
|
107
|
+
tmp = atomic_temp_path(p)
|
|
107
108
|
tmp.write_text(json.dumps(state, ensure_ascii=False, sort_keys=True,
|
|
108
109
|
indent=1) + "\n", encoding="utf-8")
|
|
109
110
|
os.chmod(tmp, 0o600)
|
|
@@ -9,6 +9,7 @@ from pathlib import Path
|
|
|
9
9
|
from typing import Any
|
|
10
10
|
|
|
11
11
|
from .invariant_shared import SAMPLE_CAP
|
|
12
|
+
from .atomic_temp import atomic_temp_path
|
|
12
13
|
|
|
13
14
|
# ---------------------------------------------------------------------------
|
|
14
15
|
# G12 — the exclusion set, defined ONCE here and imported by every consumer
|
|
@@ -437,7 +438,7 @@ def write_link_lane(conn: Any, vault: Path, *, limit: int | None = None) -> dict
|
|
|
437
438
|
except (OSError, ValueError):
|
|
438
439
|
previous = None
|
|
439
440
|
payload.update(lane_consumption(previous, all_ids, payload["generated"]))
|
|
440
|
-
tmp = out
|
|
441
|
+
tmp = atomic_temp_path(out)
|
|
441
442
|
tmp.write_text(json.dumps(payload, indent=1), encoding="utf-8")
|
|
442
443
|
tmp.replace(out)
|
|
443
444
|
return {"path": str(out), "written": len(payload["candidates"]),
|
|
@@ -13,6 +13,7 @@ import os
|
|
|
13
13
|
import platform
|
|
14
14
|
import subprocess
|
|
15
15
|
import hashlib
|
|
16
|
+
from .atomic_temp import atomic_temp_path
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
# ---------------------------------------------------------------------------
|
|
@@ -138,7 +139,7 @@ def record_current_findings(
|
|
|
138
139
|
}
|
|
139
140
|
try:
|
|
140
141
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
141
|
-
tmp =
|
|
142
|
+
tmp = atomic_temp_path(path)
|
|
142
143
|
tmp.write_text(json.dumps(payload, indent=1), encoding="utf-8")
|
|
143
144
|
os.replace(tmp, path)
|
|
144
145
|
except OSError:
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""Machine-wide cap on concurrent model work (load + inference).
|
|
2
|
+
|
|
3
|
+
WHY. One `brain search` peaks at ~4 GB for a few seconds: loading the fp32
|
|
4
|
+
reranker alone measures 3.0 GB, a 15-pair rerank adds ~1 GB (measured
|
|
5
|
+
2026-10-18, M4 Pro). On 2026-10-18 three subagents ran ten searches at once on
|
|
6
|
+
a 24 GB Mac; macOS recorded ten Python processes at 4.1-6.8 GB each and the
|
|
7
|
+
machine froze until a forced reset. Jetsam stopped only small system daemons,
|
|
8
|
+
never the searches. Nothing in brain bounded how many ran together.
|
|
9
|
+
|
|
10
|
+
WHAT. `model_slot()` waits for one of `$BRAIN_MODEL_SLOTS` (default 2) slot
|
|
11
|
+
files under `$BRAIN_MODEL_SLOT_DIR` (default ~/.cache/brain-model-slots) and
|
|
12
|
+
holds an exclusive `flock` on it for the duration of the block. The kernel drops
|
|
13
|
+
the lock when the process dies, so a crash never leaks a slot.
|
|
14
|
+
|
|
15
|
+
RE-ENTRANT PER PROCESS. A search holds a slot and its reranker worker thread
|
|
16
|
+
runs inside it; that work must not wait for a second slot. So the first
|
|
17
|
+
acquire in a process takes a slot, and nested or concurrent acquires in the
|
|
18
|
+
same process ride on it until the depth returns to zero. The cap is therefore
|
|
19
|
+
"processes doing model work", which is what costs memory.
|
|
20
|
+
|
|
21
|
+
FAIL OPEN. If the slot directory cannot be created or opened (read-only HOME,
|
|
22
|
+
Windows without fcntl), the work runs unguarded: a guard must never stop a
|
|
23
|
+
search from working. ponytail: no Windows lock — add msvcrt when a Windows
|
|
24
|
+
host runs parallel searches.
|
|
25
|
+
"""
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import contextlib
|
|
29
|
+
import os
|
|
30
|
+
import sys
|
|
31
|
+
import threading
|
|
32
|
+
import time
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
from typing import Iterator
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
import fcntl
|
|
38
|
+
except ImportError: # Windows
|
|
39
|
+
fcntl = None # type: ignore[assignment]
|
|
40
|
+
|
|
41
|
+
_POLL_SECONDS = 0.2
|
|
42
|
+
_gate = threading.Lock() # a second thread waits here while the first acquires
|
|
43
|
+
_state = threading.Lock()
|
|
44
|
+
_depth = 0
|
|
45
|
+
_fd: int | None = None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def slot_count() -> int:
|
|
49
|
+
raw = os.environ.get("BRAIN_MODEL_SLOTS", "").strip()
|
|
50
|
+
return int(raw) if raw.isdigit() and int(raw) > 0 else 2
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def slot_dir() -> Path:
|
|
54
|
+
raw = os.environ.get("BRAIN_MODEL_SLOT_DIR")
|
|
55
|
+
return Path(raw) if raw else Path.home() / ".cache" / "brain-model-slots"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _try_take(paths: list[Path]) -> int | None:
|
|
59
|
+
for path in paths:
|
|
60
|
+
fd = os.open(path, os.O_RDWR | os.O_CREAT, 0o644)
|
|
61
|
+
try:
|
|
62
|
+
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
63
|
+
except OSError:
|
|
64
|
+
os.close(fd)
|
|
65
|
+
continue
|
|
66
|
+
return fd
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _acquire() -> int | None:
|
|
71
|
+
if fcntl is None:
|
|
72
|
+
return None
|
|
73
|
+
try:
|
|
74
|
+
root = slot_dir()
|
|
75
|
+
root.mkdir(parents=True, exist_ok=True)
|
|
76
|
+
paths = [root / f"slot-{i}.lock" for i in range(slot_count())]
|
|
77
|
+
fd = _try_take(paths)
|
|
78
|
+
if fd is not None:
|
|
79
|
+
return fd
|
|
80
|
+
print(f"brain: waiting for a model slot ({len(paths)} in use on this "
|
|
81
|
+
"machine; $BRAIN_MODEL_SLOTS sets the cap)", file=sys.stderr, flush=True)
|
|
82
|
+
while fd is None:
|
|
83
|
+
time.sleep(_POLL_SECONDS)
|
|
84
|
+
fd = _try_take(paths)
|
|
85
|
+
return fd
|
|
86
|
+
except OSError as exc:
|
|
87
|
+
print(f"brain: model slot unavailable ({exc}); running unguarded",
|
|
88
|
+
file=sys.stderr, flush=True)
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@contextlib.contextmanager
|
|
93
|
+
def model_slot() -> Iterator[None]:
|
|
94
|
+
global _depth, _fd
|
|
95
|
+
with _gate:
|
|
96
|
+
with _state:
|
|
97
|
+
first = _depth == 0
|
|
98
|
+
fd = _acquire() if first else None # a KeyboardInterrupt here holds nothing
|
|
99
|
+
with _state:
|
|
100
|
+
_depth += 1
|
|
101
|
+
if first:
|
|
102
|
+
_fd = fd
|
|
103
|
+
try:
|
|
104
|
+
yield
|
|
105
|
+
finally:
|
|
106
|
+
with _state:
|
|
107
|
+
_depth -= 1
|
|
108
|
+
if _depth == 0 and _fd is not None:
|
|
109
|
+
os.close(_fd) # closing the only fd on the file drops the flock
|
|
110
|
+
_fd = None
|
|
@@ -64,6 +64,7 @@ from typing import Any, NamedTuple
|
|
|
64
64
|
|
|
65
65
|
from . import frontmatter
|
|
66
66
|
from . import overlay as ov
|
|
67
|
+
from .atomic_temp import atomic_temp_path
|
|
67
68
|
|
|
68
69
|
#: Sibling of ``keywords/`` — deliberately NOT inside it. See the module
|
|
69
70
|
#: docstring: ``keywords/`` drives ingest classification and this does not.
|
|
@@ -223,7 +224,7 @@ def _publish(path: Path, text: str) -> None:
|
|
|
223
224
|
``*.md`` glob in :func:`brain.overlay._ring_files` cannot pick a half-written
|
|
224
225
|
temp file up as a ring file of its own.
|
|
225
226
|
"""
|
|
226
|
-
tmp =
|
|
227
|
+
tmp = atomic_temp_path(path)
|
|
227
228
|
try:
|
|
228
229
|
tmp.write_text(text, encoding="utf-8")
|
|
229
230
|
os.replace(tmp, path)
|
|
@@ -47,7 +47,7 @@ from datetime import datetime, timezone
|
|
|
47
47
|
from pathlib import Path
|
|
48
48
|
from typing import Any, Callable, Optional
|
|
49
49
|
|
|
50
|
-
from . import workspaces
|
|
50
|
+
from . import atomic_temp, workspaces
|
|
51
51
|
# Wire 5 moved to `provision_mcp` (its own module: the Desktop config is a
|
|
52
52
|
# third-party file with a third party still writing to it). Re-exported here
|
|
53
53
|
# because this module is the one every caller already imports.
|
|
@@ -94,7 +94,7 @@ def write_request(vault: str | os.PathLike[str], *, role: str) -> dict[str, Any]
|
|
|
94
94
|
for zone in ("brain", "raw", "inbox"):
|
|
95
95
|
(vault_p / zone).mkdir(exist_ok=True)
|
|
96
96
|
payload = {"version": 1, "requested_at": _now_iso(), "role": role}
|
|
97
|
-
tmp =
|
|
97
|
+
tmp = atomic_temp.atomic_temp_path(marker)
|
|
98
98
|
tmp.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")
|
|
99
99
|
os.replace(tmp, marker)
|
|
100
100
|
return {"action": "provision-request", "status": "staged",
|