You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Beyond the three-PR brief. I found this doing #774's live-check, so per canon rule 9 it opens its own PR rather than riding on that branch. Close it if the lane does not want it — but read the demonstration first, because the trap is laid across the ratified live-check procedure itself.
Stacked on #774 (both touch the same script). Retarget to main once that lands.
The defect
The marker write is not DB-scoped.src/brainlayer/fallback_replay.py never reads a DB path at all — grep -n "db_path\|DEFAULT_DB_PATH\|canonical" returns nothing. replay_entry stores, then writes chunk_id into the file under --gits-root, whatever --db pointed at.
So the shape the ratified procedure asks for — a copy of the DB — permanently stamps every pending file in the real tree with an id that exists only in a throwaway DB. is_pending_entry then answers "not pending", and those memories are hidden with nothing left to say they were never stored.
Demonstrated on a scratch tree against a brand-new empty DB:
REPLAYED .../demo.md -> STORED manual-a282a4652bf84ce2
file now carries chunk_id: manual-a282a4652bf84ce2
rows in the CANONICAL DB with that id: 0
inventory pending_count: 1 -> 0 # the debt is now invisible
The plan for this work says "Test against a COPY of the real DB first", and the 2026-08-02 ratification requires it for anything touching stored data. Following that instruction with this tool, as shipped, silently hides every memory it touches. I only avoided it in #774 and #777 by also copying the fallback files — which nothing told me to do and nothing required.
The fix
marker_target_hazard fails closed on exactly the mismatch that hides data: a non-canonical --db while --gits-root resolves to the real ~/Gits. Both honest shapes stay allowed:
--db
--gits-root
copy
copy of the files
✅ a live-check
canonical
real tree
✅ the production drain
copy
real tree
⛔ exits 2, says why
canonical
copy
✅ (harmless)
The queued path is guarded the same way — it writes queued_chunk_id into the same files.
A silence next to it
_emit never printed result["error"] in text mode, so a refusal and a clean run emitted the same three count lines and only the exit code told them apart. That already applied to the two pre-existing early exits (--legacy requires queued replay and the --limit refusal), which were equally invisible. Text mode now leads with ERROR: <reason>.
Verified against the live tree
$ ... --gits-root ~/Gits --db <copy> --apply --direct-db-write --limit 200
ERROR: refusing to replay: --db `...livecheck/replay-a.db` is not the canonical DB
(`...brainlayer.db`), but --gits-root is the real tree `/Users/etanheyman/Gits`, so this run
would mark the real fallback files as replayed with chunk ids that exist only in that DB —
hiding those memories with no trace. Copy the fallback files too and pass --gits-root <copy>,
or use the canonical DB.
exit code: 2
$ inventory ~/Gits
pending_count: 122 legacy: 0 # nothing was marked
Test that fails on the base commit
Four, in tests/test_fallback_replay.py. The two guard tests fail on the base (main() == 0, no refusal); the two allow-tests pass on the base and must keep passing, which is what stops the guard from blocking the production drain. Measured before implementing.
Scope
XS. 38 hand-written source lines, 149 test lines. Read-only against the canonical DB; no production fallback file was written at any point.
ruff check and ruff format --check clean. Scoped pre-push; tests/test_fallback_replay.py 49 passed.
Medium Risk
The script marks production docs.local fallback files; a guard regression could either block the real drain or fail open and silently hide pending memories again.
Overview
Adds a fail-closed safety gate to replay_brain_store_fallbacks.py for --apply: before inventory runs, marker_target_hazard blocks replay when --db is not the canonical DB (via get_canonical_db_path, not env-affected DEFAULT_DB_PATH) while --gits-root still targets the real ~/Gits tree—including subtrees, symlinks, and case aliases detected with os.path.samefile. Unsafe runs exit 2 with an explicit refusal; copy DB + copy files and canonical DB + real tree stay allowed for live-check and production drain. The same guard covers queued replay because it also writes markers into fallback files.
Refactors main into helpers for legacy parsing, queue replay, and direct DB replay. Text-mode _emit now prints ERROR: for refusals and other result["error"] cases. Tests add broad coverage for refuse/allow shapes, guard ordering, and end-to-end production drain wiring.
Reviewed by Cursor Bugbot for commit 9d36a60. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Refuse noncanonical DB apply against the real Gits tree in replay_brain_store_fallbacks
Adds an early apply-mode guard in main that checks marker_target_hazard before scope loading or fallback inventory, returning status 2 on unsafe combinations.
The guard treats the real home Gits root, its subtrees, symlink aliases, and case-insensitive path aliases as the real tree, and allows only the canonical DB (resolved via get_canonical_db_path) with it.
Refactors main by extracting legacy parsing, queued replay, and direct replay into separate helpers; adds _emit printing of an ERROR: line in text mode for refusals and replay failures.
Adds extensive tests covering refusal and allowlist shapes, subtree/symlink/case aliases, guard ordering, and end-to-end production drain.
Behavioral Change: unsafe apply invocations that previously proceeded now exit early with status 2 and a refusal message; safe copy/copy and canonical-DB/real-tree combinations are unaffected.
Is the guard's predicate the right one? It keys on --gits-root resolving to Path.home()/"Gits". That catches the real tree wherever it is reached from, but it does NOT catch a symlink farm or a bind mount pointing at the same files under a different path. Tell me if there is a cheap way to compare the actual trees instead.
Fail-closed direction. A non-canonical DB with the real tree is refused; the canonical DB with a COPY of the files is allowed as harmless. Is that second case actually harmless, or can it hide something too?
DEFAULT_DB_PATH is read as a module global so a test can patch it. Flag it if that makes the guard bypassable in a way that matters.
The deeper fix would be to make the marker itself DB-scoped — record which DB a replay went into, and treat a marker from a non-canonical DB as still pending. I kept this PR to an entry-point guard to stay XS. Say if you think the schema change is the right call instead.
We reviewed changes in 2448231...9d36a60 on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.
Some issues found as part of this review are outside of the diff in this pull request and aren't shown in the inline review comments due to GitHub's API limitations. You can see those issues on the DeepSource dashboard.
AI Review is run only on demand for your team. We're only showing results of static analysis review right now. To trigger AI Review, comment @deepsourcebot review on this thread.
@EtanHey I found one safety gap in the guard predicate.
Path.resolve() detects a --gits-root symlink that resolves directly to ~/Gits. It does not detect a bind mount. It also does not detect a mixed tree where a repository or a directory inside an otherwise copied tree points at the real files. Those forms can still receive marker writes through a non-canonical DB.
A cheap stronger check is available. After inventory, compare each pending marker target with its corresponding path below ~/Gits:
canonical_path=real_gits_root/entry.path.relative_to(gits_root)
ifcanonical_path.exists() andentry.path.samefile(canonical_path):
# This marker target is a real fallback file.
Path.samefile() compares filesystem identity. It detects directory symlinks and bind-mounted files on normal local filesystems. Run this check for every structured and legacy entry that can receive a marker. Refuse if any target matches and --db is non-canonical. This also covers a symlink farm at the granularity that matters: the file that would be rewritten.
A root-level samefile() check alone is not sufficient. It misses a copied root that contains mounted or linked real repositories.
The allowed direction is harmless for the stated failure mode:
Canonical DB + copied files can write a marker only into the copied files.
The real fallback files remain pending.
A later production replay can store, merge, or deduplicate the real pending entry and then mark the real file.
This shape can add an extra write attempt to the canonical DB. It cannot hide the real fallback debt. I would keep it allowed.
The patchable DEFAULT_DB_PATH does not create a meaningful production bypass. The module global is mutable only for an in-process Python caller that can already import and modify the script. The normal CLI cannot change it. The test seam is reasonable. A small improvement is to pass the canonical path explicitly into marker_target_hazard() if you want to make the dependency visible, but I do not consider that required for this PR.
A DB-scoped marker is the stronger long-term fix. It would make the invariant local to is_pending_entry() instead of dependent on this CLI entry point. It requires a stable DB identity, updates to marker writers, inventory semantics, queue-drain semantics, and migration behavior for existing markers. The entry-point guard is appropriate for this XS fix, provided it checks actual marker targets rather than only the root path.
I recommend blocking on the per-entry identity check.
…(round 1)
Round-1 review, #780. All five findings real; the two HIGHs were measured on the
unfixed guard before touching it.
**@183 (HIGH) — exact equality failed open on every subtree.**
exact ~/Gits -> REFUSED
subtree ~/Gits/brainlayer -> ALLOWED <- the hole
`--gits-root ~/Gits/<repo>` with a copy DB still inventories and marks the
production `docs.local` files under that repo: the same silent hide the PR exists to
stop. A guard whose whole job is to fail closed cannot have a shape that fails open.
Now `is_relative_to`, with both sides `resolve()`d so a symlinked or `..`-laden path
cannot dodge it, and an unresolvable path refuses rather than passes. After:
exact / subtree / .worktrees deep -> REFUSED, REFUSED, REFUSED
**@185 (MEDIUM) — the allowlist was the env, not the disk.** `paths.DEFAULT_DB_PATH`
is `resolve_db_path()` evaluated at import, so it becomes whatever `BRAINLAYER_DB`
says. Measured with `BRAINLAYER_DB=/tmp/copy.db`:
DEFAULT_DB_PATH = /tmp/copy.db <- allowlisted!
get_canonical_db_path = ~/.local/share/brainlayer/brainlayer.db
So the copy became "canonical" and the trap reopened through the env instead of
through `--db`. `canonical_db_target()` now reads `get_canonical_db_path()`, and the
refusal message no longer calls an env override canonical.
**@1362 (HIGH) — the production-allow test never reached the branch it claimed.** It
passed a tmp gits-root, so the guard returned None before the DB was consulted and
the `DEFAULT_DB_PATH` monkeypatch was dead. A regression that always refused on the
real tree — blocking the production drain outright — still got `main() == 0`. The
test now fakes `Path.home()` so real-tree + canonical-DB is genuinely exercised, and
patches `get_canonical_db_path` so the whole chain runs, not a shim.
**@1305 (MEDIUM) — the refusal path walked the real tree on its way to saying no.**
`main()` called `load_scope_map` + `inventory_fallbacks` before the guard. The guard
now runs first, and a test makes both of those raise if reached, proving the refusal
touches nothing.
**@1282 (LOW) — both fail-open shapes now have tests**, plus a deeper-subtree case.
Also: moving the guard earlier pushed `C901 main is too complex` from main's
pre-existing 11 to 13. Rather than worsen a metric DeepSource already fails on, the
two replay engines are extracted into `_replay_via_queue` / `_replay_direct_to_db`
(same code, same order, only nesting changes) and the guard receipt into
`_refuse_marker_hazard`. C901 is now GONE from this file — below the threshold for
the first time, clearing a failure that predates this lane.
Smoke-tested both engines after the extraction: queued path -> 3 DEFERRED, 3 queue
files; direct path -> 1 STORED. Live-verified the guard on this machine, and `~/Gits`
still reports pending_count 122 — nothing marked.
tests/test_fallback_replay.py 60 passed. Rebased onto #774's round-1 commit
(conflict was two additive test blocks; both kept).
Agent: brainlayerClaude-ca745a2b (claude-opus-5[1m])
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
You are right that this is the whole point of the PR inverted: --gits-root ~/Gits/<repo> with a copy DB still inventories and marks the production docs.local files under that repo. A guard whose only job is to fail closed cannot have a shape that fails open.
Fixed with is_relative_to, both sides resolve()d so a symlinked or ..-laden path cannot dodge it, and an unresolvable path refuses rather than passes (cannot prove safe, so not safe). After:
Exactly as you said: paths.DEFAULT_DB_PATH is resolve_db_path() evaluated at import, so an env-selected copy became canonical and the trap reopened through BRAINLAYER_DB instead of --db. canonical_db_target() now reads get_canonical_db_path(), and the refusal message no longer calls an env override canonical.
@1362 (HIGH) — the allow-test never reached the branch it claimed
Confirmed: a tmp gits-root meant the guard returned None at the first check, so the DEFAULT_DB_PATH monkeypatch was dead and a regression that always refused on the real tree — blocking the production drain outright — still got main() == 0. The test now fakes Path.home() so real-tree plus canonical-DB is genuinely exercised, and patches get_canonical_db_path so the full chain runs rather than a shim.
@1305 (MEDIUM) — the refusal walked the real tree on its way to saying no
Right. The guard now runs beforeload_scope_map / inventory_fallbacks, and a test makes both raise if reached, so the refusal path provably touches nothing.
@1282 (LOW) — both fail-open shapes now have tests, plus a deeper-subtree case.
One thing I did beyond the findings
Moving the guard earlier pushed C901 main is too complex from main's pre-existing 11 to 13. Rather than worsen a metric DeepSource already fails on, I extracted the two replay engines into _replay_via_queue / _replay_direct_to_db (same code, same order, only nesting changes) and the receipt into _refuse_marker_hazard. C901 is now gone from this file entirely — below threshold for the first time.
Because that refactor moves real execution paths, I smoke-tested both engines after it: queued gave 3 DEFERRED and 3 queue files, direct gave 1 STORED. Live-verified the guard on this machine, and ~/Gits still reports pending_count 122 — nothing marked.
On the deeper fix you raised
I still think the entry-point guard is right for this PR, but it only protects callers that go through the script: replay_entry called directly from library code still has no DB scoping. Closing that means a frontmatter schema change (replayed_into_db) plus is_pending_entry treating a non-canonical marker as still pending — not XS, and it would want its own live-check. Say the word and I will row it.
…d` (round 2)
Round-2 review, #774. All three real.
**@618-619 (medium) — round 1's own invariant, broken one line below where I stated
it.** `""` and `" "` are PRESENT values. My round-1 docstring says only an ABSENT
key defaults to `stored`, and then the code did `if not text: return OUTCOME_STORED`.
`mcp/store_handler.py` takes a blank through `result.get("outcome", "stored")`
unchanged and `_store_receipt` rejects it, so this was fail-open on the strongest
claim in the vocabulary — the exact defect round 1 was supposed to close, one layer
down. Blank now goes to `unresolved` with the raw value in the error. Tested for
`""`, `" "` and `"\t\n"`.
**@186-192 (low) — wrong word in the receipt.** A store answering
`{"outcome": "error"}` with no chunk id described its own failure; filing it as
`rejected` conflates two distinct #725 states. Now `error` when the store said so,
and still `rejected` when the store returned nothing at all and has no word for
itself. `_raw_store_outcome` extracted so the three call sites read the field one way.
**@1280-1295 (low) — the CLI contract was unpinned.** Unresolved was unit-tested but
nothing asserted the script exits 1 and persists `outcome_counts.unresolved` through
`--receipt`. A regression clearing the error on an unresolved row would have kept
exit 0 while still printing a soft label — a green run over a write nobody can vouch
for. Now pinned end to end.
Also moved `_guard_module` into this file as the shared CLI-test loader; #780 had
defined it locally and this is its right layer.
tests/test_fallback_replay.py 53 passed; 101 with test_status_truthfulness,
test_transport_replay_attribution and test_brainstore.
Agent: brainlayerClaude-ca745a2b (claude-opus-5[1m])
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…the guard (round 2)
Round-2 review, #780. Both real, measured on this APFS volume before the fix.
**@162 (HIGH) — a differently cased path to the real tree failed open.**
os.path.samefile(~/Gits, ~/gits) True
Path.resolve() equal? False (~/Gits vs ~/gits)
guard on ~/Gits REFUSED
guard on ~/gits ALLOWED <- the hole
guard on ~/gits/brainlayer ALLOWED <- and its subtree
`resolve()` + `is_relative_to` is a string comparison. macOS is case-insensitive by
default and is the fleet's primary host; ubuntu CI is case-sensitive and would never
have surfaced this. Round 1 closed the subtree hole and left the casing one directly
beside it.
**One correction to the suggested remedy:** `realpath` + `os.path.normcase` does NOT
close this. `normcase` is a no-op on darwin — it only folds case on Windows — so on
the machine that has the bug it changes nothing. Only the inode knows, so both checks
now go through `os.path.samefile`, with a resolved-string compare only for the case
where neither path exists (two missing paths cannot be the same file).
`_names_real_gits_tree` walks to the nearest EXISTING ancestor before comparing,
because a subtree's leaf may not exist yet while the case alias is real one level up.
An `OSError` anywhere in it returns True: cannot prove it is outside the tree means
cannot prove it is safe.
**@165 (MEDIUM) — the allowlist refused a valid production drain.**
samefile(brainlayer.db, Brainlayer.db) True
allowlisted? NO <- refused an honest drain
Fail-closed is the safe direction, but it blocked the one shape this PR promises
keeps working. Now `_same_file` there too.
**@1474 (MEDIUM) — real-tree + canonical-DB never went through `main()`.** Every
allow-test called `marker_target_hazard` directly, so a regression in the wiring
rather than the predicate would have passed. That invocation is exactly what the
post-merge drain runs, and it is now driven end to end.
After, on the real machine — every shape, including the two that were wrong:
~/Gits · ~/gits · ~/GITS · ~/gits/brainlayer · ~/Gits/brainlayer/.worktrees/x ALL REFUSED
canonical DB + real tree ALLOWED
Brainlayer.db (case alias) + real ALLOWED
copy DB + a copy of the files ALLOWED
`~/Gits` still reports pending_count 122 — nothing marked. Symlink cases were already
handled by `resolve()`; tests pin both, and the case tests skip with a stated reason
on case-sensitive filesystems so ubuntu CI says why rather than passing vacuously.
Rebuilt on #774's round-2 head rather than rebased: #774 now owns `_guard_module`,
which is the conflict. Same content otherwise, `C901` still clear on this file.
68 passed in tests/test_fallback_replay.py; 116 with test_status_truthfulness,
test_transport_replay_attribution and test_brainstore.
Agent: brainlayerClaude-ca745a2b (claude-opus-5[1m])
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Round 2 — all three fixed. New head 54232898. Both findings measured before the fix.
@162 (HIGH) — a differently cased path to the real tree failed open
Reproduced on this APFS volume exactly as filed:
os.path.samefile(~/Gits, ~/gits) True
Path.resolve() equal? False (~/Gits vs ~/gits)
guard on ~/Gits REFUSED
guard on ~/gits ALLOWED ← the hole
guard on ~/gits/brainlayer ALLOWED ← and its subtree
Round 1 closed the subtree hole and left the casing one sitting directly beside it. Your read is right that ubuntu CI would never have surfaced it and that macOS is the host that matters here.
One correction to the suggested remedy:realpath + os.path.normcase does not close this. normcase is a no-op on darwin — it only folds case on Windows — so on the machine that has the bug it changes nothing. I went with os.path.samefile (inode) for both checks, keeping a resolved-string compare only for the case where neither path exists, since two missing paths cannot be the same file.
_names_real_gits_tree walks to the nearest existing ancestor before comparing, because a subtree's leaf may not exist yet while the case alias is real one level up. An OSError anywhere returns True: cannot prove it is outside the tree means cannot prove it is safe.
@165 (MEDIUM) — the allowlist refused a valid production drain
samefile(brainlayer.db, Brainlayer.db) True
allowlisted? NO ← refused an honest drain
Fail-closed is the safe direction, but it blocked the one shape this PR promises keeps working. _same_file there too.
@1474 (MEDIUM) — real-tree + canonical-DB never went through main()
Correct: every allow-test called marker_target_hazard directly, so a regression in the wiring rather than the predicate would have passed — and that invocation is exactly what the post-merge drain runs. Now driven end to end through main().
After, on the real machine — every shape, including the two that were wrong
~/Gits · ~/gits · ~/GITS · ~/gits/brainlayer · ~/Gits/brainlayer/.worktrees/x ALL REFUSED
canonical DB + real tree ALLOWED
Brainlayer.db (case alias) + real ALLOWED
copy DB + a copy of the files ALLOWED
~/Gits still reports pending_count 122 — nothing marked.
Symlink aliases were already handled by resolve(); there are now tests pinning both, and the case tests skip with a stated reason on case-sensitive filesystems so ubuntu CI says why rather than passing vacuously.
Note on the branch
Rebuilt on #774's round-2 head rather than rebased: #774 now owns _guard_module (its right layer) and that is what the rebase conflicted on. Same content otherwise, verified by test count and a clean run; C901 stays clear on this file.
68 passed in tests/test_fallback_replay.py; 116 with test_status_truthfulness / test_transport_replay_attribution / test_brainstore.
…the guard (round 2)
Round-2 review, #780. Both real, measured on this APFS volume before the fix.
**@162 (HIGH) — a differently cased path to the real tree failed open.**
os.path.samefile(~/Gits, ~/gits) True
Path.resolve() equal? False (~/Gits vs ~/gits)
guard on ~/Gits REFUSED
guard on ~/gits ALLOWED <- the hole
guard on ~/gits/brainlayer ALLOWED <- and its subtree
`resolve()` + `is_relative_to` is a string comparison. macOS is case-insensitive by
default and is the fleet's primary host; ubuntu CI is case-sensitive and would never
have surfaced this. Round 1 closed the subtree hole and left the casing one directly
beside it.
**One correction to the suggested remedy:** `realpath` + `os.path.normcase` does NOT
close this. `normcase` is a no-op on darwin — it only folds case on Windows — so on
the machine that has the bug it changes nothing. Only the inode knows, so both checks
now go through `os.path.samefile`, with a resolved-string compare only for the case
where neither path exists (two missing paths cannot be the same file).
`_names_real_gits_tree` walks to the nearest EXISTING ancestor before comparing,
because a subtree's leaf may not exist yet while the case alias is real one level up.
An `OSError` anywhere in it returns True: cannot prove it is outside the tree means
cannot prove it is safe.
**@165 (MEDIUM) — the allowlist refused a valid production drain.**
samefile(brainlayer.db, Brainlayer.db) True
allowlisted? NO <- refused an honest drain
Fail-closed is the safe direction, but it blocked the one shape this PR promises
keeps working. Now `_same_file` there too.
**@1474 (MEDIUM) — real-tree + canonical-DB never went through `main()`.** Every
allow-test called `marker_target_hazard` directly, so a regression in the wiring
rather than the predicate would have passed. That invocation is exactly what the
post-merge drain runs, and it is now driven end to end.
After, on the real machine — every shape, including the two that were wrong:
~/Gits · ~/gits · ~/GITS · ~/gits/brainlayer · ~/Gits/brainlayer/.worktrees/x ALL REFUSED
canonical DB + real tree ALLOWED
Brainlayer.db (case alias) + real ALLOWED
copy DB + a copy of the files ALLOWED
`~/Gits` still reports pending_count 122 — nothing marked. Symlink cases were already
handled by `resolve()`; tests pin both, and the case tests skip with a stated reason
on case-sensitive filesystems so ubuntu CI says why rather than passing vacuously.
Rebuilt on #774's round-2 head rather than rebased: #774 now owns `_guard_module`,
which is the conflict. Same content otherwise, `C901` still clear on this file.
68 passed in tests/test_fallback_replay.py; 116 with test_status_truthfulness,
test_transport_replay_attribution and test_brainstore.
Agent: brainlayerClaude-ca745a2b (claude-opus-5[1m])
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
On the complexity one: this PR does change main(), so I own it rather than calling it inherited. It is now 7, below the 10 it reached on this branch and below the 11 it has carried on origin/main since before this lane — so the file leaves this PR better than it arrived.
_parse_legacy_entries keeps the behaviour that matters: one unparseable legacy file still becomes its own ERROR receipt row instead of costing the whole batch.
Because that extraction moves a real execution path, I re-verified the guard on this machine rather than trusting the suite:
~/Gits · ~/gits · ~/gits/brainlayer REFUSED
canonical DB + real tree ALLOWED
Brainlayer.db (case alias) + real ALLOWED
~/Gits pending_count 122 (nothing marked)
68 passed. ruff check / ruff format --check clean.
The reason will be displayed to describe this comment to others. Learn more.
🟠 Highscripts/replay_brain_store_fallbacks.py:159
When the canonical DB file does not yet exist, _same_file returns False for case-only aliases such as Brainlayer.db and brainlayer.db, so the production replay is incorrectly refused on case-insensitive APFS. samefile cannot compare missing files and realpath preserves the differing final component; compare the existing parent inode and case-folded filename before falling back to exact paths.
- return os.path.realpath(left) == os.path.realpath(right)+ left = os.path.realpath(left)+ right = os.path.realpath(right)+ if sys.platform == "darwin":+ try:+ return (+ os.path.samefile(os.path.dirname(left), os.path.dirname(right))+ and os.path.basename(left).casefold() == os.path.basename(right).casefold()+ )+ except OSError:+ pass+ return left == right
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @scripts/replay_brain_store_fallbacks.py around line 159:
When the canonical DB file does not yet exist, `_same_file` returns `False` for case-only aliases such as `Brainlayer.db` and `brainlayer.db`, so the production replay is incorrectly refused on case-insensitive APFS. `samefile` cannot compare missing files and `realpath` preserves the differing final component; compare the existing parent inode and case-folded filename before falling back to exact paths.
…the guard (round 2)
Round-2 review, #780. Both real, measured on this APFS volume before the fix.
**@162 (HIGH) — a differently cased path to the real tree failed open.**
os.path.samefile(~/Gits, ~/gits) True
Path.resolve() equal? False (~/Gits vs ~/gits)
guard on ~/Gits REFUSED
guard on ~/gits ALLOWED <- the hole
guard on ~/gits/brainlayer ALLOWED <- and its subtree
`resolve()` + `is_relative_to` is a string comparison. macOS is case-insensitive by
default and is the fleet's primary host; ubuntu CI is case-sensitive and would never
have surfaced this. Round 1 closed the subtree hole and left the casing one directly
beside it.
**One correction to the suggested remedy:** `realpath` + `os.path.normcase` does NOT
close this. `normcase` is a no-op on darwin — it only folds case on Windows — so on
the machine that has the bug it changes nothing. Only the inode knows, so both checks
now go through `os.path.samefile`, with a resolved-string compare only for the case
where neither path exists (two missing paths cannot be the same file).
`_names_real_gits_tree` walks to the nearest EXISTING ancestor before comparing,
because a subtree's leaf may not exist yet while the case alias is real one level up.
An `OSError` anywhere in it returns True: cannot prove it is outside the tree means
cannot prove it is safe.
**@165 (MEDIUM) — the allowlist refused a valid production drain.**
samefile(brainlayer.db, Brainlayer.db) True
allowlisted? NO <- refused an honest drain
Fail-closed is the safe direction, but it blocked the one shape this PR promises
keeps working. Now `_same_file` there too.
**@1474 (MEDIUM) — real-tree + canonical-DB never went through `main()`.** Every
allow-test called `marker_target_hazard` directly, so a regression in the wiring
rather than the predicate would have passed. That invocation is exactly what the
post-merge drain runs, and it is now driven end to end.
After, on the real machine — every shape, including the two that were wrong:
~/Gits · ~/gits · ~/GITS · ~/gits/brainlayer · ~/Gits/brainlayer/.worktrees/x ALL REFUSED
canonical DB + real tree ALLOWED
Brainlayer.db (case alias) + real ALLOWED
copy DB + a copy of the files ALLOWED
`~/Gits` still reports pending_count 122 — nothing marked. Symlink cases were already
handled by `resolve()`; tests pin both, and the case tests skip with a stated reason
on case-sensitive filesystems so ubuntu CI says why rather than passing vacuously.
Rebuilt on #774's round-2 head rather than rebased: #774 now owns `_guard_module`,
which is the conflict. Same content otherwise, `C901` still clear on this file.
68 passed in tests/test_fallback_replay.py; 116 with test_status_truthfulness,
test_transport_replay_attribution and test_brainstore.
Agent: brainlayerClaude-ca745a2b (claude-opus-5[1m])
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The reason will be displayed to describe this comment to others. Learn more.
🟠 Highscripts/replay_brain_store_fallbacks.py:137
marker_target_hazard allows replay to proceed with a non-canonical DB when an outside --gits-root contains a symlink into ~/Gits; inventory_fallbacks then follows that symlink and writes replay markers into the real repository, hiding memories whose chunks exist only in the copy DB. Validate the repositories/paths that will be traversed (or reject symlinks resolving inside the real tree) before allowing replay.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @scripts/replay_brain_store_fallbacks.py around line 137:
`marker_target_hazard` allows replay to proceed with a non-canonical DB when an outside `--gits-root` contains a symlink into `~/Gits`; `inventory_fallbacks` then follows that symlink and writes replay markers into the real repository, hiding memories whose chunks exist only in the copy DB. Validate the repositories/paths that will be traversed (or reject symlinks resolving inside the real tree) before allowing replay.
…the guard (round 2)
Round-2 review, #780. Both real, measured on this APFS volume before the fix.
**@162 (HIGH) — a differently cased path to the real tree failed open.**
os.path.samefile(~/Gits, ~/gits) True
Path.resolve() equal? False (~/Gits vs ~/gits)
guard on ~/Gits REFUSED
guard on ~/gits ALLOWED <- the hole
guard on ~/gits/brainlayer ALLOWED <- and its subtree
`resolve()` + `is_relative_to` is a string comparison. macOS is case-insensitive by
default and is the fleet's primary host; ubuntu CI is case-sensitive and would never
have surfaced this. Round 1 closed the subtree hole and left the casing one directly
beside it.
**One correction to the suggested remedy:** `realpath` + `os.path.normcase` does NOT
close this. `normcase` is a no-op on darwin — it only folds case on Windows — so on
the machine that has the bug it changes nothing. Only the inode knows, so both checks
now go through `os.path.samefile`, with a resolved-string compare only for the case
where neither path exists (two missing paths cannot be the same file).
`_names_real_gits_tree` walks to the nearest EXISTING ancestor before comparing,
because a subtree's leaf may not exist yet while the case alias is real one level up.
An `OSError` anywhere in it returns True: cannot prove it is outside the tree means
cannot prove it is safe.
**@165 (MEDIUM) — the allowlist refused a valid production drain.**
samefile(brainlayer.db, Brainlayer.db) True
allowlisted? NO <- refused an honest drain
Fail-closed is the safe direction, but it blocked the one shape this PR promises
keeps working. Now `_same_file` there too.
**@1474 (MEDIUM) — real-tree + canonical-DB never went through `main()`.** Every
allow-test called `marker_target_hazard` directly, so a regression in the wiring
rather than the predicate would have passed. That invocation is exactly what the
post-merge drain runs, and it is now driven end to end.
After, on the real machine — every shape, including the two that were wrong:
~/Gits · ~/gits · ~/GITS · ~/gits/brainlayer · ~/Gits/brainlayer/.worktrees/x ALL REFUSED
canonical DB + real tree ALLOWED
Brainlayer.db (case alias) + real ALLOWED
copy DB + a copy of the files ALLOWED
`~/Gits` still reports pending_count 122 — nothing marked. Symlink cases were already
handled by `resolve()`; tests pin both, and the case tests skip with a stated reason
on case-sensitive filesystems so ubuntu CI says why rather than passing vacuously.
Rebuilt on #774's round-2 head rather than rebased: #774 now owns `_guard_module`,
which is the conflict. Same content otherwise, `C901` still clear on this file.
68 passed in tests/test_fallback_replay.py; 116 with test_status_truthfulness,
test_transport_replay_attribution and test_brainstore.
Agent: brainlayerClaude-ca745a2b (claude-opus-5[1m])
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The reason will be displayed to describe this comment to others. Learn more.
🟠 Highscripts/replay_brain_store_fallbacks.py:174
_names_real_gits_tree rejects a valid copied-tree replay when --gits-root contains .., such as ~/Gits/../gits-copy. Because Path.parents is traversed before normalization, it encounters the lexical ~/Gits ancestor and incorrectly refuses the run; normalize target before checking its ancestors.
- if _same_file(target, real_gits_root):+ target = target.resolve()+ if _same_file(target, real_gits_root):
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @scripts/replay_brain_store_fallbacks.py around line 174:
`_names_real_gits_tree` rejects a valid copied-tree replay when `--gits-root` contains `..`, such as `~/Gits/../gits-copy`. Because `Path.parents` is traversed before normalization, it encounters the lexical `~/Gits` ancestor and incorrectly refuses the run; normalize `target` before checking its ancestors.
Correcting my first gate pass here too: I read PTC-W0049 as no-self-use and applied @staticmethod, which left the pass in place and changed nothing. The rule is empty body.
On complexity: this PR does change main(), so I own it. It is now 7 — below the 10 it reached here and below the 11 origin/main has carried since before this lane, so the file leaves better than it arrived. _parse_legacy_entries keeps the behaviour that matters: one unparseable legacy file still becomes its own ERROR receipt row instead of costing the batch.
Because that extraction moves a real execution path, I re-verified on this machine rather than trusting the suite:
~/Gits · ~/gits · ~/gits/brainlayer REFUSED
canonical DB + real tree ALLOWED
Brainlayer.db (case alias) + real ALLOWED
~/Gits pending_count 122 (nothing marked)
68 passed. ruff check / ruff format --check clean.
…t that it ran (a, 1/3) (XS) (#774)
* feat(fallback-replay): the receipt says what each replay did, not just that it ran (a, 1/3)
`ReplayResult` carried a chunk id and nothing about the write behind it, so a drain
of 122 pending fallback files printed 122 identical-looking REPLAYED lines whether
every memory was freshly inserted, folded into an existing chunk, or a bare
re-send that wrote nothing. `store_memory` has reported `outcome` since #725; the
replay path threw it away.
- `ReplayResult.outcome` over the #725 vocabulary — stored / duplicate / merged /
deferred / rejected / error — plus `skipped` for a file that was already stored.
Defaults to `skipped`, so a result built without one never claims a write.
- `replay_entry` reads the store's outcome (defaulting to `stored` the way
`mcp/store_handler.py` does), `rejected` when no chunk id came back, `error`
when the store raised.
- `queue_entry` answers `deferred`: the row is durably queued and the drain
commits it later. Per #725 that is success, not a failure to re-drive.
- `scripts/replay_brain_store_fallbacks.py` prints the outcome per file, totals
them as `outcome_counts`, and `--receipt PATH` persists the JSON receipt.
Live-check against a COPY of the real DB (ratified 2026-08-02 — this touches
stored data), using the sanctioned daily backup `backups/2026-09-05.db`
APFS-cloned, and a copy of the real fallback files so production markers stayed
untouched. Reproduced the debt exactly (pending_count 122), then:
run 1: outcomes stored=122, errors=0, 122/122 chunk ids present in the copy,
pending_count 0, green true
run 2 (same copy, fresh file copy): outcomes duplicate=122, same chunk ids
Before this change both runs emit the same text. That is the whole defect.
Agent: brainlayerClaude-ca745a2b (claude-opus-5[1m])
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(fallback-replay): an unrecognized store outcome is never upgraded to `stored` (round 1)
Round-1 review, #774@605 and @221.
**@605, real — and my PR body cited the wrong line.** I claimed parity with
`mcp/store_handler.py:1126`. That line defaults only when the key is ABSENT
(`result.get("outcome", "stored")`), and `_store_receipt` then *raises* on a present
value outside the vocabulary and again on one that is present but unresolved
(`store_handler.py:601-606`). My `_store_outcome` remapped ANY present unrecognized
value — `deferred`, `rejected`, `error`, garbage — to `stored`, the strongest claim
in the vocabulary. That is weaker than both halves of the contract, and the exact
inverse of what a receipt is for.
Now: absent still defaults to `stored` (a store predating the field did commit a
write, and that parity must not regress). A present non-resolved value gets its own
`OUTCOME_UNRESOLVED` and an error naming the raw word, so the row is traceable and
the script exits non-zero instead of reporting a clean run. Not a raise: raising
would abandon the rest of a 122-file batch for one odd row, which is why the MCP
path's exact behaviour is not the right one here.
**@221, a coverage gap rather than a defect.** The already-queued -> `deferred`
early return was already correct; three new tests pin it, including the negative
side (a vanished queue file must re-enqueue and stay `deferred`) — the case where a
regression labelling it `skipped` would report queue debt as already handled.
Tests written first: the two `_store_outcome` tests failed, the three `queue_entry`
tests passed on the unfixed code, which is what identified @221 as coverage.
Verified against round-1's DeepSource note: the six findings under
`ruff --select TRY,PLW,B,S,SIM,C90` are byte-identical between `origin/main` and this
branch — including `C901 main is too complex (11 > 10)`, which is at
`replay_script.py:31` on main already. This diff introduces none of them.
tests/test_fallback_replay.py 50 passed; with test_status_truthfulness,
test_transport_replay_attribution and test_brainstore, 98 passed.
Agent: brainlayerClaude-ca745a2b (claude-opus-5[1m])
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(fallback-replay): a blank outcome is present, so it is not `stored` (round 2)
Round-2 review, #774. All three real.
**@618-619 (medium) — round 1's own invariant, broken one line below where I stated
it.** `""` and `" "` are PRESENT values. My round-1 docstring says only an ABSENT
key defaults to `stored`, and then the code did `if not text: return OUTCOME_STORED`.
`mcp/store_handler.py` takes a blank through `result.get("outcome", "stored")`
unchanged and `_store_receipt` rejects it, so this was fail-open on the strongest
claim in the vocabulary — the exact defect round 1 was supposed to close, one layer
down. Blank now goes to `unresolved` with the raw value in the error. Tested for
`""`, `" "` and `"\t\n"`.
**@186-192 (low) — wrong word in the receipt.** A store answering
`{"outcome": "error"}` with no chunk id described its own failure; filing it as
`rejected` conflates two distinct #725 states. Now `error` when the store said so,
and still `rejected` when the store returned nothing at all and has no word for
itself. `_raw_store_outcome` extracted so the three call sites read the field one way.
**@1280-1295 (low) — the CLI contract was unpinned.** Unresolved was unit-tested but
nothing asserted the script exits 1 and persists `outcome_counts.unresolved` through
`--receipt`. A regression clearing the error on an unresolved row would have kept
exit 0 while still printing a soft label — a green run over a write nobody can vouch
for. Now pinned end to end.
Also moved `_guard_module` into this file as the shared CLI-test loader; #780 had
defined it locally and this is its right layer.
tests/test_fallback_replay.py 53 passed; 101 with test_status_truthfulness,
test_transport_replay_attribution and test_brainstore.
Agent: brainlayerClaude-ca745a2b (claude-opus-5[1m])
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(tests): clear the two DeepSource issues this PR actually introduced (round 2 gate)
Read the real occurrences with `deepsource issues list --repo gh/EtanHey/brainlayer
--analyzer python --json` (CLI, authenticated) plus each run's introduced count,
instead of inferring from the JS dashboard as I did in round 1. **My earlier "not
introduced by these PRs" claim was wrong** — this run reports 3 introduced, and two
of them are mine.
**PYL-W0404 (reimported)** — `tests/test_fallback_replay.py:645`, `import json`
inside `jsonl_enqueue_func`. That inner import predates this lane; it only BECAME a
reimport when I added `import json` at module level for the `--receipt` test. My diff
caused it, so my diff removes it.
**PTC-W0049 (method doesn't use `self`)** — the `DummyStore.close` stubs my tests
added. Now `@staticmethod`; they are called as `store.close()` either way.
53 passed in tests/test_fallback_replay.py.
Agent: brainlayerClaude-ca745a2b (claude-opus-5[1m])
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(tests): a stub with a `pass` body IS the PTC-W0049 defect (round 2 gate, second pass)
My first pass at this misread the rule. I assumed PTC-W0049 was "method doesn't use
`self`" from the code snippet DeepSource renders and reached for `@staticmethod`. The
run came back with the same 3 introduced. Looked up the actual title instead of
guessing again:
PTC-W0049 = "Function/method with an empty body"
So the `pass` was the defect the whole time and `@staticmethod` never touched it.
The three nested `class DummyStore: def close(): pass` stubs are replaced by one
module-level `ClosableStoreStub` that records `self.closed = True`. Non-empty body,
uses `self`, no duplication across three call sites — and a double that remembers
being closed is more useful than one that silently does nothing.
Verified with an AST scan over this branch's added lines: no empty-body function
remains in anything this diff introduced.
53 passed. `ruff check` / `ruff format --check` clean.
Agent: brainlayerClaude-ca745a2b (claude-opus-5[1m])
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* chore: remove a scratch analysis script I committed by accident
`.empty_body_check.py` was a throwaway AST scan I wrote to audit my own diff for
empty-body functions. A `git add -A` swept it into the PR, and DeepSource flagged it
(`PYL-W1510`, `subprocess.run` without `check`) — correctly, since it was never meant
to ship. Removed.
My mistake, not the analyzer's; the finding is real and the file has no business in
this repo.
Agent: brainlayerClaude-ca745a2b (claude-opus-5[1m])
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
… is not canonical (d)
Rebuilt on #774's round-2 head. Same content as 345d1ee; the duplicate
`_guard_module` loader is dropped because #774 now owns it (its right layer), which
is what the rebase conflicted on.
Agent: brainlayerClaude-ca745a2b (claude-opus-5[1m])
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…the guard (round 2)
Round-2 review, #780. Both real, measured on this APFS volume before the fix.
**@162 (HIGH) — a differently cased path to the real tree failed open.**
os.path.samefile(~/Gits, ~/gits) True
Path.resolve() equal? False (~/Gits vs ~/gits)
guard on ~/Gits REFUSED
guard on ~/gits ALLOWED <- the hole
guard on ~/gits/brainlayer ALLOWED <- and its subtree
`resolve()` + `is_relative_to` is a string comparison. macOS is case-insensitive by
default and is the fleet's primary host; ubuntu CI is case-sensitive and would never
have surfaced this. Round 1 closed the subtree hole and left the casing one directly
beside it.
**One correction to the suggested remedy:** `realpath` + `os.path.normcase` does NOT
close this. `normcase` is a no-op on darwin — it only folds case on Windows — so on
the machine that has the bug it changes nothing. Only the inode knows, so both checks
now go through `os.path.samefile`, with a resolved-string compare only for the case
where neither path exists (two missing paths cannot be the same file).
`_names_real_gits_tree` walks to the nearest EXISTING ancestor before comparing,
because a subtree's leaf may not exist yet while the case alias is real one level up.
An `OSError` anywhere in it returns True: cannot prove it is outside the tree means
cannot prove it is safe.
**@165 (MEDIUM) — the allowlist refused a valid production drain.**
samefile(brainlayer.db, Brainlayer.db) True
allowlisted? NO <- refused an honest drain
Fail-closed is the safe direction, but it blocked the one shape this PR promises
keeps working. Now `_same_file` there too.
**@1474 (MEDIUM) — real-tree + canonical-DB never went through `main()`.** Every
allow-test called `marker_target_hazard` directly, so a regression in the wiring
rather than the predicate would have passed. That invocation is exactly what the
post-merge drain runs, and it is now driven end to end.
After, on the real machine — every shape, including the two that were wrong:
~/Gits · ~/gits · ~/GITS · ~/gits/brainlayer · ~/Gits/brainlayer/.worktrees/x ALL REFUSED
canonical DB + real tree ALLOWED
Brainlayer.db (case alias) + real ALLOWED
copy DB + a copy of the files ALLOWED
`~/Gits` still reports pending_count 122 — nothing marked. Symlink cases were already
handled by `resolve()`; tests pin both, and the case tests skip with a stated reason
on case-sensitive filesystems so ubuntu CI says why rather than passing vacuously.
Rebuilt on #774's round-2 head rather than rebased: #774 now owns `_guard_module`,
which is the conflict. Same content otherwise, `C901` still clear on this file.
68 passed in tests/test_fallback_replay.py; 116 with test_status_truthfulness,
test_transport_replay_attribution and test_brainstore.
Agent: brainlayerClaude-ca745a2b (claude-opus-5[1m])
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…urther (round 2 gate)
Read with the authenticated `deepsource` CLI plus this run's introduced count: **5
introduced**, and the ones attributable to my diff are cleared here.
**PTC-W0049 (method doesn't use `self`)** — three more `DummyStore.close` stubs in
the guard tests. Now `@staticmethod`.
**PY-R1000 (cyclomatic complexity)** — reported against `main()`, which this PR does
change, so I own it rather than call it inherited. `_parse_legacy_entries` extracted
(one unparseable legacy file still becomes its own ERROR receipt row instead of
losing the batch). `main()` is now **7**, down from 10 on this branch and from the
**11 it has carried on `origin/main`** since before this lane.
Re-verified after the extraction, since it moves a real execution path:
~/Gits · ~/gits · ~/gits/brainlayer REFUSED
canonical DB + real tree ALLOWED
Brainlayer.db (case alias) + real ALLOWED
`~/Gits` still reports pending_count 122. 68 passed in tests/test_fallback_replay.py.
Agent: brainlayerClaude-ca745a2b (claude-opus-5[1m])
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…second pass)
PTC-W0049 is "Function/method with an empty body" — I had misread it as no-self-use
and reached for `@staticmethod`, which left the `pass` untouched. The three guard-test
stubs here now reuse `ClosableStoreStub` from #774's base instead of redefining an
empty class each time.
AST scan over this branch's added lines: no empty-body function remains in anything
this diff introduces.
68 passed. `ruff check` / `ruff format --check` clean.
Agent: brainlayerClaude-ca745a2b (claude-opus-5[1m])
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A receipt failure at target.parent.mkdir(...) or target.write_text(...) aborts _emit with an uncaught filesystem exception, so a completed replay produces neither its normal stdout result nor a usable receipt. Catch the receipt write error and route it through the CLI's controlled error reporting and exit path.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @scripts/replay_brain_store_fallbacks.py around line 320:
A receipt failure at `target.parent.mkdir(...)` or `target.write_text(...)` aborts `_emit` with an uncaught filesystem exception, so a completed replay produces neither its normal stdout result nor a usable receipt. Catch the receipt write error and route it through the CLI's controlled error reporting and exit path.
Every Value below was measured by this run. A row this machine cannot measure says n/a — <reason> instead of a number; baselines in Notes name their own machine, method and date and were not measured here.
Row
Status
Value (measured by this run)
Method
Notes
commit provenance
🟢 GREEN
measured 9d36a6097d5c == PR head · checkout d1afd1cf56c2
commit graph + live PR head · in-process · runner
Which commit this whole table is about. On a pull_request event the checkout is GitHub's synthetic merge ref, whose sha is not on the PR — #759's table printed 13fa724278bf while that PR's head was 4632f979 — so this row names the PR-head parent instead, the sha a reviewer can actually see. The comparison sha is read live from repos/{owner}/{repo}/pulls/{n} when the table is collected, not taken from the event payload, because the payload cannot know the run has been overtaken. Residual window, stated rather than papered over: a push landing between that read and the comment being posted is not caught here — the run for that push refreshes the table.
baseline attestation
🟢 GREEN
baseline f421d1a7c5e6 matches the main attestation (run 33980263749 · main 24482318d82d · 2026-09-05T17:13:16Z)
main attestation artifact via Actions API · in-process · runner
What every comparison is measured AGAINST, and who says so. The baseline fields of tests/fixtures/sprint_gate/corpus.json (queries, latency_baseline_ms, thresholds) are compared to the ratchet-attestation artifact of the latest successful push or (no-input) workflow_dispatch run of ratchet-attest.yml on main, fetched through the Actions API — a PR run cannot write to another run's artifacts. A field that differs is RED unless that main run measured the new value; today no runner-side collector measures any baseline field, so today the baseline cannot move by PR at all, and this row says so instead of a hand edit passing. Boundary: the comparator is this PR's checkout of ci_ratchet_table.py, diff-reviewable, not tamper-proof.
provenance
🟢 GREEN
stamped d1afd1cf56c2 == HEAD, tree clean
wheel stamp · in-process · runner
Sha half of #749 keg-mode provenance: a keg built from this wheel can answer __build_sha__. The helper-age and served-process predicates need a running BrainBar and are measured only by scripts/sprint_gate.py on an installed Mac. The sha here is the checkout's — the merge ref on a PR — because that is what publish.yml stamps at release time; the PR-head sha this table describes is the one in commit provenance above.
fallback replay debt
⚪ n/a
n/a — no fallback queue on this machine: the pending memories live in ~/Gits/*/docs.local/decisions, and docs.local/ is gitignored, so a runner checkout has no copy of them to count
docs.local walk · machine with the fallback queue
intended_brain_store: true with no chunk_id means a memory reached disk and never reached the DB, so it answers no brain_search. Budget: 0. Any pending or unparseable file is a finding, never a band -- 122 of these sat from 2026-06-28 to 2026-09-05 because nothing counted them where a reader would look. Measured by walking the tree, so it is only ever measured on a machine that HAS the tree.
mapped bytes
⚪ n/a
n/a — no BrainBar daemon at /tmp/brainbar.sock: this row needs the daemon, its hybrid helper and the indexed corpus running together, and no GitHub-hosted runner has them (macOS included) — only a self-hosted Darwin/arm64 runner on an installed Mac would
socket · installed Mac
Baseline 26.2 GB — installed Mac, socket, 2026-09-03, after R2 drained 15,070 → 0. Up from 16.8 GB because the drain left more vectors mapped under the same cap: the change is the drain, not a leak. Not measured by this run.
search p50/p95
⚪ n/a
n/a — no BrainBar daemon at /tmp/brainbar.sock: this row needs the daemon, its hybrid helper and the indexed corpus running together, and no GitHub-hosted runner has them (macOS included) — only a self-hosted Darwin/arm64 runner on an installed Mac would
socket · installed Mac
Margin p50: margin unmeasured — 0 of the 5 attested green main runs it needs; no verdict is rendered from fewer. Margin p95: margin unmeasured — 0 of the 5 attested green main runs it needs; no verdict is rendered from fewer. Calibrated on MacBook-Pro.local at 2026-09-01T08:42:22Z under active_sprint_load (tests/fixtures/sprint_gate/corpus.json). Not measured by this run.
idle CPU
⚪ n/a
n/a — no BrainBar daemon at /tmp/brainbar.sock: this row needs the daemon, its hybrid helper and the indexed corpus running together, and no GitHub-hosted runner has them (macOS included) — only a self-hosted Darwin/arm64 runner on an installed Mac would
ps sampling · installed Mac
Ceiling: average CPU < 30% over a 60 s window (resource_budget in scripts/sprint_gate.py), ratified and kept as a hard budget. Margin daemon: margin unmeasured — 0 of the 5 attested green main runs it needs; no verdict is rendered from fewer. Margin helper: margin unmeasured — 0 of the 5 attested green main runs it needs; no verdict is rendered from fewer. Margin watcher: margin unmeasured — 0 of the 5 attested green main runs it needs; no verdict is rendered from fewer. Needs the BrainBar daemon, helper and watcher actually running. Not measured by this run.
signature_valid
⚪ n/a
n/a — the macOS signature-parity job is trigger-gated and did not run on this PR: it touches no release or signing path (pyproject.toml, scripts/release-*, scripts/brainlayer-version-check.sh, publish.yml, ratchet.yml) and carries no ratchet:signatures label — a GitHub macOS runner bills at ~10× Linux minutes and rebuilds the keg venv from source
codesign · installed keg
scripts/release-verify-signatures.sh <keg> codesign-verifies every *.so/*.dylib under libexec/venv. The macOS parity job installs the published tap formula (etanhey/layers/brainlayer), so this row measures the release path — formula, published sdist and Homebrew's relocation — and not this PR's tree. Release-time baseline for the same keg on a different machine: 442 valid / 0 invalid — installed Mac (M4 Max), brew --prefix brainlayer 1.5.11, 2026-09-03.
🟢 GREEN measured, within budget · 🔴 RED measured, out of budget — a finding to clear before merge · ⚪ n/a not measurable on this machine, never guessed.
No RED rows.
Measured on Linux/x86_64 · measured 9d36a6097d5c · PR head 9d36a6097d5c · checkout d1afd1cf56c2 · run · updated 2026-09-05 17:16:10 UTC
The reason will be displayed to describe this comment to others. Learn more.
🟠 Highscripts/replay_brain_store_fallbacks.py:180
--apply --gits-root pointing to a symlink loop crashes with RuntimeError instead of returning the documented fail-closed refusal. Path.resolve(strict=False) raises RuntimeError for symlink loops on Python 3.11/3.12, but this preflight only catches OSError; catch RuntimeError as well.
Suggested change
exceptOSError:
except(OSError, RuntimeError):
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @scripts/replay_brain_store_fallbacks.py around line 180:
`--apply --gits-root` pointing to a symlink loop crashes with `RuntimeError` instead of returning the documented fail-closed refusal. `Path.resolve(strict=False)` raises `RuntimeError` for symlink loops on Python 3.11/3.12, but this preflight only catches `OSError`; catch `RuntimeError` as well.
All six sites: pyproject.toml, src/brainlayer/__init__.py, server.json
(root + packages[0]), brain-bar/bundle/Info.plist (short, bundle, release).
Casks/brainbar.rb stays 1.5.9: no Swift change this release, declared to
scripts/brainlayer-version-check.sh via
BRAINLAYER_VERSION_CHECK_CASK_LAG_REASON="no BrainBar release for 1.5.16"
(kept in the PR body, never exported into the push env — two tests assert
the check fails without a reason).
Why a release, 22 minutes after the last one. v1.5.15 was tagged at
51a72a0 (20:12:29). #778 — the index runtime watchdog — merged at
78d92bc (20:34:55), so it is NOT in 1.5.15. Verified:
`git merge-base --is-ancestor 78d92bc v1.5.15^{}` -> false, and the
installed keg's python raises
`ModuleNotFoundError: No module named 'brainlayer.index_watchdog'`.
That watchdog is the fix for the M1's nightly `brainlayer index` job,
which ran 14h03m at ~100% CPU on 09-05, 10h past its own 4h cap. Both
Macs' 03:15 index jobs are `launchctl disable`d as a stopgap and come
back ON only after 1.5.16 is installed and the watchdog is proven
present.
Nine commits ride along: #774#777#780#778#783#785#786#787#788.
Co-authored-by: brainlayerClaude-c1601b03 running claude-opus-5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The defect
The marker write is not DB-scoped.
src/brainlayer/fallback_replay.pynever reads a DB path at all —grep -n "db_path\|DEFAULT_DB_PATH\|canonical"returns nothing.replay_entrystores, then writeschunk_idinto the file under--gits-root, whatever--dbpointed at.So the shape the ratified procedure asks for — a copy of the DB — permanently stamps every pending file in the real tree with an id that exists only in a throwaway DB.
is_pending_entrythen answers "not pending", and those memories are hidden with nothing left to say they were never stored.Demonstrated on a scratch tree against a brand-new empty DB:
The plan for this work says "Test against a COPY of the real DB first", and the 2026-08-02 ratification requires it for anything touching stored data. Following that instruction with this tool, as shipped, silently hides every memory it touches. I only avoided it in #774 and #777 by also copying the fallback files — which nothing told me to do and nothing required.
The fix
marker_target_hazardfails closed on exactly the mismatch that hides data: a non-canonical--dbwhile--gits-rootresolves to the real~/Gits. Both honest shapes stay allowed:--db--gits-rootThe queued path is guarded the same way — it writes
queued_chunk_idinto the same files.A silence next to it
_emitnever printedresult["error"]in text mode, so a refusal and a clean run emitted the same three count lines and only the exit code told them apart. That already applied to the two pre-existing early exits (--legacy requires queued replayand the--limitrefusal), which were equally invisible. Text mode now leads withERROR: <reason>.Verified against the live tree
Test that fails on the base commit
Four, in
tests/test_fallback_replay.py. The two guard tests fail on the base (main() == 0, no refusal); the two allow-tests pass on the base and must keep passing, which is what stops the guard from blocking the production drain. Measured before implementing.Scope
XS. 38 hand-written source lines, 149 test lines. Read-only against the canonical DB; no production fallback file was written at any point.
ruff checkandruff format --checkclean. Scoped pre-push;tests/test_fallback_replay.py49 passed.Agent: brainlayerClaude-ca745a2b (claude-opus-5[1m])
Note
Medium Risk
The script marks production
docs.localfallback files; a guard regression could either block the real drain or fail open and silently hide pending memories again.Overview
Adds a fail-closed safety gate to
replay_brain_store_fallbacks.pyfor--apply: before inventory runs,marker_target_hazardblocks replay when--dbis not the canonical DB (viaget_canonical_db_path, not env-affectedDEFAULT_DB_PATH) while--gits-rootstill targets the real~/Gitstree—including subtrees, symlinks, and case aliases detected withos.path.samefile. Unsafe runs exit 2 with an explicit refusal; copy DB + copy files and canonical DB + real tree stay allowed for live-check and production drain. The same guard covers queued replay because it also writes markers into fallback files.Refactors
maininto helpers for legacy parsing, queue replay, and direct DB replay. Text-mode_emitnow printsERROR:for refusals and otherresult["error"]cases. Tests add broad coverage for refuse/allow shapes, guard ordering, and end-to-end production drain wiring.Reviewed by Cursor Bugbot for commit 9d36a60. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Refuse noncanonical DB apply against the real Gits tree in
replay_brain_store_fallbacksmainthat checksmarker_target_hazardbefore scope loading or fallback inventory, returning status 2 on unsafe combinations.get_canonical_db_path) with it.mainby extracting legacy parsing, queued replay, and direct replay into separate helpers; adds_emitprinting of anERROR:line in text mode for refusals and replay failures.Macroscope summarized 9d36a60.