brainiac-cli 0.16.0__py3-none-any.whl
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.
- brain/__init__.py +39 -0
- brain/__main__.py +14 -0
- brain/_assets/AGENTS.md +564 -0
- brain/_assets/overlay/template/brand/brand-guide.md +24 -0
- brain/_assets/overlay/template/keywords/glossary.md +15 -0
- brain/_assets/overlay/template/people/roster.md +14 -0
- brain/_assets/overlay/template/voice/voice-profile.md +34 -0
- brain/_assets/routines/manifest.json +257 -0
- brain/_assets/scripts/brain-brief-mac.plist +59 -0
- brain/_assets/scripts/brain-brief.sh +74 -0
- brain/_assets/scripts/brain-synthesis-mac.plist +48 -0
- brain/_assets/scripts/brain-synthesis.sh +214 -0
- brain/_assets/scripts/install-brief-mac.sh +152 -0
- brain/_assets/scripts/install-brief-windows.ps1 +97 -0
- brain/_assets/scripts/register_tasks.py +386 -0
- brain/_assets/templates/company.md +21 -0
- brain/_assets/templates/concept.md +27 -0
- brain/_assets/templates/daily.md +20 -0
- brain/_assets/templates/decision.md +42 -0
- brain/_assets/templates/meeting.md +33 -0
- brain/_assets/templates/person.md +20 -0
- brain/_assets/templates/project.md +23 -0
- brain/_assets/templates/state-moc.md +40 -0
- brain/_version.py +12 -0
- brain/anchor.py +121 -0
- brain/audit.py +422 -0
- brain/backup.py +210 -0
- brain/brief.py +417 -0
- brain/capture.py +117 -0
- brain/chunk.py +249 -0
- brain/classification.py +134 -0
- brain/cli.py +1906 -0
- brain/config.py +368 -0
- brain/connect.py +362 -0
- brain/context.py +108 -0
- brain/core.py +3018 -0
- brain/doctor.py +1161 -0
- brain/egress.py +148 -0
- brain/embed.py +857 -0
- brain/encryption.py +217 -0
- brain/frontmatter.py +102 -0
- brain/golden_probe.py +678 -0
- brain/graph.py +369 -0
- brain/graphify.py +352 -0
- brain/index.py +1576 -0
- brain/ingest/__init__.py +19 -0
- brain/ingest/handlers/__init__.py +43 -0
- brain/ingest/handlers/base.py +95 -0
- brain/ingest/handlers/docx.py +78 -0
- brain/ingest/handlers/email.py +228 -0
- brain/ingest/handlers/html.py +142 -0
- brain/ingest/handlers/image.py +91 -0
- brain/ingest/handlers/pdf.py +99 -0
- brain/ingest/handlers/pptx.py +69 -0
- brain/ingest/handlers/tables.py +41 -0
- brain/ingest/handlers/text.py +43 -0
- brain/ingest/handlers/xlsx.py +100 -0
- brain/ingest/handlers/zip.py +163 -0
- brain/ingest/pipeline.py +839 -0
- brain/ingest/transcript.py +158 -0
- brain/init.py +870 -0
- brain/maintenance.py +2266 -0
- brain/mcp_adapter.py +217 -0
- brain/multihop.py +232 -0
- brain/notes.py +195 -0
- brain/overlay.py +183 -0
- brain/projection.py +79 -0
- brain/rerank.py +425 -0
- brain/snapshot.py +231 -0
- brain/update.py +743 -0
- brain/vectors.py +225 -0
- brainiac_cli-0.16.0.dist-info/METADATA +306 -0
- brainiac_cli-0.16.0.dist-info/RECORD +77 -0
- brainiac_cli-0.16.0.dist-info/WHEEL +5 -0
- brainiac_cli-0.16.0.dist-info/entry_points.txt +4 -0
- brainiac_cli-0.16.0.dist-info/licenses/LICENSE +202 -0
- brainiac_cli-0.16.0.dist-info/top_level.txt +1 -0
brain/config.py
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
"""Path + location policy for the brain engine.
|
|
2
|
+
|
|
3
|
+
The derived SQLite index lives under a per-user **application-data** directory,
|
|
4
|
+
NOT under Documents/Desktop (Windows Controlled-Folder-Access protected paths) —
|
|
5
|
+
CORE-01 hardening. The index is derived and disposable; delete-and-rebuild from
|
|
6
|
+
`vault/` is always safe, so its exact location is policy, not truth.
|
|
7
|
+
|
|
8
|
+
Resolution order for the index directory:
|
|
9
|
+
1. ``$BRAIN_INDEX_DIR`` (explicit override; tests use this)
|
|
10
|
+
2. Windows : ``%LOCALAPPDATA%\\profile-a-brain``
|
|
11
|
+
3. macOS : ``~/Library/Application Support/profile-a-brain``
|
|
12
|
+
4. Linux/* : ``$XDG_DATA_HOME/profile-a-brain`` or ``~/.local/share/...``
|
|
13
|
+
|
|
14
|
+
Per-vault isolation (0.3.0): under the app-data base, each vault gets its own
|
|
15
|
+
subdirectory ``vaults/<name>-<hash8>/`` derived from the resolved vault path —
|
|
16
|
+
N vaults on one machine get N independent indexes + audit chains with no env
|
|
17
|
+
var to remember. ``$BRAIN_INDEX_DIR`` still overrides completely (no nesting).
|
|
18
|
+
"""
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import hashlib
|
|
22
|
+
import os
|
|
23
|
+
import sys
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
APP_NAME = "profile-a-brain"
|
|
27
|
+
INDEX_FILENAME = "index.sqlite"
|
|
28
|
+
|
|
29
|
+
# Host/VM trust split (S06). The HOST broker is the sole writer (signs the audit
|
|
30
|
+
# chain, mutates the index, publishes snapshots). The Cowork Linux VM is a
|
|
31
|
+
# READ + DRAFT surface only — it may never write notes, open the index in
|
|
32
|
+
# WAL/write mode, or resolve a signing key. Role is resolved from $BRAIN_ROLE,
|
|
33
|
+
# default "host". See AGENTS.md §6 + docs/cowork-windows-install.md.
|
|
34
|
+
ROLE_HOST = "host"
|
|
35
|
+
ROLE_VM = "vm"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def role(explicit: str | None = None) -> str:
|
|
39
|
+
"""Resolve the trust role: explicit arg > ``$BRAIN_ROLE`` > ``host``."""
|
|
40
|
+
val = (explicit or os.environ.get("BRAIN_ROLE") or ROLE_HOST).strip().lower()
|
|
41
|
+
return ROLE_VM if val == ROLE_VM else ROLE_HOST
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def apply_role_embedder_policy(resolved_role: str) -> None:
|
|
45
|
+
"""The VM leg fails CLOSED on a dead embedder by default (DV-03, 2026-07-09).
|
|
46
|
+
|
|
47
|
+
A Cowork VM that silently answered semantic queries with random HASH vectors
|
|
48
|
+
(onnxruntime missing in the zero-install shim's python) is the exact failure
|
|
49
|
+
this guards: ``role=vm`` defaults ``$BRAIN_REQUIRE_REAL_EMBEDDER=1`` so the
|
|
50
|
+
implicit hash fallback RAISES instead of degrading. It is a no-op whenever a
|
|
51
|
+
real embedder is present (the flag only bites on a dead one), and lexical
|
|
52
|
+
verbs (``grep``/``bases-query``) never embed, so they keep working — only the
|
|
53
|
+
semantic path (``search``/``hybrid-search``) fails loud. Skipped when the
|
|
54
|
+
operator explicitly chose hash (``$BRAIN_EMBEDDER=hash``) or already pinned
|
|
55
|
+
the flag either way. Host leg is unchanged (warns, never fails closed)."""
|
|
56
|
+
if resolved_role != ROLE_VM:
|
|
57
|
+
return
|
|
58
|
+
if os.environ.get("BRAIN_EMBEDDER", "").strip().lower() == "hash":
|
|
59
|
+
return
|
|
60
|
+
os.environ.setdefault("BRAIN_REQUIRE_REAL_EMBEDDER", "1")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _app_data_base() -> Path:
|
|
64
|
+
"""Per-user app-data base dir (no vault scoping).
|
|
65
|
+
|
|
66
|
+
Never returns a Controlled-Folder-Access path (Documents/Desktop/Pictures).
|
|
67
|
+
"""
|
|
68
|
+
if sys.platform.startswith("win"):
|
|
69
|
+
base = os.environ.get("LOCALAPPDATA") or str(Path.home() / "AppData" / "Local")
|
|
70
|
+
return Path(base) / APP_NAME
|
|
71
|
+
if sys.platform == "darwin":
|
|
72
|
+
return Path.home() / "Library" / "Application Support" / APP_NAME
|
|
73
|
+
# Linux / BSD / Cowork VM
|
|
74
|
+
base = os.environ.get("XDG_DATA_HOME") or str(Path.home() / ".local" / "share")
|
|
75
|
+
return Path(base) / APP_NAME
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def index_dir(vault: str | os.PathLike[str] | None = None) -> Path:
|
|
79
|
+
"""Per-vault app-data directory holding this vault's index + audit chain.
|
|
80
|
+
|
|
81
|
+
``$BRAIN_INDEX_DIR`` overrides completely (returned as-is, no per-vault
|
|
82
|
+
nesting — tests and constrained deployments rely on that). Otherwise each
|
|
83
|
+
resolved vault path maps to its own ``vaults/<name>-<hash8>/`` subdir, so
|
|
84
|
+
any number of vaults coexist without sharing an index or audit chain.
|
|
85
|
+
"""
|
|
86
|
+
override = os.environ.get("BRAIN_INDEX_DIR")
|
|
87
|
+
if override:
|
|
88
|
+
return Path(override).expanduser()
|
|
89
|
+
v = vault_root(vault)
|
|
90
|
+
slug = f"{v.name}-{hashlib.sha256(str(v).encode()).hexdigest()[:8]}"
|
|
91
|
+
return _app_data_base() / "vaults" / slug
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def index_path(vault: str | os.PathLike[str] | None = None) -> Path:
|
|
95
|
+
"""Absolute path to this vault's SQLite index file."""
|
|
96
|
+
return index_dir(vault) / INDEX_FILENAME
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def ensure_index_dir(vault: str | os.PathLike[str] | None = None) -> Path:
|
|
100
|
+
d = index_dir(vault)
|
|
101
|
+
d.mkdir(parents=True, exist_ok=True)
|
|
102
|
+
return d
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def default_audit_log(vault: str | os.PathLike[str] | None = None) -> Path:
|
|
106
|
+
"""Default per-vault audit-chain path, with a one-time legacy notice.
|
|
107
|
+
|
|
108
|
+
Pre-0.3.0 installs kept ONE global index + audit chain directly under the
|
|
109
|
+
app-data base. The index is a disposable cache (just rebuild), but the old
|
|
110
|
+
audit chain must not silently disappear: it stays frozen at the legacy
|
|
111
|
+
path — verifiable there forever — and new writes start a fresh per-vault
|
|
112
|
+
chain (same model as a key rotation, see SECURITY.md).
|
|
113
|
+
"""
|
|
114
|
+
log = index_dir(vault) / "audit_chain.jsonl"
|
|
115
|
+
legacy = _app_data_base() / "audit_chain.jsonl"
|
|
116
|
+
if not os.environ.get("BRAIN_INDEX_DIR") and legacy.exists() and not log.exists():
|
|
117
|
+
print(
|
|
118
|
+
f"brain: NOTE — a pre-0.3.0 global audit chain exists at {legacy}. "
|
|
119
|
+
f"It stays there, frozen and verifiable; new writes chain at {log}.",
|
|
120
|
+
file=sys.stderr,
|
|
121
|
+
)
|
|
122
|
+
return log
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
# --------------------------------------------------------------------------
|
|
126
|
+
# file permission policy (hardening pass)
|
|
127
|
+
# --------------------------------------------------------------------------
|
|
128
|
+
# The derived SQLite index and the published read-only snapshot can carry note
|
|
129
|
+
# bodies up to and including MNPI-tier content (the classification gate is an
|
|
130
|
+
# egress *decision*, not containment -- see docs/operations/egress-provider-
|
|
131
|
+
# posture.md §2). Neither must ever be left world-readable. The snapshot was
|
|
132
|
+
# previously chmod'd 0o444 (read-only, but readable by every local account on a
|
|
133
|
+
# shared/multi-user machine); the index inherited whatever the process umask
|
|
134
|
+
# happened to be (often 0o644 on a typical single-user default). Both are now
|
|
135
|
+
# tightened to owner-only immediately after creation, regardless of umask.
|
|
136
|
+
SECURE_FILE_MODE = 0o600 # owner rw only; use 0o640 if a deployment intentionally
|
|
137
|
+
# shares index/snapshot files with a trusted local group
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def secure_file_permissions(path: "os.PathLike[str] | str", mode: int = SECURE_FILE_MODE) -> None:
|
|
141
|
+
"""Best-effort tighten ``path`` to ``mode`` (default owner-only 0600).
|
|
142
|
+
|
|
143
|
+
Never raises: a chmod call that fails (unsupported filesystem, Windows ACL
|
|
144
|
+
semantics where POSIX mode bits are only partially honored, a race where the
|
|
145
|
+
file vanished) must not break index/snapshot creation -- it degrades to
|
|
146
|
+
"as restrictive as the platform default allowed", not a crash.
|
|
147
|
+
"""
|
|
148
|
+
try:
|
|
149
|
+
os.chmod(path, mode)
|
|
150
|
+
except OSError:
|
|
151
|
+
pass
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def vault_root(explicit: str | os.PathLike[str] | None = None) -> Path:
|
|
155
|
+
"""Resolve the vault root: explicit arg > ``$BRAIN_VAULT`` > CWD/vault.
|
|
156
|
+
|
|
157
|
+
When it falls back to CWD/vault, warn (stderr) if ``./vault`` is not yet a
|
|
158
|
+
Brainiac vault (no ``./vault/.brain``) — so brain never SILENTLY writes to a
|
|
159
|
+
phantom ``./vault/.brain/`` in whatever directory it happened to run from (a
|
|
160
|
+
footgun that once scattered 231 drafts into a stray ``migration/vault/``
|
|
161
|
+
mid-migration). Creation flows (``brain init``, the installer's sample-vault
|
|
162
|
+
build) still work: the fallback path is unchanged — only now it is loud.
|
|
163
|
+
"""
|
|
164
|
+
if explicit:
|
|
165
|
+
return Path(explicit).expanduser().resolve()
|
|
166
|
+
env = os.environ.get("BRAIN_VAULT")
|
|
167
|
+
if env:
|
|
168
|
+
return Path(env).expanduser().resolve()
|
|
169
|
+
cwd_vault = (Path.cwd() / "vault").resolve()
|
|
170
|
+
if not (cwd_vault / ".brain").is_dir():
|
|
171
|
+
import sys as _sys
|
|
172
|
+
print(
|
|
173
|
+
f"brain: WARNING no --vault/$BRAIN_VAULT given; resolved to {cwd_vault}, "
|
|
174
|
+
f"which is not yet a vault (no {cwd_vault / '.brain'}). This is the "
|
|
175
|
+
f"CWD/vault fallback ({Path.cwd()} + 'vault'), not an error -- but if this "
|
|
176
|
+
f"isn't the vault you meant, pin it: export BRAIN_VAULT={cwd_vault} "
|
|
177
|
+
"(or pass --vault explicitly on every call). Otherwise brain creates/uses "
|
|
178
|
+
"a vault at the path above.",
|
|
179
|
+
file=_sys.stderr,
|
|
180
|
+
)
|
|
181
|
+
return cwd_vault
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def vault_slug8(vault: str | os.PathLike[str] | None = None) -> str:
|
|
185
|
+
"""The 8-hex per-vault id — the SAME hash the per-vault app-data dir uses
|
|
186
|
+
(see ``index_dir``). One vault => one stable id, distinct vaults => distinct
|
|
187
|
+
ids, so per-vault artifacts (index, audit chain, nightly task) never collide."""
|
|
188
|
+
return hashlib.sha256(str(vault_root(vault)).encode()).hexdigest()[:8]
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def nightly_label(vault: str | os.PathLike[str] | None = None) -> str:
|
|
192
|
+
"""launchd label (macOS) for this vault's nightly maintenance task, made
|
|
193
|
+
PER-VAULT so two registered vaults don't install to one shared label and
|
|
194
|
+
clobber each other's job. The legacy single label
|
|
195
|
+
``com.profile-a-brain.daily-brief`` is migrated away from on next install."""
|
|
196
|
+
return f"com.brainiac.nightly.{vault_slug8(vault)}"
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
# --------------------------------------------------------------------------
|
|
200
|
+
# workspace runtime locations (S06 — Cowork-Windows workspace-install path)
|
|
201
|
+
# --------------------------------------------------------------------------
|
|
202
|
+
# The Cowork Linux VM mounts ONLY the workspace and sees ``vault/.brain/``. The
|
|
203
|
+
# runtime dir holds the per-arch ``brain`` binary, the bundled ``model.onnx``,
|
|
204
|
+
# the read-only published ``snapshot/`` the VM reads, and the writable
|
|
205
|
+
# ``capture-inbox/`` the VM drops drafts into. All four resolve from env first so
|
|
206
|
+
# a workspace install can point them at a workspace-root ``.brain/`` if desired;
|
|
207
|
+
# the default keeps everything under the gitignored ``vault/.brain/`` (spec §2),
|
|
208
|
+
# which ``notes.scan_vault`` already excludes from indexing.
|
|
209
|
+
def brain_runtime_dir(vault: str | os.PathLike[str] | None = None) -> Path:
|
|
210
|
+
override = os.environ.get("BRAIN_RUNTIME_DIR")
|
|
211
|
+
if override:
|
|
212
|
+
return Path(override).expanduser()
|
|
213
|
+
return vault_root(vault) / ".brain"
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def snapshot_dir(vault: str | os.PathLike[str] | None = None) -> Path:
|
|
217
|
+
"""Dir holding the read-only published snapshot (DB + manifest)."""
|
|
218
|
+
override = os.environ.get("BRAIN_SNAPSHOT_DIR")
|
|
219
|
+
if override:
|
|
220
|
+
return Path(override).expanduser()
|
|
221
|
+
return brain_runtime_dir(vault) / "snapshot"
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def snapshot_db_path(vault: str | os.PathLike[str] | None = None) -> Path:
|
|
225
|
+
"""Absolute path to the read-only snapshot DB the VM ``brain`` reads."""
|
|
226
|
+
from .snapshot import SNAPSHOT_DB
|
|
227
|
+
|
|
228
|
+
return snapshot_dir(vault) / SNAPSHOT_DB
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def capture_inbox_dir(vault: str | os.PathLike[str] | None = None) -> Path:
|
|
232
|
+
"""Writable dir the VM drops capture drafts into (host drains it on invoke).
|
|
233
|
+
|
|
234
|
+
Lives under ``.brain/`` so it is host-visible on the shared mount AND
|
|
235
|
+
excluded from ``scan_vault`` — a draft is never auto-indexed; only the host
|
|
236
|
+
promotes it (sign + index) via drain-on-invoke.
|
|
237
|
+
"""
|
|
238
|
+
override = os.environ.get("BRAIN_CAPTURE_INBOX")
|
|
239
|
+
if override:
|
|
240
|
+
return Path(override).expanduser()
|
|
241
|
+
return brain_runtime_dir(vault) / "capture-inbox"
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def memory_dir(vault: str | os.PathLike[str] | None = None) -> Path:
|
|
245
|
+
"""Session-memory dir (ADR-0003 Ruling 4, MEM-01/02) — handoff.md, hot.md,
|
|
246
|
+
lessons.md, archive/. Host-only, never indexed (under ``.brain/``)."""
|
|
247
|
+
return brain_runtime_dir(vault) / "memory"
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def brief_dir(vault: str | os.PathLike[str] | None = None) -> Path:
|
|
251
|
+
"""Generated HTML brief/digest dir (AUT-01/AUT-03, ADR-0003 Ruling c) —
|
|
252
|
+
gitignored, local, snapshot-adjacent (under ``.brain/``). HOST-ONLY: never
|
|
253
|
+
committed, never published into the VM snapshot."""
|
|
254
|
+
return brain_runtime_dir(vault) / "brief"
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def recommendations_open_path(vault: str | os.PathLike[str] | None = None) -> Path:
|
|
258
|
+
"""Open-recommendations JSONL (MEM-03) — one JSON object per line, lifecycle
|
|
259
|
+
``open -> surfaced -> (resolved, removed here + logged)``."""
|
|
260
|
+
return memory_dir(vault) / "recommendations-open.jsonl"
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def recommendations_log_path(vault: str | os.PathLike[str] | None = None) -> Path:
|
|
264
|
+
"""Resolved-recommendations log (MEM-03) — append-only Markdown, one dated
|
|
265
|
+
entry per closed recommendation."""
|
|
266
|
+
return memory_dir(vault) / "recommendations-log.md"
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def maintain_state_path(vault: str | os.PathLike[str] | None = None) -> Path:
|
|
270
|
+
"""Per-branch ``brain maintain`` state (ADR-0003 Ruling 5/d) — ONE file
|
|
271
|
+
serving both the catch-up last-run markers and the heartbeat (last
|
|
272
|
+
attempt/status/consecutive-failures per branch). Read by
|
|
273
|
+
``.claude/hooks/session-start.sh`` for the stale-nightly warning."""
|
|
274
|
+
override = os.environ.get("BRAIN_MAINTAIN_STATE")
|
|
275
|
+
if override:
|
|
276
|
+
return Path(override).expanduser()
|
|
277
|
+
return brain_runtime_dir(vault) / "maintain-state.json"
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def maintain_lock_path(vault: str | os.PathLike[str] | None = None) -> Path:
|
|
281
|
+
"""Single-runner lock for ``brain maintain`` (HARDENED:codex) — a second
|
|
282
|
+
concurrent run skips with a logged notice instead of racing the first."""
|
|
283
|
+
return brain_runtime_dir(vault) / "maintain.lock"
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def graph_dir(vault: str | os.PathLike[str] | None = None) -> Path:
|
|
287
|
+
"""GRF-01 discovery-graph runtime artifacts (ADR-0003 Ruling 6/(a)) —
|
|
288
|
+
gitignored, host-only, never published into the VM snapshot. Holds the
|
|
289
|
+
published ``graph.json`` + its corpus-drift ``manifest.json``, and (only on
|
|
290
|
+
a failed/partial build) a ``BUILD_FAILED.json`` marker written to a path
|
|
291
|
+
SEPARATE from the consumable ``graph.json`` (HARDENED:codex)."""
|
|
292
|
+
return brain_runtime_dir(vault) / "graph"
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def graph_manifest_path(vault: str | os.PathLike[str] | None = None) -> Path:
|
|
296
|
+
"""Per-note content-hash manifest the drift gate compares against."""
|
|
297
|
+
return graph_dir(vault) / "manifest.json"
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def graph_json_path(vault: str | os.PathLike[str] | None = None) -> Path:
|
|
301
|
+
"""The published, non-authoritative discovery graph."""
|
|
302
|
+
return graph_dir(vault) / "graph.json"
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def graph_build_failed_marker_path(vault: str | os.PathLike[str] | None = None) -> Path:
|
|
306
|
+
"""A failed/partial build's marker — NEVER the consumable ``graph.json``
|
|
307
|
+
path, so a partial build can never be mistaken for a valid publish."""
|
|
308
|
+
return graph_dir(vault) / "BUILD_FAILED.json"
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def _health_history_root(vault: str | os.PathLike[str] | None = None) -> Path:
|
|
312
|
+
"""Directory holding ``health-history.jsonl`` + its lock + archive
|
|
313
|
+
segments. Honors ``$BRAIN_HEALTH_HISTORY`` (the override's PARENT dir
|
|
314
|
+
becomes this root) so the lock and archive dir never drift from the
|
|
315
|
+
actual history file location — fix for review finding [5]: they used to
|
|
316
|
+
always resolve under ``brain_runtime_dir`` even when the history file
|
|
317
|
+
itself was overridden elsewhere (e.g. in tests), so a test pointing
|
|
318
|
+
``$BRAIN_HEALTH_HISTORY`` at a tmp path still rotated/locked against the
|
|
319
|
+
real vault's ``.brain/`` dir."""
|
|
320
|
+
override = os.environ.get("BRAIN_HEALTH_HISTORY")
|
|
321
|
+
if override:
|
|
322
|
+
return Path(override).expanduser().parent
|
|
323
|
+
return brain_runtime_dir(vault)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def health_history_path(vault: str | os.PathLike[str] | None = None) -> Path:
|
|
327
|
+
"""OBS-01 per-run health-metrics JSONL — one record per ``maintain`` run.
|
|
328
|
+
Rotated at ~1MB into ``health_archive_dir``."""
|
|
329
|
+
override = os.environ.get("BRAIN_HEALTH_HISTORY")
|
|
330
|
+
if override:
|
|
331
|
+
return Path(override).expanduser()
|
|
332
|
+
return brain_runtime_dir(vault) / "health-history.jsonl"
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def health_history_lock_path(vault: str | os.PathLike[str] | None = None) -> Path:
|
|
336
|
+
"""Dedicated short-lived exclusive lock serializing append+rotation
|
|
337
|
+
(OBS-01 correction 3) — separate from ``maintain_lock_path`` because a
|
|
338
|
+
stale/broken maintain lock must never also jam health-history writes."""
|
|
339
|
+
return _health_history_root(vault) / "health-history.lock"
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def health_archive_dir(vault: str | os.PathLike[str] | None = None) -> Path:
|
|
343
|
+
"""Rotated health-history segments — ``.brain/archive/health-history-*.jsonl``
|
|
344
|
+
(or alongside an overridden ``$BRAIN_HEALTH_HISTORY`` file's own dir)."""
|
|
345
|
+
return _health_history_root(vault) / "archive"
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def health_sparse_path(vault: str | os.PathLike[str] | None = None) -> Path:
|
|
349
|
+
"""Never-rotated sidecar holding ONLY the sparse health metrics
|
|
350
|
+
(``golden_score``, ``synthesis_cost_usd``) whenever they are non-null —
|
|
351
|
+
review finding [7]. The main ``health-history.jsonl`` read is bounded to a
|
|
352
|
+
~14-day window (fix [6]) which would silently truncate a sparse metric's
|
|
353
|
+
prior observation older than that (golden scores land on a >quarterly
|
|
354
|
+
cadence), disabling its regression check. This sidecar grows ~one line per
|
|
355
|
+
week and is trivially small forever, so it is never windowed or rotated —
|
|
356
|
+
``health_trend`` reads it in full for the sparse comparisons."""
|
|
357
|
+
return _health_history_root(vault) / "health-sparse.jsonl"
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def anchor_dir() -> Path | None:
|
|
361
|
+
"""Off-host audit-chain anchor dir (SEC-03), if configured.
|
|
362
|
+
|
|
363
|
+
No path lives under the vault by default (anchoring INTO the vault buys
|
|
364
|
+
nothing — see brain.anchor). ``None`` means no anchor is configured; the
|
|
365
|
+
scheduled `integrity`/`maintain` check then has no truncation guarantee
|
|
366
|
+
to fold in and says so explicitly (M-2)."""
|
|
367
|
+
override = os.environ.get("BRAIN_ANCHOR_DIR")
|
|
368
|
+
return Path(override).expanduser() if override else None
|