Public Contracts and Support Taxonomy¶
Created: 2026-05-21
Originating TODO: architecture-contract-map-and-support-taxonomy
Checked SHA: 893768130f3b3aad249549f897538a172a0f8230
This document classifies BenchBox surfaces by compatibility tier and names the source of truth for each one. It is intentionally narrower than a full architecture guide: if a future PR changes a public, beta-public, generated, deprecated, or experimental surface, that PR must update this map or state why the map is unchanged.
Contract Tiers¶
Tier |
Meaning |
Breaking-change rule |
|---|---|---|
|
User-facing surface that should remain compatible across beta patch/minor releases unless a documented migration exists. |
Needs migration note and compatibility registry update when behavior changes. |
|
User-facing surface exposed during beta. It is supported, but details can change before 1.0 with documented rationale. |
Needs same-PR docs/tests and deprecation path when practical. |
|
Implementation detail. External callers should not depend on it. |
Can change with focused tests; public docs must not promise it. |
|
Prototype or research surface. It may ship for convenience without product support. |
Can change or disappear; docs must label it experimental. |
|
Compatibility surface retained temporarily. |
Must have owner, migration path, and target review/removal window in |
|
Output derived from source metadata, schemas, fixtures, or build scripts. |
Source metadata is authoritative; hand edits are drift unless explicitly marked editorial. |
|
Contributor, planning, audit, or release-support surface that is not a user product API. |
May change with repo workflow docs; wheel/API stability does not apply. |
Public Surface Map¶
Surface |
Current tier |
Owner |
Compatibility promise |
Deprecation path |
Verification gate |
Source of truth |
|---|---|---|---|---|---|---|
CLI commands and documented options |
|
cli-runtime |
Documented commands and option meanings are supported for beta users; option breadth can change with docs/tests. |
Release notes plus docs update; backward-compatible aliases when practical. |
CLI unit tests, generated CLI reference checks, |
|
Top-level Python wrapper facades, for example |
|
benchmark-api |
Wrapper imports and facade methods covered by |
Registry row in |
|
|
|
|
core-runtime |
Public base for wrapper benchmarks and orchestration helpers; result helper compatibility is tracked. |
Compatibility registry row when kwargs, result helpers, or method contracts change. |
Runtime contract and wrapper tests. |
|
|
|
core-runtime |
Standard programmatic execution hook for CLI-adjacent tools and MCP; callers pass an adapter and run options. |
ADR or contract-map update before replacing it as the orchestration API. |
MCP benchmark tests plus runtime contract tests. |
|
|
|
benchmark-api |
Registry-backed runtime loader for CLI/core orchestration. It is not a public Python API and should not be imported by external callers. |
Promote only through a contract-map update and migration docs. |
Loader/registry parity tests and benchmark API contract tests. |
|
|
|
core-runtime |
Documented internal compatibility base retained for the remaining |
Remove only after those implementations migrate to |
Backward-compatibility registry review, benchmark loader/runtime tests, benchmark API contract tests. |
|
Adapter subclassing hooks and base mixins |
|
platform-runtime |
Adapter authors can depend on documented |
Adapter refactor map update, migration note, and representative adapter tests. |
|
|
|
|
platform-runtime |
Adapter instances are serial execution objects. One instance may be reused for multiple benchmark runs sequentially; |
A concurrency or service-mode promotion needs a contract-map update and a shared run-context design before claiming support. |
|
|
DataFrame adapter execution path |
|
dataframe-runtime |
Production DataFrame execution routes through |
Changes need DataFrame mixin tests, result-bundle parity coverage, and same-PR docs. |
DataFrame mixin tests plus exported SQL/DataFrame result parity. |
|
|
|
dataframe-runtime |
Deprecated internal compatibility runner retained for old tests and helper imports. It is not the production DataFrame lifecycle path. |
Backward-compatibility registry review after one beta cycle; migrate remaining behavior to |
Compatibility tests only. |
|
Platform registry metadata |
|
platform-runtime |
Registry metadata is the source for platform discovery, capabilities, dependency hints, and platform support status. |
Same-PR metadata/docs migration; aliases require compatibility note. |
Platform registry tests and docs drift checks. |
|
MCP tools |
|
mcp |
Tool schemas and documented parameters are supported as a smoke/control-plane automation surface, not a CLI-equivalent execution surface. MCP result bundles are schema-comparable to CLI bundles, but MCP must not import CLI command internals or imply CLI option parity. |
MCP reference update and contract tests; product-tier or option-parity changes need a decision note and, for CLI equivalence, a shared non-CLI run service below CLI and MCP. |
|
|
Visualization semantic chart IDs |
|
visualization |
Result-aware chart IDs accepted by CLI, MCP, templates, ASCII runtime dispatch, and Results Explorer must derive from the semantic registry. Raw textcharts primitive IDs are a separate dependency namespace. |
Same-PR registry, template, discovery, Explorer, and parity-fixture updates; deprecate IDs rather than silently removing them. |
Visualization registry tests, exporter tests, Explorer registry parity tests, and parity-fixture drift checks. |
|
|
|
visualization |
Compatibility data-first ASCII primitive renderer for callers that already have chart-specific data objects. It intentionally has narrower coverage than the result-aware semantic renderer and excludes |
Promote a missing semantic chart only when a data-first payload contract exists; otherwise keep the explicit result-aware-only error. |
|
|
Result JSON bundles |
|
results |
Schema-versioned result bundles are product data consumed by CLI, submission validation, hosted results, explorer, and SQL/DataFrame comparisons. SQL and DataFrame bundles must preserve the cross-mode invariants below. |
Schema policy and hosted-results contract update before changing accepted versions, field semantics, or cross-mode parity guarantees. |
Result schema policy, loader, normalizer, submission, explorer, and exported SQL/DataFrame parity tests. |
|
Explorer read model and generated browser inputs |
|
results-explorer |
Browser data stores are generated from accepted result bundles; generated outputs should be reproducible from source bundles and pipeline code. |
Read-model version bump or pipeline contract update. |
Explorer pipeline contract tests and browser release gates. |
|
Public submission validator behavior |
|
hosted-results |
PR-based public result submissions must receive deterministic validation errors and privacy/trust handling. |
Hosted-results contract update and validator tests. |
|
|
SQL compatibility rule catalog |
|
sql-compat |
Hybrid governance catalog: every source-detected adapter CREATE TABLE rewrite must be runtime-dispatched by |
sql_compat README and contract-map update before changing the governance guarantee. |
|
|
Generated compatibility docs |
|
sql-compat |
Generated docs must match registry/rule metadata; hand edits are drift unless the section says it is editorial. |
Regenerate from source metadata or update the generator. |
|
|
|
|
architecture |
Ships in the default wheel for developer convenience but is outside the supported beta product surface. |
Promote through a contract-map update and tests, or extract/remove through the experimental future-state plan. |
Package metadata review and explicit tests for promoted surfaces only. |
|
|
|
maintainers |
Contributor workflow and project governance aids; not user-facing API. |
Repo workflow docs or TODO updates. |
Script-specific tests where present. |
|
TODO, DONE, and ADR/future-state docs |
|
maintainers |
Planning and decision records guide implementation but do not themselves create runtime API. |
Move accepted decisions into user/developer docs when they become product contracts. |
TODO validation and review. |
|
Support Status Taxonomy¶
support_status is a product-support classification for platforms and
benchmarks. It is different from local dependency availability: a stable
platform can be unavailable on a developer machine because an optional SDK is not
installed.
Allowed values:
Status |
Meaning |
Packaging |
Docs |
Registry visibility |
MCP exposure |
CI coverage |
Breakage policy |
|---|---|---|---|---|---|---|---|
|
Supported product surface for normal users. |
Included or installable through documented extras. |
Full user docs and examples where relevant. |
Listed by default. |
Exposed when the MCP surface supports that capability. |
Fast/unit plus representative smoke or integration coverage. |
Fix promptly or document temporary known issue. |
|
Supported beta surface with known evolution risk. |
Included or installable through documented extras. |
Docs must label beta caveats. |
Listed by default with beta status. |
Exposed if behavior is covered by MCP docs/tests. |
Focused tests for core behavior. |
Can change with same-PR docs/tests and migration guidance. |
|
Prototype or research surface. |
May ship in default wheel or optional extra, but must be labeled. |
Experimental docs only; no support implication. |
Hidden or clearly labeled. |
Omitted unless the MCP tool explicitly labels it. |
Best-effort targeted tests. |
May change or be removed without compatibility promise. |
|
Contributor or source-checkout-only surface. |
Not promised in wheels. |
Developer/project docs only. |
Hidden from user discovery. |
Not exposed. |
Script or workflow checks only when useful. |
May change with repo workflow updates. |
|
Temporarily retained compatibility surface. |
Retained until target review/removal window. |
Migration path required. |
Listed with deprecation status or hidden after warning window. |
Exposed only if existing clients need it. |
Compatibility tests until removal. |
Removal follows registry target and release notes. |
|
Documented external concept or planned support with no runtime implementation. |
No package promise. |
Docs must say it is not executable support. |
Not listed as runnable. |
Not exposed. |
Link/static doc checks only. |
No runtime breakage claim. |
Platform and benchmark registry metadata now carry exactly one support_status
for every runtime entry. Benchmark support status is distinct from benchmark
surface visibility and from capability flags such as supports_dataframe. The
auditable per-benchmark rationale and promotion criteria are in
Benchmark Support Status Criteria, whose
per-benchmark status rows are drift-checked against the registry by
tests/unit/core/test_benchmark_api_contract.py.
Benchmark Visibility Policy¶
Three registry fields govern how a benchmark appears on public surfaces, and
they are independent. Tests in tests/unit/core/test_registry_surface_field.py,
tests/unit/cli/test_benchmark_manager_behavioral.py, and the MCP discovery /
resource / handler suites enforce this matrix for current and future statuses.
surface is the only discovery gate. It decides whether a benchmark is
listed or hidden, regardless of support_status:
|
CLI interactive listing |
MCP |
MCP resources |
Runnable by explicit ID |
MCP |
|---|---|---|---|---|---|
|
Listed and labeled with its status |
Exposed, including |
Exposed, including |
Yes |
Returns metadata including |
|
Hidden |
Hidden ( |
Hidden |
Yes (explicit-ID exception) |
Returns |
support_status controls the product-support label shown next to a public
benchmark; it never hides one. For any public benchmark, regardless of status:
|
CLI label |
Listed by default |
DataFrame routing |
|---|---|---|---|
|
|
Yes |
Per |
|
|
Yes |
Per |
|
|
Yes |
Per |
|
|
Yes, until the removal window |
Per |
|
|
Yes |
Per |
|
|
Only if also |
Per |
supports_dataframe is a capability flag: it controls DataFrame routing and
is never inferred from support_status. A beta benchmark may be
DataFrame-capable; an experimental one may not.
Explicit-ID exception. Internal benchmarks (for example joinorder_synthetic)
stay runnable by exact ID so contributor workflows keep working, but they must
not leak product-support claims onto discovery surfaces. get_query_details
therefore returns only neutral identifiers (display_name, category) for an
internal benchmark and omits support_status; get_benchmark_info,
list_available, and the benchmark resources hide them entirely.
To hide a public benchmark, change its surface to internal — do not repurpose
support_status. Demotion to deprecated keeps it listed (with a label) until a
separate surface/removal decision.
Count and Drift Policy¶
Benchmark API snapshot: 23 registry entries; 23 loader-resolved core families; 22 public discovery entries; 21 top-level Python benchmark facades; 15 lazy facades; 6 eager facades; 2 core-only benchmark IDs. Benchmark support status: 5 stable, 12 beta, 5 experimental, 1 repo-only, 0 deprecated, 0 document-only.
Evidence snapshot updated by benchmark-support-status-and-discovery-policy:
Source |
Current evidence |
Contract implication |
|---|---|---|
|
23 benchmark metadata entries and 23 loader-resolved IDs; support status counts are stable=5, beta=12, experimental=5, repo_only=1, deprecated=0, document_only=0. |
Benchmark count and support claims must derive from registry metadata or avoid exact counts. |
|
50 platform metadata entries: 45 SQL-capable, 19 DataFrame-capable, 14 dual-mode. |
README and platform docs must not carry unqualified hand-maintained platform counts. |
|
Current result schema version: |
Result schema version claims must update with the named consumer policy or defer to this policy module. |
|
Landing-page bullets claimed 22 benchmarks, 42 SQL platforms, and 9 DataFrame platforms. |
Exact counts were stale relative to registry metadata; README now links to this policy instead of being authoritative. |
Authoritative count statements should come from the relevant registry metadata. Editorial lists may remain in narrative docs, but they must not claim to be exhaustive unless a generated or tested check keeps them synchronized.
Benchmark Claim Classes¶
Public benchmark breadth claims fall into four classes:
Claim class |
Rule |
Example |
|---|---|---|
Registry-derived |
Generated from or checked against |
README |
Checked exact claim |
A hand-written exact count that a drift gate pins to the registry. Allowed on durable public surfaces. |
Landing “22 Benchmarks”; architecture “currently 22 benchmarks”. |
Editorial example |
Narrative that names representative benchmarks without an exhaustive total. Preferred when the exact number does not matter. |
“TPC-H, TPC-DS, ClickBench, and more”. |
Generated output |
Produced by a named generator; hand edits are drift. |
Generated compatibility docs. |
Rules:
An exact public benchmark count must be registry-derived or covered by a drift gate; otherwise reword it as an editorial example with no total.
Integrity-spec coverage claims (
N of M benchmarks) are checked exact claims:Nis the spec count andMis the public benchmark count.Generated outputs must state their generator; do not hand-edit them.
Internal
TODO/DONE/ADR history is not public contract — historical count evidence there is never treated as a stale public claim.
The drift gate for the checked exact claims is
tests/unit/core/test_benchmark_api_contract.py::test_public_benchmark_count_claims_are_registry_derived,
which derives the expected counts from the registry and names any stale source
file. The currently gated durable surfaces are landing/index.html,
docs/design/architecture.md, docs/development/result-integrity-validation.md,
docs/reference/cli/utilities.md, and docs/usage/faq.md; the README marker is
gated separately by the platform-registry marker tests.
Drift Check Ownership¶
Each public claim class has one owner and one preferred verification gate:
Claim class |
Owner |
Source of truth |
Verification gate |
|---|---|---|---|
Platform counts, platform support status, and optional import health |
platform-runtime |
|
Platform registry tests and generated platform docs checks. |
Benchmark counts, benchmark wrapper/loader/discovery reachability, and benchmark support status |
benchmark-api |
|
|
Result schema versions and consumer acceptance policy |
results |
|
Result schema policy, loader, submission, explorer, and hosted-results contract tests. |
MCP run parameters and product-surface limits |
mcp |
|
|
SQL compatibility DDL rewrite governance |
sql-compat |
|
|
The contract map is the routing layer for these gates, not the sole source of every generated table. A PR that changes a claim class should update the owning source and its gate first, then update this map only when the public contract or ownership boundary changes.
Visualization Chart Contract¶
benchbox/core/visualization/chart_types.py is the semantic result-aware chart
registry for CLI, MCP, templates, ASCII runtime dispatch, and Results Explorer.
Adding a semantic chart ID starts there, then updates the result-aware runtime
handler in benchbox/core/visualization/ascii_runtime.py, any templates that
should include it, results-explorer/src/lib/chartRegistry.ts, and generated
parity fixtures from make parity-fixtures. Registry tests must report missing
and extra IDs by name, not only by count.
benchbox.core.visualization.render_ascii_chart is a compatibility data-first
renderer for callers that already hold primitive chart payloads. It intentionally
does not cover power_bar, because power_bar needs normalized BenchBox result
context and is rendered through render_ascii_chart_from_results(). Raw
textcharts primitive IDs such as bar or heatmap are dependency internals, not
BenchBox semantic chart IDs. Textcharts dependency updates should be validated by
the visualization registry tests plus the runtime drift snippet that compares
ALL_CHART_TYPES, _CHART_TYPE_DISPATCH, and the primitive exporter registry.
BenchBox MCP exposes only the result-aware suggest_charts and generate_chart
tools. It does not register or proxy the external textcharts-mcp server.
When a user configures both servers, textcharts_* tools are a separate raw
primitive rendering namespace; BenchBox MCP chart_type values remain semantic
IDs from the registry above, and template names come from
benchbox.core.visualization.templates.
SQL Compatibility Governance Decision¶
Decision from sql-compat-governance-ddl-hardening: sql_compat is a hybrid
governance catalog plus optional runtime dispatcher. BaseDdlOptimizer is the
preferred dispatch path for ordered statement-to-statement DDL transforms, but
adapters may keep local CREATE TABLE rewrite paths when the rewrite depends on
adapter state, SDK-specific create/load loops, or platform deployment settings.
governance_only=True rules are allowed to represent real runtime behavior.
They are not dispatch targets; they are the auditable source of intent that the
drift checker requires before CI can call DDL governance clean. compat_lint CLEAN
means the source scanner found no unregistered or uninspectable adapter
CREATE TABLE rewrite behavior. It does not mean every DDL rewrite flows through
BaseDdlOptimizer.
DataFrame Runner Lifecycle Decision¶
Checked for dataframe-runner-lifecycle-and-bundle-parity at
8604d0a413f6c3d4bd7db211bb472c2370a932c3.
Production DataFrame execution is run_benchmark_lifecycle() ->
adapter.run_benchmark() -> BenchmarkExecutionMixin.run_benchmark().
benchbox.core.runner.dataframe_runner.run_dataframe_benchmark() is a
deprecated internal compatibility runner. The module still provides internal
mode/query helpers used by CLI/dry-run code, but new lifecycle behavior belongs
in benchbox/platforms/dataframe/benchmark_mixin.py.
Production behavior tests are the DataFrame mixin and adapter lifecycle tests,
plus exported result parity in tests/unit/core/results/test_result_parity.py.
tests/unit/core/runner/test_dataframe_runner.py and
tests/unit/core/runner/test_dataframe_runner_lifecycle.py are compatibility
tests for the deprecated standalone runner until the beta review window closes.
Evidence rechecked:
Evidence |
Finding |
|---|---|
|
DataFrame adapter branches call |
|
The mixin owns production DataFrame result construction, phase status, query execution, skip summaries, and plan-capture counters. |
|
Standalone runner duplicates older result construction and is not called by production lifecycle execution. |
|
DataFrame architecture now points at the adapter mixin path instead of the deprecated standalone runner. |
|
Exported JSON bundle parity is enforced after writing through |
SQL/DataFrame Result Bundle Invariants¶
SQL and DataFrame runs may use different engines and execution models, but their exported result bundles must remain schema-comparable.
Fields that must match for the same benchmark, scale, query subset, and run configuration:
Result schema version.
Benchmark identity:
benchmark.id,benchmark.name,benchmark.scale_factor, and test type.Reproducibility config, excluding execution-mode-specific values: compression, seed, phases, and query subset.
Presence of the standard phase keys.
Query IDs in exported
queries.Row counts when both modes report them.
Validation state and absence/presence of exported error records.
Execution metadata key shape, except conditional SQL translation metadata under
execution.translation.Timing field names and units: run-level milliseconds and query-level
ms.
Allowed differences:
execution.modeandconfig.modeare expected to besqlvsdataframe.Platform name, version, client version, family, and driver metadata can differ.
Phase status may differ when the DataFrame benchmark owns loading; for example, SQL can report
data_loading=SUCCESSorCOMPLETEDwhile DataFrame reportsSKIPPED.Optional
tablesblocks can be absent when a DataFrame benchmark manages or skips generic loading.execution.translationis SQL-only additive metadata and may appear when SQL dialect translation was attempted. DataFrame bundles are not expected to emit matching translation metadata.Exact timing values must not be compared across modes.
Result Model Extension Policy¶
BenchmarkResults is an internal producer model; schema-v2 result JSON is the
compatibility boundary. Model cleanup must not move or remove exported keys
unless the same PR includes loader, validator, explorer, MCP, and public
submission coverage for the migration.
Platform-specific structured data should use the narrowest existing exported location first:
Platform identity, deployment, cloud, compute, storage, and raw platform details serialize under the
platformblock.Lifecycle stage summaries serialize under
phases.<stage>.Cross-engine or cross-platform comparisons serialize under
comparisons.Invocation and validation metadata serialize under
executionandsummary.New top-level keys require a public-contract update, schema-validator update, loader behavior, and explorer input policy review in the same PR.
A future platform_extensions block is acceptable only for additive structured
data that has no existing canonical block. Existing fields may move there only
with an alias period: old exported keys keep loading and writing until all
documented consumers are migrated. Unknown nested keys inside documented blocks
may be ignored by read-model consumers, but unknown top-level keys remain
schema-governed and must not bypass schema-v2 validation.
Current extension inventory:
Internal field/source |
Current exported key |
Loader behavior |
Downstream consumers |
Recommended action |
|---|---|---|---|---|
|
|
Reconstructs |
Result loader/exporter; public schema validation now accepts the producer key; explorer ignores the comparison block. |
Keep exported key; do not move before a comparison read-model design exists. |
|
|
Reconstructs summary-level |
Result loader/exporter; explorer reads phase durations from |
Keep as canonical lifecycle-stage location. |
|
|
Not reconstructed (setup sub-phases serialize as flat summaries); omitted entirely when the opt-in statistics phase did not run. |
Result exporter; explorer reads phase durations from |
Keep as canonical lifecycle-stage location; legacy runs without the phase (or without the reset/per-table controls) stay byte-identical. |
Standalone pg_mooncake migration script |
Top-level |
Not reconstructed by result loader. |
Script-local reports only; not a canonical schema-v2 result extension. |
Leave separate or convert in a dedicated PR to |
|
|
Reconstructs platform info and raw metadata blocks. |
Loader/exporter, anonymizer, explorer platform/version extraction. |
Keep; move internally only if exported keys remain stable. |
|
|
Reconstructs the normalized platform facets. |
Loader/exporter, environment compatibility tests, explorer environment facets. |
Keep as canonical platform facets. |
|
|
Loader preserves selected execution metadata, not the full internal context. |
CLI/MCP/exporter/explorer metadata consumers. |
Keep selected exported fields; expand only by explicit schema policy. |
|
|
Reconstructs from |
Filename builder, loader, explorer IDs, result parity tests. |
Keep internal compatibility field until builder and loader identity handling are redesigned. |
Translation and Validation Mode Policy¶
Decision from fail-open-policy-and-translation-strictness: SQL translation is
mode-aware. Interactive/local runs may fail open by returning source SQL with a
warning, but CI, publishing, compatibility governance, and any caller making a
public correctness claim must use strict translation or treat fallback metadata
as uncertainty.
Mode |
Translation failure policy |
Result-bundle policy |
Consumer expectation |
|---|---|---|---|
Interactive/local default |
Fail open and warn. |
|
Users can keep experimenting, but the bundle is not a clean correctness claim. |
CI and compatibility governance |
Fail closed with CLI |
Strict failures raise before a result is published as successful. |
Gates should fail rather than accept untranslated SQL. |
Publishing and explorer ingestion |
Reject non-clean validation statuses or translation fallback metadata. |
|
Hosted/public views must not rank or present unchecked/fallback runs as validated results. |
Translation metadata is exported under execution.translation without a schema
version bump because it is an additive field inside the existing execution
block. It records strict mode, aggregate status, attempt counts, translators,
source/target dialects, warning/error categories, and compact grouped outcomes.
summary.validation="passed" is reserved for runs with evidence that validation
actually ran. When no validation record or validation details exist, lifecycle
finalization labels the result not_run instead of passed.
Post-load validation that is not applicable because an adapter does not expose
connection validation hooks records no validation stage; exceptions after a
validation attempt remain failed validation stages.
Evidence Snapshot¶
This TODO revalidated the contract map against the following files before editing:
Evidence |
Finding |
|---|---|
|
Beta disclaimer exists; |
|
Compatibility registry tracks shims; wrapper cleanup notes preserve top-level wrappers and keep |
|
Wrapper facades are tested public behavior, not accidental reachability. |
|
Completed decision moved MCP away from CLI internals and onto public benchmark/adapter APIs. |
|
Future-state proposals already classify MCP API formalization and experimental isolation as active architecture decisions. |
|
|
|
Platform registry declares itself the metadata and adapter-registration source of truth. |
|
Benchmark registry declares itself the shared benchmark metadata source for CLI and MCP. |
Benchmark API Evidence Snapshot¶
Checked SHA: 1d454632ba73911bc4ff0cf0a3fb8ec22227a7a8
Evidence |
Finding |
|---|---|
|
Public |
|
Deprecated internal compatibility base remains distinct; only |
|
Wrapper methods are asserted behavior and should not be treated as accidental duplicate reachability. |
|
Top-level package exposes 21 benchmark facades: 6 eager imports and 15 lazy |
|
Loader is registry-backed and internal; it resolves 23 core benchmark families from |
|
Registry has 23 benchmark metadata entries; public discovery hides only |
|
Compatibility row now records the legacy core base as a retained internal base with a migration target rather than a public extension path. |