v13.14.4
Forty-eight commits since v13.14.3. Three load-bearing themes plus
infrastructure cleanup.
1. Phase BX close — editor polish + role-reframe row. The L2
editor's open cells from the 2026-05 cold-read land:
- BX.3 Rail list table view (
?view=tablesession toggle, grouped
by source role with single-leg rails grouping by theirleg_role
too; toggle anchors stripembed=1so a click from a home-section
embed lands on the chromed standalone page, not the bare fragment). - BX.4 Read-card sections mirror the edit form's grouping (Identity
/ Classification / Topology / etc.) instead of one flat<dl>. - BX.9 Theme editor reorder — essentials (accent / secondary / logo)
at top with a live-preview card and 300ms-debounced auto-save on
blur; everything else collapses into<details>Advanced</details>,
default-closed at DEFAULT_PRESET, default-open when an L2 has
non-default values. - BX.14 Domain-flavor banking phrasing for every L2 validator error
[?]glossary triggers in the rejected-save banner, sourced from
8 new GLOSSARY entries keyed by error code family.
- BX.15
[?]chip tooltips next to Coverage + Trainer toggles on
the diagram sidebar. - BX.16 Inline chain shape-preview below the children chip-list on
chain edit pages (reuses the BX.8 mini-diagram wasm-graphviz
renderer; fires onloadso the initial paint pre-populates from
the form's current parent value). - BX.17 Polish cluster — duration picker quick-select chips
(Instant / 1h / EOD / Next-day + free-text), reference-panel
default-open behavior on empty list pages only, completion-DSL
autocomplete.
The persona audit confirmed BXa absorbed all live references; two
dead-code refs cleaned up (_VALID_KINDS retightened to
frozenset[EntityKind]; unreachable ("theme", "persona") ternary
removed).
2. Untyped-enum sweep — typed primitives for the five most-touched
enum surfaces. Per a fresh audit
(docs/audits/untyped_enum_audit_2026_06_11.md), L2 primitives carried
five enums as bare str despite stable schema CHECKs (or no CHECK at
all). Sweep landed in staged commits for bisect-ability:
AmountDirection = Literal["Debit", "Credit"](closed); ~12 spine
writers +etl.write_transactionannotated.POSTED_STATUS: Final = "Posted"(half-open per operator lock —
"Posted" is canonical materialized state; other values stay
integrator-extensible; no CHECK added).Scopethreaded through 4 spine generators that were dropping to
barestr(chain_completion / failed_transaction / supersession /
inv_fanout).SupersedeReasonannotated on_txn_row{,_tuple}and supersession
writers (str | None→SupersedeReason | None).Origin—ORIGIN_INTERNAL_INITIATED,ORIGIN_EXTERNAL_FORCE_POSTED,
ORIGIN_EXTERNAL_AGGREGATEDFinalconstants (half-open like
status; the survey also surfaced + fixed a typo bug in
inv_fanout.py:222,241that used"ExternalInitiated"— not in
the canonical set per fuzzer + ETL walkthrough; corrected to
ORIGIN_EXTERNAL_AGGREGATED. SPEC docs synced).
A new no-raw-enum-equality AST lint sits at the test boundary so
raw-string comparisons against the canonical values can't regress
(extended through the constructor-input sweep — 187 raw kind=/scope=/
origin= etc. kwargs across 25 test files now carry typed constants).
3. BX.new.list-cascade-reload HTMX-inheritance silent swap bug.
The biggest single fix this cycle. The standalone list page's
cascade-reload wrapper carried hx-select="#list-page-body", which
HTMX 1.9 inherits to all descendants by default. Every Delete / Edit /
save / toggle button inside the wrapper picked up the inherited
hx-select. When the Delete button fired its hx-get to /delete-confirm,
HTMX applied the inherited select against the countdown HTML response
— there is no #list-page-body in that response, so the swap silently
emptied the target. afterSwap fires successfully; the wrapper just
gets cleared with nothing in. ~3h of MutationObserver tracing to find.
Fix: hx-disinherit="*" on the cascade-reload wrapper. Documented as a
generic quirks-log entry in docs/reference/quicksight-quirks.md —
this is exactly the silent-success failure mode the docs file exists to
catch.
Test infrastructure + audits. Five smaller landings:
- CB.5 deletion + runner collapse — the two deprecated agreement
test files (test_inv_dashboard_agreement.py+test_audit_dashboard_agreement.py)
carrying "CB.5 follow-up deletes the file entirely" comments since
2026-05 finally get deleted (1,835 lines), with a 13-file orphan
reference sweep. The runner's qs_browser layer's two-invocation
split (theagree_fileOracle-DDL-race carve-out) collapses to one. - BK.6 / #35 —
LimitBreachandInboundCapBreachplants on cust1- cust2 (was cust1-only) so the L1 inverse-picker test has ≥2
distinct account_id values. Calendar-drift footgun caught the next
day:days_ago=8sat at the boundary of a 7-day window and broke
at the day rollover; clamped to5and filed as backlog for a
boundeddays_into_windowtype (seedate_range_model_audit.md §10).
- cust2 (was cust1-only) so the L1 inverse-picker test has ≥2
- QS typeahead_filter — non-empty-query branch landed on
QsEmbedDriverper the code comment that described the pattern;
unblocks 4 dispatch-level skips. The picker tests themselves were
already callingfilter_options/pick_filter, so the actual
skip reclaim is narrower than the triage claimed. - Three audit docs landed — untyped enum surface, no-raw-str-args
lint blast radius (1,364 hits across the corpus; recommended
Option A scoped to 39 enum-shaped names + treat as its own future
phase), QS Browser skip triage (117 skips → 40 recoverable across
8 buckets). - PG snapshotter pgcrypto FileLock refactor — already shipped in
v13.14.2, now fully exercised; one corner-case stale-tab race
caught + fixed.
Operator-facing UX rotation worth flagging. Delete behavior went
through three iterations this cycle in response to live dogfood:
top-of-page confirm banner → in-place button swap with countdown +
Cancel + reason tooltip → terser "In use" button text + dropped
Cancel + smaller tooltip → text-on-button only ("Delete" / "In Use"),
no countdown tooltip. The cumulative result is a Delete UX that
indicates state via button text + position rather than a separate
hover/popup affordance. Browser e2e tests cover all three render
paths.
Two clusters land together:
Phase CT — Oracle 19c plant flow fix (user-reported #197 /
CS.10.followup #328). Two date-literal footguns where bare ISO-8601
strings were used as TIMESTAMP comparands. Oracle 19c rejects with
ORA-01843 ("not a valid month") — its NLS_TIMESTAMP_FORMAT default
doesn't auto-coerce that shape. PG + DuckDB happened to accept it via
implicit coercion, so the bug was Oracle-only and silently swallowed
inside the trainer Apply flow (the DELETE error'd out before reaching
the matview, leaving the plant a no-op).
Fix: use date_literal(iso, dialect) from common/sql/dialect.py
→ DATE 'YYYY-MM-DD' (portable across PG / DuckDB / Oracle).
Affected callsites:
common/l2/plant_registry.py::_invoke_balance_cadence_gap_plant
— the user-reported triggercommon/l2/deploy_pipeline.py::_build_generator_sql(X.4.h trainer
cutoff / scrub-head feature) — sibling bug, same shape
Verification: Oracle 19c trainer plant matrix went from 7/16 passed
to 8/16 passed (the formerly Oracle-only skip on
balance_cadence_gap is gone). Other 8 remain universally skipped
under BV.3.3.c.bug4-followup (chain-coherence dashboard rendering,
not Oracle-specific). Two new unit gates pin the typed DATE 'YYYY-MM-DD' shape across all three dialects so the regression
can't reappear.
Two CI-stabilization companions landed alongside CT.0:
- CT.1 —
App2Driver.pick_filternow peekscurvs the resolved
target BEFORE running the action; when equal, skip the 30s
_wait_for_refetch(setValue is a no-op, nochangefires, no
refetch comes). Defensive against any picker test that hits the
already-at-target case. - CT.2 — Narrow
pytest.skipon the[app2]parametrize of
test_inv_drilldown.py::test_account_network_table_walk_rerenders_table
pending backlog #331 (App2 Anchor parameter pick fires change but
no/visuals/*/datarefetch on CI;[qs]variant continues to
gate the same K.4.8 invariant on the production renderer).
Phase CS — 14 backlog items (CR follow-throughs + operator-visible
polish + Oracle/Studio bug bash). Highlights:
- CS.1 — Removed dead SQLite plumbing surfaced by no-sqlite-prose
lint (CB.8 cleanup). DB connection-leak gate renamed to
RECON_GEN_DB_CONN_LEAK_GATE. - CS.2 — Re-lit
test_inv_drilldownafter CR.6.a; now uses
drill_from_first_row_via_menuto match the production
DATA_POINT_MENUtrigger + row-content assertion (catches the
K.4.8f-3 no-op shape that a count-based assertion would miss). - CS.4 — Sankey node-cap operator visibility: subtitles name the
cap on Investigation + L2FT Sankeys; caps aligned at 50.
Dynamic banner deferred to CS.4.followup #326. - CS.6 — Reordered L1 + L2 dashboards: Exceptions sheet promoted
to position 2 (right after Getting Started) so operator's daily
triage flow lives above lower-volume secondary surfaces. SheetIds
unchanged so deep-link URLs still resolve. - CS.7 — L1 Exceptions copy: prose said 10 invariants; matview
has 12 (post-CL.6 + AB-era chain-coherence). New
L1_EXCEPTIONS_BRANCH_NAMEStyped tuple is now the single source
of truth; unit gate parses the matview SQL and asserts the
bidirectional match so the count can't drift again. - CS.8 — Themed 503 page for
PoolReleasedDuringRefresh(the
CO.x DuckDB writer-lock fix's transient-state surface). Operator
sees a calm "Data refresh in progress" with auto-reload meta
refresh instead of an untemplated 500. - CS.9 —
RECON_GEN_STUDIO_ETL_HOOK_TIMEOUT_SECSenv knob with
positive_intvalidator. Operators with slow upstream ETLs can
raise the timeout cap without forking; subprocess termination
returns rc=124 (GNU timeout convention) on timeout. - CS.11 — Pinned the Distinct Senders KPI binding shape; root-
cause for the App2-side "renders as None" regression needs live
env reproduction → deferred to CS.11.followup #327. - CS.12 —
anomaly/money_trailapp2 tests now derive the
expected sender role from the L2 instance instead of hardcoding
CustomerSubledger(which doesn't exist on sasquatch_pr). - CS.13 — Session Start probes for base schema before refresh;
emits actionable remedy instead of silent no-op when missing.
Three CS items filed as backlog follow-ups (#326, #327, #328) for
the parts that couldn't be completed in-session.