Changelog
What shipped, including what we got wrong.
Every release, in order. When Brainlyy gets something wrong, the correction goes here rather than quietly into a commit message.
Unreleased
- FIXEDthe important one
A note could call itself verified. The dashboard decided a note was VERIFIED by checking whether its text contained the word "VERIFIED", and drew a green badge for it. Nothing verified anything, in the one project whose premise is catching claims that carry no check.
The status is now computed by an evaluator and never by the note. A claim declares named evidence; the names resolve only to checks in config, because a note is untrusted input. Three states:
VERIFIED(every check passed, recently),UNVERIFIED(checkable, and something failed or expired), andASSERTED(not machine-checkable, which is legitimate, and where most notes live). Astatus:field in frontmatter is ignored and flagged as self-certification.This demotes most existing notes, which is the correct outcome. The badges were wrong.
- ADDED
brainlyy verify, which re-checks every claim, records what was observed to an append-only log, and reports how long the longest-lived false claim read VERIFIED before a run caught it.--strictexits 1 for CI. - ADDED
docs/claims.md. - ADDED
A dashboard.
brainlyy uiserves a local web UI: tier and capabilities, note search, a vault browser where provenance is the primary visual dimension, and a code-graph view. Light and dark, phone-first. Vanilla HTML/CSS/JS served by the same Python process, with no Node, no bundler and no build step. Fonts are bundled rather than fetched, so it renders identically with no internet. - ADDED
A sign-in page.
brainlyy passphrasesets a local passphrase; the dashboard signs in at/login. scrypt (n=2¹⁵) with a random salt and the parameters stored beside the hash so they can be raised later; the passphrase itself is never written. Sessions are signed rather than stored, and the signature is verified before the payload reaches a JSON parser. Five failures locks an address out for fifteen minutes. No third-party identity provider, no account, and it works offline. For a team, put an OIDC reverse proxy in front instead, which the security page explains. - ADDED
Security headers on every response:
nosniff,no-referrer,X-Frame-Options: DENY, and a CSP with nounsafe-inline. The pre-paint theme script is allowed by a sha256 computed from the exact bytes served, so the hash cannot drift from the file. - ADDED
Browser sessions.
/?k=<token>is exchanged for an HttpOnly, SameSite=Strict cookie and redirected, so the token leaves the URL bar. Neither cookie ever authenticates/mcp, because the browser attaches them automatically, so anything they open is open to any page the browser is on, and the tools must never be in that set. - ADDED
ui.bind(loopbackby default,meshto opt in) andui.remote_writes(off by default), judged on the address the connection arrived on. - ADDED
A JSON API:
/api/status,/api/search,/api/notes,/api/notes/{path},/api/agents,/api/graph. - ADDED
Write actions, loopback-gated.
POST /api/agent/run,/api/rateand/api/postare proxied to the loopback write server with its own token, so everything past that hop is enforced exactly as it is for a local caller. The UI decides only whether to forward, judged on the address the connection arrived on. Every refusal is distinguishable on purpose: 401 no credential, 403 wrong door, 400 bad body, 503 write server not running, 502 it refused./api/status.writes_enabledis computed by the same function that gates the endpoints, so the dashboard cannot render a control the server would refuse. - ADDED
A code-graph explorer. Canvas rendering with the layout settled across animation frames so the page never blocks, pan and zoom, a floating toolbar, counted communities and
kind:/repo:/path:filter chips. Totals are always exact while only the 450 most-connected nodes are drawn. A dashboard that reported the size of its own sample would be lying in the most plausible way. Symbols are namespaced per repository, because two repos that both definemainare two symbols, and merging them would invent call edges that do not exist. - ADDED
brainlyy docs --buildrendersdocs/*.mdinto one self-contained page in the same design system, with the fonts inlined. The markdown ships in the wheel, so it works on a machine that has never seen the repository. - ADDED
LICENSE, MIT, matching whatpyproject.tomland the README already claimed. The repository was not actually licensed before this. - ADDED
Continuous integration: the property suite runs on push across Linux, macOS and Windows on Python 3.10 to 3.13, plus a clean-machine
brainlyy init. - ADDED
Documentation set: quickstart, concepts, install, CLI reference, security, glossary and FAQ.
- ADDED
CONTRIBUTING.md. - FIXED
Config discovery preferred the working directory over an explicitly set
BRAINLYY_HOME, socdsilently changed which instance answered: standing in a directory that happened to hold an unrelatedconfig.yamlloaded it, and the tool then reported confidently about the wrong vault. An explicitly named instance now wins. Found by running the property suite from an installed package, where the instance directory is also the working directory.
0.1.02026-09-03
- ADDED
Installable package.
brainlyy init/doctor/serve/search. - ADDED
Two MCP servers: a read server bound to a mesh address, and a write server that exits at startup if configured to bind anything but loopback.
- ADDED
Capability detection with tiers 0 to 4. Nothing is required; each dependency found turns on more.
- ADDED
Tier 0 note search: lexical ranking over a markdown vault, no vector store.
- ADDED
Knowledge and agent (SOUL) templates, rendered by
init. - ADDED
A property suite of 66 assertions covering bind addresses, the loopback guard, allowlist refusals, and that no deployment-specific value travels with the package. It ships inside the wheel, so an installed instance verifies itself with the same assertions.
- FIXED
register()resolvedclaudewithshutil.which()and then invoked the bare name. On Windows those are different lookups, becauseCreateProcessappends.exe, so it verified one executable and ran another. - FIXED
_detect_mesh_ip()testedstartswith("100."), which matches public address space, and excluded the CGNAT base address by string prefix rather than by range. - FIXED
_fill()silently passed through a config key that matched no template line, handing the user the example's placeholder values as their own answers.