You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
proof(SafeUrl): annotate 16 bodyless decls as OWED (Refs standards#158) (#61)
## Summary
Apply the OWED-with-justification convention (set 2026-05-20 by
SafeChecksum / SafeBuffer / SafeCryptoAccel / SafeHKDF / SafeBloom /
SafeFPGA, lives in PRs #37-#46) to the 16 bodyless declarations in
`src/Proven/SafeUrl/Proofs.idr`.
Refs hyperpolymath/standards#158.
Three parts per decl:
```idris
||| OWED: <one-sentence statement>
||| Held back by <specific Idris2 0.8.0 blocker>. Discharge once
||| <what would unblock it>.
0 declarationName : Type
```
No `postulate` keyword (consistent with every other `Proofs.idr` in
the estate). No `believe_me`, no `idris_crash`. Erased multiplicity
(`0 `) — these are stated assumptions, not runtime code, parallel
to the I6/I7 stated-assumption pattern in `proof-of-work`'s ABI
seam.
## Note on two runtime-consumed lemmas
Two of the 16 OWED decls (`isSafeSchemeNotJavascript`,
`lteFrom65535Check`) have in-file runtime call sites in
`validateSafe` and `validatePort`. To keep the convention strictly
uniform across all 16 decls, the corresponding data-constructor
proof arguments are switched to erased multiplicity:
- `MkSafeURL`'s second arg → `(0 _ : Not (url.scheme = Just (Custom
"javascript")))`
- `MkValidPort`'s second arg → `(0 _ : LTE p 65535)`
This is a same-file change. Verified scope:
- `SafeUrl.idr` does **not** import `SafeUrl.Proofs` (the dependency
edge runs the other way), so external API is unchanged.
- The similarly-named `MkValidPort` in `SafeNetwork/Port.idr` is a
separate single-arg local constructor, unrelated to this one.
## The 16 decls
### Encoding / decoding (String/Char FFI opacity)
1. **`unreservedNotEncoded`** — `isAlphaNum c = True ⇒ percentEncode c =
singleton c`.
Blocked by `Data.Char.isAlphaNum` / `isUnreserved` routing through
`prim__charPred` FFI; `Refl` cannot reduce the `if`-branch.
2. **`encodePreservesAlphaNum`** — fully-alphanumeric string is fixed by
`urlEncode`.
Blocked by `unpack`/`pack`/`concat` String FFI opacity (SafeFile
`boundedReadAtMostLimit` family) + per-element `isAlphaNum`.
3. **`decodeUnreservedIdentity`** — fully-alphanumeric string
round-trips through `urlDecode`.
Same blocker family as #1 + #2.
4. **`encodeDecodeIdentity`** — full `urlDecode ∘ urlEncode = Just`.
Blocked by String FFI + Char FFI + the percent-hex arithmetic
identity. Same family as proof-of-work I7 (FFI-correctness
assumption).
### Query builder / accessor (String equality, list folding)
5. **`emptyBuilderEmpty`** — `buildQueryString emptyQuery = ""`.
Blocked by `Data.String.joinWith` not reducing through its inner
`where`-block; `++` on String is FFI-opaque (same family as
SafeHeader `renderedHeaderSafe`).
6. **`addParamIncreasesCount`** — `paramCount (addParam k v qb).params =
S (paramCount qb.params)`.
Reduces to the standard `length (xs ++ [x]) = S (length xs)`
list-folding identity; needs induction on `xs`, `Refl` cannot
close it. Stdlib-plumbing OWED (discharge in-Idris2).
7. **`setGetIdentity`** — `getParam k (setParam k v qs) = Just v`.
Blocked by String `==` routing through `prim__eqString` FFI in
the `if k == key` branches.
8. **`removeHasNot`** — `hasParam k (removeAllParams k qs) = False`.
Same String-equality FFI blocker as #7.
9. **`filterPreservesOnly`** — `filterParams` keeps only entries with
permitted keys.
The standard `filterAll : all p (filter p xs) = True` lemma
instantiated at our predicate; blocked by `Data.List` stdlib not
exposing it `%reducible` + `elem` on String FFI opacity.
### Query parameter parsing (FFI-correctness assumptions)
10. **`parseIntValid`** — parsing `"42"` from a matching key yields
`Just 42`.
Blocked by `parseInteger`'s `strM`/`unpack`/`all isDigit`/`foldl`
String+Char FFI chain (same family as SafeArgs
`intParsingHandlesNegative`).
11. **`parseBoolTrue`** — parsing `"true"` yields `Just True`.
Blocked by `case toLower s of "true" => …` String-literal match
via `prim__eqString`.
12. **`parseBoolFalse`** — symmetric to `parseBoolTrue`.
### Query string merge (stdlib-plumbing + String equality)
13. **`mergeEmptyLeft`** — `mergeQueryStrings [] qs = qs`.
Blocked by per-step `hasParam k acc` String-equality FFI opacity
+ `lengthSnoc`-style induction on `qs`. Same family as
`setGetIdentity` + `addParamIncreasesCount`.
14. **`appendAssociative`** — inherited from
`Data.List.appendAssociative`.
Stdlib-plumbing OWED — proof exists in Prelude but not exposed
`%reducible`; discharge by import + rewrite. Not a fundamental gap.
### URL security / port validation (String equality, LTE-from-Bool)
15. **`isSafeSchemeNotJavascript`** — `isSafeScheme url = True ⇒ Not
(scheme = Just (Custom "javascript"))`.
Blocked by `Custom "javascript" = Custom "javascript"` routing
through `prim__eqString` for the payload — case-equality opaque
to `Refl`. **Runtime-consumed** (see "Note" above); `MkSafeURL`
arg moved to `(0 _ : ...)`.
16. **`lteFrom65535Check`** — `p <= 65535 = True ⇒ LTE p 65535`.
The well-known `lteReflectsLTE` reflective lemma. Stdlib proof
exists (`Decidable.Order.fromLte`-family) but the chain through
`Ord Nat`'s `<=` does not reduce by `Refl`. **Runtime-consumed**
(see "Note" above); `MkValidPort` arg moved to `(0 _ : ...)`.
## Out of scope
- Discharging any OWED. Each annotation pins a stated path
(reflective tactic / property-test / stdlib-plumbing); discharge
is a separate per-decl PR per the standards#158 campaign rhythm.
- Any file other than `src/Proven/SafeUrl/Proofs.idr`. The two
`(0 _ : ...)` data-constructor argument changes are in the same
file and have no out-of-file consumers (verified by grep).
## Test plan
- [ ] Idris2 0.8.0 `--check` on `Proven.SafeUrl.Proofs` (build was
not green pre-PR — file had 16 bodyless decls; this PR's job
is to make the OWED state *discoverable* and convention-conformant,
not to fix the build, per the standards#158 campaign rhythm.)
- [ ] Grep audit: `^||| OWED:` count = `^0 [a-zA-Z]` count = 16.
- [ ] No `postulate`, `believe_me`, or `idris_crash` introduced.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
0 commit comments