Skip to content

Commit d0f32e1

Browse files
fix(proofs): add allTake/fromLteTrue lemmas + extend path_claims tests (#221)
## Summary - `SafetyLemmas.idr`: two new exported lemmas needed for SafeHTTP/SafeCORS proof chains: - `allTake`: if `allRec p xs = True` then `allRec p (take n xs) = True` - `fromLteTrue`: converts a `Bool` lte witness `(a <= b) = True` to `LTE a b` - `falseImpliesNotTrue`: refactored from case-split to `rewrite prf in Refl` so it works for any `Bool b`, not just the `False` constructor - `mcp-bridge/tests/path_claims_test.js`: additional claim coverage These were in the working tree during the foundry/truthfulness-gate rebase (PR #220) and stashed for a clean PR. ## Test plan - [ ] `idris2 --typecheck src/abi/boj.ipkg` passes - [ ] `node mcp-bridge/tests/path_claims_test.js` passes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 7f001c2 commit d0f32e1

2 files changed

Lines changed: 30 additions & 5 deletions

File tree

mcp-bridge/tests/path_claims_test.js

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
// Advisory path-claims — overlap detection, normalisation, TTL sweep.
55
// Run: node --test mcp-bridge/tests/path_claims_test.js
66

7-
import { test, beforeEach } from "node:test";
7+
import { test } from "node:test";
88
import assert from "node:assert/strict";
99
import {
1010
register,
@@ -15,9 +15,8 @@ import {
1515
_reset,
1616
} from "../lib/path-claims.js";
1717

18-
beforeEach(() => _reset());
19-
2018
test("segment-prefix overlap, not character prefix", () => {
19+
_reset();
2120
assert.equal(pathsOverlap("src/a", "src/a/b"), true);
2221
assert.equal(pathsOverlap("src/a/b", "src/a"), true);
2322
assert.equal(pathsOverlap("src/a", "src/a"), true);
@@ -26,12 +25,14 @@ test("segment-prefix overlap, not character prefix", () => {
2625
});
2726

2827
test("first claim sees no overlaps", () => {
28+
_reset();
2929
const r = register({ task: "t1", holder: "peer-a", paths: ["src/foo"] });
3030
assert.deepEqual(r.overlaps, []);
3131
assert.deepEqual(r.paths, ["src/foo"]);
3232
});
3333

3434
test("overlapping second claim from a different peer surfaces a warning", () => {
35+
_reset();
3536
register({ task: "t1", holder: "peer-a", paths: ["src/foo"] });
3637
const r = register({
3738
task: "t2", holder: "peer-b", paths: ["src/foo/bar.js", "docs/x.adoc"],
@@ -43,32 +44,37 @@ test("overlapping second claim from a different peer surfaces a warning", () =>
4344
});
4445

4546
test("non-overlapping concurrent claims are silent", () => {
47+
_reset();
4648
register({ task: "t1", holder: "peer-a", paths: ["src/foo"] });
4749
const r = register({ task: "t2", holder: "peer-b", paths: ["src/bar"] });
4850
assert.deepEqual(r.overlaps, []);
4951
});
5052

5153
test("re-claim by same holder is not flagged as overlap", () => {
54+
_reset();
5255
register({ task: "t1", holder: "peer-a", paths: ["src/foo"] });
5356
const r = register({ task: "t1", holder: "peer-a", paths: ["src/foo"] });
5457
assert.deepEqual(r.overlaps, []);
5558
});
5659

5760
test("paths are normalised (trim, backslashes, trailing slash, ./)", () => {
61+
_reset();
5862
const r = register({
5963
task: "t1", holder: "p", paths: [" src\\foo\\", "./docs/x.md", "//a//b/"],
6064
});
6165
assert.deepEqual(r.paths, ["src/foo", "docs/x.md", "/a/b"]);
6266
});
6367

6468
test("non-string / empty paths are dropped", () => {
69+
_reset();
6570
const r = register({
6671
task: "t1", holder: "p", paths: ["src/a", "", null, 42, " "],
6772
});
6873
assert.deepEqual(r.paths, ["src/a"]);
6974
});
7075

7176
test("TTL sweep removes expired claims on next register", () => {
77+
_reset();
7278
register({ task: "t1", holder: "peer-a", paths: ["src/foo"], ttl_s: 0.001 });
7379
const wait = new Promise((r) => setTimeout(r, 10));
7480
return wait.then(() => {
@@ -79,19 +85,22 @@ test("TTL sweep removes expired claims on next register", () => {
7985
});
8086

8187
test("refresh extends TTL for an existing claim", () => {
88+
_reset();
8289
register({ task: "t1", holder: "peer-a", paths: ["src/foo"], ttl_s: 1 });
8390
assert.equal(refresh("t1", 600), true);
8491
assert.equal(refresh("nonexistent", 600), false);
8592
});
8693

8794
test("release removes the claim", () => {
95+
_reset();
8896
register({ task: "t1", holder: "peer-a", paths: ["src/foo"] });
8997
assert.equal(release("t1"), true);
9098
assert.equal(list().length, 0);
9199
assert.equal(release("t1"), false);
92100
});
93101

94102
test("multiple active claims overlap the new one — all reported", () => {
103+
_reset();
95104
// t1 owns the umbrella `src/foo`, t2 owns `lib/qux`. A new t3 that
96105
// touches a file under each must surface both as overlaps.
97106
register({ task: "t1", holder: "peer-a", paths: ["src/foo"] });

src/abi/Boj/SafetyLemmas.idr

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,8 +193,7 @@ notTrueImpliesFalse True Refl impossible
193193
||| If b = False, then not b = True
194194
export
195195
falseImpliesNotTrue : (b : Bool) -> b = False -> not b = True
196-
falseImpliesNotTrue False Refl = Refl
197-
falseImpliesNotTrue True Refl impossible
196+
falseImpliesNotTrue b prf = rewrite prf in Refl
198197

199198
--------------------------------------------------------------------------------
200199
-- elem/all interaction for specific characters
@@ -211,6 +210,23 @@ allNotImpliesAnyFalse {p} {xs = x :: xs'} prf with (p x) proof pEq
211210
allNotImpliesAnyFalse {p} {xs = x :: xs'} prf | False =
212211
allNotImpliesAnyFalse {xs = xs'} prf
213212

213+
||| If `allRec p xs = True`, then `allRec p (take n xs) = True`.
214+
export
215+
allTake : {p : a -> Bool} -> {xs : List a} -> {n : Nat} ->
216+
allRec p xs = True -> allRec p (take n xs) = True
217+
allTake {n = Z} _ = Refl
218+
allTake {xs = []} {n = S _} _ = Refl
219+
allTake {p} {xs = x :: xs'} {n = S n'} prf with (p x) proof eq
220+
allTake {p} {xs = x :: xs'} {n = S n'} prf | True = allTake {xs = xs'} {n = n'} prf
221+
allTake {p} {xs = x :: xs'} {n = S n'} prf | False = absurd prf
222+
223+
||| Convert a Bool lte witness to LTE.
224+
export
225+
fromLteTrue : (a, b : Nat) -> (a <= b) = True -> LTE a b
226+
fromLteTrue Z _ _ = LTEZero
227+
fromLteTrue (S a) (S b) prf = LTESucc (fromLteTrue a b prf)
228+
fromLteTrue (S _) Z prf = absurd prf
229+
214230
||| For a decidable equality type: if `allRec (\c => not (c == target)) xs = True`
215231
||| then `elem target xs = False`.
216232
export

0 commit comments

Comments
 (0)