Date: 2026-03-04
Branch: source-replacement
Commit: 7795a09d5f1 — "Skip non-field refs in upgrade-field-ref-to-name"
| Metric | Value |
|---|---|
| Total cards audited | 25,412 |
| Compilable before upgrade | 24,754 (97.4%) |
| Compilable after upgrade | 24,754 (97.4%) |
| Pre-existing broken cards | 658 (2.6%) |
| Regressions | 0 |
| Fixes | 0 |
| Upgrade errors | 600 (2.4% of all cards, 2.42% of compilable cards) |
| Queries changed by upgrade | 11,947 (47.0%) |
| Queries unchanged | 13,465 (53.0%) |
The critical safety invariant holds: zero regressions. No card that was compilable became non-compilable after upgrade. The 600 upgrade errors are non-fatal — they throw during the upgrade process but leave the query intact and still compilable (509 of 519 in the largest bucket remained compilable).
The upgrade audit (dev.nocommit.upgrade-audit) runs field-refs/upgrade! on every card in the app-db. For each card it:
- Records whether the query compiles before upgrade
- Runs
upgrade!(converts ID-based field refs → name-based) - Records whether the query compiles after upgrade
- Tracks:
query-changed?,regressed?,upgrade-error
The upgrade is Phase 1 of source replacement — normalizing field references so they survive source swaps. It converts [:field {} 42] → [:field {:base-type :type/Text} "COLUMN_NAME"].
| # | Bucket | Count | % of Errors | Compilable Before | Compilable After | Changed Query | Root Cause |
|---|---|---|---|---|---|---|---|
| 1 | Aggregation ref in viz settings | 519 | 86.5% | 509 | 509 | 486 | Bug in upgrade code |
| 2 | Legacy MBQL fails pMBQL conversion | 70 | 11.7% | 0 | 0 | 0 | Pre-existing data quality |
| 3 | Nil table metadata during field resolution | 9 | 1.5% | 0 | 0 | 9 | Pre-existing data quality |
| 4 | Expression ref dropped by upgrade | 1 | 0.2% | 1 | 1 | 1 | Bug in upgrade code |
| 5 | FK target field ID is empty string | 1 | 0.2% | 0 | 0 | 1 | Pre-existing data quality |
| Total | 600 | 510 | 510 | 497 |
Error: Error converting :aggregation reference: no aggregation at index 0
Example: Card 4488 — upgrade-field-ref-to-name in field_refs.clj:45 tries to convert [:aggregation 0] (a legacy aggregation reference) found in visualization settings like pivot_table.column_split. It calls lib.convert/->pMBQL which expects aggregation context to resolve index 0 → UUID, but no aggregation list is provided.
Stacktrace path:
field_refs/upgrade-field-ref-to-name (field_refs.clj:45)
→ lib.convert/->pMBQL (convert.cljc:406)
→ fails: no aggregation at index 0
Impact: Non-fatal. 509 of 519 cards were compilable before AND after. The query itself was upgraded successfully (486 changed); only the viz settings upgrade failed. The error is caught and reported but doesn't prevent the query upgrade from proceeding.
Root cause: upgrade-field-ref-to-name doesn't guard against non-:field refs in viz settings locations. Viz settings like pivot_table.column_split.rows can contain :aggregation refs (e.g., ["ref", ["aggregation", 0]]) and :expression refs alongside :field refs.
Fix: The commit 7795a09d5f1 was intended to fix this by adding (when (= :field (first field-ref)) ...) guard. However, this audit was run at that commit and the error persists for 519 cards. This suggests either:
- The guard was added but the
->pMBQLconversion happens before the guard check (the legacy format["aggregation", 0]needs conversion before the:fieldcheck can work) - The guard is in place but
->pMBQLis called first and throws before reaching the guard
This is the P0 fix target — resolving it eliminates 86.5% of all errors.
Error: Query with a :type or :lib/type key, got: {}
Example: Card 1424 — stored with legacy MBQL 4 format: {"type": "query", "database": 1, "query": {...}}. The normalize-query function in transforms.clj tries to convert legacy → pMBQL via lib.query/query-from-legacy-query, which fails because the converted query has invalid references (e.g., join-alias "Product" but no join defined).
Impact: None. All 70 cards are not compilable before the upgrade and remain not compilable after. These are pre-existing broken cards. Zero queries changed.
Root cause: Pre-existing data quality. These are very old cards with legacy MBQL that references joins or features that didn't exist in the format they're stored in. They can't even be loaded into pMBQL format. The upgrade correctly skips them (no query change).
No fix needed in upgrade code — these cards were already broken.
Error: Invalid output: ["Valid Table metadata, got: nil"]
Cards: 6594, 3027, 3456, 3673, 6593, 4455, 3322, 3413, 3414
Example: Card 6594 — during upgrade-field-ref → resolve-field-ref → returned-columns, the metadata provider calls lib.metadata/table for a source table and gets nil. This happens when the card references a table that no longer exists in the metadata (deleted, or from a disconnected database).
Stacktrace path:
source_swap/upgrade-field-ref (source_swap.clj:139)
→ field.resolution/resolve-field-ref (resolution.cljc:688)
→ join/returned-columns → stage/visible-columns → lib.metadata/table
→ returns nil → Malli validation fails
Impact: All 9 cards are not compilable before the upgrade. They reference tables that don't exist. Despite the error, the query was changed in all 9 cards — partial upgrade succeeded before hitting the nil table.
Root cause: Pre-existing data quality. The cards reference deleted/inaccessible tables. The resolve-field-ref path doesn't gracefully handle nil table metadata.
Suggested fix: Add nil guard in resolve-field-ref — if table metadata is nil, skip that field ref's upgrade rather than throwing.
Error: Invalid :expression reference: no expression named "Original Time Copy"
Card: 14975
Details: This card has 4 expressions: "Big Endian Time", "Tax Rate", "Date Only Time", "Original Time Copy". After upgrade, only 2 expressions remain in the :expressions clause — "Big Endian Time" and "Tax Rate". "Date Only Time" and "Original Time Copy" were dropped, but :fields still references them as [:expression {} "Original Time Copy"].
The dropped expressions were likely deduplicated incorrectly by upgrade-field-refs-in-stage with {:distinct? true} for :expressions. Commit d1e416f5e6a changed this to {:distinct? false}, but this audit ran after that commit and the error persists. This card may have expressions that are structurally identical after stripping namespaced keys, triggering the remaining dedup logic.
Impact: The card was compilable before (true) and after (true) — so despite the error, the query survived. The stage validation catches the dangling expression reference.
Root cause: Bug in upgrade code — expression deduplication. The fix in d1e416f5e6a may not fully cover this case.
Error: Invalid output: {:fk-target-field-id ["should be a positive int, got: \"\""]}
Card: 7169
Details: Card 7169 sources from card 7168, which has result_metadata containing :fk-target-field-id "" (empty string instead of a positive integer or nil). When ->card-metadata-column processes this metadata, Malli validation rejects the empty string.
Impact: Card was not compilable before, remains not compilable after.
Root cause: Pre-existing data quality — bad result_metadata in source card 7168. The empty string FK target was written by old code.
No fix needed in upgrade code.
| Category | Buckets | Cards | Action |
|---|---|---|---|
| Bug in upgrade code | 1, 4 | 520 | Fix required |
| Pre-existing data quality | 2, 3, 5 | 80 | No action (or defensive guard) |
| Compilable Before | Not Compilable Before | |
|---|---|---|
| Upgrade error | 510 (Bucket 1 + 4) | 90 (Buckets 2, 3, 5) |
| No error | 24,244 | 568 |
The 510 compilable cards with upgrade errors are the ones that matter — they represent real cards whose viz settings upgrade fails. The 90 non-compilable cards with errors were already broken.
11,947 of 25,412 cards had their query modified by the upgrade. This means nearly half of all cards had at least one ID-based field ref that was converted to name-based. This is expected — cards created before v56 predominantly use ID-based refs.
The 13,465 unchanged cards are likely:
- Cards already using name-based refs (created in v56+)
- Native SQL cards (no MBQL field refs to upgrade)
- Cards with no field refs (pure aggregations, etc.)
The upgrade-field-ref-to-name function at field_refs.clj:43-53 needs to skip non-:field refs before attempting ->pMBQL conversion. The current guard added in 7795a09d5f1 may be checking after conversion rather than before.
The fix should:
- Check the raw legacy ref format (e.g.,
["aggregation", 0]) for non-field types before calling->pMBQL - Return the ref unchanged for
:aggregationand:expressionrefs
Fixing this one issue eliminates 86.5% of all upgrade errors, bringing the error rate from 2.4% to 0.3%.
Add a nil check in the field resolution path so that cards referencing deleted tables fail gracefully rather than throwing.
Check why "Date Only Time" and "Original Time Copy" expressions are being dropped despite {:distinct? false} for expressions.
The swap-error-buckets.md and findings/ANALYSIS_7795a09d5f1.md in the source-replacement repo document swap operation findings (Phase 1 + Phase 2 together, tested on 99 cards). This audit is upgrade-only (Phase 1) tested on all 25,412 cards.
| Source | Scope | Cards | Phase | Error Rate |
|---|---|---|---|---|
This audit (upgrade_findings/) |
Full instance | 25,412 | Upgrade only | 2.4% |
findings/ANALYSIS_7795a09d5f1.md |
Sample | 99 | Upgrade + Swap | 23.2% errored |
swap-error-buckets.md |
Curated errors | ~30 | Upgrade + Swap | N/A (error catalog) |
The upgrade phase in isolation is much more reliable than upgrade+swap together. The swap phase introduces additional failure modes (join alias resolution, permission checks, source compatibility assertions) documented in the swap findings.
Bucket 1 (519 cards): All cards listed in summary.edn :entities-with-upgrade-errors except those in buckets 2-5.
Bucket 2 (70 cards): 1424, 1394, 1426, 1398, 1410, 1414, 1412, 1392, 1404, 1406, 1434, 1438, 1400, 1374, 1364, 1336, 1418, 1416, 1350, 1396, 1436, 1340, 1376, 1342, 1372, 1358, 1386, 1380, 1338, 1344, 1334, 1366, 1368, 1378, 1370, 1348, 1352, 1382, 1384, 1354, 1388, 1362, 1356, 1360, 1346, 1390, 1408, 1402, 1432, 1420, 1430, 1428, 1422, 1238, 902, 705, 702, 646, 426, 418, 347, 1630, 1675, 1685, 1872, 1925, 2086, 2180, 2283, 2556
Bucket 3 (9 cards): 6594, 3027, 3456, 3673, 6593, 4455, 3322, 3413, 3414
Bucket 4 (1 card): 14975
Bucket 5 (1 card): 7169