Skip to content

feat(registry): flux-verified name normalization + batch hardening - #32

Open
willemneal wants to merge 1 commit into
mainfrom
feat/flux-verification
Open

feat(registry): flux-verified name normalization + batch hardening#32
willemneal wants to merge 1 commit into
mainfrom
feat/flux-verification

Conversation

@willemneal

Copy link
Copy Markdown
Collaborator

Adds the Flux refinement checker as a verification gate over the registry contract — 437/437 functions checked, including scerr/contracttrait macro-generated code.

Refined type

Normalized now carries its invariant in the type: #[refined_by(n: int)] #[invariant(n <= MAX_NAME_LENGTH)] — every slice/index of the 64-byte buffer is machine-checked. normalize() walks bytes instead of chars() (behavior-identical for the crates.io charset: any byte of a multi-byte UTF-8 char fails is_ascii_alphanumeric exactly like its char counterpart), which also drops the unsafe as_bytes_mut and the deferred chars_to_change buffer.

Real hardenings from the checker

  1. NormalizedName::hash() used a 100-byte scratch buffer for a 64-byte-max name — 65..=100-byte unnormalized names (reachable only via unsafe new_unchecked misuse) hashed silently; >100 sliced out of bounds. Now a right-sized 64-byte buffer behind an explicit assert.
  2. process_batch: cursor + limit could overflow u32 and trap on a caller-supplied limit near u32::MAX. Now saturating_add (clamps to "process to the end"), with #[opts(check_overflow = \"strict\")] discharging the remaining arithmetic.

Notes

  • Overflow checking is scoped per-item because soroban_sdk_tools::scerr's generated from_seq arithmetic can't be annotated; slice/index bounds are always checked regardless.
  • Batch iteration switched to index loops (soroban Vec iteration ICEs flux) — behaviorally identical.
  • Attributes erase to no-ops on stable: toolchain pin (1.93.0), wasm artifacts, and tests unchanged.

Gates

just flux green (437 checked, ~290 ms) · clippy pedantic green · workspace tests green (44 registry tests) · just build green.

🤖 Generated with Claude Code

https://claude.ai/code/session_01M52tZC7sppbbo1HDBpj9KK

Add the Flux refinement checker as a verification gate over the registry
contract: 437/437 functions checked (including scerr/contracttrait
macro-generated code).

Normalized is now a flux-refined struct — the len <= MAX_NAME_LENGTH
invariant lives on the type, so every slice/index of the 64-byte buffer
is machine-checked. normalize() walks bytes instead of chars()
(behavior-identical for the crates.io charset: every multi-byte UTF-8
byte fails is_ascii_alphanumeric exactly like its char did), dropping
the unsafe as_bytes_mut and the deferred chars_to_change buffer.

Two real hardenings out of the checker run:
- NormalizedName::hash() sized its scratch buffer at 100 bytes for a
  64-byte-max name: 65..=100-byte unnormalized names (only reachable via
  unsafe new_unchecked misuse) hashed silently, >100 sliced out of
  bounds. Now a 64-byte buffer behind an explicit assert.
- process_batch's 'cursor + limit' could overflow u32 and trap on a
  caller-supplied limit near u32::MAX; now saturating_add (clamps to
  'process to the end'), with #[opts(check_overflow = "strict")]
  discharging the remaining arithmetic.

Batch iteration switches to index loops (soroban Vec iteration ICEs
flux, projections.rs:720) — behaviorally identical.

Gates: cargo flux green; clippy pedantic green; workspace tests green
(44 registry tests); just build green. New just flux recipe.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M52tZC7sppbbo1HDBpj9KK
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