feat(proof): backend-assurance harness for prim__strToCharList (2/4) — campaign complete#140
Merged
Merged
Conversation
Final slice of epic #87 Tier C backend-assurance campaign. Closes the 4-primitive sequence after #129 (prim__eqChar, slice 1/4) and #139 (prim__strAppend + prim__strSubstr, slices 3/4 + 4/4). With this slice merged, all 5 class-(J) believe_me axioms in src/abi/Boj/SafetyLemmas.idr have external backend-assurance evidence — modulo any future backend (JS, native, etc.) that would need a fresh trusted-extraction argument. Discharges external evidence for one class-(J) believe_me axiom: :218 unpackLength : (s : String) -> length (unpack s) = length s The in-language proof does NOT change. The %unsafe / believe_me site stays in SafetyLemmas.idr. The harness shrinks the trusted base from "we trust the backend" to "we read the backend lowering and randomly tested the operation against the property". Artefacts (matching the #129 / #139 template): - elixir/test/backend_assurance/prim_str_to_char_list_test.exs BEAM property tests via stream_data over UTF-8 binaries. Properties: length preservation, empty-string corner, round-trip identity (to_string ∘ to_charlist), charlist element-type sanity (legal codepoints, no surrogates), + boundary table covering all four UTF-8 encoding widths (ASCII / Latin-1 / CJK / astral). - docs/backend-assurance/prim__strToCharList.md Trusted-extraction prose: Chez (R6RS string->list + string-length, §6.7 + §11.12) and BEAM (Elixir String.to_charlist/1 + String.length/1 over UTF-8, prefix-free codepoint counting). - docs/backend-assurance/README.md Coverage table flipped from _pending_ for prim__strToCharList. - PROOF-NEEDS.md Backend-assurance evidence column filled for unpackLength; class column updated to "J ✓" marker indicating externally-validated class-(J) status (convention introduced by #139). Site line number corrected from :211 to :218 (the file has shifted since the 2026-05-18 audit; the previous PR didn't sync this row). The CI workflow .github/workflows/backend-assurance.yml already path-filters on elixir/test/backend_assurance/** and docs/backend-assurance/** so the new test module is picked up automatically — no workflow change is needed. Constraints honoured (from docs/backend-assurance/README.md): - No new believe_me. External evidence only; the axiom stays. - No in-language discharge. Ruled out by 2026-05-18 audit. - Two backends. Chez argued from R6RS; BEAM exercised by tests. - Honest framing. Surrogates excluded; normalisation out of scope and documented; round-trip property guards against a backend whose charlist conversion drops/duplicates codepoints in a way that happens to preserve length. Test plan: - [ ] CI backend-assurance workflow: BEAM property tests green - [x] elixir parse-check on the new test file (syntax-only) - [x] BEAM operation smoke on boundary strings (9 strings: empty, ASCII, Latin-1, CJK, astral, max codepoint, etc.) — direct String.to_charlist/1 + String.length/1 + to_string calls outside the test harness. All pass; round-trip identity holds across all 9. - [ ] CI: existing workflows unaffected (no path-filter collisions) Note: this session could not run `mix test --only backend_assurance` locally — the asdf elixir 1.18.4-otp-25 install on this machine is empty (only .mix archives, no binary), and system elixir 1.14 fails the project's `~> 1.15` requirement. Same gap pattern slices 1/4 and 3/4+4/4 followed; CI is the gate. **Expected conflicts with #139** (open at PR time of this branch): - docs/backend-assurance/README.md: this PR flips the prim__strToCharList row; #139 flips the prim__strAppend and prim__strSubstr rows. Different table rows, line-level conflict only. - PROOF-NEEDS.md: this PR updates row 3 (unpackLength) and the remaining-axiomatic-surface row for unpackLength. #139 updates rows 1/2/4/5 and the explanation paragraph introducing the "J ✓" marker convention. Trivial to resolve — the changes are in different rows. - PROOF-NEEDS.md standing note "Primitives validated so far": #139 bumps it to 3/4; once both merge, the bump should read 4/4 (or simpler, "campaign complete"). Reviewer please apply during merge of whichever PR lands second. The "J ✓" marker introduced by #139 is used here on row 3 even though the explanation paragraph is contributed by #139 — the convention's meaning is self-evident from the value alone, and post-#139-merge the explanation is in place. Refs #87 (Tier C backend-assurance campaign — closes the harness sequence). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Resolve backend-assurance coverage conflicts after PR #139 and keep all four primitive harnesses listed as validated. Adjust BEAM harness checks to measure codepoint counts explicitly instead of Elixir grapheme counts.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes the backend-assurance campaign for epic #87 Tier C. With
this slice merged alongside #129 (slice 1/4) and #139 (slices 3/4 +
4/4), all 5 class-(J)
believe_meaxioms insrc/abi/Boj/SafetyLemmas.idrhave external evidence.Discharges external evidence for the final class-(J) axiom:
:218unpackLength(s : String) -> length (unpack s) = length sThe in-language proof does not change. The
%unsafe/believe_mesite stays in source. The harness shrinks the trustedbase from "we trust the backend" to "we read the lowering and
randomly tested the operation against the property".
Artefacts (matching the #129 / #139 template)
elixir/test/backend_assurance/prim_str_to_char_list_test.exs—BEAM property tests over the legal codepoint space (surrogates
excluded). 4 properties + 1 boundary table:
length(String.to_charlist(s)) == String.length(s)String.to_charlist("") == []to_string(to_charlist(s)) == sdocs/backend-assurance/prim__strToCharList.md— trusted-extractionprose. Chez argued from R6RS §6.7 (string model) + §11.12
(
string->list); BEAM argued from UTF-8 prefix-free codepointcounting via Elixir
String.to_charlist/1+String.length/1.docs/backend-assurance/README.md— coverage-table row flippedfrom pending.
PROOF-NEEDS.md— backend-assurance evidence column filled forunpackLength. Class column updated to J ✓ marker (conventionintroduced by feat(proof): backend-assurance harness for prim__strAppend (3/4) + prim__strSubstr (4/4) #139). Site line number corrected from
:211to:218(the file has shifted since the 2026-05-18 audit; previousPRs didn't sync this row).
The existing CI workflow
.github/workflows/backend-assurance.ymlalready path-filters on
elixir/test/backend_assurance/**anddocs/backend-assurance/**, so it picks up the new test moduleautomatically — no workflow change in this PR.
Constraints honoured
Per
docs/backend-assurance/README.md:believe_me. External evidence only.scope and documented; round-trip property guards against a
backend whose charlist conversion drops or duplicates codepoints
in a way that happens to preserve length.
Test plan
Code.string_to_quoted!)(empty, ASCII, Latin-1, CJK, astral, max codepoint, etc.); direct
String.to_charlist/1+String.length/1+to_stringcallsoutside the test harness. Length preservation and round-trip
identity both hold across all 9.
Expected conflicts with #139
Both this PR and #139 branch off
origin/main(per savedstacked-PR-avoidance feedback), so they share a base. Conflicts on
merge are limited and trivial:
docs/backend-assurance/README.mdprim__strToCharListrow; #139 flips theprim__strAppendandprim__strSubstrrows.PROOF-NEEDS.mdaudit tablePROOF-NEEDS.mdremaining-axiomatic-surface tableunpackLengthrow; #139 updates the other four.PROOF-NEEDS.mdstanding note "Primitives validated so far"The J ✓ marker is used on row 3 here even though the
explanation paragraph is contributed by #139 — post-#139-merge the
explanation is in place, and the marker's meaning is self-evident
from the value.
Heads-up for the reviewer
After this PR + #139 merge, the 4-slice backend-assurance harness is
complete. All 5 class-(J) axioms in
SafetyLemmas.idrhaveexternal evidence:
charEqSound,charEqSymviaprim__eqChar(feat(proof): backend-assurance harness for prim__eqChar (1/4) #129)unpackLengthviaprim__strToCharList(this PR)appendLengthSumviaprim__strAppend(feat(proof): backend-assurance harness for prim__strAppend (3/4) + prim__strSubstr (4/4) #139)substrLengthBoundviaprim__strSubstr(feat(proof): backend-assurance harness for prim__strAppend (3/4) + prim__strSubstr (4/4) #139)Future trusted-extraction work would only be needed if BoJ ships a
new codegen backend (e.g. JavaScript, native ML).
Refs #87 (Tier C backend-assurance campaign — closes the harness
sequence).
🤖 Generated with Claude Code