fix(gust): the composite gate had a syntax error, and CI could not have seen it - #254
Merged
Merged
Conversation
…ve seen it #250's finding was that a gate could not detect the thing it existed to guard. It shipped with the same defect one level up. 1. build-fused-gustos.sh does not parse. The new instantiation-count block was inserted BETWEEN a line-continuation backslash and its `&& echo`, orphaning the continuation: line 178: syntax error near unexpected token `&&' `bash -n` is clean on a9bbfbf~1 and broken on a9bbfbf. The script aborts at 178, so every check after it never ran -- including the residual-import delivery invariant, which is the gate the release notes tell people to trust. 2. CI structurally could not have caught it. gustos-publish.yml's path filter lists build-gustos-components.sh but not build-fused-gustos.sh. NONE of the six files #250 touched matched the filter, so the workflow that runs the script never triggered. Its 61/61 green was real and said nothing about this change. 3. run-gustos-hostrun.sh swallowed the failure. `build-fused-gustos.sh | tail -2 || exit 1` tests tail's status, not the script's -- which is exactly why a syntax error stayed quiet. Now checks PIPESTATUS[0]. Fixes: reattach the continuation; extend the path filter to build-fused-gustos.sh, run-gustos-hostrun.sh and gustos-hostrun/** (all six of #250's files now match); add a `bash -n` gate over the 16 driver + silicon scripts, because this class is free to catch; propagate the build failure. And the one that was the point: CI now EXECUTES the composite. VER-OS-COMPOSITE-EXEC-001 requires "run in CI on every change to any provider so the property cannot silently stop holding" -- until now nothing ran it, so E1 was committed evidence rather than a gate. The step runs the harness and its negative controls. The harness exits 3, not 0: claims held, 3 WIT return-contract deviations observed (timer.sleep returns success for an out-of-range or already-done handle, so the arm is silently lost -- reported not fixed in #250, providers were out of scope). A bare `run:` would have failed the step on a passing result. The gate accepts 3 only when the count is exactly 3, so a FOURTH deviation fails instead of hiding. Verified locally after the fix: bash -n: 16/16 scripts clean E1: 21 passed, 0 FAILED -- one-scheduler and one-clock HELD under execution negative control 1 (two schedulers): 16 passed, 5 FAILED, exit 1 -- still a live gate negative control 2 (two stateless clocks): 21 passed -- the documented blind spot, recorded not gated Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011QG86sovTbfnPNY9SfhSmo
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
avrabe
added a commit
that referenced
this pull request
Aug 6, 2026
…ts an artifact (#255) * close(v0.7.0): E1's V closes on a GATED result, and the timer defect gets an artifact REQ/VER-OS-COMPOSITE-EXEC-001 proposed -> verified. The evidence existed on main since #250 but the V could not honestly close on it: VER-OS-COMPOSITE-EXEC-001 requires "run in CI on every change to any provider so the property cannot silently stop holding", and nothing ran it. #254 wired it in, so what closes here is a gate, not a run that happened once. Recorded on the verification artifact: the handle crossing spawn -> timer -> exec, 21 passed / 0 FAILED, the joint 8-slot table exhaustion that distinguishes one table from two, and the negative control that must refute a two-scheduler composite. Also recorded, because the checks pass anyway: two STATELESS time-provider instances pass all 21 checks -- both read the same register through the same host import, so they are indistinguishable at the seam. A second clock with a different source or offset IS caught. And untouched: the dissolved path, channel/io, clock wraparound, multi-task interleaving, silicon. FIND-OS-TIMER-SLEEP-CONTRACT-001 (new): timer.sleep returns 0 (success) instead of 0xFFFFFFFF for an out-of-range id, a non-id, and an already-done task. set_deadline correctly no-ops, so nothing is corrupted -- the defect is the RETURN VALUE. The arm is silently lost and the caller believes a wake is scheduled that will never fire. On a failsafe path that is the difference between a missed deadline that is detected and one that is not. It was reported-not-fixed in #250 (providers were out of scope there) and until now lived only in a PR body and a pinned count in a CI step. A defect recorded nowhere is a defect nobody is tracking. The CI gate pins the count at 3 so a fourth fails; that bounds the blast radius, it does not fix these three. rivet validate: PASS, 341 warnings before and after. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011QG86sovTbfnPNY9SfhSmo * chore(rivet): method 'test' is not an allowed value — 8 artifacts, 341 -> 333 warnings The schema allows automated-test, manual-test, review, static-analysis, formal-verification, simulation, inspection, walkthrough. Eight sw-verification artifacts carried a bare 'test', which validates as a warning and silently drops them out of any query that filters by method. All eight are script-driven executed tests -- CI workflows or bench capture scripts -- so automated-test is the accurate value, not merely the closest one. VER-OS-WCET-EVT-001 and VER-OS-PARTITION-TENANT-001 run against silicon, but via scripts rather than a human following steps, so they are automated-test too rather than manual-test. Kept as its own commit: it is mechanical and touches seven artifacts this branch does not otherwise change. rivet validate: PASS, 341 -> 333 warnings, invalid-method class now zero. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011QG86sovTbfnPNY9SfhSmo --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
avrabe
added a commit
that referenced
this pull request
Aug 6, 2026
… the seam The dissolve existed as a script anyone could forget to run. VER-OS-COMPOSITE-DISSOLVE-001 asks for more than that: the seam property must not be able to silently stop holding. Its own workflow rather than a step in gustos-publish.yml, because this needs the full dissolve toolchain -- meld + loom + synth -- which NO gale workflow has ever installed. Keeping them apart means the cheap structural gate still runs in seconds on provider edits while this heavier one fires only when something that can change the dissolve moves. Toolchain by pinned RELEASE BINARY with checksum verification, not `cargo install --git`: faster, and the checksum is a gate rather than a hope. meld 0.41.3 / loom 1.2.0 / synth 0.52.0 -- the exact three that produced measurements/composite-dissolved.md, since a dissolve measured with a different compiler is not comparable to the recorded figure. The path filter covers build-fused-gustos.sh and build-dissolve-gustos.sh, not just the providers. gale#254 was precisely a filter that did not cover the script it gated, and a 61/61 green that said nothing about the change. Object drift is a WARNING, not a failure: if a provider change moves the committed object that is a real finding to review, not something to absorb silently or to block on. Also documented the script's exit 4 in its header -- the negative control passing is a distinct outcome from the gate failing, and it was implemented but unlisted. VER-OS-COMPOSITE-DISSOLVE-001 stays `proposed` in this commit. The gate has not run yet; marking it verified before it has is the mistake #254/#255 exists to prevent. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011QG86sovTbfnPNY9SfhSmo
avrabe
added a commit
that referenced
this pull request
Aug 7, 2026
* feat(gust): E2 — the whole OS dissolves, and the seam survives it
v0.6.0 shipped the composed OS as 20 741 bytes and said plainly that this was a WASM
figure implying nothing about code size, SRAM or cycles. build-fused-gustos.sh
carried the same caveat in its own header: "NOT done here: lowering the composite to
native". It is done here.
fused-gustos.component.wasm 20741 B (wasm component)
-> meld fuse --memory shared -> loom optimize --passes inline
-> synth compile --target cortex-m3 --all-exports --relocatable
text 4792 data 0 bss 0 0 B of the STM32F100RB's 8192 SRAM
31 functions, 25 import-call relocations, 3 external symbols
Pinned to synth 0.52.0 (#208) rather than PATH, so the figure is comparable to what
is already recorded rather than to whatever happens to be installed.
THE GATE, and its direction. Undefined symbols must be EXACTLY the declared seam:
undefined (3): poll-task read32 write32
taskdisp gust:hal/mmio
Two ways to fail, and the second is the one worth stating: MORE than that means the
OS depends on something we never declared; an EMPTY set means the seam was inlined
away. That yields a smaller object and reads like a win, but an OS whose hardware
seam has been swallowed is no longer swappable -- which is the whole thesis. Exit 2.
The negative control BITES, and runs every invocation: wasm-kernel/fused.wasm lowers
with zero undefined symbols -- exactly the seam-swallowed shape -- and the gate
refuses it. If that control ever passes, the script exits 4, because a gate that has
stopped discriminating reports a green that means nothing. gale#254 is why this is in
the script rather than in my head.
NOT claimed, stated in the measurement doc rather than left to be assumed:
- 20741 -> 4792 is NOT a ratio. A wasm component carrying component metadata and a
.text section measure different things; quoting it as compression is the exact
conflation loom-across-the-fuse.md already had to correct once.
- bss 0 is zero STATIC allocation in the object, not "the OS needs no RAM".
Stacks and any arena come from the embedder.
- relocatable object, not a linked image -- it still needs the TCB bridge.
- NOT executed. E1 ran the component on a host engine; nothing has run this
object. Renode is E3, silicon is E4, both v0.7.1.
REQ-OS-COMPOSITE-DISSOLVE-001 proposed -> implemented, deliberately NOT verified:
nothing in CI runs this yet, so the property can still silently stop holding. That is
what VER-OS-COMPOSITE-DISSOLVE-001 asks for and it is the next commit.
This unblocks the rest of v0.7.0: T2 had no object to verify, T4 had nothing to
bound, and T3 consumes T4.
rivet validate: PASS, 333 warnings, unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011QG86sovTbfnPNY9SfhSmo
* ci(gust): gate E2 — run the dissolve on every change that could break the seam
The dissolve existed as a script anyone could forget to run. VER-OS-COMPOSITE-DISSOLVE-001
asks for more than that: the seam property must not be able to silently stop holding.
Its own workflow rather than a step in gustos-publish.yml, because this needs the full
dissolve toolchain -- meld + loom + synth -- which NO gale workflow has ever installed.
Keeping them apart means the cheap structural gate still runs in seconds on provider
edits while this heavier one fires only when something that can change the dissolve moves.
Toolchain by pinned RELEASE BINARY with checksum verification, not `cargo install --git`:
faster, and the checksum is a gate rather than a hope. meld 0.41.3 / loom 1.2.0 /
synth 0.52.0 -- the exact three that produced measurements/composite-dissolved.md, since
a dissolve measured with a different compiler is not comparable to the recorded figure.
The path filter covers build-fused-gustos.sh and build-dissolve-gustos.sh, not just the
providers. gale#254 was precisely a filter that did not cover the script it gated, and a
61/61 green that said nothing about the change.
Object drift is a WARNING, not a failure: if a provider change moves the committed
object that is a real finding to review, not something to absorb silently or to block on.
Also documented the script's exit 4 in its header -- the negative control passing is a
distinct outcome from the gate failing, and it was implemented but unlisted.
VER-OS-COMPOSITE-DISSOLVE-001 stays `proposed` in this commit. The gate has not run yet;
marking it verified before it has is the mistake #254/#255 exists to prevent.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011QG86sovTbfnPNY9SfhSmo
* ci(gust): the dissolve gate needs ubuntu-24.04 — loom's binary won't load on 22.04
First CI run of the E2 gate failed, and not in the gate:
loom: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.38' not found (required by loom)
loom: /lib/x86_64-linux-gnu/libstdc++.so.6: version `GLIBCXX_3.4.31' not found
loom v1.2.0's published x86_64-unknown-linux-gnu binary requires GLIBC 2.38;
ubuntu-22.04 ships 2.35. meld 0.41.3 loaded fine on the same runner, so this is
loom's binary specifically, not the toolchain approach.
ubuntu-24.04 ships GLIBC 2.39 and GCC 13 (GLIBCXX 3.4.32), satisfying both.
Worth noting the 'Versions actually in use' step did exactly what it is for: the
failure is one legible line before the gate runs, rather than a confusing error from
inside the dissolve. Keeping it.
Reporting upstream separately -- a release binary that will not load on a currently
supported GitHub runner is a portability problem for every consumer, not just us.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011QG86sovTbfnPNY9SfhSmo
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
avrabe
added a commit
that referenced
this pull request
Aug 7, 2026
…already drifted (#258) * feat(gust): E2 — the whole OS dissolves, and the seam survives it v0.6.0 shipped the composed OS as 20 741 bytes and said plainly that this was a WASM figure implying nothing about code size, SRAM or cycles. build-fused-gustos.sh carried the same caveat in its own header: "NOT done here: lowering the composite to native". It is done here. fused-gustos.component.wasm 20741 B (wasm component) -> meld fuse --memory shared -> loom optimize --passes inline -> synth compile --target cortex-m3 --all-exports --relocatable text 4792 data 0 bss 0 0 B of the STM32F100RB's 8192 SRAM 31 functions, 25 import-call relocations, 3 external symbols Pinned to synth 0.52.0 (#208) rather than PATH, so the figure is comparable to what is already recorded rather than to whatever happens to be installed. THE GATE, and its direction. Undefined symbols must be EXACTLY the declared seam: undefined (3): poll-task read32 write32 taskdisp gust:hal/mmio Two ways to fail, and the second is the one worth stating: MORE than that means the OS depends on something we never declared; an EMPTY set means the seam was inlined away. That yields a smaller object and reads like a win, but an OS whose hardware seam has been swallowed is no longer swappable -- which is the whole thesis. Exit 2. The negative control BITES, and runs every invocation: wasm-kernel/fused.wasm lowers with zero undefined symbols -- exactly the seam-swallowed shape -- and the gate refuses it. If that control ever passes, the script exits 4, because a gate that has stopped discriminating reports a green that means nothing. gale#254 is why this is in the script rather than in my head. NOT claimed, stated in the measurement doc rather than left to be assumed: - 20741 -> 4792 is NOT a ratio. A wasm component carrying component metadata and a .text section measure different things; quoting it as compression is the exact conflation loom-across-the-fuse.md already had to correct once. - bss 0 is zero STATIC allocation in the object, not "the OS needs no RAM". Stacks and any arena come from the embedder. - relocatable object, not a linked image -- it still needs the TCB bridge. - NOT executed. E1 ran the component on a host engine; nothing has run this object. Renode is E3, silicon is E4, both v0.7.1. REQ-OS-COMPOSITE-DISSOLVE-001 proposed -> implemented, deliberately NOT verified: nothing in CI runs this yet, so the property can still silently stop holding. That is what VER-OS-COMPOSITE-DISSOLVE-001 asks for and it is the next commit. This unblocks the rest of v0.7.0: T2 had no object to verify, T4 had nothing to bound, and T3 consumes T4. rivet validate: PASS, 333 warnings, unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011QG86sovTbfnPNY9SfhSmo * ci(gust): gate E2 — run the dissolve on every change that could break the seam The dissolve existed as a script anyone could forget to run. VER-OS-COMPOSITE-DISSOLVE-001 asks for more than that: the seam property must not be able to silently stop holding. Its own workflow rather than a step in gustos-publish.yml, because this needs the full dissolve toolchain -- meld + loom + synth -- which NO gale workflow has ever installed. Keeping them apart means the cheap structural gate still runs in seconds on provider edits while this heavier one fires only when something that can change the dissolve moves. Toolchain by pinned RELEASE BINARY with checksum verification, not `cargo install --git`: faster, and the checksum is a gate rather than a hope. meld 0.41.3 / loom 1.2.0 / synth 0.52.0 -- the exact three that produced measurements/composite-dissolved.md, since a dissolve measured with a different compiler is not comparable to the recorded figure. The path filter covers build-fused-gustos.sh and build-dissolve-gustos.sh, not just the providers. gale#254 was precisely a filter that did not cover the script it gated, and a 61/61 green that said nothing about the change. Object drift is a WARNING, not a failure: if a provider change moves the committed object that is a real finding to review, not something to absorb silently or to block on. Also documented the script's exit 4 in its header -- the negative control passing is a distinct outcome from the gate failing, and it was implemented but unlisted. VER-OS-COMPOSITE-DISSOLVE-001 stays `proposed` in this commit. The gate has not run yet; marking it verified before it has is the mistake #254/#255 exists to prevent. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011QG86sovTbfnPNY9SfhSmo * ci(gust): the dissolve gate needs ubuntu-24.04 — loom's binary won't load on 22.04 First CI run of the E2 gate failed, and not in the gate: loom: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.38' not found (required by loom) loom: /lib/x86_64-linux-gnu/libstdc++.so.6: version `GLIBCXX_3.4.31' not found loom v1.2.0's published x86_64-unknown-linux-gnu binary requires GLIBC 2.38; ubuntu-22.04 ships 2.35. meld 0.41.3 loaded fine on the same runner, so this is loom's binary specifically, not the toolchain approach. ubuntu-24.04 ships GLIBC 2.39 and GCC 13 (GLIBCXX 3.4.32), satisfying both. Worth noting the 'Versions actually in use' step did exactly what it is for: the failure is one legible line before the gate runs, rather than a confusing error from inside the dissolve. Keeping it. Reporting upstream separately -- a release binary that will not load on a currently supported GitHub runner is a portability problem for every consumer, not just us. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011QG86sovTbfnPNY9SfhSmo * fix(gust): one gust:hal, not two — delete the vendored copy that had already drifted benches/gust/drivers/wit-os/deps/hal/gust-hal.wit was a hand-vendored copy of the canonical wit/gust-hal.wit, and it had ALREADY drifted: 158 lines against 173. Missing from the OS-side copy were `interface wdg` (all six functions) and `world wdg-driver`. That is not an arbitrary interface to lose. wdg is the six-functions-no-stop contract the driver model is argued from -- the absence of `stop` IS the cannot-un-start safety property -- and half the repo could not see it. It did not bite yet, and I checked rather than assumed: all seven SHARED interfaces (mmio, irq, gpio, timer, spi, uart, dma) are byte-identical, and the only consumer of wdg is wdg-thin, which builds against the canonical copy. So the drift was additive and nothing shipped is wrong. The danger was the NEXT edit: change a shared interface and drivers and providers get built against different definitions of the same seam -- exactly what composition is supposed to make impossible. Fixed by removing the duplicate rather than policing it. The 8 crates that built against wit-os now resolve both packages: world: "gust:os/time-provider@0.1.0", path: ["../wit", "../wit-os"] Two details, both forced by real errors rather than guessed: - canonical FIRST. wit-bindgen parses path entries in sequence, so ["../wit-os", "../wit"] fails: wit-os is read before gust:hal exists. - the world must be QUALIFIED. With a path list there is no single main package to infer from ("There are multiple main packages"). Verified: 8/8 crates build, and the dissolved composite is BYTE-IDENTICAL to before this change -- 7473 B, text 4792 / data 0 / bss 0, undefined exactly {poll-task, read32, write32}, negative control still refused. A WIT-resolution change that moved the artifact would not have been plumbing. wit-os/deps/sched/gust-sched.wit stays vendored, correctly: gust:sched has no canonical copy elsewhere, so it is already single-source. Durable half: wit/ is in the gustos-dissolve workflow's path filter, so an edit to the canonical seam now re-runs the dissolve instead of drifting unnoticed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011QG86sovTbfnPNY9SfhSmo * measure(T4): the whole OS bounds 1 function of 31 — and not for the reason we planned E2 made T4 possible for the first time (no object existed to bound). Running synth --emit-wcet over the dissolved composite gives the honest coverage number: BOUNDED 1 of 31 (gust:os/time#deadline, 36 cycles) DECLINED 30 callee-unbounded 11 a cascade -- resolves when the leaves do unmodeled-op 9 "op not classified by the cycle model" <- synth loop 8 backward branch, no proven trip count <- scry call 2 direct call to an import <- BY DESIGN The `call` pair is not a gap: gust:os/time#now and poll_task are exactly the two functions that reach the SEAM -- now calls the imported read32, poll_task calls the embedder's dispatch. An intra-procedural bound SHOULD stop at an import whose cost belongs to the host. The seam showing up in the WCET data is a good sign it is real. THE FINDING: docs/releases/v0.7.0-plan.md named ONE T4 blocker -- "scry loop-bound inference ... the one thing standing between per-function bounds and a schedulability argument". That is no longer accurate. unmodeled-op (9) is LARGER than loop (8), and emit-wcet.sh's own header documents only reason=call and reason=loop -- this third category is absent from the track's written model of itself. Closing loop-bound inference alone takes coverage from 1/31 to at most 9/31, not to a schedulability argument. Plan corrected rather than left to be discovered at the gate. Real work is 17 leaves (9 unmodeled-op + 8 loop); the 11 cascades come free. FRICTION TO ROUTE (synth): the unmodeled-op declines do not say WHICH op -- {"reason":"unmodeled-op","note":"op not classified by the cycle model"} and nothing else. Not actionable without bisecting by hand. Requested: name the offending opcode (and ideally its offset). Recorded here because GitHub is down; file when it is back. NOT claimed: this is not a WCET for the OS, not a partition budget, and no hardware was involved. Per the track's own rule DWT may only falsify a model, never size one. 1/31 is a baseline, not a regression -- T4 had never been run against the composite. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011QG86sovTbfnPNY9SfhSmo * docs: link the T4 unmodeled-op finding to synth#921 The measurement and the plan both recorded the friction and said it would be filed when GitHub came back. It is filed: synth#921, a follow-up to the closed synth#778 that shipped --emit-wcet. Both now point at it, so the local record and the upstream record agree rather than drifting. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011QG86sovTbfnPNY9SfhSmo --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
#250found that a gate could not detect the thing it existed to guard. It shipped with the same defect one level up. Found by running the harness onmain, not by reading it.1.
build-fused-gustos.shdoes not parse on mainThe new instantiation-count block was inserted between a line-continuation
\and its&& echo, orphaning the continuation.bash -nis clean ona9bbfbf~1, broken ona9bbfbf.The script aborts at 178, so every check after it never ran — including the residual-import delivery invariant, which is the gate the v0.6.0 release notes tell people to verify with.
2. CI structurally could not have caught it
gustos-publish.yml's path filter listsbuild-gustos-components.shbut notbuild-fused-gustos.sh. None of the six files#250touched matched the filter, so the workflow that runs the script never triggered. Its 61/61 green was real, and said nothing whatsoever about this change.3. The harness swallowed the failure
build-fused-gustos.sh | tail -2 || exit 1teststail's status, not the script's. That is why a syntax error stayed quiet. Now checksPIPESTATUS[0].Fixes
build-fused-gustos.sh,run-gustos-hostrun.sh,gustos-hostrun/**— all six of feat(gust): execute the composite — the claim holds, and the gate that checked it did not #250's files now matchbash -ngate over the 16 driver + silicon scripts; this class is free to catchPIPESTATUSAnd the point: CI now executes the composite
VER-OS-COMPOSITE-EXEC-001requires "run in CI on every change to any provider so the property cannot silently stop holding." Nothing ran it, so E1 was committed evidence, not a gate. This wires the harness and its negative controls in.The harness exits 3, not 0 — claims held, but 3 WIT return-contract deviations (
timer.sleepreturns success for an out-of-range or already-done handle, so the arm is silently lost; reported-not-fixed in #250, providers were out of scope). A barerun:would have failed the step on a passing result. The gate accepts 3 only when the count is exactly 3, so a fourth deviation fails rather than hides.Verified locally after the fix
Follow-up, not in this PR
REQ/VER-OS-COMPOSITE-EXEC-001stayproposed. Once this gate is green on main the V can close on gated evidence rather than committed evidence — and thetimer.sleepdeviation deserves its own artifact rather than a footnote in a verification note.