Skip to content

Keep every local model path out of the site and the endpoint id - #5

Merged
devYRPauli merged 7 commits into
mainfrom
fix/site-windows-model-paths
Oct 14, 2026
Merged

devYRPauli merged 7 commits into
mainfrom
fix/site-windows-model-paths

Conversation

@devYRPauli

Copy link
Copy Markdown
Owner

Problem

The site sanitizers use Path::is_absolute. On Unix, that check does not match a Windows path, a ~ path or a file: URI. A result with such a model path puts the full path into index.html, outcomes.html, appendix.html, results.csv and results.json. A Windows path and a ~user path carry the user name.

The registry rule is_local_absolute_path also misses four path forms. willitcall run --model takes any string, so each form can reach resolve:

  • a rooted Windows path (\Users\...)
  • a drive-relative path (C:Users\...)
  • a ~user path
  • a file: URI with one slash or in upper case

For these forms, resolve keeps the full path as the endpoint id.

This PR is stacked on #4. Merge #4 first.

Fix

  • One helper, safe_local_value in site/data.rs, replaces the three copies in data.rs, export.rs and html.rs. It uses wic_core::registry::is_local_absolute_path, which is now pub.
  • For a local path, the site shows only the last path component.
  • is_local_absolute_path now matches a leading /, \ or ~, a case-insensitive file:, and a drive letter with a separator later in the value. An Ollama tag such as q:8b has no separator, so it stays a model id.
  • The safe_selector patterns in schemas/model-registry-v1.schema.json mirror the new rule. None of the 32 published selectors changes.

Testing

  • cargo fmt --check and cargo clippy --workspace --all-targets -- -D warnings are clean.
  • cargo test --workspace: 151 tests pass. Fix the three Codex findings on #2 #4 has 150.
  • site_never_renders_absolute_model_paths writes each path form as a model_id and as a v3 artifact source_id. It checks five generated files for the user name.
  • every_local_path_form_is_an_unsafe_selector_and_keeps_only_its_file_name sends each path form through resolve, the schema and the registry loader. It also checks that five model ids, such as q:8b and file-model:latest, stay valid selectors.
  • Each mutant makes a new test fail:
    • The three site sanitizers from Fix the three Codex findings on #2 #4: the site test fails on !generated.contains("someone").
    • The old is_local_absolute_path rule: both new tests fail.
    • The old schema patterns: the registry test fails.
    • No sanitizer on source_id in html.rs, or in export.rs: the site test fails.
  • willitcall site --results results/ gives output that is byte-identical to Fix the three Codex findings on #2 #4.
  • The tools unittest and the redaction ledger check pass.

This PR and the wic-50 v2 PR change the same import block in site/data.rs. The second one to merge needs main merged in first. The merged state of both passes all 155 tests.

devYRPauli and others added 7 commits October 12, 2026 15:16
run_measurement copied the id and revision of the frozen wic-50 v1
catalog into every run, but it hashed the scenarios that actually ran.
A run with --scenarios therefore claimed to be wic-50 v1. Default runs
made the same claim, because the embedded set no longer matches the v1
hash since 964f6e5.

A run now keeps the catalog id and revision only when its corpus hash
equals the catalog hash. Any other corpus records id "custom" and its
hash as the revision. The M7 spec states the rule under T1.2.

The evidence test pinned the old label for a one-scenario corpus. It
now expects "custom". A new test runs the frozen catalog scenarios and
expects wic-50 v1. The CLI test checks the full corpus block of a
--scenarios run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
validate deserialized a result with serde and checked only the totals.
Serde accepts values that the published schema rejects, so validate
printed "valid" for a failure_class of null, a failure.http_status of 0,
or a preflight_override with forced false. The same gap existed for v1
and v2: a v2 failure_class of "bogus" and a v1 run_id passed too.

The parser now checks the raw document against the embedded schema for
its version, after serde so the serde messages stay the same. The check
covers validate, site, annotate and rescore, because all of them parse
through it. The hand-written v2 property check is removed, because the
v2 schema covers it. jsonschema moves from a dev-dependency to a
dependency. Cargo.lock does not change.

annotate now refuses a v1 result. The v1 schema has no cause field, so
the edited file always failed its own schema.

All 187 tracked result files still validate, and the generated site is
byte-identical. The release binary grows from 7.9 MB to 12.3 MB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The parser accepted any string in evidence_path. The site then wrote it
verbatim into results.json and results.csv, and linked it from the
index and the appendix. A result with /Users/alice/... therefore
published the author's home directory. annotate --reference had the
same defect: it accepted /Users/alice/notes.md, and validate and site
both passed the edited file.

validate_result and validate_measurement now check both fields. Every
parse runs one of them, and every write runs them first. An
evidence_path must be a relative path under evidence/. A cause
reference must be an http(s) URL or a relative repository path. Each
path segment must be non-empty, must not start with '.', and may use
only ASCII letters, digits, '.', '_' and '-'. The check does not use
std::path, because a Unix host does not see C:\ as absolute.

All 187 tracked result files pass. One site fixture had a leading '/'
on its evidence path. It is fixed, and the site golden does not change.
The leading '/' trims in evidence_url and reference_url are removed,
because no such value reaches them now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An endpoint can answer with a status from 600 to 999. The runner
recorded that status in failure.http_status, but the v3 schema allows
100 to 599 only. Since the readers check the schema, run wrote a file
that validate, site, annotate and rescore then refused.

RFC 9110 defines status codes 100 to 599 only. The runner now records
http_status only in that range. The failure code stays http_error, and
the evidence keeps the raw status.

Both result writers now check the serialized document the way the
readers do, before they write it. A writer and the schema can no longer
drift apart without a loud error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reference check applied the path segment rule to the whole value.
It refused a case-study link with a section anchor, such as
docs/case-studies/x.md#section, and a line anchor such as
runner.rs#L770. The base accepted both and rendered working links.

The check now splits off one '#' fragment. The path part keeps the
segment rule. The fragment allows ASCII letters, digits, '.', '_' and
'-' only, so it cannot carry a local path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The site sanitizers used Path::is_absolute. On Unix that misses
C:\..., \\server\..., ~/... and file://, so a result recorded on
Windows or with a home path put the user name into index.html,
outcomes.html, results.csv and the JSON export.

The site now uses wic_core::registry::is_local_absolute_path, the
same rule that the registry applies to selectors, and shows only the
last path component. One helper in site/data.rs replaces the three
copies. The 32 checked-in results give a byte-identical site.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
is_local_absolute_path matched only a leading '/', '~/', '~\', a drive
letter with a separator, '\\' and 'file://'. It missed a rooted
Windows path (\Users\...), a drive-relative path (C:Users\...), a
'~user' path and a 'file:' URI in another case or with one slash.
llama-server -m accepts each of these forms, so the site and a
resolved endpoint id kept the user name.

The rule now matches a leading '\', any leading '~', a case-insensitive
'file:' and a drive letter with a separator later in the value. An
Ollama tag such as 'q:8b' has no separator, so it stays a model id.
The registry schema mirrors the rule. None of the 32 published
selectors changes.

A registry test covers each form through resolve, the schema and the
loader. The site test now covers each form in model_id and a v3
artifact source_id, which reaches the appendix and the JSON export.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@devYRPauli
devYRPauli merged commit 5e42f01 into main Oct 14, 2026
4 checks passed
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