Adds tests/connection_budget.py, a helper that records which databases a
block of code opens, reads, writes or ATTACHes, and tests/test_connection_budget.py
which asserts budgets for ~60 endpoints, startup, sweeps and telemetry with
50 attached databases.
Fixes for every all-database loop it found:
- Index page: tables, views, hidden tables, columns, primary keys, FTS
tables and foreign-key relationship counts come from the _internal
catalog (a few queries for any number of databases). Row counts are only
run when at most 20 databases need counting (COUNT_MAX_DATABASES);
above that the page shows table counts plus counts already known.
- Derived-table permissions: allowed_resources() no longer queries every
database per page; dependency maps are computed by the SchemaWatcher from
the rows it scans, cached per database, and read back from the catalog
for databases restored from a persisted catalog.
- catalog_tables gains a type column (PRAGMA table_list: table, virtual,
shadow) so hidden shadow tables can be identified without a connection.
- PaginatedResources.all() fetches pages of 1000 after the first.
- /-/api and /-/debug/autocomplete use the catalog; /-/schema does one
permission query and skips databases with nothing visible.
- /-/versions computes SQLite details once per instance, without crossdb
ATTACHes; detect_json1() caches its answer.
- /-/databases only stats/hashes databases the actor can see.
- Immutable table counts are computed when first needed, not at startup.
- check_databases() (serve, --get) reuses the catalog scan instead of
opening every database a second time, and reports "file is not a
database" as a usage error.
- SchemaWatcher: failed scans are retried when the file changes, not on
every sweep; scan connections skip crossdb ATTACH; catalog_generation
counter for caches.
- The SQL write UI no longer forces a stat sweep of every database.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwkLo2rJWidpkDVT3HkvNv
Per-database schema_watch modes (owned / external / immutable):
- write path reads PRAGMA schema_version after every write task and
rebuilds that one database's catalog before the write returns
- external files are polled with a stat() fingerprint prefilter
(racily-clean fingerprints re-verified), PRAGMA schema_version only on
change, rebuild only when the version moved or the inode changed
- immutable databases are scanned once
- remove_database deletes its catalog rows explicitly; startup deletes
rows for databases that are not attached (no per-refresh full scans)
- catalog reads use one short-lived connection per database inside one
read transaction; shared-cache memory databases go through their
write thread
- replaced/deleted files invalidate cached connections (generation)
- fingerprints persisted in catalog_databases.fingerprint (migration
0002) so restarts skip unchanged files
- new setting schema_watch_interval_ms (default 1000, 0 = no polling)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwkLo2rJWidpkDVT3HkvNv