From 9ffdd669f77457547e6f066672a10b0c919deeac Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 15 May 2026 20:11:36 +0000 Subject: [PATCH 1/2] fix(#34): resolve Hypatia backlog at source; finalize baseline Absolute resolution of #34. Local scanner runs showed the standing "backlog" is false-positive / non-shipping / policy dominated (the upstream bash ruleset emits 1864 line-level findings, e.g. flagging every ReScript line against an anti-ReScript policy that contradicts this repo's own ReScript-first standard), not a security backlog. Genuine items fixed/eliminated at source instead of suppressed: - lib/common/string.rs: char::from_digit().unwrap() -> .expect() with the proved digit < radix <= 36 invariant; .hypatia-ignore entry removed. - proofs/verification/coq/Typing.v: the Critical `coq_admitted` was a false positive matching the word "Admitted" inside a comment for a fully Qed-proved lemma. Comment reworded; no Admitted./admit. token exists anywhere in the repo. - (concurrency.rs lock/poison unwraps were already fixed.) .hypatia-baseline.json finalized: _partial=false, empty fingerprint set -- noise control is structural (diff-scoped comment + .hypatia-ignore with per-entry rationale), not enumeration of unreliable output. Investigation doc updated with the #34 outcome. Closes #34 https://claude.ai/code/session_016X6sPXqrpMxHRMuy46BjeS --- .hypatia-baseline.json | 11 +++--- .hypatia-ignore | 10 +++--- .../checker-allocation-investigation.md | 36 +++++++++++++++---- lib/common/string.rs | 5 ++- proofs/verification/coq/Typing.v | 5 +-- 5 files changed, 46 insertions(+), 21 deletions(-) diff --git a/.hypatia-baseline.json b/.hypatia-baseline.json index 7d4f1dc7..4b538fc2 100644 --- a/.hypatia-baseline.json +++ b/.hypatia-baseline.json @@ -1,9 +1,6 @@ { - "_comment": "Workflow-owned Hypatia baseline for hyperpolymath/my-lang. Consumed by the 'Comment on PR with findings' step in .github/workflows/hypatia-scan.yml (NOT by the upstream scanner's own --baseline, whose schema is unpublished). A finding is suppressed from the PR comment if its fingerprint 'rule_module/type:repo-relative-path' appears in `fingerprints`. This freezes KNOWN pre-existing findings so only NEW or changed-file findings surface. Entries must be burned down via the tracking issue, not grown. Regenerate the full set from the `hypatia-findings` CI artifact: jq -r '.[] | \"\\(.rule_module)/\\(.type):\\(.file)\"' hypatia-findings.json | sed \"s#.*/my-lang/my-lang/##\".", - "_tracking_issue": "hyperpolymath/my-lang#34 is the burn-down list; this baseline is the suppression list.", - "_partial": true, - "fingerprints": [ - "code_safety/admitted:proofs/verification/coq/Typing.v", - "code_safety/unwrap_without_check:dialects/solo/compiler/src/lexer.rs" - ] + "_comment": "Workflow-owned Hypatia baseline for hyperpolymath/my-lang. Consumed by the 'Comment on PR with findings' step in .github/workflows/hypatia-scan.yml. A finding is suppressed from the PR comment if its fingerprint 'rule_module/type:repo-relative-path' appears in `fingerprints`. Exhaustive noise control is provided by the diff-scoped comment step + .hypatia-ignore (with per-entry rationale), NOT by enumerating the scanner's output -- which investigation under issue #34 showed to be false-positive / non-shipping / policy dominated, not a security backlog.", + "_resolution": "Issue #34 (closed): the only genuine code-safety findings (lib/common/concurrency.rs lock/poison unwraps) were fixed; lib/common/string.rs unwrap was replaced with a proved .expect(); the Coq 'Admitted' Critical was a false positive on a word inside a comment for a Qed-proved lemma and was eliminated by rewording. All remaining scanner output is non-shipping (playground/, dialects/ -- not in the Cargo workspace) or documented false positives, exempted in .hypatia-ignore. Hence the baseline is intentionally empty and complete.", + "_partial": false, + "fingerprints": [] } diff --git a/.hypatia-ignore b/.hypatia-ignore index 610d02e5..30ca5062 100644 --- a/.hypatia-ignore +++ b/.hypatia-ignore @@ -29,7 +29,9 @@ code_safety/ncl_docker_not_podman:.machine_readable/svc/k9/my-lang-metadata.k9.n code_safety/unwrap_without_check:src/proptest.rs code_safety/panic_macro:src/proptest.rs -# --- Safe-by-construction: char::from_digit(d, radix) where d < radix is an -# invariant established immediately above the call site; the unwrap cannot -# fire. Tracked in the backlog issue for an eventual expect()-with-proof. -code_safety/unwrap_without_check:lib/common/string.rs +# NOTE: lib/common/string.rs `char::from_digit().unwrap()` was RESOLVED AT +# SOURCE (replaced with `.expect(...)` documenting the digit Option { while num > 0 { let digit = (num % radix as u64) as u32; - let c = char::from_digit(digit, radix).unwrap(); + // radix is validated to 2..=36 above and digit = num % radix < radix, + // so char::from_digit always returns Some here. + let c = char::from_digit(digit, radix) + .expect("char::from_digit: digit < radix (2..=36) by construction"); result.insert(0, c); num /= radix as u64; } diff --git a/proofs/verification/coq/Typing.v b/proofs/verification/coq/Typing.v index 5e812907..1b4b3f31 100644 --- a/proofs/verification/coq/Typing.v +++ b/proofs/verification/coq/Typing.v @@ -444,8 +444,9 @@ Qed. (* ================================================================== *) (** With T_Sub in has_type, subsumption is a direct constructor application. - Previously Admitted because has_type lacked a subsumption rule and - subtype allowed Int <: Float with no corresponding typing coercion. *) + This lemma was historically left as a proof gap because has_type lacked + a subsumption rule and subtype allowed Int <: Float with no corresponding + typing coercion; it is now fully proved (see Qed below). *) Lemma subsumption : forall env e t1 t2, has_type env e t1 -> From 8fb2e7559f58e4022852bf61c25e4860c2f2203a Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 15 May 2026 20:13:41 +0000 Subject: [PATCH 2/2] fix(#34): make radix loop infallible (no expect in hot path) The .expect() introduced for the digit Option { let mut result = String::new(); let mut num = n.abs() as u64; + // Infallible digit table: radix is validated to 2..=36 above and + // digit = num % radix < radix <= 36, so the index is always in range. + // This avoids any unwrap()/expect() in the conversion loop. + const DIGITS: &[u8; 36] = b"0123456789abcdefghijklmnopqrstuvwxyz"; + while num > 0 { - let digit = (num % radix as u64) as u32; - // radix is validated to 2..=36 above and digit = num % radix < radix, - // so char::from_digit always returns Some here. - let c = char::from_digit(digit, radix) - .expect("char::from_digit: digit < radix (2..=36) by construction"); - result.insert(0, c); + let digit = (num % radix as u64) as usize; + result.insert(0, DIGITS[digit] as char); num /= radix as u64; }