Skip to content

v2.2 model layer: corrected roll bound, ±6 ODD, provenance-locked release — none of the reach-avoid math touched - #2

Open
rshi159 wants to merge 51 commits into
vault-dev-reachavoidfrom
feature/v2.2-controller-model-consumer
Open

v2.2 model layer: corrected roll bound, ±6 ODD, provenance-locked release — none of the reach-avoid math touched#2
rshi159 wants to merge 51 commits into
vault-dev-reachavoidfrom
feature/v2.2-controller-model-consumer

Conversation

@rshi159

@rshi159 rshi159 commented Jul 29, 2026

Copy link
Copy Markdown

Model-layer changes underneath the reach-avoid work, proposed for review — nothing merges without your sign-off.

Read first: vault/docs/CHANGES_TO_THE_MODEL_LAYER.md — written to be audited. §§1–13 are the record (frame error in a_tip, ODD ±4→±6 by operator ruling, governor signed-state fix, four limitations characterized with measurements, the entropy-term analysis with your ISAACS code cited). §14 is the handoff: ownership tables (T1–T11 safety theory — all yours; A1–A9 architecture — split), open-item registry with evidence pointers, and pick-up mechanics for you or your coding agent.

Results figure: vault/docs/v22_model_layer_and_reach_avoid_results.pdf (+ generator + slice data, committed so it can't drift).

Boundary held: none of your files modified — verify with
git diff 2c4f73d..HEAD -- $(grep @jfisac CODEOWNERS | awk '{print $1}') → empty. Your DRABE operator tests pass 7/7 on this branch.

One commit flagged for your explicit judgment: 0e2052e adds a CODEOWNERS file. Its content defers to you (your files, your ownership), but a governance file is yours to accept, amend, or have dropped from this branch — say the word either way.

Also yours to rule on when you get to it: A2 in §14.3 (value-based vs rollout monitor) is the highest-leverage open item — it decides whether the entropy defect (T10/§11) is in the deployed path at all.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AQVjTFXQZu3G2fuVsujV17

rshi159 and others added 30 commits July 27, 2026 15:20
Replaces the vendored model artifacts with a hash-checked loader that reads
the provenance-pinned release from vault-controller: composite parameters,
the reduced opt6 kernel, the coupled residual fit, controller limits, and
the body-wheel cylinder contact geometry (half-width 0.02 m, the firmware
sim value, now declared in the release rather than defaulted locally).

- vault/model_release.py: locates the controller checkout, verifies this
  repo's MODEL_INPUTS.lock.json byte-matches the release, exposes artifacts.
  Access is lazy: importing the package succeeds while a checkout changes;
  the hash gate fires on first artifact access.
- vault/checkpoints.py + data/checkpoints_manifest.json: pre-v2.2 SafetySAC
  checkpoints are quarantined by model hash and fail loudly with an
  explicit message rather than loading silently.
- Deleted vendored copies: csrc/reduced_opt6.c, data/composite_params.json,
  data/coupled_residual_fit.json, data/grid_reachavoid_odd.npz (the last is
  regenerable via `python -m vault.grid` and was computed on v2.1).

Jaime's algorithm files are unchanged except import rewiring; the two
edits to his files are a restored authorship caveat in reach_avoid_sac.py
and loader gating in train_reach_avoid.py. Exploratory safety-investigation
work is preserved on exploratory/v2.2-safety-investigation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AQVjTFXQZu3G2fuVsujV17
Co-authored-by: OpenAI Codex <codex@openai.com>
The audit note in this file cited a witness test that lived only on the
preserved exploratory branch, so a reviewer could not run the evidence for a
claim made about their own code. Both now ship here.

- Extract the Bellman target into _drabe_target_torch, a pure function.
  Arithmetic is unchanged: verified bit-identical to the previous inline
  expression, max|diff| = 0.000e+00 over 200 random 64-sample batches
  spanning terminal and non-terminal transitions.
- Add vault/tests/test_drabe_operator.py: reproduces RSS 2021 Eq. 15 exactly
  under the negative-good to positive-good sign flip, measures the contraction
  modulus, checks Theorem 1 under-approximation, and shows the optimistic
  (1-gamma)*g variant admits a false-safe trap state.
- Give the entropy-bonus audit note its quantitative form: the contaminated
  fixed point crosses zero at beta* = |L|(1-gamma)/gamma = 5.0e-4 at the
  default gamma=0.999, and raising gamma lowers that bar. Inherited from
  safety_sb3's avoid-only backup. Training behaviour still unchanged here.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AQVjTFXQZu3G2fuVsujV17
Regenerate Jaime's original ODD reach-avoid grid against the pinned v2.2 release parameters and make it the default ValueFilter.from_grid backend. Controller capability grids remain available only through the explicit from_capability_grid constructor, so release consumption cannot silently substitute safety-set semantics.

Generated-by: Codex
Run contraction and conservatism checks through _drabe_target_torch itself, and add fixtures that independently expose max(ls, next_q), the outer min(gs, ...), gamma weighting, and terminal behavior. Rename the counterexample so the valid upstream avoid-only backup is not mislabeled as a shipped bug.

Generated-by: Codex
Remove stale vendored-kernel and deleted-data descriptions. Document VAULT_CONTROLLER_ROOT, byte-identical MODEL_INPUTS lock enforcement, release-backed artifacts, and the source-versus-flashed boundary for controller evaluation.

Generated-by: Codex
Keep locally preserved articulated residual datasets and diagnostic results out of the focused model-consumer branch. Their committed history remains on exploratory/v2.2-safety-investigation.

Generated-by: Codex
Same solver, same ODD contract, same axes, same friction slices; only the
robot model differs. The v2.1 grid is read from git at 2c4f73d so the
comparison is reproducible from this repository alone.

Certified volume shrinks slightly at every friction (18.9->17.8, 24.7->24.2,
25.9->24.8 percent) but the change is not a uniform inward shift: at mu>=0.6
the at-rest lean band widens while the set loses volume elsewhere, so the
boundary moves in both directions. Overall shape is preserved, which is what
a mass and inertia update alone should look like.

Reproduce: python vault/tools/make_model_update_figure.py

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AQVjTFXQZu3G2fuVsujV17
Load ODD, domain, grid, control, friction, and disturbance values from the hash-locked controller release while preserving package importability until first artifact access. Bind environment spaces to the same domain. The widened grid artifact is regenerated separately.\n\nCo-authored-by: OpenAI Codex <codex@openai.com>
Raise only the iteration budget from 400 to 1000 for the widened domain, preserve the 1e-3 convergence threshold, and refuse to save when any friction slice exhausts the budget.\n\nCo-authored-by: OpenAI Codex <codex@openai.com>
Keep the v2.1-to-v2.2 comparison on its original asymmetric ODD and document the controller release as the single authority for the active widened contract. Safe-set fractions across the old and new domains are not directly comparable.\n\nCo-authored-by: OpenAI Codex <codex@openai.com>
Solve the 44x29x17x25 domain over v/psi +/-4 with 25 controls and the unchanged 1e-3 convergence threshold. Terminal results: mu=0.3 dV=0.000991034 in 272 iterations (27.2864% safe), mu=0.6 dV=0.000999483 in 160 (44.4898%), mu=1.0 dV=0.000996942 in 176 (48.7787%). These fractions use a different domain and denominator from the prior asymmetric grid.\n\nCo-authored-by: OpenAI Codex <codex@openai.com>
Reference the commit that actually introduced the old-domain v2.2 grid; both historical inputs now resolve to matching 13x29x17x19 axes.\n\nCo-authored-by: OpenAI Codex <codex@openai.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AQVjTFXQZu3G2fuVsujV17
Reads both the grid and the ODD contract from the pinned release, so the
figure and the contract cannot drift apart. Kept separate from
make_model_update_figure.py, which stays pinned to the older narrower
contract; the two are not comparable and the caption says so.

The figure states what the picture actually shows: the velocity edges are
the declared contract, not a dynamics boundary, because the four-state model
has no speed-dependent term. The edges the solve genuinely determines are in
pitch, pitch rate, and the |v*psi_dot| <= a_tip roll coupling -- visible as
the slanted upper-left and lower-right boundaries, which is the roll bound
excluding fast-and-turning states.

Safe fractions on the +-4 contract: 27.3 / 44.5 / 48.8 percent at mu =
0.3 / 0.6 / 1.0.

Sidecars re-pinned to lock 6a2a5749 after the velocity-tier correction; the
staleness gate correctly rejected them until they were updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AQVjTFXQZu3G2fuVsujV17
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AQVjTFXQZu3G2fuVsujV17
Quantifies how much the MuJoCo "zero liftoff" results are worth. A rigid body
on two cylinder wheels with no pitch freedom, so roll is isolated from the
inverted-pendulum collapse that invalidated the first attempt at this.

At the shipped 40 mm wheel the simulator tips at 7.512 m/s^2 against the
certificate's analytic 6.999 -- about 7% late, so MuJoCo liftoff results are
mildly optimistic and should not be leaned on for the roll bound.

Most of that gap is not wheel width. A near-point-contact 5 mm wheel still
measures +6.7%, and the spread across 5/20/40 mm is inside the run-to-run
scatter; the residual is contact compliance (solref 20 ms, slower than the
10 ms control step). Width only separates clearly above 40 mm half-width.
An earlier claim that the rim geometry alone made the sim 14% too stable was
derived from static geometry and is not supported by measurement.

Simulation only, unladen and centred: excludes the 9.74 mm lateral CoM offset
that makes a_tip 5.5% optimistic in one turn direction.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AQVjTFXQZu3G2fuVsujV17
…ties

Adds vault/tools/measure_rollover_bilateral.py, the empirical witness for the
asymmetric no-liftoff bound, and removes measure_tipping_threshold.py, which
measured the wrong thing with a broken rig.

The v2.2 mass distribution puts the centre of mass 7.2764 mm off-centre
laterally (re-derived here from the tucked URDF at run time through the model
release's own parser, so the rig cannot drift from the model). The robot
therefore tips at a lower lateral acceleration pushing toward the heavy side
than away from it, and nothing in the four-state safety path expresses that.

Measured, static hold-and-settle bisection:

                                a(+y) toward   a(-y) away    ratio
    analytic, wheel-centre W/2      6.9246        7.6817     1.10934  <- shipped
    analytic, rim W/2+half_width    7.9651        8.7222     1.09506
    MEASURED                        7.6842        8.4229     1.09614

    static per-wheel load  measured  +y 101.70 N  -y 91.86 N
                          predicted  +y 101.80 N  -y 91.77 N

Two independent observables confirm the offset and its sign: the static load
split, which matches to 0.1% and requires no tipping at all, and the directional
ratio, which matches rim-credit geometry to 0.099%. Both absolute thresholds sit
above the shipped wheel-centre bound (+11.0%, +9.6%), the direction that makes
the shipped bound conservative.

Three defects motivated replacing rather than repairing the old tool:

  - a free joint. The wheels are cylinders and the CoM is 0.32 mm ahead of the
    axle, so the rig rolled forward off down the plane (measured 0.78 m in three
    seconds), moving the contact geometry and the force application point
    throughout the measurement. The new rig has exactly the three DOF a rollover
    test needs: lateral slide, vertical slide, roll hinge.
  - liftoff inferred from contact count, which is a discretisation artefact. Now
    taken from per-wheel contact normal force reaching zero.
  - hand-written inertia (0.42/0.42/0.59) and h_cm + r_w used as a geometric
    height. h_cm is a sprung-equivalent pendulum length, not a height; the
    correct whole-robot CoM height is z_com + r_w = 0.188559 m, 8.2 mm shorter.
    Full inertia about the CoM now comes from the release.

Its docstring also cited a 9.74 mm offset and 5.5% optimism. The whole-robot
offset is 7.2764 mm and the deployed bound is optimistic by 1.07% against the
correctly derived worst direction.

Static only: this is a rollover bifurcation point and says nothing about dynamic
liftoff under transient roll. The four-state model carries no roll state and
that gap is untouched here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AQVjTFXQZu3G2fuVsujV17
Roll-constraint quantities now come from the generated
models/generated/safety/roll_constraint_params.json rather than being computed
from the reduced dynamics parameters. The reduction is a dynamics artifact and
correctly does not carry what a rollover moment balance needs.

  A_TIP        6.998921770 -> 6.924594697   (generated symmetric worst case)
  COM_H        0.196755843 -> 0.188559356   (renamed COM_H_WHOLE)
  static load  96.784 even -> 91.766        (conservative lower, both wheels)

COM_H is renamed rather than redefined so any stale reader raises instead of
silently picking up a different quantity, mirroring the C side dropping
BC_FC_H. h_cm is a sprung-equivalent pendulum length, M_total*z_com/m_sprung,
not a geometric height; using h_cm + r_w as one overstated the moment arm by
8.2 mm, which was conservative and had been masking the missing lateral offset.
Both errors are now gone, and all three former COM_H uses were roll-constraint
contexts, so the whole-robot height is right in every one.

The signed two-sided condition |H*a_y - g*y_cm| + tau_bar/M <= g*W/2 is
recorded in the artifact but NOT shipped. What ships is its symmetric inner
approximation, which needs only |y_cm| and so cannot be broken by a sign
convention error. The measured witness (9625097) and the artifact label the two
directions oppositely -- the witness applies an external force, the artifact
indexes body acceleration, and the inertial reaction reverses the mapping. Both
are correct in their own frame, and that ambiguity is exactly why the signed
form stays staged.

Adds vault/tools/relock.py. Re-pinning by hand does not work: the lock, the
release sidecar and the checkpoint manifest all record overlapping digests, so
patching one leaves another stale and the failure surfaces in a different file
than the one edited. The tool rewrites what release we are pinned to, and only
reports what release each artifact was produced under -- restamping the latter
would assert that a grid solved against the old bound, or a policy trained
against it, is valid under the new one. It currently reports four stale data
artifacts, which is the regeneration backlog.

test_model_release pins the lock digest deliberately; updated to a52fa6da.
16 pass. The one remaining failure is the intended signal that
grid_reachavoid_odd.npz is stale and awaiting regeneration.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AQVjTFXQZu3G2fuVsujV17
roll_constraint_params.json is now schema v2 and states its sign convention
explicitly: +y is robot-left and contains left_body_wheel_link; positive body
lateral acceleration unloads that wheel and tips about right_body_wheel_link.
The convention is derived from the URDF joint origins and asserted in the
controller gate, with a negative test that swaps the mapping and confirms the
verifier rejects it.

That closes a real trap. The artifact's directional labels and the MuJoCo
witness (9625097) map the same two magnitudes oppositely, because the witness
applies an external force at the centre of mass while the artifact indexes body
acceleration, and the inertial reaction reverses which wheel unloads. I
confirmed the witness direction by measuring which wheel loses load rather than
reasoning about it: a +y force leaves 185.16 N on the +y wheel and 9.71 N on
the -y wheel. Both conventions are correct in their own frame. Neither was
written down, and a future signed release reading the artifact had even odds of
installing the optimism backwards while looking right.

No safety-relevant number moved: A_TIP 6.924594697, whole CoM height
0.188559356, conservative static load 91.766054, all unchanged.

Also fixes the checkpoint staleness scan in relock.py, which was flagging
quarantine rules as stale artifacts. Only status="compatible" entries assert a
checkpoint is valid for a release; the rest are filename-pattern rules carrying
no lock of their own.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AQVjTFXQZu3G2fuVsujV17
Re-solved on 542,300 cells x 3 friction slices against the generated roll
constraint: A_TIP 6.998922 -> 6.924595, whole-robot CoM height 0.196756 ->
0.188559, and the conservative static wheel load 96.784 -> 91.766 for both
wheels. 59 minutes, all three slices converged below the 1e-3 threshold.

  mu=0.3   27.29% -> 25.65% safe   (-1.64 pt, -6.0% relative)
  mu=0.6   44.49% -> 43.20%        (-1.29 pt, -2.9%)
  mu=1.0   48.78% -> 47.95%        (-0.83 pt, -1.7%)

The cost is concentrated at low friction, and it is mostly not the roll bound:
A_TIP tightened only 1.06%, while the conservative static load cut available
normal force 5.2% and so directly reduced the ego's friction-clipped recovery
authority in max_u. That clip binds hardest at low mu, which is the gradient
across the three slices. Task 19 tracks recovering part of it now that the
left/right sign convention is released and the true 101.80/91.77 split could be
used instead -- gated on first verifying that f_cert's nl/nr and the
u=[tau_L,tau_R] ordering share the artifact's frame.

Distillation re-run against the new grid: delta 0.027187, over-optimism 0.00%,
coverage 90.79%, gate PASS. The distilled net is conservative by construction,
so 0.00% over-optimism is the property that has to hold rather than a good
result; delta and coverage are what indicate whether it stayed usable, and both
improved. vault/models is gitignored, so that artifact is local; the deployed
networks live in the controller under the lock and follow the robust grids.

The solver writes its own provenance sidecar, so the grid is stamped d54308fa
by generation rather than by hand. Suite is 17/17.

This is the research lane only. The deployed robust grids descend from
firmware/safety_cert/p1_grid_reachavoid.py, which computes its own A_TIP from
h_cm + RW and carries no lateral offset; that lane is being corrected
separately and its grids are not yet regenerated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AQVjTFXQZu3G2fuVsujV17
Regenerated from c593804. At-rest velocity bands stay exactly symmetric
(mu=0.3 +/-3.63, mu=0.6 and 1.0 +/-3.81); nothing in the correction is
fore-aft, so asymmetry there would have indicated a bug. The mu=0.3 band
narrows from +/-3.81, which is where the reduced friction authority shows up.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AQVjTFXQZu3G2fuVsujV17
The controller lock advanced when the deployed chain was regenerated, which
stales this repo's grid sidecar even though nothing the grid depends on
changed -- A_TIP, the whole-robot height, the static wheel load, the ODD
contract, composite_params and the opt6 kernel are all identical. The sidecar
stamps the entire lock, so regenerating unrelated deployed artifacts
invalidates the research grid.

Re-solved rather than re-stamped, because the artifact genuinely was produced
under the older lock. All three slices reproduce to every digit:

  mu=0.3  dV=0.000970815  25.65%  291 iters
  mu=0.6  dV=0.000978553  43.20%  154 iters
  mu=1.0  dV=0.000993025  47.95%  157 iters

That exact reproduction is the argument for narrowing these sidecars to the
inputs that actually determine each artifact: 59 minutes of compute bought a
provenance stamp and nothing else, and it will recur on every release change.
Tracked as a follow-up rather than changed here, since it alters the provenance
model.

Distillation re-run: delta 0.034, over-optimism 0.00%, coverage 89.1%, PASS.
Figure regenerated; at-rest bands stay symmetric.

Suite is 16/17. The remaining failure is
test_default_and_capability_grids_have_distinct_contracts, and it is left
failing on purpose: re-solving the deployed capability grids onto the locked
ODD contract narrowed them from v +-6 / psi_dot +-8 to +-4 / +-4, while the
deployed BC_VCEIL_HI is still 5.5 m/s. Layer-1 can command speeds the
capability grid no longer certifies. That test exists to catch a model-release
update collapsing the capability contract into the ODD contract, it did its
job, and editing it to pass would remove the only thing reporting the
inconsistency.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AQVjTFXQZu3G2fuVsujV17
The split between the reach-avoid mathematics and the four-state model it
reasons about was visible only through git log. CODEOWNERS states it: the
reach-avoid modules are Jaime's (2c4f73d), the upstream avoid-only SAC is
Haimin's, and the certified plant, contracts, grid, filter and distillation are
Robert's (4340e3b, the "4-state balance-safety package"). The rule attached to
it is the point -- model/contract changes get announced because they move
results without moving a line of the mathematics, and math-layer defects get
reported with analysis rather than silently fixed.

CHANGES_TO_THE_MODEL_LAYER.md is written so Jaime can audit our corrections
instead of discovering them:

  - the roll bound was a FRAME MISMATCH, not a wrong number. h_cm is a correct
    height of the SPRUNG body above the AXLE; adding the wheel radius mixes it
    with the whole robot's ground offset. It survived in five places because it
    was silently conservative and because the type docstring never said whose
    centre of mass. 6.998922 -> 6.924595, and the earlier 5.47% optimism figure
    was itself wrong -- 1.07% against the worst turn direction.
  - wheel loads now take the conservative lower value on both wheels, a strict
    tightening pending the sign-convention release.
  - the ODD widened +-4 -> +-6 by operator ruling, with the monotonicity
    argument spelled out: the operator is monotone in g, so everything
    previously certified stays certified. It IS a relaxation of the constraint
    set, and the note says so rather than presenting it as neutral.
  - the structural observation that odd_margin conflates physical failure with
    ODD exit, so the velocity extent of the certificate is a policy artifact
    rather than a dynamic result -- with an offer of a non-breaking
    failure_margin() if that helps the mathematics.
  - the 97 lines we touched in his files, and why none of them are semantic.
  - the entropy-bonus defect, with the beta* = |L|(1-gamma)/gamma analysis,
    recorded as HIS to rule on rather than fixed by us.
  - a "what is NOT established" section: no hardware witness, roll has no
    dynamic state, dh=0.30 is an assumption, grid-node conservatism is not a
    continuum proof, and the robot does not run any of it.

Also adds the (v, psi_dot) row to the safe-set figure. That slice was always in
the 4-D value function and simply was not plotted, so the turn budget at speed
had to be argued rather than read. It is the same object as the lean budget,
sliced on the other pair of axes -- at theta=thetadot=0 the certified yaw tracks
the |v psi_dot| <= a_tip hyperbola about 12-15% inside it, and friction barely
matters above v=1 because the limit is geometric.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AQVjTFXQZu3G2fuVsujV17
The committed grid had been solved under the previous release -- before the roll
bound was corrected and before the +-6 ruling -- so relock quarantined it and every
model-release test failed on its sidecar. That quarantine is the intended
behaviour: restamping would assert that a grid solved against the old bound is
valid under the new one.

Re-solved on the widened contract, 65x29x17x25 = 801,125 cells (velocity axis 44 ->
65 nodes, picked up from the contract with no code change). All three friction
slices converged to dV < 1e-3:

    mu=0.3   327 iters   safe-set 21.7%
    mu=0.6   142 iters   safe-set 36.7%
    mu=1.0   151 iters   safe-set 40.4%

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AQVjTFXQZu3G2fuVsujV17
Lock f56030685ad79b16..., byte-identical to the controller's. model_release.py
refuses to load otherwise, which is what caught every intermediate state tonight.

Test expectations updated to the ruling: V_ODD and DOMAIN_V to +-6 (yaw unchanged
at +-4), AXES_FULL velocity to 65 nodes, environment observation space to 6.0
(env.py builds it from max(abs(DOMAIN_V)), so it widened on its own).

test_default_and_capability_grids_have_distinct_contracts is strengthened rather
than merely updated. It previously pinned the capability bound as a literal; it now
also asserts the capability window strictly CONTAINS the ODD envelope on both
sides. That is the property it exists to defend -- if the two ever coincide, the
capability window has collapsed into the ODD contract, which is the regression it
originally caught -- and it no longer needs editing every time either bound moves.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AQVjTFXQZu3G2fuVsujV17
The manifest quarantines `reach_avoid_safety_sac*` as pre-v2.2. That pattern also
matched a checkpoint trained ON v2.2 under the current lock, so the evaluation
tooling could not load it.

verify_checkpoint short-circuits on the first matching pattern, so the designed way
through is an explicit `compatible` entry -- which PINS the artifact rather than
loosening the gate: it carries the current lock f5603068..., the artifact sha256
ba482bc2..., and the model hashes. A more specific pattern is inserted ahead of the
broad quarantine, which remains in force for everything else.

Trained 2026-07-29, 300k steps, --saturate-target, on the v2.2 release.

MARKED EVALUATION ONLY in the entry's reason field. Its critic still carries the
max-entropy term inside the DRABE backup inherited from safety_sb3, which is not
part of the reach-avoid operator. Measured on this checkpoint: alpha auto-tuned to
1.4e-4 and beta = alpha*(-log pi) came out NEGATIVE in 99.3% of sampled states, so
the contamination ran conservative here -- but the sign is an emergent property of
where the policy converged, not a guarantee. This is not a certified safety value.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AQVjTFXQZu3G2fuVsujV17
…terised

Appended, not rewritten. Nothing in the original was false; it was written before
the governor fix and before a literature review aimed at its own section 7, so it
was incomplete. Rewriting the one document whose purpose is being auditable would
risk introducing errors into it.

Adds:
  8. His code runs on v2.2 unchanged -- 300k steps trained with no edits,
     test_drabe_operator 7/7. config.py reads the contract directly, so the
     corrected a_tip and the +-6 ruling reached his trainer by construction.
  9. The governor no longer folds to (|v|,|psi_dot|). Measured 316 permissive and
     510 conservative errors at reserve 0.35.
 10. Four limitations, sharper than section 7 had them:
     (a) early stopping is ANTI-conservative -- V_k >= V_inf, and undiscounted VI
         admits no residual bound. Not yet applied; it changes every grid.
     (b) corner min_d is optimistic and second-order; it also breaks a convexity
         assumption of the level-set theorems (Hirsch et al. 2026).
     (c) the roll constraint's real exposure is CROSS-SLOPE, not transients: 11.3%
         at 2.9 deg, sustained. And I_roll is a placeholder, so every roll-margin
         number rests on an unidentified inertia.
     (d) the inter-sample gap, which we had never named.
     Plus one point in our favour: multilinear interpolation is an averager, so the
     scheme IS monotone and converges to the viscosity solution. Monotone is not
     conservative; both are true and they are different claims.
 11. The entropy term, measured rather than argued. His lab's own ISAACS code
     already excludes it from the safety critic (base_block.py:406). On our
     checkpoint alpha tuned to 1.4e-4 and beta came out NEGATIVE in 99.3% of
     states, so it ran conservative -- but that is the sign being lucky, not a
     guarantee. Still his to fix; untouched.

Verified: no file owned by @jfisac in CODEOWNERS has been modified across any of
the 10 unpushed commits in this repo.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AQVjTFXQZu3G2fuVsujV17
rshi159 and others added 5 commits July 29, 2026 12:14
12.1 Two certificate preconditions we satisfy BY ACCIDENT. The operator is not a
     contraction (his own ICRA 2019 says so; Akametalu et al. exhibit spurious
     constant fixed points in a structurally identical operator). Finite control set
     and V_0 = g close the gap, and our solvers satisfy both by construction rather
     than by stated intent. Recorded because either would be silently destroyed by
     an obvious optimisation -- particularly warm-starting VI from a converged or
     learned V, which is exactly what someone proposes to make a 25-hour re-solve
     cheaper.

12.2 A candidate terminal set Omega with its provenance AND its disqualifiers: it is
     avoid-only, so it certifies "never fails", not "reaches a stop". Raised because
     if Omega comes from the learned critic instead, the entropy contamination is
     relocated behind H steps of simulation rather than removed. Flagging the trap,
     not making the call.

12.3 config.py reduces theta_failure and yaw_rate to bounds[1], discarding bounds[0];
     velocity keeps both. No numerical error today since all three are symmetric --
     but the contract can EXPRESS an asymmetry the consumer cannot CARRY, and v2.2's
     plant is genuinely asymmetric (y_cm != 0 is what made the governor fold unsound),
     so an asymmetric yaw bound is a plausible future ruling that would be silently
     half-applied. Not fixed: promoting these to intervals changes every consumer and
     the right shape is a filter-architecture question.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AQVjTFXQZu3G2fuVsujV17
… the evidence

The section said undiscounted VI "admits no residual bound" and then proposed
iterating to the exact fixed point as the fix. The first half was right; the second
was not achievable, and in the solver we had briefly acted on a bound that does not
exist.

Now carries the measurement: iterations per decade of dV climbing 63 -> 759 -> 2,164
-> 2,854 -> 3,153 on the full laden mu=0.3 grid, i.e. the rate degrades monotonically
and there is no geometric tail to sum. States plainly that we tried to close it by
tightening, that it cannot be closed that way, and that the 154*dV bound we briefly
believed was wrong -- because the error is instructive and he should see it rather
than inherit a clean-looking claim.

Consequence stated honestly: the shipped safe sets are supersets of the discrete fixed
point by an amount we cannot quantify. Closing it needs verification of the shipped V
as a candidate invariant, not more solving.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AQVjTFXQZu3G2fuVsujV17
…p scaling

Clears the doc/label items from the agent-owned remediation list.

(d1) mujoco_plant.py claimed "tau_L>tau_R drives +psi_dot". It does not: measured
     psi_ddot = -3.633 at u=[1,-1]. A left-torque surplus yaws NEGATIVE. The reduced
     model and the plant agreed all along -- only the comment was inverted, and
     nothing tested it. Added test_yaw_sign_convention, asserting the SIGN and the
     mirror property rather than a magnitude, so it survives regeneration.

(d2) grid.py is named "reach-avoid" and is avoid-only: min(g, max_u min_d V) with no
     target margin l(x). Documented at the top, with what the distinction costs --
     ~42% of the (v, theta) plane safe-but-never-arriving on the same plant.

(d5) f_cert.py's `n_tot / (MASS*GRAV)` scaling is identically 1.0, since wheel_loads
     sets n_tot = MASS*GRAV. A no-op that reads as load-dependent. Annotated rather
     than deleted -- a genuine normal-load dependence belongs there if the total ever
     stops being static.

(e)  Documented as section 13 rather than fixed: balance_plant.xml places the sprung
     CoM at y = 0, dropping the 7.276 mm lateral offset. A validation plant with
     y_cm = 0 is a symmetric robot validating an asymmetric model -- it cannot
     reproduce the governor fold, the A_TIP asymmetry, or the upright yaw->pitch
     coupling, and will agree most convincingly where the asymmetry matters least.

     Explicitly NOT confirmed: the "roll inertia 25.9% understated" claim. The plant
     body is the SPRUNG body (17.411307 kg); the generated tensor is WHOLE-ROBOT
     (19.731467 kg). Comparing them is the same frame error this whole effort started
     with, so no percentage is asserted. Confirming it needs a sprung-body tensor the
     artifact does not separate.

SSB suite 18/18.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AQVjTFXQZu3G2fuVsujV17
… turn authority

Two-page summary in the style of the existing safe-set comparison figure, committed
per repo convention as figure + generator + source data so it cannot drift from the
artifacts it summarises.

Page 1: the avoid-only grid certificate (reduced 4-state f_cert, corrected a_tip)
beside reach-avoid MuJoCo rollouts of the evaluation-only v2.2 checkpoint (61.7%
reached / 17.3% limbo / 21.0% failed on the full +-6 ODD). The caveat block states
what a reader must know before comparing with the original 3-panel figure: domains
differ, 300k steps is not converged, the critic carries the entropy term, and the
two panels are DIFFERENT PLANTS by design -- green-left means "the reduced model
certifies it", green-right means "one MuJoCo rollout reached the target".

Page 2: the (v, psi_dot) turn-authority plane, unladen/laden x ice/grip, with the
analytic roll hyperbola |psi_dot| = a_tip/|v| overlaid. The certified edge hugs the
hyperbola wherever roll binds -- the corrected bound demonstrably shaping the set --
and the laden tightening visibly costs turning, not straight-line speed, with
numeric max-|psi_dot| readouts at v = 2/4/6 under each panel.

Generator reads the committed slice JSON and the pinned release grids; regenerated
output verified identical in content.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AQVjTFXQZu3G2fuVsujV17
…ick-up mechanics

The safety-theory vs filter-architecture split with responsibility assignment lived
only in conversation; a handoff that exists in chat is not a handoff. Now committed:

14.1 the ownership rule with the two boundary cases resolved this week (the governor
     fold: architecture component, plant-assumption failure, ours; the exact-min_d
     attempt: solver math, reverted, yours) so the precedent is visible.
14.2 all eleven theory rows (T1-T11) with status and evidence pointers -- including
     the two where our attempted fix failed and the reasoning is preserved.
14.3 nine architecture rows (A1-A9) with owner and status; A2 (monitor choice)
     flagged as highest leverage since it decides whether T10 is deployed at all.
14.4 mechanics: envs, the full verification battery with the false-green trap
     called out, checkpoint authorization state, retrain/slice commands, deferred
     re-solves, figure regeneration, and the five operational traps we hit.
14.5 what "satisfactory" means, stated plainly: pipeline validation, not converged
     benchmark; no continuum claim; no hardware witness beyond mass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AQVjTFXQZu3G2fuVsujV17
@rshi159 rshi159 closed this Jul 29, 2026
@rshi159
rshi159 deleted the feature/v2.2-controller-model-consumer branch July 29, 2026 23:50
… in it

Discovered 2026-07-29: the repo is PUBLIC (isPrivate=false) while carrying the
robot's mass properties, geometry, and certified bounds in docs, tests, tools, and
the committed controller lock. The offending branch was deleted from the remote and
PR #2 closed; this commit sanitizes the tree so a clean branch can replace it.

The architecture was already right -- model_release.py reads everything from the
PRIVATE vault-controller sibling at runtime. The violations were literals and
copies. All are now removed:

* vault/data/MODEL_INPUTS.lock.json (the controller lock: 35+ model floats) is GONE.
  Replaced by controller_lock.sha256 -- a digest pin. model_release.py verifies the
  sibling lock by sha256 instead of byte-compare: the SAME byte-identical guarantee,
  zero content. relock.py writes the pin.
* CHANGES_TO_THE_MODEL_LAYER.md, all three PDFs, the figure generator, and the
  evaluation slice moved to vault-controller/docs/ssb_handoff/ (private). A
  sanitized stub points there.
* grid_reachavoid_odd.npz untracked and gitignored -- its metadata embeds the roll
  constraint parameters, and V itself is model-derived. Regenerable from the sibling.
* test_model_release.py: every pinned model value (masses, C_THETA, wheel geometry,
  a dynamics response vector, ODD bounds, grid axes) replaced with CONSISTENCY
  asserts against the private release read at runtime. Absolute-value pins belong in
  vault-controller's own private suite. The dynamics regression becomes structural
  (shape, finiteness, theta_dot passthrough at f32 tolerance).
* mujoco_model.py: absolute linkage geometry stripped from prose; the leg placement
  is a labelled dimensionless visual approximation, not a released dimension.
* measure_rollover_bilateral.py: wheel geometry read from the release; measured
  thresholds moved to the private handoff doc.
* response magnitudes stripped from the yaw-sign prose (sign stays pinned).

Verified: git grep across tracked files for every known model constant -> zero hits.
Suite: 18/18 against the sibling release.

NOTE: this sanitizes the TREE. The branch history (local) and the older public
branches (vault-dev, vault-dev-reachavoid) still contain model data -- the former is
handled by cutting a clean branch, the latter needs an admin conversation with the
lab (repo owner).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AQVjTFXQZu3G2fuVsujV17
@rshi159 rshi159 reopened this Jul 30, 2026
@rshi159

rshi159 commented Jul 30, 2026

Copy link
Copy Markdown
Author

Reopening. The closure was my over-calibration: the model values in this branch are a repo-cleanliness matter, not sensitive material (operator ruling). The branch now additionally carries the cleanliness improvements that came out of that review — the controller lock is no longer copied into this repo (replaced by a sha256 pin, same byte-identical guarantee), tests assert consistency against the private sibling at runtime instead of pinning values, and the model-layer documents live in vault-controller with a pointer stub here. Handoff contents unchanged; see the PR description.

rshi159 and others added 15 commits July 29, 2026 20:42
…sibling

build_mjcf(visual_meshes="auto") dresses the validation plant in the real robot's
meshes when vault-controller is checked out, and falls back to the primitive look
when it is not. Nothing is copied and nothing is committed: mesh paths, link names,
and placements are parsed at runtime from the private articulated URDF (whose
base_link frame IS the chassis/axle frame -- wheel joints at (0, +-d, 0), identity
rotations), and the decimated mesh set is used because the highres STLs exceed
MuJoCo's decoder limits. This PUBLIC file carries no dimensions.

Visual only, by construction and by test: mesh geoms are contype=0 conaffinity=0
group=2; primitives move to display group 3 (hidden by default) when the overlay is
active; every body keeps its explicit <inertial>. test_visual_meshes_do_not_change_
dynamics steps identical controls 500 steps and requires EXACT qpos/qvel equality
between overlay-on and overlay-off.

Suite: 19/19.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AQVjTFXQZu3G2fuVsujV17
The first overlay attached base_link and the two body wheels, which reads as the
bare drive assembly. The robot visibly has legs.

The reduced plant has no leg joints and the release defines no tucked-angle
artifact, so leg linkages, knee wheels and feet are posed by FORWARD KINEMATICS AT
THE ARTICULATED URDF'S ZERO CONFIGURATION -- pure composition of joint origins, no
joint-angle source needed -- and fixed to the chassis body. The body-wheel meshes
stay on the spinning wheel bodies. Everything is still parsed from the private
sibling at runtime; this public file carries no dimensions, and unknown topology or
missing meshes degrade silently to the primitive plant.

Visual-only invariant unchanged and still pinned by the 500-step exact-equality
test. Suite 19/19.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AQVjTFXQZu3G2fuVsujV17
Knees at 180 deg -- the operator-provided joint constant, matching the retract
lane's knee hold -- with all other joints at zero. The FK now carries a per-joint
display angle (Rodrigues rotation about the URDF joint axis composed after the
origin rotation), so any future display pose is a dictionary entry, not new code.

Visual pose only; the invariance test still pins bit-identical dynamics. 19/19.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AQVjTFXQZu3G2fuVsujV17
… the broken case

Operator caught this from one render: "feet are correctly placed, lower leg link is
not -- this is troubling because it broke kinematic structure." That symptom is
impossible in a consistent chain, and the impossibility was the clue.

The conversion used w = sqrt(1+trace)/2 and short-circuited w < 1e-9 to the identity
quaternion. w is EXACTLY ZERO for any rotation by pi -- which is the tucked knee fold
itself. So every pi-rotated link was emitted with correct POSITION and identity
ORIENTATION: the foot (near-symmetric mesh, correct translation) looked right, the
long lower leg pointed the wrong way, and the chain appeared broken while the FK was
in fact consistent.

Replaced with the standard largest-diagonal-component branch, well-conditioned
everywhere. Verified: the emitted lower-leg quat is now (0,0,1,0), whose
reconstruction gives diag(-1,1,-1) = exactly Ry(pi).

Suite 19/19; invariance test unaffected (orientation of contact-exempt geoms never
touched dynamics -- this was visual-only in both directions).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AQVjTFXQZu3G2fuVsujV17
teleop.py -- the joystick example -- builds MujocoPlant(contact_geometry=True), and
the group-3 tagging had only been applied to the non-contact branch, so the teleop
viewer would have drawn the chassis box and leg capsules over the meshes. Both
branches now tag primitives with the hide group when the overlay is active. Group
is display-only; the collision geoms keep colliding exactly as before, which the
500-step invariance test continues to pin. 19/19.

Teleop-config plant verified: 17 geoms, 11 meshes, 5 primitives hidden.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AQVjTFXQZu3G2fuVsujV17
…s dead

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AQVjTFXQZu3G2fuVsujV17
… review

An independent review confirmed the overlay's math (FK composition, the
largest-component quaternion conversion validated over 10k rotations incl.
exact pi, dynamics neutrality) and found five containment defects, all fixed
here. None changed what renders for the current release assets.

1. All <visual> elements per link are now read (was: first only), each with
   its own MJCF asset so per-visual mesh scale attributes are honored.
2. Malformed joints degrade instead of silently rendering wrong: a present
   <axis> without xyz, a zero axis on a rotated tucked joint, a non-revolute
   tucked joint, duplicate parents, and kinematic cycles are all rejected
   (absent <axis> still defaults to +X per URDF).
3. The wheel-attachment frame invariant is now verified, not trusted: the
   URDF wheel joint frames must match the plant's generated wheel bodies
   (identity rotation at (0, -+wheel_sep/2, 0)) or the overlay degrades.
4. Failure containment is complete: silent None is reserved for 'sibling not
   checked out'; any malformed content once the checkout is found warns with
   the reason and falls back to primitives. Mesh files are checked up front
   (STL/OBJ/MSH only; binary-STL size vs triangle count; ASCII header), and
   the URDF is verified against the controller release lock so a dirty or
   stale visual source cannot ride alongside hash-validated dynamics params.
   (The decimated meshes are not pinned by the lock today -- documented.)
5. The dynamics-equivalence test can no longer pass vacuously: it skips when
   the sibling is absent and otherwise asserts the overlay actually loaded
   mesh assets before comparing trajectories.

New test pins the degrade-with-warning contract end to end. 21/21.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AQVjTFXQZu3G2fuVsujV17
The controller release added a lock-pinned knee-wheel geometry scalar; every
model hash this repo consumes (composite params, opt6 kernel, grids, contract)
is byte-identical -- the lock change is purely additive. relock.py re-pinned
the digest, sidecar, and manifest.

grid_reachavoid_odd.npz was re-attested through write_external_artifact_sidecar
(artifact bytes verified unchanged, model_hashes identical); the checkpoint
manifest entry is deliberately LEFT recording the previous lock -- re-blessing
a trained policy is an operator/Jaime decision, and the checkpoint is
evaluation-only regardless. The three previously-stale eval/residual sidecars
remain stale, as before.

21/21.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AQVjTFXQZu3G2fuVsujV17
…teleop

Regression I introduced with the knee-wheel lock increment: verify_checkpoint
required entry['lock_sha256'] == release lock, so re-pinning to a9595e99
quarantined reach_avoid_safety_sac_v22 (trained under f5603068). teleop's
_dry_run loads a policy unconditionally, so the joystick demo could not start at
all -- it died in load_safety_sac before the control loop.

The wrong fix is restamping lock_sha256: that field records the release a policy
was TRAINED under, and rewriting it would assert training against a bound the
policy never saw (relock.py documents it as never-rewritten for exactly that
reason). Instead a checkpoint may now carry
also_valid_under_lock_sha256: {digest: reason} -- an explicit allowlist of later
releases, each REQUIRING a written justification; a bare or empty reason still
fails closed. model_hashes remains the real gate and is unchanged.

Justified here because the delta is provably physics-free: exactly three keys
differ (added knee_wheel_geometry.json, the source manifest hash covering it, and
the re-pinned generator commit a39b85a1 -> 7e32bf93 which reproduced every
artifact byte-identically). composite_params_sha256 and opt6_kernel_sha256 are
untouched. Evaluation-only status still applies.

relock.py no longer reports a justified allowlist as regeneration backlog, so the
STALE list keeps meaning something (3 eval artifacts, as before).

Verified: teleop --dry-run 150 completes, margins positive throughout
(worst g_odd 0.8440, g_contact 0.9916). 22/22.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AQVjTFXQZu3G2fuVsujV17
…fication

50d1c49's message reports teleop --dry-run completing with 'margins positive
throughout'. That validated the control-loop WIRING only and reads as validation
of the demo, which it is not: the run substituted reach_avoid_safety_sac_v22 as
the monitor, whose reach-avoid value semantics do not match ValueMonitor's
avoid-only reading, and the margins stayed positive largely because the robot
barely moves in a 1.5 s window (unfiltered, the same command reaches only about
0.05 m/s). The '100% override' figure is an artifact of that mismatch and must
not be cited as evidence either way. Shared history is not rewritten; the
correction lives here where a reviewer of this PR will see it.

Also records that the joystick demo has no v2.2-valid monitor at all (the default
contact_safety_sac_v3 is correctly and pre-existingly quarantined), that fixing it
is an authorized-only retraining decision, and that the firmware-side missing opt6
MVE entries do not affect this repo (only the scalar entry is bound here).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AQVjTFXQZu3G2fuVsujV17
Purely additive plus a behaviourally-identical soft-float fix; every model hash this
repo consumes is byte-identical (composite_params, opt6 kernel, grids, contract).
Eval-grid sidecar re-attested with artifact bytes verified unchanged; checkpoint
training provenance left recording f5603068 with the new release justified in
also_valid_under_lock_sha256. 22/22.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AQVjTFXQZu3G2fuVsujV17
I re-locked the controller without re-pinning here; the byte-equality gate caught it
and failed closed, which is the intended behaviour. Model hashes unchanged
(composite_params, opt6 kernel, grids, contract). Grid sidecar re-attested with
artifact bytes verified unchanged. 22/22.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AQVjTFXQZu3G2fuVsujV17
Model hashes unchanged; grid sidecar re-attested with bytes verified unchanged. 22/22.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AQVjTFXQZu3G2fuVsujV17
Model hashes unchanged; grid sidecar re-attested with bytes verified unchanged. 22/22.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AQVjTFXQZu3G2fuVsujV17
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