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(SafeJson): annotate 13 bodyless decls as OWED (Refs standards#158) (#56)
## Summary
Converts the 13 bare `export <sig>` postulates in
`src/Proven/SafeJson/Proofs.idr` to the estate's
OWED-with-justification convention established 2026-05-20 across
the SafeChecksum / SafeBuffer / SafeCryptoAccel / SafeHKDF /
SafeBloom / SafeFPGA / SafeAPIKey / SafeCORS / SafeCSV / SafeSemVer /
SafeHtml / SafeArgs / SafeHTTP / SafeFile / SafeHeader / SafeMath /
SafeTOML / SafeCSRF / SafeEnv siblings:
- Triple-pipe `|||` docstring stating the claim.
- Leading `0 ` erased-multiplicity (runtime-stripped).
- Bare type signature; **no** `postulate` keyword.
- Explicit Idris2 0.8.0 blocker + discharge condition per decl.
Refs hyperpolymath/standards#158.
## The 13 OWED items, grouped by blocker family
### Family A — String FFI opacity (4 decls)
`set` / `get` / `remove` / `hasKey` thread through `prim__eq_String`
(or its `(/=)` lift via `filter`). Idris2 0.8.0 has no reflective
`prim__eq_String`-to-`Dec`-eq lemma. Same class-(J) family as
`boj-server` `Boj.SafetyLemmas.charEqSym` (`prim__eqChar` reflection
gap) and `gossamer` `stringNotEqCommut` (`Panels.Distinct`, landed in
gossamer PR #41).
1. `setGetIdentity` — `get k (set k v (JsonObject obj)) = Just v`.
2. `setPreservesOther` — `Not (k1 = k2) -> get k2 (set k1 v obj) = get
k2 obj`.
3. `setHasKey` — `isObject obj = True -> hasKey k (set k v obj) = True`.
4. `removeNotHasKey` — `hasKey k (remove k obj) = False`.
Joint discharge: ship a class-(J) `String` reflection axiom (mirror
of `gossamer` `stringNotEqCommut`) or refactor `update` / `lookup` /
`filter` onto a `DecEq` carrier.
### Family B — List length-append composition (1 decl)
`Data.List.lengthAppend` in Idris2 0.8.0 is stated
`length (xs ++ ys) = length xs + length ys`. Closing
`length (arr ++ [v]) = length arr + 1` requires composing
`lengthAppend` + `length [v] = 1` + `plusZeroRightNeutral` + `S`
congruence — doable but multi-step, deferred to batch-discharge.
5. `appendLengthInc` —
`arrayLength (append v (JsonArray arr)) = Just (length arr + 1)`.
Discharge: write the 4-step composed proof, or add a one-step
`lengthAppendSingleton` lemma to base.
### Family C — Covering-vs-total reduction (2 decls)
`getPath` (in `Proven.SafeJson.Access`) and `matchesType` are
`covering` (mutual recursion through `getSegment` / `all`). Idris2
0.8.0 will not reduce a `covering` function on abstract variables —
same shape as the `deletePath` unreachable-clause warning already in
this build.
6. `singleKeyPath` — `getPath [Key k] (JsonObject obj) = lookup k obj`.
7. `anyMatchesTAny` — `matchesType v TAny = True` for all `v`.
Discharge: restate the callee as `total` via a structurally decreasing
metric (path length / `JsonValue` size), or hand-write per-arm splits
that elaborate `Refl` constructor-by-constructor.
### Family D — Parser FFI opacity (6 decls)
`parseJson` dispatches into `Proven.SafeJson.Parser` and threads
through `unpack` / `pack` / `strHead` / `strSubstr` plus fuel-driven
recursion. No `parseJson "<literal>"` reduces to its result by `Refl`
under Idris2 0.8.0. Same blocker family as `SafeChecksum` Luhn/ISBN
(FFI-bound String) and `SafeHtml.escapePreservesNoLT`.
8. `parseNullCorrect` — `parseJson "null" = Just JsonNull`.
9. `parseTrueCorrect` — `parseJson "true" = Just (JsonBool True)`.
10. `parseFalseCorrect` — `parseJson "false" = Just (JsonBool False)`.
11. `parseEmptyFails` — `parseJson "" = Nothing`.
12. `parseEmptyArray` — `parseJson "[]" = Just (JsonArray [])`.
13. `parseEmptyObject` — `parseJson "{}" = Just (JsonObject [])`.
Joint discharge: ship a reflective `String`-to-`List Char` axiom (or
refactor the parser onto `List Char` end-to-end so the literal
`unpack` can be supplied as a trusted constant at the call site).
## Verification
```
cd src && idris2 -p base --source-dir . --check --total \
Proven/SafeJson/Proofs.idr
# -> exit 0 (5/5 modules build, no errors, --total accepted)
```
- All 13 OWED sigs carry erased multiplicity (`0 …`), so they are
runtime-stripped — no executable axiom escapes into the built
artifact.
- Module-level shared comment for Family D avoids 6× near-duplicate
docstrings while keeping each decl individually annotated per the
convention.
- Pre-existing `Proven.SafeJson.Access:282 deletePath _ _ = Nothing`
unreachable-clause warning is unrelated to this PR and stays as
the upstream baseline (it is the very `covering` family-C blocker
cited for `singleKeyPath`).
## Why DRAFT
Filed DRAFT per the convention (estate proven-CI queue settles
asynchronously, sibling OWED PRs are landed by the owner in batches).
## Test plan
- [x] `idris2 --check --total` green on the touched module against
`origin/main` (rebased to `df6b0a1`, current SafeEnv tip).
- [x] No other files touched (`git diff --stat` = 1 file).
- [x] No callers depend on these sigs in a way that requires bodies
(they were already bodyless `export` postulates on main).
- [ ] Owner: merge after the estate proven-CI queue catches up.
Refs hyperpolymath/standards#158.
0 commit comments