Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
90 commits
Select commit Hold shift + click to select a range
3482039
chore(brainstorm): write-side-trust
itechmeat Oct 10, 2026
799c986
feat(search): keep the write-approval review lane out of the index
itechmeat Oct 10, 2026
4bc3764
feat(secrets): named per-agent MCP token store, hash-at-rest (write-s…
itechmeat Oct 10, 2026
17a9075
feat(brain): resolve the signal-lane review gate inside writeSignal
itechmeat Oct 10, 2026
4e90a61
feat(permissions): vault permissions document loader and resolver (wr…
itechmeat Oct 10, 2026
974cd24
feat(permissions): decision ledger store with month/device JSONL shar…
itechmeat Oct 10, 2026
801fd6f
feat(mcp): transport token authentication and request-scoped identity…
itechmeat Oct 10, 2026
9c75c94
feat(permissions): brain permissions verb, unreadable-document doctor…
itechmeat Oct 10, 2026
b9f9414
feat(brain): open-decision vault for parked questions
itechmeat Oct 10, 2026
fc8c3b7
feat(brain): stage note and ingest writes in a multi-lane pending queue
itechmeat Oct 10, 2026
ef9fdb9
Merge branch 'wave/lane-A' into wave/lane-C
itechmeat Oct 10, 2026
c012001
feat(trust): owner-write gate refuses a caller-named foreign owner on…
itechmeat Oct 10, 2026
9644eea
Merge branch 'wave/lane-C' into wave/lane-D
itechmeat Oct 10, 2026
7d09d44
feat(brain): document-backed write dispositions and the force-confirm…
itechmeat Oct 10, 2026
5d23b74
feat(trust): note-lane owner-frontmatter guard under the owner-write …
itechmeat Oct 10, 2026
21c048f
feat(brain): ambient consent and TTL for extracted signals
itechmeat Oct 10, 2026
d56ab04
feat(cli): bootstrap command, mcp token dispatcher, and rotation (wri…
itechmeat Oct 10, 2026
3bf3401
Merge branch 'wave/lane-B' into feat/write-side-trust
itechmeat Oct 10, 2026
45a16c9
Merge branch 'wave/lane-C' into feat/write-side-trust
itechmeat Oct 10, 2026
3e34649
Merge branch 'wave/lane-D' into feat/write-side-trust
itechmeat Oct 10, 2026
a674479
Merge branch 'wave/lane-E' into feat/write-side-trust
itechmeat Oct 10, 2026
e4b05b4
test(architecture): reconcile the measured census pins across the mer…
itechmeat Oct 10, 2026
5334d0a
test(mcp): expect the write-refusal codes in the tool-error-code regi…
itechmeat Oct 10, 2026
1e4d47e
docs: document the write-side-trust surface for 1.79.0
itechmeat Oct 10, 2026
a8dd140
chore(release): bump version to 1.79.0
itechmeat Oct 10, 2026
6bd90d3
chore(openclaw): rebuild the bundle for 1.79.0
itechmeat Oct 10, 2026
70d6479
fix(trust): pin token-first credential resolution and name the disagr…
itechmeat Oct 10, 2026
255dce0
test(mcp): pin the open-decisions brief section to the operator-queue…
itechmeat Oct 10, 2026
47fc20b
refactor(permissions): state the real entry precedence and drop dead …
itechmeat Oct 10, 2026
7e13874
fix(decisions): refuse unreadable titles and reword the duplicate ref…
itechmeat Oct 10, 2026
ba80069
fix(cli): make the empty pending list message lane-aware
itechmeat Oct 10, 2026
40d4a4d
fix(bootstrap): honor the receipt and verdict contracts on the check …
itechmeat Oct 10, 2026
c53dbd7
fix(cli): name the real flag syntax in the mcp_tokens_required mint hint
itechmeat Oct 10, 2026
4c061ee
fix(brain): raise an unreadable guardrails config on the ambient capt…
itechmeat Oct 10, 2026
6424a58
fix(pending): publish staged notes as staged and preview the reject r…
itechmeat Oct 10, 2026
9626671
refactor(mcp): drop the dead tokensRequired option from authenticateR…
itechmeat Oct 10, 2026
3f0da92
fix(brain): log the owner-gate warn row only when the update commits
itechmeat Oct 10, 2026
7a31384
fix(trust): keep credential-shaped test literals out of scanner shape…
itechmeat Oct 10, 2026
aca2f2f
test(cli): give the secret bundle round trip an explicit subprocess b…
itechmeat Oct 10, 2026
aa43cd5
test(windows): compare the staged ingest path in one separator spelli…
itechmeat Oct 10, 2026
125faa0
fix(dream): drop expired signals before topic clustering and promotion
itechmeat Oct 10, 2026
6e8d03f
feat(doctor): report a permissions document that denies the local age…
itechmeat Oct 10, 2026
9b7af49
fix(tags): widen the shared tag rule to Obsidian's documented grammar
itechmeat Oct 10, 2026
d923336
fix(capture): record a telegram vocabulary refusal instead of crash-l…
itechmeat Oct 10, 2026
2f8e506
fix(brain): flag session summary divergence only on same-instant conf…
itechmeat Oct 10, 2026
c2e68c2
fix(brain): strip case-insensitive think and thinking blocks from pay…
itechmeat Oct 10, 2026
cc266f2
test(tags): pin the surviving orphan finding for cross-document digit…
itechmeat Oct 10, 2026
f4270f5
feat(sessions): report the ambient withheld count on imports
itechmeat Oct 10, 2026
5f520da
test(brain): seed the quiet-revisions scenario with explicit instants
itechmeat Oct 10, 2026
b0669b8
fix(secrets): authenticate credential-bundle metadata and refuse edit…
itechmeat Oct 10, 2026
3403bc4
fix(secrets): reach a wrapped store from a second process and unwrap …
itechmeat Oct 10, 2026
8882778
fix(brain): bind import approval digest to the content it approved
itechmeat Oct 10, 2026
6b71ada
fix(brain): carry the upgrade plan digest from dry run to apply
itechmeat Oct 10, 2026
fd7f420
fix(bootstrap): state the token boundary and stop losing minted material
itechmeat Oct 10, 2026
2f59710
test(mcp): expect the rotation grace window at the transport
itechmeat Oct 10, 2026
769fb73
fix(ingest): record the extraction contract per manifest entry
itechmeat Oct 10, 2026
bdab22a
fix(secrets): normalize reference names for the store and bound redac…
itechmeat Oct 10, 2026
0de28df
fix(mcp): name the unlock routes that work in the locked-store reason
itechmeat Oct 10, 2026
ccdb589
fix(brain): complete the write-disposition module the pending lanes r…
itechmeat Oct 10, 2026
1ca2d39
chore: sync the codex README mirror
itechmeat Oct 10, 2026
b0ea8c2
fix(decisions): keep duplicate questions resolvable and re-openable
itechmeat Oct 10, 2026
d34d3b1
fix(decisions): survive adversarial headings and a failed log mirror
itechmeat Oct 10, 2026
42d1fb6
fix(mcp): gate decision resolve and discard at the caller's reach
itechmeat Oct 10, 2026
eb8ca68
fix(cli): say that non-Latin decision titles hash to an unnamed id
itechmeat Oct 10, 2026
68d19d8
test(decisions): cover the resolved log mirror's non-throwing guard
itechmeat Oct 10, 2026
891b011
feat(trust): thread the request identity into every write-side decision
itechmeat Oct 10, 2026
a458f0c
fix(brain): consult the permissions document on the signal lane
itechmeat Oct 10, 2026
ad2ae10
fix(brain): refuse denied mutations and audit the review door
itechmeat Oct 10, 2026
12c51e7
fix(mcp): keep the auth gate closed when the token store cannot be read
itechmeat Oct 10, 2026
cb8e682
fix(brain): resolve the write-refusal exit lazily to break the diagno…
itechmeat Oct 10, 2026
f0abc25
fix(brain): read the write refusal's exit from a leaf, closing the di…
itechmeat Oct 10, 2026
04c0715
fix(mcp): trim the brain_decision descriptions under the registry cap
itechmeat Oct 10, 2026
66d79ad
test(brain): point the tag-grammar refusals at purely numeric tokens
itechmeat Oct 10, 2026
9208cb1
test(architecture): re-measure the write-site census for the envelope…
itechmeat Oct 10, 2026
9172e25
test(cli): sanction the bootstrap receipt's parameterized pointers
itechmeat Oct 10, 2026
dd1a7dd
test(maintenance): apply the reviewed plan digest in the self-heal su…
itechmeat Oct 10, 2026
254c5e2
docs: fold the review-round repairs into the 1.79.0 changelog
itechmeat Oct 10, 2026
4aac3c9
test: pin the platform-dependent halves of the new suites
itechmeat Oct 10, 2026
7a5237b
chore(openclaw): rebuild the bundle
itechmeat Oct 10, 2026
3adc821
test: normalize the staged-path separator in the ask-arm assertion
itechmeat Oct 10, 2026
9e7244b
fix(decisions): keep the resolve mint candidates inside the slug cap
itechmeat Oct 10, 2026
1eb71cf
fix(bootstrap): rescue a minted token when the registration half fail…
itechmeat Oct 10, 2026
6e5db12
fix(mcp): answer a retried write over a pending stage with its named …
itechmeat Oct 10, 2026
9fe54cf
fix(permissions): resolve the no-subject principal through the caller…
itechmeat Oct 10, 2026
3a56652
fix(mcp): fail closed when token enforcement is requested and the sto…
itechmeat Oct 10, 2026
144c769
docs: align the escape and finding docblocks with what the code does
itechmeat Oct 10, 2026
a059335
fix(secrets): report the original read-back refusal when the restore …
itechmeat Oct 10, 2026
4c71153
docs: fold the delta-review repairs into the 1.79.0 changelog
itechmeat Oct 10, 2026
5d598dc
fix(bootstrap): keep the orphaned-token receipt's pointers on the wri…
itechmeat Oct 10, 2026
363d6cf
chore(openclaw): rebuild the bundle
itechmeat Oct 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "open-second-brain",
"version": "1.78.0",
"version": "1.79.0",
"description": "Plugin-first second brain package for AI agents and humans.",
"author": {
"name": "Open Second Brain contributors"
Expand Down
2 changes: 1 addition & 1 deletion .codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "open-second-brain",
"version": "1.78.0",
"version": "1.79.0",
"description": "Plugin-first second brain package for Codex, Hermes, Claude Code, OpenClaw, and other agent runtimes.",
"author": {
"name": "Open Second Brain contributors",
Expand Down
41 changes: 41 additions & 0 deletions CHANGELOG.md

Large diffs are not rendered by default.

8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,16 @@ The full router with readiness criteria is [`install.md`](install.md); native Wi
- **Semantic search:** an embedding provider plus `sqlite-vec`; the `embeddings-setup` skill walks through it: [`skills/embeddings-setup/SKILL.md`](skills/embeddings-setup/SKILL.md).
- **Decision models:** a typed judgment model that can rerank search and filter candidates, off by default per use: [`docs/decision-models.md`](docs/decision-models.md).
- **Deep relational recall:** a fourth search arm over typed links, off by default. Its traversal runs under width budgets - 8 seeds, 4 edges per node, 16 nodes in total, and a hub above 12 walked edges is reached but not expanded - each overridable through an `OPEN_SECOND_BRAIN_SEARCH_TRAVERSAL_*` environment variable or a `search_traversal_*` config key; entity co-occurrence bridges join the walk by default and switch off separately (`OPEN_SECOND_BRAIN_SEARCH_ENTITY_BRIDGES`): [retrieval quality](docs/cli-reference.md#retrieval-quality-and-context-delivery-since-v1370).
- **Staged review for agent writes, off by default.** With no key set every write publishes exactly as before. `write_approval.notes` / `OPEN_SECOND_BRAIN_WRITE_APPROVAL_NOTES_ENABLED` and `write_approval.ingest` / `OPEN_SECOND_BRAIN_WRITE_APPROVAL_INGEST_ENABLED` (each falling back to the `write_approval.enabled` master, default off) stage note creates and ingest summary pages into `Brain/pending/` beside the staged signals, where they stay out of the search index until an operator runs `o2b brain pending list` and applies or rejects them; [write-path integrity](docs/cli-reference.md#write-path-integrity-and-store-safety-since-v1320).
- **A permissions document, absent by default.** `Brain/_permissions.yaml` (an operator-edited vault file) resolves `allow`/`ask`/`deny` per agent, role and target for the write, ingest and owner-write actions; with the file absent every check behaves exactly as today. `ask` stages the write, `deny` refuses with the `write-refused` token and the next command `o2b brain permissions show`, and every ask/deny verdict lands in a queryable decision ledger under `Brain/logs/decisions/`; `o2b brain permissions show` dry-runs the decision table, `ledger` reads the rows; [Brain CLI](docs/cli-reference.md#brain-observing-memory).
- **The owner-write gate, off by default.** `integrity.owner_scope_writes` in `Brain/_brain.yaml` (`off` | `warn` | `fail`, default `off`) refuses a caller-named owner that differs from the caller's resolved identity on the preference and note lanes (`warn` allows and records one decision-ledger row); a document `owner_write` verdict composes most-restrictive-wins with the gate; [write-time integrity](docs/cli-reference.md#write-time-integrity-and-governance-since-v0440).
- **Named MCP tokens, optional.** `o2b mcp token mint|rotate|revoke|list` keeps a hash-at-rest token per agent (`.open-second-brain/secrets/mcp-tokens.json`; material shown exactly once). Over HTTP a valid token authenticates as its agent per request, the shared `--api-key` stays valid as the operator master credential, and `mcp_tokens_required` / `OPEN_SECOND_BRAIN_MCP_TOKENS_REQUIRED` (default `false`) makes a non-empty token map refuse credential-less requests. `o2b bootstrap --target <harness> [--token] [--rotate] [--check]` provisions MCP registration, token and receipt in one idempotent command, and `o2b bootstrap --remove <harness>` tears a provision down again; the minted token authenticates HTTP clients configured by hand, while the registered stdio harness presents no credential and keeps its config-derived identity; [core CLI](docs/cli-reference.md#core).
- **Ambient capture consent, opt-out.** `guardrails.ambient_writeback: false` suppresses the ambient extraction lane with a counted `ambient-withheld` event (absent keeps today's behavior), and `guardrails.ambient_ttl_days` stamps an `expiration_date` on ambient-extracted signals so reads drop them after the window (absent stamps nothing); a TTL-stamped signal still stages when the review gate is on and survives apply verbatim.
- **Open decisions.** `o2b brain decision open --title <t> --question <q> --option <o> [...]` parks a question with enumerated options at `Brain/decisions/open-<slug>.md`; `resolve` mints the real `type: decision` page, `discard` closes without deciding, and the morning brief renders up to five open questions: [belief lifecycle](docs/cli-reference.md#belief-lifecycle-and-decision-memory-since-v1330).

## What is new

1.78.0 puts credential custody under an operator passphrase. The secrets keyfile wraps into a scrypt envelope behind `o2b brain secret unlock`/`lock`, `$secret:NAME` references resolve through the custody store at the embedding, decision-model, research, Telegram and installation-secret use sites, and `secret export`/`import` move the store between installs as one passphrase-encrypted bundle. Resolved credential literals are redacted at the error and config-mapping boundaries, composed tags must parse as Obsidian tags, a declared page vocabulary gates capture writes, import and upgrade plans carry an approval digest that apply must match, and a changed extraction contract reprocesses the sources it covers. Every release is described in the [CHANGELOG](CHANGELOG.md).
1.79.0 gives every agent write a name, a rule and a review door. `o2b mcp token` mints a hash-at-rest token per agent that authenticates over HTTP as that agent per request (the shared key stays valid; `mcp_tokens_required` can make the map mandatory), `o2b bootstrap` provisions a harness in one idempotent command, and `Brain/_permissions.yaml` resolves allow/ask/deny at one chokepoint with every ask/deny verdict in a queryable decision ledger. Gated writes stage into `Brain/pending/` where recall cannot see them until an operator applies them, `integrity.owner_scope_writes` refuses a caller-named foreign owner, `o2b brain decision open` parks a question with enumerated options until it becomes a real decision, and ambient extraction answers to `guardrails.ambient_writeback` consent and `guardrails.ambient_ttl_days`. Every gate ships default-off: with no tokens, no document and no keys, every write path behaves exactly as before. Every release is described in the [CHANGELOG](CHANGELOG.md).

## Documentation

Expand Down
47 changes: 47 additions & 0 deletions docs/brainstorm/write-side-trust/cli-output/claude.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Consultant output - write-side trust

Produced in-session by the design orchestrator (host mode: the consultant run and the orchestrator are the same session; no external process was spawned). The prompt used is at `cli-output/prompt.md`; the analysis below is the verbatim variant output, also mirrored in `variants.md`.

### Variant 1: Substrate-first spine - one permissions document, one chokepoint, gates as consumers

- **Approach**: Land identity and authorization as pure leaf modules first: a token store that maps hash-at-rest credentials to agent names, and a permissions document (`Brain/_permissions.yaml`) with a pure resolver returning allow/ask/deny per (subject, action, target). Every action gate - the staged-review lanes, the owner-write gate, the force-confirmed rule - becomes a consumer of one disposition function that consults the document, or the legacy per-feature keys when no document exists. Ask stages into a generalized multi-lane pending queue (the A3 precedent: staging is a change of directory), deny refuses with a named token, and every non-allow verdict appends one row to a decision ledger that records the rule that decided. Ambient capture ships last, strictly behind the gates. Landing order: document + resolver + ledger + token store (pure, disjoint) -> transport auth, recall exclusion, signal chokepoint, owner-write preference lane, multi-lane staging, open decisions, ambient consent (parallel, disjoint files) -> document-backed dispositions, note-lane owner guard, bootstrap (integration) -> reconciliation.
- **Trade-offs**:
- Pro: every card inherits the same substrate, so "who was allowed, asked, or denied what, by which rule" has one answer and one record. The t_29798f41 requirement (a queryable ledger replacing scattered point checks) is satisfied by construction rather than by a later reporting pass over heterogeneous gates.
- Pro: default-identity is trivially auditable: with no document, no tokens, and no keys, every consumer short-circuits to today's behavior, so the existing suites are the byte-identity proof.
- Pro: the chokepoints already exist and are proven - `writeSignal` for signals (the `targetDir` staging seam), `createNote`/`applyWriteBatch` for notes, `resolvedOwnerFor` for preferences, `admitToIndex` for recall. The wave adds predicates beside them, it does not reroute traffic.
- Pro: the ask verdict reuses the pending queue's apply/reject semantics, so the human approval door is the existing CLI door with a widened id grammar - one review UX, not one per lane.
- Con: the document resolver must be a true leaf module or the import-cycle ratchet (`tests/core/architecture/import-cycles.test.ts`) blocks the gates from consulting it; this constrains what the substrate may reuse.
- Con: five lanes touching one spine need the contract pinned in the plan (signatures, config keys, shared-append files), or the merge is where the design actually happens.
- Con: two staging sources (document vs `write_approval.*` keys) need an explicit precedence rule or operators get different answers on different days; the design pays this with "document present = document only".
- **Complexity**: medium-high
- **Risk**: low-medium (byte-identity is per-consumer and independently testable; the substrate is pure and small)

### Variant 2: Gate-local first, document later

- **Approach**: Ship each card on its own config keys exactly as the existing gates work: staged review extends via `write_approval.*` lane keys, the owner-write gate via a new integrity key, tokens via the transport, bootstrap as orchestration. Defer the permissions document to a later wave that retrofits a policy layer over the now-existing gates, mapping each key into a document entry.
- **Trade-offs**:
- Pro: each task is independently shippable with the smallest possible blast radius; no substrate commit needs to land first, so lanes never wait.
- Pro: no new operator-facing document to design, validate, and fail-close this wave; the `write_approval` and `integrity` key patterns are established and understood.
- Con: the approval door gets built twice - the pending queue generalization and the ledger need a verdict vocabulary now, and a later document has to either subsume the keys (a second migration) or live beside them (two sources of truth for the same question, the exact "scattered point checks" shape the card exists to remove).
- Con: the ledger records gate verdicts that have no rule identity beyond a config key; retrofitting entry/role/default provenance onto rows written by key-driven gates means a schema migration on an append-only store.
- Con: `force_confirmed` and the role-matrix gap stay unanswerable: without a document there is no principal model to hang the "requires allow" rule on, so the one bypass the recon proved ships unchanged with no decision recorded.
- Con: identity does not compose - a token identity with no document gives per-caller `brain_context` attribution and per-caller owner-scope refusal, but no per-caller write policy, so the t_85059d6d and t_29798f41 cards land as strangers.
- **Complexity**: medium (per task) but higher cumulative (double build of the door)
- **Risk**: medium (the deferred document wave re-opens every file this wave touches)

### Variant 3: Transport middleware - decide at the dispatch boundary

- **Approach**: Put identity, policy, and staging in one middleware layer at the MCP/CLI boundary: `authenticateRequest` resolves identity, a policy check runs before every tool handler from a table keyed by tool name, and mutating calls are redirected into review by the wrapper rather than by the write primitives. Core modules stay untouched; the permissions document is read only by the wrapper.
- **Trade-offs**:
- Pro: smallest core diff - one wrapper, one policy table, no changes to write primitives; trivially reversible.
- Pro: the dispatch seam already records refusals (the `mapFrozen` pattern at `src/mcp/server.ts:336`), so denial logging has an existing home.
- Con: the boundary is not the write seam, which this project has already learned the hard way: internal writers (dream apply, hygiene, write-session commit, session import, inline scan, capture lifecycle) never cross the dispatch wrapper, so staged review would miss exactly the bulk lanes the card names. The t_107cac80 recon shows the same lesson on the read side: the gate lives at `coerceAgentScope` because that is the one reader, not because the boundary is privileged.
- Con: CLI verbs bypass the wrapper entirely, so the operator's own paths and any script calling core directly would need a parallel enforcement story.
- Con: staging needs the resolved target path, which exists only inside the write primitives (`resolveNoteTarget`, `resolveEffectiveScope`); a wrapper can only see raw arguments, so it would re-implement path resolution or stage the unresolved name - both drift from what publish would actually write.
- Con: per-lane review granularity (stage creates, allow updates of published notes) is a property of the operation, not the tool; a tool-name table cannot express it.
- **Complexity**: small
- **Risk**: high (repeats the `o2b brain protect` failure the wave exists to fix: enforcement at a layer the writers bypass)

## Recommendation

Variant 1, the substrate-first spine. The wave's seven cards share one question - "may this principal do this write, and who says so" - and only Variant 1 answers it in one place. The chokepoints the gates need already exist and are census-pinned, so the spine is predicates beside proven seams rather than new plumbing; the pending queue generalization gives the ask verdict a human door that already has apply/reject semantics, tests, and a CLI. Variant 2 is honest about sequencing but builds the approval door and ledger twice and leaves `force_confirmed` unanswerable; Variant 3 is the smallest diff and the wrong layer, missing the internal and CLI writers that make up most of the write surface.
Loading
Loading