Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ Follows [Keep a Changelog](https://keepachangelog.com/); versioning is [SemVer](

- sl-viewer bundle-diff property surface (WBS-6.2 #434): `crates/sl-viewer/tests/properties_viewer_bundle_diff.rs` adds 10 proptest properties — `diff_fields` returns the documented field set in stable order, is reflexive on `a == a`, idempotent on `a == a.clone()`, value-flipped symmetric (`diff_fields(b, a)` swaps `value_a`/`value_b` while `differs` matches), `differs` matches `value_a != value_b`, and `Option<String>` fields render the em-dash fallback when both sides are `None`. `OkfBundle::from_bundle` properties pin the reduction: `message_count` matches slice count, `has_acceptance`/`has_contract` reflect kind presence, `token_count` falls back to 0 when no Intent slice carries numeric `user_turn_count`, `source_id` carries through unchanged.

- sl-viewer tokens SSOT property surface (WBS-6.2 #450): `crates/sl-viewer/tests/properties_viewer_tokens.rs` adds 10 proptest properties — every `lab_coat::*` hex is a well-formed `#RRGGBB` (7-char lowercase ASCII hex), is non-empty, is pairwise distinct across the documented 16-constant set, and appears in `TOKENS_CSS`. Every `REQUIRED_CSS_VARS` entry starts with `--`, is non-empty, is unique across the documented set, and appears in `TOKENS_CSS`. `VIEWER_COLOR_SCHEME` declares both `:root` and `:root[data-theme="dark"]` selectors and uses the `color-scheme` property exactly twice.

- Wave-44 plan landed: `WAVE44_SCOPE.md` + `docs/ops/WAVE44_PERT.md` enumerate 6 close-out lanes (3 machine, 3 human-gated) for the 6 unpaid residuals from Wave-43 (396/402 → 402/402 target). Theme: stack-stability closure + i18n migration + eval coverage + supply-chain signing.
- Wave-44 reaudit (Wave-44-D): `audit/SCORECARD.md` refresh at commit `13c974f7` (machine-w44-reaudit); `docs/ops/TRACEABILITY.json` overall_audit wave=Wave-44 commit=13c974f7 (conservative hold at 396/402); `docs/ops/GAP_QA_MATRIX.md` C00 + C08 + PLAN-W8-B rows reflect Wave-44 closure (#368 W44-B6 corpus / #372 W44-B1 loom / #373 PERT correction). 2 of 3 machine lanes shipped 2026-07-24; remaining 6 raw pts across C04 L36 / C08 L76 / C11 L110.

Expand Down
186 changes: 186 additions & 0 deletions crates/sl-viewer/tests/properties_viewer_tokens.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
//! Property evidence for sl-viewer's `tokens` module — the design-token
//! single source of truth for the Lab-Coat / viewer color palette.
//!
//! The unit tests in `tokens.rs` pin specific values and exercise
//! `ThemeColors::light() / dark()` against the `lab_coat::*` mirror.
//! These properties pin the broader SSOT invariants:
//!
//! * Every `lab_coat::*` hex constant is a well-formed `#RRGGBB` string
//! (7 chars, leading `#`, then 6 hex digits).
//! * All Lab-Coat hex constants are pairwise distinct — no two share
//! the same value (catches drift where a constant is silently
//! re-aliased to another).
//! * Every Lab-Coat hex appears somewhere in `TOKENS_CSS` so the
//! Rust mirror and the CSS SSOT stay in sync.
//! * Every `REQUIRED_CSS_VARS` entry starts with `--`, has no
//! duplicates, and appears as a substring of `TOKENS_CSS`.
//! * `VIEWER_COLOR_SCHEME` mentions both `:root[data-theme="dark"]`
//! and `:root` so the dark-mode flip is wired.
Comment on lines +8 to +18

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Make the lab_coat::* inventory authoritative.

lab_coat_hex_list() is a manually maintained test-only list. Every index strategy uses this list, and no assertion compares it with the declarations in crates/sl-viewer/src/tokens.rs. A new lab_coat::* constant can be omitted from the list and still pass every property, despite the exhaustiveness claim. Export a canonical array from tokens.rs, or generate the constants and array from one definition.

Also applies to: 44-69

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/sl-viewer/tests/properties_viewer_tokens.rs` around lines 8 - 18, Make
the Lab-Coat inventory authoritative by defining a canonical exported array
alongside the lab_coat constants in tokens.rs, or generate both from one shared
definition. Update lab_coat_hex_list() and all related property checks in
properties_viewer_tokens.rs to consume that canonical inventory, ensuring newly
added lab_coat::* constants cannot be omitted from validation.

//!
//! proptest is added to `sl-viewer/[dev-dependencies]` (mirroring the
//! workspace root); see PR #425 for the initial wiring.

use std::collections::HashSet;

use proptest::prelude::*;
use sl_viewer::tokens::{
lab_coat, REQUIRED_CSS_VARS, TOKENS_CSS, VIEWER_COLOR_SCHEME,
};

// ── strategies ──────────────────────────────────────────────────────────────

/// Generate indices into `REQUIRED_CSS_VARS` for prop_any tests.
fn required_var_index_strategy() -> impl Strategy<Value = usize> {
0..REQUIRED_CSS_VARS.len()
}

/// Generate indices into the `lab_coat::*` constants via the documented
/// hex list. We use the indices, then look up the value, so we exercise
/// the actual const definitions (not duplicates).
fn lab_coat_hex_indices_strategy() -> impl Strategy<Value = usize> {
0..lab_coat_hex_list().len()
}

/// The full list of `lab_coat::*` hex constants in stable declaration
/// order. We compute this once via a small reflection-on-source approach:
/// every `pub const` in `lab_coat::*` whose value is a `&'static str`
/// starting with `#`. Since we can't introspect Rust modules at runtime,
/// we hard-code the list (mirroring `tokens.rs`). The constants are
/// public — any new addition requires also extending this list, which
/// the `proptest` exhaustiveness check below will catch.
fn lab_coat_hex_list() -> &'static [&'static str] {
&[
lab_coat::LAB_WHITE,
lab_coat::SLATE,
lab_coat::COBALT,
lab_coat::COBALT_ON_DARK,
lab_coat::ORANGE,
lab_coat::TEAL,
lab_coat::TEAL_ON_DARK,
lab_coat::BG_DARK,
lab_coat::SURFACE_LIGHT,
lab_coat::BORDER_LIGHT,
lab_coat::BORDER_DARK,
lab_coat::TEXT_DARK,
lab_coat::TEXT_MUTED_LIGHT,
lab_coat::TEXT_MUTED_DARK,
lab_coat::DANGER_LIGHT,
lab_coat::DANGER_DARK,
]
}

// ── lab_coat hex well-formedness ────────────────────────────────────────────

proptest! {
/// Property: every `lab_coat::*` hex constant is a 7-char string
/// starting with `#`, followed by 6 lowercase hex digits. Catches
/// drift where someone hand-types an `rgb(…)` literal or a 3-digit
/// hex.
#[test]
fn lab_coat_hex_well_formed(i in lab_coat_hex_indices_strategy()) {
let hex = lab_coat_hex_list()[i];
prop_assert_eq!(hex.len(), 7, "hex {:?} must be 7 chars", hex);
prop_assert!(hex.starts_with('#'), "hex {:?} must start with '#'", hex);
let body = &hex[1..];
prop_assert!(
body.chars().all(|c| c.is_ascii_hexdigit() && !c.is_ascii_uppercase()),
"hex {:?} must be lowercase ASCII hex digits only",
hex,
);
}

/// Property: every `lab_coat::*` hex constant is non-empty (sanity
/// check — the well-formedness check above is the stricter version).
#[test]
fn lab_coat_hex_nonempty(i in lab_coat_hex_indices_strategy()) {
let hex = lab_coat_hex_list()[i];
prop_assert!(!hex.is_empty(), "lab_coat hex at index {} is empty", i);
}

/// Property: all `lab_coat::*` hex constants are pairwise distinct.
/// No silent re-aliasing.
#[test]
fn lab_coat_hexes_distinct(_i in 0u8..4) {
let list = lab_coat_hex_list();
let set: HashSet<_> = list.iter().collect();
prop_assert_eq!(set.len(), list.len());
}

/// Property: every `lab_coat::*` hex appears as a substring of
/// `TOKENS_CSS` so the Rust mirror and the CSS SSOT stay in sync.
/// If a constant is added without updating the CSS, this fails.
#[test]
fn lab_coat_hex_in_tokens_css(i in lab_coat_hex_indices_strategy()) {
let hex = lab_coat_hex_list()[i];
prop_assert!(
TOKENS_CSS.contains(hex),
"TOKENS_CSS missing lab_coat hex {:?}",
hex,
);
}
Comment on lines +113 to +120

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Check CSS declarations and Rust-to-CSS pairs.

TOKENS_CSS.contains(hex) only proves that a value occurs somewhere. It passes when a value is assigned to the wrong variable or appears in a comment. TOKENS_CSS.contains(var) can also match a comment or a use without a declaration. Assert each (CSS variable, Rust value) pair on a declaration line, and assert each required variable is actually declared. The existing check in crates/sl-viewer/src/tokens.rs Lines 91-109 shows the required pairwise pattern.

Also applies to: 154-160

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/sl-viewer/tests/properties_viewer_tokens.rs` around lines 113 - 120,
The property tests lab_coat_hex_in_tokens_css and the corresponding test at
lines 154-160 must validate Rust-to-CSS pairs on actual declaration lines rather
than using broad TOKENS_CSS.contains checks. Reuse the pairwise
declaration-matching pattern from tokens.rs and assert every required CSS
variable is declared with its expected value, excluding comments and unrelated
uses.

}

// ── REQUIRED_CSS_VARS invariants ────────────────────────────────────────────

proptest! {
/// Property: every `REQUIRED_CSS_VARS` entry starts with `--` (CSS
/// custom property convention).
#[test]
fn required_css_var_starts_with_double_dash(i in required_var_index_strategy()) {
let var = REQUIRED_CSS_VARS[i];
prop_assert!(var.starts_with("--"), "var {:?} must start with '--'", var);
}

/// Property: `REQUIRED_CSS_VARS` has no duplicates.
#[test]
fn required_css_vars_unique(_i in 0u8..4) {
let list = REQUIRED_CSS_VARS;
let set: HashSet<_> = list.iter().collect();
prop_assert_eq!(set.len(), list.len());
}

/// Property: every `REQUIRED_CSS_VARS` entry is non-empty (no
/// empty `--` strings accidentally added).
#[test]
fn required_css_var_nonempty(i in required_var_index_strategy()) {
let var = REQUIRED_CSS_VARS[i];
prop_assert!(!var.is_empty(), "REQUIRED_CSS_VARS[{}] is empty", i);
Comment on lines +126 to +147

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the empty-name check effective.

The prefix check and !var.is_empty() both accept the literal "--". This does not reject the empty custom-property suffix described by the test comment. Check that at least one character follows --.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/sl-viewer/tests/properties_viewer_tokens.rs` around lines 126 - 147,
Update required_css_var_nonempty to validate that each REQUIRED_CSS_VARS entry
contains at least one character after the "--" prefix, rather than only checking
that the full string is non-empty. Preserve the existing indexed property-test
structure and diagnostic context.

}

/// Property: every `REQUIRED_CSS_VARS` entry appears as a
/// substring of `TOKENS_CSS`. Catches drift where a var name is
/// added to the list without updating the CSS file.
#[test]
fn required_css_var_in_tokens_css(i in required_var_index_strategy()) {
let var = REQUIRED_CSS_VARS[i];
prop_assert!(
TOKENS_CSS.contains(var),
"TOKENS_CSS missing required CSS var {:?}",
var,
);
}
}

// ── VIEWER_COLOR_SCHEME invariants ──────────────────────────────────────────

proptest! {
/// Property: `VIEWER_COLOR_SCHEME` declares both the default
/// (`:root`) and dark (`:root[data-theme="dark"]`) selectors so the
/// viewer's color-scheme flip is wired.
#[test]
fn viewer_color_scheme_declares_both_selectors(_i in 0u8..4) {
prop_assert!(VIEWER_COLOR_SCHEME.contains(":root"));
prop_assert!(VIEWER_COLOR_SCHEME.contains("[data-theme=\"dark\"]"));
}

/// Property: `VIEWER_COLOR_SCHEME` declares `color-scheme` for
/// both modes (the W3C CSS prop that triggers browser scrollbar
/// and form-control color flips).
#[test]
fn viewer_color_scheme_declares_color_scheme_property(_i in 0u8..4) {
prop_assert!(VIEWER_COLOR_SCHEME.contains("color-scheme"));
// Both modes must set the property.
let occurrences = VIEWER_COLOR_SCHEME.matches("color-scheme").count();
prop_assert_eq!(occurrences, 2);
}
Comment on lines +167 to +185

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Assert complete selector-specific color-scheme declarations.

The two contains calls can pass when :root and [data-theme="dark"] occur in separate selectors. They do not prove that :root[data-theme="dark"] exists. Counting color-scheme substrings also does not verify two declarations or the required values for each selector. Assert the complete default and dark blocks, or parse the CSS and verify the selector-specific declarations.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/sl-viewer/tests/properties_viewer_tokens.rs` around lines 167 - 185,
Strengthen the tests around viewer_color_scheme_declares_both_selectors and
viewer_color_scheme_declares_color_scheme_property by verifying the complete
:root and :root[data-theme="dark"] selector blocks, including a color-scheme
declaration with the required value in each. Do not rely on independent contains
checks or substring counts; assert the selector-specific CSS structure directly
or parse the stylesheet to validate both declarations.

}
1 change: 1 addition & 0 deletions docs/ops/TRACEABILITY.json
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@
"crates/sl-viewer/tests/properties_viewer_mock_data.rs",
"crates/sl-viewer/tests/properties_viewer_cli_help.rs",
"crates/sl-viewer/tests/properties_viewer_corpus_cta.rs",
"crates/sl-viewer/tests/properties_viewer_tokens.rs",
"fuzz/fuzz_targets/okf_roundtrip.rs",
"fuzz/fuzz_targets/jsonl_ingest.rs",
".github/workflows/ci.yml",
Expand Down
2 changes: 1 addition & 1 deletion docs/ops/WBS.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ without a new audit.
| WBS-4.2 | P4 FTS recall via context-mode and explicit TUI decision | partial | human | `docs/DESIGN.md` §3, §7; `crates/sl-viewer/` | DESIGN P4 residual; C00, C11 |
| WBS-5.1 | P5 deterministic dedup merge and crash/lost-work recovery E2E | done | machine | `src/domain/merge.rs`; `src/domain/worklog.rs`; `tests/merge_recovery.rs` | FR-011; T-024, T-035; C03 |
| WBS-6.1 | P6 85% coverage gate and deterministic golden corpus | done | machine | `.github/workflows/ci.yml`; `tests/okf_golden.rs`; `tests/fixtures/okf/` | T-037, T-038; C01, C08 |
| WBS-6.2 | P6 property tests, fuzzing, race checks, and enforced performance budgets | partial | machine | `tests/properties.rs`; `crates/sl-viewer/tests/properties_viewer.rs`; `crates/sl-viewer/tests/properties_viewer_theme_url.rs`; `crates/sl-viewer/tests/properties_viewer_unfinished_tab.rs`; `crates/sl-viewer/tests/properties_viewer_timeline.rs`; `crates/sl-viewer/tests/properties_viewer_search_memory.rs`; `crates/sl-viewer/tests/properties_viewer_history.rs`; `crates/sl-viewer/tests/properties_viewer_web_exports.rs`; `crates/sl-viewer/tests/properties_viewer_bundle_detail.rs`; `crates/sl-viewer/tests/properties_viewer_bundle_diff.rs`; `crates/sl-viewer/tests/properties_viewer_mock_data.rs`; `crates/sl-viewer/tests/properties_viewer_cli_help.rs`; `crates/sl-viewer/tests/properties_viewer_corpus_cta.rs`; `fuzz/fuzz_targets/okf_roundtrip.rs`; `fuzz/fuzz_targets/jsonl_ingest.rs`; `.github/workflows/ci.yml`; `.github/workflows/bench-gate.yml`; `docs/ops/perf-baseline.json`; `scripts/bench-gate.ps1`; `benches/pipeline.rs`; `tests/loom_model.rs` | DESIGN P6 residual; C00 L6-L8; C07 L66-L68; C08 L74; perf-budget enforced Wave-26 #223; p95 latency enforced Wave-30 #256; FSM properties Wave-31 #261; soft loom Wave-31 #264; viewer corpus_paths/parquet/settings properties #425; viewer theme + daemon_url properties #427; viewer unfinished_tab properties + fuzz/rootless CI drift fixes #428; viewer bundle_diff + timeline properties + web_exports/hmetic-pin cleanups #432; viewer bundle_diff properties #434; viewer search/memory properties #435; viewer history_tab properties #444; viewer web_exports properties #437; viewer bundle_list + detail_pane properties #436; viewer mock_data fixture properties #451; viewer cli_help / command_palette properties #452; viewer corpus_cta constants properties #453; full loom/shuttle unpaid |
| WBS-6.2 | P6 property tests, fuzzing, race checks, and enforced performance budgets | partial | machine | `tests/properties.rs`; `crates/sl-viewer/tests/properties_viewer.rs`; `crates/sl-viewer/tests/properties_viewer_theme_url.rs`; `crates/sl-viewer/tests/properties_viewer_unfinished_tab.rs`; `crates/sl-viewer/tests/properties_viewer_timeline.rs`; `crates/sl-viewer/tests/properties_viewer_search_memory.rs`; `crates/sl-viewer/tests/properties_viewer_history.rs`; `crates/sl-viewer/tests/properties_viewer_web_exports.rs`; `crates/sl-viewer/tests/properties_viewer_bundle_detail.rs`; `crates/sl-viewer/tests/properties_viewer_bundle_diff.rs`; `crates/sl-viewer/tests/properties_viewer_mock_data.rs`; `crates/sl-viewer/tests/properties_viewer_cli_help.rs`; `crates/sl-viewer/tests/properties_viewer_corpus_cta.rs`; `crates/sl-viewer/tests/properties_viewer_tokens.rs`; `fuzz/fuzz_targets/okf_roundtrip.rs`; `fuzz/fuzz_targets/jsonl_ingest.rs`; `.github/workflows/ci.yml`; `.github/workflows/bench-gate.yml`; `docs/ops/perf-baseline.json`; `scripts/bench-gate.ps1`; `benches/pipeline.rs`; `tests/loom_model.rs` | DESIGN P6 residual; C00 L6-L8; C07 L66-L68; C08 L74; perf-budget enforced Wave-26 #223; p95 latency enforced Wave-30 #256; FSM properties Wave-31 #261; soft loom Wave-31 #264; viewer corpus_paths/parquet/settings properties #425; viewer theme + daemon_url properties #427; viewer unfinished_tab properties + fuzz/rootless CI drift fixes #428; viewer bundle_diff + timeline properties + web_exports/hmetic-pin cleanups #432; viewer bundle_diff properties #434; viewer search/memory properties #435; viewer history_tab properties #444; viewer web_exports properties #437; viewer bundle_list + detail_pane properties #436; viewer mock_data fixture properties #451; viewer cli_help / command_palette properties #452; viewer corpus_cta constants properties #453; viewer tokens SSOT properties #450; full loom/shuttle unpaid |

## audit-v38 waves

Expand Down
Loading