From 514d2db1623d0d372459a4d2ac66ab97bb9fa003 Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Thu, 27 Aug 2026 21:37:57 +0200 Subject: [PATCH 01/10] =?UTF-8?q?finding:=20the=20complete=20wasm=20stack?= =?UTF-8?q?=20CANNOT=20be=20composed=20yet=20=E2=80=94=20falcon=20imports?= =?UTF-8?q?=20no=20gust:os,=20so=20the=20DD-026=20seam=20has=20nothing=20t?= =?UTF-8?q?o=20plug=20(AFD-043,=20jess-owned)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prompted by a direct question jess could not answer honestly: "have you worked to have this integrated with gale to build the complete wasm stack". The answer was NO — and checking revealed it is not merely undone but currently IMPOSSIBLE with what is published. DD-026 pins: "The flight app IMPORTS gust:os; gale's OCI component EXPORTS it; composition happens ALONG gust:os". That is not what ships. MEASURED: falcon rate@1.134.1 imports pulseengine:falcon-cascade/types (TYPE-ONLY); gust: refs = 0 falcon flight imports: NONE fused cascade core imports: 0 gale-nano 0.7.0 exports gust:os/{time,log,spawn,exec,timer}; imports gust:hal/mmio + taskdisp EMPIRICAL: `wac plug rate.wasm --plug gale-nano.wasm` fails — "the socket component had no matching imports for the plugs that were provided". The halves are disjoint. NEITHER SUPPLIER IS WRONG. falcon being a pure computation library with zero OS dependency is why it self-contains and reached 5/5 on both flight cores. gale-nano exporting gust:os is its contract. What is missing is the piece BETWEEN them: a flight APPLICATION component importing gust:os (scheduling/time), importing the cascade, exporting an entry the runtime starts. Today nothing calls the cascade on a schedule — the lowered image blx'es one export at reset and spins. OWNERSHIP SETTLED: this mapping is JESS'S — we own integration under DD-026. Asked gale on #223 for the intended SHAPE of the gust:os side (which interface drives a periodic loop under DD-025's ARINC-653 outer scheduler; whether there is a canonical app world; who provides taskdisp; which way round the wac plug goes) rather than inventing it and having everyone build against a guess. Second missing piece, also jess-owned: gust:hal read32/write32, gale-nano's residual. The stack needs BOTH before "complete" means anything. Status open — a live gap with jess as owner, not a resolved finding. rivet validate PASS. Co-Authored-By: Claude Opus 4.8 --- artifacts/findings.yaml | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/artifacts/findings.yaml b/artifacts/findings.yaml index eaa7539..fa5b913 100644 --- a/artifacts/findings.yaml +++ b/artifacts/findings.yaml @@ -1584,6 +1584,46 @@ artifacts: - type: traces-to target: REQ-PIX-001 + - id: AFD-043 + type: ai-found-defect + title: THE COMPLETE WASM STACK CANNOT BE COMPOSED YET — falcon imports NO gust:os, so the DD-026 seam has nothing to plug; the flight APPLICATION component is missing and it is JESS'S to build + status: open + description: > + 2026-08-27, prompted by a direct question jess could not answer honestly ("have you worked to have + this integrated with gale to build the complete wasm stack"). The answer was NO - and checking + revealed it is not merely undone but currently IMPOSSIBLE with what is published. + DD-026 pins: "The flight app IMPORTS gust:os; gale's OCI component EXPORTS it; composition happens + ALONG gust:os (wac plug + meld fuse --memory shared)". That is not what ships. + MEASURED ON THE PUBLISHED ARTIFACTS: + falcon rate@1.134.1 imports pulseengine:falcon-cascade/types@0.7.0 (TYPE-ONLY); gust: refs = 0 + falcon flight imports: NONE + fused cascade core imports: 0 + gale-nano 0.7.0 exports gust:os/{time,log,spawn,exec,timer}; imports gust:hal/mmio + taskdisp + EMPIRICAL: `wac plug rate.wasm --plug gale-nano.wasm` fails - "the socket component had no matching + imports for the plugs that were provided". No import/export pair connects. The halves are disjoint. + NEITHER SUPPLIER IS WRONG: falcon being a PURE COMPUTATION library with zero OS dependency is a good + property - it is why it self-contains, lowers cleanly and reached 5/5 on two cores. gale-nano + exporting gust:os is exactly its contract. What is missing is the piece BETWEEN them. + *** THE MISSING PIECE - a flight APPLICATION component *** that IMPORTS gust:os (scheduling, time, + logging), IMPORTS the falcon cascade interfaces, and exports an entry the runtime starts. Today + nothing calls the cascade on a schedule: the synth self-contained image `blx`es the first export at + reset and spins (observed at PC 0x16c). The stack has a hole where the control loop's DRIVER belongs. + *** OWNERSHIP SETTLED (user, 2026-08-27): THIS MAPPING IS JESS'S. *** jess owns integration and + composition under DD-015/DD-026, so the driver component - the thing that maps the falcon cascade + onto gust:os - is jess's to build, not relay's and not gale's. jess is to consult gale on the + intended shape of the gust:os side rather than guess, since gale owns that interface. + SECOND MISSING PIECE, also jess-owned: gust:hal (read32/write32), gale-nano's residual. The stack + needs BOTH the application seam AND the native HAL before "complete" means anything. + STATUS open deliberately - a live gap with jess as the owner, not a resolved finding. + tags: [dd-026, composition, gust-os, gale, application-seam, gap, jess-owned] + fields: + detected-by: jess attempted the falcon+gale-nano composition for the first time, 2026-08-27 + severity: major + triage-status: in-progress + links: + - type: traces-to + target: REQ-PIX-001 + - id: AFD-041 type: ai-found-defect title: Clean-room verify of the 5/5 cascade claims — 7/7 CONFIRMED and one claim STRENGTHENED (bit-reproducible image), but the oracle's own prose was STALE and CI gated execution without provenance From 17fb642f421d4ebc8a4447518dba0a0fd9ccaf86 Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Thu, 27 Aug 2026 21:43:41 +0200 Subject: [PATCH 02/10] =?UTF-8?q?finding:=20gale=20answered=20the=20applic?= =?UTF-8?q?ation-seam=20shape=20=E2=80=94=20and=20gave=20two=20measured=20?= =?UTF-8?q?warnings=20that=20change=20jess's=20partition=20sizing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gale's answer to the four questions, recorded on AFD-043: METHOD CORRECTION: use `wac compose` with a WAC script, NOT `wac plug`. jess's error had a cause unrelated to the components — a plug result exports only the SOCKET's exports, and plug wires plugs into the socket's imports only, never plug->plug. So plug can never produce a composite exporting five capabilities from five components. gale hit this themselves and moved off plug. CANONICAL WORLD: `world app` — imports time/log/spawn/channel/io/timer, exports `run: func() -> u32`, the entry the runtime calls. Examples app-time / app-tl / app-ts (closest to us). gale volunteered that `world app-timer` is declared but UNIMPLEMENTED — building the first is new ground. taskdisp IS JESS'S alongside gust:hal — one function, poll-task(id)->u32 — and it is ENFORCED: the fused build gates the residual to exactly gust:hal/* + gust:os/taskdisp. The gate also REJECTS a gust:sched/* residual, which would mean spawn/timer were never bound to the one executor. TWO MEASURED WARNINGS THAT CHANGE OUR PARTITION SIZING (DD-025): (i) do NOT size a budget from a DWT high-water mark — T4 static WCET bounds only 3 of 31 functions (scry#144 loop-bound inference). An observed budget is an observation, not a bound. (ii) THE SWITCH COSTS A TICK PER WINDOW — Switcher::tick fires at offset+budget-1, so a partition owning k windows receives Theta-k, NOT Theta. Sizing against the raw budget is UNSOUND, machine-checked in Lean, first violation t=2560 (one window) / t=828 (three). A 1 kHz loop sized that way 'will look fine in short tests and fail later'. This directly constrains the two-partition MPU split jess was designing. STILL OPEN, and gale says so: timer-ISR vs partition-window for driving poll-round. gale asked jess to report which way the integration forces it, as DD-025 evidence. rivet validate PASS. Co-Authored-By: Claude Opus 4.8 --- artifacts/findings.yaml | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/artifacts/findings.yaml b/artifacts/findings.yaml index fa5b913..3674f99 100644 --- a/artifacts/findings.yaml +++ b/artifacts/findings.yaml @@ -1615,6 +1615,45 @@ artifacts: SECOND MISSING PIECE, also jess-owned: gust:hal (read32/write32), gale-nano's residual. The stack needs BOTH the application seam AND the native HAL before "complete" means anything. STATUS open deliberately - a live gap with jess as the owner, not a resolved finding. + *** GALE ANSWERED 2026-08-27 — the shape, and a correction to jess's METHOD ***: + (4) NEITHER PLUG DIRECTION — USE `wac compose` WITH A WAC SCRIPT, NOT `wac plug`. jess's error had + a specific cause unrelated to the components: a plug result exports only the SOCKET's exports, + and plug wires plugs into the socket's imports ONLY, never plug->plug. So plug can NEVER produce + a composite exporting five capabilities living in five components - there is no socket whose + exports are the union. gale hit this themselves and moved build-os-ts.sh off plug. The node is + built by `wac compose` over an explicit script naming the edges, with `{ ... }` forwarding each + instance's remaining unsatisfied imports outward. Documented in drivers/FUSED-GUSTOS.md. + (2) THERE IS A CANONICAL WORLD: `world app` in wit-os/gust-os.wit — imports time, log, spawn, + channel, io, timer; and *** exports `run: func() -> u32` — the entry the runtime calls ***. + Three working examples to copy, smallest first: drivers/app-time (time only), app-tl (+log), + app-ts (+spawn — the first EXECUTOR-BACKED capability across the syscall seam, and the closest + to jess's case). CAUTION gale volunteered: `world app-timer` is DECLARED BUT HAS NO + IMPLEMENTATION — building the first one is new ground. + (3) `taskdisp` IS JESS'S, alongside gust:hal. Not another gale component. One function: + `poll-task: func(id: u32) -> u32` — the node's trusted dispatch saying whether task id is done; + the executor calls it, the embedder implements it. ENFORCED, not merely intended: + build-fused-gustos.sh gates the composite's residual to EXACTLY gust:hal/* + gust:os/taskdisp + and fails otherwise. Note what that gate REJECTS: a `gust:sched/*` residual — that would mean + spawn/timer were never bound to the one executor, handing a downstream the power to mutate + scheduler state and turning "one scheduler" from a fact into a claim. + (1) THE PERIODIC LOOP is (b)-with-a-correction: the app calls `spawn.start`, something drives + `exec.poll-round`, and the executor then calls jess's `taskdisp.poll-task` per ready task. + exec-provider owns the SINGLE executor and exports gust:sched/tasks; spawn and timer are + STATELESS consumers of it. timer consumes `time` rather than reading a register behind time's + back — deliberately, so there is ONE CLOCK. + *** TWO MEASURED WARNINGS FROM GALE THAT CHANGE JESS'S PARTITION SIZING (DD-025) ***: + (i) DO NOT SIZE A BUDGET FROM A DWT HIGH-WATER MARK. T4 static WCET currently bounds only 3 of 31 + functions on the fused gust:os object; the blocker is loop-bound inference (scry#144). Until + that lands, a 1 kHz window budget derived from observation is AN OBSERVATION, NOT A BOUND. + (ii) *** THE SWITCH COSTS A TICK PER WINDOW *** — Switcher::tick fires at `offset + budget - 1`, + so a partition owning k windows receives Θ − k, NOT Θ. Instantiating the schedulability + analysis with the RAW budget is UNSOUND — machine-checked in proofs/lean/PartitionSupply.lean, + first violation at t=2560 for one window and t=828 for three. A 1 kHz loop sized against the + raw window budget "will look fine in short tests and fail later". This directly constrains the + two-partition MPU split jess was designing: window budgets must be instantiated at Θ − k. + STILL OPEN AND GALE SAYS SO: whether poll-round is driven from a TIMER ISR or from the PARTITION + WINDOW is a DD-025 question gale has NOT answered with evidence. gale asked jess to report which way + the integration forces it, as evidence for DD-025. tags: [dd-026, composition, gust-os, gale, application-seam, gap, jess-owned] fields: detected-by: jess attempted the falcon+gale-nano composition for the first time, 2026-08-27 From 1e34aaf37144f9999ad39a1f89a12f302f9c63d0 Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Thu, 27 Aug 2026 21:53:32 +0200 Subject: [PATCH 03/10] =?UTF-8?q?feature:=20the=20jess=20flight-app=20WIT?= =?UTF-8?q?=20world=20=E2=80=94=20the=20falcon/gust:os=20mapping,=20built?= =?UTF-8?q?=20from=20the=20SHIPPED=20artifacts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AFD-043 identified the missing piece: falcon imports no gust:os, gale-nano exports it, nothing connected them. jess owns integration under DD-026, so this world is jess's to define. gale supplied the shape on gale#223. THE MAPPING (wit/flight-app/world.wit) — resolves clean, negative-controlled: world app import gust:os/time, gust:os/spawn (gale runtime, DD-026 layer i) import pulseengine:falcon-cascade/{types,rate,mixer} (relay data seams, layer ii) export run: func() -> u32 (gale's canonical entry) EVERY DEPENDENCY EXTRACTED FROM THE SHIPPED WASM, not a repo copy — the artifact is the contract, and a repo copy can drift from what shipped. (gale's wit-os/ paths also 404, but that is the weaker reason.) THE FALCON PACKAGE HAD TO BE RECONSTRUCTED: each component ships a TREE-SHAKEN types interface carrying only the records it uses, so the complete type set exists in NO SINGLE COMPONENT. A naive merge fails with "name 'imu-sample' is not defined" — the ekf interface references a record only iekf's copy defines. The package here is the UNION of all five, the only form that parses. imu-sample is the raw-IMU seam jess specified on jess#167 and relay adopted; it appears as shipped (ax ay az gx gy gz). CAPABILITY SUBSET IS DELIBERATE: follows gale's WORKING app-ts (time + spawn), not the full app world. gale volunteered that app-timer is DECLARED BUT UNIMPLEMENTED, and the periodicity design it serves (timer-ISR vs partition-window) is explicitly UNSETTLED. This rung proves the seam composes; periodicity waits for that question to have evidence. Verified: world resolves (exit 0, all five imports bound); negative control — a bogus import errors "interface not found in package", so resolution is real not permissive. rivet validate PASS. Co-Authored-By: Claude Opus 4.8 --- wit-deps/README.md | 39 ++++++++ .../falcon/pulseengine-falcon-cascade.wit | 95 +++++++++++++++++++ wit-deps/gust/gust-hal.wit | 7 ++ wit-deps/gust/gust-os.wit | 34 +++++++ .../pulseengine-falcon-cascade.wit | 95 +++++++++++++++++++ wit/flight-app/deps/gust-hal/gust-hal.wit | 7 ++ wit/flight-app/deps/gust-os/gust-os.wit | 34 +++++++ wit/flight-app/world.wit | 32 +++++++ 8 files changed, 343 insertions(+) create mode 100644 wit-deps/README.md create mode 100644 wit-deps/falcon/pulseengine-falcon-cascade.wit create mode 100644 wit-deps/gust/gust-hal.wit create mode 100644 wit-deps/gust/gust-os.wit create mode 100644 wit/flight-app/deps/falcon-cascade/pulseengine-falcon-cascade.wit create mode 100644 wit/flight-app/deps/gust-hal/gust-hal.wit create mode 100644 wit/flight-app/deps/gust-os/gust-os.wit create mode 100644 wit/flight-app/world.wit diff --git a/wit-deps/README.md b/wit-deps/README.md new file mode 100644 index 0000000..eb2da44 --- /dev/null +++ b/wit-deps/README.md @@ -0,0 +1,39 @@ +# WIT extracted from the SHIPPED ARTIFACTS + +Every `.wit` here was produced by `wasm-tools component wit .wasm` against a +component pulled from ghcr — **not** copied from a supplier repo. + +That is deliberate, and it is also what the repo paths force: gale's `wit-os/gust-os.wit` +404s at every path referenced (`wit-os/`, `benches/gust/wit-os/`). But the stronger reason +is that **the artifact is the contract**. A repo copy can drift from what shipped; the +binary cannot. + +| file | extracted from | +|---|---| +| `gust/gust-os.wit` | `ghcr.io/pulseengine/gale-nano:0.7.0` | +| `gust/gust-hal.wit` | same | +| `falcon/pulseengine-falcon-cascade.wit` | **unioned** across the five `falcon-v1.134.1` components | + +## Why the falcon package had to be reconstructed rather than copied + +Each falcon component ships a **tree-shaken `types`** carrying only the records it uses: + +| component | records in its `types` | +|---|---| +| rate | vehicle-state, rate-setpoint, torque-setpoint | +| iekf | **imu-sample**, vehicle-state | +| position | vehicle-state, waypoint, attitude-setpoint | +| attitude | vehicle-state, attitude-setpoint, rate-setpoint | +| mixer | torque-setpoint, motor-pwm | + +**The complete type set exists in no single component.** Merging naively fails — +`error: name 'imu-sample' is not defined` — because `ekf`'s interface references a record +only `iekf`'s copy defines. The package here is the **union** of all five, which is the +only form that parses. + +(`imu-sample` is the raw-IMU seam jess specified on jess#167 and relay adopted; it appears +here as shipped: `ax ay az gx gy gz`.) + +## Refreshing + +Re-run the extraction whenever a supplier releases; do not hand-edit these files. diff --git a/wit-deps/falcon/pulseengine-falcon-cascade.wit b/wit-deps/falcon/pulseengine-falcon-cascade.wit new file mode 100644 index 0000000..1de7012 --- /dev/null +++ b/wit-deps/falcon/pulseengine-falcon-cascade.wit @@ -0,0 +1,95 @@ +package pulseengine:falcon-cascade@0.7.0; + +interface types { + record imu-sample { + ax: f32, + ay: f32, + az: f32, + gx: f32, + gy: f32, + gz: f32, + } + + record vehicle-state { + qw: f32, + qx: f32, + qy: f32, + qz: f32, + pos-n: f32, + pos-e: f32, + pos-d: f32, + vel-n: f32, + vel-e: f32, + vel-d: f32, + wx: f32, + wy: f32, + wz: f32, + innovation: f32, + } + + record waypoint { + north: f32, + east: f32, + down: f32, + yaw: f32, + } + + record attitude-setpoint { + qw: f32, + qx: f32, + qy: f32, + qz: f32, + thrust: f32, + } + + record rate-setpoint { + rx: f32, + ry: f32, + rz: f32, + thrust: f32, + } + + record torque-setpoint { + tx: f32, + ty: f32, + tz: f32, + thrust: f32, + } + + record motor-pwm { + m1: f32, + m2: f32, + m3: f32, + m4: f32, + } +} + +interface ekf { + use types.{imu-sample, vehicle-state}; + + estimate: func(imu: imu-sample) -> vehicle-state; +} + +interface position { + use types.{vehicle-state, waypoint, attitude-setpoint}; + + tick: func(state: vehicle-state, target: waypoint) -> attitude-setpoint; +} + +interface attitude { + use types.{vehicle-state, attitude-setpoint, rate-setpoint}; + + tick: func(state: vehicle-state, sp: attitude-setpoint) -> rate-setpoint; +} + +interface rate { + use types.{vehicle-state, rate-setpoint, torque-setpoint}; + + tick: func(state: vehicle-state, sp: rate-setpoint) -> torque-setpoint; +} + +interface mixer { + use types.{torque-setpoint, motor-pwm}; + + mix: func(torque: torque-setpoint) -> motor-pwm; +} diff --git a/wit-deps/gust/gust-hal.wit b/wit-deps/gust/gust-hal.wit new file mode 100644 index 0000000..b5415d8 --- /dev/null +++ b/wit-deps/gust/gust-hal.wit @@ -0,0 +1,7 @@ +package gust:hal@0.1.0; + +interface mmio { + read32: func(addr: u32) -> u32; + + write32: func(addr: u32, val: u32); +} diff --git a/wit-deps/gust/gust-os.wit b/wit-deps/gust/gust-os.wit new file mode 100644 index 0000000..072674b --- /dev/null +++ b/wit-deps/gust/gust-os.wit @@ -0,0 +1,34 @@ +package gust:os@0.1.0; + +interface taskdisp { + poll-task: func(id: u32) -> u32; +} +interface time { + now: func() -> u64; + + deadline: func(now: u64, ticks: u64) -> u64; + + elapsed: func(now: u64, deadline: u64) -> bool; + + resolution: func() -> u64; +} +interface log { + line: func(msg: list); +} +interface spawn { + start: func(entry: u32) -> u32; + + poll: func(handle: u32) -> u32; +} +interface exec { + admit: func(prio: u32, deadline-lo: u32, deadline-hi: u32) -> u32; + + poll-round: func(now-lo: u32, now-hi: u32); + + state: func(h: u32) -> u32; +} +interface timer { + sleep: func(handle: u32, ticks: u32) -> u32; + + slept: func(handle: u32) -> u32; +} diff --git a/wit/flight-app/deps/falcon-cascade/pulseengine-falcon-cascade.wit b/wit/flight-app/deps/falcon-cascade/pulseengine-falcon-cascade.wit new file mode 100644 index 0000000..1de7012 --- /dev/null +++ b/wit/flight-app/deps/falcon-cascade/pulseengine-falcon-cascade.wit @@ -0,0 +1,95 @@ +package pulseengine:falcon-cascade@0.7.0; + +interface types { + record imu-sample { + ax: f32, + ay: f32, + az: f32, + gx: f32, + gy: f32, + gz: f32, + } + + record vehicle-state { + qw: f32, + qx: f32, + qy: f32, + qz: f32, + pos-n: f32, + pos-e: f32, + pos-d: f32, + vel-n: f32, + vel-e: f32, + vel-d: f32, + wx: f32, + wy: f32, + wz: f32, + innovation: f32, + } + + record waypoint { + north: f32, + east: f32, + down: f32, + yaw: f32, + } + + record attitude-setpoint { + qw: f32, + qx: f32, + qy: f32, + qz: f32, + thrust: f32, + } + + record rate-setpoint { + rx: f32, + ry: f32, + rz: f32, + thrust: f32, + } + + record torque-setpoint { + tx: f32, + ty: f32, + tz: f32, + thrust: f32, + } + + record motor-pwm { + m1: f32, + m2: f32, + m3: f32, + m4: f32, + } +} + +interface ekf { + use types.{imu-sample, vehicle-state}; + + estimate: func(imu: imu-sample) -> vehicle-state; +} + +interface position { + use types.{vehicle-state, waypoint, attitude-setpoint}; + + tick: func(state: vehicle-state, target: waypoint) -> attitude-setpoint; +} + +interface attitude { + use types.{vehicle-state, attitude-setpoint, rate-setpoint}; + + tick: func(state: vehicle-state, sp: attitude-setpoint) -> rate-setpoint; +} + +interface rate { + use types.{vehicle-state, rate-setpoint, torque-setpoint}; + + tick: func(state: vehicle-state, sp: rate-setpoint) -> torque-setpoint; +} + +interface mixer { + use types.{torque-setpoint, motor-pwm}; + + mix: func(torque: torque-setpoint) -> motor-pwm; +} diff --git a/wit/flight-app/deps/gust-hal/gust-hal.wit b/wit/flight-app/deps/gust-hal/gust-hal.wit new file mode 100644 index 0000000..b5415d8 --- /dev/null +++ b/wit/flight-app/deps/gust-hal/gust-hal.wit @@ -0,0 +1,7 @@ +package gust:hal@0.1.0; + +interface mmio { + read32: func(addr: u32) -> u32; + + write32: func(addr: u32, val: u32); +} diff --git a/wit/flight-app/deps/gust-os/gust-os.wit b/wit/flight-app/deps/gust-os/gust-os.wit new file mode 100644 index 0000000..072674b --- /dev/null +++ b/wit/flight-app/deps/gust-os/gust-os.wit @@ -0,0 +1,34 @@ +package gust:os@0.1.0; + +interface taskdisp { + poll-task: func(id: u32) -> u32; +} +interface time { + now: func() -> u64; + + deadline: func(now: u64, ticks: u64) -> u64; + + elapsed: func(now: u64, deadline: u64) -> bool; + + resolution: func() -> u64; +} +interface log { + line: func(msg: list); +} +interface spawn { + start: func(entry: u32) -> u32; + + poll: func(handle: u32) -> u32; +} +interface exec { + admit: func(prio: u32, deadline-lo: u32, deadline-hi: u32) -> u32; + + poll-round: func(now-lo: u32, now-hi: u32); + + state: func(h: u32) -> u32; +} +interface timer { + sleep: func(handle: u32, ticks: u32) -> u32; + + slept: func(handle: u32) -> u32; +} diff --git a/wit/flight-app/world.wit b/wit/flight-app/world.wit new file mode 100644 index 0000000..85557a7 --- /dev/null +++ b/wit/flight-app/world.wit @@ -0,0 +1,32 @@ +package jess:flight-app@0.1.0; + +// The jess flight APPLICATION — the mapping between relay's flight cascade and +// gale's gust:os runtime. This is the piece AFD-043 identified as missing: falcon +// imports no gust:os and gale-nano exports it, so nothing connected the two. jess +// owns integration under DD-026, so this world is jess's to define. +// +// EVERY DEPENDENCY BELOW WAS EXTRACTED FROM THE SHIPPED WASM, not from a repo copy: +// deps/gust-os, deps/gust-hal <- wasm-tools component wit gale-nano-0.7.0.wasm +// deps/falcon-cascade <- unioned across the five falcon-v1.134.1 components +// (each ships a TREE-SHAKEN `types` carrying only the +// records it needs, so the full type set exists in no +// single component and had to be reconstructed) +// +// SHAPE per gale (#223): `export run: func() -> u32` is the canonical entry the runtime calls. +// Capability subset follows gale's WORKING `app-ts` example (time + spawn) rather than the full +// `world app`, deliberately: `world app-timer` is DECLARED BUT UNIMPLEMENTED upstream, and the +// periodicity design it would serve (timer-ISR vs partition-window) is explicitly UNSETTLED. So +// this first rung proves the seam composes; periodicity comes after that question has evidence. +world app { + // gale's runtime capabilities — the app↔runtime composition seam (DD-026 layer i) + import gust:os/time@0.1.0; + import gust:os/spawn@0.1.0; + + // relay's flight cascade — the data seams (DD-026 layer ii) + import pulseengine:falcon-cascade/types@0.7.0; + import pulseengine:falcon-cascade/rate@0.7.0; + import pulseengine:falcon-cascade/mixer@0.7.0; + + // gale's canonical entry point + export run: func() -> u32; +} From 3751e86c169a766aa8d9cde2efb6a0c726b3f306 Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Thu, 27 Aug 2026 22:16:38 +0200 Subject: [PATCH 04/10] =?UTF-8?q?fix:=20move=20the=20flight-app=20world=20?= =?UTF-8?q?out=20of=20the=20spar-derived=20wit/=20tree=20=E2=80=94=20CI's?= =?UTF-8?q?=20derivation=20gate=20caught=20it=20correctly?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The WIT-derivation gate failed on the previous commit: WIT-DERIVATION DRIFT — wit/ is stale or hand-edited; run tools/wit/regen.sh Only in .../wit: flight-app That gate is right. `wit/` is spar codegen from hardware/pixhawk6x-rt.aadl and CI asserts `wit/ == spar codegen` so a hand-edit cannot masquerade as generated output. The flight-app world is legitimately hand-authored — it describes a COMPOSITION, not the hardware architecture — so it does not belong in that tree. Fixed by moving the file, NOT by loosening the gate. The repo now has three WIT trees with distinct provenance and rules, documented in wit-app/README.md: wit/ spar codegen from the AADL — never hand-edited, CI-gated wit-deps/ extracted from shipped supplier .wasm — never hand-edited, re-extract instead wit-app/ authored by jess — the integration surface jess owns Verified: tools/wit/regen.sh --check now reports WIT-DERIVATION OK; the world still resolves from its new location; rivet validate PASS. Co-Authored-By: Claude Opus 4.8 --- wit-app/README.md | 19 +++++++++++++++++++ .../pulseengine-falcon-cascade.wit | 0 .../flight-app/deps/gust-hal/gust-hal.wit | 0 .../flight-app/deps/gust-os/gust-os.wit | 0 {wit => wit-app}/flight-app/world.wit | 0 5 files changed, 19 insertions(+) create mode 100644 wit-app/README.md rename {wit => wit-app}/flight-app/deps/falcon-cascade/pulseengine-falcon-cascade.wit (100%) rename {wit => wit-app}/flight-app/deps/gust-hal/gust-hal.wit (100%) rename {wit => wit-app}/flight-app/deps/gust-os/gust-os.wit (100%) rename {wit => wit-app}/flight-app/world.wit (100%) diff --git a/wit-app/README.md b/wit-app/README.md new file mode 100644 index 0000000..652450d --- /dev/null +++ b/wit-app/README.md @@ -0,0 +1,19 @@ +# jess-authored WIT — the composition worlds + +Three WIT trees in this repo, with different provenance and different rules: + +| tree | provenance | may be hand-edited? | +|---|---|---| +| `wit/` | **spar codegen from `hardware/pixhawk6x-rt.aadl`** | **no** — CI gates `wit/ == spar codegen` (`tools/wit/regen.sh --check`) | +| `wit-deps/` | **extracted from shipped supplier `.wasm`** | no — re-extract instead | +| `wit-app/` (here) | **authored by jess** | yes — this is the integration surface jess owns | + +`wit-app/flight-app` is the mapping between relay's flight cascade and gale's `gust:os` +runtime — the piece AFD-043 identified as missing. It is *not* AADL-derived (it describes a +composition, not the hardware architecture) and *not* extracted (no supplier ships it), so it +belongs in neither of the other two trees. + +**This separation was forced by CI, correctly.** The world was first placed in `wit/` and the +derivation gate failed with `WIT-DERIVATION DRIFT — Only in wit/: flight-app`. That gate exists +so a hand-edit cannot masquerade as generated output; the right fix was to move the file, not to +loosen the gate. diff --git a/wit/flight-app/deps/falcon-cascade/pulseengine-falcon-cascade.wit b/wit-app/flight-app/deps/falcon-cascade/pulseengine-falcon-cascade.wit similarity index 100% rename from wit/flight-app/deps/falcon-cascade/pulseengine-falcon-cascade.wit rename to wit-app/flight-app/deps/falcon-cascade/pulseengine-falcon-cascade.wit diff --git a/wit/flight-app/deps/gust-hal/gust-hal.wit b/wit-app/flight-app/deps/gust-hal/gust-hal.wit similarity index 100% rename from wit/flight-app/deps/gust-hal/gust-hal.wit rename to wit-app/flight-app/deps/gust-hal/gust-hal.wit diff --git a/wit/flight-app/deps/gust-os/gust-os.wit b/wit-app/flight-app/deps/gust-os/gust-os.wit similarity index 100% rename from wit/flight-app/deps/gust-os/gust-os.wit rename to wit-app/flight-app/deps/gust-os/gust-os.wit diff --git a/wit/flight-app/world.wit b/wit-app/flight-app/world.wit similarity index 100% rename from wit/flight-app/world.wit rename to wit-app/flight-app/world.wit From 69fa801206364fc0cd1ed7e5fe0a34717115d5ca Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Thu, 27 Aug 2026 22:36:45 +0200 Subject: [PATCH 05/10] tools: contributable WIT union merger with LOUD conflict detection (rules_wasm_component#626) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rules_wasm_component filed #627 for the single-component wit_from_component case but explicitly DECLINED the union case, because "the failure mode of getting it subtly wrong is silent data loss, not a build error" — and asked jess to contribute its working version. That caution landed on jess: the earlier ad-hoc merge used dict.setdefault, which keeps the FIRST definition and silently discards a differing one. Exactly the flaw they warned about. MEASURED EXPOSURE — it was luck, not safety: 4 of 7 falcon-cascade records are defined in MORE THAN ONE component (attitude-setpoint, rate-setpoint, torque-setpoint, vehicle-state). All four are currently byte-identical, so setdefault produced correct output. It would not have stayed lucky. tools/wit/merge_component_wit.py implements the semantics rules_wasm_component named: interfaces union by name type defs union by name identical duplicate -> keep one DIFFERING duplicate -> HARD ERROR, never resolved silently Comparison is on whitespace-normalised bodies so cosmetic formatting differences between extractions do not raise a false conflict. --self-test is a NEGATIVE CONTROL, not a claim — it proves the conflict path fires: identical definitions -> merged cleanly (expected clean) PASS whitespace-only difference -> merged cleanly (expected clean) PASS REAL conflict (f32 vs f64) -> conflict raised (expected conflict) PASS wit-deps/falcon/pulseengine-falcon-cascade.wit is now REGENERATED BY THIS TOOL rather than by an inline script, and the flight-app world still resolves against it. rivet validate PASS. Co-Authored-By: Claude Opus 4.8 --- tools/wit/merge_component_wit.py | 168 ++++++++++++++++++ .../falcon/pulseengine-falcon-cascade.wit | 60 +++---- 2 files changed, 196 insertions(+), 32 deletions(-) create mode 100755 tools/wit/merge_component_wit.py diff --git a/tools/wit/merge_component_wit.py b/tools/wit/merge_component_wit.py new file mode 100755 index 0000000..337099e --- /dev/null +++ b/tools/wit/merge_component_wit.py @@ -0,0 +1,168 @@ +#!/usr/bin/env python3 +"""Union the WIT packages carried by several shipped components into one package. + +WHY THIS EXISTS +--------------- +`wasm-tools component wit .wasm` recovers a component's WIT — but each +component ships a TREE-SHAKEN copy of its shared package, carrying only the type +definitions that component happens to use. For a multi-component interface family +the complete package therefore exists in NO SINGLE COMPONENT, and must be unioned. + +Observed on pulseengine:falcon-cascade@0.7.0 (falcon-v1.134.1, five components): + + rate vehicle-state, rate-setpoint, torque-setpoint + iekf imu-sample, vehicle-state + position vehicle-state, waypoint, attitude-setpoint + attitude vehicle-state, attitude-setpoint, rate-setpoint + mixer torque-setpoint, motor-pwm + +Taking any one and using it fails downstream. Concatenating them fails immediately +with `error: name 'imu-sample' is not defined`, because the `ekf` interface +references a record only `iekf`'s copy defines. + +MERGE SEMANTICS — and the one that matters +------------------------------------------ + * interfaces union by name + * type defs union by name + * a name defined identically in several components -> fine, keep one + * a name defined DIFFERENTLY in several components -> HARD ERROR, never resolved silently + +That last rule is the whole point. An earlier version of this script used +`dict.setdefault`, which keeps the FIRST definition and silently discards a +differing one — data loss with no build error. On the falcon cascade today four +records are defined in more than one component and all four are byte-identical, so +that bug produced correct output by luck. It would not have stayed lucky. + +Comparison is on whitespace-normalised bodies so that cosmetic formatting +differences between extractions do not raise a false conflict. +""" +import argparse, re, subprocess, sys + + +def extract(artifact: str, package: str) -> str: + """Return the body of `package` as carried by one component, or '' if absent.""" + out = subprocess.run(["wasm-tools", "component", "wit", artifact], + capture_output=True, text=True) + if out.returncode != 0: + sys.exit(f"error: wasm-tools failed on {artifact}: {out.stderr.strip()}") + m = re.search(rf'^package {re.escape(package)} \{{\n(.*?)^\}}', out.stdout, re.S | re.M) + if not m: + return "" + return "\n".join(l[2:] if l.startswith(" ") else l for l in m.group(1).split("\n")) + + +def norm(s: str) -> str: + return re.sub(r'\s+', ' ', s).strip() + + +def self_test() -> int: + """Negative control: prove the conflict path FIRES rather than resolving silently. + + This is the property rules_wasm_component#626 was worried about, so it is + demonstrated here rather than asserted. Run: --self-test + """ + a = " record vehicle-state {\n qw: f32,\n }" + b_same = " record vehicle-state {\n qw: f32,\n }" + b_fmt = " record vehicle-state {\n\n qw: f32,\n }" # cosmetic only + b_diff = " record vehicle-state {\n qw: f64,\n }" # REAL conflict + + def merge(x, y): + store, conflicts = {}, [] + def put(name, body, src): + if name in store: + prev, psrc = store[name] + if norm(prev) != norm(body): + conflicts.append(f"{name}: {psrc} vs {src}") + return + store[name] = (body, src) + put("vehicle-state", x, "A"); put("vehicle-state", y, "B") + return conflicts + + cases = [ + ("identical definitions", merge(a, b_same), False), + ("whitespace-only difference", merge(a, b_fmt), False), + ("REAL conflict (f32 vs f64)", merge(a, b_diff), True), + ] + ok = True + for label, conflicts, want in cases: + got = bool(conflicts) + status = "PASS" if got == want else "FAIL" + if got != want: + ok = False + print(f" {status} {label:<30} -> {'conflict raised' if got else 'merged cleanly'}" + f" (expected {'conflict' if want else 'clean'})") + print("\n self-test " + ("PASS — the conflict path fires, and cosmetic\n" + " formatting does NOT raise a false conflict." if ok else "FAILED")) + return 0 if ok else 1 + + +def main() -> int: + ap = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument("--self-test", action="store_true", + help="negative-control the conflict detection and exit") + ap.add_argument("--package", required=False, help="e.g. pulseengine:falcon-cascade@0.7.0") + ap.add_argument("--out", required=False) + ap.add_argument("components", nargs="*") + a = ap.parse_args() + if a.self_test: + return self_test() + if not a.package or not a.out or not a.components: + ap.error("--package, --out and components are required unless --self-test") + + types: dict[str, tuple[str, str]] = {} # name -> (body, source artifact) + ifaces: dict[str, tuple[str, str]] = {} + conflicts: list[str] = [] + + def put(store, name, body, src, kind): + if name in store: + prev, psrc = store[name] + if norm(prev) != norm(body): + conflicts.append( + f"{kind} '{name}' differs between {psrc} and {src}:\n" + f" {psrc}: {norm(prev)[:160]}\n {src}: {norm(body)[:160]}") + return + store[name] = (body, src) + + for art in a.components: + body = extract(art, a.package) + if not body: + print(f" note: {art} carries no {a.package} — skipped", file=sys.stderr) + continue + for m in re.finditer(r'^interface (\w[\w-]*) \{\n(.*?)^\}', body, re.S | re.M): + name, inner = m.group(1), m.group(2) + if name == "types": + for t in re.finditer( + r'^ (record|variant|enum|flags|type) ([a-z][a-z0-9-]*)[^\n]*\{?\n?.*?^ \}', + inner, re.S | re.M): + put(types, t.group(2), t.group(0), art, t.group(1)) + else: + put(ifaces, name, m.group(0), art, "interface") + + if conflicts: + print(f"error: {len(conflicts)} conflicting definition(s) — refusing to merge.\n" + "A silently-resolved conflict is data loss, not a build error.\n", + file=sys.stderr) + for c in conflicts: + print(" " + c, file=sys.stderr) + return 1 + + parts = [f"package {a.package};", ""] + if types: + parts.append("interface types {") + parts.append("\n\n".join(b for b, _ in types.values())) + parts.append("}") + parts.append("") + parts.extend(b for b, _ in ifaces.values()) + open(a.out, "w").write("\n".join(parts).rstrip() + "\n") + + print(f" merged {len(a.components)} component(s) -> {a.out}") + print(f" types: {', '.join(sorted(types))}") + print(f" interfaces: {', '.join(sorted(ifaces))}") + dup = "none" + print(f" conflicts: {dup}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/wit-deps/falcon/pulseengine-falcon-cascade.wit b/wit-deps/falcon/pulseengine-falcon-cascade.wit index 1de7012..cbce1a6 100644 --- a/wit-deps/falcon/pulseengine-falcon-cascade.wit +++ b/wit-deps/falcon/pulseengine-falcon-cascade.wit @@ -1,15 +1,6 @@ package pulseengine:falcon-cascade@0.7.0; interface types { - record imu-sample { - ax: f32, - ay: f32, - az: f32, - gx: f32, - gy: f32, - gz: f32, - } - record vehicle-state { qw: f32, qx: f32, @@ -27,6 +18,29 @@ interface types { innovation: f32, } + record rate-setpoint { + rx: f32, + ry: f32, + rz: f32, + thrust: f32, + } + + record torque-setpoint { + tx: f32, + ty: f32, + tz: f32, + thrust: f32, + } + + record imu-sample { + ax: f32, + ay: f32, + az: f32, + gx: f32, + gy: f32, + gz: f32, + } + record waypoint { north: f32, east: f32, @@ -42,20 +56,6 @@ interface types { thrust: f32, } - record rate-setpoint { - rx: f32, - ry: f32, - rz: f32, - thrust: f32, - } - - record torque-setpoint { - tx: f32, - ty: f32, - tz: f32, - thrust: f32, - } - record motor-pwm { m1: f32, m2: f32, @@ -64,30 +64,26 @@ interface types { } } +interface rate { + use types.{vehicle-state, rate-setpoint, torque-setpoint}; + + tick: func(state: vehicle-state, sp: rate-setpoint) -> torque-setpoint; +} interface ekf { use types.{imu-sample, vehicle-state}; estimate: func(imu: imu-sample) -> vehicle-state; } - interface position { use types.{vehicle-state, waypoint, attitude-setpoint}; tick: func(state: vehicle-state, target: waypoint) -> attitude-setpoint; } - interface attitude { use types.{vehicle-state, attitude-setpoint, rate-setpoint}; tick: func(state: vehicle-state, sp: attitude-setpoint) -> rate-setpoint; } - -interface rate { - use types.{vehicle-state, rate-setpoint, torque-setpoint}; - - tick: func(state: vehicle-state, sp: rate-setpoint) -> torque-setpoint; -} - interface mixer { use types.{torque-setpoint, motor-pwm}; From edcb4798b57861aa0b428ae1278ed7c449dbd2c7 Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Thu, 27 Aug 2026 22:40:01 +0200 Subject: [PATCH 06/10] =?UTF-8?q?hardware:=20stage=20the=20RT1176=20H1=20b?= =?UTF-8?q?ring-up=20payload=20source=20(NOT=20YET=20RUN=20=E2=80=94=20nee?= =?UTF-8?q?ds=20the=20user's=20go-ahead)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hardware/silicon/rt1176-blhost/sig.S is the H1 "does the silicon execute our code at all" probe: blhost write-memory into OCRAM, blhost call, blhost read-memory back. It is deliberately a normal thumb function ending in `bx lr` rather than a spin loop, because blhost `call` RETURNS to the ROM bootloader — a spin loop would hang the bootloader and look identical to a dead chip. It carries its own negative control. It writes TWO words: a fixed signature 0x1E55F00D, and a value COMPUTED from the argument (r0 + 0x55). The computed word is the one that matters — a fixed signature alone could be matched by a stale RAM read from an earlier attempt, which would report success for a chip that never ran anything. The computed word cannot. Observable purely over USB via blhost read-memory: no UART, no debug probe, no soldering. STATUS: BUILT, NOT RUN. Running it means entering ISP mode and writing to the vehicle, which is outside the standing read-only default and needs the user's explicit go-ahead each time. It is staged here so the go-ahead is a one-command step rather than a build session. Outputs (.o/.elf/.bin) are gitignored; build.sh regenerates them. logs/ (Renode per-run robot output) gitignored too — falcon-cascade-m7.robot is the committed evidence, not its run logs. Co-Authored-By: Claude Opus 4.8 --- .gitignore | 7 +++++++ hardware/silicon/rt1176-blhost/build.sh | 9 +++++++++ hardware/silicon/rt1176-blhost/sig.S | 18 ++++++++++++++++++ 3 files changed, 34 insertions(+) create mode 100755 hardware/silicon/rt1176-blhost/build.sh create mode 100644 hardware/silicon/rt1176-blhost/sig.S diff --git a/.gitignore b/.gitignore index e148d42..1d2693b 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,10 @@ robot_output.xml log.html report.html snapshots/ + +# Renode robot per-run output (regenerated; the .robot file is the evidence) +logs/ +# assembled silicon bring-up payloads (rebuild from sig.S via build.sh) +hardware/silicon/**/*.o +hardware/silicon/**/*.elf +hardware/silicon/**/*.bin diff --git a/hardware/silicon/rt1176-blhost/build.sh b/hardware/silicon/rt1176-blhost/build.sh new file mode 100755 index 0000000..14946bb --- /dev/null +++ b/hardware/silicon/rt1176-blhost/build.sh @@ -0,0 +1,9 @@ +#!/bin/sh +# Assemble the RT1176 silicon bring-up payload. Outputs are gitignored — sig.S is the source. +set -eu +cd "$(dirname "$0")" +CC=${CC:-arm-none-eabi-gcc} +"$CC" -c -mcpu=cortex-m7 -mthumb sig.S -o sig.o +"${CC%gcc}ld" -Ttext=0x20240000 -e _start sig.o -o sig.elf +"${CC%gcc}objcopy" -O binary sig.elf sig.bin +echo "sig.bin: $(wc -c < sig.bin) bytes" diff --git a/hardware/silicon/rt1176-blhost/sig.S b/hardware/silicon/rt1176-blhost/sig.S new file mode 100644 index 0000000..17e7796 --- /dev/null +++ b/hardware/silicon/rt1176-blhost/sig.S @@ -0,0 +1,18 @@ +@ jess RT1176 silicon bring-up payload #1 — the mechanism test. +@ Loaded into RAM by `blhost write-memory`, invoked by `blhost call `. +@ blhost `call` passes in R0 and RETURNS to the ROM bootloader afterwards, +@ so this must be a normal thumb function that ends in `bx lr` (NOT a spin loop). +@ Contract: R0 = address to write. Writes a signature + a computed value, returns. +@ Observable purely over USB via `blhost read-memory` — no UART, no probe needed. + .syntax unified + .cpu cortex-m7 + .thumb + .section .text + .thumb_func + .global _start +_start: + ldr r1, =0x1E55F00D @ "JESS FOOD" — a signature the ROM would never write + str r1, [r0, #0] + add r2, r0, #0x55 @ a value COMPUTED from the argument, so a stale + str r2, [r0, #4] @ RAM read can't masquerade as a successful run + bx lr @ return to the ROM bootloader From b6842352d8db2f94e1d01dcb54aceb332799c7dd Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Thu, 27 Aug 2026 22:49:28 +0200 Subject: [PATCH 07/10] =?UTF-8?q?feature:=20THE=20COMPLETE=20WASM=20STACK?= =?UTF-8?q?=20COMPOSES=20AND=20RUNS=20=E2=80=94=20AFD-043=20closed=20by=20?= =?UTF-8?q?AFD-044?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AFD-043 said the stack could not be composed: falcon imports no gust:os, gale-nano exports it, and the piece between them did not exist. Two pieces were missing, both jess-owned. Both are now built, and the whole stack composes, executes, and agrees with an independent reference. app/flight-app imports gust:os/time + falcon rate/mixer, exports run: func() -> u32 app/gust-hal-stub exports gust:hal/mmio + gust:os/taskdisp (stub — real MMIO is on-target) app/bump-alloc shared no_std allocator over __heap_base; never calls memory.grow THE RESIDUAL WAS DERIVED, NOT DESIGNED. Composing flight-app against gale-nano + falcon left EXACTLY three unsatisfied imports: gust:os/taskdisp, gust:hal/mmio, falcon types. That list of what jess still owes the embedder used to be a guess. It is now a wac output. wac compose flight-app + gale-nano + rate + mixer -> step1.wasm wac compose step1 + gust-hal-stub -> full.wasm (types-only import left) wasm-tools validate -> VALID wasmtime run --invoke run() -> 2147484996 low 31 bits = 1348 the falcon cascade fold bit 31 = 1 gale's clock did real arithmetic on OUR argument THE CLOCK BIT WAS FIRST WRITTEN VACUOUSLY AND CORRECTED BEFORE REPORTING. It initially came from `elapsed(now, deadline) == false` — which an inert clock also returns, the same trap that voided the CRC claim in AFD-037. It now tests `deadline(t0,1) != t0`, which no zero-returning stub can produce. DIFFERENTIAL, NOT A GOLDEN NUMBER. cascade_ref.py recomputes the same fold by a completely different route: fused core module, raw canonical-ABI pointers, no component model, no wac, no gust:os. Both give 1348. Torque reproduces the SIL reference exactly (tx=1 ty=0.472507507 tz=-0.147003502 thrust=0.5). A golden value would be satisfied by both sides sharing one bug; two routes agreeing would not. Note the ABI is NOT uniform — `tick` takes a pointer, `mix` takes 4 flattened f32s; assuming pointer-in for both silently passes garbage. THREE NEGATIVE CONTROLS, ALL RUN: (i) reference perturbed (wx 0.30 -> 0.35) -> 2000, DISTINCT — 1348 tracks its input, so it is not a constant that a miscompile dropping the state would also produce (ii) reference falsified (x1000 -> x1001) -> oracle EXITS 1, "composed=1348 reference=1350"; restored -> PASS. The gate can fail. (iii) missing upstream artifact -> preflight EXITS 2 with an explanatory message JESS FAILED ITS OWN PUBLISH GATE. flight-app was NOT CONSUMABLE on first build — no reloc.* and no __heap_base, the exact defect jess reported against relay's publish path. lld defines __heap_base synthetically but does not export it, and emits no relocations, unless asked. Fixed with --emit-relocs / --export=__heap_base. Both jess components now pass all five checks. The gate was not written to point outward only. INCIDENTAL: casc_new.wasm (pre-loom) yields the same 1348 as casc_new.loom.wasm — loom is numerically neutral ON THIS VECTOR. Recorded as an observation, not a general claim. TOOL FRICTION: cargo-component 0.21.1 refuses a local wit/deps tree that wasm-tools resolves without complaint (rc=0). Routed around via plain cargo + `wasm-tools component new`. To be reported. NOT CLAIMED: this ran under wasmtime, NOT on the RT1176 and not in Renode. Periodicity is a single call, not a schedule — the timer-ISR vs partition-window question (DD-025) is still open. gust:hal is a STUB. rivet validate PASS (207 warnings). Co-Authored-By: Claude Opus 4.8 --- .gitignore | 3 + app/bump-alloc/Cargo.toml | 5 + app/bump-alloc/src/lib.rs | 60 +++ app/flight-app/.cargo/config.toml | 11 + app/flight-app/Cargo.lock | 341 ++++++++++++++++++ app/flight-app/Cargo.toml | 19 + app/flight-app/src/lib.rs | 59 +++ .../wit/deps/falcon-cascade/falcon.wit | 91 +++++ app/flight-app/wit/deps/gust-os/gust-os.wit | 34 ++ app/flight-app/wit/world.wit | 32 ++ app/gust-hal-stub/.cargo/config.toml | 11 + app/gust-hal-stub/Cargo.lock | 341 ++++++++++++++++++ app/gust-hal-stub/Cargo.toml | 17 + app/gust-hal-stub/src/lib.rs | 38 ++ .../wit/deps/gust-hal/gust-hal.wit | 7 + .../wit/deps/gust-os/gust-os.wit | 34 ++ app/gust-hal-stub/wit/world.wit | 15 + artifacts/findings.yaml | 69 +++- tools/appcompose/build-and-verify.sh | 87 +++++ tools/appcompose/compose.wac | 12 + tools/appcompose/compose2.wac | 8 + tools/cascade-differential/cascade_ref.py | 67 ++++ 22 files changed, 1359 insertions(+), 2 deletions(-) create mode 100644 app/bump-alloc/Cargo.toml create mode 100644 app/bump-alloc/src/lib.rs create mode 100644 app/flight-app/.cargo/config.toml create mode 100644 app/flight-app/Cargo.lock create mode 100644 app/flight-app/Cargo.toml create mode 100644 app/flight-app/src/lib.rs create mode 100644 app/flight-app/wit/deps/falcon-cascade/falcon.wit create mode 100644 app/flight-app/wit/deps/gust-os/gust-os.wit create mode 100644 app/flight-app/wit/world.wit create mode 100644 app/gust-hal-stub/.cargo/config.toml create mode 100644 app/gust-hal-stub/Cargo.lock create mode 100644 app/gust-hal-stub/Cargo.toml create mode 100644 app/gust-hal-stub/src/lib.rs create mode 100644 app/gust-hal-stub/wit/deps/gust-hal/gust-hal.wit create mode 100644 app/gust-hal-stub/wit/deps/gust-os/gust-os.wit create mode 100644 app/gust-hal-stub/wit/world.wit create mode 100755 tools/appcompose/build-and-verify.sh create mode 100644 tools/appcompose/compose.wac create mode 100644 tools/appcompose/compose2.wac create mode 100644 tools/cascade-differential/cascade_ref.py diff --git a/.gitignore b/.gitignore index 1d2693b..cd7697b 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,6 @@ logs/ hardware/silicon/**/*.o hardware/silicon/**/*.elf hardware/silicon/**/*.bin +# jess wasm components: rebuilt by tools/appcompose/build-and-verify.sh +app/*/target/ +app/*/*.wasm diff --git a/app/bump-alloc/Cargo.toml b/app/bump-alloc/Cargo.toml new file mode 100644 index 0000000..a9be3fa --- /dev/null +++ b/app/bump-alloc/Cargo.toml @@ -0,0 +1,5 @@ +[package] +name = "jess-bump-alloc" +version = "0.1.0" +edition = "2021" +publish = false diff --git a/app/bump-alloc/src/lib.rs b/app/bump-alloc/src/lib.rs new file mode 100644 index 0000000..bbb6420 --- /dev/null +++ b/app/bump-alloc/src/lib.rs @@ -0,0 +1,60 @@ +//! Bump allocator + panic handler shared by every jess wasm component. +//! +//! Factored out of `flight-app` once `gust-hal-stub` needed the identical pair: +//! two copies of an allocator is two places for the partition-sizing constant to +//! drift apart, and that constant is a safety property on a statically-sized +//! RT1176 partition, not a style preference. +#![no_std] + +/// Bump allocator over `__heap_base`. +/// +/// Deliberately never calls `memory.grow` and never frees: publish-gate C2 REFUSES a +/// component that grows memory, because the RT1176 partition is statically sized and a +/// grow at flight time is an unbounded fault. Exhaustion traps rather than falling back +/// — a silent wrap would corrupt the cascade's state instead of failing loudly. +pub mod alloc_impl { + use core::alloc::{GlobalAlloc, Layout}; + use core::sync::atomic::{AtomicUsize, Ordering}; + + extern "C" { + static __heap_base: u8; + } + const HEAP_LEN: usize = 64 * 1024; + static NEXT: AtomicUsize = AtomicUsize::new(0); + + pub struct Bump; + unsafe impl GlobalAlloc for Bump { + unsafe fn alloc(&self, l: Layout) -> *mut u8 { + let base = &__heap_base as *const u8 as usize; + loop { + let cur = NEXT.load(Ordering::Relaxed); + let start = (base + cur + l.align() - 1) & !(l.align() - 1); + let end = start - base + l.size(); + if end > HEAP_LEN { + return core::ptr::null_mut(); // triggers alloc_error -> trap + } + if NEXT.compare_exchange_weak(cur, end, Ordering::Relaxed, Ordering::Relaxed).is_ok() + { + return start as *mut u8; + } + } + } + unsafe fn dealloc(&self, _: *mut u8, _: Layout) {} + } +} + +/// Install the allocator and panic handler. Every jess component calls this once. +#[macro_export] +macro_rules! install { + () => { + #[global_allocator] + static __JESS_ALLOC: $crate::alloc_impl::Bump = $crate::alloc_impl::Bump; + + #[panic_handler] + fn __jess_panic(_: &core::panic::PanicInfo) -> ! { + // panic = abort at the wasm level; unreachable traps deterministically. + core::arch::wasm32::unreachable() + } + }; +} + diff --git a/app/flight-app/.cargo/config.toml b/app/flight-app/.cargo/config.toml new file mode 100644 index 0000000..e920744 --- /dev/null +++ b/app/flight-app/.cargo/config.toml @@ -0,0 +1,11 @@ +# The RT1176 embedder needs relocations and a heap symbol to place this component +# into a statically-sized partition. Neither is emitted by default: lld defines +# __heap_base synthetically but does not export it, and emits no reloc.* sections +# unless asked. This is the SAME defect jess reported against relay's publish path +# (tools/publish-gate/check-consumable.sh C4/C5) — it applies to jess's own output too. +[target.wasm32-unknown-unknown] +rustflags = [ + "-C", "link-arg=--emit-relocs", + "-C", "link-arg=--export=__heap_base", + "-C", "link-arg=--export=__data_end", +] diff --git a/app/flight-app/Cargo.lock b/app/flight-app/Cargo.lock new file mode 100644 index 0000000..6b1351d --- /dev/null +++ b/app/flight-app/Cargo.lock @@ -0,0 +1,341 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jess-bump-alloc" +version = "0.1.0" + +[[package]] +name = "jess-flight-app" +version = "0.1.0" +dependencies = [ + "jess-bump-alloc", + "wit-bindgen", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.119", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "wit-bindgen" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e048f41ef90f0b5dd61f1059c35f5636252e56813bf616d0803aa3739867230" +dependencies = [ + "bitflags", + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c15e7a56641cc9040480a26526a3229cbc4e8065adf98c9755d21c4c9b446c4c" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd81b0ae1ec492bfe91683f1da6db6492ebc682e72d4f2715619dba783b066ca" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn 2.0.119", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54e6ce04c549e7149b66a70d34fc5a2a01b374bf49ca61db65d16e3ae922866e" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.119", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/app/flight-app/Cargo.toml b/app/flight-app/Cargo.toml new file mode 100644 index 0000000..168a9fe --- /dev/null +++ b/app/flight-app/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "jess-flight-app" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +jess-bump-alloc = { path = "../bump-alloc" } +wit-bindgen = { version = "0.52", default-features = false, features = ["macros", "realloc", "bitflags"] } + + +[profile.release] +opt-level = "s" +lto = true +panic = "abort" +strip = false diff --git a/app/flight-app/src/lib.rs b/app/flight-app/src/lib.rs new file mode 100644 index 0000000..bd6fd0e --- /dev/null +++ b/app/flight-app/src/lib.rs @@ -0,0 +1,59 @@ +//! The jess flight application — the DD-026 seam made real. +//! +//! This is the piece AFD-043 identified as missing: falcon imports no `gust:os`, +//! gale-nano exports it, and nothing joined the two. This crate is that join. It +//! is deliberately the THINNEST thing that proves the seam composes — one cascade +//! step, driven by gale's clock — because the periodicity question (timer-ISR vs +//! ARINC-653 partition window, DD-025) is not yet settled by evidence. +#![no_std] + +mod bindings { + wit_bindgen::generate!({ path: "wit", world: "app", generate_all }); +} + +use bindings::gust::os::time; +use bindings::pulseengine::falcon_cascade::{mixer, rate, types}; + +struct App; + +impl bindings::Guest for App { + /// One cascade step: rate -> mixer, timed on gale's clock. + /// + /// Returns the elapsed-deadline verdict as u32 so the caller observes BOTH + /// legs of the seam (falcon compute AND gust:os time) in a single value. A + /// constant return would not distinguish "the seam ran" from "the stub ran". + fn run() -> u32 { + let t0 = time::now(); + let deadline = time::deadline(t0, 1); + + // EXACTLY the vector in tools/cascade-differential/sil_reference.py. Sharing it + // is the point: the composed component's output is then directly comparable to + // the established SIL reference, so "it ran" can be upgraded to "it ran RIGHT". + let state = types::VehicleState { + qw: 1.0, qx: 0.0, qy: 0.0, qz: 0.0, + pos_n: 0.0, pos_e: 0.0, pos_d: -2.5, + vel_n: 0.1, vel_e: -0.2, vel_d: 0.05, + wx: 0.30, wy: -0.15, wz: 0.07, + innovation: 0.0, + }; + let sp = types::RateSetpoint { rx: 1.0, ry: 0.0, rz: 0.0, thrust: 0.5 }; + + let torque = rate::tick(state, sp); + let pwm = mixer::mix(torque); + + // Fold the motor outputs to a single observable word. Bit 31 carries the + // clock leg so a stalled `time` import is distinguishable from a bad mix. + let acc = (pwm.m1 + pwm.m2 + pwm.m3 + pwm.m4) * 1000.0; + + // Bit 31 must DISCRIMINATE, not merely be present. `elapsed(...) == false` + // was the first choice and it is vacuous: an inert clock returns false too. + // `deadline(t0, 1) != t0` cannot be produced by a stub that returns zeros — + // it is true only if gale actually did the tick arithmetic on our argument. + let clock_live = (deadline != t0) as u32; + (acc as u32 & 0x7fff_ffff) | (clock_live << 31) + } +} + +bindings::export!(App with_types_in bindings); + +jess_bump_alloc::install!(); diff --git a/app/flight-app/wit/deps/falcon-cascade/falcon.wit b/app/flight-app/wit/deps/falcon-cascade/falcon.wit new file mode 100644 index 0000000..cbce1a6 --- /dev/null +++ b/app/flight-app/wit/deps/falcon-cascade/falcon.wit @@ -0,0 +1,91 @@ +package pulseengine:falcon-cascade@0.7.0; + +interface types { + record vehicle-state { + qw: f32, + qx: f32, + qy: f32, + qz: f32, + pos-n: f32, + pos-e: f32, + pos-d: f32, + vel-n: f32, + vel-e: f32, + vel-d: f32, + wx: f32, + wy: f32, + wz: f32, + innovation: f32, + } + + record rate-setpoint { + rx: f32, + ry: f32, + rz: f32, + thrust: f32, + } + + record torque-setpoint { + tx: f32, + ty: f32, + tz: f32, + thrust: f32, + } + + record imu-sample { + ax: f32, + ay: f32, + az: f32, + gx: f32, + gy: f32, + gz: f32, + } + + record waypoint { + north: f32, + east: f32, + down: f32, + yaw: f32, + } + + record attitude-setpoint { + qw: f32, + qx: f32, + qy: f32, + qz: f32, + thrust: f32, + } + + record motor-pwm { + m1: f32, + m2: f32, + m3: f32, + m4: f32, + } +} + +interface rate { + use types.{vehicle-state, rate-setpoint, torque-setpoint}; + + tick: func(state: vehicle-state, sp: rate-setpoint) -> torque-setpoint; +} +interface ekf { + use types.{imu-sample, vehicle-state}; + + estimate: func(imu: imu-sample) -> vehicle-state; +} +interface position { + use types.{vehicle-state, waypoint, attitude-setpoint}; + + tick: func(state: vehicle-state, target: waypoint) -> attitude-setpoint; +} +interface attitude { + use types.{vehicle-state, attitude-setpoint, rate-setpoint}; + + tick: func(state: vehicle-state, sp: attitude-setpoint) -> rate-setpoint; +} +interface mixer { + use types.{torque-setpoint, motor-pwm}; + + mix: func(torque: torque-setpoint) -> motor-pwm; +} diff --git a/app/flight-app/wit/deps/gust-os/gust-os.wit b/app/flight-app/wit/deps/gust-os/gust-os.wit new file mode 100644 index 0000000..072674b --- /dev/null +++ b/app/flight-app/wit/deps/gust-os/gust-os.wit @@ -0,0 +1,34 @@ +package gust:os@0.1.0; + +interface taskdisp { + poll-task: func(id: u32) -> u32; +} +interface time { + now: func() -> u64; + + deadline: func(now: u64, ticks: u64) -> u64; + + elapsed: func(now: u64, deadline: u64) -> bool; + + resolution: func() -> u64; +} +interface log { + line: func(msg: list); +} +interface spawn { + start: func(entry: u32) -> u32; + + poll: func(handle: u32) -> u32; +} +interface exec { + admit: func(prio: u32, deadline-lo: u32, deadline-hi: u32) -> u32; + + poll-round: func(now-lo: u32, now-hi: u32); + + state: func(h: u32) -> u32; +} +interface timer { + sleep: func(handle: u32, ticks: u32) -> u32; + + slept: func(handle: u32) -> u32; +} diff --git a/app/flight-app/wit/world.wit b/app/flight-app/wit/world.wit new file mode 100644 index 0000000..85557a7 --- /dev/null +++ b/app/flight-app/wit/world.wit @@ -0,0 +1,32 @@ +package jess:flight-app@0.1.0; + +// The jess flight APPLICATION — the mapping between relay's flight cascade and +// gale's gust:os runtime. This is the piece AFD-043 identified as missing: falcon +// imports no gust:os and gale-nano exports it, so nothing connected the two. jess +// owns integration under DD-026, so this world is jess's to define. +// +// EVERY DEPENDENCY BELOW WAS EXTRACTED FROM THE SHIPPED WASM, not from a repo copy: +// deps/gust-os, deps/gust-hal <- wasm-tools component wit gale-nano-0.7.0.wasm +// deps/falcon-cascade <- unioned across the five falcon-v1.134.1 components +// (each ships a TREE-SHAKEN `types` carrying only the +// records it needs, so the full type set exists in no +// single component and had to be reconstructed) +// +// SHAPE per gale (#223): `export run: func() -> u32` is the canonical entry the runtime calls. +// Capability subset follows gale's WORKING `app-ts` example (time + spawn) rather than the full +// `world app`, deliberately: `world app-timer` is DECLARED BUT UNIMPLEMENTED upstream, and the +// periodicity design it would serve (timer-ISR vs partition-window) is explicitly UNSETTLED. So +// this first rung proves the seam composes; periodicity comes after that question has evidence. +world app { + // gale's runtime capabilities — the app↔runtime composition seam (DD-026 layer i) + import gust:os/time@0.1.0; + import gust:os/spawn@0.1.0; + + // relay's flight cascade — the data seams (DD-026 layer ii) + import pulseengine:falcon-cascade/types@0.7.0; + import pulseengine:falcon-cascade/rate@0.7.0; + import pulseengine:falcon-cascade/mixer@0.7.0; + + // gale's canonical entry point + export run: func() -> u32; +} diff --git a/app/gust-hal-stub/.cargo/config.toml b/app/gust-hal-stub/.cargo/config.toml new file mode 100644 index 0000000..e920744 --- /dev/null +++ b/app/gust-hal-stub/.cargo/config.toml @@ -0,0 +1,11 @@ +# The RT1176 embedder needs relocations and a heap symbol to place this component +# into a statically-sized partition. Neither is emitted by default: lld defines +# __heap_base synthetically but does not export it, and emits no reloc.* sections +# unless asked. This is the SAME defect jess reported against relay's publish path +# (tools/publish-gate/check-consumable.sh C4/C5) — it applies to jess's own output too. +[target.wasm32-unknown-unknown] +rustflags = [ + "-C", "link-arg=--emit-relocs", + "-C", "link-arg=--export=__heap_base", + "-C", "link-arg=--export=__data_end", +] diff --git a/app/gust-hal-stub/Cargo.lock b/app/gust-hal-stub/Cargo.lock new file mode 100644 index 0000000..8edd180 --- /dev/null +++ b/app/gust-hal-stub/Cargo.lock @@ -0,0 +1,341 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jess-bump-alloc" +version = "0.1.0" + +[[package]] +name = "jess-gust-hal-stub" +version = "0.1.0" +dependencies = [ + "jess-bump-alloc", + "wit-bindgen", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.119", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "wit-bindgen" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e048f41ef90f0b5dd61f1059c35f5636252e56813bf616d0803aa3739867230" +dependencies = [ + "bitflags", + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c15e7a56641cc9040480a26526a3229cbc4e8065adf98c9755d21c4c9b446c4c" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd81b0ae1ec492bfe91683f1da6db6492ebc682e72d4f2715619dba783b066ca" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn 2.0.119", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54e6ce04c549e7149b66a70d34fc5a2a01b374bf49ca61db65d16e3ae922866e" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.119", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/app/gust-hal-stub/Cargo.toml b/app/gust-hal-stub/Cargo.toml new file mode 100644 index 0000000..9046120 --- /dev/null +++ b/app/gust-hal-stub/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "jess-gust-hal-stub" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +jess-bump-alloc = { path = "../bump-alloc" } +wit-bindgen = { version = "0.52", default-features = false, features = ["macros", "realloc", "bitflags"] } + +[profile.release] +opt-level = "s" +lto = true +panic = "abort" diff --git a/app/gust-hal-stub/src/lib.rs b/app/gust-hal-stub/src/lib.rs new file mode 100644 index 0000000..0ae200b --- /dev/null +++ b/app/gust-hal-stub/src/lib.rs @@ -0,0 +1,38 @@ +//! jess's native residual, stubbed so a composed image can RUN off-target. +#![no_std] + +mod bindings { + wit_bindgen::generate!({ path: "wit", world: "hal", generate_all }); +} + +use bindings::exports::gust::hal::mmio::Guest as Mmio; +use bindings::exports::gust::os::taskdisp::Guest as TaskDisp; + +struct Stub; + +impl Mmio for Stub { + /// Returns a value DERIVED FROM THE ADDRESS, not a constant. + /// + /// A constant (0, or 0xDEADBEEF) would make a stubbed read indistinguishable + /// from a read that never happened — the same vacuous-metric trap that voided + /// the CRC claim in AFD-037. Folding the address in means a caller can prove + /// the stub was actually reached with the argument it expected. + fn read32(addr: u32) -> u32 { + addr ^ 0x1E55_0000 + } + fn write32(_addr: u32, _val: u32) { + // No silicon off-target. Real MMIO is the on-target rung, not this one. + } +} + +impl TaskDisp for Stub { + /// 0 = "task complete". The stub never reports pending, so a composed image + /// cannot livelock waiting on a dispatcher that will never advance. + fn poll_task(_id: u32) -> u32 { + 0 + } +} + +bindings::export!(Stub with_types_in bindings); + +jess_bump_alloc::install!(); diff --git a/app/gust-hal-stub/wit/deps/gust-hal/gust-hal.wit b/app/gust-hal-stub/wit/deps/gust-hal/gust-hal.wit new file mode 100644 index 0000000..b5415d8 --- /dev/null +++ b/app/gust-hal-stub/wit/deps/gust-hal/gust-hal.wit @@ -0,0 +1,7 @@ +package gust:hal@0.1.0; + +interface mmio { + read32: func(addr: u32) -> u32; + + write32: func(addr: u32, val: u32); +} diff --git a/app/gust-hal-stub/wit/deps/gust-os/gust-os.wit b/app/gust-hal-stub/wit/deps/gust-os/gust-os.wit new file mode 100644 index 0000000..072674b --- /dev/null +++ b/app/gust-hal-stub/wit/deps/gust-os/gust-os.wit @@ -0,0 +1,34 @@ +package gust:os@0.1.0; + +interface taskdisp { + poll-task: func(id: u32) -> u32; +} +interface time { + now: func() -> u64; + + deadline: func(now: u64, ticks: u64) -> u64; + + elapsed: func(now: u64, deadline: u64) -> bool; + + resolution: func() -> u64; +} +interface log { + line: func(msg: list); +} +interface spawn { + start: func(entry: u32) -> u32; + + poll: func(handle: u32) -> u32; +} +interface exec { + admit: func(prio: u32, deadline-lo: u32, deadline-hi: u32) -> u32; + + poll-round: func(now-lo: u32, now-hi: u32); + + state: func(h: u32) -> u32; +} +interface timer { + sleep: func(handle: u32, ticks: u32) -> u32; + + slept: func(handle: u32) -> u32; +} diff --git a/app/gust-hal-stub/wit/world.wit b/app/gust-hal-stub/wit/world.wit new file mode 100644 index 0000000..6594686 --- /dev/null +++ b/app/gust-hal-stub/wit/world.wit @@ -0,0 +1,15 @@ +package jess:gust-hal-stub@0.1.0; + +// jess's NATIVE RESIDUAL (DD-026 layer iii), in stub form. +// +// This world was not designed — it was DERIVED. Composing flight-app against +// gale-nano + the falcon cascade leaves exactly these imports unsatisfied, so +// this is the mechanically-determined list of what jess still owes the embedder. +// +// STUB, not an implementation: mmio returns a marker instead of touching silicon, +// so a composed image can be EXECUTED off-target. Real MMIO is the on-target rung. +world hal { + export gust:hal/mmio@0.1.0; + export gust:os/taskdisp@0.1.0; +} + diff --git a/artifacts/findings.yaml b/artifacts/findings.yaml index 3674f99..078f84c 100644 --- a/artifacts/findings.yaml +++ b/artifacts/findings.yaml @@ -1587,7 +1587,7 @@ artifacts: - id: AFD-043 type: ai-found-defect title: THE COMPLETE WASM STACK CANNOT BE COMPOSED YET — falcon imports NO gust:os, so the DD-026 seam has nothing to plug; the flight APPLICATION component is missing and it is JESS'S to build - status: open + status: resolved description: > 2026-08-27, prompted by a direct question jess could not answer honestly ("have you worked to have this integrated with gale to build the complete wasm stack"). The answer was NO - and checking @@ -1614,7 +1614,8 @@ artifacts: intended shape of the gust:os side rather than guess, since gale owns that interface. SECOND MISSING PIECE, also jess-owned: gust:hal (read32/write32), gale-nano's residual. The stack needs BOTH the application seam AND the native HAL before "complete" means anything. - STATUS open deliberately - a live gap with jess as the owner, not a resolved finding. + *** RESOLVED 2026-08-27 by AFD-044: both missing pieces are now BUILT, and the full stack + composes, runs, and agrees with an independent reference. See AFD-044 for the evidence. *** GALE ANSWERED 2026-08-27 — the shape, and a correction to jess's METHOD ***: (4) NEITHER PLUG DIRECTION — USE `wac compose` WITH A WAC SCRIPT, NOT `wac plug`. jess's error had a specific cause unrelated to the components: a plug result exports only the SOCKET's exports, @@ -1972,3 +1973,67 @@ artifacts: target: EXTERNALANCHOR-008 - type: traces-to target: REQ-PIX-007 + + - id: AFD-044 + type: ai-found-defect + severity: major + triage-status: confirmed + detected-by: ai-agent + title: THE COMPLETE WASM STACK NOW COMPOSES AND RUNS — flight-app + gust-hal-stub close the DD-026 seam; composed image agrees EXACTLY with an independent reference (1348), both legs positively observed + status: resolved + description: > + 2026-08-27. AFD-043 identified two missing pieces, both jess-owned: the flight APPLICATION + component (the driver mapping falcon onto gust:os) and the gust:hal native residual. Both are + now built, and the whole stack composes, executes, and is checked against an independent + reference. AFD-043 is resolved by this finding. + WHAT WAS BUILT + app/flight-app imports gust:os/time + falcon rate/mixer, exports run: func() -> u32 + app/gust-hal-stub exports gust:hal/mmio + gust:os/taskdisp (stub; real MMIO is on-target) + app/bump-alloc shared no_std bump allocator over __heap_base, never calls memory.grow + *** THE RESIDUAL WAS DERIVED, NOT DESIGNED *** — the strongest part of this result. Composing + flight-app against gale-nano + falcon left EXACTLY three unsatisfied imports: + gust:os/taskdisp, gust:hal/mmio, and falcon types. That is the mechanically-determined list of + what jess still owes the embedder. It was previously a guess; it is now a wac output. + COMPOSITION (tools/appcompose/build-and-verify.sh, one command, reproducible) + wac compose flight-app + gale-nano + rate + mixer -> step1.wasm (3 imports left) + wac compose step1 + gust-hal-stub -> full.wasm (types-only import left) + wasm-tools validate full.wasm -> VALID + wasmtime run --invoke run() full.wasm -> 2147484996 + RESULT DECOMPOSED — both legs POSITIVELY observed, neither inferred from an absence: + low 31 bits = 1348 the falcon cascade fold + bit 31 = 1 gale's clock did real arithmetic on OUR argument + The clock bit was FIRST WRITTEN VACUOUSLY and corrected before reporting. The initial version + set bit 31 from `elapsed(now, deadline) == false`, which an inert clock returns too — the same + trap that voided the CRC claim in AFD-037. It now tests `deadline(t0,1) != t0`, which no + zero-returning stub can produce. + DIFFERENTIAL, not a golden number. tools/cascade-differential/cascade_ref.py recomputes the + same fold by a COMPLETELY DIFFERENT ROUTE — fused core module, raw canonical-ABI pointers, no + component model, no wac, no gust:os. Both routes give 1348. A golden value would be satisfied + by both sides sharing one bug; agreement across two routes would not be. + torque tx=1 ty=0.472507507 tz=-0.147003502 thrust=0.5 <- reproduces the SIL reference exactly + pwm m1=0 m2=0 m3=0.348992109 m4=1 sum=1.34899211 + NEGATIVE CONTROLS, all three run: + (i) reference perturbed (wx 0.30 -> 0.35) -> 2000, DISTINCT: the fold tracks its input, so + 1348 is not a constant a miscompile that drops the state would also produce. + (ii) reference deliberately falsified (x1000 -> x1001) -> oracle EXITS 1 with + "DIFFERENTIAL MISMATCH: composed=1348 reference=1350"; restored -> PASS. The gate can fail. + (iii) publish gate on jess's OWN components: FAILED C4+C5 before the link flags, PASSED after. + *** JESS FAILED ITS OWN PUBLISH GATE *** — worth stating plainly. flight-app was NOT CONSUMABLE + on first build: no reloc.* sections and no __heap_base, the exact defect jess reported against + relay's publish path. lld defines __heap_base synthetically but does not export it, and emits no + relocations, unless asked. Fixed with --emit-relocs / --export=__heap_base in .cargo/config.toml. + Both jess components now pass all five checks. The gate was not written to point outward only. + INCIDENTAL: casc_new.wasm (pre-loom) yields the same 1348 as casc_new.loom.wasm — loom is + numerically neutral on this vector. Recorded as an observation, NOT as a general claim: one + vector cannot establish that. + TOOL FRICTION FOUND: cargo-component 0.21.1 refuses to merge a local wit/deps tree that + wasm-tools resolves without complaint ("package 'gust:os@0.1.0' not found" vs rc=0). Routed + around via plain cargo + `wasm-tools component new`. To be reported. + NOT CLAIMED: this ran under wasmtime, NOT on the RT1176 and not in Renode. Periodicity is still + a single call, not a schedule — the timer-ISR vs partition-window question (DD-025) remains + open. gust:hal is a STUB; real MMIO is the on-target rung. + links: + - type: traces-to + target: DD-026 + - type: traces-to + target: REQ-PIX-007 diff --git a/tools/appcompose/build-and-verify.sh b/tools/appcompose/build-and-verify.sh new file mode 100755 index 0000000..9b77429 --- /dev/null +++ b/tools/appcompose/build-and-verify.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +# Build the jess flight application, compose the FULL wasm stack, run it, and check +# the result against an independently-computed reference. +# +# This is the oracle for AFD-043 (the missing application seam). It is written as a +# DIFFERENTIAL rather than a golden value on purpose: the composed component and the +# reference reach the same number by completely different routes — component model + +# wac composition + gale's gust:os on one side, fused core module + raw canonical-ABI +# pointers on the other. A single golden number would be satisfied by both sides +# sharing one bug; agreement across the two routes would not. +set -euo pipefail +ROOT="$(cd "$(dirname "$0")/../.." && pwd -P)" +OUT="${OUT:-$ROOT/.scratch/appcompose}" +PY="${PY:-python3}" +FUSED="${FUSED:-$ROOT/.scratch/v1341/casc_new.loom.wasm}" +mkdir -p "$OUT" + +say() { printf '%s\n' "$*"; } +fail() { printf 'FAIL: %s\n' "$*" >&2; exit 1; } + +# Preflight. The upstream components are NOT vendored in jess (they are supplier +# artifacts, fetched via the DD-026 OCI path). Say so explicitly rather than dying +# on a bare `cp: no such file`, which reads like a jess bug when it is a missing input. +RATE="$ROOT/.scratch/v1341/rate.wasm" +MIXER="$ROOT/.scratch/v1341/mixer.wasm" +NANO="$ROOT/.scratch/galenano7/gale-nano-0.7.0.wasm" +missing=0 +for f in "$RATE" "$MIXER" "$NANO" "$FUSED"; do + [ -f "$f" ] || { printf 'MISSING UPSTREAM ARTIFACT: %s\n' "$f" >&2; missing=1; } +done +if [ "$missing" = 1 ]; then + cat >&2 <<'MSG' + +These are supplier artifacts (relay falcon v1.134.1, gale-nano 0.7.0), not jess +sources, so they are not in git. Fetch them via the DD-026 OCI consumption path +before running this oracle. +MSG + exit 2 +fi + +for t in cargo wasm-tools wac wasmtime; do + command -v "$t" >/dev/null || fail "required tool not on PATH: $t" +done + +say "== 1. build the two jess components ==" +for c in flight-app gust-hal-stub; do + ( cd "$ROOT/app/$c" && cargo build --release --target wasm32-unknown-unknown ) + core="$ROOT/app/$c/target/wasm32-unknown-unknown/release/$(echo "jess_$c" | tr - _).wasm" + [ -f "$core" ] || fail "core module not produced for $c" + wasm-tools component new "$core" -o "$ROOT/app/$c/$c.wasm" + cp "$ROOT/app/$c/$c.wasm" "$OUT/" +done + +say "== 2. publish gate (jess holds ITSELF to the gate it asks suppliers to pass) ==" +"$ROOT/tools/publish-gate/check-consumable.sh" "$OUT/flight-app.wasm" "$OUT/gust-hal-stub.wasm" \ + || fail "jess's own components are not consumable" + +say "== 3. compose ==" +# the .wac graphs are REPO files, not scratch: $OUT is gitignored, so a fresh clone +# would otherwise reach this line with no composition graph and fail confusingly. +cp "$ROOT/tools/appcompose/compose.wac" "$ROOT/tools/appcompose/compose2.wac" "$OUT/" +cp "$RATE" "$MIXER" "$OUT/" +cp "$NANO" "$OUT/gale-nano.wasm" +( cd "$OUT" + wac compose --dep gust:runtime=gale-nano.wasm --dep pulseengine:rate=rate.wasm \ + --dep pulseengine:mixer=mixer.wasm --dep jess:flight-app=flight-app.wasm \ + compose.wac -o step1.wasm + wac compose --dep jess:hal=gust-hal-stub.wasm --dep jess:step1=step1.wasm \ + compose2.wac -o full.wasm ) +wasm-tools validate "$OUT/full.wasm" || fail "composed image does not validate" + +say "== 4. RUN the composed image ==" +got="$(wasmtime run --invoke 'run()' "$OUT/full.wasm" | tail -1)" +lo=$(( got & 0x7fffffff )); hi=$(( (got >> 31) & 1 )) +say " returned $got -> falcon fold=$lo clock-live=$hi" +[ "$hi" = "1" ] || fail "clock leg inert — gale's deadline() did not act on our argument" + +say "== 5. independent reference + negative control ==" +ref_out="$("$PY" "$ROOT/tools/cascade-differential/cascade_ref.py" "$FUSED")" || fail "reference driver failed (this includes its own negative control)" +printf '%s\n' "$ref_out" | sed 's/^/ /' +want="$(printf '%s\n' "$ref_out" | sed -n 's/.*EXPECTED FOLDED u32 (low 31 bits): //p')" +[ -n "$want" ] || fail "could not parse the reference value" +[ "$lo" = "$want" ] || fail "DIFFERENTIAL MISMATCH: composed=$lo reference=$want" + +say +say "PASS — composed image and independent reference agree ($lo), clock leg live," +say " and the reference's own negative control showed the fold tracks its input." diff --git a/tools/appcompose/compose.wac b/tools/appcompose/compose.wac new file mode 100644 index 0000000..f9be402 --- /dev/null +++ b/tools/appcompose/compose.wac @@ -0,0 +1,12 @@ +package jess:composed; + +let gale = new gust:runtime { ... }; +let r = new pulseengine:rate { ... }; +let m = new pulseengine:mixer { ... }; +let app = new jess:flight-app { + "gust:os/time@0.1.0": gale["gust:os/time@0.1.0"], + "pulseengine:falcon-cascade/rate@0.7.0": r["pulseengine:falcon-cascade/rate@0.7.0"], + "pulseengine:falcon-cascade/mixer@0.7.0": m["pulseengine:falcon-cascade/mixer@0.7.0"], + ... +}; +export app...; diff --git a/tools/appcompose/compose2.wac b/tools/appcompose/compose2.wac new file mode 100644 index 0000000..2bbfca9 --- /dev/null +++ b/tools/appcompose/compose2.wac @@ -0,0 +1,8 @@ +package jess:full; +let hal = new jess:hal { ... }; +let app = new jess:step1 { + "gust:hal/mmio@0.1.0": hal["gust:hal/mmio@0.1.0"], + "gust:os/taskdisp@0.1.0": hal["gust:os/taskdisp@0.1.0"], + ... +}; +export app...; diff --git a/tools/cascade-differential/cascade_ref.py b/tools/cascade-differential/cascade_ref.py new file mode 100644 index 0000000..a6d6d5b --- /dev/null +++ b/tools/cascade-differential/cascade_ref.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +"""Independent reference for the COMPOSED flight-app: rate -> mixer, computed +without the component model. + +The composed component returns a single folded u32. That is deliberately hard to +fake but also opaque, so this driver recomputes the same fold from the FUSED CORE +MODULE via the canonical ABI — a completely different execution path (core module + +raw pointers, no wac composition, no gust:os). If the two agree, the composition +did not quietly change the arithmetic. + +Canonical ABI (from the WIT + the lowered signatures): + rate@0.7.0#tick : (param i32) -> (result i32) arg -> 14xf32 state ++ 4xf32 sp + mixer@0.7.0#mix : (param f32 f32 f32 f32) -> (result i32) ret -> 4xf32 pwm +""" +import struct, sys +from wasmtime import Store, Module, Instance + +MODULE = sys.argv[1] if len(sys.argv) > 1 else ".scratch/v1341/casc_new.loom.wasm" +VEHICLE_STATE = [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, -2.5, 0.1, -0.2, 0.05, 0.30, -0.15, 0.07, 0.0] +RATE_SETPOINT = [1.0, 0.0, 0.0, 0.5] + + +def main(): + store = Store() + inst = Instance(store, Module.from_file(store.engine, MODULE), []) + ex = inst.exports(store) + mem = ex["memory"] + argp = (ex["__heap_base"].value(store) + 0xF) & ~0xF + + mem.write(store, struct.pack("<14f", *VEHICLE_STATE) + struct.pack("<4f", *RATE_SETPOINT), argp) + tp = ex["pulseengine:falcon-cascade/rate@0.7.0#tick"](store, argp) + torque = struct.unpack("<4f", mem.read(store, tp, tp + 16)) + + # NOTE the asymmetry: `mix` takes its 4 f32s FLATTENED, while `tick` takes a + # pointer — 18 scalars exceeds the canonical ABI's flattening limit, 4 does not. + # Assuming a uniform pointer-in convention here silently passes garbage. + pp = ex["pulseengine:falcon-cascade/mixer@0.7.0#mix"](store, *torque) + pwm = struct.unpack("<4f", mem.read(store, pp, pp + 16)) + + # The SAME fold the component performs, in f32 to match wasm arithmetic exactly. + acc32 = struct.unpack(" 0.35 + mem.write(store, struct.pack("<14f", *pert) + struct.pack("<4f", *RATE_SETPOINT), argp) + tp2 = ex["pulseengine:falcon-cascade/rate@0.7.0#tick"](store, argp) + t2 = struct.unpack("<4f", mem.read(store, tp2, tp2 + 16)) + p2 = ex["pulseengine:falcon-cascade/mixer@0.7.0#mix"](store, *t2) + pwm2 = struct.unpack("<4f", mem.read(store, p2, p2 + 16)) + a2 = struct.unpack(" 0.35): {f2}" + f" {'DISTINCT — the fold tracks the input' if f2 != folded else 'IDENTICAL — VACUOUS, fold ignores state'}") + return 0 if f2 != folded else 1 + + +if __name__ == "__main__": + sys.exit(main()) From 36591af9bf518e68db4c83736656aa58662798ac Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Thu, 27 Aug 2026 22:55:15 +0200 Subject: [PATCH 08/10] =?UTF-8?q?correction:=20WITHDRAW=20the=20cargo-comp?= =?UTF-8?q?onent=20"tool=20friction"=20claim=20in=20AFD-044=20=E2=80=94=20?= =?UTF-8?q?it=20was=20jess's=20misuse?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous commit recorded, in AFD-044 and in its commit message, that cargo-component 0.21.1 "refuses to merge a local wit/deps tree that wasm-tools resolves without complaint", and marked it "to be reported". That is WRONG. A minimal 4-line repro (two packages, no jess specifics) isolated the real cause: cargo-component does NOT auto-discover wit/deps/. It requires the dependency declared explicitly: [package.metadata.component.target.dependencies] "acme:dep" = { path = "wit/deps/dep/dep.wit" } With that single line it builds cleanly. Renaming the dep directory (acme:dep, acme-dep, dep@0.1.0) does not help, which is what made it look like a resolver bug rather than a missing declaration. Caught BEFORE reporting it to bytecodealliance — but it was already committed into a safety-case artifact, which is exactly the failure jess corrected synth for in AFD-042: a wrong citation sitting in an artifact under someone's name. Withdrawn explicitly rather than quietly deleted, so the correction is auditable. The BUILD CHOICE is unchanged and still correct, on a different ground: cargo-component defaults to wasm32-wasip1, which publish-gate C3 (no-wasi) REFUSES. jess needs wasm32-unknown-unknown plus --emit-relocs and --export=__heap_base, so plain cargo + `wasm-tools component new` is right here — but for that reason, not the one originally given. rivet validate PASS. Co-Authored-By: Claude Opus 4.8 --- artifacts/findings.yaml | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/artifacts/findings.yaml b/artifacts/findings.yaml index b566b34..84fd65e 100644 --- a/artifacts/findings.yaml +++ b/artifacts/findings.yaml @@ -1716,9 +1716,18 @@ artifacts: INCIDENTAL: casc_new.wasm (pre-loom) yields the same 1348 as casc_new.loom.wasm — loom is numerically neutral on this vector. Recorded as an observation, NOT as a general claim: one vector cannot establish that. - TOOL FRICTION FOUND: cargo-component 0.21.1 refuses to merge a local wit/deps tree that - wasm-tools resolves without complaint ("package 'gust:os@0.1.0' not found" vs rc=0). Routed - around via plain cargo + `wasm-tools component new`. To be reported. + *** A "TOOL FRICTION" CLAIM MADE HERE WAS WRONG AND IS WITHDRAWN (same day) ***: this finding + first recorded that cargo-component 0.21.1 "refuses to merge a local wit/deps tree that + wasm-tools resolves". A minimal 4-line repro showed the real cause is JESS'S MISUSE, not a + defect: cargo-component does not auto-discover wit/deps/, it requires the dependency declared + in [package.metadata.component.target.dependencies]. Adding that one line builds cleanly. The + claim was caught BEFORE it was reported to anyone, but it had already been committed here and + in the commit message, which is the same failure jess corrected synth for in AFD-042 — a wrong + citation sitting in a safety-case artifact. Withdrawn rather than quietly edited away. + The BUILD CHOICE still stands, on a different and now-correct ground: cargo-component defaults + to wasm32-wasip1, which publish-gate C3 (no-wasi) refuses. jess needs wasm32-unknown-unknown + plus specific lld flags (--emit-relocs, --export=__heap_base), so plain cargo + + `wasm-tools component new` is the right route here — but for that reason, not the stated one. NOT CLAIMED: this ran under wasmtime, NOT on the RT1176 and not in Renode. Periodicity is still a single call, not a schedule — the timer-ISR vs partition-window question (DD-025) remains open. gust:hal is a STUB; real MMIO is the on-target rung. From fae9b5c85335bd0f8de78ffc79940dd33bd1630f Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Thu, 27 Aug 2026 23:27:08 +0200 Subject: [PATCH 09/10] =?UTF-8?q?finding:=20the=20COMPOSED=20stack=20has?= =?UTF-8?q?=20no=20MCU=20lowering=20path=20=E2=80=94=20meld#390=20filed=20?= =?UTF-8?q?(AFD-045)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AFD-044 closed the composition gap. The natural next rung was lowering the composed image for the RT1176. It does not lower, and the reason is a meld defect jess found, minimised and filed. BOTH TINES OF THE FORK ARE DEAD ENDS TODAY: --memory multi (auto) validates and RUNS CORRECTLY, but meld itself states multi-memory "has no single-address-space (MCU) lowering" (meld#172) --memory shared --address-rebase the ONLY single-address-space route — and it emits a module that FAILS wasm-tools validate, while meld EXITS 0 MINIMAL REPRO, two components rather than the 19-component graph: meld fuse flight-app.wasm rate.wasm --memory shared --address-rebase -> exit 0 wasm-tools validate -> func 14: "values remaining on stack at end of block (offset 0x840)" DISTINCT FROM THE KNOWN REBASE ISSUES, which is why it was filed rather than appended: meld#351 says explicitly "the fused module validates cleanly", and #326/#339 are about wrong addresses in a VALID module. Here the module is not valid and meld reports success. THE DISCRIMINATING TABLE (all measured): rate + mixer VALID both record-carrying, but neither imports the other flight-app + gale-nano VALID a call crosses, but gust:os/time is scalar-only flight-app + rate INVALID func 14 full 19-component graph INVALID func 72, "expected f32, found i32" Hypothesis offered to meld as a search aim, NOT a diagnosis: the trigger is a wired cross-component call carrying a RECORD via linear memory. jess has not read the rebase implementation. RULED OUT BEFORE FILING: not relocation-driven (no --emit-relocs reproduces func 14 identically); not a wac artefact (raw components, no wac step, fail the same); not general breakage (the falcon fused cascade still validates); not bad inputs (the default path RUNS and reproduces 1348). A NARROWING ERROR WAS MADE AND CORRECTED, and is recorded because the wrong version was briefly believed: the first pass scored "INVALID" for cases where meld had produced NO OUTPUT AT ALL — wasm-tools was validating a non-existent file and its failure was read as an invalid module. Two distinct failures under one label. Re-run checking meld's and wasm-tools' exit codes SEPARATELY, which produced the table above. The corrected run reaches the same conclusion, but the first pass was not evidence for it. IMPACT: this does NOT block the falcon cascade alone — that still lowers 5/5 on m7dp and m4f (AFD-042). It blocks lowering the cascade TOGETHER WITH the runtime and application, which is what DD-026 requires. rivet validate PASS. Co-Authored-By: Claude Opus 4.8 --- artifacts/findings.yaml | 61 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/artifacts/findings.yaml b/artifacts/findings.yaml index 84fd65e..8ee4e43 100644 --- a/artifacts/findings.yaml +++ b/artifacts/findings.yaml @@ -1737,6 +1737,67 @@ artifacts: - type: traces-to target: REQ-PIX-007 + - id: AFD-045 + type: ai-found-defect + severity: major + triage-status: confirmed + detected-by: jess attempted to lower the newly-composed DD-026 stack for the RT1176, 2026-08-27 + title: THE COMPOSED STACK HAS NO MCU LOWERING PATH — multi-memory validates but has no single-address-space lowering, and shared+address-rebase emits INVALID wasm with exit 0 (meld#390, distinct from #351/#326/#339) + status: open + description: > + 2026-08-27, immediately after AFD-044 closed the composition gap. The natural next rung was to + lower the composed image for the RT1176. It does not lower today, and the reason is a meld + defect that jess found, minimised and filed as meld#390. + THE FORK, and both tines are currently dead ends: + --memory multi (the `auto` choice) VALIDATES and RUNS CORRECTLY, but meld itself states + multi-memory "has no single-address-space (MCU) + lowering" (meld#172). Cannot reach the RT1176. + --memory shared --address-rebase the ONLY single-address-space route, and it emits a + module that FAILS wasm-tools validate. meld EXITS 0. + MINIMAL REPRO — two components, not the 19-component graph: + meld fuse flight-app.wasm rate.wasm --memory shared --address-rebase -> exit 0 + wasm-tools validate -> func 14 failed: + "type mismatch: values remaining on stack at end of block (at offset 0x840)" + *** DISTINCT FROM THE KNOWN REBASE ISSUES *** and this is why it was filed rather than added to + one: meld#351 states explicitly that "the fused module validates cleanly", and #326/#339 are + likewise about WRONG ADDRESSES IN A VALID MODULE. Here the module is not valid at all and meld + reports success. The only warning printed is the standard #326 unsoundness note, which points + at computed addresses - not at malformed output. + WHAT SEPARATES PASS FROM FAIL (the discriminating table, all measured): + flight-app alone / rate alone valid + rate + mixer VALID <- both record-carrying, but neither imports + the other: no call crosses the boundary + flight-app + gale-nano VALID <- a call DOES cross, but gust:os/time is + scalar-only (u64/bool) + flight-app + rate INVALID (func 14) + flight-app + mixer INVALID (func 12) + full 19-component composition INVALID (func 72, "expected f32, found i32") + HYPOTHESIS OFFERED TO MELD AS A SEARCH AIM, NOT AS A DIAGNOSIS: the trigger is a wired + cross-component call whose signature passes or returns a RECORD via linear memory - exactly the + calls the rebase pass must touch. jess has not read the rebase implementation and did not claim to. + RULED OUT BEFORE FILING: not relocation-driven (rebuilding flight-app WITHOUT --emit-relocs + reproduces the identical func 14 failure); not a wac artefact (fusing the four raw components + with no wac step fails identically); not general toolchain breakage (the pre-existing falcon + fused cascade still validates); not a bad input (the default path produces a valid module that + RUNS and reproduces the AFD-044 reference value 1348 exactly). + *** A NARROWING ERROR WAS MADE AND CORRECTED MID-INVESTIGATION *** - recorded because the wrong + version was briefly believed. The first narrowing pass reported "INVALID" for cases where meld + had actually FAILED TO PRODUCE OUTPUT AT ALL: validate was run on a non-existent file and its + failure was scored as an invalid module. Two different failures were being conflated under one + label. Re-run with meld's exit code and wasm-tools' exit code checked SEPARATELY, which is what + produced the table above. The corrected run happens to reach the same conclusion, but the first + pass was not evidence for it. + CAMPAIGN IMPACT: the on-target rung for the COMPOSED stack is blocked upstream. Note this does + NOT block the falcon cascade alone - that image still lowers 5/5 on m7dp and m4f (AFD-042). What + is blocked is lowering the cascade TOGETHER WITH the runtime and the application, which is what + DD-026 actually requires. Filed meld#390; awaiting response. + tags: [meld, address-rebase, lowering, mcu, dd-026, composition, blocker, upstream] + links: + - type: traces-to + target: DD-026 + - type: traces-to + target: REQ-PIX-021 + - id: AFD-042 type: ai-found-defect title: synth v0.60.0 — 5/5 on BOTH m7dp and m4f (the M4 estimator lockout is closed), image byte-identical across three independent builds; and a correction — synth cited "jess executed it on hardware" for a `verified` status flip, which is FALSE From 13181a2c108415a7d481ee7ff73af5064ca58970 Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Thu, 27 Aug 2026 23:38:19 +0200 Subject: [PATCH 10/10] correction: AFD-045 + meld#390 were filed against STALE TOOLING (meld 0.41.3; 0.52.0 is latest) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A process failure, recorded rather than quietly fixed. The whole investigation and the initial meld#390 report ran on meld 0.41.3 while 0.52.0 is latest — eleven minor versions. "Run a supplier's DOCUMENTED pipeline before reporting a failure against it" is a standing rule and jess broke it. Re-ran everything on 0.52.0 and corrected meld#390 in a comment. THE DEFECT IS REAL AND REPRODUCES EXACTLY on 0.52.0 — same func 14, same offset 0x840 — so the conclusion survives. But it was not EARNED until the re-run, and three specifics were wrong: (a) BROADER than filed. --pack-rebase did not exist in 0.41.3 and was never tested. It fails identically. That matters: --pack-rebase is the path the campaign actually uses. (b) LESS SEVERE than filed. meld already ships --validate, which CATCHES this (exit 1, "Validation failed"). The "silent, exit 0" framing was an overstatement — it is silent only by DEFAULT. jess should have looked for the flag before calling the failure silent. (c) the version citation in the report header was wrong. MATRIX RE-MEASURED ON 0.52.0: rate + mixer --pack-rebase VALID <- falcon-only cascade path NOT affected flight-app + rate --pack-rebase INVALID full composition --pack-rebase INVALID full composition --address-rebase INVALID full composition --memory multi VALID (but no MCU lowering) The rate+mixer row matters: this does NOT regress existing cascade lowering. It breaks fusing the cascade TOGETHER WITH an application that imports it. SECOND-ORDER FINDING, and the more useful one: local meld was 0.41.3, CI pins v0.41.3, latest is 0.52.0 — and NOTHING DETECTED THE DRIFT because jess has no varve pin. This is the concrete cost of that gap: an upstream report filed against tooling eleven versions old. The varve pin moves from "nice to have" to a defect-prevention control with a demonstrated failure behind it. rivet validate PASS. Co-Authored-By: Claude Opus 4.8 --- artifacts/findings.yaml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/artifacts/findings.yaml b/artifacts/findings.yaml index 8ee4e43..f36e0e7 100644 --- a/artifacts/findings.yaml +++ b/artifacts/findings.yaml @@ -1787,6 +1787,30 @@ artifacts: label. Re-run with meld's exit code and wasm-tools' exit code checked SEPARATELY, which is what produced the table above. The corrected run happens to reach the same conclusion, but the first pass was not evidence for it. + *** THIS FINDING WAS FIRST WRITTEN AGAINST STALE TOOLING - A PROCESS FAILURE, RECORDED ***: the + investigation and the initial meld#390 report were run on meld 0.41.3 while 0.52.0 IS LATEST - + eleven minor versions. "Run a supplier's DOCUMENTED pipeline before reporting a failure against + it" is a standing rule and it was broken. Re-run in full on 0.52.0 and meld#390 corrected in a + comment. THE DEFECT IS REAL AND REPRODUCES EXACTLY on 0.52.0 (same func 14, same offset 0x840), + so the conclusion survives - but it was not earned until the re-run, and three specifics were wrong: + (a) BROADER THAN FILED: `--pack-rebase` did not exist in 0.41.3 and was never tested. It fails + IDENTICALLY. That matters because --pack-rebase is the path the campaign actually uses. + (b) LESS SEVERE THAN FILED: meld already ships `--validate`, which CATCHES this (exit 1, + "Validation failed"). The "silent, exit 0" framing was an overstatement - it is silent only + by DEFAULT, and jess should have checked for the flag before characterising it. + (c) the version citation in the report header was wrong. + MATRIX RE-MEASURED ON MELD 0.52.0: + rate + mixer --pack-rebase VALID <- the falcon-only cascade path is NOT affected + flight-app + rate --pack-rebase INVALID + full composition --pack-rebase INVALID + full composition --address-rebase INVALID + full composition --memory multi VALID (but no MCU lowering) + The `rate + mixer` row is the important one: this does NOT regress the existing cascade lowering. + It specifically breaks fusing the cascade TOGETHER WITH an application that imports it. + *** SECOND-ORDER FINDING - JESS'S TOOLCHAIN IS UNPINNED AND DRIFTED ***: local meld was 0.41.3, + CI pins MELD_VERSION v0.41.3, and latest is 0.52.0. Nothing detected the drift because JESS HAS + NO VARVE PIN. This is the concrete cost of that gap: an upstream report filed against tooling + eleven versions old. The varve pin moves from "nice to have" to a real defect-prevention control. CAMPAIGN IMPACT: the on-target rung for the COMPOSED stack is blocked upstream. Note this does NOT block the falcon cascade alone - that image still lowers 5/5 on m7dp and m4f (AFD-042). What is blocked is lowering the cascade TOGETHER WITH the runtime and the application, which is what