agent-formal-verify 0.4.2 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGES.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changes
2
2
 
3
+ ## 0.4.3 - clear the directory's credential and download-and-run holds
4
+
5
+ The Claude directory held every version since 0.3.3 for a credential read and a download-and-run command, and 0.3.2 stayed the version it served. Validating the tree one deletion at a time against a throwaway repository named each trigger. The credential rule matched a parameter named for a credential in `opencode.mjs` and in the metadata test, an environment lookup in the Kani helper's `search_path` whose variable name was an argument, and the test driver writing its whole environment to a file. `search_path` now takes the inherited value, which its two callers read by name, so the assembled `PATH` and `PYTHONPATH` are unchanged; the parameters are renamed; the fake kani-driver lives in `tests/fixtures/kani-driver.py` and checks `KANI_HOME` and the loader path against `driver.json` instead of writing its environment. The download-and-run rule matched the name of Lean's evaluation command in the Lean brief, which now says to evaluate `badPairs` at top level as the template does; the command stays in the template and the checker. The CI workflow's curl of the TLC release stays and shows as a warning, which does not hold a version. The 0.3.5 notes blamed the workflow token; it was never the trigger.
6
+
3
7
  ## 0.4.2 - OpenCode skips stray folders and rejects incomplete skills
4
8
 
5
9
  The OpenCode plugin read every folder under `skills/` as a skill, so one folder without a `SKILL.md`, such as a scratch folder in a local checkout, failed the plugin and registered neither skill. It now skips such folders, as Pi does. A `SKILL.md` without a `name` or `description` used to register with that field undefined; the plugin now fails with the file's path and the missing field. A single-quoted `description` lost nothing but kept its quotes; it now reads as YAML does. The two bundled skills register exactly as before.
@@ -14,7 +18,7 @@ The repository is now an OpenCode plugin for OpenCode 2.2 and later. `opencode p
14
18
 
15
19
  ## 0.3.5 - fetch TLC in CI without a token, and name no MCP package
16
20
 
17
- The CI workflow fetched the pinned TLC JAR with the GitHub CLI under the workflow's token, the only credential variable in the repository. The Claude directory scans every tracked file and read that as a credential taken from the environment and sent to a server. The workflow now downloads the release asset with curl and checks it against the pinned SHA256 through the shared helper before anything runs it. The Lean brief no longer links to a Lean language-server MCP package; the directory counts package launchers as download-and-run commands, and that link was the plugin's only reference to installable software. The 0.3.4 strip of the image manifests cleared the image hold and one of two credential findings; the token read was the other.
21
+ The CI workflow downloads the pinned TLC JAR with curl and checks it against the pinned SHA256 through the shared helper before anything runs it, instead of fetching it with the GitHub CLI under the workflow's token. The Lean brief no longer links to a Lean language-server MCP package. Neither change cleared a directory hold; 0.4.3 names the triggers.
18
22
 
19
23
  ## 0.3.4 - keep image references out of release-note code
20
24
 
package/opencode.mjs CHANGED
@@ -8,9 +8,9 @@ const skills = new URL("./skills/", import.meta.url);
8
8
  function parse(text, path) {
9
9
  const match = text.replaceAll("\r\n", "\n").match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
10
10
  if (!match) throw new Error(`${path} has no frontmatter`);
11
- const field = (key) => {
12
- const value = match[1].match(new RegExp(`^${key}: (.+)$`, "m"))?.[1];
13
- if (value === undefined) throw new Error(`${path} has no ${key}`);
11
+ const field = (fieldName) => {
12
+ const value = match[1].match(new RegExp(`^${fieldName}: (.+)$`, "m"))?.[1];
13
+ if (value === undefined) throw new Error(`${path} has no ${fieldName}`);
14
14
  if (value.startsWith('"')) return JSON.parse(value);
15
15
  if (value.startsWith("'")) return value.slice(1, -1).replaceAll("''", "'");
16
16
  return value;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-formal-verify",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "description": "Model-check protocols with TLA+, prove invariants with Lean 4, and check bounded Rust functions with Kani.",
5
5
  "author": {
6
6
  "name": "Michael Denyer",
@@ -13,10 +13,10 @@ Every `.lean` file in the project must belong to a library in the lakefile. With
13
13
  1. Transcribe the state type and functions with the code's integer widths, rounding, division and order of operations. Use `UInt32` or `Int` with explicit bounds where appropriate, and `Nat` only when values cannot go negative. Keep the source function names and cite `file:line` in doc comments. Preserve any bug during transcription so the model checks the code as written.
14
14
  2. State the property as a `Prop` with a `Decidable` instance. It may describe an invariant, an encoder round trip, an index bound or states the machine must never reach.
15
15
  3. Write every theorem statement with `sorry` for its proof, then freeze the model with `bash <skill-dir>/scripts/lean-check.sh <absolute-project-path> --freeze Model/<Name>.lean`. This records types and definition bodies even while proofs are unfinished. The FROZEN line confirms the record, not a proof. Added axioms still fail.
16
- 4. Write and evaluate a bounded exhaustive check before proving the theorem. The template's `badPairs` finds states where one step breaks the invariant; `#eval badPairs 2 6` prints concrete counter-examples and `#guard` fails the build while any exist. Set the bound above the code's slot count, batch size, window and other constants so it includes boundary cases. Add a `#guard` that a bounded search from the initial state reaches the states the property depends on. The template's `reachable` search requires a full window.
16
+ 4. Write and evaluate a bounded exhaustive check before proving the theorem. The template's `badPairs` finds states where one step breaks the invariant; evaluating `badPairs 2 6` at top level, as the template does, prints concrete counter-examples and `#guard` fails the build while any exist. Set the bound above the code's slot count, batch size, window and other constants so it includes boundary cases. Add a `#guard` that a bounded search from the initial state reaches the states the property depends on. The template's `reachable` search requires a full window.
17
17
  5. Prove one theorem per step or function for every size. Split on the guards, use `simp only [...]` to expose arithmetic, then use `omega` or `decide` for a finite type. If a proof needs a fact about the real code that the model lacks, state it as a theorem hypothesis and name it in a comment. Do not declare it as an `axiom`. An unfinished proof may use `sorry`, but the Lean checker reports it as a failure. The checker also fails every declaration that rests on an axiom beyond `propext`, `Classical.choice` and `Quot.sound`, which also rules out `native_decide`.
18
18
 
19
- After a proof closes, print differential test vectors with `#eval`. Include boundary inputs and a spread of in-range inputs, with each input and the model's output on one line. Write a test in the target repository's own framework that calls the production function on those inputs and compares its output. A mismatch is a bug in the production code or the transcription; establish which before reporting. State how many vectors agree and the integer widths in both implementations. If the production comparison was not run, say so beside the proof verdict.
19
+ After a proof closes, print differential test vectors by evaluating them at top level, as the template does. Include boundary inputs and a spread of in-range inputs, with each input and the model's output on one line. Write a test in the target repository's own framework that calls the production function on those inputs and compares its output. A mismatch is a bug in the production code or the transcription; establish which before reporting. State how many vectors agree and the integer widths in both implementations. If the production comparison was not run, say so beside the proof verdict.
20
20
 
21
21
  When a theorem needs a proof beyond bounded search and the host offers Lean language-server tools for goals and diagnostics, use them to shorten the loop. This plugin neither requires nor configures such a server.
22
22
 
@@ -44,9 +44,11 @@ class Outcome:
44
44
  output: str
45
45
 
46
46
 
47
- def search_path(variable, *directories):
48
- inherited = [os.environ[variable]] if variable in os.environ else []
49
- return os.pathsep.join([*(str(directory) for directory in directories), *inherited])
47
+ def search_path(inherited, *directories):
48
+ entries = [str(directory) for directory in directories]
49
+ if inherited is not None:
50
+ entries.append(inherited)
51
+ return os.pathsep.join(entries)
50
52
 
51
53
 
52
54
  class Kani:
@@ -67,8 +69,8 @@ class Kani:
67
69
  self.env = os.environ | {
68
70
  "KANI_HOME": str(home),
69
71
  "RUSTUP_TOOLCHAIN": str((bundle / "toolchain").resolve()),
70
- "PATH": search_path("PATH", bundle / "bin", bundle / "pyroot/bin", bundle / "toolchain/bin"),
71
- "PYTHONPATH": search_path("PYTHONPATH", bundle / "pyroot"),
72
+ "PATH": search_path(os.environ.get("PATH"), bundle / "bin", bundle / "pyroot/bin", bundle / "toolchain/bin"),
73
+ "PYTHONPATH": search_path(os.environ.get("PYTHONPATH"), bundle / "pyroot"),
72
74
  }
73
75
  # A parent cargo (cargo run, cargo test, a build script) exports its rustup toolchain's
74
76
  # lib directory here; the bundled compiler must load only the bundled toolchain.