Implements deferred mission #1257 WP09 / closes #1301.
`SchemaInterface` gains `listTableNames(): list<string>`. `DBALSchema`
delegates to Doctrine's `AbstractSchemaManager::listTableNames()` which
is portable across every DBAL-supported driver (SQLite, MySQL,
PostgreSQL, and beyond).
`HealthChecker::findOrphanSubtables()` replaces the SQLite-only
`SELECT name FROM sqlite_master WHERE name LIKE ?` query with
`$schema->listTableNames()` + a `str_starts_with($name, $baseTable . '__')`
filter in PHP. The `\Throwable` swallow path that masked non-SQLite
errors is removed — operators on MySQL / PostgreSQL now get
ORPHAN_BUNDLE_SUBTABLE coverage that previously failed silently with
only an info-level log.
LIKE-pattern escaping (`%` and `_` quoting) is gone with the SQL,
replaced by exact-prefix string match — same correctness, no escaping
concerns. The `__` separator between base and bundle is reserved (per
bundle-scoped-storage.md §Naming) so prefix matching is unambiguous.
Tests:
- `DBALSchemaTest` gains 3 new tests for `listTableNames()` (empty,
multiple tables, contract).
- Existing `HealthCheckerBundleSubtableDriftTest::orphanSubtableIsReportedAsInformational`
still passes — orphan output is unchanged on SQLite.
Spec updates:
- `docs/specs/infrastructure.md` — Spec reviewed comment.
- `docs/specs/bundle-scoped-storage.md` §"Drift diagnostic" — portability
language updated; `sqlite_master` fast-path note removed.
- `docs/specs/operator-diagnostics.md` §"Bundle Subtable Drift" — orphan
detection algorithm description updated to match the new portable path.
Non-SQLite test matrix coverage (docker-compose env-gated MySQL /
PostgreSQL runs) is a CI extension tracked separately; the production
code path is dialect-portable as of this PR.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>