Changelog

Dates are when the work landed, not when it was announced. Where something was found to be broken along the way, it is written down here rather than quietly fixed.

Breno is built in phases, and each phase is signed off against acceptance criteria written before it started. This page is that log, edited for an audience that does not work in the codebase. It is not a roadmap: nothing below is a plan, and nothing is listed before it exists.

9 October 2026

Production readiness

  • Row-level security now covers every table that carries a workspace id — 71 of them — with the twenty deliberate exemptions named in the migration and the reason each cannot have one. A test walks the database catalogue and fails by name if a new table is added without either, so the gap cannot quietly reopen.
  • Migrations run as their own step behind an advisory lock rather than at application startup, and the API refuses to serve against a database that is behind the binary.
  • Backups no longer dump as the low-privilege application role into a directory that disappears with the pod. They dump as a privileged role into object storage, encrypted, with a separate job that reads the object back and restores it into a scratch database to prove it works.
  • A real end-to-end suite with no mocks, no fixtures and no intercepted requests: a real sign-up, an upload that becomes searchable, retrieval, opening the cited source, a real export downloaded and inspected, and account deletion proved by the sign-in stopping.

What did not go well. Tracing was wired end to end and had never once started. Two resource-merge calls disagreed about a schema URL, so the API exited on startup in every environment that switched tracing on — and no test had ever called the initialisation path, so nothing caught it. The fix came with a regression test that calls it and fails on the original error. Two further things surfaced the same week: a log line the operators grep by hand had been carrying no trace id since the day it was written, and an early attempt to read the trace capture was wrong because the Go and JavaScript exporters emit different encodings — the conclusion drawn from it was backwards until it was checked again against the raw bytes.

Scale, and one honest failure

  • Sync jobs now book against a shared rate-limit budget in Redis keyed by provider, rather than each replica keeping its own — a provider’s limit is a real limit, and a per-process counter would have handed every replica a fresh allowance.
  • Webhook deliveries now record a connection the moment authorization finishes and queue its first sync immediately, with the reconcile sweep dropped to an hourly backstop.

What did not go well. The thousand-connection acceptance run was executed and failed its gate: p95 scheduling lag of 327.66 seconds against a 300 second budget, with five hundred jobs past the two-minute job timeout. Throughput itself held. The run also skipped the proxy layer and the host suspended partway through, so it is not a clean measurement — but it failed, it is recorded as having failed, and it is why the thousand-source figure is described as a target rather than an achievement.

Retrieval quality

  • Keyword search was one pattern match over the entire question. It is now a proper full-text index ranked by relevance, with a trigram fallback for identifiers it cannot see — a ticket number like INC-4471 is two tokens, so searching for4471 used to match nothing at all.
  • Filters moved out of the request layer and into each retrieval path’s own query, so the top results are computed after filtering rather than before it.
  • A permanent evaluation gate: sixty seeded documents and fifty-five questions run through the real pipeline nightly. Hit rate at three is 83.3% against a floor of 80%, and mean reciprocal rank is 0.757 against a floor of 0.65.

What did not go well. A better ranking method was measured, built, and then deliberately not shipped: it changed the scale that the “refuse rather than guess” guard reads, and at its floor it answered 22 of 48 answerable questions where the current method answers 26. Better on average, worse where it matters.

Portability

  • The export was rebuilt. It is now a documented archive — items with their metadata and relationships, full text and superseded versions as Markdown, original uploads byte for byte, memory, decisions, the graph, and a checksum file — produced as a background job rather than a request that times out on a large workspace.
  • Import reads three shapes: that archive, an Obsidian or plain Markdown vault, and a Notion export. Identity is provenance, so importing the same material twice updates it rather than duplicating it, and the round trip out and back in is a test.
  • Deleting your account is self-service, behind a typed confirmation and, where enabled, an authenticator code. Every one of the 88 workspace tables now cascades from the workspace itself, so a deletion is genuinely complete rather than approximately complete.

What did not go well. Two export paths had been silently broken by scanning a nullable column into a plain string — an item extracted without a description failed the entire export, not just its own line. Separately, the cleanup that purges private items on deletion had an inverted guard and had been purging nothing at all; it was found while writing the cascade test.

8 October 2026

Reaching the knowledge from outside

  • A Model Context Protocol server, so an assistant you already use can search, fetch, capture and export from a workspace — under the same access rule that governs the interface, with no separate permission model to get wrong.
  • OAuth 2.1 with dynamic client registration, so the server can be added by URL rather than by editing a configuration file.
  • A REST API over the same operations with scopes, rate limits and an audit trail, plus Go and TypeScript client libraries.

Provenance and sync

  • Synced items carry where they came from: which connection, which external id, the source URL, the author and the timestamps. Every citation shows it.
  • Editing a source updates the item rather than creating a second one, and a deletion at the source moves it to the trash — but never on a partial listing, so a provider having a bad day cannot make Breno delete your material.
  • Signed webhooks from Slack and GitHub queue a sync within seconds.
  • Sync history is kept for 30 days and finished sync jobs for 90, both configurable by the operator of an instance.

7 October 2026

The knowledge model

  • Every item has an owner, a source type and its original file metadata. Private items are readable only by their owner, through the same shared rule as everything else.
  • Rename, sensitivity and visibility from a single item page, with sensitivity labels restricted to the ones you are actually cleared for.
  • Deletion goes to a trash with a 30-day restore window, and a purge removes everything derived from the item — twenty tables, the stored files, and its edges in the graph.

6 October 2026

Focus

  • Navigation and positioning narrowed to what the product is actually for: knowledge preservation and portability. Surfaces outside that scope were taken out of the interface while their code stays intact and tested.
  • Built the missing chat, team, audit, analytics, settings, connect-AI and export pages.

Closing the leaks

  • One shared read rule, enforced in every retrieval path — keyword search, semantic search, the graph, memory recall, citations, the API, the MCP server, export and bulk actions. It had not been, and each rule is now proven by an integration test that fails when the rule is removed.
  • API tokens now die with membership, erasure, revocation or workspace deletion. Before this, a token could outlive the access it was granted under.
  • Production refuses to start without row-level security enforced, a non-superuser database role, and a credential encryption key. A misconfigured deployment fails loudly at boot rather than quietly serving.

Reliable capture

  • Uploads up to 25 MB across documents, spreadsheets, presentations, HTML, CSV, audio and scanned PDFs, with the limits shown in the interface rather than discovered by failing.
  • Links are fetched and normalised: a link to a PDF, an Office file or an audio file is extracted, and an unsupported type is refused before it is queued rather than failing afterwards.
  • Jobs are claimed by a bounded worker pool with automatic retry, a lease reaper for work whose worker died, and a manual retry that re-fetches links. An item stays searchable even when enrichment fails.

Note. This log begins here. Everything before it is the work of getting the system to a state worth describing, and it is not reconstructed after the fact.

Back to the home page