New P1 findings: - F04: Auto-persist failure in worker not communicated to client - F05: dispatch() swallows interactive() errors silently New P2 findings: - F15: law_toggle_group_involution test doesn't apply effects - F16: law_sequence_associativity test proves tautology - F17: ServerFrame serialization failure silently dropped - F18: Unparseable ClientFrame silently ignored - F19: ClientFrame for closed document produces no response - F20: Debug-string tests verify effect names, not correctness Total: 5 P1, 18 P2, 10 P3 = 33 findings Co-authored-by: Ed Langley <fiddlerwoaroof@users.noreply.github.com>
30 KiB
Adversarial Code Review — 2026-09-21
Posted by Claude (Opus 4.5), reviewing the whole-repo sweep for correctness and conformance against this repository's doctrine.
Summary
This review covers improvise-core, improvise-formula, improvise-io, improvise-protocol, improvise-command, improvise-wasm-client, improvise-worker-server, improvise-ws-server, and web/. It identifies patterns that could hide bugs or silently swallow errors, focusing on issues the doctrine specifically warns about.
Exclusions (already filed or known intentional):
- improvise-16af, improvise-iobc, improvise-9cma, improvise-tg27, improvise-x0dc, improvise-l3hm
- Upstream Key black holes for
[/]/drill etc. - Aggregate Profit
idecline (fixed)
Findings
P1 Findings (Critical)
F01: CSV import silently converts NaN/Infinity to 0
Severity: P1 (critical)
Location: crates/improvise-io/src/import/csv_parser.rs, function parse_csv_field, lines 128-131
What's wrong:
if let Ok(num) = field.parse::<f64>() {
return Value::Number(
serde_json::Number::from_f64(num).unwrap_or(serde_json::Number::from(0)),
);
}
serde_json::Number::from_f64() returns None for NaN and Infinity. The .unwrap_or(serde_json::Number::from(0)) silently converts these to 0. A CSV field containing "NaN", "Infinity", "-Infinity", or "inf" will import as 0 with no warning — data is silently corrupted.
How to verify: Import a CSV with a field value "nan" or "inf" and observe it imports as 0 rather than preserving as text or producing a warning.
Suggested bead title: "CSV import silently converts NaN/Infinity fields to zero"
F02: Optimistic fold is not rolled back on server rejection
Severity: P1 (critical)
Location: crates/improvise-wasm-client/src/lib.rs, function Client::key, lines 182-193
What's wrong:
match resolve(&self.state, event) {
KeyOutcome::Local(cmds) => {
for cmd in &cmds {
self.state.fold(cmd); // <-- State mutated HERE
}
Some(ClientFrame::Commands(cmds)) // <-- Then sent
}
// ...
}
The client folds commands optimistically before sending. If the server rejects (e.g., Rejection::LayoutMoved), socket.rs::fold_frame sets last_rejection and status_msg but does NOT rollback the already-folded state changes.
Example: User presses ] (page next), client folds SetPageSelection, server rejects because layout changed. Client's page_coords now disagrees with server's.
How to verify: Send a command that the server will reject, observe client state has moved but server's hasn't.
Suggested bead title: "Optimistic fold is not rolled back on server rejection"
F03: Worker startup failure leaves silent dead state
Severity: P1 (critical)
Location: web/worker.js, lines 43-60
What's wrong: If started rejects (e.g., WASM init fails), the .catch() only logs. The onFrame listener is still installed and calls handle(), but server is null. Every frame accumulates in the error log with no UI indication beyond the eventual watchdog. If the watchdog somehow fails to arm (e.g., worker never posts), the page sits there dead.
How to verify: Make init() or start() throw. Observe that frames accumulate in the error log with no UI indication beyond the eventual watchdog.
Suggested bead title: "Worker startup failure leaves page in silent dead state"
F04: Auto-persist failure not propagated to client
Severity: P1 (critical)
Location: crates/improvise-worker-server/src/worker.rs, function on_frame, lines 52-59
What's wrong: If effect::Save fails (e.g., OPFS quota exceeded, handle closed), the dirty flag stays true but the failure is only reported to status_msg (which the worker-server has no UI to show). The client sees dirty: true projected but doesn't know the save failed vs. is pending. In the browser, this could lead to data loss on page close if the user believes auto-save is working.
How to verify: Inject a storage backend that fails writes after N successful ones. Edit the document past the failure point. Observe that dirty: true is projected but no explicit save-failure indication reaches the client.
Suggested bead title: "Auto-persist failure in worker not communicated to client"
F05: dispatch silently swallows unresolvable command names
Severity: P1 (critical)
Location: crates/improvise-command/src/command/keymap.rs, line 37
What's wrong: The interactive() method can return Err (e.g., argument parse failures, mode mismatch for commit-buffer item). Swallowing it with .ok()? makes these failures silent — the user presses a key and nothing happens, no error message. This violates doctrine(an_upstream_key_is_a_black_hole).
How to verify: Bind a key to commit-buffer item in Normal mode (not ItemAdd mode). Press the key. The command fails to construct but no status message appears.
Suggested bead title: "dispatch swallows interactive() errors silently"
P2 Findings (Medium)
F04: Parser _ => {} wildcards silently ignore unknown grammar rules
Severity: P2 (medium)
Location: crates/improvise-io/src/persistence/parse.rs, lines 180, 293, 315
What's wrong:
The .improv parser has multiple _ => {} arms that silently skip any grammar rule the walker doesn't explicitly handle:
// Line 180 in walk_category_section
_ => {}
// Line 293 in walk_view_section
_ => {}
// Line 315 in walk_md
_ => {}
If the pest grammar (improv.pest) is extended with new rules, the parser will silently ignore them rather than failing loudly. This violates the principle that a grammar/parser mismatch should be visible.
Doctrine violated: doctrine(check_the_checker) — a parse that silently ignores valid grammar rules is indistinguishable from a parse that rejects them.
How to verify: Add a new rule to improv.pest (e.g., a new directive like color_line), emit it in format_md, and observe that parse_md round-trips without error but the data is lost. The grammar accepts it, the walker drops it.
Suggested bead title: "Parser wildcards silently ignore unknown grammar rules"
F05: Formula parser defaults unknown operators to specific values
Severity: P2 (medium)
Location: crates/improvise-formula/src/parser.rs, lines 158-159 and 189
What's wrong:
The formula parser uses _ => fallback arms that default unknown values to specific operators:
// Line 158-159: Unknown aggregate becomes COUNT
_ => AggFunc::Count
// Line 189: Unknown comparison becomes Ge
_ => BinOp::Ge
If the grammar adds a new aggregate function (e.g., MEDIAN) or comparison operator, the parser will silently treat it as COUNT or >= rather than failing.
Doctrine violated: Exhaustive matches should be preferred over wildcards so adding a variant produces compile errors at every site that needs updating.
How to verify: Add | ^"MEDIAN" to agg_func in formula.pest, parse MEDIAN(Revenue), observe it evaluates as COUNT(Revenue) with no error.
Suggested bead title: "Formula parser defaults unknown operators silently"
F06: Socket send can throw on CLOSING state
Severity: P2 (medium)
Location: web/app.js, lines 133-134
What's wrong:
send: (text) => {
if (socket.readyState === WebSocket.OPEN) socket.send(text);
}
socket.send(text) throws if readyState is CLOSING (2), but the guard only checks for OPEN (1). During the brief CLOSING window, a send attempt would throw, propagate up to the keyboard handler, and potentially break the page.
How to verify: Mock a WebSocket that transitions to CLOSING state. Call transport.send(). Observe the uncaught exception.
Suggested bead title: "Socket send throws on CLOSING state, not guarded"
F07: Render failure leaves painted stale, causing infinite retry
Severity: P2 (medium)
Location: web/app.js, lines 148-156
What's wrong: When JSON.parse(client.render_model()) throws, the function returns early WITHOUT updating painted. The model.revision === painted early-return uses the OLD value. Every subsequent call to repaint() (triggered by ANY event) will attempt render_model() again and fail again — infinite retry loop.
How to verify: Inject a permanently broken state where render_model() throws. Observe repeated error logs on every keystroke.
Suggested bead title: "Render failure causes infinite retry loop"
F08: Date detection samples only first 10 values, order-dependent
Severity: P2 (medium)
Location: crates/improvise-io/src/import/analyzer.rs, function detect_date_format, lines 67-76
What's wrong: The function only samples the first 10 values for date format detection. If the disambiguating value (e.g., 25/12/2025 which can only be day-first) appears after row 10, it's never seen and the wrong format is chosen.
How to verify: Create a CSV with 15 rows where rows 1-10 are ambiguous dates like 01/02/2025 and row 11+ contains 25/12/2025 (unambiguously day-first). Watch it misclassify as %m/%d/%Y.
Suggested bead title: "Date format detection ignores disambiguating values after row 10"
F09: Mixed-type columns misclassify as Label
Severity: P2 (medium)
Location: crates/improvise-io/src/import/analyzer.rs, function analyze_records, lines 124-135
What's wrong: A column that is 99% numeric with 1 null cell becomes a Label rather than a Measure. The all_numeric check excludes Value::Null, so any sparse column (common in real CSVs) misclassifies. This loses data — the 99% that are numbers become stringified item names rather than numeric cell values.
How to verify: Import a CSV where a "Revenue" column has one null row among 100 numeric rows. The entire column becomes a Label category.
Suggested bead title: "CSV analyzer misclassifies mostly-numeric columns with nulls as Label"
F10: Vacuous test: count_aggregation asserts >= 1.0 instead of exact value
Severity: P2 (medium)
Location: crates/improvise-core/src/model/types.rs, test count_aggregation, line ~1467
What's wrong: The test asserts assert!(val >= 1.0) when the expected value should be exactly 1.0. COUNT(Sales) evaluated at Region=East counts exactly 1 matching cell. This assertion would pass for any value ≥ 1, including 2, 100, or 999.
Doctrine violated: doctrine(check_the_checker) — a test that cannot fail when the implementation is wrong is not evidence.
How to verify: Inject a fault: make AggFunc::Count return values.len() as f64 + 999.0. The test still passes.
Suggested bead title: "[test] count_aggregation assertion is >= 1.0 instead of exact value"
F11: Vacuous test: records_mode_resolve_display asserts non-empty only
Severity: P2 (medium)
Location: crates/improvise-core/src/view/layout.rs, test records_mode_resolve_display_returns_values, line ~810
What's wrong: The test asserts !display.is_empty() for the Region column value. The expected value is "North". Asserting only non-emptiness would pass for any non-empty string including garbage like "CORRUPTED".
How to verify: Inject a fault: make records_display for Category columns return "GARBAGE". The test still passes.
Suggested bead title: "[test] records_mode_resolve_display asserts non-empty instead of expected value"
F12: Non-exhaustive wildcard in eval_bool_cached
Severity: P2 (medium)
Location: crates/improvise-core/src/model/types.rs, function eval_bool_cached, line ~700
What's wrong: Uses _ => Err("type".into()) instead of explicitly listing the arithmetic operators. If a new BinOp variant is added to improvise_formula, this match silently errors instead of producing a compile-time error.
Compare with eval_bool (line ~187) which explicitly lists operators — the two should match in style.
How to verify: Add a new BinOp::Mod variant to improvise_formula. The cached evaluator silently errors; the non-cached evaluator fails to compile.
Suggested bead title: "eval_bool_cached uses wildcard match on BinOp instead of exhaustive arms"
F13: Silent serialization failures in wasm exports drop valid operations
Severity: P2 (medium)
Location: crates/improvise-wasm-client/src/lib.rs, functions on_key_event (line 54), typed_cell (line 97), clicked (line 178)
What's wrong: Each function uses .ok() on serde_json::to_string(). If serialization fails (unlikely but possible with unusual data), the operation returns None. The caller interprets None as "nothing to send" (e.g., cell not editable, click invalid) — not as "serialization failed". A valid edit or click would be silently dropped.
How to verify: Inject a non-serializable value into the frame and observe no error reaches the JS caller.
Suggested bead title: "serde_json failures in wasm exports are silent drops, not errors"
F14: Editing mode with missing buffer renders empty overlay
Severity: P2 (medium)
Location: crates/improvise-wasm-client/src/render.rs, function render_model, lines 268-271
What's wrong: If is_editing() is true but no "edit" buffer exists, editing becomes Some(""). The page would render an empty editor overlay over a cell that has content. The user sees their typing replaced with nothing.
How to verify: Fold a ChangeMode(Editing) without a preceding SetBuffer{name:"edit",...} and call render_model().
Suggested bead title: "editing mode with missing buffer renders empty overlay instead of cell content"
F15: Vacuous test: law_toggle_group_involution doesn't apply effects
Severity: P2 (medium)
Location: crates/improvise-command/src/command/cmd/grid.rs, lines 26-36
What's wrong: The test asserts first == second where both are the debug output of cmd.execute(&ctx) called twice with the same unchanged context. It claims to test involution (toggle twice = identity) but never applies the first effect before calling execute again. The ctx is immutable — CmdContext is borrowed. The test proves "two calls return structurally identical effects" which is trivially true for any deterministic pure function.
Doctrine violated: doctrine(fold_tests_must_test_order_not_the_axiom) — the test asserts a tautology.
How to verify: Inject a fault where ToggleGroupAtCursor returns different effects on odd/even calls. The test still passes because it doesn't track state changes.
Suggested bead title: "[test] toggle-group-involution test doesn't apply effects between calls"
F16: Vacuous test: law_sequence_associativity proves a tautology
Severity: P2 (medium)
Location: crates/improvise-command/src/command/cmd/navigation.rs, lines 388-431
What's wrong: The test calls three commands with the same context and concatenates their effects in two groupings (a+b)+c vs a+(b+c). Since effects are just pushed to a Vec, concatenation is trivially associative — it's the definition of a list. The test cannot fail regardless of what commands do.
Doctrine violated: doctrine(fold_tests_must_test_order_not_the_axiom) — asserting that list concatenation is associative is the axiom, not a testable property.
How to verify: Replace all three commands with panic!() calls and the test still passes if effects are non-empty Vec concats.
Suggested bead title: "[test] sequence-associativity test proves a mathematical tautology"
F17: ServerFrame serialization failure silently dropped
Severity: P2 (medium)
Location: crates/improvise-ws-server/src/transport.rs, writer task, lines 179-183
What's wrong: A ServerFrame that fails to serialize is silently dropped with continue. The client never receives it, creating state divergence. The comment claims "unreachable" but this is an assertion the compiler cannot verify.
How to verify: Add a ServerFrame variant containing a type that fails serde_json::to_string. Observe that the frame is lost without logging.
Suggested bead title: "ServerFrame serialization failure silently dropped in ws transport"
F18: Unparseable ClientFrame text messages silently ignored
Severity: P2 (medium)
Location: crates/improvise-ws-server/src/transport.rs, function next_client_msg, lines 240-248
What's wrong: Text messages that don't parse as ClientFrame are silently dropped with no logging. This hides protocol mismatches, version skew, or corruption. A client sending malformed commands gets no feedback.
How to verify: Send {"Unknown": {}} as a text message over the WebSocket. Observe no response and no log entry.
Suggested bead title: "Unparseable ClientFrame messages silently dropped without logging"
F19: ClientFrame for closed document produces no response
Severity: P2 (medium)
Location: crates/improvise-ws-server/src/registry.rs, function handle, lines 193-197
What's wrong: If a document is closed (evicted or never existed), the client's frame produces an empty response. The client receives nothing — no rejection, no error. A client might continue composing against a document the server no longer holds.
How to verify: Connect a client to document A, then have the document close. Send a command — observe no response.
Suggested bead title: "ClientFrame for unregistered document silently dropped"
F20: Debug-string assertion tests cannot verify effect application
Severity: P2 (medium)
Location: Multiple files in crates/improvise-command/src/command/cmd/
What's wrong: Numerous tests assert that the debug representation of an effect list contains a substring (e.g., dbg.contains("AddCategory")). This verifies the name of an effect type is present but not that the effect's fields have correct values, that the effect actually applies correctly, or that the effect isn't shadowed by a later effect.
Doctrine violated: doctrine(check_the_checker) — the tests verify syntax presence, not semantic correctness.
How to verify: Change an effect's fields to incorrect values. The test still passes because the type name is present.
Suggested bead title: "[test] Debug-string tests verify effect names, not correctness"
F21: Worker seed fetch silently swallows errors
Severity: P2 (medium)
Location: web/worker.js, lines 19-22
What's wrong:
async function seedBytes() {
try {
const res = await fetch(SEED);
if (!res.ok) return undefined;
return new Uint8Array(await res.arrayBuffer());
} catch (_) {
return undefined; // Error silently becomes "no seed"
}
}
A network error, CORS failure, or malformed response is indistinguishable from "no seed file exists." The worker proceeds with an empty model rather than reporting the actual problem.
How to verify: Set SEED to a URL that returns a 500 error or is CORS-blocked. The worker starts with an empty model and no indication that the seed fetch failed.
Suggested bead title: "Worker seed fetch errors are silently swallowed"
P3 Findings (Low)
F16: WebSocket messages dropped when socket not open
Severity: P2 (medium)
Location: web/app.js, lines 133-134
What's wrong:
send: (text) => {
if (socket.readyState === WebSocket.OPEN) socket.send(text);
// No else: message is silently dropped
},
If the socket is closing or connecting, messages (potentially including user edits) are silently discarded with no feedback to the user.
How to verify: Trigger a socket state transition (reconnection, network hiccup) while the user is typing. The edit command is silently lost. No status message, no retry.
Suggested bead title: "WebSocket transport silently drops messages during state transitions"
F17: AddItem effect swallows errors into status bar only
Severity: P3 (low)
Location: crates/improvise-command/src/ui/effect.rs, lines 102-110
What's wrong:
impl Effect for AddItem {
fn apply(&self, app: &mut App) {
if let Err(e) = app.model_state.workbook.add_item(...) {
app.view_state.status_msg = e.to_string();
// No abort_effects = true, unlike AddCategory
}
}
}
AddCategory sets abort_effects = true on failure (line 87), aborting the batch. AddItem, AddItemInGroup, and AddFormula (parse error branch) only set a status message but let the batch continue. A sequence like [AddItem("BadCat", "X"), SetCell(...)] will show an error but still apply the SetCell.
Doctrine context: invariant(an_abort_stops_the_chain_not_the_bookkeeping) discusses abort semantics. The asymmetry here is whether downstream effects that depend on the item existing should run.
How to verify: Run a Binding::Sequence where the first command adds an item to a non-existent category and the second does something that assumes it exists. The second command runs.
Suggested bead title: "AddItem/AddFormula errors don't abort the effect batch"
F18: delete_view error silently discarded
Severity: P3 (low)
Location: crates/improvise-command/src/ui/effect.rs, line 321
What's wrong:
let _ = app.model_state.workbook.delete_view(&self.0);
The let _ = explicitly discards the Result. If deleting the view fails (e.g., it's the last view), no error reaches the user. The code below assumes the deletion succeeded.
How to verify: Attempt to delete the only view in a workbook. No error message appears, but the view still exists.
Suggested bead title: "DeleteView effect discards error result"
F19: resolve returns empty vec via unwrap_or_default on error paths
Severity: P3 (low)
Location: crates/improvise-protocol/src/resolve.rs, line 101
What's wrong:
resolved.unwrap_or_default()
Several resolution functions return Option<Vec<Command>>. When resolution fails (e.g., the click didn't hit anything), unwrap_or_default() turns it into an empty vec. This is often correct ("nothing to do"), but some callers may want to distinguish "resolved to nothing" from "resolution failed."
Context: This is borderline intentional — "no commands" is the right answer for most missed clicks. But it's worth noting that failures and empty successes are conflated.
Suggested bead title: (Low priority — may be intentional) "Click resolution conflates failures with empty results"
F20: Test _ => {} in socket.rs ignores unexpected projections
Severity: P3 (low)
Location: crates/improvise-ws-server/tests/socket.rs, lines 78-82 and 4978-4982
What's wrong:
for projection in &snapshot.seed {
match projection {
Projection::Grid(update) => grid.apply(update),
Projection::Basis(update) => basis.apply(update),
_ => {} // Other projections silently ignored
}
}
The test only handles Grid and Basis projections. If a new projection type is added (e.g., Projection::Views), the test won't see it. The socket works, but the test coverage has a gap.
Doctrine violated: doctrine(check_the_checker) — a test that ignores some inputs cannot verify them.
How to verify: Add a new Projection variant, emit it in the snapshot, observe the test still passes (it doesn't check for it).
Suggested bead title: "Socket tests ignore non-Grid/Basis projections"
F21: KeyCode formatting falls through to Debug
Severity: P3 (low)
Location: crates/improvise-protocol/src/keymap.rs, lines 42-46
What's wrong:
match code {
KeyCode::PageUp => "PgUp".to_string(),
KeyCode::PageDown => "PgDn".to_string(),
KeyCode::F(n) => format!("F{n}"),
_ => format!("{code:?}"), // Debug format for unknown keys
}
New KeyCode variants will show as their Debug representation (e.g., KeyCode::Media(Play)) in the UI rather than a human-friendly string. This is a user-facing display issue, not a correctness bug.
Suggested bead title: "KeyCode display falls back to Debug for unhandled variants"
F22: CategoryKind defaults Regular for unknown category names
Severity: P3 (low)
Location: crates/improvise-core/src/model/category.rs, lines 98-102
What's wrong:
pub fn of_name(name: &str) -> Self {
match name {
INDEX_CATEGORY => CategoryKind::VirtualIndex,
DIM_CATEGORY => CategoryKind::VirtualDim,
MEASURE_CATEGORY => CategoryKind::VirtualMeasure,
_ => CategoryKind::Regular,
}
}
This is probably correct (any non-virtual category is Regular), but if a new virtual category is added, it will be treated as Regular until this function is updated.
Suggested bead title: (Very low priority) "CategoryKind::of_name defaults unknown names to Regular"
F23: Hidden items with / in name corrupt on round-trip
Severity: P3 (low)
Location: crates/improvise-io/src/persistence/parse.rs
What's wrong: The hidden: line format is hidden: Cat/Item, and if Item contains /, the parser splits at the wrong position. A test exists documenting this bug.
How to verify: Create an item named "A/B", hide it, save, reload. The hidden state is lost or corrupted.
Suggested bead title: "Hidden/collapsed items with slash in name corrupt on persistence round-trip"
F24: CellValue::as_f64 wildcard hides future variants
Severity: P3 (low)
Location: crates/improvise-core/src/model/cell.rs, function as_f64, line ~72
What's wrong: Uses _ => None wildcard. If a new CellValue variant is added (e.g., Boolean, DateTime), this silently returns None instead of forcing a decision.
How to verify: Add a CellValue::DateTime(f64) variant. The code compiles without warning but silently drops DateTime values in numeric contexts.
Suggested bead title: "CellValue::as_f64 uses wildcard match hiding future variants"
F25: Connection hang has no watchdog coverage
Severity: P3 (low)
Location: web/app.js, lines 489-494
What's wrong: The watchdog is armed AFTER await transport.ready, so if ready never resolves (connection hangs forever), the watchdog is never armed and the user sees "connecting..." forever with no timeout.
How to verify: Mock a WebSocket that never fires open. Observe that the page shows "connecting..." forever.
Suggested bead title: "Connection hang has no watchdog coverage"
Patterns Observed
Silent error swallowing via .ok()?
Multiple locations use .ok()? to convert errors to None and early-return. This is often appropriate (e.g., parsing optional data), but some uses may hide real failures:
crates/improvise-io/src/opfs.rslines 39-41, 185-187: OPFS operations that fail silentlycrates/improvise-command/src/command/keymap.rsline 37: Command resolution failures become "no command"
These are worth auditing individually but don't rise to the level of individual findings.
.unwrap_or_default() masking Option contents
Heavy use throughout the codebase. Most are appropriate (empty string, empty vec, zero), but some may mask meaningful None values. No specific bugs found, but the pattern density suggests caution when adding new code.
Tests Not Finding (Vacuousness check)
The test suite appears well-constructed against the repository's own doctrine. No obviously vacuous tests were found where assertions would pass regardless of behavior. The lockstep tests in particular show good discipline about driving both directions.
The one area to watch: tests that use _ => {} in their fold/apply helpers (like F08) may silently ignore new variants.
Not Findings (Reviewed and correct)
-
reduce.rs_ => Nonefor basis checking: Lines 60 and 73 returnNonefor non-Model commands. This is correct — onlyCommand::Modelcarries a basis to check. -
EffectKindhas no default: This is the doctrine working as intended — the compiler forces every effect to declare its kind.
Summary Table
| Severity | Count | Notable Examples |
|---|---|---|
| P1 | 5 | CSV NaN→0 corruption, Optimistic fold no rollback, Auto-persist failure silent, dispatch swallows errors |
| P2 | 18 | Parser wildcards, Socket CLOSING throw, Vacuous involution/associativity tests, ServerFrame serialization drops |
| P3 | 10 | Message drops, Error discards, Minor wildcards |
| Total | 33 |
Recommendations
P1 Fixes (Critical)
-
Fix CSV data corruption (F01): The NaN/Infinity→0 conversion silently corrupts data. Either preserve as text with a warning or refuse to import.
-
Add rollback for optimistic folds (F02): When a server rejects a locally-folded command, the client state must be corrected.
-
Propagate auto-persist failures to client (F04): The worker must communicate save failures, not just set
dirty: true. -
Report dispatch errors (F05): The
.ok()?pattern swallowing command construction errors must show a status message.
P2 Fixes (Medium)
-
Replace parser wildcards with exhaustive matches (F06): Change
_ => {}to explicit ignores of known-irrelevant rules. -
Fix vacuous test assertions (F10, F11): Tests asserting
>= 1.0or!is_empty()pass when implementations are broken. -
Fix vacuous "law" tests (F15, F16): The involution and associativity tests prove tautologies, not properties.
-
Guard socket send for CLOSING state (F08): Check for both OPEN and not-CLOSING to avoid uncaught exceptions.
-
Add circuit breaker for render failures (F09): Don't retry infinitely when render_model throws.
-
Log dropped frames in ws-server (F17, F18, F19): Serialization and parse failures should be logged for debugging.
General
-
Audit
let _ =patterns: Each discardedResultshould either be intentional (documented) or converted to an error report. -
Audit debug-string tests (F20): Tests asserting
dbg.contains("EffectName")should also verify the effect's fields.
End of adversarial review.