Skip to content

fix(export): compliance bundle was 842 MB dashboard mirror#279

Merged
avrabe merged 1 commit into
mainfrom
fix/compliance-bundle-single-page
May 15, 2026
Merged

fix(export): compliance bundle was 842 MB dashboard mirror#279
avrabe merged 1 commit into
mainfrom
fix/compliance-bundle-single-page

Conversation

@avrabe
Copy link
Copy Markdown
Contributor

@avrabe avrabe commented May 14, 2026

Summary

The release pipeline shipped `rivet-v0.9.0-compliance-report.tar.gz` at 842 MB — a static mirror of the `rivet serve` dashboard (every page, source mirror, AADL viewer JS, per-schema help pages). That's a browseable website dump, not the audit-quality bundle the artifact name promises. Auditors opening this archive got a confusing browse tree instead of a focused dossier.

Three surgical changes restore the audit-bundle shape:

1. `rivet-cli/src/main.rs` — wire the CLI flags through

`cmd_export_html` declared `single_page`, `theme`, and `offline` parameters all prefixed with `_` — explicitly ignored. The CLI advertised `--single-page` in `--help`; the binary swallowed it silently. Un-underscored and wired through:

  • `--single-page true`: short-circuit at the top of the function, call `rivet_core::export::render_single_page` (already exists at `export.rs:2451`), write one `index.html`, return.
  • `--offline true`: build `ExportConfig { offline: true }` so no external fonts are loaded.
  • `--theme light|dark`: select `ExportTheme::Light` or `ExportTheme::Dark`.

2. `rivet-core/src/export.rs` — UTF-8 boundary panic in `render_single_page`

The graph-node sublabel at line 1875 did `&title[..26]` which panics on any title with a multi-byte UTF-8 character at the 26-byte boundary. Hits this dogfood title: "Human review degradation — reviewer trusts AI without verification" (em-dash at bytes 25-27). Replaced with `chars().take(26).collect::()` + ellipsis.

3. `.github/actions/compliance/action.yml` — new `single-page` input

Adds `single-page: true` (default) as a new action input. Compliance reports want single-page by default; the multi-page mirror remains opt-in. Also flips `offline: 'false' → 'true'` as default — auditors typically open bundles disconnected from the internet.

Result (rivet's own dogfood)

v0.9.0 (current) After this PR
Size 842 MB 1.4 MB (600× smaller)
Shape Static mirror of `rivet serve` (index/coverage/help/validate/matrix/graph + per-schema pages + AADL viewer JS) Single self-contained `index.html` with 9 inline sections
External resources Google Fonts + WASM viewers Zero (verified with `grep -c "https://fonts\\."\`)
Browser Requires HTTP server to serve relative paths Opens directly via `file://`

Inline sections in the new bundle:
`index`, `requirements`, `documents`, `stpa`, `eu-ai-act`, `coverage`, `matrix`, `validation`, `graph`.

Test plan

  • Local: `./target/release/rivet export --format html --single-page --offline --output /tmp/c` → 1.4 MB `index.html`, 9 `
    ` blocks, 0 external resources
  • `cargo test -p rivet-core --lib export::` → 42/42 pass (UTF-8 boundary fix doesn't break existing tests)
  • Multi-page path (no `--single-page` flag) unchanged — short-circuit only fires when `single_page` is true
  • Next release will produce a small, audit-readable `compliance-report.tar.gz` instead of an 842 MB blob

Trailer

`Fixes: REQ-010` (export contract integrity) · `Refs: REQ-007` (CLI surface)

🤖 Generated with Claude Code

The release pipeline shipped `rivet-v0.9.0-compliance-report.tar.gz` at
842 MB — a static mirror of the `rivet serve` dashboard (index.html,
coverage/, help/, validate/, matrix/, graph/, per-schema help pages,
AADL viewer JS, source mirrors). That's a browseable website dump,
not the audit-quality bundle the artifact name implies. Auditors got
a confusing browse tree instead of a focused dossier.

Three changes restore the audit-bundle shape:

1. **rivet-cli/src/main.rs (`cmd_export_html`):** the `single_page`,
   `theme`, and `offline` parameters were all `_`-prefixed — explicitly
   ignored by the function body. The CLI surface advertised them and
   the help text described them, but the binary swallowed them silently.
   Un-underscored and wired through: when `--single-page` is true, the
   function now calls `rivet_core::export::render_single_page` and
   writes one `index.html` to the output directory, returning early
   before the multi-page mirror path. `--offline` is honored by
   building the `ExportConfig` with `offline: true`. `--theme` selects
   `ExportTheme::Dark` or `ExportTheme::Light`.

2. **rivet-core/src/export.rs (`render_single_page` callee):** byte-
   indexing panic at line 1875. The graph-node sublabel did
   `&title[..26]` which panics on any title with a multi-byte UTF-8
   character at the 26-byte boundary (e.g. an em-dash `—` at chars
   25-27). Replaced with `title.chars().take(26).collect::<String>()`
   plus an ellipsis. Surfaces on the rivet repo's own dogfood —
   "Human review degradation — reviewer trusts AI without
   verification" hits this case.

3. **.github/actions/compliance/action.yml:** add `single-page: true`
   as a new input (default true — the action is purpose-built for
   compliance reports, where single-page is the right shape) and
   flip `offline: 'false' → 'true'` as default (auditors open bundles
   disconnected). Pass `--single-page` and `--offline` through to the
   `rivet export` invocation.

Result on the rivet repo's own dogfood:

  before: rivet-v0.9.0-compliance-report.tar.gz  842 MB
  after:  single-page index.html                  1.4 MB
          (600× smaller, all 9 sections inline:
           index / requirements / documents / stpa /
           eu-ai-act / coverage / matrix / validation / graph,
           zero external resources, inline CSS, browser-openable)

Refs: REQ-010 (export contract), REQ-007 (CLI surface)
@github-actions
Copy link
Copy Markdown

📐 Rivet artifact delta

No artifact changes in this PR. Code-only changes (renderer, CLI wiring, tests) don't touch the artifact graph.

Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark 'Rivet Criterion Benchmarks'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.20.

Benchmark suite Current: adfd297 Previous: ff89f98 Ratio
store_insert/10000 14562227 ns/iter (± 1944650) 11199692 ns/iter (± 431145) 1.30

This comment was automatically generated by workflow using github-action-benchmark.

avrabe added a commit that referenced this pull request May 15, 2026
…284)

Three PRs today (#279, #281, and previously #275/#278) failed CI on
the same test: `graph_focused_view_renders_svg`. Cause is structural,
not a real-flake: the test's `fetch()` helper hardcodes a 5s read
timeout, and the focused /graph endpoint takes ~5s on the dogfood
corpus (742 nodes, 1477 edges) — BFS frontier + etch layout pass.
The test sits exactly on the edge; CI runner load tips it over.

Add a `fetch_with_timeout(port, path, htmx, timeout)` variant. Keep
the default 5s `fetch()` for everything else. Bump the graph test's
deadline to 15s — well past the genuine wall-clock for this endpoint
and short enough that a real performance regression still bubbles up.

The wall-clock `Instant::elapsed()` log line stays, so an actual slow
regression would still be visible in the test output even though the
read timeout no longer blocks it.

Verified locally: `cargo test -p rivet-cli --test serve_integration
graph_focused_view_renders_svg` passes in 2.79s with the new helper.

Refs: REQ-007 (CLI surface)
@avrabe avrabe merged commit 2b215c4 into main May 15, 2026
21 of 23 checks passed
@avrabe avrabe deleted the fix/compliance-bundle-single-page branch May 15, 2026 06:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant