@@ -6,7 +6,7 @@ This is the central engineering notebook for the RIDDL project. It tracks curren
66
77## Current Status
88
9- ** Last Updated** : February 14 , 2026
9+ ** Last Updated** : February 16 , 2026
1010
1111** Scala Version** : 3.7.4 (overrides sbt-ossuminc's 3.3.7 LTS
1212default due to compiler infinite loop bug with opaque
@@ -139,6 +139,24 @@ Added `ast2bast(root: Root)` to:
139139AI-friendly validation pass for MCP server integration. See design
140140section below.
141141
142+ ### 5. Scala.js Validation Performance (DONE)
143+ ** Status** : Complete (February 16, 2026)
144+
145+ Four-phase optimization to make validation practical for large
146+ models in Scala.js (browser playground, future LSP):
147+
148+ - ** Phase 0** : ParentStack caching — replaced type alias with
149+ wrapper class that caches ` toParents ` (toSeq) result
150+ - ** Phase 1** : ValidationPass micro-optimizations —
151+ ` recursiveFindByType ` cache, single-pass handler
152+ classification, combined SagaStep validation
153+ - ** Phase 2** : ` validateStringQuick() ` — ` ValidationMode ` enum
154+ gates expensive postProcess checks for interactive feedback
155+ - ** Phase 3** : ` IncrementalValidator ` — context-level
156+ fingerprinting and message caching for repeated validation
157+
158+ See session log February 16, 2026 for details.
159+
142160---
143161
144162## Blocked Tasks
@@ -422,6 +440,75 @@ suggestions to CLAUDE.md files, create `/release` skill.
422440
423441---
424442
443+ ### February 16, 2026 (Scala.js Validation Performance)
444+
445+ ** Focus** : Four-phase optimization to make validation practical
446+ for large RIDDL models in Scala.js. The ossum.ai Playground's
447+ ` validateString() ` took 168s for reactive-bbq (8,105 lines);
448+ even ` getTree() ` took 152s due to framework-level overhead.
449+
450+ ** Root Cause** : ` ParentStack.toParents ` (AST.scala) called
451+ ` mutable.Stack.toSeq ` on every AST node visit — O(N* D)
452+ allocations where N=nodes, D=average depth.
453+
454+ ** Work Completed** :
455+ 1 . ** Phase 0: ParentStack caching** — Replaced
456+ ` type ParentStack = mutable.Stack[Branch[?]] ` with a
457+ ` final class ParentStack ` that caches the ` toSeq ` result,
458+ invalidating only on push/pop. Same API surface. Expected
459+ 50-70% speedup on all pass traversals.
460+ 2 . ** Phase 1: ValidationPass micro-optimizations** — Added
461+ ` recursiveFindByTypeCache ` to Finder (mirrors existing
462+ ` findByTypeCache ` ). Added ` walkStatements[CV] ` helper for
463+ recursive statement walking. Replaced ` classifyHandlers `
464+ with single-pass mutable counter walk. Replaced
465+ ` validateSagaStep ` 4×recursiveFindByType with 2 walks
466+ using mutable Sets.
467+ 3 . ** Phase 2: ` validateStringQuick() ` ** — Added
468+ ` ValidationMode ` enum (Full/Quick). Quick mode skips
469+ ` checkStreaming() ` and ` classifyHandlers() ` in postProcess.
470+ Added ` quickValidationPasses ` to Pass companion.
471+ Exposed via RiddlLib trait, RiddlAPI JS facade, and
472+ TypeScript declarations.
473+ 4 . ** Phase 3: Incremental validation** — Created
474+ ` ContextFingerprint ` (FNV-1a 64-bit hashing of Context
475+ source spans, cross-platform). Created
476+ ` IncrementalValidator ` — stateful validator that caches
477+ messages per-Context, re-validates only changed Contexts.
478+ Conservative: falls back to full validation when >50%
479+ changed. Exposed via ` createIncrementalValidator() ` and
480+ ` validateIncremental() ` in RiddlLib/RiddlAPI/TypeScript.
481+
482+ ** Also fixed** : Pre-existing infix method warnings in
483+ PassTest.scala (` must be(x) ` → ` .must(be(x)) ` ), updated
484+ ` mutable.Stack.empty ` → ` ParentStack.empty ` in test code.
485+
486+ ** Test Results** : 800 tests across language (280), passes
487+ (270), riddlLib (12), commands (238) — all passing on JVM.
488+ Full ` sbt test ` (JVM + JS + Native) passes.
489+
490+ ** Files Created** :
491+ - ` passes/shared/.../ContextFingerprint.scala `
492+ - ` passes/shared/.../IncrementalValidator.scala `
493+
494+ ** Files Modified** :
495+ - ` language/shared/.../AST.scala ` — ParentStack class
496+ - ` language/shared/.../Finder.scala ` — recursiveFindByType
497+ cache
498+ - ` passes/shared/.../Pass.scala ` — quickValidationPasses
499+ - ` passes/shared/.../validate/ValidationPass.scala ` —
500+ ValidationMode, walkStatements, optimized classifyHandlers
501+ and validateSagaStep
502+ - ` passes/jvm-native/.../PassTest.scala ` — ParentStack.empty,
503+ infix fixes
504+ - ` riddlLib/shared/.../RiddlLib.scala ` — validateStringQuick,
505+ createIncrementalValidator, validateIncremental, doValidate
506+ refactor
507+ - ` riddlLib/js/.../RiddlAPI.scala ` — JS facades
508+ - ` riddlLib/js/types/index.d.ts ` — TypeScript declarations
509+
510+ ---
511+
425512### February 14, 2026 (RiddlResult ADT)
426513
427514** Focus** : Add cross-platform ` RiddlResult[T] ` result type to
@@ -1923,6 +2010,140 @@ Tool(
19232010
19242011---
19252012
2013+ ## Scala.js Validation Performance (from ossum.ai playground)
2014+
2015+ ** Filed:** 2026-02-16
2016+ ** Context:** The ossum.ai RIDDL Playground loads models from
2017+ riddl-models via ` .bast ` files, converts them to source with
2018+ ` root2RiddlSource() ` , and optionally validates with
2019+ ` validateString() ` . For the ` reactive-bbq ` model (8,105 lines /
2020+ 213KB after flattening), ` validateString() ` takes ** 168 seconds**
2021+ in the browser (Scala.js) vs seconds on JVM. This makes
2022+ interactive validation impractical for large models.
2023+
2024+ ### Benchmark (reactive-bbq, Chrome/V8)
2025+
2026+ | Operation | Time |
2027+ | -----------| ------|
2028+ | ` bast2FlatAST() ` | 57ms |
2029+ | ` root2RiddlSource() ` | 12ms |
2030+ | ` parseString() ` | 24ms |
2031+ | ` getTree() ` | 152,714ms |
2032+ | ` validateString() ` | 168,673ms |
2033+
2034+ ** Note:** ` getTree() ` runs only ` TreePass ` (not the full
2035+ validation pipeline), yet still takes 2.5 minutes. This is
2036+ unexpectedly slow and warrants separate investigation — the
2037+ tree pass should be lightweight.
2038+
2039+ ### Identified Bottlenecks in ValidationPass
2040+
2041+ #### 1. Double iteration in ` classifyHandlers() `
2042+
2043+ ** Location:** ` ValidationPass.scala ` ~ lines 1260-1299
2044+
2045+ For every handler, ` recursiveFindByType[Statement] ` collects
2046+ all statements, then the list is iterated ** twice** — once
2047+ with ` .count() ` for executable statements, once with
2048+ ` .count() ` for prompt statements. A single-pass accumulator
2049+ would halve this work.
2050+
2051+ ** Fix:** Replace two ` .count() ` calls with one ` .foreach `
2052+ that increments both counters:
2053+
2054+ ``` scala
2055+ var executableCount = 0
2056+ var promptCount = 0
2057+ allStatements.foreach {
2058+ case _ : TellStatement | _ : SendStatement |
2059+ _ : MorphStatement | _ : SetStatement |
2060+ _ : BecomeStatement | _ : ErrorStatement |
2061+ _ : CodeStatement =>
2062+ executableCount += 1
2063+ case _ : PromptStatement =>
2064+ promptCount += 1
2065+ case _ => ()
2066+ }
2067+ ```
2068+
2069+ ** Estimated impact:** ~ 15-20% of postProcess time
2070+
2071+ #### 2. Redundant ` symbols.parentsOf() ` in streaming
2072+
2073+ ** Location:** ` StreamingValidation.scala ` ~ line 44
2074+
2075+ ` symbols.parentsOf(connector) ` is called for every connector
2076+ without caching. Additionally, the reverse adjacency map is
2077+ built in a separate pass instead of simultaneously with the
2078+ forward adjacency, and two full BFS traversals run (sources→
2079+ sinks, then sinks→sources) when one bidirectional pass would
2080+ suffice.
2081+
2082+ ** Fix:** Cache ` parentsOf() ` results in a local map. Build
2083+ forward and reverse adjacency simultaneously.
2084+
2085+ ** Estimated impact:** ~ 10-15%
2086+
2087+ #### 3. Four tree walks per SagaStep
2088+
2089+ ** Location:** ` ValidationPass.scala ` ~ lines 976-1010
2090+
2091+ Each SagaStep creates two Finders (do/undo), walks each
2092+ ** twice** (once for ` TellStatement ` , once for
2093+ ` SendStatement ` ), creates intermediate ` Seq ` s, converts to
2094+ ` Set ` s. Could be 2 walks instead of 4 by collecting both
2095+ statement types in a single pass.
2096+
2097+ ** Estimated impact:** ~ 8-12%
2098+
2099+ #### 4. Finder cache is per-instance
2100+
2101+ ** Location:** ` Finder.scala ` ~ lines 27-64
2102+
2103+ Each ` Finder(container.contents) ` creates a fresh
2104+ ` findByTypeCache ` . Across hundreds of handlers creating new
2105+ Finders, no cache reuse occurs. A shared cache keyed by
2106+ container identity could help.
2107+
2108+ ### Micro-optimization Summary
2109+
2110+ These fixes sum to roughly ** 20-30% improvement** (~ 30-50s
2111+ off the 3-minute Scala.js runtime). Meaningful but doesn't
2112+ solve the fundamental 10-50x Scala.js vs JVM gap for
2113+ allocation-heavy compute.
2114+
2115+ ### Architectural Approaches (Higher Impact)
2116+
2117+ 1 . ** Pre-compute validation results into BAST** — Store
2118+ validation messages alongside ` .bast ` files at build time.
2119+ The playground would just display them with zero compute.
2120+ This is the highest-impact, lowest-risk change.
2121+
2122+ 2 . ** Incremental validation** — Only re-validate the changed
2123+ definition and its dependents, not the entire model. This
2124+ is how language servers (LSP) stay fast. High effort but
2125+ would make the playground truly interactive for any size
2126+ model.
2127+
2128+ 3 . ** ` validateStringLite() ` for JS** — A lighter validation
2129+ pass that skips expensive checks (streaming analysis, saga
2130+ compensation, handler completeness) that matter less in a
2131+ browser playground context. Medium effort, could bring
2132+ large-model validation under 30 seconds.
2133+
2134+ ### Current ossum.ai Workaround
2135+
2136+ The playground uses a tiered strategy:
2137+ - ` parseString() ` on main thread (~ 24ms) for every keystroke
2138+ - Sources under 10KB: auto-validate in Web Worker on
2139+ keystroke (debounced)
2140+ - Sources over 10KB: manual "Validate" button triggers Web
2141+ Worker; page stays responsive while validation runs in
2142+ background
2143+ - BAST-loaded models: skip validation entirely (pre-validated)
2144+
2145+ ---
2146+
19262147## Git Information
19272148
19282149** Branch** : ` development `
0 commit comments