makepad-ports/makepad_assessment.md
andodeki 7ddd75fb70 Makepad 2.0 ports: rider, koboyo, insurance (+ HTML sources, assessments)
Three single-file HTML apps ported to Makepad 2.0 (dev b41e740) script_mod! DSL:
- rider (ride.html): 21 unit + 9 integration + 9 headless UI tests
- koboyo (koboyo.html): 29 + 14 + 8
- insurance (insurance.html): 28 + 14 + 8
All 140 tests green. Includes headless-Linux platform patch, full README,
setup script, and three brutal codebase assessments with execution plans.
2026-08-04 05:07:18 +00:00

364 lines
24 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Makepad 2.0 (dev @ `b41e740`) — Brutal Professional Assessment
**Scope & evidence base.** This reviews the dev branch as attached in the
workspace (sparse clone: `AGENTS.md`, `examples/splash`, `examples/uizoo`,
`libs/makepad_test`) **plus a full fresh clone used for metrics** and
compiler/test telemetry from building and testing three example apps against
the complete tree this week. It is *not* a line-by-line audit of all 5,548
files — where a claim is telemetry-based rather than line-read, it says so.
Hard numbers, all measured on `b41e740`:
| Metric | Value | Source |
|---|---|---|
| Tree size / files | 209 MB (excl. .git) / 5,548 files | fresh clone |
| Core Rust LOC (`platform+draw+widgets`) | ~283k lines in 505 `.rs` files | `wc -l` |
| `platform/script` (the DSL language) | 80 files / 52,822 lines (parser.rs alone: 4,291) | `wc -l` |
| `unsafe` in `platform/src` | **2,565** (~7.4 per file) | grep |
| `#[test]` across the entire core | **157, in only 27 files** | grep |
| `.unwrap()` in core sources | 1,562 | grep |
| Compiler warnings on stock toolchain | 14 (`makepad-widgets`) + ~40 more in `draw`, incl. `f64→f32` fallbacks that rustc flags as **future hard errors** (rust-lang issue #154024) | build logs from this session |
| CI configuration | **None. No `.github/`, no workflows anywhere in the tree** | `find` |
| Toolchain pin / committed lockfile | **Neither** (`rust-toolchain*` absent, `Cargo.lock` gitignored) | tree |
| Vendored code in `libs/` | **1.4M lines / 2,811 files** (rapier 18 MB, vulkan 13 MB, stitch 13 MB, CEF, CUDA, MLX, TTS, windows-bindgen…) | `du` |
| Root-directory clutter | 9 planning `.md` docs + 5 floating `download_*.sh` blobs at repo root | `ls` |
| Registration duplication | 161 `workspace.members` vs 20 `RunStudioRelease` lines in `makepad.splash` — both must be hand-edited per example | grep |
| Mega-files | `vulkan.rs` 7,120 lines, `widget_tree.rs` 3,917, `text_input.rs` 3,574, `portal_list.rs` 3,463 | `wc -l` |
Scorecard (010):
| Axis | Score | One-liner |
|---|---|---|
| Architecture | 6.5 | Ambitious, coherent layers — undermined by an interpreted DSL whose correctness lives in a markdown file |
| Code quality | 5.0 | Competent, idiomatic Rust in places; crushed by scale per file, warning debt, and test scarcity |
| Bugs / correctness | 4.0 | **dev head does not compile headless-Linux at all**; emulator-grade traps in the widget kit |
| Performance | 7.5 | Rendering engine is world-class; repo/tooling ergonomics (clone size, boot, build) are not |
| Design (API/DX) | 4.5 | Ten undocumented-or-markdown-documented sharp edges, several of them silent-failure |
| Security / safety | 3.5 | Documented memory-layout footgun for GPU buffers, floating multi-GB downloads without checksums, massive unaudited vendored surface |
| Tooling & CI | 2.0 | No CI, no pin, no lockfile, warning debt — the supply side of this project is running on vibes |
| **Overall** | **4.7** | **A research-grade engine with production-grade ambition and zero production guardrails** |
The good, stated once: the render stack (shader VM, retained areas,
batching, hot reload) is genuinely the most advanced in the Rust-UI space;
the cross-platform breadth (desktop, mobile, WASM, OpenXR, CEF) is real
code, not vaporware; `examples/splash`/`uizoo` contain zero `.unwrap()` (the
demos are cleaner than the framework); the new `makepad_test` studio-protocol
harness is a legitimately good idea; and publishing an `AGENTS.md` contract
for AI contributors is forward-thinking. None of that excuses what follows.
---
## 1. Architecture
**A1 — The framework's correctness is enforced by a Markdown file, not the
type system.** `script_mod!` waits until *app startup* to parse the UI: every
DSL mistake (`=` instead of `:`, wrong key name, missing scope) is a runtime
boot error. That's the deliberate hot-reload tradeoff, but the safety
fallback is **855 lines of rules for humans/agents** instead of compiler
guidance. Concretely, `AGENTS.md` rule 16 states that field order in
`#[repr(C)]` draw-shader structs is a **memory-safety contract**: the system
"uses an unsafe pointer trick in `DrawVars::as_slice()` that reads
contiguously past the end of `dyn_instances` into the subsequent `#[live]`
fields… [misordering] will **corrupt the GPU instance buffer**." A "Rust is
safe" framework whose GPU buffer integrity depends on developers reading a
rulebook is a documented footgun, full stop. The fix (derive-time layout
assertions) is a weekend of proc-macro work; it hasn't been done.
**A2 — A 53k-line bespoke language is the price of admission.**
`platform/script` (parser.rs 4,291 lines + VM + test app) is the core of
2.0: a whole scripting language whose tokenizer quirks are normalized as
usage rules — `#2ecc71` fails because `2e` parses as scientific notation
("write `#x2ecc71`"), and comments before the first token shift all error
columns because "Rust's proc macro token stream strips comments entirely."
These are *bugs documented as features*; every downstream team will
rediscover them one cryptic error at a time.
**A3 — Split-brain event architecture.** Two lanes: low-level
`event.hits_with_capture_overload(cx, area, …)` vs high-level widget
`finger_down(actions)` — and **`Event::FingerDown` doesn't exist at all** on
the enum; you must know to ask for `Hit::FingerDown`. Worse, a `View` only
*emits* `FingerDown` if a `cursor` is set: **cursor styling gates input
semantics**. A tooltip changed your clickability. This cost real debugging
time in two separate ports this week.
**A4 — Layering discipline inverts at the edges.** `libs/` — nominally
third-party vendored code — contains 1.4M lines including ML inference
(`diffusion`, `mlx`, `tts`, `cuda`), a physics engine (`rapier`), and a
browser (`cef`). These are *example-app dependencies living in the platform
repo*: every `cargo metadata`, clone, index, and audit pays for demo assets.
Meanwhile `studio/` (the design tool) is compiled into the same workspace as
`widgets`, so example apps that import widgets transitively configure the
studio protocol — which is also the test harness's control channel.
**A5 — Duplicated registries.** Example crates must be added to
`Cargo.toml workspace.members` **and** `makepad.splash` **and** use a
`#[makepad_test]` package convention; 161 members vs 20 splash items shows
the two lists already drift arbitrarily. One source of truth generated from
Cargo metadata is the obvious fix AGENTS.md already half-implements by
policy.
**A6 — Headless is a figurative platform, not a real one.** There *is* an
`os/headless` backend — and dev `@ b41e740` **does not compile it on Linux**:
`gl_render_bridge` un-gated, `can_play_type_impl` missing the headless arm,
`libc_sys`/`ipc`/`dma_buf` modules absent under headless, `shared_framebuf`
importing them unconditionally, `HeadlessLoadedModule` missing `symbol()`,
`Cx::is_draw_shader_window_ready` unimplemented. Five separate compile breaks
= nobody has built this configuration in CI. (See §3, B1.)
## 2. Code quality
- **Q1 — File-scale pathology.** `vulkan.rs` at 7,120 lines, a 4,626-line
*test* `main.rs` in script, 3.9k `widget_tree.rs`. These aren't modules,
they're walls. Review and blame archaeology are dead on arrival.
- **Q2 — Warning debt on the hot path.** 14 warnings in `makepad-widgets`
alone, including `#[live(4.0)]` picking up the deprecated `f64→f32`
fallback the compiler explicitly schedules as a future **hard error**
(chart.rs:383-385, per rust-lang#154024). When rustc flips that switch this
crate stops compiling. This is known, dated breakage sitting in tree.
- **Q3 — Panic-as-control-flow at scale.** 1,562 `.unwrap()` in core sources.
Not fatal in demo apps; in a rendering framework each is a potential
app-crash under user data — and with no fuzzing or property tests in tree,
they're uncharacterized.
- **Q4 — Tokenizer debt is user-facing.** The `#x` hex prefix rule has a
second edge nobody documents: it applies only when a *digit directly
precedes `e`* (`#2ecc71` fails, `#e8926a` passes — verified empirically
here). Developers will hit the unlucky combinations at random.
- **Q5 — Leaky core abstractions.** `PortalList::next_visible_item`
**over-requests one item past the declared range end**; index blindly and
panic (verified in the rider chat list — the crash is in *their* pattern,
not user code). `ids!(var)` in a loop silently hashes the *literal* string
`"var"` — compiles clean, returns the wrong widget, no error. Dynamic lookup
requires `&[var]`, discovered via "unused variable" warnings, not docs.
- **Q6 — Doc coverage is uneven by an order of magnitude.** `widgets` has
~1,846 doc lines for 1,079 `pub fn`s (fine); `platform/os/headless` and
the script VM's public surface are essentially uncommented. The README is
marketing + socials; the deep contract lives in a file addressed to
language models.
- **Q7 — Repo hygiene as quality signal.** Root hosts `aigame.md`,
`handoff.md`, `talk.md`, `splashgame.md` etc. — sprint notes shipped as
project surface, next to 5 floating download scripts. This is what
"no release process" looks like.
## 3. Bugs (ranked — upstream, verified this session unless noted)
- **B1 (Critical) — dev head does not build for headless Linux.** Six
independent failures (listed in `patches/makepad-dev-headless-linux.patch`,
which this workspace ships): missing `cfg` gate on `gl_render_bridge`;
missing headless arm in `cx_api::can_play_type_impl`; three missing module
shims (`libc_sys`, `ipc`, `dma_buf`) under `os/mod.rs`; unconditional
imports in `shared_framebuf.rs`; unimplemented `symbol()` on
`HeadlessLoadedModule`; unimplemented `Cx::is_draw_shader_window_ready()`.
Shipping a workspace where a documented platform doesn't compile means the
platform matrix isn't gated *at all*.
- **B2 (High) — Warning class scheduled to become fatal.** `f64→f32`
implicit fallback in `#[live]` defaults (≥2 sites in chart.rs, plus
draw-glyph/draw-pbr warnings). Silence today, hard error on a future
toolchain the project doesn't pin (and can't pin — no `rust-toolchain`).
- **B3 (High) — `PortalList` range overrun** (see Q5): core list widget
emits item ids outside the declared range; every consumer must defensively
`.get()` or panic. This belongs in the widget, not in 100 downstream
guards.
- **B4 (Medium) — Cursor-gated hit testing** (A3): a `View` with no `cursor`
and no animator is click-invisible. Undocumented in code; breaks the
mental model "drawn area == hittable area".
- **B5 (Medium) — Comment-stripping breaks diagnostics**: leading comments
or blank lines inside `script_mod!` desynchronize error spans (the fix
"start with real code" is legislated in AGENTS.md rule 18 instead of the
tokenizer tracking trivia).
- **B6 (Medium) — Hex/scientific-notation collision** (rule 19): tokenizer
wart preserved as permanent API surface via the `#x` escape.
- **B7 (Medium) — Boot-order fragility**: the widget tree builds lazily, so
boot-time `ui.widget(cx, …)`/`view(cx, …)` borrows return nothing and any
unconditional unwrapping crashes; consumers must poll/guard. Also:
`.view()` and `.widget()` return *different* sets (`ViewSet` vs
`WidgetSet`) — a custom widget queryable via one and silently absent via
the other.
- **B8 (Low) — Test-harness locator semantics**: `makepad_test` `click()`
always targets widget center, `wait_visible` doesn't check viewport
containment, no offset/click-at-point primitive; failure artifacts are
good (`failure.txt`/`widget-tree.txt`) but the panic line
(`runtime.rs:1374`) is the only stack context.
- **B9 (Low) — Floating example assets**: `download_tts.sh` pulls
`ggml-large-v3-turbo.bin` from a HF `resolve/main` URL — content changes
underneath you; reproducible example builds are impossible by construction.
## 4. Performance
- **P1 (Strength) — The render architecture earns its reputation.** Shader-
driven immediate-mode-over-retained-areas, draw-call batching, per-area
invalidation, hot reload, GL/Vulkan/DX/Metal backends, headless raster.
UI rendering performance is not the problem.
- **P2 — Everyone pays the ML tax.** `libs/` vendoring (`cuda`, `diffusion`,
`mlx`, `tts`, `rapier`, `cef`, `windows-bindgen`) inflates clone to 209 MB
and metadata/graph resolution for *every* consumer, including the phone-
sized demo apps that use none of it.
- **P3 — Boot cost discipline is absent.** Because the DSL is interpreted at
startup, app boot includes full script parse+link; in the headless harness
each app instance (with protocol handshake) costs **~1020 s** cold. That
also defines the floor for any startup-time-sensitive product — and it's
unmeasured anywhere in tree (no startup benchmark exists).
- **P4 — Build-time is the real DX bottleneck.** From-scratch `cargo check`
of a two-example workspace on this sandbox ran into tens of minutes
(instrumented indirectly via long build phases this session); with no CI
caching artifacts and no sccache config committed, every contributor eats
it raw.
- **P5 — Test parallelism defeat**: `#[makepad_test]` boots one child app
per test, serially in practice — the 9-test rider suite needs ~3 minutes.
With ~zero pure unit tests upstream (§2), the only green/red loop is slow
by construction.
- **P6 — No perf guardrails**: no frame-time harness, no allocation
tracking, no per-commit renderer benchmarks. A framework that sells
performance should have a `benches/` that CI enforces; there isn't one.
## 5. Security & safety
- **S1 — GPU buffer integrity by convention** (A1, AGENTS.md rule 16).
`DrawVars::as_slice()` performs a documented out-of-slice contiguous read
into trailing fields; correctness depends on field *order* in user
structs. This is memory-unsafety reachable from perfectly ordinary app
code, mitigated only by "read the doc". Any safety-branded framework must
encode this invariant in the derive macro — compile error, not markdown.
- **S2 — Unsafe surface with no audit scaffolding.** 2,565 `unsafe`
occurrences in `platform`, no `#[deny(unsafe_op_in_unsafe_fn)]` policy, no
Miri/ASAN config in tree, no security.md, no audit comments linking to
invariants. For OS interop some of this is necessary — but there's no
evidence of a systematic review trail.
- **S3 — Supply chain: floating, unverified multi-GB fetches.**
`download_cef.sh` (spotify CDN), `download_map.sh` (geofabrik),
`download_tts.sh`/`download_voice.sh` (HuggingFace `resolve/main`) — no
pinned versions, no SHA-256, no signatures (verified in the scripts
themselves: straight `curl`/`wget`). One poisoned CDN account away from
shipping blobs into user apps.
- **S4 — Studio protocol exposure.** The studio hub and the test harness
speak a control protocol into the app process (stdin/stdout in
`makepad_test`, hub channels in `studio/`). There's no visible auth or
scoping in-tree; treating a design-time channel as implicitly-safe is how
debug endpoints end up in production builds (cf. every framework that
learned this the hard way).
- **S5 — Vendored 1.4M lines of third-party code without provenance
tracking.** No `vendor.lock`, no audit metadata, no automated `cargo
audit` equivalent that can even *see* these. Vulnerabilities in the
vendored `windows` bindgen / `rapier` / `stitch` snapshots are invisible
to the entire Rust security tooling ecosystem by construction.
- **S6 — No lockfile committed for a 161-member workspace** (`.gitignore`:
`Cargo.lock`). Consumers resolve fresh dependency graphs per build;
combined with no toolchain pin, "works on my machine" is the *designed*
state.
- **S7 (positive)** — Example app code (splash/uizoo) avoids `unwrap`
entirely and the script engine has its own dedicated test app
(`platform/script/test`, 4.6k lines): the language authors test their
language. The rest of the org does not test theirs.
## 6. Design (API & developer experience)
- **DX1 — The 855-line AGENTS.md is both the project's best document and its
worst smell.** It exists because the failure modes are real: it's a
catalog of every sharp edge formalized as user obligations (rules 16/18/19
quoted above, the `let X = #(X::register_widget(vm))` incantation,
`instance()`/`uniform()` shader-only, `theme.` prefixes, `+:` merge). Good
frameworks remove edges; this one documents them and continues.
- **DX2 — Silent-failure ergonomics.** `ids!(var)` (Q5), `.view()` vs
`.widget()` set difference (B7), mod.widgets assignment *not* bringing a
widget into scope (triageable only via "variable X not found in scope"
much later), cursor-gated actions (B4): a consistent pattern — the wrong
code compiles and runs, just misbehaves.
- **DX3 — Naming/macro ceremony.** `live_id!`, `ids!`, `id!`,
`live_id_unchecked`, `#[makepad_test]`, `app_main!`, `script_mod!`,
`script_shader!`, `register_widget(vm)`, `WidgetSet` vs `ViewSet` vs
`WidgetRef` — the conceptual surface per trivial app is far above
egui/dioxus-tier, and migration from 1.x `live_design!` is breaking with
no codemod in tree.
- **DX4 — Test authoring is the best DX in the repo** (once discovered):
`Selector::id(...).click(); app.press_key(...)` — but it needs
click-at-point, viewport checks, and faster boots to graduate from
"usable" to "good". Notably, the only real test culture visible on dev
lives in *example apps*, not the framework.
- **DX5 — 1.x→2.0 doc gap.** Search engines serve makepad 1.x `live_design!`
answers; 2.0 changed the shape of everything and the only authoritative
source is in-repo. Every external LLM, blog, or tutorial mis-teaches 2.0
users today.
- **DX6 — `makepad.splash`/`Cargo.toml` dual registration** (A5) and the
20-of-161 splash coverage: the example discovery story is itself
unenforced.
---
## 7. Execution plan — make this codebase actually improvable
Written to be actionable from two seats: **the Makepad team** (upstream) and
**a consumer running a private fork** (what this workspace can do today).
Rough effort assumes a small team.
### Phase 0 — Supply-side hygiene (23 days; fork can do all of it)
| # | Task | Acceptance |
|---|---|---|
| 0.1 | Commit `rust-toolchain.toml`, un-ignore `Cargo.lock` for workspace+examples, add `audit.toml`/`cargo-deny` | Reproducible builds; `cargo deny check` green |
| 0.2 | **Stand up CI** (GitHub Actions matrix: Linux/macOS/Windows × {x11, wayland, **headless**}): `cargo check --workspace`, run `platform/script/test`, run `makepad_test` suites for 3 example apps | dev head must compile every declared platform; B1 class of bug becomes impossible to merge |
| 0.3 | Zero-warning policy: fix the 54 warnings incl. `f64→f32` live defaults (B2); add `-D warnings` to CI | Tree compiles clean; future-hard-error class extinguished |
| 0.4 | Pin + checksum all `download_*.sh` assets (URL → version + SHA-256 manifest), move to `xtask` | Tamper-evident asset fetches (S3) |
| 0.5 | **Upstream the five headless-Linux fixes** from `patches/makepad-dev-headless-linux.patch` as one PR with a headless CI leg proving it | Patch deleted locally |
### Phase 1 — Kill the documented footguns (36 weeks)
| # | Task | Acceptance |
|---|---|---|
| 1.1 | Encode the draw-shader layout invariant **in the derive**: generated `const _: ()` asserting no non-instance data after `DrawVars`/instance fields; `DrawVars::as_slice` gets `unsafe_op_in_unsafe_fn` + SAFETY comments (S1) | Misordered struct = compile error; AGENTS.md rule 16 deleted as obsolete |
| 1.2 | Tokenizer fixes: treat trivia (comments) in span tracking (B5); disambiguate `e` in hex without `#x` — and make `#x` a warning-free alias for one release (B4/DX4) | `#2ecc71` parses; rule 19 removed |
| 1.3 | Compile-time DSL validation: parse `script_mod!` in the proc-macro as far as possible (head/token/`=` vs `:` checks) with rustc diagnostics; runtime errors only for true dynamic features | Common DSL mistakes fail at `cargo check`, citing file:line |
| 1.4 | Hard-error the silent failures: `ids!` of a non-literal when a same-named var is in scope = error suggesting `&[var]`; `View` without cursor that has finger handlers = warning; `.view()` vs `.widget()` hints in error text | Every trap from §2 produces a message at the point of mistake |
| 1.5 | `PortalList` contract: never yield out-of-range item ids (B3) + regression test in-tree | `.get()` guard no longer required downstream |
### Phase 2 — Test & quality architecture (48 weeks, overlapping)
| # | Task | Acceptance |
|---|---|
| 2.1 | Test-floor policy: every `widgets/src` widget gets at least smoke coverage; grow core `#[test]` count from 157 toward a per-file baseline; property tests for script parser/VM (corpus exists in `platform/script/test` — generalize) | CI reports coverage; trend enforced non-decreasing |
| 2.2 | `makepad_test` v2: click-at-point, viewport-visibility semantics for `wait_visible`, parallel app boots, boot-time budget alert (~>5 s/instance fails perf gate), richer failure diffing (current text artifact is fine, add tree diff vs expectation) | Rider-scale suite (9 tests) < 60 s wall |
| 2.3 | Renderer benchmark harness (`benches/`): frame time on canonical scenes, allocation regression gate, startup-time measurement (P3/P6) | Perf numbers on every PR |
| 2.4 | Unsafe audit program: categorize the 2,565 with `cargo geiger`, Miri on `platform/script` VM + `shared_framebuf` + draw-vars paths; SAFETY-comment lint | Audit report committed; Miri leg in CI |
| 2.5 | Clippy pedantic on changed code baseline; mega-file roadmap (split `vulkan.rs`, `widget_tree.rs`, `portal_list.rs` behind submodules — no behavior change) | Largest file < 2.5k lines |
### Phase 3 — Repo & ecosystem surgery (26 weeks)
| # | Task | Acceptance |
|---|---|
| 3.1 | Move `libs/` ML/CEF/demo stacks into a separate `makepad-extras` repo (or crates.io deps); core clone back to <60 MB | Clone time / `cargo metadata` drop visibly (P2) |
| 3.2 | Vendor manifest for what remains (`vendor.lock` with upstream commit + license), license audit of the whole tree | SBOM generated in CI (S5) |
| 3.3 | Root cleanup: planning docs → `docs/internal/` or wiki; single registration source (`xtask` generates splash run-items from workspace metadata) | A5 resolved; no dual entry (DX6) |
| 3.4 | Authoritative docs: rustdoc for `widgets` + `platform/script`, published per-commit; AGENTS.md content rendered into the human docs (it's currently agent-only); 1.x→2.0 migration guide + codemod script | DX5 closed; `docs.rs`-grade reference |
| 3.5 | Studio protocol: audit surface, default-off in release profiles, scoped loopback + token for test channel (S4) | Release builds ship no open control plane |
### Phase 4 — What the fork in this workspace should do *now* (days)
1. Carry the five headless patches until Phase 0.5 lands (already done).
2. Keep the three test-suffixed example crates as the project's accidental
"integration lab": they already exercise PageFlip, PortalList, custom
draw shaders, script_apply_eval, hit capture, and the test harness —
formalize them as the fork's regression suite for upstream bumps.
3. Gate any upstream re-sync on: clean `git apply` of patches, all 140
port tests green (currently: rider 21+9+9, koboyo 29+14+8, insurance
28+14+8), zero new warnings.
### Definition of done (framework-level)
1. `dev` compiles on every platform it names, in CI, with `-D warnings`.
2. No memory-safety invariant lives only in documentation (S1 closed).
3. A new example app can be built by reading rustdoc alone — 0 AGENTS.md
rules required to avoid silent traps (DX1/DX2).
4. Test culture shift measurable: core `#[test]` count ×10 at minimum and
CI-time <15 min with caching.
5. Clone-and-build to running `uizoo` from a cold machine <10 min.
---
### Bottom line
Makepad is **the best Rust-UI render stack wrapped in research-lab
engineering discipline**. The engine deserves the ambition; the repository
doesn't currently deserve users. The single highest-leverage change costs
almost nothing — CI + warnings + the headless fix (Phase 0) — and would have
prevented every upstream bug hit in this workspace. Everything durable
beyond that is one theme: convert obligations currently printed in
`AGENTS.md` into compiler-enforced invariants, and the framework grows from
"impressive demo platform" into something teams can bet on.