Skip to content

verify(v1.139): the review checkpoint is done — 9 findings, every one dispositioned - #461

Merged
avrabe merged 2 commits into
mainfrom
verify/v1139-review-checkpoint
Sep 18, 2026
Merged

avrabe merged 2 commits into
mainfrom
verify/v1139-review-checkpoint

Conversation

@avrabe

@avrabe avrabe commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Verify-Filter: (= id "FV-RELAY-REVIEW-139")

Code-free promotion PR (two-commit rule). Refs #458, #459.

The review ran, and what it cost to run it

/code-review ultra on the candidate falcon-v1.138.0..9f1df80. The full candidate is 186 files / 21 420 lines — beyond the tool as one diff, and ~9.5 k of those lines are Bazel lockfile churn. Split against a base pinned at the previous tag: #458 (gate tooling + CI workflows, 19 files) and #459 (the 28 source files with 50+ changed lines). Both are closed and neither was ever merged.

9 findings, all dispositioned

Fixed in #460, merged to main before any tag:

  1. soak.yml interpolated a workflow_dispatch input into a run: block — Actions expands ${{ }} before bash sees it, so a dispatch-capable actor could run commands with the job's environment.
  2. The analytic soak tier lost its rungs to -e: a job whose purpose is publishing a number produced a red step and no number.
  3. check-evidence.rs read its own environment to decide whether a $VAR was an unfilled placeholder — the census disagreed with itself across machines.
  4. Two relay_ scanners disagreed on an identifier boundary (my_relay_foo → phantom root).
  5. setup-gh cached under $RUNNER_TEMP, which Actions empties every job — gh re-downloaded twice per fleet tick.
  6. release.yml's --generate-notes fallback could not fire; now a loud failure, because deleting it outright would leave a silent no-op on an empty notes body.

Filed into v1.140 (maintainer decision):
7. Losing the battery sense in flight raised no failsafe — None froze the low/critical latches and the presence flag is only read at arm time, so a vehicle that armed healthy and lost its ADC flew until the pack was flat. #413's in-flight half. Reproduced (still Loiter after 4 s of silence), fixed on the v1.140 branch as SWREQ-FALCON-BATTERY-P03 + FV-FALCON-BATTERY-003.

Dismissed, each with the measurement — all three were artefacts of my slicing, recorded so the next split doesn't repeat it:
8. "read_battery_v → Option<f32> breaks two impls" — both already return Option<f32> on main; their diffs are under 50 lines so the slice excluded them.
9. "Three gate scripts are never invoked" / "they exit 1 today" — they are cited in rivet artifact steps the slice excluded, and measured on main at 9f1df80 all three exit 0; the slice lacked the very files that make them pass.

What this does not claim

The reviewed diff excluded rivet artifact YAML, the Cargo.toml component descriptions, wasm/, tests/, examples/, docs and lockfiles. The claims text a partner reads was therefore checked mechanically (check-component-claims.rs, capability-reachability.rs), not by a reader. That is weaker, and it is stated in the artifact rather than papered over.

After this merges, readiness is 15/17: the two remaining artifacts are FV-RELAY-VGATE-005 and SWREQ-RELAY-VGATE-P04, which you decided stay implemented until after the tag because their evidence is the rendered release body.

🤖 Generated with Claude Code

https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG

… dispositioned

/code-review ultra ran on the candidate falcon-v1.138.0..9f1df80 on
2026-09-18. It could not be reviewed as one diff (186 files, 21 420
lines, ~9.5 k of them lockfile churn), so it was split against a base
pinned at the previous tag: #458 (gate tooling + CI, 19 files) and #459
(the 28 source files with 50+ changed lines). Neither was ever merged.

  FIXED in #460 (f9ea446, on main before any tag): an injectable
  workflow_dispatch input; the analytic soak tier losing its rungs to
  -e; check-evidence.rs reading its own environment to decide whether a
  $VAR was a placeholder; two relay_ scanners disagreeing on an
  identifier boundary; setup-gh caching where Actions wipes it; a dead
  --generate-notes fallback, now a loud failure rather than a silent
  no-op.

  FILED into v1.140 by maintainer decision: losing the battery sense in
  flight raised no failsafe (#413's in-flight half) — reproduced, fixed
  on the v1.140 branch, SWREQ-FALCON-BATTERY-P03 + FV-FALCON-BATTERY-003.

  DISMISSED with measurements: three findings that were artefacts of the
  slicing (impls already updated on main; scripts cited in rivet steps
  the slice excluded; all three scripts exit 0 on main at 9f1df80).

NOT claimed: the reviewed diff excluded artifact YAML, the component
descriptions, wasm/, tests/, examples/, docs and lockfiles — the claims
TEXT a partner reads was checked mechanically, not by a reader.

Code-free promotion (two-commit rule): SWREQ-RELAY-REVIEW-P01 and
FV-RELAY-REVIEW-139 proposed -> verified.

Refs #458, #459

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG
@avrabe
avrabe enabled auto-merge (squash) September 18, 2026 06:55
@github-actions

github-actions Bot commented Sep 18, 2026 •

Copy link
Copy Markdown

✅ Rivet verification gate — falcon

1/1 passed

count
Passed 1
Failed 0
Skipped (bench-only — needs hardware / sim) 0
Skipped (no steps) 0

Source of truth: artifacts/verification/FV-FALCON-*.yaml.

…un `gh`

The first draft of FV-RELAY-REVIEW-139's steps cited
`gh pr view 458/459` as the evidence of the findings. The verification
gate failed them with rc=127: gh is not installed on the self-hosted
runners — the same lesson as #153/#436, walked into again by writing a
step that only runs on a laptop.

The deeper problem is that it made a release's audit trail depend on
GitHub PR comments, which can be edited and belong to PRs that can be
deleted. The findings and their nine dispositions now live in
docs/reviews/falcon-v1.139.0-review.md, and the steps check that file:
it exists, and it carries exactly nine dispositions.

All four steps verified by running them as the gate does (sh -c).

Refs #458, #459

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG
@avrabe

avrabe commented Sep 18, 2026

Copy link
Copy Markdown
Contributor Author

The gate caught my own step: gh is not on the runners

The first draft of this artifact's steps cited gh pr view 458/459 as the evidence. The verification gate failed them with rc=127 — gh is not installed on the self-hosted runners, which is exactly #153/#436, walked into again by writing a step that only runs on a laptop.

It was also the wrong evidence. It made a release's audit trail depend on GitHub PR comments, which can be edited, on PRs that can be deleted.

The nine findings and their dispositions are now in docs/reviews/falcon-v1.139.0-review.md, and the steps check the file rather than the network: it exists, and it carries exactly nine dispositions. All four steps were run as the gate runs them (sh -c) before pushing.

Still code-free under the two-commit rule: that check counts .rs files under crates/ and examples/, and this adds only docs plus the artifact.

@avrabe
avrabe merged commit 5b365f7 into main Sep 18, 2026
12 checks passed
@avrabe
avrabe deleted the verify/v1139-review-checkpoint branch September 18, 2026 10:11
avrabe added a commit that referenced this pull request Sep 18, 2026
#465)

CI checks out the PR's MERGE commit, but the rule diffed from
`pull_request.base.sha` — the base as it was when the event fired. On a
branch that has fallen behind, that diff contains MAIN's commits too, so
the rule attributes them to the PR.

Measured on #456 (2026-09-18): it changes code and was failed for
"promoting artifact status to verified", citing two `+ status: verified`
lines that came from #461, a code-free promotion PR someone else had
already merged to main. The branch itself promotes nothing.

The rule now diffs from `git merge-base origin/<base ref> HEAD`, which
on the checked-out merge commit is the current base tip, and falls back
to the event's base sha if that cannot be computed. The echo line prints
both so a future failure says which base it used.

Refs #456


Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
avrabe added a commit that referenced this pull request Sep 23, 2026
…f) — and this falsifies a `verified` artifact (#398) (#478)

* measure(gz): rotor-out recovery does not hold on the real plant (#398)

FV-FALCON-FAULT-005 is the last engineering item in v1.140's scope. It
is NOT met. Four pristine-server gz trials, four FAILs.

Control first, so a rotor-out failure cannot be confused with a bench
that cannot fly: --scenario=flightcore PASSES on the same rig (2 m hold,
final 0.11 m, horizontal 0.01 m). Then rotor 0 killed at 10.0 s of 25 s:

  trial   isolated   peak tilt after    peak horiz after   landed/disarmed
  rot1    None       3.142 rad (180deg) 9.55 m             no / no
  rot2    Some(0)    0.886 rad (50.8)   3.94 m             11.148 s / yes
  rot3    Some(0)    0.867 rad (49.7)   8.38 m             10.972 s / yes
  diag    Some(0)    1.272 rad (72.9)   5.32 m             11.000 s / yes

Two failure modes from an unchanged binary on an unchanged world: one
total inversion with no isolation, and three isolate-and-come-down runs
peaking at 50-73 deg against a 0.5 rad bar. The SPREAD is the finding --
whether the FDI isolates at all flips between runs, and an intermittent
FDI on a safety property is worse than a consistently failing one,
because a campaign that samples it a few times reports success.

Every trial gets a fresh server via gz-trial.rs: the world does not
reset, so trial N+1 on a live server inherits trial N's final pose.

TWO CORRECTIONS to the artifact's own text, made BEFORE any measurement
was accepted, so this cannot be read as moving the goalposts:

1. It demanded "holding altitude after the loss". SWREQ-FALCON-FAULT-P02
   says "isolated and the body settles upright (no tumble)" and names the
   rank deficiency (Mueller & D'Andrea). A three-rotor quad relinquishes
   yaw and cannot hold altitude. Altitude hold was never the claim.
2. It asked for the gz command "recorded as steps" -- gz is not on the
   runners. Third instance of the rc=127 class after gh (#461) and the
   prebuilt binary (ENDURANCE-001).

Scope: this is the NATIVE half only. The gz branch applies tuning the
component never receives -- main.rs says the wasm cascade flies this same
FlightCore with NONE of it -- and #398 was reported against wasm.

Also corrects two stale in-code claims: "the loop keeps the vehicle
aloft" (a physical impossibility stated as a pass condition) and the mock
plant being torque-free (untrue since #435, and while it was true every
endurance row read exactly 0.000 m horizontal).

Status stays `proposed`. No cause is claimed; no trace was taken.

Refs #398

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG

* measure(gz): the same run falsifies FV-FALCON-FAULT-003, which is `verified`

FAULT-005 staying `proposed` understates the result. FAULT-P02 carries six
verified-by links and FIVE are `verified`. One is directly contradicted.

FV-FALCON-FAULT-003 (v1.114, `verified`) ends with: "wrong if a
single-rotor-out from a settled hover is observed to exceed ~45 deg tilt
... on the analytic plant (this oracle) OR THE GZ PLANT". All four trials
exceed 45 deg: 49.7, 50.8, 72.9, 180. It named the gz plant itself, so
this is its own stated kill-criterion being met.

Sharper: FAULT-003 says the defect it fixed was a flip "to ~180 deg
(reproduced on the real gz plant)". That is exactly rot1. Either the fix
did not hold on gz, or something since regressed it.

NOT claimed: that FAULT-003's analytic oracle is wrong. Its 200-trial
full-loop numbers (worst peak tilt 0.274 rad) are a different measurement
and were not re-run. The gap is oracle-vs-gz -- the same gap FAULT-003
was itself written to close.

A `verified` artifact whose kill-criterion has been met is a maintainer
decision (demote, or judge the gz clause over-reach), not something this
pass resolves silently.

Refs #398

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
avrabe added a commit that referenced this pull request Sep 24, 2026
…and v1.140/141/142 scoping (#480)

* measure(gz): rotor-out recovery does not hold on the real plant (#398)

FV-FALCON-FAULT-005 is the last engineering item in v1.140's scope. It
is NOT met. Four pristine-server gz trials, four FAILs.

Control first, so a rotor-out failure cannot be confused with a bench
that cannot fly: --scenario=flightcore PASSES on the same rig (2 m hold,
final 0.11 m, horizontal 0.01 m). Then rotor 0 killed at 10.0 s of 25 s:

  trial   isolated   peak tilt after    peak horiz after   landed/disarmed
  rot1    None       3.142 rad (180deg) 9.55 m             no / no
  rot2    Some(0)    0.886 rad (50.8)   3.94 m             11.148 s / yes
  rot3    Some(0)    0.867 rad (49.7)   8.38 m             10.972 s / yes
  diag    Some(0)    1.272 rad (72.9)   5.32 m             11.000 s / yes

Two failure modes from an unchanged binary on an unchanged world: one
total inversion with no isolation, and three isolate-and-come-down runs
peaking at 50-73 deg against a 0.5 rad bar. The SPREAD is the finding --
whether the FDI isolates at all flips between runs, and an intermittent
FDI on a safety property is worse than a consistently failing one,
because a campaign that samples it a few times reports success.

Every trial gets a fresh server via gz-trial.rs: the world does not
reset, so trial N+1 on a live server inherits trial N's final pose.

TWO CORRECTIONS to the artifact's own text, made BEFORE any measurement
was accepted, so this cannot be read as moving the goalposts:

1. It demanded "holding altitude after the loss". SWREQ-FALCON-FAULT-P02
   says "isolated and the body settles upright (no tumble)" and names the
   rank deficiency (Mueller & D'Andrea). A three-rotor quad relinquishes
   yaw and cannot hold altitude. Altitude hold was never the claim.
2. It asked for the gz command "recorded as steps" -- gz is not on the
   runners. Third instance of the rc=127 class after gh (#461) and the
   prebuilt binary (ENDURANCE-001).

Scope: this is the NATIVE half only. The gz branch applies tuning the
component never receives -- main.rs says the wasm cascade flies this same
FlightCore with NONE of it -- and #398 was reported against wasm.

Also corrects two stale in-code claims: "the loop keeps the vehicle
aloft" (a physical impossibility stated as a pass condition) and the mock
plant being torque-free (untrue since #435, and while it was true every
endurance row read exactly 0.000 m horizontal).

Status stays `proposed`. No cause is claimed; no trace was taken.

Refs #398

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG

* measure(gz): the same run falsifies FV-FALCON-FAULT-003, which is `verified`

FAULT-005 staying `proposed` understates the result. FAULT-P02 carries six
verified-by links and FIVE are `verified`. One is directly contradicted.

FV-FALCON-FAULT-003 (v1.114, `verified`) ends with: "wrong if a
single-rotor-out from a settled hover is observed to exceed ~45 deg tilt
... on the analytic plant (this oracle) OR THE GZ PLANT". All four trials
exceed 45 deg: 49.7, 50.8, 72.9, 180. It named the gz plant itself, so
this is its own stated kill-criterion being met.

Sharper: FAULT-003 says the defect it fixed was a flip "to ~180 deg
(reproduced on the real gz plant)". That is exactly rot1. Either the fix
did not hold on gz, or something since regressed it.

NOT claimed: that FAULT-003's analytic oracle is wrong. Its 200-trial
full-loop numbers (worst peak tilt 0.274 rad) are a different measurement
and were not re-run. The gap is oracle-vs-gz -- the same gap FAULT-003
was itself written to close.

A `verified` artifact whose kill-criterion has been met is a maintainer
decision (demote, or judge the gz clause over-reach), not something this
pass resolves silently.

Refs #398

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG

* verify(v1.114): demote FV-FALCON-FAULT-003 verified -> implemented (#479)

Maintainer decision on #479. This artifact's OWN falsification condition
is met: "wrong if a single-rotor-out from a settled hover is observed to
exceed ~45 deg tilt ... on the analytic plant (this oracle) OR THE GZ
PLANT". Measured on gz at e1d00be, four pristine-server trials: 49.7,
50.8, 72.9 and 180 deg. All four exceed 45. It named the gz plant itself.

Sharper: the artifact says the defect it FIXED was a flip "to ~180 deg
(reproduced on the real gz plant)". That is exactly trial rot1.

NOT RETRACTED: the analytic-plant oracle stands as measured (200 dispersed
full-loop trials, worst peak tilt 0.274 rad, 0 failures) and the Kani and
allocator results are untouched. None was re-run; none is disputed. What
is withdrawn is the claim that this VERIFIES FAULT-P02's end-to-end
clause, because the plant the requirement answers to refutes it.
`implemented` is the honest status.

rivet validate PASS; coverage unchanged (FAULT-P02 retains four other
verified-by links); v1.140 readiness unaffected (this is a v1.114
artifact).

Re-promotion requires a gz-valid oracle, not a re-run of the analytic one.

Closes #479

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG

* req(v1.141): OCI-P07 — the stage components shall compose into the FLOWN law

Raised by the bench engineer (Christof, 2026-09-23): "falcon cascade is
only a middle, and it should be built out of components." Both halves are
correct and both are measurable.

ONLY A MIDDLE is the signature, literally: step(sensors, target) ->
motor-pwm returns no state, which is why the verified MAVLINK-P06 stack
has nothing to read. That is TRANSPORT-P01's RETURN direction; P07
depends on it rather than restating it.

BUILT OUT OF COMPONENTS: the decomposition already exists in
cascade.wit (five stage worlds) but decomposes the LEGACY PID cascade.
Measured with scripts/audit-component-deps.rs:

  estimator  relay-iekf     vs relay-iekf                  MATCH
  position   relay-pos      vs relay-geo                   NO
  attitude   relay-att      vs relay-geo  (geometric SE3)  NO
  rate       relay-rate     vs relay-adrc (ADRC)           NO
  mixer      relay-mix-quad vs relay-mix-quad              MATCH

The audit lists relay-geo and relay-adrc under FLOWN BUT NOT WRAPPED BY
ANY COMPONENT: the two middle stages of the flown law have no component.
OCI-DISTRIBUTION.md already states the consequence -- "fusing the
per-stage components does not produce the flown control law".

COMPOSITION PATH SPECIFIED (maintainer, 2026-09-23): wac -> meld ->
synth. This answers the obvious objection that five components means
five runtime seams and would dilute the single-seam DIFFERENTIAL=1
result: meld FUSES at build time and synth compiles the fused artifact,
so the boundary is authoring/verification structure erased before
deployment, and the differential runs against the fused artifact.
OCI-P05 (relocation metadata for meld) is the verified groundwork.

Blockers recorded in bite order: no socket (#393, wac plug has no
matching imports -- correct behaviour, and how this was found); relay-geo
and relay-adrc need wrappers (the tractable first step); tuning is
host-side and must be distributed or carried by vehicle-config, or the
composed stack flies differently from the native one.

Ordering: P07 leads, ORPHAN-P01's retirement of the legacy stage
components follows -- publishing something labelled legacy beats
publishing nothing until a flown replacement exists.

FALSIFICATION: wrong if a wac-composed + meld-fused + synth-compiled
cascade under DIFFERENTIAL=1 on the gz falcon-quad shows ANY non-zero
per-motor difference from the native FlightCore, or cannot be built.

rivet validate PASS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG

* investigate(gz): the FDI gate chatters on the hover limit cycle (#398/#479)

ROOT CAUSE FOUND, and it is not the FDI residual logic.

The detector runs only while fdi_steady = tilt_cos > 0.90 && rp_rate2 <
4.0 (~26 deg, ~2 rad/s roll+pitch). Traced over the 25 ticks BEFORE the
kill -- a healthy, PASSING hover -- rp_rate2 oscillates 3.596..4.521,
straddling the 4.0 threshold. The vehicle hovers at 1.90-2.12 rad/s
roll/pitch against a 2.0 rad/s gate, and THE GATE IS SHUT 15 OF 25
HEALTHY-HOVER TICKS (60%).

So whether a rotor loss is detectable at all is decided by the phase of
the hover limit cycle at the instant of failure. The code's own comment
records raising this gate 1 -> 2 rad/s because the gz plant limit-cycled
"near 1 rad/s". It is now ~2 rad/s; that margin is consumed.

PREDICTION HELD 5/5 -- gate state on the kill tick vs outcome:
  G2 OPEN -> isolated in ONE tick     G3 shut -> never isolated, 3.142 rad
  G4 OPEN -> isolated                 G5 OPEN -> isolated
  G6 OPEN -> isolated
One-way: once the vehicle departs, tilt_cos falls and rp_rate2 stays
high, so a gate shut at the kill NEVER reopens.

TWO DEFECTS, NOT ONE:
1. Detection is a coin flip (above). NOT a residual defect -- with the
   gate open the residual is unambiguous ([1.000 0 0 0]), one-tick
   isolation.
2. Recovery is weak even with immediate isolation: G2/G4/G5/G6 isolated
   within a tick and still peaked at 1.01-1.46 rad (58-84 deg) against
   FAULT-003's analytic claim of 0.274 rad. Perfect detection alone would
   not make this property hold.

THE FIX IS NOT THE GATE. Raising it again would make the runs pass
without making the vehicle safer -- the vacuous-gate shape this train
keeps finding. The healthy-hover limit cycle is the defect: ~2 rad/s
(115 deg/s) ringing during a hover the position loop reports as good
(0.11 m). Position hold MASKS a ringing attitude loop. Worth checking
against the wobble the bench engineer saw in the Wireshark capture.

CORRECTION to this finding's own headline: with 12 trials the tally is
11 FAIL / 1 PASS, not 4/4. T2 passed (0.338 rad). The property is
INTERMITTENT, so a campaign sampling it a few times reports success --
now demonstrated rather than argued.

Adds FDI_TRACE=<ticks> to the scenario (unset = shipped behaviour), and
fixes scripts/gz-trial.rs's KEEP allowlist, which silently swallowed the
entire trace and made it look like the instrumentation did not fire.

Refs #398 #479

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG

* plan: reorganise around the thesis — gale + component model, not PX4 parity

Goal set by the maintainer 2026-09-23: best-in-class drone software,
running on gale, with every flight capability a WebAssembly component.
Replaces the v1.139->v1.142 release-number framing; releases are now an
output and the phases are the plan. Supersedes the OCI-P07 section this
same PR added earlier -- it is Phase 2 now.

SYSREQ-FALCON-022 states the thesis as FOUR FALSIFIABLE CLAIMS, each with
its measured 2026-09-23 baseline:

1. Every flight capability is a component. falcon-core is 6090 lines
   carrying the supervisor, every failsafe and the FDI, with ZERO
   kani::proof harnesses and no kani.yml entry -- the 31 flight-path
   harnesses are all on leaves. The proofs SURROUND the defect. It must
   dissolve, not grow.
2. Composition is free: wac + meld + synth must stay bit-identical to
   native under DIFFERENTIAL=1. Held today across ONE seam; the thesis
   needs it to survive decomposition.
3. It runs on gale on silicon and no oracle hides a fault silicon takes
   -- gale#398: dissolved objects address wasm memory at ~1 MiB, unmapped
   on qemu (reads 0, writes dropped, GATES PASS), BusFault on a board.
4. The flight behaviour is genuinely good. The gz hover rings at
   1.90-2.12 rad/s while the position verdict reports 0.11 m and PASSES.

THE CONSTRAINT THAT SHAPES THE PLAN: decomposition and memory packing are
the SAME problem. Each component carries its own data base; gale's
fused.o holds two whose bases are 1.1 MiB apart through one r11 base --
more RAM than any bench board has (192 KB max), "not fixable in the
embedder". So `meld --pack-rebase` on the flight object is a
PRECONDITION for claim 1, not an optimisation. Proven on iso-core; jess
measured 5x on the M7.

Standing falsifier: what lowers to the M7 today is the LEGACY PID
cascade. The only proof-point for "runs as components on silicon" is
about code the vehicle does not use.

Phases, each owning its issues so nothing is invisible: 0 make the ground
true (#270 root defect, #398 downstream); 1 the seam returns state (jess
answered on jess#167 -- second export, seqlock, OCRAM, 32-byte aligned;
and corrected our premise: no flattening cliff on returns); 2 the
component model becomes real (OCI-P07: relay-geo/relay-adrc have no
component at all); 3 fit on silicon (gale#398, #330); 4 the safety layer
as components -- THE FIRST-FLIGHT BLOCKER, since relay_fsm has no Manual
mode and the seam has no RC channel, so no human can take the sticks;
5 evidence on a real vehicle (jess's MAVLink feed already works).

Parks the parity-table filler orphans rather than wiring them: they were
built to tick rows in a table that was itself wrong for 82 versions.

rivet validate PASS.

Refs #270 #398 #422

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG

* measure(gz): the "plant limit cycle" is ESO windup under mixer saturation

Phase 0. Adds RING_TRACE (per-tick rate-loop slice: measured gyro in,
desired rate + commanded torque out, resulting motors) to both the
flightcore and supervised paths so they are directly comparable.

THE PLANT IS NOT RINGING. Same world, same commit, same binary:

  flightcore settled hover    roll/pitch rate rms 0.0014 rad/s
                              motors 0.5809-0.5869, gate shut ~0%
  supervised hover (8-10 s)   roll/pitch rate rms 1.70/1.80 rad/s
                              motors 0.12 <-> 1.0000, gate shut 96.8%

Three orders of magnitude, and both paths apply IDENTICAL estimator and
altitude tuning and the same hover_thrust 0.585. The ring belongs to a
PATH, not to the plant -- which is how #270 has been misfiled since July.

(Also corrects my own earlier "gate shut 60% of ticks", taken from a
25-tick sample. Over 500 ticks it is 96.8%: the gate is essentially
always shut, with rare open windows. That is why rotor-out isolation
succeeds occasionally rather than half the time.)

THE RATE LOOP IS NOT TRACKING. Commanded omega_d rms 0.1443 rad/s vs
measured 2.4674 -- 17x the command, tracking error 2.5061. So this is not
a bad setpoint from the attitude loop; the inner loop oscillates on its
own at 3.5 Hz with motors railing.

ONSET IS SATURATION. Clean at 0.0019 rad/s through t=0.5 s; motors first
hit their limits at 0.50-0.75 s; ring established by 0.75-1.00 s; still
running 7 s later. Never recovers.

MECHANISM. relay-adrc drives its ESO with `u_prev`, the RAW computed
control, lag-filtered by tau. Its own comment states the right principle
("the ESO must be driven by the torque actually DELIVERED") and
implements it for actuator LAG ONLY. But falcon-core mixes with
mix_thrust_floor, which is THRUST-PRIORITY (MIX-P05): it computes the
largest s in [0,1] keeping every motor in range and delivers s*torque. s
is computed inline, is not exposed, and never reaches the ADRC. So under
saturation the ESO integrates a torque that was not applied, z2 winds up
at beta2 = omega_o^2 = 1600, the control answers with MORE torque, and
the loop latches into a rail-to-rail cycle that sustains its own
saturation. Thrust-priority is also why it breaks at TAKEOFF: collective
leaves the least torque headroom exactly then.

flightcore is immune because it never saturates (s == 1, ESO told truth).

NOT CLAIMED: that anti-windup is the fix. The evidence is correlational
and arithmetically consistent, but the decisive test is to feed the ESO
the achieved torque and re-measure. Falsification stated in the finding.

Refs #270 #398

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG

* fix(rate): stop lying to the ESO about delivered torque, and hold it on the ground (#270)

Two genuine defects, found chasing the attitude limit cycle. NEITHER
CURES IT -- my stated falsification was run and FAILED. Landing them
because each is independently wrong, with honest claims.

DEFECT 1 -- the ESO was driven by a torque that was never applied.
falcon-core mixes with mix_thrust_floor, which is THRUST-PRIORITY
(MIX-P05): under saturation it scales the torque triple by s in [0,1]
and delivers s*torque. s was computed inline in the mixer, never
exposed, and never reached relay-adrc. The observer therefore integrated
the FULL commanded torque, its residual grew, z2 wound up at
beta2 = omega_o^2 (1600 for roll/pitch), and the control answered with
more torque -- which saturates harder.
  - relay-mix-quad: record + expose last_torque_scale (additive).
  - relay-adrc: u_delivered_frac, distinct from u_act. u_act models the
    actuator LAG (torque arrives late); this models torque that never
    arrives at all. Defaults to 1.0, so an allocator that never reports
    leaves behaviour bit-identical to before.
  - falcon-core: feed s back after the mix. Tick N uses tick N-1's
    fraction, which is correct -- the ESO is one tick behind by
    construction, since what was applied is only knowable after
    application.
  set_delivered_fraction takes a per-AXIS array although a saturating
  mixer has one scalar s: the axes are independent observers with
  different tuning (yaw omega_c=3.0/tau=0.025 vs roll-pitch 12.0/0.0125)
  and MIX-P06 desaturates in priority order, giving up yaw before
  roll/pitch -- one scalar for all three would then be a lie about the
  axes that kept authority.

DEFECT 2 -- the observer was never reset. AdrcAxis::reset() says "e.g.
on arming" and had ZERO call sites in the flight path. It ran
continuously from construction, straight through ground spin-up, when
the plant CANNOT answer a torque command because the gear takes the
moment. Held at zero until flying(), leaving a pure proportional command
on the ground.

MEASURED ON GZ, supervised-rotorout hover:
                                   gyro rp rms   gate shut
  baseline                            2.47         96.8%
  + mixer anti-windup                 2.02         51.0%
  + ground hold                       1.91         35.8%
The bar was < 0.1 rad/s. It is 1.91 -- SO ANTI-WINDUP IS NOT THE PRIMARY
CAUSE. Gate availability 96.8% -> 35.8% shut is still a real gain for
#398. The single-run outcome column must NOT be read as ranking the
fixes: with 11/12 baseline failures already measured, n=1 cannot
distinguish them.

WEAKNESS IN MY OWN CONTROL, stated: flightcore is not a clean control
for the rate loop -- it holds altitude only and its motors never leave a
0.03-wide band, so it never LOADS the rate loop. "flightcore is clean"
shows the loop is never asked to do anything, not that it is healthy.
The honest claim is narrower: the ring appears when the rate loop is
actually loaded.

Tests: relay-adrc 65, relay-mix-quad 11, falcon-core 26 -- all pass, and
that is EXPECTED rather than reassuring: the analytic plant hovers in a
0.0001-wide motor band, so s == 1 there and both changes are no-ops. The
23 FlightSupervisor tests cannot see the bug or the fix.

Kani: QuadMixer gained a field; relay-mix-quad proofs must re-run in CI
(#429 documents that harness's SAT heavy tail).

Status stays at most `implemented` (two-commit rule).

Refs #270 #398

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG

* revert(rate): move the ADRC anti-windup + ground hold to its own PR

Not a retraction -- both defects are real and the measurements stand.
This PR already carries six concerns (the FAULT-005 measurement, the FDI
root cause, the FAULT-003 demotion, OCI-P07, the thesis + plan rewrite,
and the ring diagnosis). A CONTROL-LAW change to Kani-proven crates is a
different kind of change from measurement and documentation, and it
wants its own review and its own Kani run -- especially since it does
NOT achieve its stated goal (the ring survives at 1.91 rad/s against a
0.1 bar).

The work is preserved on branch phase0/rate-loop-antiwindup and comes
back as its own PR. The bench-evidence finding that records the
falsification stays here, because it is measurement.

Refs #270

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG

* docs(bench): keep the falsification RESULT with the measurement

The revert that moved the control-law change to its own PR also undid the
doc corrections, which are measurement and belong here: the counted 23
FlightSupervisor sites (and that they are BLIND, not failing, because the
analytic plant hovers in a 0.0001-wide motor band), and the recorded
FALSIFICATION of my own hypothesis (ring survives at 1.91 rad/s against a
0.1 bar). Also drops the now-untrue 'this hypothesis has not yet been
tested' line -- it was tested, and it failed.

Refs #270

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG

* plan(release): move FAULT-005 to v1.141, Phase-2 scope to v1.142, file RATE-P05

Release scoping so the phases map onto actual releases.

v1.140 "Hold" -> 9/10, one blocker: FV-RELAY-REVIEW-140, the maintainer
review checkpoint. SCOPE MOVED OUT AND LOGGED: FV-FALCON-FAULT-005 ->
v1.141. It cannot be verified while #270 stands (the FDI gate is shut
96.8% of healthy-hover ticks, so rotor-loss detectability is decided by
limit-cycle phase), and the ring's primary cause is open. v1.140's own
theme -- the hold -- is done and measured: estimator, endurance, battery,
fleet, mixproof. Holding a finished release hostage to an open control
investigation is the scope error, not the fix.

v1.141 "The rate loop and the seam" (Phases 0-1): RATE-P05, FAULT-005
behind it, TRANSPORT-P01, REVIEW-141.

v1.142 "Components" (Phase 2): OCI-P07, ORPHAN-P01, CONFIG-P01 and
SHOWCASE-P01 all moved here from v1.141; the platform-integrity items
already scoped at v1.142 ride alongside and never block.

SWREQ-FALCON-RATE-P05 is NEW and owns the ring. It is P05, not P01,
because RATE-P01..P04 already exist and are about the LEGACY PID rate
controller relay-rate, which the flight core no longer flies (it flies
ADRC). None of them is falsified by this measurement: they are properties
of the law in isolation -- anti-windup bounds, pole placement, Lyapunov,
WCET -- and none says anything about the CLOSED loop on a plant that can
saturate. That gap is the requirement.

I first wrote P05's content OVER SWREQ-FALCON-RATE-P01.yaml, which holds
FOUR artifacts, orphaning P02/P03/P04 and breaking 6 links (caught by
rivet validate: FAIL, 6 errors). Restored from HEAD and filed in its own
file after checking the id was free. This is the documented
batch-authoring clobber hazard, hit again.

Useful fallout: RATE-P04 fixes the control period at "1 ms (1 kHz update
rate)", and relay-adrc's filter comment reasons about "the 1 kHz loop" --
while this bench runs at 250 Hz. That design-vs-deployment rate mismatch
is now RATE-P05's leading untested hypothesis.

Also corrects stale text in FAULT-005: the 60% gate figure (a 25-tick
sample) -> 96.8% over 500, and "four trials, four FAILs" -> 11 FAIL /
1 PASS over twelve.

rivet validate PASS. Readiness v1.140 9/10.

Refs #270 #398

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG

* review(clean-room): withdraw the gate-shut figure — it was measured on the wrong signal

Clean-room review with three cold verifiers. 18/18 code+trace claims and
7/7 measurement claims confirmed independently -- but the reviewers found
defects the author could not see, and one of them invalidates a headline
number repeated throughout this work.

THE GATE-SHUT FIGURE WAS THE RAW GYRO, NOT THE GATE'S SIGNAL.
RING_TRACE printed backend.last_imu() -- the RAW gyro. The FDI gate tests
`gyro_f = gyro_lpf.filter(..)` (lib.rs:810,861). Measured both, and
cross-checked against the gate's own boolean:

                  raw (reported)   filtered (what the gate reads)
  climb              91.2%                 48.8%
  pre-kill           97.8%                 33.8%

So ~34% shut, NOT 96.8%. The reviewer caught it by ARITHMETIC before any
re-run: a gate shut 96.8% of the time that never reopens cannot produce 9
isolations in 12 trials. The 60% I "superseded" came from FDI_TRACE --
the CORRECT signal -- so I replaced a roughly-right number with a wrong
one. "Essentially always shut" and "rare open window" are withdrawn.
The before/after gate percentages quoted for the anti-windup change were
raw-derived too and are withdrawn pending re-measurement.

THE MECHANISM IS UNAFFECTED: gate-state-at-kill still predicts the
outcome (5/5 here, consistent across the reviewer's 3 independent runs),
and ~66% open reconciles cleanly with 9 of 12 trials isolating.

IT IS NOT A HOVER. Independent reproduction measured mode=Takeoff at the
kill: 1.49 m against a 2.0 m target, still climbing at 0.09 m/s, in a
saturated relay. Every "healthy hover" in this work should read
"saturated climb that never reaches hover".

RING now emits the gate's OWN quantities (filtered rp_rate2, tilt_cos,
gate_open) so this cannot recur, and "RING " is added to gz-trial.rs's
KEEP allowlist -- I added "FDI " there after it swallowed one trace and
then walked into the same trap with the next knob, which is how the
reviewer lost its first run.

flightcore-rotorout: its verdict is `isolated == Some(rotor) && finite`
-- no tilt, no altitude, no position -- so a 180 deg inversion that
drifts and crashes PASSES. I had rewritten the comment above it to claim
the loop "must keep the airframe UPRIGHT", which the code does not check.
Warning recorded in place; not cited as FAULT-P02 evidence until wired to
true_tilt_rad() like its sibling.

rivet validate PASS -- and, checked this time, PASS under the
GATE-PINNED 0.19.0 (123 warnings) as well as local 0.37.0 (395).

Refs #270 #398

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG

* style: cargo fmt the RING_TRACE additions

The Format gate FAILED and blocked this PR. The RING_TRACE blocks and the
FDI_TRACE env parse were not rustfmt-clean.

Against project memory: this gate used to be MASKED (continue-on-error)
and logged 893 diffs while reporting green. It is unmasked now and it
bit, which is the gate working -- and a reminder to run `cargo fmt --all`
before pushing rather than after CI says so.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
avrabe added a commit that referenced this pull request Sep 25, 2026
…140's stale-aiding fallback was harmful (#483) (#487)

* measure(gz): rotor-out recovery does not hold on the real plant (#398)

FV-FALCON-FAULT-005 is the last engineering item in v1.140's scope. It
is NOT met. Four pristine-server gz trials, four FAILs.

Control first, so a rotor-out failure cannot be confused with a bench
that cannot fly: --scenario=flightcore PASSES on the same rig (2 m hold,
final 0.11 m, horizontal 0.01 m). Then rotor 0 killed at 10.0 s of 25 s:

  trial   isolated   peak tilt after    peak horiz after   landed/disarmed
  rot1    None       3.142 rad (180deg) 9.55 m             no / no
  rot2    Some(0)    0.886 rad (50.8)   3.94 m             11.148 s / yes
  rot3    Some(0)    0.867 rad (49.7)   8.38 m             10.972 s / yes
  diag    Some(0)    1.272 rad (72.9)   5.32 m             11.000 s / yes

Two failure modes from an unchanged binary on an unchanged world: one
total inversion with no isolation, and three isolate-and-come-down runs
peaking at 50-73 deg against a 0.5 rad bar. The SPREAD is the finding --
whether the FDI isolates at all flips between runs, and an intermittent
FDI on a safety property is worse than a consistently failing one,
because a campaign that samples it a few times reports success.

Every trial gets a fresh server via gz-trial.rs: the world does not
reset, so trial N+1 on a live server inherits trial N's final pose.

TWO CORRECTIONS to the artifact's own text, made BEFORE any measurement
was accepted, so this cannot be read as moving the goalposts:

1. It demanded "holding altitude after the loss". SWREQ-FALCON-FAULT-P02
   says "isolated and the body settles upright (no tumble)" and names the
   rank deficiency (Mueller & D'Andrea). A three-rotor quad relinquishes
   yaw and cannot hold altitude. Altitude hold was never the claim.
2. It asked for the gz command "recorded as steps" -- gz is not on the
   runners. Third instance of the rc=127 class after gh (#461) and the
   prebuilt binary (ENDURANCE-001).

Scope: this is the NATIVE half only. The gz branch applies tuning the
component never receives -- main.rs says the wasm cascade flies this same
FlightCore with NONE of it -- and #398 was reported against wasm.

Also corrects two stale in-code claims: "the loop keeps the vehicle
aloft" (a physical impossibility stated as a pass condition) and the mock
plant being torque-free (untrue since #435, and while it was true every
endurance row read exactly 0.000 m horizontal).

Status stays `proposed`. No cause is claimed; no trace was taken.

Refs #398

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG

* measure(gz): the same run falsifies FV-FALCON-FAULT-003, which is `verified`

FAULT-005 staying `proposed` understates the result. FAULT-P02 carries six
verified-by links and FIVE are `verified`. One is directly contradicted.

FV-FALCON-FAULT-003 (v1.114, `verified`) ends with: "wrong if a
single-rotor-out from a settled hover is observed to exceed ~45 deg tilt
... on the analytic plant (this oracle) OR THE GZ PLANT". All four trials
exceed 45 deg: 49.7, 50.8, 72.9, 180. It named the gz plant itself, so
this is its own stated kill-criterion being met.

Sharper: FAULT-003 says the defect it fixed was a flip "to ~180 deg
(reproduced on the real gz plant)". That is exactly rot1. Either the fix
did not hold on gz, or something since regressed it.

NOT claimed: that FAULT-003's analytic oracle is wrong. Its 200-trial
full-loop numbers (worst peak tilt 0.274 rad) are a different measurement
and were not re-run. The gap is oracle-vs-gz -- the same gap FAULT-003
was itself written to close.

A `verified` artifact whose kill-criterion has been met is a maintainer
decision (demote, or judge the gz clause over-reach), not something this
pass resolves silently.

Refs #398

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG

* verify(v1.114): demote FV-FALCON-FAULT-003 verified -> implemented (#479)

Maintainer decision on #479. This artifact's OWN falsification condition
is met: "wrong if a single-rotor-out from a settled hover is observed to
exceed ~45 deg tilt ... on the analytic plant (this oracle) OR THE GZ
PLANT". Measured on gz at e1d00be, four pristine-server trials: 49.7,
50.8, 72.9 and 180 deg. All four exceed 45. It named the gz plant itself.

Sharper: the artifact says the defect it FIXED was a flip "to ~180 deg
(reproduced on the real gz plant)". That is exactly trial rot1.

NOT RETRACTED: the analytic-plant oracle stands as measured (200 dispersed
full-loop trials, worst peak tilt 0.274 rad, 0 failures) and the Kani and
allocator results are untouched. None was re-run; none is disputed. What
is withdrawn is the claim that this VERIFIES FAULT-P02's end-to-end
clause, because the plant the requirement answers to refutes it.
`implemented` is the honest status.

rivet validate PASS; coverage unchanged (FAULT-P02 retains four other
verified-by links); v1.140 readiness unaffected (this is a v1.114
artifact).

Re-promotion requires a gz-valid oracle, not a re-run of the analytic one.

Closes #479

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG

* req(v1.141): OCI-P07 — the stage components shall compose into the FLOWN law

Raised by the bench engineer (Christof, 2026-09-23): "falcon cascade is
only a middle, and it should be built out of components." Both halves are
correct and both are measurable.

ONLY A MIDDLE is the signature, literally: step(sensors, target) ->
motor-pwm returns no state, which is why the verified MAVLINK-P06 stack
has nothing to read. That is TRANSPORT-P01's RETURN direction; P07
depends on it rather than restating it.

BUILT OUT OF COMPONENTS: the decomposition already exists in
cascade.wit (five stage worlds) but decomposes the LEGACY PID cascade.
Measured with scripts/audit-component-deps.rs:

  estimator  relay-iekf     vs relay-iekf                  MATCH
  position   relay-pos      vs relay-geo                   NO
  attitude   relay-att      vs relay-geo  (geometric SE3)  NO
  rate       relay-rate     vs relay-adrc (ADRC)           NO
  mixer      relay-mix-quad vs relay-mix-quad              MATCH

The audit lists relay-geo and relay-adrc under FLOWN BUT NOT WRAPPED BY
ANY COMPONENT: the two middle stages of the flown law have no component.
OCI-DISTRIBUTION.md already states the consequence -- "fusing the
per-stage components does not produce the flown control law".

COMPOSITION PATH SPECIFIED (maintainer, 2026-09-23): wac -> meld ->
synth. This answers the obvious objection that five components means
five runtime seams and would dilute the single-seam DIFFERENTIAL=1
result: meld FUSES at build time and synth compiles the fused artifact,
so the boundary is authoring/verification structure erased before
deployment, and the differential runs against the fused artifact.
OCI-P05 (relocation metadata for meld) is the verified groundwork.

Blockers recorded in bite order: no socket (#393, wac plug has no
matching imports -- correct behaviour, and how this was found); relay-geo
and relay-adrc need wrappers (the tractable first step); tuning is
host-side and must be distributed or carried by vehicle-config, or the
composed stack flies differently from the native one.

Ordering: P07 leads, ORPHAN-P01's retirement of the legacy stage
components follows -- publishing something labelled legacy beats
publishing nothing until a flown replacement exists.

FALSIFICATION: wrong if a wac-composed + meld-fused + synth-compiled
cascade under DIFFERENTIAL=1 on the gz falcon-quad shows ANY non-zero
per-motor difference from the native FlightCore, or cannot be built.

rivet validate PASS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG

* investigate(gz): the FDI gate chatters on the hover limit cycle (#398/#479)

ROOT CAUSE FOUND, and it is not the FDI residual logic.

The detector runs only while fdi_steady = tilt_cos > 0.90 && rp_rate2 <
4.0 (~26 deg, ~2 rad/s roll+pitch). Traced over the 25 ticks BEFORE the
kill -- a healthy, PASSING hover -- rp_rate2 oscillates 3.596..4.521,
straddling the 4.0 threshold. The vehicle hovers at 1.90-2.12 rad/s
roll/pitch against a 2.0 rad/s gate, and THE GATE IS SHUT 15 OF 25
HEALTHY-HOVER TICKS (60%).

So whether a rotor loss is detectable at all is decided by the phase of
the hover limit cycle at the instant of failure. The code's own comment
records raising this gate 1 -> 2 rad/s because the gz plant limit-cycled
"near 1 rad/s". It is now ~2 rad/s; that margin is consumed.

PREDICTION HELD 5/5 -- gate state on the kill tick vs outcome:
  G2 OPEN -> isolated in ONE tick     G3 shut -> never isolated, 3.142 rad
  G4 OPEN -> isolated                 G5 OPEN -> isolated
  G6 OPEN -> isolated
One-way: once the vehicle departs, tilt_cos falls and rp_rate2 stays
high, so a gate shut at the kill NEVER reopens.

TWO DEFECTS, NOT ONE:
1. Detection is a coin flip (above). NOT a residual defect -- with the
   gate open the residual is unambiguous ([1.000 0 0 0]), one-tick
   isolation.
2. Recovery is weak even with immediate isolation: G2/G4/G5/G6 isolated
   within a tick and still peaked at 1.01-1.46 rad (58-84 deg) against
   FAULT-003's analytic claim of 0.274 rad. Perfect detection alone would
   not make this property hold.

THE FIX IS NOT THE GATE. Raising it again would make the runs pass
without making the vehicle safer -- the vacuous-gate shape this train
keeps finding. The healthy-hover limit cycle is the defect: ~2 rad/s
(115 deg/s) ringing during a hover the position loop reports as good
(0.11 m). Position hold MASKS a ringing attitude loop. Worth checking
against the wobble the bench engineer saw in the Wireshark capture.

CORRECTION to this finding's own headline: with 12 trials the tally is
11 FAIL / 1 PASS, not 4/4. T2 passed (0.338 rad). The property is
INTERMITTENT, so a campaign sampling it a few times reports success --
now demonstrated rather than argued.

Adds FDI_TRACE=<ticks> to the scenario (unset = shipped behaviour), and
fixes scripts/gz-trial.rs's KEEP allowlist, which silently swallowed the
entire trace and made it look like the instrumentation did not fire.

Refs #398 #479

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG

* plan: reorganise around the thesis — gale + component model, not PX4 parity

Goal set by the maintainer 2026-09-23: best-in-class drone software,
running on gale, with every flight capability a WebAssembly component.
Replaces the v1.139->v1.142 release-number framing; releases are now an
output and the phases are the plan. Supersedes the OCI-P07 section this
same PR added earlier -- it is Phase 2 now.

SYSREQ-FALCON-022 states the thesis as FOUR FALSIFIABLE CLAIMS, each with
its measured 2026-09-23 baseline:

1. Every flight capability is a component. falcon-core is 6090 lines
   carrying the supervisor, every failsafe and the FDI, with ZERO
   kani::proof harnesses and no kani.yml entry -- the 31 flight-path
   harnesses are all on leaves. The proofs SURROUND the defect. It must
   dissolve, not grow.
2. Composition is free: wac + meld + synth must stay bit-identical to
   native under DIFFERENTIAL=1. Held today across ONE seam; the thesis
   needs it to survive decomposition.
3. It runs on gale on silicon and no oracle hides a fault silicon takes
   -- gale#398: dissolved objects address wasm memory at ~1 MiB, unmapped
   on qemu (reads 0, writes dropped, GATES PASS), BusFault on a board.
4. The flight behaviour is genuinely good. The gz hover rings at
   1.90-2.12 rad/s while the position verdict reports 0.11 m and PASSES.

THE CONSTRAINT THAT SHAPES THE PLAN: decomposition and memory packing are
the SAME problem. Each component carries its own data base; gale's
fused.o holds two whose bases are 1.1 MiB apart through one r11 base --
more RAM than any bench board has (192 KB max), "not fixable in the
embedder". So `meld --pack-rebase` on the flight object is a
PRECONDITION for claim 1, not an optimisation. Proven on iso-core; jess
measured 5x on the M7.

Standing falsifier: what lowers to the M7 today is the LEGACY PID
cascade. The only proof-point for "runs as components on silicon" is
about code the vehicle does not use.

Phases, each owning its issues so nothing is invisible: 0 make the ground
true (#270 root defect, #398 downstream); 1 the seam returns state (jess
answered on jess#167 -- second export, seqlock, OCRAM, 32-byte aligned;
and corrected our premise: no flattening cliff on returns); 2 the
component model becomes real (OCI-P07: relay-geo/relay-adrc have no
component at all); 3 fit on silicon (gale#398, #330); 4 the safety layer
as components -- THE FIRST-FLIGHT BLOCKER, since relay_fsm has no Manual
mode and the seam has no RC channel, so no human can take the sticks;
5 evidence on a real vehicle (jess's MAVLink feed already works).

Parks the parity-table filler orphans rather than wiring them: they were
built to tick rows in a table that was itself wrong for 82 versions.

rivet validate PASS.

Refs #270 #398 #422

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG

* measure(gz): the "plant limit cycle" is ESO windup under mixer saturation

Phase 0. Adds RING_TRACE (per-tick rate-loop slice: measured gyro in,
desired rate + commanded torque out, resulting motors) to both the
flightcore and supervised paths so they are directly comparable.

THE PLANT IS NOT RINGING. Same world, same commit, same binary:

  flightcore settled hover    roll/pitch rate rms 0.0014 rad/s
                              motors 0.5809-0.5869, gate shut ~0%
  supervised hover (8-10 s)   roll/pitch rate rms 1.70/1.80 rad/s
                              motors 0.12 <-> 1.0000, gate shut 96.8%

Three orders of magnitude, and both paths apply IDENTICAL estimator and
altitude tuning and the same hover_thrust 0.585. The ring belongs to a
PATH, not to the plant -- which is how #270 has been misfiled since July.

(Also corrects my own earlier "gate shut 60% of ticks", taken from a
25-tick sample. Over 500 ticks it is 96.8%: the gate is essentially
always shut, with rare open windows. That is why rotor-out isolation
succeeds occasionally rather than half the time.)

THE RATE LOOP IS NOT TRACKING. Commanded omega_d rms 0.1443 rad/s vs
measured 2.4674 -- 17x the command, tracking error 2.5061. So this is not
a bad setpoint from the attitude loop; the inner loop oscillates on its
own at 3.5 Hz with motors railing.

ONSET IS SATURATION. Clean at 0.0019 rad/s through t=0.5 s; motors first
hit their limits at 0.50-0.75 s; ring established by 0.75-1.00 s; still
running 7 s later. Never recovers.

MECHANISM. relay-adrc drives its ESO with `u_prev`, the RAW computed
control, lag-filtered by tau. Its own comment states the right principle
("the ESO must be driven by the torque actually DELIVERED") and
implements it for actuator LAG ONLY. But falcon-core mixes with
mix_thrust_floor, which is THRUST-PRIORITY (MIX-P05): it computes the
largest s in [0,1] keeping every motor in range and delivers s*torque. s
is computed inline, is not exposed, and never reaches the ADRC. So under
saturation the ESO integrates a torque that was not applied, z2 winds up
at beta2 = omega_o^2 = 1600, the control answers with MORE torque, and
the loop latches into a rail-to-rail cycle that sustains its own
saturation. Thrust-priority is also why it breaks at TAKEOFF: collective
leaves the least torque headroom exactly then.

flightcore is immune because it never saturates (s == 1, ESO told truth).

NOT CLAIMED: that anti-windup is the fix. The evidence is correlational
and arithmetically consistent, but the decisive test is to feed the ESO
the achieved torque and re-measure. Falsification stated in the finding.

Refs #270 #398

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG

* fix(rate): stop lying to the ESO about delivered torque, and hold it on the ground (#270)

Two genuine defects, found chasing the attitude limit cycle. NEITHER
CURES IT -- my stated falsification was run and FAILED. Landing them
because each is independently wrong, with honest claims.

DEFECT 1 -- the ESO was driven by a torque that was never applied.
falcon-core mixes with mix_thrust_floor, which is THRUST-PRIORITY
(MIX-P05): under saturation it scales the torque triple by s in [0,1]
and delivers s*torque. s was computed inline in the mixer, never
exposed, and never reached relay-adrc. The observer therefore integrated
the FULL commanded torque, its residual grew, z2 wound up at
beta2 = omega_o^2 (1600 for roll/pitch), and the control answered with
more torque -- which saturates harder.
  - relay-mix-quad: record + expose last_torque_scale (additive).
  - relay-adrc: u_delivered_frac, distinct from u_act. u_act models the
    actuator LAG (torque arrives late); this models torque that never
    arrives at all. Defaults to 1.0, so an allocator that never reports
    leaves behaviour bit-identical to before.
  - falcon-core: feed s back after the mix. Tick N uses tick N-1's
    fraction, which is correct -- the ESO is one tick behind by
    construction, since what was applied is only knowable after
    application.
  set_delivered_fraction takes a per-AXIS array although a saturating
  mixer has one scalar s: the axes are independent observers with
  different tuning (yaw omega_c=3.0/tau=0.025 vs roll-pitch 12.0/0.0125)
  and MIX-P06 desaturates in priority order, giving up yaw before
  roll/pitch -- one scalar for all three would then be a lie about the
  axes that kept authority.

DEFECT 2 -- the observer was never reset. AdrcAxis::reset() says "e.g.
on arming" and had ZERO call sites in the flight path. It ran
continuously from construction, straight through ground spin-up, when
the plant CANNOT answer a torque command because the gear takes the
moment. Held at zero until flying(), leaving a pure proportional command
on the ground.

MEASURED ON GZ, supervised-rotorout hover:
                                   gyro rp rms   gate shut
  baseline                            2.47         96.8%
  + mixer anti-windup                 2.02         51.0%
  + ground hold                       1.91         35.8%
The bar was < 0.1 rad/s. It is 1.91 -- SO ANTI-WINDUP IS NOT THE PRIMARY
CAUSE. Gate availability 96.8% -> 35.8% shut is still a real gain for
#398. The single-run outcome column must NOT be read as ranking the
fixes: with 11/12 baseline failures already measured, n=1 cannot
distinguish them.

WEAKNESS IN MY OWN CONTROL, stated: flightcore is not a clean control
for the rate loop -- it holds altitude only and its motors never leave a
0.03-wide band, so it never LOADS the rate loop. "flightcore is clean"
shows the loop is never asked to do anything, not that it is healthy.
The honest claim is narrower: the ring appears when the rate loop is
actually loaded.

Tests: relay-adrc 65, relay-mix-quad 11, falcon-core 26 -- all pass, and
that is EXPECTED rather than reassuring: the analytic plant hovers in a
0.0001-wide motor band, so s == 1 there and both changes are no-ops. The
23 FlightSupervisor tests cannot see the bug or the fix.

Kani: QuadMixer gained a field; relay-mix-quad proofs must re-run in CI
(#429 documents that harness's SAT heavy tail).

Status stays at most `implemented` (two-commit rule).

Refs #270 #398

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG

* revert(rate): move the ADRC anti-windup + ground hold to its own PR

Not a retraction -- both defects are real and the measurements stand.
This PR already carries six concerns (the FAULT-005 measurement, the FDI
root cause, the FAULT-003 demotion, OCI-P07, the thesis + plan rewrite,
and the ring diagnosis). A CONTROL-LAW change to Kani-proven crates is a
different kind of change from measurement and documentation, and it
wants its own review and its own Kani run -- especially since it does
NOT achieve its stated goal (the ring survives at 1.91 rad/s against a
0.1 bar).

The work is preserved on branch phase0/rate-loop-antiwindup and comes
back as its own PR. The bench-evidence finding that records the
falsification stays here, because it is measurement.

Refs #270

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG

* docs(bench): keep the falsification RESULT with the measurement

The revert that moved the control-law change to its own PR also undid the
doc corrections, which are measurement and belong here: the counted 23
FlightSupervisor sites (and that they are BLIND, not failing, because the
analytic plant hovers in a 0.0001-wide motor band), and the recorded
FALSIFICATION of my own hypothesis (ring survives at 1.91 rad/s against a
0.1 bar). Also drops the now-untrue 'this hypothesis has not yet been
tested' line -- it was tested, and it failed.

Refs #270

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG

* plan(release): move FAULT-005 to v1.141, Phase-2 scope to v1.142, file RATE-P05

Release scoping so the phases map onto actual releases.

v1.140 "Hold" -> 9/10, one blocker: FV-RELAY-REVIEW-140, the maintainer
review checkpoint. SCOPE MOVED OUT AND LOGGED: FV-FALCON-FAULT-005 ->
v1.141. It cannot be verified while #270 stands (the FDI gate is shut
96.8% of healthy-hover ticks, so rotor-loss detectability is decided by
limit-cycle phase), and the ring's primary cause is open. v1.140's own
theme -- the hold -- is done and measured: estimator, endurance, battery,
fleet, mixproof. Holding a finished release hostage to an open control
investigation is the scope error, not the fix.

v1.141 "The rate loop and the seam" (Phases 0-1): RATE-P05, FAULT-005
behind it, TRANSPORT-P01, REVIEW-141.

v1.142 "Components" (Phase 2): OCI-P07, ORPHAN-P01, CONFIG-P01 and
SHOWCASE-P01 all moved here from v1.141; the platform-integrity items
already scoped at v1.142 ride alongside and never block.

SWREQ-FALCON-RATE-P05 is NEW and owns the ring. It is P05, not P01,
because RATE-P01..P04 already exist and are about the LEGACY PID rate
controller relay-rate, which the flight core no longer flies (it flies
ADRC). None of them is falsified by this measurement: they are properties
of the law in isolation -- anti-windup bounds, pole placement, Lyapunov,
WCET -- and none says anything about the CLOSED loop on a plant that can
saturate. That gap is the requirement.

I first wrote P05's content OVER SWREQ-FALCON-RATE-P01.yaml, which holds
FOUR artifacts, orphaning P02/P03/P04 and breaking 6 links (caught by
rivet validate: FAIL, 6 errors). Restored from HEAD and filed in its own
file after checking the id was free. This is the documented
batch-authoring clobber hazard, hit again.

Useful fallout: RATE-P04 fixes the control period at "1 ms (1 kHz update
rate)", and relay-adrc's filter comment reasons about "the 1 kHz loop" --
while this bench runs at 250 Hz. That design-vs-deployment rate mismatch
is now RATE-P05's leading untested hypothesis.

Also corrects stale text in FAULT-005: the 60% gate figure (a 25-tick
sample) -> 96.8% over 500, and "four trials, four FAILs" -> 11 FAIL /
1 PASS over twelve.

rivet validate PASS. Readiness v1.140 9/10.

Refs #270 #398

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG

* review(clean-room): withdraw the gate-shut figure — it was measured on the wrong signal

Clean-room review with three cold verifiers. 18/18 code+trace claims and
7/7 measurement claims confirmed independently -- but the reviewers found
defects the author could not see, and one of them invalidates a headline
number repeated throughout this work.

THE GATE-SHUT FIGURE WAS THE RAW GYRO, NOT THE GATE'S SIGNAL.
RING_TRACE printed backend.last_imu() -- the RAW gyro. The FDI gate tests
`gyro_f = gyro_lpf.filter(..)` (lib.rs:810,861). Measured both, and
cross-checked against the gate's own boolean:

                  raw (reported)   filtered (what the gate reads)
  climb              91.2%                 48.8%
  pre-kill           97.8%                 33.8%

So ~34% shut, NOT 96.8%. The reviewer caught it by ARITHMETIC before any
re-run: a gate shut 96.8% of the time that never reopens cannot produce 9
isolations in 12 trials. The 60% I "superseded" came from FDI_TRACE --
the CORRECT signal -- so I replaced a roughly-right number with a wrong
one. "Essentially always shut" and "rare open window" are withdrawn.
The before/after gate percentages quoted for the anti-windup change were
raw-derived too and are withdrawn pending re-measurement.

THE MECHANISM IS UNAFFECTED: gate-state-at-kill still predicts the
outcome (5/5 here, consistent across the reviewer's 3 independent runs),
and ~66% open reconciles cleanly with 9 of 12 trials isolating.

IT IS NOT A HOVER. Independent reproduction measured mode=Takeoff at the
kill: 1.49 m against a 2.0 m target, still climbing at 0.09 m/s, in a
saturated relay. Every "healthy hover" in this work should read
"saturated climb that never reaches hover".

RING now emits the gate's OWN quantities (filtered rp_rate2, tilt_cos,
gate_open) so this cannot recur, and "RING " is added to gz-trial.rs's
KEEP allowlist -- I added "FDI " there after it swallowed one trace and
then walked into the same trap with the next knob, which is how the
reviewer lost its first run.

flightcore-rotorout: its verdict is `isolated == Some(rotor) && finite`
-- no tilt, no altitude, no position -- so a 180 deg inversion that
drifts and crashes PASSES. I had rewritten the comment above it to claim
the loop "must keep the airframe UPRIGHT", which the code does not check.
Warning recorded in place; not cited as FAULT-P02 evidence until wired to
true_tilt_rad() like its sibling.

rivet validate PASS -- and, checked this time, PASS under the
GATE-PINNED 0.19.0 (123 warnings) as well as local 0.37.0 (395).

Refs #270 #398

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG

* test(hold): the stale-aiding fallback is HARMFUL, not merely untested (#483)

Clean-room review found that HOLD-001's cited oracle,
gnss_outage_restores_the_gravity_update_and_stays_bounded, could not fail
for the branch it names: the plant had zero gyro noise and zero bias, so
15 s of dead reckoning was exact either way.

Fixing the plant turned up something worse than a weak test.

TWO CHANGES TO MAKE IT DISCRIMINATING:
1. gyro_bias_drift = 0.0004, so dead reckoning can actually drift. The
   gyro bias ramp is UNOBSERVABLE without the gravity update.
2. Assert on ATTITUDE error, not horizontal position. The fallback
   restores an attitude reference; it cannot restore position, which is
   dead-reckoned by double integration and drifts on any real plant. The
   old `peak < 5.0 m over 15 s` bound was satisfiable ONLY on a noiseless
   plant -- which is why the plant was noiseless.

MEASURED, peak |cos(tilt)_est - cos(tilt)_true| over the 15 s outage:

    WITH the stale-aiding fallback (as shipped)     0.3787
    WITH the fallback deleted (the mutation)        0.0155

THE FALLBACK MAKES THE ATTITUDE ESTIMATE 24x WORSE.

WHY. The premise in the code -- "through a GNSS outage the accelerometer
is the only attitude reference there is" -- holds for a LEVEL, STATIONARY
vehicle. During an outage this one is MANEUVERING: the position loop
chases a drifting estimate, so it tilts and accelerates. A multirotor
accelerometer measures SPECIFIC FORCE ALONG THE THRUST AXIS, not gravity
-- the exact insight #452 was opened on. The fallback re-introduces the
error #452 removed, gated on aiding staleness instead of every tick. So
v1.140's hold fix suppresses the gravity update when aided (correct) and
resumes it under precisely the condition that makes it most wrong.

CAVEAT: one test, one plant, one drift magnitude. Direction and margin
are unambiguous; not swept or reproduced on gz.

The test now asserts the correct property, carries both numbers, and is
#[ignore]d pointing at #483 so CI stays green and the evidence stays
runnable (`cargo test -p falcon-core -- --ignored gnss_outage`).
Suite: 64 passed, 0 failed, 4 ignored.

This affects FV-FALCON-HOLD-001, which is `verified` in v1.140's scope.

Refs #483 #452

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG

* fix(est): the accelerometer is never a tilt reference in flight (#483)

Removes the v1.140 stale-aiding fallback. It resumed the gravity update
after ~1 s without fixes, which is the wrong test: during a GNSS outage
the position loop chases a drifting estimate, so the vehicle is
MANEUVERING -- exactly when the accelerometer is least valid.

MEASURED, peak |cos(tilt)_est - true| through a 15 s outage on a plant
with a gyro-bias ramp:

    staleness fallback (v1.140, shipped)            0.3787
    + |f| within 25% of g                           0.3787
    + |f| within 0.5% of g                          0.3787
    + |f| within 0.5% of g AND rate < 0.1 rad/s     0.3457
    NO in-flight gravity update (this change)       0.0155

A VALIDITY GATE ON |f| DOES NOT WORK. I implemented one first -- it was
the option I recommended -- and a 50x tolerance sweep moved the number by
zero. It is not a tuning problem: a quad holding altitude at tilt theta
has |f| = g/cos(theta), inside 0.5% of g for theta < 5.7 deg, while the
DIRECTION is along body -z. So the update asserts "you are level"
whatever the real tilt. Magnitude cannot distinguish the case, because
the thrust axis is what the accelerometer reports. It does not merely
fail to inform; it ERASES tilt.

In flight the attitude reference is the gyro, its bias observed through
GNSS/mag/heading. On the ground, at rest, the accelerometer genuinely
reads -g and remains the pre-arm reference.

`gravity_hold_ticks` is now dead and removed -- which also disposes of
#486 (it computed 4 s, not the documented 1 s, on the supervised path,
because FlightSupervisor hardcodes loop_hz=1000 while the bench runs
250 Hz). `ticks_since_aiding` stays as a freshness signal.

COLLATERAL, and it is the same defect class again:
high_wind_saturation_commands_rtl broke, because WindBackend conveyed
tilt through the accelerometer alone with `gyro: [0.0; 3]` -- idealized
by construction, and the exact pattern the project memory warns about.
With the in-flight gravity update gone it had no way to express a tilt
and stopped firing the failsafe it names. Fixed properly: it now rotates
into the tilt on the GYRO (0.61 rad/s for 200 ticks -> ~0.488 rad) and
then holds, with the accelerometer reporting the specific force
consistent with that attitude -- what a real IMU would produce.

VERIFIED:
  falcon-core   65 passed, 0 failed, 3 ignored (the outage test is
                un-ignored and now potent: deleting the branch it names
                changes the number by 24x)
  relay-adrc / relay-mix-quad / relay-iekf  all green
  gz flightcore hold UNCHANGED: final_dist 0.12 m, horizontal 0.01 m,
                rms_steady 0.10 m -- the hold claim survives the fix

CAVEAT: one plant, one drift magnitude, not swept or reproduced on gz.

Affects FV-FALCON-HOLD-001, `verified` in v1.140's scope -- its text
still describes the removed fallback and needs re-wording before any tag.

Refs #483 #486 #452

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG

* docs(trace): HOLD-001 describes the policy that now exists, not the removed one (#483)

Its cited oracle could not fail for the branch it named, and repairing
that oracle showed the branch was HARMFUL rather than untested. The
artifact now records: why the old test was inert (noiseless plant, so
dead reckoning was exact either way), why the assertion moved from
position to ATTITUDE (position is dead-reckoned by double integration and
drifts on any real plant, so the old 5 m bound was satisfiable only on a
noiseless one), the four measured numbers, and why a validity gate on
|f| cannot rescue the branch.

The gz claim is unchanged and re-measured after the fix: final_dist
0.12 m, horizontal 0.01 m.

Refs #483

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG

* fix(est): flying() must fail SAFE — an over-estimated hover thrust diverged the hold 30 m (#481)

`flying()` gates `in_flight`, which after #483 is the ONLY thing
admitting the gravity update. Its errors are not symmetric:

  says NOT flying while airborne -> the gravity update runs IN FLIGHT,
    which is the full #452 divergence (specific force points along the
    thrust axis, the tilt estimate is pulled level, the hold diverges).
    Catastrophic and silent.
  says flying while on the ground -> no gravity update on the ground, so
    pre-arm loses its tilt reference. Visible at pre-arm; it does not
    take off.

The threshold was `0.7 * hover_thrust`, which fails the DANGEROUS way
whenever hover_thrust is over-estimated -- and it is hardcoded to 0.5 in
every shipped entry point (wasm/cm/flight, embedded/falcon-cortex-m,
falcon-param, falcon-hitl, FlightSupervisor::new), while wasm/cm/cascade
falls back to 0.5 when `configure` was never called. An airframe hovering
at 0.35 mean then reads NOT flying in a STEADY HOVER.

MEASURED, 0.35-hover plant told hover_thrust=0.5, 5 Hz aiding, 60 s:
    old `0.7 * hover_thrust`      peak horizontal 30.01 m  (diverges)
    with an absolute floor        peak horizontal  < 1.0 m

Adds `AIRBORNE_FLOOR = 0.25` and takes the LOWER of it and
`0.7 * hover_thrust`, so any vehicle holding a mean collective above the
floor is flying whatever the configuration claims. Ground idle is well
below it.

The regression test is MUTATION-CHECKED: reverting the threshold makes it
fail at 30 m. Getting there needed two corrections to my own first
attempt, both the idealized-harness class this review keeps surfacing --
the first plant was level and undisturbed (the gravity update does no
harm when nothing tilts, so it could not tell the policies apart), and
the first assertion also bounded ALTITUDE, which conflates the separate
and already-recorded consequence of a wrong feedforward
(SWREQ-FALCON-TRANSPORT-P01 measured 58.8 m). It now uses the excitation
known to expose this -- 5 Hz aiding, starting 1 m off so the vehicle must
TILT to fly back -- and asserts only the horizontal divergence signature.

NOT fixed here: the hardcoded 0.5 itself. The shipped entry points still
pass it and `configure` is still optional. This change makes that
misconfiguration non-catastrophic; it does not make it correct. #481
stays open for the configuration path.

falcon-core 66 passed, 0 failed, 3 ignored. rivet validate PASS.

Refs #481 #483 #452

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG

* fix(sim,trace): take specific force after the ground clamp (#485); actually demote GNSS-001 (#484)

#485 -- MockPhysics computed specific force as `thrust - drag` INSIDE the
integration loop, ignoring the ground entirely. SimBackend was fixed for
exactly this in the same change that first gave MockPhysics a floor, and
the fix was not carried across -- while that change also switched
ground_contact ON for supervised-rotorout.

Measured consequence at sub-hover throttle while in contact: with motors
at 0 the reading was [0,0,0], so Iekf::update_gravity rejected it
(|a| <= 1e-3) and the estimator got NO tilt reference on the ground --
the exact opposite of what pre-arm relies on -- while propagation
integrated a 9.81 m/s^2 free fall on a vehicle sitting still.

Now derived from the ACHIEVED velocity change, after the clamp, so the
ground reaction appears by construction: at rest on the floor the clamp
holds velocity, giving f = -g = [0,0,-GRAVITY], which is what a real
accelerometer reads at rest. Near hover in the air it is identical to the
old expression. Mock supervised-rotorout unchanged (PASS, peak tilt
0.048 rad); falcon-sitl-gz 32 passed.

#484 -- FV-FALCON-GNSS-001 has claimed in prose since 0185e8a that it was
"STATUS DROPPED to `implemented` in v1.140", while `status:` still read
`verified`. The field was never touched. That is worse than forgetting:
the prose reads as though the two-commit rule was followed, and the
artifact's claim 2 (over-trust DIVERGES) is now known false -- the test
was renamed to variance_matching_helps_and_over_trust_no_longer_diverges.
Status now actually says implemented, with the discrepancy recorded.

v1.140 readiness unchanged at 9/10 (GNSS-001 is not in its scope).
rivet validate PASS.

Closes #484 #485

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG

* ci(gate): actually run `rivet validate` — the gate installed it and never did (#482)

The verification gate installs rivet v0.19.0 (pinned) and then only calls
`rivet list` / `rivet get` through run-falcon-verification.py. Neither
resolves link targets. `grep -rn "rivet validate" .github/workflows/`
matched a COMMENT and nothing else.

So the trace's structural integrity rested on the author remembering to
run a local command -- with a local binary that is not the pinned one
(0.37.0 vs 0.19.0, and they disagree: 395 warnings against 123 on the
same tree).

MEASURED COST, this session: overwriting one artifact file that happened
to hold FOUR artifacts orphaned three of them and broke six
verifies/satisfies links. `rivet validate` caught it immediately (FAIL,
6 errors). This gate would not have.

Checked before enabling: validate passes on this tree under BOTH 0.19.0
(123 warnings) and 0.37.0 (395), so turning it on needs no cleanup first.
Warnings do not fail the step; only errors -- missing link targets,
schema violations -- do.

Closes #482

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG

* style: cargo fmt the RING_TRACE additions

The Format gate FAILED on #480 and blocked it. My RING_TRACE blocks and
the FDI_TRACE env parse were not rustfmt-clean.

Worth noting against project memory: this gate used to be MASKED
(continue-on-error) and logged 893 diffs while reporting green. It is
unmasked now and it bit, which is the gate working as intended -- and a
reminder to run `cargo fmt --all` before pushing rather than after CI
says so.

falcon-core 66 passed, 0 failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG

* release: put rivet's trace-derived release note in the release body

The release body was a hand-written file plus verification-tracks.rs. It
never carried the TRACE's own account of the release, which `rivet
release notes` generates in the ASPICE 11-03 shape:

  - what was committed, and what was DELIVERED vs WITHHELD (v1.140: 10 in
    scope, 9 delivered, 1 withheld -- FV-RELAY-REVIEW-140);
  - which delivered artifacts carry no verification evidence;
  - and the part a hand-written note always drops: the ASPICE elements
    rivet does NOT compute -- application parameters, variants, intended
    environment, cross-component impact, compatibility, licensing,
    delivery approval, product dependencies -- DECLARED, not silently
    omitted.

PIN BUMPED 0.19.0 -> 0.38.0 in BOTH release.yml and
verification-gate.yml, as their comments require. `rivet release` does
not exist in 0.19.0 (`error: unrecognized subcommand 'release'`), so
wiring this without the bump would have produced a silently missing
section.

Verified on this tree with a scratch-built v0.38.0 BEFORE bumping:
  rivet validate        PASS (580 warnings; 395 under 0.37, 123 under
                        0.19 -- warnings do not fail, errors do)
  rivet release notes   works
  rivet release status  works, and independently agrees with
                        scripts/release-readiness.rs: "NOT cuttable"

A GOTCHA THAT WOULD HAVE BROKEN THIS SILENTLY: `rivet release notes`
returns NON-ZERO AS A VERDICT, not as an error. Measured -- v1.139.0 (all
verified) exits 0; v1.140.0 (one withheld) exits 1, with the full 49-line
note on STDOUT and stderr EMPTY in both cases. The obvious `|| rc=$?`
handling would therefore have appended the note and then declared it
UNAVAILABLE directly underneath, on every release that is not fully
verified. The step now decides on OUTPUT (non-empty and starting with
`# Release note`) and treats a non-zero exit as a ::notice:: pointing at
the note's own Limitations section. Genuine absence is still LOUD, with
stderr echoed.

COST, stated: the bump forces one cold recompile of rivet on the
self-hosted runners -- the OOM-prone install step the pin exists to
avoid. Once per bump, not per run.

NOTE for future scope moves: `rivet release move` exists and logs the
decision. I re-targeted FAULT-005 and the Phase-2 artifacts by editing
`release:` in YAML, which the project rules say not to do. The result
validates and is correct, but `move` is the right tool.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG

* plan(v1.143): one release for issue hygiene — BOARD-P01

Maintainer call: spend a release on the issue board. Measured it first
rather than planning against the feeling.

BASELINE 2026-09-24, 57 open:
  oldest issue                    163 d (#4)
  older than 6 months               0
  untouched 30+ days               24  (42%)
  untouched 100+ days               6  (#4 #7 #8 #145 #153 #177)
  named in docs/RELEASE-PLAN.md    46
  INVISIBLE to the plan            11  (5 filed today)

The instinct "57 open with very old ones is a bad sign" is only partly
right here, and the difference changes what the release should do:

  NOTHING IS ANCIENT. No issue predates 6 months. The oldest three
  (#4/#7/#8) are relay-FRAMEWORK items from before the falcon flight arc,
  not forgotten flight bugs.

  THE PLAN IS MOSTLY WORKING. 46 of 57 are named in RELEASE-PLAN.md, so
  most "stale" issues are PARKED BY DESIGN with a phase that owns them.
  Last-comment staleness therefore OVERSTATES the problem.

  BUT the parked set has NO EXPIRY -- nothing distinguishes "parked
  deliberately" from "quietly abandoned", and six have gone 100+ days.

  AND SOME ARE PROBABLY ALREADY FIXED. Heavy cross-reference traffic with
  no closure is the tell: #262 (16), #153 (11), #8 (9), #350 (8).

Scope: every issue gets exactly ONE disposition; closure as fixed cites a
TAGGED RELEASE, never a merge (merged is not released, and the tag is the
evidence an outsider can check); re-verify against the current artifact
before closing, never "probably covered by #NNN"; drive the
invisible-to-plan count to zero; audit the `closes #N` cascade that
already falsely closed #413 fifteen hours before its fix.

The durable half is `scripts/board-health.rs` printing that baseline
table on a schedule. Without it this is a sweep that decays the week
after -- the measurement is what keeps it true, not the sweep.

NOT the target: a smaller number. An issue closed without evidence costs
a re-report and teaches the board to lie. 57 issues each with a live
disposition is healthy; 20 nobody trusts is not.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG

* verify(trace): re-pin HOLD-001's step to the policy that exists (#483)

The Verification gate FAILED on this artifact's own step:

  grep -q '!self.in_flight || self.ticks_since_aiding > self.gravity_hold_ticks'  (rc=1)

That is the gate WORKING. The step pinned the stale-aiding fallback, and
this PR deletes it -- resuming the gravity update on aiding staleness made
the attitude estimate 24x worse (0.3787 vs 0.0155), because the
accelerometer reads specific force along the thrust axis and a
maneuvering vehicle is exactly when that is least valid.

Re-pinned to assert the policy that EXISTS (`if !self.in_flight {`) plus
two NEGATIVE assertions that the removed fallback has not crept back --
neither the staleness condition nor the `gravity_hold_ticks` field. A
step that only asserts presence cannot catch a regression that re-adds
something.

All three verified by running them exactly as CI does. rivet validate
PASS.

Refs #483

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HvusAXYbHLyv3uTzfBcMbG

---------

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant