These are short documents recording the reasoning behind major technical choices. An ADR records the decision as it stood on its date, and is not kept current. The code, the workflows and the rest of docs/ describe the system as it is now.
Every load-bearing decision in the Linux subproject has an ADR. If a contributor asks "why did we pick X", the answer should already be in this folder. If it is not, we missed an ADR.
# 000N: Title
- **Status**: Accepted
- **Date**: YYYY-MM-DD
## Context
The forces at play. What is the situation that requires a decision?
## Decision
The choice we made, in one sentence.
## Rationale
Why this choice over the alternatives. Reference the alternatives explicitly.
## Consequences
What we accept by making this choice. Positive, negative, neutral.
## Alternatives considered
Brief note on each alternative and why it lost.Context, Decision and Consequences are always there. Rationale and Alternatives considered are there when other options were weighed. A section an ADR needs beyond these, such as a matrix or a dated note, comes after them.
The Status is Accepted, Superseded by 000M or Deprecated. A decision for every platform is in core/docs/decisions/, and a Status names one of those as core 000M. An accepted ADR that a later one changed in part reads Accepted; <part> superseded by 000M (replaced) or amended by 000M (extended). The amended <date> statuses below predate this rule.
ADRs are short on purpose. If yours is more than a page, you are probably arguing instead of recording.
A new ADR supersedes the old one, as in Michael Nygard's Documenting Architecture Decisions. The new ADR says what it replaces and why. The old ADR's only edit is its Status line, naming the new one, plus the same change to its row in the index. Do not rewrite an old ADR's body to match the current system.
A fact the repository keeps elsewhere, such as a workflow trigger, a branch, a variable's default or a ruleset, is pointed to from an ADR, not copied into it, so the ADR stays true without edits.
| # | Title | Status | Summary |
|---|---|---|---|
| 0001 | No plugin system; drivers are statically linked | Superseded by core 0004 | Drivers are crates, registered at compile time. No .tableplugin-equivalent on Linux. |
| 0002 | Rust + GTK4 + libadwaita | Accepted; version floor superseded by 0015, the Relm4 consequence by 0012 | Validated by spike. Only stack with a production virtualized data grid (GtkColumnView). |
| 0003 | Relm4 for app architecture | Superseded by 0012 | Elm-style components scale to TablePro's view count. |
| 0004 | Secret storage through oo7 | Accepted; store API and Flatpak path amended 2026-09-15, the rules for edits 2026-10-15 | The Secret Service on the host, and oo7's own keyring file with a key from the Secret portal inside Flatpak; never a plain-text file. |
| 0005 | Supply-chain policy | Accepted; the MySQL CA bundle's watch list amended by 0037 | Maintained crates first; custom code needs recorded evidence and a removal trigger. |
| 0006 | Meson drives Cargo | Accepted; where the version lives amended by 0017, the release tarball and the place of the Cargo workspace amended by core 0001 | Meson owns install, data files and the build-time config; Cargo stays the Rust compiler. |
| 0007 | Preferences and window geometry in GSettings | Accepted; the schema id and path per profile amended 2026-10-15 | A schema with ranges and enums replaces preferences.json and window.json; keyed data stays in files. |
| 0008 | Flatpak sandbox permissions | Accepted; the Flathub submission deferred by 0018; local sockets amended by 0035; Flathub permission review amended by 0041 | Network, display, home and removable drives for SQLite, the SSH agent and the Kerberos KCM socket; portals for the rest; two Flathub lint exceptions. |
| 0009 | One crypto provider, upstream sources and dependency bans | Accepted; sources and the PostgreSQL and ClickHouse TLS setup superseded by 0016, the ring ban amended by 0023, the rsa ban amended by core 0008 | aws-lc-rs only; deny.toml enforces sources, licenses, bans and advisory ignores. |
| 0010 | DDL plans against table definitions | Accepted; truncating and schema and database changes amended by core 0013 | Each engine plans structure changes through its DdlDialect against the table's definition; the UI offers only what the plan accepts. |
| 0012 | One window per connection session, built from gtk-rs subclasses | Accepted; what a window holds superseded by 0028 | One TableProApplication per profile through D-Bus uniqueness; a window holds at most one session and returns to the picker on disconnect; the application, the window and its workspace are subclasses, not Relm4 components. |
| 0013 | SSH through the system OpenSSH client | Accepted; the forced options amended by 0019, the TCP port amended by 0023, the russh consequence amended by core 0008 | ControlMaster per connection, Unix-socket forwards, prompts through an askpass helper. |
| 0014 | Lossless values and engine dialects | Accepted | A value reaches the user and goes back without loss; every statement is spelled by the dialect of the connection that runs it. |
| 0015 | GNOME 50 platform floor | Accepted; the runtime row amended by 0018 | GTK 4.22, libadwaita 1.9, GtkSourceView 5.18, GLib 2.88; AdwShortcutsDialog replaces the shortcuts window. |
| 0016 | Driver contract v2 | Accepted; the no-fork rule for tiberius superseded by core 0005, a database change outside a transaction amended by core 0013 | Owned calls with engine stops, phase-aware write outcomes, server-side row limits; PostgreSQL on tokio-postgres; no forks. |
| 0017 | Distribution channels | Superseded by 0018 | Flathub, a .deb for Ubuntu 26.04 on GitHub Releases built with rustup, and an .rpm for Fedora 44 on COPR, all from meson install and one vendor tarball; no AppImage. |
| 0018 | A PPA and COPR first, Flathub later | Accepted; the source tarball amended by core 0001; release channels and the Snap alternative superseded by 0041; Flathub permission review amended by 0041; the source of debian/copyright superseded by 0040 |
A Launchpad PPA built with rustc-1.97 and a COPR project built on each release are the primary channels; Flathub follows as a submission a maintainer writes; Rust 1.97 is the MSRV. |
| 0019 | A chosen SSH key file limits the keys ssh offers | Superseded by 0020 | A key file comes with IdentitiesOnly=yes, so the agent's other keys cannot use up MaxAuthTries first. |
| 0020 | Pin only a key file the agent does not hold | Accepted | IdentitiesOnly=yes only for a key file the agent lacks, so an agent-held certificate or encrypted PEM key still signs. |
| 0021 | Engines without SQL keep the driver contract and say what they hold | Accepted; amended by 0026 | A key-value engine states DataModel::KeyValue, lists keys with scan_keys and reads them with read_key, has its own SqlGrammar variant, and is held read-only by the driver's check where the server cannot. |
| 0022 | An AI assistant and a local MCP server | Accepted; the level of connections saved before it existed superseded by 0033 | An MCP server on 127.0.0.1, off until turned on, with a token and a per-connection External Clients level, every client write approved in the app; an AI assistant with no provider until the user adds one and a per-connection AI policy. |
| 0023 | A loopback relay for the CQL client | Accepted; the Linux-only relay amended by core 0008 | Each scylla session dials a 127.0.0.1 relay that serves only this user and carries every connection over Transport::open, so CQL reaches a server through an SSH forward. |
| 0024 | Multiple cursors on text marks, in the app | Accepted | The SQL editor's view, a GtkSourceView subclass, keeps extra cursors as GtkTextMark pairs, runs GTK's own commands once per cursor and repeats committed text from end-user-action, so one undo takes back every cursor's edit. |
| 0025 | DuckDB built from the duckdb crate's bundled sources | Accepted; the crate version superseded by 0031 | No PPA or COPR target but Ubuntu 26.10 ships libduckdb, so DuckDB 1.5.6 is compiled from the crate on every channel, with no extension fetched or loaded on its own. |
| 0026 | Document collections open in the table tab, and their statements are shell calls | Accepted | A document engine states DataModel::Document, lists collections as tables, opens them in the table tab, and every builder writes a mongosh call with bound values through a document arm chosen by the grammar. |
| 0027 | One data grid on GtkColumnView, with rows and a focused cell | Accepted; row numbers superseded by 0044 | Every surface shows rows in one DataGrid: row selection plus a focused cell, stable row slots, a row number column, values as text and a row detail pane. |
| 0028 | A window holds several sessions in one tab view | Accepted | A WorkspaceShell per window shares one tab view among its sessions, each with its own workspace; the sidebar is a tree of the saved connections with each open session's schemas and tables. |
| 0029 | Images from the database decode in glycin's sandbox | Accepted | Row Details decodes a value's picture with the glycin crate 3.x, in a sandboxed loader process, never with GTK's in-process loaders. |
| 0030 | A query tab's database, and another database as a session of its own | Accepted; dropping a sibling's database amended by 0034 | A query tab's own editor session moves to its database through the driver, or opens there where the engine binds it at connect; another database the window shows is a sibling session with its own workspace and sidebar row. |
| 0031 | DuckDB 2.0, writing files DuckDB 1.x reads | Accepted | duckdb 1.20000.1 compiles DuckDB 2.0.0, and every database opens with storage_compatibility_version = 'v0.10.2' so new and attached files stay readable by DuckDB 1.x. |
| 0032 | The connection list is the library's tree in a list view | Accepted | The picker and the window's tree show groups nested and in the library's order from one outline keyed by id, with Favourites and Recent sections in the picker, drag and drop, and a menu item for every move. |
| 0033 | No compatibility with pre-release states | Accepted | Every app-owned format starts at version 1 with no upgrade step and the history schema is one migration; nothing reads what a build before the first release wrote. |
| 0034 | Dropping a database the window holds open | Accepted | Drop is never offered for a session's own database; a sibling is closed after its prompts before the connection drops its database; a session whose database is gone parks. |
| 0035 | Flatpak access to local database sockets | Accepted | The manifest grants /run/postgresql and /run/mysqld read-only, so PostgreSQL, MySQL and MariaDB sockets reach the sandbox; /tmp and /var/lib/mysql stay out. |
| 0036 | A query tab edits the one result of its run, through the table tab's save | Accepted | One tracker per query tab under its tab id, held by the run's one editable result; saves go through the window's connection with Confirm Writes, history and row guards, and the statement runs again after a save. |
| 0037 | When the MySQL CA bundle goes | Accepted | sqlx #4044/#4045 add rustls-platform-verifier features, not a caller's ClientConfig; the bundle goes once sqlx's own TLS passes system-trust, extra-CA and name tests against MySQL. |
| 0038 | Preferences > Account, the device limit dialog, offline files and the license ask | Accepted; the Features section superseded by 0042 | The page shows the plan and features from the entitlement and the teams and devices from /v2; over the limit a dialog removes a device in place; an offline file works signed out; from the 30th day of use without a license a toast asks, then every 90 days. |
| 0039 | Usage data through posthog-rs, on by default as on macOS | Accepted | posthog-rs's blocking client sends the clients.md events to the key -Dposthog_key gives; on by default, off from Preferences > General > Privacy; no key, no client. |
| 0040 | The copyright file describes the source tarballs | Accepted | debian/copyright covers every crate cargo vendor ships and the code crates carry from other projects, from cargo metadata and each crate's license files; the binary inventory keeps the About dialog, the notices and the RPM License tag. |
| 0041 | Beta repositories, a Flatpak repository of our own, and packages on every GitHub release | Accepted; the run that builds a tag's packages amended by 0043 | A beta tag goes to a beta PPA, COPR project and Flatpak repository that testers add beside the stable ones; a signed Flatpak repository on R2 serves every other distribution with updates; every GitHub release carries the tested .deb and .rpm and a Flatpak bundle that updates from that repository, with SHA256SUMS. |
| 0042 | Preferences > Account lists only the features TablePro for Linux has | Accepted | Features lists only the codes whose platform flag is on and hides when none is; the licensing links move to a License group; the copy speaks of the plan and the license, never of a feature missing on Linux. |
| 0043 | A tag publishes what its commit's dry run built | Accepted | A tag's run takes the tarballs, template, source packages, packages and unsigned Flatpak repositories from the green dry run of its commit, re-signs only the Flatpak, checks the rest against the dry run's SHA256SUMS, and fails before publishing when there is no such run. |
| 0044 | Row numbers share the grid’s scrolling and input | Accepted | A passive gutter shares the main grid’s selection and vertical adjustment inside one scroller; native input stays in the main view. |
0011 is not used. It was reserved for the DDL decision, which was recorded as 0010, and docs cite ADRs by number, so the later ones keep theirs.
- Take the number after the highest one. Never reuse a number, even a deprecated ADR's.
- Copy the format above into a new file. Use a dash-separated lowercase title slug.
- If it replaces an earlier decision, set the earlier ADR's Status to
Superseded by 000N, orAccepted; <part> superseded by 000N, in the same PR. - Open a PR. ADRs go through the same review as code.
- After merge, link from the index above.