Skip to main content

Local PostgreSQL and MySQL access

The local bundle runs a shared SQL service. Its private Unix socket provides independent console sessions by default, using an OS peer check and a managed local login. PostgreSQL, MySQL and native HTTP/TLS listeners are independently opt-in. All adapters share account coordination and current authorization.

Native HTTP/TLS sessions​

For remote consoles, configure the native listener and an enabled account:

sql:
native:
local: true
http:
address: 0.0.0.0
port: 19109
tls: true
certificate_file: ./secrets/server.pem
key_file: ./secrets/server.key
accounts:
- name: developer
role: local_owner
authentication: password
password_file: ./secrets/developer.password
bootstrap_owner: true

The certificate must cover the hostname used by clients. The console connection file supplies the HTTPS endpoint, SQL account/database and password/CA references. No server signing key, installation record or filesystem mount is needed. Native HTTP uses the same shared runtime with both database-wire listeners disabled. Native TLS uses its explicit certificate/key independently of sql.tls, which governs PG/MySQL. Plain HTTP is admitted only for an explicitly configured loopback trust profile.

sql.native.local: false disables local admission. It does not delete accepted work. The reserved local account cannot be redeclared in sql.accounts. Source bindings use sql.native.sources_file for local sessions or sql.accounts[].sources_file for network accounts; see source declarations. The configured sources are service-owned identities, never supplied by a console.

Use the same installed bundle throughout an installation's lifetime. A compatible installation can enable SQL by changing its YAML configuration and restarting transit server. An incompatible definition or executable hash rejects before opening retained state. Keep the matching old bundle to resolve its accepted work; initializing a different directory is a separate operation. The server never deletes or resets an incompatible installation.

Start locally without TLS​

Use Linux x86_64, the local bundle, and ordinary psql and Oracle mysql clients. The development shell supplies both clients. Add this section to transit.yaml, selecting free ports:

sql:
postgres:
port: 5434
mysql:
port: 13306
tls:
mode: disabled
accounts:
- name: owner
role: local_owner
authentication: trust
bootstrap_owner: true

Run transit server for both a new installation and an existing compatible one. SQL setup creates its account ledger and records the owner credential binding automatically. Once SQL reports accepting, connect without certificates or password files:

psql -h localhost -p 5434 -U owner -d transit
mysql --protocol=TCP -h localhost -P 13306 -u owner -D transit --ssl-mode=DISABLED

Both listeners bind IPv4 loopback. trust accepts a client's claimed configured SQL login: any local process able to connect can use that login's privileges. It does not verify the operating-system user. Unknown or disabled SQL names reject. Native role eligibility, database access, Table grants and current credential checks apply to every session and protected operation.

bootstrap_owner: true explicitly binds a newly enrolled SQL account to the installation's original local_owner role. It uses the shared authorization operations and a saved request/receipt journal. It runs once per account; restart never rebinds a disabled credential or restores revoked privileges. Keep this declaration unchanged for that account. It cannot be enabled later on an account enrolled without it, or used with another role. A recreated role with the same name cannot replace the original owner. SQL workers never receive the installation's administrator credential.

The local profile selects database transit and schema public; it does not create a database matching the OS username. Account names contain 1–63 ASCII letters, digits, underscores or hyphens. Both listeners remain opt-in. Configuring a listener without an enabled account fails validation before child startup.

The core ready record includes sql.state and sql.status_file. SQL setup continues independently of core readiness. Status transitions, actual connection commands and failures are printed to stderr and saved in .transit/sql/status.json. Configured ports and accepting ports are distinct. The file is the supervisor's last observed snapshot and can remain after shutdown; the private /health probe checks admission readiness and /recovery checks worker readiness at sql.health_port (19108 by default).

Select TLS and password authentication​

Transport encryption and login authentication are independent settings:

SettingBehavior
sql.tls.mode: disabledNo TLS files are created or opened; PostgreSQL and MySQL trust logins use plaintext
sql.tls.mode: optionalOne listener accepts plaintext or TLS; TLS setup must succeed before either is exposed
sql.tls.mode: requiredDefault; plaintext connections receive a protocol error
Account authentication: trustNo password file or password exchange; loopback only
Account authentication: passwordDefault; an explicit password_file supplies the initial verifier

PostgreSQL password authentication uses SCRAM with or without TLS. MySQL's full caching_sha2_password exchange requires TLS. With optional TLS a plaintext MySQL password login rejects before requesting the password; a MySQL listener with disabled TLS and an enabled password account fails configuration validation. Trust works with any of the three TLS modes.

For passwords, create a private file outside Git containing exactly 1–128 printable ASCII bytes, without a trailing newline. For example, in Bash or Zsh:

umask 077
read -r -s sql_password
printf '%s' "$sql_password" > sql-owner.password
unset sql_password

Set sql.tls.mode: required and replace the owner's authentication: trust with authentication: password and password_file: ./sql-owner.password when first enrolling it. Keep bootstrap_owner: true to record the owner binding automatically. Existing credential files are never silently regenerated by configuration changes; the rotation workflow below replaces password verifiers. A trust credential has no password verifier, and cannot authenticate a password login merely because its configuration changed.

Enroll other roles explicitly​

For an account with bootstrap_owner: false (the default), setup publishes reviewable proposals under .transit/sql/accounts/<name>/. At awaiting_role, prepare/apply create-role.json; at awaiting_binding, prepare/apply bind-credential.json. Use the shared authorization client with the generated administrator profile. Prepare the typed action at its recorded policy revision, persist the signed record, then explicitly submit and observe each step until publication. The CLI does not provide administration commands, and these proposals cannot be executed as console SQL.

Inspect the preparation preview. If apply reports pending, repeat apply on the same saved file until it reports a published terminal outcome. A rejected unsent proposal with StaleAuthorizationRevision needs a refreshed proposal and review. Do not prepare another operation to recover a lost reply. The authorization guide explains exact recovery.

New roles start without data rights. Grant their database/schema access and Table privileges explicitly using the examples below. The generated authorization.yaml and catalog.yaml select a separate private administrator with root-role membership; protect their seed as full administrative access. SQL receives separate worker, ingress, coordinator, Table and private-reader credentials. Account initialization and normal admission use separate fenced activations. One initialized account can serve connections while others await authorization.

Connect and share a Table​

For the password configuration, use verified TLS (substitute your configured ports):

PGSSLMODE=verify-full PGSSLROOTCERT="$PWD/.transit/sql/tls/ca.pem" \
psql -X -h localhost -p 15432 -U owner -d transit -W

mysql --protocol=TCP -h localhost -P 13306 -u owner -D transit --password \
--ssl-mode=VERIFY_IDENTITY --ssl-ca="$PWD/.transit/sql/tls/ca.pem" \
--default-character-set=utf8mb4

Substitute the status file's CA path and ports when using another installation path or supplied trust. Both listeners bind 127.0.0.1. The certificate identifies localhost and 127.0.0.1. TLS policy is enforced before login.

Run one statement at a time. In psql:

CREATE TABLE shared_rows (id BIGINT PRIMARY KEY, label UTF8);
INSERT INTO shared_rows (id, label) VALUES (1, 'snow ☃');
SHOW DATABASES;
SHOW SCHEMAS;
SHOW TABLES;
SHOW sql.postgres.port;

In mysql, against the same catalog:

SELECT id, label FROM shared_rows;
UPDATE shared_rows SET label = NULL WHERE id = 1;
SELECT COUNT(*) FROM shared_rows WHERE label IS NULL;
DELETE FROM shared_rows WHERE id = 1;

The SQL gateway reference defines the supported autocommit SQL, scalar types, prepared statements and protocol limits. Transactions, general ORM compatibility and remote listener exposure are outside this local interface. Stop with Ctrl-C and resume with transit server; retained Tables, account identities and operation history remain available.

Separate writer and reader roles​

Add accounts named writer and reader, each with its own password file and a same-named role, to sql.accounts, then restart. For each new role the account status is awaiting_role. Prepare/apply its generated create-role.json using the workflow above, then its bind-credential.json. These are explicit policy changes; startup does not create SQL login roles or rebind revoked credentials.

After binding, review access.json and prepare it through AuthorizationSet. It grants the role the catalog-process transport rights, settings inspection, database CONNECT and schema USAGE. It grants no Table SELECT/INSERT/UPDATE/DELETE or schema CREATE privilege. Repeat bounded apply on the saved changeset until all intents finish.

Catalog name resolution requires CONNECT and schema USAGE even for a policy administrator. First review and explicitly apply the generated discovery grant:

Use the same shared authorization lifecycle to prepare and apply .transit/sql/catalog-access.json as a scoped changeset.

This grants CONNECT on the local database and USAGE on its public schema to the administrator's selected root role. It grants no Table data privileges. Repeat apply on that saved file if pending. Startup refreshes the proposal's policy basis but never applies it or restores revoked discovery rights.

Then put the desired Table grant in writer.sql:

GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE public.shared_rows TO writer;

Compile the statement through the Table bridge/service compile_authorization operation under the selected catalog binding. Check that the resolved plan matches the selected realm, principal and role. Convert it to its native action and use the authorization bridge prepare action with the recorded policy revision. Persist the resulting record and explicitly submit/observe its steps through the shared client.

Use the same workflow for GRANT SELECT ON TABLE public.shared_rows TO reader; and for REVOKE. Authorization SQL uses this dedicated public compiler and apply workflow. Executing a GRANT through a database connection is not supported. Existing and new sessions check current authority. Restart never restores these grants or a credential that an operator disabled. An enrolled account's role, database and schema bindings are immutable; use a new account name for a different binding, preserving the old account's recovery identity.

Trust and password rotation​

Without sql.tls file references, Transit generates an installation-local CA and server certificate through its pinned TLS library when TLS is optional or required. The CA is valid for ten years and the server certificate for one year. Their private checkpoint precedes PEM publication. Missing exported managed PEM files can be recreated from that checkpoint; missing retained trust state rejects rather than generating a new identity. There is no automatic renewal.

To supply or replace trust, configure all three references together:

sql:
tls:
certificate_file: ./private/server-chain.pem
key_file: ./private/server.key
ca_file: ./private/ca.pem

Merge this into the existing sql object. The leaf must be valid now, match the private key, chain to the supplied CA, permit TLS server use, and have SANs for both local names above. Invalid or expired trust prevents SQL admission while retained ledger recovery and core administration remain available. Replace files/configuration and restart. Distribute the replacement CA explicitly to clients when changing trust; the server does not modify a client trust store.

The initial password file creates a salted verifier once. Changing that input file does not rotate an enrolled account. Write a new private password file and use the existing operator control with a strictly increasing verifier revision:

transit-sql-gateway control .transit/sql/accounts/owner/control.json \
credential ./new-password .transit/sql/accounts/owner/verifier.json 2

The replacement is atomic. Sessions compare the complete credential revision before new requests. Password rotation does not alter data privileges or reinitialize the operation ledger. Keep passwords and signing keys out of connection URLs, shell arguments and shared logs.

Disable admission and inspect uncertain writes​

Set either listener to null, or omit it, and restart to close that protocol. Set an account's enabled to false or remove its declaration to prevent its next login after restart. These changes preserve the account's credentials, immutable catalog binding, ledger and accepted work. Completed removed accounts remain inspectable through their retained control profiles without a resident worker. Accounts with unsettled operations continue recovery with current worker rights.

With both listeners disabled, retained workers require no PEM files and open no SQL sockets. The private /recovery probe describes worker readiness; /health also requires configured SQL admission. Disabling a listener is not cancellation. A bounded shutdown stops accepting connections, drains tasks and joins workers; native generations fence replacements. Revoked worker authority must be restored by an explicit operator decision before recovery can progress.

SQL setup or worker failures leave core administration available. The supervisor stops admission after a failure and makes at most three setup/start attempts per server invocation. Inspect error_type in the SQL status, repair the reported configuration or authority, and restart to resume. Uncertain native transport outcomes retain their exact requests and retry observation without consuming this failure budget or allocating replacement work.

Each account's status names its control.json profile. If a connection loses a write response, retain any reported operation ID. If it did not report one, inspect the account's paginated inventory and correlate the original attempt:

transit-sql-gateway control .transit/sql/accounts/owner/control.json list
transit-sql-gateway control .transit/sql/accounts/owner/control.json status OPERATION_ID
transit-sql-gateway control .transit/sql/accounts/owner/control.json pending

list returns a cursor when more records exist. status distinguishes current operation state from a confirmed Table outcome. Use the documented reconcile/cancel controls with the original ID where appropriate. Their saved pending file preserves exact control identity; neither closing a socket nor an empty result justifies blindly resubmitting SQL. Current authorization still governs operator inspection.

Local configuration admits at most 32 simultaneous active/recovering accounts. Idle PostgreSQL/MySQL connections remain open by default (sql.idle_ms: 0). A positive value enables idle expiry; PostgreSQL reports 57P05, and MySQL reports 4031. An existing idle_ms: 30s entry still enables that timeout. Native HTTP sessions use the independent renewable sql.native.session_lease_ms lease. See configuration for bounds.

A busy SQL status with HttpCapacity means the supervisor could not complete its account observation because native request capacity was full. It does not restart the SQL process or establish current readiness. The next scheduled check retries the observation; sessions continue to enforce current authorization. Connection hints appear once for each listener process, including replacements.

Connection, authentication, statement/idle/drain deadlines and initial per-account operation/byte/pending quotas have explicit bounded settings. Initial ledger quotas are frozen at enrollment; changing their YAML defaults does not resize an existing ledger. Use its explicit capacity control. Retained historical account count is independent of resident memory. See the generated settings reference for defaults and bounds.