The dimension-editor and hardening release: editable MDX queries; Copy/Move/Pin and
explicit top-level membership in the structural dimension editor; de-duplicated
consolidation rollups and a lowercase, display-name-decoupled on-disk layout; the
suppress-zeros split and a consolidated view-save flow; plus a wide adversarial
hardening pass (an app-wide error boundary, WebSocket-reload data-loss guards,
accessibility, and performance) and the security fixes below.
Added
- Editable MDX queries in the cube view. The "MDX for this view" dialog is now
an editable textarea with a Run button that executes a full
SELECT … ON COLUMNS / ON ROWS FROM [cube] [WHERE ( … )]query against the cube
and renders the resulting cellset, surfacing parse/validation errors inline. The
epiphany-mdxparser gained aSELECT-query layer (parse_query) above its set
sublanguage, and a newPOST /api/v1/cubes/{cube}/mdxendpoint executes it through
the sameReadauthorization and element-masking path as the ad-hoc cellset query. - Copy to / Move to for dimension members. The member action menu now offers
Copy to (add the member to another consolidation as an alternate rollup, keeping
its existing parents; a consolidation it already belongs to is greyed as "already
contains") and Move to (reparent it under exactly one consolidation or the top
level, with a confirm when that would drop its other rollups). Self and descendant
targets are excluded so a cycle can't be formed. - Separate zero-suppression for rows and columns. The single "Suppress zeros"
option is now two independent toggles, Suppress zero rows and Suppress zero
columns, on the cube viewer's Save dialog and the view editor. Views saved before
this map a legacysuppress_zeros: trueto both flags. - Explicit top-level membership (ADR-0038). A member can be pinned to the top level
so it shows as a display root while still rolling up under its consolidations - it
appears both at the top and under each parent (the latter carry a "(pinned)" badge).
Pin via the row menu's Copy to -> Top level or a discoverable drag-to-top-level
drop zone; copying a top-level member into a consolidation auto-pins it so it stays at
the top; Move to -> Top level is the exclusive variant (reparent to a standalone
root). A display-only marker: no edge, weight, or value changes. Full stack - a
per-element flag (core), a name-addressed WAL record (persist), apinned_to_topDTO
field + pin/unpin edit ops (api), and the editor + pivot rendering. - Expand all / Collapse all in the dimension editor toolbar, matching the cube
viewer and the model tree.
Changed
- Views are created from the cube viewer. The duplicative "Add view..." authoring
path was removed; arrange a layout in the cube viewer and use Save view to create
one. The Views workspace now opens and edits existing views in place. - Consolidation rollups de-duplicate (ADR-0039). A member reachable via more than
one path to a consolidation - e.g. a direct child of a total that is also a child of
one of that total's sub-consolidations - is now counted ONCE in that total, at its
most-direct (fewest-hops) path weight, instead of summed per path. It flows through
every consolidated read, spreading, and element-security masking, and makes the
multi-parent / pinning structures safe from accidental double-counting. - On-disk cube folders are lowercase, filesystem-safe slugs decoupled from the
display name (ADR-0037). A cube "Sales" persists undercubes/sales/while still
presenting as "Sales" (the engine reads the name from the snapshot, not the folder).
Existing folders migrate by rename on first start (resilient: never deletes; a real
slug collision is left intact and logged), and cube names are now unique
case-insensitively. For cross-platform (Linux, case-sensitive filesystem) consistency.
Fixed
- Cube view: pivot rows/cells no longer duplicate or strand cells under an
alternate rollup (a member reachable from two consolidation parents). Rows, cells,
and header spans are keyed by each member's drill path instead of its bare name,
and saved-subset member lists are de-duplicated. - Cube view: the "Who can see it" dropdown in the Save-view dialog now paints
above the dialog instead of behind it (the dropdown layer ranked below the modal
overlay in the z-index scale). - Dimension editor: right-clicking a member opens the action menu at the cursor
(it previously anchored to the row's ⋯ button). - Cube tab strip: removed the spurious vertical scroll arrows.
- Durability: a checkpoint can no longer lose data on a crash. The snapshot is
fsynced (file and directory) before the write-ahead log is cleared, and a
reindexing dimension edit (reorder/delete/insert) folds outstanding writes and
empties the WAL before rewriting the snapshot, so recovery can never replay a
stale element index onto the new layout. A WAL truncated below its header by a
crash is now treated as fresh rather than failing the open. - A failed model-definition commit no longer leaks into the next one. If
persisting a definition change fails midway, the in-memory model is rolled back
to the last published version, so the partial change cannot ride out on a later
successful commit. - What-if sandbox overrides now follow structural dimension edits. Reordering,
inserting, or deleting a member remaps each sandbox override to its member's new
index (and drops overrides on a deleted member) instead of leaving them on the
wrong member or panicking the checkpoint. - Flows: the TypeScript stripper no longer drops
switchcase bodies such as
case 0: break;(a case/default colon was mistaken for a type annotation), and
no longer corruptsinterfaceused as an ordinary identifier. - Dimension aliases: reassigning an alias no longer leaves the old one
resolvable (which also falsely blocked another member from claiming the freed
name). - Cube view: an alternate-rollup member can now be expanded independently under
each parent (expansion is keyed by drill path, not member name); a slow cell
read can no longer overwrite a newer one; and a set that fails to resolve surfaces
the error instead of silently blanking the axis. - Dimension editor: rapid keyboard and context-menu structural edits are gated
so they cannot race a prior in-flight edit, and member-table range selection
survives a re-sort or filter. - A non-admin schedule author can open the jobs workspace again (the admin-only
run list no longer fails the whole load), and opening a saved view no longer
drops its layout when the cube loads slowly. - Windows service: a failed startup now reports a non-zero exit to the SCM
instead of a clean stop. - Dimension editor: drag-and-drop is a reliable, parent-aware move. A member can
now be dragged into a consolidation, and every drag emits the minimal, duplicate-free
edits: dropping onto a consolidation moves the dragged occurrence there while keeping
the member's other rollups, and the additive add runs before the detach so an
interrupted multi-step move can never orphan a member. Editor and pivot now agree on
child order, and the drop-inside target band is larger and recomputed at release. - Login: you stay signed in after reloading the page. The session is restored from
the cookie on load (it was previously held only in memory), including the forced
password-change screen, and the duplicate password-manager autofill prompt is gone. - Login: Enter signs you in, and autofilled credentials work. The form submits on
Enter even when a password manager swallows the browser's implicit submission, and the
submit reads the live field values, so a manager that fills the fields without firing
the usual events no longer sends blank credentials. - The what-if sandbox bar no longer shows in the structural dimension editor
(what-if overlays cell values; editing members and edges has no cells to override). - Dimension editor: a row's label click expands but never collapses (only the caret
collapses), and dragging a multi-parent member to the top level reports honestly
("Removed X from Y" when it still rolls up elsewhere) rather than a misleading "moved
to the top level". - A render error in one pane no longer blanks the whole app. A new error boundary
shows a recoverable fallback for the affected pane and leaves the tab strip and tree
usable; switching or closing the tab clears it. - No more silent data loss from background reloads. Unsaved rule edits survive a
WebSocket reload caused by activity in another tab; editing a flow or schedule that
was deleted elsewhere now disables Save (so it is not silently re-created) and says so;
entering the Administration view honors the unsaved-edit guard; and deleting an open
object no longer pops a nonsensical post-delete discard prompt. - Object tree stays correct under concurrent loads and search. A slower, stale
response can no longer overwrite freshly loaded children, and a delete or rename during
a search no longer leaves a stale row in the results. - Dimension editor robustness and accessibility. Adding a member as a child is now
all-or-nothing (a declined conversion or a failed step no longer strands an orphan with
the form trapped on a duplicate-name error), duplicate names are caught before the
round-trip, an empty dimension shows guidance, successful edits and keyboard moves are
announced to screen readers, and a pathological cyclic edge set can no longer overflow
the tree builder. - Cube viewer: stale numbers are not shown during a refresh. While re-querying after
a layout or filter change the grid dims and announces "Refreshing", so a previous
slice's values are never read as the current ones, and an empty row axis shows an
explicit "No data" row. - Accessibility: closing the active tab moves keyboard focus to a neighbor (rather
than losing it on the document body), and the Administration sub-nav is now a correct
button group instead of a malformed tabs widget.
Security
- The last administrator can no longer be deleted or demoted, which would have
locked everyone out of the admin control plane. - Sandbox lifecycle endpoints now require cube access (Read to list/create/get/
discard, Write to commit), matching the data endpoints; a user with no grant on a
cube could previously enumerate and operate sandboxes there. - Deleting a user, resetting their password, or an admin setting their password
now revokes that user's sessions, so a stale token stops working immediately. - The session cookie is marked
Securewhen the server serves over TLS, so it
is never sent on a plain-HTTP request. - Serving plain HTTP on a non-loopback address now logs a warning that the API
and login are exposed unencrypted.