Skip to content

WBS-6.2: property tests for sl-viewer corpus_paths, parquet source, settings - #425

Merged
KooshaPari merged 2 commits into
mainfrom
fix/viewer-fuzz-properties-20260807
Aug 7, 2026
Merged

WBS-6.2: property tests for sl-viewer corpus_paths, parquet source, settings#425
KooshaPari merged 2 commits into
mainfrom
fix/viewer-fuzz-properties-20260807

Conversation

@KooshaPari

@KooshaPari KooshaPari commented Aug 7, 2026

Copy link
Copy Markdown
Owner

User description

Summary

Adds integration-test property evidence for the three new modules introduced by PR #419:

  • corpus_paths::CorpusPathConfig round-trip and merging
  • parquet_source::ParquetCorpusSource list/load invariants
  • settings::Settings persistence round-trip

The properties live under crates/sl-viewer/tests/properties_viewer.rs and are gated via #[cfg(feature = "parquet")] where applicable so the parquet case only runs when the feature is enabled.

Properties

  1. corpus_paths_round_trip_preserves_all_paths — arbitrary 0..16 paths survive save -> load; also asserts the round-trip does not silently dedupe (user-picked paths are authoritative).
  2. corpus_paths_empty_predicate_agrees_with_fieldis_empty() and custom_paths.is_empty() agree for arbitrary inputs.
  3. parquet_list_matches_loaded_sessions — for any set of 1..12 rows bounded to ASCII printable bodies and a recognised role label, writing one parquet per unique session id and asking list() must return exactly those ids, and each load(id) must rehydrate every message body in write-order with a recognised role.
  4. settings_round_trip_preserves_all_fields — every (Theme, DefaultTab) pair (3 x 9 = 27 cases) survives save_to_path -> load_from_path equality.

Implementation notes

  • proptest is a workspace dev-dep declared in the root Cargo.toml; I re-declare it under sl-viewer/[dev-dependencies] so the integration test can use proptest::prelude::*. Version kept in sync.
  • The fixture writer is duplicated from parquet_source::test_fixture because that helper is gated on #[cfg(test)] mod test_fixture and is not reachable from an integration test. The schema literal in the test mirrors CLAUDE_SCHEMA (session_id, role, content, ts_ms, cwd, title) — the extra cwd / title columns are written as None since the property does not exercise metadata.

Validation

  • cargo test -p sl-viewer --test properties_viewer --features "desktop parquet" --locked4 passed.
  • cargo fmt -p sl-viewer — clean.
  • cargo clippy -p sl-viewer --test properties_viewer --features "desktop parquet" --locked -- -D warnings — clean.
  • cargo build --release -p sl-viewer --features desktop --locked — succeeds.

Out of scope

  • The pre-existing corpus_cta::tests::folder_picker_returns_none_in_headless_builds test fails in this headless test env because rfd cannot spawn a native dialog off the main thread. That test is unrelated to this change and was already failing before my edits.

WBS-6.2 row in docs/ops/WBS.md will flip from partial -> done in a follow-up commit once this lands.


CodeAnt-AI Description

Add property-based coverage for viewer path, parquet, and settings behavior

What Changed

  • Verifies custom corpus paths survive saving and loading without being removed or deduplicated
  • Verifies empty-path checks match the actual configured paths
  • Verifies parquet session listings contain exactly the available sessions, and loading preserves message count, content, and order
  • Verifies all supported themes and default tabs survive settings persistence

Impact

✅ Fewer viewer configuration regressions
✅ Reliable parquet session discovery and loading
✅ Preserved viewer preferences across restarts

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

WBS-6.2 evidence: integration tests under crates/sl-viewer/tests/
that exercise the PR #419 surfaces with proptest.

* corpus_paths: round-trip preserves all paths, is_empty() agrees
  with custom_paths.is_empty().
* parquet source: list() returns exactly the unique session ids
  written, load(id) rehydrates messages in write-order with a
  recognised role. Fixture writer lives inside the integration test
  because the unit-test helper is gated on cfg(test).
* settings: every (Theme, DefaultTab) pair survives save -> load.

proptest is added to sl-viewer's [dev-dependencies] (mirrors the
workspace root) so the integration test can reach proptest::prelude.
Copilot AI lite review requested due to automatic review settings August 7, 2026 22:55
@codeant-ai

codeant-ai Bot commented Aug 7, 2026

Copy link
Copy Markdown

🤖 CodeAnt AI — Review Status

Status Commit Started (UTC) Finished (UTC)
✅ Reviewed your PR 97c91a2 Aug 07, 2026 · 22:55 22:57

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@codeant-ai

codeant-ai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@KooshaPari, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 53 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e0cb3ade-e9d8-4c29-861b-b3f04924b6be

📥 Commits

Reviewing files that changed from the base of the PR and between c0076df and d7231af.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • crates/sl-viewer/Cargo.toml
  • crates/sl-viewer/tests/properties_viewer.rs
  • docs/ops/TRACEABILITY.json
  • docs/ops/WBS.md

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codeant-ai codeant-ai Bot added the size:L This PR changes 100-499 lines, ignoring generated files label Aug 7, 2026
Comment on lines +213 to +215
for (i, (id, rows)) in by_id.iter().enumerate() {
let path = dir.path().join(format!("sessions-{i:04}.parquet"));
write_fixture(&path, rows);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The fixture generation creates one file for each unique session ID, so the property never exercises the loader's duplicate-session-across-files behavior. A regression in resolving or hydrating an ID claimed by multiple parquet files would pass this suite. Generate multiple files that intentionally share at least one session ID and assert the documented resolution behavior. [incomplete implementation]

Severity Level: Major ⚠️
- ⚠️ Duplicate parquet exports lack property coverage.
- ⚠️ Session hydration depends on untested file resolution.
- ❌ A resolution regression can display incomplete session data.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** crates/sl-viewer/tests/properties_viewer.rs
**Line:** 213:215
**Comment:**
	*Incomplete Implementation: The fixture generation creates one file for each unique session ID, so the property never exercises the loader's duplicate-session-across-files behavior. A regression in resolving or hydrating an ID claimed by multiple parquet files would pass this suite. Generate multiple files that intentionally share at least one session ID and assert the documented resolution behavior.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment on lines +244 to +256
prop_assert_eq!(
session.messages.len(),
expected_rows.len(),
"session {} message count mismatch",
id,
);
for (actual, expected) in session.messages.iter().zip(expected_rows.iter()) {
prop_assert_eq!(
actual.content.clone(),
expected.content.clone(),
"session {} message body mismatch",
id,
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The generated rows include ts_ms, and the fixture writes that column, but the property never compares the loaded timestamps with the expected values. A timestamp extraction, conversion, or row-alignment regression can therefore pass despite the fixture exercising timestamp data. Assert each loaded message's ts_ms against the corresponding expected.ts_ms. [incomplete implementation]

Severity Level: Major ⚠️
- ⚠️ Viewer activity ordering can use missing timestamps.
- ⚠️ Corpus timeline metadata is not property-checked.
- ⚠️ Timestamp conversion regressions remain undetected.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** crates/sl-viewer/tests/properties_viewer.rs
**Line:** 244:256
**Comment:**
	*Incomplete Implementation: The generated rows include `ts_ms`, and the fixture writes that column, but the property never compares the loaded timestamps with the expected values. A timestamp extraction, conversion, or row-alignment regression can therefore pass despite the fixture exercising timestamp data. Assert each loaded message's `ts_ms` against the corresponding `expected.ts_ms`.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment on lines +259 to +273
let recognised = matches!(
actual.role,
session_ledger::domain::session::Role::User
| session_ledger::domain::session::Role::Assistant
| session_ledger::domain::session::Role::System
| session_ledger::domain::session::Role::Tool
| session_ledger::domain::session::Role::Subagent
);
prop_assert!(
recognised,
"session {} produced unrecognised role {:?}",
id,
actual.role,
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The test accepts any recognised role regardless of the generated input role. A loader regression that maps every row to Role::User would still pass, including for assistant, system, tool, and subagent inputs. Compare actual.role with the expected role derived from expected.role instead of only checking that it is one of the enum variants. [incomplete implementation]

Severity Level: Major ⚠️
- ❌ Viewer transcripts can misattribute message authors.
- ⚠️ Subagent and tool distinctions are not verified.
- ⚠️ Role-sensitive downstream processing can receive wrong data.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** crates/sl-viewer/tests/properties_viewer.rs
**Line:** 259:273
**Comment:**
	*Incomplete Implementation: The test accepts any recognised role regardless of the generated input role. A loader regression that maps every row to `Role::User` would still pass, including for assistant, system, tool, and subagent inputs. Compare `actual.role` with the expected role derived from `expected.role` instead of only checking that it is one of the enum variants.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Append crates/sl-viewer/tests/properties_viewer.rs to the WBS-6.2
evidence set so the audit gap matrix reflects PR #425. Status stays
partial — fuzzing cadence, full loom/shuttle, and perf-budget gates
remain.
@KooshaPari
KooshaPari merged commit 8bba1e6 into main Aug 7, 2026
79 of 95 checks passed
@KooshaPari
KooshaPari deleted the fix/viewer-fuzz-properties-20260807 branch August 7, 2026 23:06
Err(session_ledger::ports::PortError::NotFound(missing)) => {
panic!("list() returned {} but load() reported NotFound({})", id, missing);
}
Err(err) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Silent backend error swallowing weakens property coverage

The Err(err) branch at lines 278-282 silently accepts any error other than NotFound, printing only to stderr. A loader regression that returns PermissionDenied or another backend error for every session would still pass this property test because no assertions are executed for errored sessions.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

roles.push(Some(row.role.as_str()));
contents.push(Some(row.content.as_str()));
ts_values.push(row.ts_ms);
cwds.push(None);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: cwd and title columns never populated with non-None values

cwds.push(None) and titles.push(None) at lines 156-157 mean the fixture always writes NULL for these schema columns. The property never exercises non-None values, so a regression in how the loader handles populated cwd or title fields would go undetected.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Aug 7, 2026

Copy link
Copy Markdown

Code Review Summary

Status: 2 Issues Found | Recommendation: Address before merge

Overview

Severity Count
WARNING 1
SUGGESTION 1
Issue Details (click to expand)

WARNING

File Line Issue
crates/sl-viewer/tests/properties_viewer.rs 278 Silent backend error swallowing weakens property coverage

SUGGESTION

File Line Issue
crates/sl-viewer/tests/properties_viewer.rs 156 cwd and title columns never populated with non-None values
Files Reviewed (2 files)
  • crates/sl-viewer/tests/properties_viewer.rs - 2 issues
  • crates/sl-viewer/Cargo.toml - no issues

Fix these issues in Kilo Cloud


Reviewed by step-3.7-flash · Input: 53.2K · Output: 14.7K · Cached: 348.4K

KooshaPari pushed a commit that referenced this pull request Aug 7, 2026
WBS-6.2 (follow-up to #425): extends the sl-viewer property surface
to cover the theme and daemon_url modules that PR #419 also touched.

Theme (crates/sl-viewer/src/theme.rs):
* Theme JSON round-trip preserves the variant and the lowercase
  serialisation contract documented on the type.
* Theme::default() is always System (regression guard for the
  #[default] attribute).
* ThemeColors::for_theme is total: every variant maps to the same
  palette as the matching dark()/light() constructor; System falls
  back to dark (design contract).
* ThemeColors gains PartialEq + Eq derives so the property test can
  compare palettes structurally.

daemon_url (crates/sl-viewer/src/daemon_url.rs):
* daemon_api_url(path) is exactly base + '/' + path-with-leading-
  slash-stripped, regardless of leading or trailing slashes on the
  path argument.
* daemon_api_url is idempotent w.r.t. leading slash stripping
  (api/x and /api/x produce the same URL).
* daemon_host_display never starts with http(s):// and never ends
  with '/'.

WBS-6.2 evidence list and CHANGELOG Unreleased reflect the new
property file (properties_viewer_theme_url.rs).
KooshaPari added a commit that referenced this pull request Aug 8, 2026
* Add property tests for sl-viewer theme + daemon_url

WBS-6.2 (follow-up to #425): extends the sl-viewer property surface
to cover the theme and daemon_url modules that PR #419 also touched.

Theme (crates/sl-viewer/src/theme.rs):
* Theme JSON round-trip preserves the variant and the lowercase
  serialisation contract documented on the type.
* Theme::default() is always System (regression guard for the
  #[default] attribute).
* ThemeColors::for_theme is total: every variant maps to the same
  palette as the matching dark()/light() constructor; System falls
  back to dark (design contract).
* ThemeColors gains PartialEq + Eq derives so the property test can
  compare palettes structurally.

daemon_url (crates/sl-viewer/src/daemon_url.rs):
* daemon_api_url(path) is exactly base + '/' + path-with-leading-
  slash-stripped, regardless of leading or trailing slashes on the
  path argument.
* daemon_api_url is idempotent w.r.t. leading slash stripping
  (api/x and /api/x produce the same URL).
* daemon_host_display never starts with http(s):// and never ends
  with '/'.

WBS-6.2 evidence list and CHANGELOG Unreleased reflect the new
property file (properties_viewer_theme_url.rs).

* Add IntentState JSON round-trip + terminal-invariant properties

WBS-6.2 (follow-up to #427): tests/properties.rs gains two proptest
properties for the IntentState FSM's serde contract:

* intent_state_json_round_trip_preserves_variant — every IntentState
  variant serialises to its lowercase kebab-case Debug name and
  round-trips back to the same variant. Catches drift in the
  `#[serde(rename_all = "kebab-case")]` attribute.
* intent_state_terminal_invariant_holds_across_serde — `is_terminal`
  agrees with the serde representation: serialising + deserialising
  Pruned must not silently turn it into something non-terminal.

---------

Co-authored-by: SessionLedger Bot <team@sessionledger.local>
KooshaPari added a commit that referenced this pull request Aug 8, 2026
…rift fixes (#429)

* Fix pre-existing CI drift (fuzz + rootless + clippy) + viewer unfinished_tab properties

Four bounded cleanups observed while CI was running on #425/#427:

1) scripts/fuzz-cadence-check.ps1: the "PR smoke stays short" anchor
   was checking ci.yml for max_total_time=10, but the 10s fuzz-smoke
   job no longer lives there (consolidated into fuzz-blocking.yml at
   30s). Re-pointed the check at the actual PR fuzz budget:
   fuzz-blocking.yml with max_total_time=30.

2) scripts/rootless-nonet-check.ps1 + .github/workflows/ci.yml: the
   script expected ci.yml to cross-reference rootless-nonet.yml, but
   that anchor had drifted out of ci.yml while security.yml retained
   it. Added a small rootless-nonet-policy smoke job to ci.yml (matches
   the docs/ops/sandbox-boundary.md C04 L40 contract that 'ci.yml
   cross-reference | done') and tightened the script's regex so
   continue-on-error detection can't bleed across jobs.

3) clippy -D warnings under --all-targets --all-features:
   * tests/alloc_profile.rs: panic-in-if-then — folded the Windows
     panic into an explicit else branch.
   * tests/replay_breadth.rs: 6 unnecessary trailing commas in assert!
     macros.

4) crates/sl-viewer/tests/properties_viewer_unfinished_tab.rs: 6
   proptest properties covering reason_label (non-empty, injective) and
   unfinished_items (deterministic, descending by ts_ms, tiebreak by
   session_id asc, length-monotonic). Adds to WBS-6.2 viewer property
   surface alongside #425/#427.

WBS-6.2 evidence list and CHANGELOG Unreleased reflect the new
property file.

* WBS-6.2: link new viewer unfinished_tab surface + CI drift fixes

WBS-6.2 evidence list and TRACEABILITY.json gain the new
properties_viewer_unfinished_tab.rs and reference #428 for the
unfinished-tab properties + CI drift fixups. Status stays partial
(fuzzing cadence, full loom/shuttle, perf-budget gates remain).

---------

Co-authored-by: SessionLedger Bot <team@sessionledger.local>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants