Skip to content

Commit c83e078

Browse files
proof(SafePassword): DISCHARGE withMinLengthCorrect via nested constructor case-split (#143)
## Summary Discharge \`Proven.SafePassword.Proofs.withMinLengthCorrect\` via double constructor case-split. The OWED comment correctly suggested case-split on \`b = MkPolicyBuilder _\`. **Empirically verified** that DOUBLE case-split is needed — both the outer \`MkPolicyBuilder\` AND the inner \`MkPolicy\` constructor must be exposed for the projection \`({ minLength := n } p).minLength\` to reduce. ## Discharge \`\`\`idris withMinLengthCorrect n (MkPolicyBuilder (MkPolicy _ _ _ _ _ _ _ _ _ _)) = Refl \`\`\` The 10 underscores correspond to PasswordPolicy's 10 fields (\`minLength\` through \`forbiddenWords\`). ## Why double case-split Tested at \`/tmp/charrefl/src/TestPolicyBuilder.idr\`: - Single case-split on \`MkPolicyBuilder _\` alone: **fails** — \`{minLength := n} p\` doesn't reduce through abstract \`p\` - Double case-split exposing both constructors: **succeeds** — the record-update + projection chain reduces fully ## Sister OWED not addressed \`chainedBuildersCompose\` (Proofs.idr:430) is **NOT** discharged here. It requires reducing \`Policy.policyBuilder\` (a top-level constant). Same blocker family as SafeCSV's \`defaultDelimiterIsComma\` — top-level \`public export\` constants don't reduce for downstream record projection. Discharge waits for either an Idris2 0.8.0 fix or a named-constant-bridge lemma technique. ## Verification - \`idris2 --check src/Proven/SafePassword/Proofs.idr\` → Exit 0 - \`/tmp/charrefl/src/TestPolicyBuilder.idr\` prototype confirms double case-split is necessary and sufficient - Zero \`believe_me\` / \`postulate\` / \`idris_crash\` Refs proven#90 (Phase 3 OWED triage), proven#107 (overly-cautious OWED meta). ## Test plan - [x] Local type-check clean - [x] /tmp/charrefl test harness - [ ] CI \`idris2 --check proven.ipkg\` green - [ ] CI test suite green 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 9f7fd41 commit c83e078

1 file changed

Lines changed: 12 additions & 11 deletions

File tree

src/Proven/SafePassword/Proofs.idr

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -402,18 +402,19 @@ public export
402402
public export
403403
0 builderProducesPolicy : build Policy.policyBuilder = Policy.defaultPolicy
404404

405-
||| OWED: `withMinLength n` followed by `build` yields a policy whose
406-
||| `minLength` field equals `n`. The implementation is
407-
||| `withMinLength n (MkPolicyBuilder p) = MkPolicyBuilder ({minLength
408-
||| := n} p)`, so the equation is true by record-update normalisation
409-
||| but Idris2 0.8.0 does not reduce the record-update through the
410-
||| `MkPolicyBuilder` wrapper for an abstract starting builder
411-
||| `b : PolicyBuilder`. Discharge by case-splitting on
412-
||| `b = MkPolicyBuilder _` plus `Refl`, or by marking `build` and
413-
||| `withMinLength` `%inline`.
405+
||| DISCHARGED: `withMinLength n` followed by `build` yields a policy
406+
||| whose `minLength` field equals `n`. The OWED comment suggested the
407+
||| pattern: case-split on `b = MkPolicyBuilder _` to expose the
408+
||| constructor. Verified empirically that a DOUBLE case-split
409+
||| (`MkPolicyBuilder (MkPolicy _ _ _ _ _ _ _ _ _ _)`) is needed
410+
||| the inner `PasswordPolicy` constructor also has to be exposed for
411+
||| the record-update projection `({ minLength := n } p).minLength`
412+
||| to reduce. Once both are exposed, the chain collapses to `Refl`.
413+
||| Test at `/tmp/charrefl/src/TestPolicyBuilder.idr`.
414414
public export
415-
0 withMinLengthCorrect : (n : Nat) -> (b : PolicyBuilder) ->
416-
minLength (build (withMinLength n b)) = n
415+
withMinLengthCorrect : (n : Nat) -> (b : PolicyBuilder) ->
416+
minLength (build (withMinLength n b)) = n
417+
withMinLengthCorrect n (MkPolicyBuilder (MkPolicy _ _ _ _ _ _ _ _ _ _)) = Refl
417418

418419
||| OWED: chained builder calls compose correctly — `withMinLength n
419420
||| (withUppercase policyBuilder)` then `build`'s `minLength` is `n`.

0 commit comments

Comments
 (0)