Skip to content

chore: Shan-readiness — docs/AGENTS/.bazelversion + Verus quickstart doc - #32

Merged
avrabe merged 1 commit into
mainfrom
chore/shan-readiness-cleanup
May 3, 2026
Merged

chore: Shan-readiness — docs/AGENTS/.bazelversion + Verus quickstart doc#32
avrabe merged 1 commit into
mainfrom
chore/shan-readiness-cleanup

Conversation

@avrabe

@avrabe avrabe commented May 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Punch-list items from the second-pass "Shan reviewer" audit. Lands the in-progress Verus quickstart doc together with the doc hygiene needed for it to be honest. Companion to #31 (the Verus parse-error fix); this PR is doc/process only — no Rust source touched.

Changes

# File What
1 `AGENTS.md` Drop `nix develop --command` prefix from the four `bazel test` invocations (no flake.nix in the repo). Add a one-paragraph note about nix-build on PATH for rules_rocq_rust.
2 `.bazelversion` (new) Pin Bazel 9.1.0 (matches what bazelisk currently resolves to on Linux runners and locally on macOS). Without it, default would silently drift on the day of next release.
3 `docs/safety/verification-honesty.md` Append "Trusted code: `external_body`, `assume_specification`, `--no-cheating`" section. Closes the only real correctness gap — the doc was silent on Verus's trusted-base annotations. New table enumerates 133 `external_body` + 2 `assume_specification` calls per file (net_buf 36, pm 22, ipc 22, mmu 20, usage 18, thread_lifecycle 5, sched 4, poll 3, mpu 3).
4 `README.md` Anchor the headline "805 verified, 0 errors" claim with a footnote linking to verification-honesty.md and stating the number is gated by `formal-verification.yml`. No change to the number itself; PR #31 is the companion fix that gets the Verus job back green.
5 `docs/research/verus-quickstart-for-shan.md` (new) The hand-off doc for @arsene1995 (Shan) following microsoft/verus-proof-synthesis#45, with the two punch-list patches applied: `--verify-module` whole-crate-parse caveat + `assume_specification` listed under `--no-cheating`.

Audit cross-references

This PR addresses items from the punch list:

  • Item 1 (BLOCKER) — handled by fix(verus): paren-wrap struct literal in ring_buf ensures (CI red 8+ runs) #31 (separate PR; needs to land first or together for the README claim's footnote to stop being aspirational)
  • Item 2 (Doc warn that `--verify-module` parses whole crate) — applied to Shan doc + acknowledged via the parse-error footnote in verification-honesty.md
  • Item 3 (`--no-cheating` description missing `assume_specification`) — applied to Shan doc + full new section in verification-honesty.md
  • Item 4 (README line numbers into external verus.bzl) — disclaimer about line numbers already in the Shan doc
  • Punch-list "punch list" items 2-6 (this PR) — all applied

Test plan

🤖 Generated with Claude Code

…kstart

Punch-list items from the second-pass "Shan reviewer" audit. Lands
the in-progress quickstart doc together with the surrounding doc
hygiene needed for it to be honest:

1. AGENTS.md L107-121 — drop `nix develop --command` prefix from the
   four bazel test invocations. There is no flake.nix in the repo;
   the prefix was misleading. Replaced with plain `bazel test ...`
   plus a one-paragraph note that nix-build must be on PATH (and
   how to put it there) because rules_rocq_rust pulls Nix for
   toolchain resolution.

2. .bazelversion — new file pinning Bazel 9.1.0 (matches what
   bazelisk currently resolves to on Linux runners and locally on
   macOS). Without the pin, Bazelisk's default would silently drift
   the day a new release lands.

3. docs/safety/verification-honesty.md — append a "Trusted code:
   external_body, assume_specification, --no-cheating" section.
   Closes the only real correctness gap in the honesty story: the
   doc was silent on Verus's trusted-base annotations. New table
   enumerates 133 external_body instances + 2 assume_specification
   calls per file (net_buf 36, pm 22, ipc 22, mmu 20, usage 18,
   thread_lifecycle 5, sched 4, poll 3, mpu 3). Notes that today's
   CI does NOT pass --no-cheating, intentionally, and that a
   --no-cheating run would correctly fail until those trust units
   are discharged.

4. README.md L21 — anchor the headline "805 verified, 0 errors"
   claim with a footnote linking to verification-honesty.md and
   stating that the number is gated by the formal-verification.yml
   CI job (i.e., when that job is red, the number is stale until it
   goes green again). No change to the number itself; PR #31 is the
   companion fix that gets the Verus job back green.

5. docs/research/verus-quickstart-for-shan.md — new file. The
   hand-off doc for arsene1995 (Shan) following
   microsoft/verus-proof-synthesis#45. Includes the two table-cell
   patches from the punch list:
     - --verify-module: caveat that Verus parses the whole crate
       from src/lib.rs before isolating to the named module, so a
       parse error elsewhere poisons even targeted runs.
     - --no-cheating: now lists assume_specification alongside
       assume / admit / external_body, and points at the trusted-
       base inventory in verification-honesty.md.

Companion to PR #31 (which fixes the parse error in src/ring_buf.rs
that has had formal-verification.yml red on main since Apr 24).
This PR is doc/process hygiene only — no Rust source touched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented May 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@avrabe
avrabe merged commit 1efcca8 into main May 3, 2026
56 of 59 checks passed
@avrabe
avrabe deleted the chore/shan-readiness-cleanup branch May 3, 2026 05:47
avrabe added a commit that referenced this pull request May 10, 2026
Pushed the wasm-cross-LTO experiment all the way to a buildable bench
ELF integrated via wasm-ld+arm-ar+linker-substitute. Discovered an
additional synth backend bug while attempting silicon measurement:

  synth's emitted memset/memcpy/memmove don't terminate correctly on
  Zephyr's startup `memset(bss, 0, sizeof(bss))` invocation. The chip
  hangs in memset+0x4c forever, bouncing between offsets 0x668 and
  0x67e in a tight inner loop. The synth disassembly reveals i64
  shift instructions (`subs.w r3, r2, #32; rsb r3, r2, #32;
  lsl.w r3, r1, r3`) lowered into what should be a byte-counter loop
  — same root cause as the u64-packed FFI return codegen issue
  documented earlier: synth's i64 codegen is incomplete.

End-to-end status:

  - wasm-ld static-merging: WORKS. shim.wasm.o + libgale_ffi.a → 1MB
    merged.wasm with z_impl_k_sem_give and gale_k_sem_give_decide both
    present.

  - synth inlining at merged-module scope: STRUCTURALLY WORKS. The
    output `z_impl_k_sem_give` body has zero bl gale_k_sem_give_decide
    instructions. Verified by disassembly. 138 bytes vs LLVM-LTO's
    82 bytes — 1.68x larger but inlined.

  - Bench integration: BUILDS. CMake bench builds with
    -DGALE_WASM_LTO_OVERRIDE_SEM_GIVE=1 + custom libgale_ffi.a +
    --allow-multiple-definition. Final ELF 219 KB FLASH, 66 KB RAM.

  - Chip boot: BLOCKED. PC stuck in synth-emitted memset. Workarounds
    via objcopy --weaken-symbol, --strip-symbol, --redefine-sym all
    failed to evict synth's broken memset bytes from the final ELF.

Three synth backend issues filed against pulseengine/synth, ordered:

  1. (blocker) memset/memcpy/memmove i64-codegen non-termination —
     prevents the merged-wasm bench from booting at all.
  2. u64-packed FFI return unpacking — ~50% of the LTO-parity size
     delta. Same i64-codegen root cause as #1.
  3. wasm linear-memory access lowering — ~20% of the size delta.
     Cosmetic compared to #1 and #2.

Plus one issue against pulseengine/loom:

  - Z3 SortDiffers panic in inline_functions pass on i64-heavy
    wasm modules. Without loom, the verified-LTO claim doesn't hold.

The structural claim — "wasm-cross-LTO via PulseEngine pipeline
dissolves the C↔Rust seam at wasm IR level" — is **proven by
disassembly**. The cyclical claim — "silicon timing matches LLVM-LTO"
— is **blocked on synth's memset codegen**. Neither is a fundamental
architectural barrier; both are well-scoped engineering work.

This commit only updates the NOTES with the integration findings.
The bench source is restored to clean state (the gale_sem.c
#ifndef edit was transient) and verified building unchanged at
27 KB FLASH at the canonical rustc-direct path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
avrabe added a commit that referenced this pull request Jun 24, 2026
…ves its toolchain via execroot path

The Bazel 8 pin got the module loading, analyzing, building (16 actions) and the
three tests EXECUTING — they then died at runtime, all three:
  ...renode_1.15.3+...-dotnet_portable/renode-test: No such file or directory

Root cause: rules_renode's renode_test wrapper (defs.bzl:104) invokes the
toolchain binary by its EXECROOT-relative path
"external/rules_renode++renode+renode_toolchain/.../renode-test". Inside the
test's runfiles tree (CWD = ...runfiles/_main) that path only resolves if Bazel
materialises the legacy "_main/external/<repo>" symlinks — i.e.
--legacy_external_runfiles. Its default flipped to false in recent Bazel, so the
wrapper can't find renode-test. The rule was authored when it defaulted true
(matching upstream's bazelisk-latest CI at the time). Setting it true in the
module .bazelrc restores resolution.

Verified in 8.7.0 that the flag exists and defaults false (bazel help test).
This is the same upstream-friction item (task #32): the rule should rlocation
the toolchain binary instead of relying on the execroot path + legacy symlinks.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
avrabe added a commit that referenced this pull request Jun 24, 2026
…stack (north-star end-to-end) (#105)

* feat(gust): gust_stack — the dissolved CM composition driven ON the kiln stack (north-star end-to-end)

gust_fused proved the dissolve (CM → meld fuse → synth → run-demo()=53, one-shot).
gust_stack proves the other half — "running on our stack": the kiln-async
Scheduler (gust's executor) drives the SAME dissolved composition (gale-app-demo +
gale-kiln, fused → native) as the body of a kiln task, re-polled every scheduler
round. Bare-metal qemu Cortex-M3: 5000 poll rounds, dissolved run-demo()=53 each
round, 0 mismatches, no wasm runtime.

So the whole BYO-OS north-star executes end-to-end: components on top (Component
Model) → meld fuse (one merged-memory module) → synth dissolve (native) → driven
by the kiln-async scheduler on the gust stack.

- src/bin/gust_stack.rs — kiln Scheduler + the dissolved run-demo as the task body.
- build.rs — links fused.o into gust_stack too (scoped -bin=).
- DEMONSTRATOR.md — the on-stack section.
- rivet FIND-BYOOS-008 (verifies REQ-BYOOS-MULTIRT-001, related-to SAC-BYOOS-EXEC);
  rivet validate PASS.

Next rungs: a richer driven example (engine_control control-loop as the task body)
and the same on real silicon (G474RE/F100).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(gust): rung 1 — engine_control driven on the kiln stack (realistic sensors→actuators loop)

gust_control: the kiln-async scheduler drives the dissolved engine_control
algorithm (synth-dissolved control_step, same as control.c / the WIT component)
as its task body — one control tick per scheduler round: synthesize a crank
sample → control_step → spark/fuel actuators. Where gust_stack drives the
fixed-result run-demo, this drives a real control loop, the workload an engine
node runs.

Bare-metal qemu Cortex-M3: gate control_step(3000,50,80,0) = spark 33° fuel 2300µs
(== C/wasmtime); 5000 ticks; last sample control_step(4700,75,80,0) = spark 38°
fuel 2440µs (== wasmtime). Dissolved, no runtime, on the gust/kiln stack.

Two TCB-integration findings (documented):
- control_step needs synth --native-pointer-abi (it reads ignition/fuel TABLES
  from wasm linmem; the plain --relocatable .o emits no data section → reads 0)
  + --shadow-stack-size 8192 (else it reserves the full 128KB linmem as .bss and
  overflows RAM — synth#383 class). And an r11=0 TCB trampoline: native-pointer-abi
  pins the linmem base to r11==0, which the kiln scheduler clobbers, so in-loop
  calls need r11 re-zeroed (same shim the dissolved gust kernel uses).
- Built with SYNTH_NO_LOCAL_PROMOTE=1: v0.14.0's default-on local promotion
  register-exhausts on this denser function (filing to synth — the promotion
  cost-gate needs register-pressure awareness; the non-promoted lowering is fine).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(gust): rung 2 — engine_control on the kiln stack runs on REAL Cortex-M4 silicon (G474RE)

gust_control flashed on the physical NUCLEO-G474RE (probe-rs): the kiln-async
scheduler drives the dissolved engine_control control loop on real hardware —
control_step(3000,50,80,0)=spark33/fuel2300 (== C/wasmtime), 5000 ticks, last
(4700,75,80,0)=spark38/fuel2440 (== wasmtime). The full north-star executes on
silicon: components (CM) → meld fuse / synth dissolve → driven by kiln on gust,
Cortex-M4, no runtime.

- wasm-kernel/control_step-cm4.o — cortex-m4 dissolve (native-pointer-abi +
  shadow-stack-size 8192, SYNTH_NO_LOCAL_PROMOTE per synth#474); build.rs picks
  cm4 for thumbv7em / cm3 for qemu+F100 (the cortex-m3 .o won't link into M4).
- silicon/RESULTS-g474re.md — the on-silicon rung-2 record.
F100/M3 silicon pending the board.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(gust): rules_renode CI-cycle gate — 3 M3 device classes (kernel 8K + control 64K + real STM32F100)

The durable answer to "why not renode": a one-off `renode --console` on a dev
box isn't reproducible — a Bazel `renode_test` target is. Extend the existing
self-contained renode-test module (own MODULE.bazel + git_override on
pulseengine/renode-bazel-rules) from one target to three M3 device classes,
each with a checked-in hermetic .repl (no dependency on the pinned Renode's
bundled platform set):

  - gust-renode         generic M3 8K  — dissolved gust kernel (existing)
  - gust-control-renode M3 64K (F103RE)— north-star rung 1: kiln driving the
                                          dissolved engine_control control_step;
                                          deterministic cycle count
  - gust-f100-renode    STM32F100RB    — the real STM32VLDISCOVERY part (8K/128K),
                                          dissolved kernel pinned to its memory class

gust_control's 9408 B .bss needs the 64K class — it does NOT fit the F100's 8K
SRAM, so the F100 target runs the kernel and the 64K F103RE-class runs the
control loop (honest device-fit constraint).

Verified locally on the same Renode engine (1.16.1): gust_control = 0x162CB6 =
1,453,238 instr over RunFor 2s, no fault, SP at the 64K top; the kernel on F100
= 200,000,000 instr, bss fits (ends 0x200012B4 under the 8K top 0x20002000), no
fault. M3 is cacheless so instr ≈ cycles — this is the CI-reproducible cycle-
class seed, complementary to the qemu correctness gate and the G474RE DWT bench.

Honest finding (documented in README): the SemihostingUart heartbeat is NOT
capturable headless on the macOS portable (CreateFileBackend on cpu.uartSemihosting
captures nothing), and the repo's proven `Wait For Line` tests assert on a real
USART, not a SemihostingUart — so a content assertion is deferred to CI rather
than added blind (would risk a 120s timeout -> CI-red). Correctness stays gated
by the qemu exit-code run + gale_decider_diff; Renode adds the cycle dimension.

Also adds a targeted .gitignore rule for .claude/pulseengine/ agent working
state (per the issue-hunt skill) so per-package working-context.md can't be
committed by accident.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* ci(gust): wire the renode-test bazel targets into CI + fix Bazel-9 load break

No workflow ran the gust renode-test bazel targets — so the module's "first
green is in CI" was aspirational, and a latent break went unnoticed: under the
repo's pinned Bazel 9.1.0, rules_renode (pinned 5fc76ad) fails to LOAD because
its renode/defs.bzl uses PyInfo / py_* as builtins, which Bazel 8+ removed
("name 'PyInfo' is not defined", defs.bzl:10 and :179). The existing
gust-renode target was equally broken; nothing exercised it.

Fixes:
- benches/gust/renode-test/.bazelrc: re-inject the legacy Python globals via
  --incompatible_autoload_externally so the pinned rule loads unmodified, and
  --check_direct_dependencies=off for the rules_python 0.36.0->1.7.0 transitive
  skew. Verified locally: `bazel query //:*` now resolves all three targets on
  Bazel 9.1.1 (was: load error).
- .github/workflows/gust-renode.yml: a dedicated Linux job running
  `bazel test //:gust-renode //:gust-control-renode //:gust-f100-renode`. This
  is the actual CI runner that makes the dissolved-artifact, real-M3-model,
  deterministic-cycle gate reproducible (no board), complementing renode-tests.yml
  (Zephyr sem robots) and zephyr-tests.yml (qemu M3 functional).

Upstream friction to file on pulseengine/renode-bazel-rules: defs.bzl should
`load("@rules_python//python:defs.bzl", "PyInfo", ...)` rather than rely on the
removed builtins (breaks on Bazel 8/9 without the autoload shim).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* ci(gust): pin renode-test module to Bazel 8 (rules_renode is Bazel-9-incompatible)

First CI run failed at analysis on Bazel 9.1 with a second, deeper break beyond
PyInfo: "@@rules_shell+//shell/runfiles:runfiles_impl must produce a single
file". Root cause: the pinned rules_renode (5fc76ad == upstream HEAD, nothing
newer to bump to) is authored against Bazel 8 — it relies on the PyInfo/py_*/sh_*
globals AND the old rules_shell runfiles_impl single-file behavior, all of which
Bazel 8 provides via the DEFAULT --incompatible_autoload_externally migration
set. Bazel 9 emptied that set (migration window closed), so gale's root-pinned
9.1.0 breaks the module at both load and analysis. Upstream's own CI uses
bazelisk-latest with no .bazelversion (i.e. Bazel 8), so 8.x is the known-good
config.

Fix: a module-local .bazelversion (8.7.0) — bazelisk picks the nearest one from
cwd, so it overrides root 9.1.0 only for this self-contained module (own
MODULE.bazel; the rest of gale stays on 9.1.0). Dropped the explicit
--incompatible_autoload_externally override from .bazelrc: on Bazel 8 its
default set already covers both py_* and sh_*; replacing it with only the py
symbols would have broken the sh_* autoload the runfiles path needs. Verified:
`bazel query //:*` resolves all three targets on 8.7.0 with no autoload flag.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* ci(gust): --legacy_external_runfiles=true — renode_test wrapper resolves its toolchain via execroot path

The Bazel 8 pin got the module loading, analyzing, building (16 actions) and the
three tests EXECUTING — they then died at runtime, all three:
  ...renode_1.15.3+...-dotnet_portable/renode-test: No such file or directory

Root cause: rules_renode's renode_test wrapper (defs.bzl:104) invokes the
toolchain binary by its EXECROOT-relative path
"external/rules_renode++renode+renode_toolchain/.../renode-test". Inside the
test's runfiles tree (CWD = ...runfiles/_main) that path only resolves if Bazel
materialises the legacy "_main/external/<repo>" symlinks — i.e.
--legacy_external_runfiles. Its default flipped to false in recent Bazel, so the
wrapper can't find renode-test. The rule was authored when it defaulted true
(matching upstream's bazelisk-latest CI at the time). Setting it true in the
module .bazelrc restores resolution.

Verified in 8.7.0 that the flag exists and defaults false (bazel help test).
This is the same upstream-friction item (task #32): the rule should rlocation
the toolchain binary instead of relying on the execroot path + legacy symlinks.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant