Skip to content

Commit e2abf3a

Browse files
hyperpolymathclaude
andcommitted
proof(SafePassword): DISCHARGE withMinLengthCorrect via nested constructor case-split (proven#90 #107)
The OWED comment for `withMinLengthCorrect` correctly diagnosed the blocker (record-update through `MkPolicyBuilder` wrapper doesn't reduce for abstract builder) and suggested case-split. Empirically verified that a 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 to `n`. DISCHARGE (one line): ```idris withMinLengthCorrect n (MkPolicyBuilder (MkPolicy _ _ _ _ _ _ _ _ _ _)) = Refl ``` The 10 underscores correspond to PasswordPolicy's 10 fields (`minLength`, `maxLength`, `requireUppercase`, `requireLowercase`, `requireDigit`, `requireSymbol`, `minUniqueChars`, `maxRepeatedChars`, `forbiddenPatterns`, `forbiddenWords`). Test harness verified at `/tmp/charrefl/src/TestPolicyBuilder.idr` — single case-split on `MkPolicyBuilder _` alone is NOT sufficient because `{minLength := n} p` doesn't reduce through abstract `p`. Both layers needed. The sister OWED `chainedBuildersCompose` (line 430) is NOT discharged in this PR — it requires reducing `Policy.policyBuilder` (a top-level constant) which still trips the record-projection- through-top-level-constant blocker we hit in SafeCSV PR #137. Discharge of that one waits for either an Idris2 0.8.0 fix or a named-constant-bridge lemma technique. Zero `believe_me`/`postulate`/`idris_crash`. Refs proven#90 (Phase 3 OWED triage), proven#107 (overly-cautious OWED meta). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 40b127f commit e2abf3a

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)