diff --git a/CHANGELOG.md b/CHANGELOG.md index 5845721..3f3a7ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,22 @@ Versioning: [SemVer 2.0](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +## [3.2.3] — 2026-07-15 + +Fix: **the v3.2.2 program-points cap was per-function only**, so scry-on-scry's +~800 functions still produced a ~10 MB `self-analysis.html` (800 × 20 rows). + +### Fixed — scry-viz + +- Program points now render a **cheap one-row summary for every function**, and a + detailed per-point table only for the first `FUNCS_WITH_DETAIL_CAP` (12) + functions — so the page stays well under 1 MB regardless of function count. +- The page-size test now synthesizes points across **many functions** (500 × 30), + not just many points in one function, so this regression is caught. +- Corrected an inaccurate cap note: the capped per-point rows are scry's library + `AnalysisResult` output, not the `guidance.json` feed (which carries advisories + + trap verdicts, not per-point invariants). + ## [3.2.2] — 2026-07-15 Headline: **make the dashboard readable.** A four-persona review of the deployed diff --git a/Cargo.lock b/Cargo.lock index 809118e..19f329a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1834,7 +1834,7 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "scry-host-tests" -version = "3.2.2" +version = "3.2.3" dependencies = [ "anyhow", "jsonschema", @@ -1851,18 +1851,18 @@ dependencies = [ [[package]] name = "scry-sai-analyzer" -version = "3.2.2" +version = "3.2.3" dependencies = [ "scry-sai-core", ] [[package]] name = "scry-sai-bits" -version = "3.2.2" +version = "3.2.3" [[package]] name = "scry-sai-core" -version = "3.2.2" +version = "3.2.3" dependencies = [ "scry-sai-bits", "scry-sai-float", @@ -1880,19 +1880,19 @@ dependencies = [ [[package]] name = "scry-sai-float" -version = "3.2.2" +version = "3.2.3" [[package]] name = "scry-sai-handle" -version = "3.2.2" +version = "3.2.3" [[package]] name = "scry-sai-interval" -version = "3.2.2" +version = "3.2.3" [[package]] name = "scry-sai-lattice" -version = "3.2.2" +version = "3.2.3" dependencies = [ "bitflags", "scry-sai-octagon", @@ -1901,34 +1901,34 @@ dependencies = [ [[package]] name = "scry-sai-octagon" -version = "3.2.2" +version = "3.2.3" [[package]] name = "scry-sai-pentagon" -version = "3.2.2" +version = "3.2.3" [[package]] name = "scry-sai-poly" -version = "3.2.2" +version = "3.2.3" [[package]] name = "scry-sai-provenance" -version = "3.2.2" +version = "3.2.3" [[package]] name = "scry-sai-segment" -version = "3.2.2" +version = "3.2.3" dependencies = [ "scry-sai-interval", ] [[package]] name = "scry-sai-taint" -version = "3.2.2" +version = "3.2.3" [[package]] name = "scry-sai-viz" -version = "3.2.2" +version = "3.2.3" dependencies = [ "cpp_demangle 0.5.1", "rustc-demangle", diff --git a/Cargo.toml b/Cargo.toml index 302c2a3..7d386ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,7 +69,7 @@ default-members = [ # on crates.io matches the release artifacts. The crates.io publish workflow # asserts the pushed `v*` tag equals this version, so a release bump must move # both in lockstep (and the internal path-dep `version = "..."` fields below). -version = "3.2.2" +version = "3.2.3" edition = "2024" license = "MIT OR Apache-2.0" repository = "https://github.com/pulseengine/scry" diff --git a/crates/scry-analyze-core/Cargo.toml b/crates/scry-analyze-core/Cargo.toml index e6d88dc..e31bfac 100644 --- a/crates/scry-analyze-core/Cargo.toml +++ b/crates/scry-analyze-core/Cargo.toml @@ -31,7 +31,7 @@ path = "src/lib.rs" # Path deps carry `version` so `cargo publish` rewrites them to the crates.io # coordinate (crates.io rejects path-only deps). The version equals the # workspace version and must be bumped in lockstep with it. -scry-sai-interval = { path = "../scry-interval", version = "3.2.2" } +scry-sai-interval = { path = "../scry-interval", version = "3.2.3" } # Step 2 (DD-012): the analyze body + helpers moved here. wasmparser parses # the input Wasm Core Model module; sha2 digests the module bytes for @@ -43,44 +43,44 @@ sha2 = { workspace = true } # Security-label (taint) lattice for the noninterference analysis (FEAT-009) # and the pure meld<->scry provenance boundary crate (FEAT-002 / DD-002). -scry-sai-taint = { path = "../scry-taint", version = "3.2.2" } -scry-sai-provenance = { path = "../scry-provenance", version = "3.2.2" } +scry-sai-taint = { path = "../scry-taint", version = "3.2.3" } +scry-sai-provenance = { path = "../scry-provenance", version = "3.2.3" } # Octagon relational domain (FEAT-016 slice-2b-ii): carried alongside the # intervals through the structured-CFG fixpoint so a loop counter bounded by a # VARIABLE relation (`i < n`) stays bounded where the interval domain alone # widens it to ⊤. Same pure `#![no_std]` dual-compile crate as scry-interval. -scry-sai-octagon = { path = "../scry-octagon", version = "3.2.2" } +scry-sai-octagon = { path = "../scry-octagon", version = "3.2.3" } # Known-bits × interval-guarded congruence reduced product (FEAT-037 / DD-017): # an additive bit/alignment/stride companion computed in a straight-line-sound # pass, surfaced library-only on `AnalysisResult.bit_facts`. Same pure # `#![no_std]` dual-compile crate as the other domains. -scry-sai-bits = { path = "../scry-bits", version = "3.2.2" } +scry-sai-bits = { path = "../scry-bits", version = "3.2.3" } # Pentagons weakly-relational domain (FEAT-044 / AC-014): intervals + strict # `x < y` facts, the cheap relational layer behind sound out-of-bounds-trap # detection (FEAT-046). An additive guard-recording pass surfaces proven # strict relations library-only on `AnalysisResult.pentagon_facts`. Same pure # `#![no_std]` dual-compile crate as the other domains. -scry-sai-pentagon = { path = "../scry-pentagon", version = "3.2.2" } +scry-sai-pentagon = { path = "../scry-pentagon", version = "3.2.3" } # IEEE-754 float-interval domain (FEAT-047 / AC-022): sound f32/f64 abstraction # with NaN/±inf tracking + round-to-nearest-aware widening. An additive # straight-line pass surfaces sound float intervals library-only on # `AnalysisResult.float_facts`. Same pure `#![no_std]` dual-compile crate. -scry-sai-float = { path = "../scry-float", version = "3.2.2" } +scry-sai-float = { path = "../scry-float", version = "3.2.3" } # Affine Component-Model handle-state lattice (FEAT-049 / MF-007): tracks # own/borrow resource-handle state to flag use-after-drop / double-drop. A # straight-line pass over the canonical-ABI `[resource-drop]` call sites # surfaces findings library-only on `AnalysisResult.handle_findings`. -scry-sai-handle = { path = "../scry-handle", version = "3.2.2" } +scry-sai-handle = { path = "../scry-handle", version = "3.2.3" } # FEAT-058: the linear-memory segmentation domain (content-sensitive memory). # The interpreter tracks per-offset interval content for i32 loads/stores # instead of degrading every load to ⊤. -scry-sai-segment = { path = "../scry-segment", version = "3.2.2" } +scry-sai-segment = { path = "../scry-segment", version = "3.2.3" } [dev-dependencies] # Test-only (the crate is otherwise dep-light + no_std): assemble the .wat diff --git a/crates/scry-analyze-core/src/lib.rs b/crates/scry-analyze-core/src/lib.rs index 2fe9229..17e49df 100644 --- a/crates/scry-analyze-core/src/lib.rs +++ b/crates/scry-analyze-core/src/lib.rs @@ -856,7 +856,7 @@ mod domain { scry_taint::join(a, b) } } -const SCRY_VERSION: &str = "3.2.2"; +const SCRY_VERSION: &str = "3.2.3"; const INVARIANT_SCHEMA_URL: &str = "https://pulseengine.eu/scry-invariants/v1"; /// Default Wasm linear-memory page size (64 KiB). diff --git a/crates/scry-segment/Cargo.toml b/crates/scry-segment/Cargo.toml index d9c5ae2..ad41efd 100644 --- a/crates/scry-segment/Cargo.toml +++ b/crates/scry-segment/Cargo.toml @@ -20,4 +20,4 @@ path = "src/lib.rs" # The per-segment content domain. Path dep carries `version` so `cargo publish` # rewrites it to the crates.io coordinate; the version equals the workspace # version and is bumped in lockstep. -scry-sai-interval = { path = "../scry-interval", version = "3.2.2" } +scry-sai-interval = { path = "../scry-interval", version = "3.2.3" } diff --git a/crates/scry-viz/Cargo.toml b/crates/scry-viz/Cargo.toml index 84c6a7c..e63b32b 100644 --- a/crates/scry-viz/Cargo.toml +++ b/crates/scry-viz/Cargo.toml @@ -22,7 +22,7 @@ path = "src/main.rs" # The only dependency: the published analyzer library. scry-viz is a plain # `std` host tool, so it can read the `AnalysisResult` plain-Rust types and # render them — no WIT, no component, no wasmtime. -scry-sai-core = { path = "../scry-analyze-core", version = "3.2.2" } +scry-sai-core = { path = "../scry-analyze-core", version = "3.2.3" } # Assemble `.wat` inputs to module bytes (so the CLI accepts both .wat and # .wasm); host-only, same dep the test harness uses. wat = { workspace = true } diff --git a/crates/scry-viz/src/lib.rs b/crates/scry-viz/src/lib.rs index 33d39d1..a90bc7c 100644 --- a/crates/scry-viz/src/lib.rs +++ b/crates/scry-viz/src/lib.rs @@ -39,12 +39,19 @@ use scry_analyze_core::{ /// dashboard"). Used in both the `
{n_funcs} function(s) with program points — summary below; \
+ detailed per-point invariants for the first {FUNCS_WITH_DETAIL_CAP}. The full \
+ per-point data is scry's library AnalysisResult; the actionable \
+ subset is in guidance.json.
| function | points | max locals | \ +
|---|---|---|
| {} | {count} | {nloc} |
… showing {} of {} points; full data in the JSON feed \
- (guidance.json).
… showing {} of {} points for this function \ + (full per-point invariants are scry's library output).
", POINTS_PER_FN_CAP, n_points, ); } @@ -1914,41 +1943,43 @@ mod tests { #[test] fn points_section_is_capped_and_page_stays_small() { - // Page-size sanity: a function with far more than the cap of program - // points must render only the first `POINTS_PER_FN_CAP`, plus a summary - // and a "showing N of M" note — and the whole page must stay well under - // 1 MB. We synthesize a large point set on a real result (the analyzer's - // own point count depends on its fixpoint, so we don't rely on it). + // Page-size sanity: scry-on-scry has ~800 FUNCTIONS each with points, so + // a per-function cap alone is not enough (800 × 20 rows is still ~10 MB). + // Synthesize points across many functions and assert (a) only the first + // `FUNCS_WITH_DETAIL_CAP` functions get a detailed table, and (b) the + // whole page stays well under 1 MB. let mut r = analyze_wat( "(module (func (export \"run\") (result i32) i32.const 42 i32.const 7 i32.add))", ); let template = r.invariants.points[0].clone(); r.invariants.points.clear(); - for pc in 0..3000u32 { - let mut p = template.clone(); - p.func_index = 0; - p.pc = pc; - r.invariants.points.push(p); + const FUNCS: u32 = 500; + const PTS_PER_FN: u32 = 30; + for f in 0..FUNCS { + for pc in 0..PTS_PER_FN { + let mut p = template.clone(); + p.func_index = f; + p.pc = pc; + r.invariants.points.push(p); + } } - let total = r.invariants.points.len(); - assert!( - total > POINTS_PER_FN_CAP * 5, - "fixture must produce many points (got {total})" - ); + assert_eq!(r.invariants.points.len() as u32, FUNCS * PTS_PER_FN); let html = render_html(&r, "big"); - // The cap note is present … + // Every function appears in the cheap summary line … assert!( - html.contains("full data in the JSON feed"), - "capped points note present" + html.contains("function(s) with program points"), + "per-function summary present" ); + // … but only the first FUNCS_WITH_DETAIL_CAP get a detailed table. + let detailed = html.matches("class=\"fn-points\"").count(); assert!( - html.contains("program point(s)"), - "per-function summary line" + detailed <= FUNCS_WITH_DETAIL_CAP, + "detailed tables capped at {FUNCS_WITH_DETAIL_CAP}, got {detailed}" ); - // … and the page is small despite thousands of points. + // … and the whole page stays under 1 MB despite 15k points / 500 funcs. assert!( html.len() < 1_000_000, - "page must stay under 1 MB even for many points; was {} bytes", + "page must stay under 1 MB across many functions; was {} bytes", html.len() ); }