Repository navigation
v1.87.0 - Consent Before the Write
Open Second Brain v1.87.0 - Consent Before the Write
Open Second Brain wrote to host configuration on demand: o2b install --target X --apply, brain protect --apply and brain doctor --repair --apply rewrote files on the call, and brain unprotect rewrote a protected file on every invocation with no consent, no backup and no record. v1.87.0 puts the guarded host-write lanes behind an explicit consent ladder - the intended change shown as a unified diff, a timestamped backup beside an audit receipt before anything is written, and exactly the approved preview landed, with the write refused when the file moved under it - and brain unprotect joins the lanes. An opt-in PreToolUse hook extends the same care to an agent's file writes against host Claude settings, thirteen more MCP list tools walk any large result set by an offset cursor instead of stopping at one capped page, a fourth decision-model route speaks the bearer-authenticated Converse protocol (Bedrock-style) beside the existing three adapters, and the trigger queue learns to snooze while a dismissal holds until its finding is grounded in something new.
What ships
- The three apply lanes demand consent before touching a file.
o2b install --target X --apply,o2b brain protect --applyando2b brain doctor --repair --applyrefuse a--jsonor non-TTY run with a non-zero exit unless--yesis passed (the install refusal exits 6 and names the flag) and prompt once in an interactive run; bothprotect --applyanddoctor --repairprint a unified diff of the intended change before anything is written, and a headless refusal emits its own envelope ({ consent: "headless-refused", written: false, error }) on the payload stream. - The applied change is the approved preview.
brain protect --applyandbrain unprotect --applycarry the before/after pair the consent ladder showed into the write: the write re-derives the change and refuses it (PREVIEW_DRIFT, nothing written, no backup taken) when the target file or one of the manifest inputs moved since the preview, and the write re-reads the target right before the rename, absent a target excepted, so the bytes that land are the bytes that were approved. brain unprotectjoins the consent lanes. Plainbrain unprotectbecomes a read-only preview - under--jsona"preview": trueenvelope carrying the before/after pair, so a headless consumer can tell it from the apply envelope it shares keys with ---yeswithout--applyis refused by name, and--applyruns the same fail-closed ladder: a confirmed run takes a timestamped backup of the file's current content beside it and appends the audit receipt before anything is written, so a refused consent or a failed backup leaves the protected file untouched.- An opt-in PreToolUse hook backs up host Claude settings before agent file writes. With
host_settings_backupon (envOPEN_SECOND_BRAIN_HOST_SETTINGS_BACKUPover the_brain.yamlkey, default off), a Write/Edit/MultiEdit/apply_patch call whose path canonically names a.claude/settings.jsonor.claude/settings.local.jsongets its current content copied into a timestamped backup at<dir>/.o2b-backups/<name>/<UTC stamp>- created private at0600, the backup carries the same secrets the settings file does, and exclusive-create ladders same-second names so exhaustion is a typed error, never an overwrite - beside one audit receipt, before the call is allowed to proceed; a backup that cannot be taken denies the call via stdoutpermissionDecision: "deny", a successful backup emits nothing, and the off path is a byte-identical no-op. - Cursor pagination across the MCP list tools, so an agent can walk any large result set. Thirteen limit-only list tools adopt the shared offset cursor beside the three surfaces that already paged, bringing the walking surface to sixteen: each declares a
cursorinput, cuts one offset window over the full filtered set through the one core cursor helper, and answersnext_cursorpresent in every response andnullwhen the walk is exhausted; a malformed cursor is refused as INVALID_PARAMS rather than silently restarting the walk; two of the walking scanners,brain_event_traceandbrain_unlinked_mentions, bound each page's scan one record past the page's reach so a cursor walk costs one page of work per call; andbrain_searchandbrain_file_contextstay deliberately limit-only, because offset-paging a ranked, index-dependent answer would present an index race as data. - A
conversedecision-model route for bearer-authenticated Converse-protocol (Bedrock-style) gateways, declared uncalibrated. A fourth adapter besidesystemone,vercel-evaluateandllm-emulation, with aconversepreset whose base URL, pinned model id and key variable are the operator's, riding the request path (POST <base>/model/<model_id>/converse); the reply's message text holds one JSON object keyed by question id, validated item by item by the same rules as every route, and astopReasonthat is notend_turnrefuses the reply rather than half-parsing it; the reply is generated text whose probabilities the model wrote itself, so anenforcesetting is a config error until the operator opts in withdecision_model_allow_uncalibrated: true, and even then it runs asshadowuntil this route's own shadow data tunes its threshold profile; the request passes the shared egress guard under thedecision-model-converseregistry id, ando2b decision-model check,doctorand the setup skill name the new preset and profile. - A
snoozeaction on the trigger queue, withunsnoozeand a terminalsnoozedstatus.o2b brain trigger snooze <id>and the MCPbrain_triggersnoozeoperation defer an open trigger - legal from open states only - for a bounded window (three days) or until the finding's grounding moves, whichever comes first;unsnoozewakes it by hand, and the window's end or a grounding movement wakes it on its own, both restoring the exact status the snooze interrupted; the movement half persists the same grounding fingerprint the dismissal gate does, so a bare re-scan of an unchanged finding keeps the snooze holding and only stamps the ledger; a snoozed trigger is terminal like a suppressed one - hidden fromlist, present inhistory, never in the brief, and governed by its snooze window rather than the TTL expiry clock - and re-snoozing a still-snoozed trigger is refused rather than treated as idempotent. - A dismissed trigger no longer returns on a cooldown clock. Dismissal used to silence a cooldown key for
trigger_cooldown_days(default 7) after the dismissal, so a finding nobody had touched resurfaced on a timer and a genuinely moved finding stayed hidden until the clock ran out; a dismissed finding now stays dismissed for however long the finding's grounding holds still, and returns - as a new record through the normal creation path, never as a status flip of the resolved one - only when the grounding moves: the re-fire introduces a source artifact the dismissed record does not name, or its grounding fingerprint (a sha256 of the finding's reason and context, normalized so the clock projections those texts embed - day counts, ages, scores - cannot read as movement) differs from the one persisted at dismissal time; the comparison is set-based and order-insensitive, a bare re-scan of an unchanged finding keeps the silence whatever the calendar says, scans name the silenced re-fires with a newdismissedskip reason, and the acted status keeps its cooldown window whilesuppressstays clockless.
Process wins
- One source per fact: every What ships claim traces to the 1.87.0 CHANGELOG entry or the merged PR #266 description, and the quality numbers come from the CI run at the merge commit, never estimates.
- Reviewed before the merge: the OCR delegation review covered 55 of 55 reviewable files with 6 confirmed findings, all 6 fixed; bot review is skipped for this change set because at 131 changed files it exceeds the 100-file limit above which CodeRabbit declines to review, and the internal review covered the whole diff.
- Quality record: 19,074 tests pass / 0 fail / 20 skip on the validate job of the CI run at the merge commit f6d9f95, the exact content this release tags, on the CI-pinned Bun 1.4.0; typecheck clean, lint at 0 errors,
sync-versiongreen across its 8 manifests, the plugin scanners green, and every check green at that head, all ten Windows shards included. - The housekeeping in the wave: #262 bumps
hashgraph-online/ai-plugin-scanner-actionfrom 1.2.772 to 1.2.787, and #263 bumps the dev toolchain (@types/bun1.4.2 to 1.4.3,oxfmt0.72.0 to 0.73.0,oxlint1.87.0 to 1.88.0). - #265 moves the
osb-pr-prepareplaybook to 1.4.0: one shared dev quality cycle replaces its four review nodes, an owner approval gate now sits before anything is pushed, and fixes made after a review are reviewed too; the PR also fixes a date-seeded storage test that would have failed on every runner from 2026-10-17.
Notes
- Breaking on purpose: headless callers that scripted the old write-on-
--applybehavior must add--yes(the install refusal's exit code 6 names it), and headless scripts that calledbrain unprotectunattended must move to--apply --yes; plainbrain unprotectanswers a read-only preview instead of rewriting. - The converse route's honesty has a price: until the operator opts in, an
enforcesetting on the preset is a config error; as a chat-shaped route its output tokens are billed beside input ones once both prices are configured, and one price alone leaves the cost unknown. - The PreToolUse hook documents its honest hard floor - no deny when bun or the hook script is missing - and its uncovered lanes (Bash-class writes, temp-then-rename) in
hooks/README.md. brain_truthkeeps its documented advance-sincetime-window paging, and the diagnostics whose rows are aggregates a second page could not continue carry recorded report-cap reasons pinned beside the census tests.- A snoozed trigger is governed by its snooze window rather than the TTL expiry clock, so a long snooze cannot be cut short by expiry;
unsnoozeon a hand-edited record that lost its interrupted status refuses instead of inventing a lifecycle position to restore into. check-release.shnow also refuses a new CHANGELOG heading when the change kind is no-release (#265).- The version bump to 1.87.0 shipped inside the PR (#266), per the project rule in
CLAUDE.md. - Release image: the canonical terminal style, here as an interaction diagram (animated GIF in this body; static PNG, 2x PNG and the SVG source attached as assets).
