Skip to content

Background sweeps project phantom injected system columns from federated remote tables — absorbed today by the SqlDriver unknown-column retry ladder #8414

Description

@os-zhuang

Found during #7737's end-to-end re-verification (measured on a real os dev boot of examples/app-showcase, origin/main @ b432ae472, SQL captured at SqlDriver.findRows with temporary instrumentation). Filed unassigned, observation-class: no wrong user-visible answer was measured on any current dialect — the cost today is wasted work and log noise, plus two latent halves documented below.

The measurement

Two platform background sweeps interrogate federated (ADR-0015 external) objects about columns the platform injected into the registered schema but never provisioned on the remote table — the same phantom-anchor family #7835 closed for plugin-security's predicate injectors, but these are PROJECTION consumers, which #7835's scope did not cover.

  1. Pinyin search companion backfill (backfillSearchCompanion, packages/plugins/plugin-pinyin-search/src/companion-projection.ts), at every boot, stack-confirmed via kernel hook dispatch:
select `id`, `name`, `__search` from `customers` order by `id` asc limit 1000
  1. Lifecycle dangling-reference audit (auditDanglingReferences, packages/objectql/src/integrity/dangling-reference-audit.ts, via LifecycleService.sweep — first pass 60s after boot), stack-confirmed:
select `id`, `organization_id`, `created_by`, `updated_by`, `owner_id`, `owning_business_unit_id` from `customers` limit 500

Both against the remote table customers of the showcase_external datasource, which physically has only id, name, email, region, lifetime_value (plus orders, same shape of statement). Neither __search nor any of the five anchors exists remotely.

Why nothing visibly breaks today

Directly verified with better-sqlite3 13.0.2: a backtick-quoted unknown column in a SELECT projection raises no such column — it does NOT take SQLite's double-quote literal fallback. The failing statement is then caught by SqlDriver.findRows's unknown-column retry ladder (the isUnknownColumn branch, packages/drivers/driver-sql/src/sql-driver.ts), which retries select *: rows come back without the phantom columns, so

  • the dangling-reference audit sees undefined anchors and reports no false dangling ids;
  • the pinyin backfill sees no blob and (on this fixture, ASCII data) no CJK source, and skips.

The retry predicate also matches Postgres/MySQL's column ... does not exist, so the absorption is cross-dialect.

The costs and the latent halves

  • Per federated object, per pass: one doomed statement plus a full-row select * retry (up to 500/1000 rows fetched to audit columns that cannot exist). The lifecycle sweep repeats on its interval, the pinyin backfill on every boot.
  • Each pass also emits the 分页读取在没有 orderBy 时同样不确定:tie-breaker 只覆盖了「排了序的翻页」 #4363 "Paged read ... is NOT deterministic" stderr warning per federated object (the retry drops nothing here — the walk itself pages a table the driver did not create).
  • Latent (a): a federated remote holding CJK text would make the pinyin backfill compute companion values and attempt engine.update on a read-only federated object every boot — refused by the external write gate, one warn per row.
  • Latent (b): both sweeps are protected only by the retry ladder's error-message sniffing; SqlDriver.count has no such ladder, so any future sweep that counts by a phantom column fails hard.

Suggested direction (not prescriptive)

Sweep enumerators should skip phantom columns at the source — the provenance derivations #7835 used for Layer 0 (platformProvisionsStorage / the injectedSystemColumnDefs family in @objectstack/spec/data) answer exactly this question — rather than any consumer-side tolerance of the failing read (Prime Directive #12).

Related


Generated by Claude Code

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions