Conversation
ColdBox merges an app's moduleSettings over a module's defaults with a
shallow append, so the documented minimal config
`visualizer = { enabled = true }` replaced the whole default visualizer
struct and dropped metricsStore/datasourceName. RuleEventBus.onDIComplete
then threw on the missing key (its catch block re-read the same key, so
the error escaped), the bus was never built, and every RuleBook run failed
with a null publish() call.
- ModuleConfig: single visualizerDefaults() source; onLoad() deep-merges
the defaults under whatever the app supplied.
- RuleEventBus, Visualizer handler, SQLiteMetricsStore: tolerate missing
visualizer keys (defence in depth); resolveMetricsStore() no longer reads
settings inside its own catch block.
- Add VisualizerSettingsSpec.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Yc5MDcm3tyR2yqPWs9RpgX
- Count consecutive failures in the store; after visualizer.circuitBreakerThreshold (5) open a circuit for circuitBreakerCooldownSeconds (60). recordEvent is a cheap no-op while open; one error is logged on open and one info on recovery. Insert failures no longer escape to RuleEventBus (which logged an error per evaluation). - ensureSchema remembers a failed state and recordEvent re-attempts it before inserting. - Add CREATE INDEX IF NOT EXISTS on rulebox_events (rulebookName, ruleName, id). - Add retention: visualizer.retentionDays (30) and maxStoredEvents (100000), pruned with a DELETE every 500 inserts (0 disables each). - Make the SQL runner and clock injectable so breaker/retention logic is unit-tested without a database; add DB-backed specs behind the existing skip guard. - Fix stale class headers that called SQLite the default store. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Yc5MDcm3tyR2yqPWs9RpgX
ModuleConfig is now the single place that sets up and checks the visualizer settings: onLoad() deep-merges the app's values over visualizerDefaults(), then throws RuleBox.InvalidSettingException for a value of the wrong shape. The handler, RuleEventBus and SQLiteMetricsStore go back to reading the settings directly, with no scattered fallbacks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Yc5MDcm3tyR2yqPWs9RpgX
5 of 6 tasks
retentionDays, maxStoredEvents, circuitBreakerThreshold and circuitBreakerCooldownSeconds get their defaults and whole-number validation in ModuleConfig. SQLiteMetricsStore reads them directly; numericSetting() is gone. Specs build store settings from the module's completed settings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Yc5MDcm3tyR2yqPWs9RpgX
The defaults do not depend on instance state. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Yc5MDcm3tyR2yqPWs9RpgX
The defaults do not depend on instance state. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Yc5MDcm3tyR2yqPWs9RpgX
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Yc5MDcm3tyR2yqPWs9RpgX
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Yc5MDcm3tyR2yqPWs9RpgX
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Yc5MDcm3tyR2yqPWs9RpgX
A module is loaded once, so the defaults are a static struct instead of a function. configure() copies it, and onLoad() fills the keys an app left out with append( static.VISUALIZER_DEFAULTS, false ), so the static is never written to. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Yc5MDcm3tyR2yqPWs9RpgX
…ge' into fix/sqlite-store-hardening # Conflicts: # ModuleConfig.bx
5 tasks done
…re-hardening # Conflicts: # ModuleConfig.bx # test-harness/tests/specs/VisualizerSettingsSpec.bx
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Hardens the opt-in
SQLiteMetricsStore(reviewer findings):RuleEventBus.metricsStoreFailednever tripped; every publish then ran a failing INSERT andRuleEventBuslogged an error each time. The store now tracks consecutive failures itself. AftercircuitBreakerThreshold(default 5) it opens a circuit forcircuitBreakerCooldownSeconds(default 60): one error is logged when it opens,recordEventis a cheap no-op while open, then a single trial insert is allowed; on success one info line is logged and the circuit closes. A failed trial stays open quietly. A failedensureSchemais remembered (schemaReady=false) and the nextrecordEventre-attempts the DDL before inserting.CREATE INDEX IF NOT EXISTS idx_rulebox_events_rule ON rulebox_events (rulebookName, ruleName, id)backsqueryRuleMetrics(called once per rule on the chain page). New settingsvisualizer.retentionDays(30) andvisualizer.maxStoredEvents(100000), 0 disables each, enforced by a DELETE (id-range / timestamp) once every 500 successful inserts, never on every insert. A failing prune is logged as a warning and does not trip the breaker.recordEventstill performs a synchronous INSERT on the request thread. Left as is on purpose; noted in the docs and class header.Settings are read defensively in the store (missing/invalid values fall back to defaults), so it works whether or not the settings deep-merge fix has landed. New defaults were added inside the existing
visualizer = {...}block ofModuleConfig.bx. Also fixed the stale class headers inSQLiteMetricsStore.bx/IMetricsStore.bxthat called SQLite "the default" (the default isInMemoryMetricsStore@rulebox), and added a short docs subsection todocs/guides/visualizer.md.Behavior change for maintainers:
recordEventno longer throws on a DB failure (the store absorbs it), soRuleEventBus's per-event error log no longer fires for this store.Testing: the SQL runner and clock are injectable (
setSqlRunner,setClock,setPruneEvery) so breaker, schema-retry and prune-cadence logic is covered without a database; DB-backed specs use the existing skip guard. Verified locally on BoxLang 1.18.0 with a real bx-sqlite datasource using a throwaway BDD shim (the TestBox runner does not work under miniserver): against the old store 13 of 15 new no-DB specs and all 3 SQLite specs fail; with the change all 23 specs in the file pass. Repro of the original problem: 20recordEventcalls against a missing datasource threw 20 times (so the bus logged 20 errors); now 0 throws and exactly 1 error log. CI runs the real suite.Issues
Reviewer findings on
SQLiteMetricsStoreahead of 2.0.0 (no separate GitHub issue filed).Type of change
Checklist
Noticed, not fixed
queryEvents, summaries,reset) still callqueryExecutedirectly and are not behind the circuit breaker (they run on admin page requests, not per evaluation).run()in a spec are hoisted into one scope, so twodescribeblocks cannot define the same helper name (I used distinct names).timestampwithout an index (bounded bymaxStoredEvents; if you disable that limit on a very high-volume table, consider a timestamp index).🤖 Generated with Claude Code
https://claude.ai/code/session_01Yc5MDcm3tyR2yqPWs9RpgX
Generated by Claude Code