Skip to content

fix: close ten v0.6.0 triage issues — flash MRAM guard, Zephyr executor parity, the emit-site gate, and two host-dependent parity suites - #331

Closed
alpCaner wants to merge 12 commits into
devfrom
fix/v06-triage-batch
Closed

fix: close ten v0.6.0 triage issues — flash MRAM guard, Zephyr executor parity, the emit-site gate, and two host-dependent parity suites#331
alpCaner wants to merge 12 commits into
devfrom
fix/v06-triage-batch

Conversation

@alpCaner

@alpCaner alpCaner commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Ten issues from the v0.6.0 milestone triage, each fixed, reviewed and verified against the current tree rather than against its own issue text.

Fixed

#224 — the emit-site gate could not see codes built by a prefixing helper. release-blocker. The Rust gate (crates/tan-cli/tests/contract.rs:735) was fixed but crates/ ships to nobody, so on the shipping surface no source-to-registry scan existed at all. New python/tests/gates/test_every_issue_code_is_registered.py catches literal emit sites and f-string assembly through a prefixing helper (bootstrap_cmd.py, debug_config_cmd.py, doctor_cmd.py). It found 146 unregistered issue codes, all now registered reserved in contract/issue-codes.json. The gate carries a self-test proving it rejects a deliberately unregistered code — a gate that cannot fail is not a gate (tan-cli#275).

#309 — an os: zephyr slice that never loaded Zephyr was reported ok. A CMakeLists.txt that never calls find_package(Zephyr ...) still configures and links under west build, so exit code 0 alone was letting a plain host binary pass as firmware. Ports the oracle's zephyr_boilerplate_loaded / dir_shows_zephyr into python/tan/commands/build/manifest.py, applied in execute.py's status assembly. Handles the --sysbuild case, where only the nested per-image build carries the signal.

#308 — a stale ambient $ZEPHYR_BASE beat the workspace tan had just resolved. build_cmd.py passed gap_fillers=() with a literal # NOT YET PORTED. New python/tan/core/zephyr_env.py derives ZEPHYR_BASE / EXTRA_ZEPHYR_MODULES from the resolved west_workspace_dir. EXTRA_ZEPHYR_MODULES keeps its ; separator on every platform via the existing sep_for_key, and an inherited PYTHONPATH is still extended rather than replaced.

#311 — an ELF artefact could have its headers written into on-die MRAM. plan_alif_mram_jlink emitted loadbin unconditionally, with no is_raw_bin guard, unlike its sibling plan_swd_probe. Now raises FlashPlanError naming the artefact and pointing at zephyr.bin. Deliberately a refusal, not a loadfile fallback — that would silently ignore slot0_load_address. Latent today (no in-tree board.yaml sets that field), fixed before it isn't.

#312 — the DPIDR preflight blamed the wiring for a probe that was still re-enumerating. A banner carrying a different DP ID and a banner carrying none at all both got "Check the probe selection (flash_args.jlink_serial) and the wiring." Now distinguished; the second advises a retry. Refusal behaviour is unchanged — both still refuse to write MRAM.

#318 — a required check was red on the release branch. ensure_venv_reuses_a_venv_with_usable_pip wrote its shim inline and spawned it; under cargo test's parallel threads a sibling fork could hold the write fd, making the spawn fail ETXTBSY. Adds wait_until_spawnable at both inline shim sites, exactly as 204c3a0 did for exit_code_shim. Measured 20/20 green against a 1-in-20 pre-fix failure rate.

#313 and #324 — frozen parity fixtures encoded the capture host's tool inventory. Eight cases branch on a which() probe, so their frozen answers recorded which tools the capture host had. Three yocto_wic cases failed wherever neither bmaptool nor dd existed; three west_forward cases failed wherever west did. Neither was a port gap — under a sanitised environment the oracle and the port emit byte-identical envelopes. The replay now pins the Python side's PATH to the recorded inventory. PROVENANCE.txt documents the inventory and the amended re-capture recipe, because the original recipe now hard-errors for these eight by design.

#213parity.yml runs were not correlatable by alp-sdk. Adds run-name surfacing client_payload.sdk_ref to the Actions API display_title. The non-dispatch arm is an empty string so GitHub's default title still renders on push and pull_request runs.

Also closed as already-fixed, with evidence on each: #180, #179, #139.

Not fixed, and why

#297 stays open. Its diagnosis — that an ambient ZEPHYR_BASE makes test_west_resolved_reproduces_and_closes_tan_cli_123 flaky — is wrong. find_workspace_venv's upward .venv walk (python/tan/core/venv.py:66-72) resolves before the ZEPHYR_BASE read at :74 is ever reached, and the test plants its .venv at the subprocess's own cwd. Measured with a hostile ZEPHYR_BASE: 5 passed, 128 deselected, identical to the unset baseline. The original Windows failure remains unreproduced on Linux and needs a fresh Windows-side repro.

A genuine ZEPHYR_BASE leak did turn up elsewhere and is fixed here: conftest.py's autouse _scrub_sdk_discovery_env now scrubs it, and test_native_sim_e2e.py reads REAL_ENVIRON instead of os.environ. That is not #297's cause and is not claimed as one.

The crates/ change

#318 touches the frozen tree. That is deliberate and narrow: the test is a required check (test (ubuntu-latest)) that is red today while 0.5.0-rc3 is being promoted, the change is test-hermeticity with zero production delta, and 204c3a0 set the precedent in the same file for the same root cause. The race does not port — cargo test runs tests in parallel threads, while the Python suite is serial (no xdist, no addopts beyond pythonpath), so there is no concurrent-fork window on the Python side and no counterpart fix is needed.

Filed while triaging

Gates

  • python -m pytest tests -q from python/ — see the run in the PR checks; zero failures is the bar, not a count
  • cargo fmt --all --check — exit 0, no output
  • cargo clippy --all-targets --workspace -- -D warnings — exit 0, no warnings
  • cargo build --all-targets — clean
  • cargo test --locked — exit 0, 1145 passed / 0 failed across all 11 test binaries, frozen_issue_codes green against the 146 new registry entries

Note for the release

contract/issue-codes.json gains 146 reserved entries and zero removals. It ships as a release asset, and this repo writes CHANGELOG sections at release time rather than per PR, so the next release notes should name it.

Closes #224
Closes #308
Closes #309
Closes #311
Closes #312
Closes #313
Closes #318
Closes #324
Closes #213

The two ensure_venv_* tests write their stand-in interpreter with
std::fs::write and let ensure_venv spawn it immediately. cargo test runs
tests in parallel threads, so a sibling test's fork can still hold a write
fd on the just-written file and the spawn fails ETXTBSY. ensure_venv fails
CLOSED on a probe error and recreates the venv, so the assertion that the
venv was REUSED then panics -- a required check (test (ubuntu-latest)) going
red with no code change, at a measured 1 in 20.

wait_until_spawnable already exists in this file for exactly this reason:
204c3a0 added it to exit_code_shim to cure the same race for a sibling test,
but did not touch these two, which still write their shim inline.

crates/ is frozen for feature work; this is the deliberate test-hermeticity
exception 204c3a0 set the precedent for. Zero production delta.

Measured post-fix: 20/20 green over a full-binary loop.
…sal causes (#311, #312)

plan_alif_mram_jlink emitted `loadbin <artefact> <app_address>` uncondi-
tionally, with no is_raw_bin guard -- unlike its sibling plan_swd_probe,
which branches loadbin-for-.bin vs loadfile-for-ELF. A slot0-linked artefact
that is not a raw .bin would have had its ELF headers written into on-die
MRAM at the load address.

It now raises FlashPlanError naming the artefact and pointing at zephyr.bin.
Deliberately a refusal rather than plan_swd_probe's loadfile fallback: a
loadfile here would silently ignore slot0_load_address, which is the worse
failure. Latent today -- no in-tree board.yaml sets slot0_load_address.

Separately, the Flow D DPIDR preflight conflated two causes. A banner
reporting a DIFFERENT DP ID (a real wrong-board or probe-selection problem)
and a banner reporting no DP ID at all (connect failed outright, typically
the J-Link still re-enumerating after a prior JLinkExe close) both got:

  Check the probe selection (flash_args.jlink_serial) and the wiring.

The second now says the probe reported no DP ID at all and advises a retry.
Detection is conservative and falls back to the existing generic message
when it cannot tell -- a widened refusal that claimed the wiring was fine
would be worse than the ambiguity. Both cases still refuse to write MRAM;
this is a diagnostic split only.

Neither backend has a v0.4.1 oracle counterpart -- alif_mram_jlink is new --
so there is no envelope to diff against here.
…at never loaded Zephyr (#308, #309)

Two oracle-parity divergences in the executor, where the v0.4.1 oracle does
the right thing and the port did not.

#308: build_cmd.py passed gap_fillers=() with a literal '# NOT YET PORTED',
and execute.py starts from dict(os.environ). ADR-0020 plans never carry
ZEPHYR_BASE, so a stale ambient $ZEPHYR_BASE in the user's shell beat the
west workspace tan had itself just resolved. The oracle fills it via
zephyr_env_overrides; the resolved value already existed on this side as
west_workspace_dir. Ported as pure logic into python/tan/core/zephyr_env.py
rather than into the command module.

EXTRA_ZEPHYR_MODULES joins with ';' on every platform -- it is a CMake list
that Zephyr's own zephyr_module.py splits on ';' regardless of host OS, and
joining it with ':' on Linux breaks west build configure with 'is not a
valid zephyr module'. The values flow through the existing sep_for_key
machinery rather than re-implementing the join, and assemble_slice_env's
seed-then-append still extends an inherited PYTHONPATH instead of replacing
it. Both are covered by tests.

#309: a CMakeLists.txt that never calls find_package(Zephyr ...) still
configures and links fine under `west build -b <board>` -- CMake only emits
a dev warning about the missing project() call -- so a real exit code 0 was
not sufficient evidence and a core declared `os: zephyr` could produce a
plain host binary and be reported [+] ok. The board name is never even
validated, because nothing loaded the code that would validate it.

zephyr_boilerplate_loaded / dir_shows_zephyr are ported into build/
manifest.py and applied in execute.py's status assembly, checked only on an
otherwise-successful slice and skipped when the slice redirects west's build
dir, where the evidence lives somewhere this cannot see. It looks one level
below build/ as well, because --sysbuild nests the real per-image Zephyr
builds under its own superbuild and only the nested build carries a signal.

The guard exposed 13 tests whose plan fixtures declared backend zephyr while
dispatching a trivial stand-in tool that produces no Zephyr evidence. Twelve
are relabelled to a non-zephyr backend after checking per test that no
assertion touched zephyr artefact resolution; the one test genuinely about
west's own build-dir resolution instead has its stand-in west script create
a real zephyr/ directory, keeping it on the live code path.

The separate question of whether `tan init --template minimal-app` should
emit a different CMake shape is a maintainer decision and is untouched here;
the scaffold is byte-identical to the oracle's.
The Rust gate for this landed in 78d8308 (contract.rs's
every_prefixed_issue_code_is_registered, with PREFIXING_SITES), but crates/
ships to nobody -- the release assets are PyInstaller freezes of python/ --
so on the shipping surface there was no source-to-registry scan at all,
prefixed or otherwise.

The shape the Rust gate exists to catch is already live here: bootstrap_cmd
builds f"bootstrap.{code}", debug_config_cmd builds f"debug-config.{code}",
doctor_cmd does the same. A literal-only scan sees none of them.

The new gate walks emit sites for both literal codes and f-string assembly
through a prefixing helper, reading the registry through the same source
test_frozen_issue_codes.py already uses rather than inventing a second one.
It carries a self-test proving it REJECTS a deliberately unregistered code:
a gate that cannot fail is not a gate, which this repo learned for real in
tan-cli#275 when a scrubbed env var made one skip silently for eleven
commits. Where it must skip, it skips loudly and names what was missing.

Run against the tree it found 146 unregistered codes, including
cli.command-deferred. All are registered `reserved` in contract/
issue-codes.json -- 146 additions, zero removals, zero modifications.
cargo test --locked stays green, frozen_issue_codes included.

contract/issue-codes.json ships as a release asset; the next release notes
should name these entries.
…nventory (#313, #324)

Eight parity cases branch on a which() probe, so their frozen answers record
which tools the CAPTURE host had -- and the Python side is then compared
live against the REPLAY host. The two sides were not measuring the same
environment, so the comparison measured the host rather than the port.

#313: three yocto_wic cases froze 'would run dd if=...', because the capture
host had dd and no bmaptool. plan_yocto_wic prefers bmaptool when planning
only and dd is absent, so on a host with neither the live Python side picked
bmaptool and the three failed. Green wherever dd exists, hence green in CI.

#324: three west_forward cases froze the 'west not found on PATH' launch
error, because the capture host had no west. On a host that HAS west the
live Python side genuinely launches it and reports the child's failure
instead. Same bug, opposite direction.

Neither is a port gap. Run both binaries under a sanitised environment and
they agree byte for byte -- same command, same exitCode, same data, same
issue code, same message. The port already carries the identical check and
the identical string.

Replay now pins the Python side's PATH to the recorded inventory. Because
that side runs as a subprocess, an in-process monkeypatch cannot reach it,
so the pin crosses the process boundary as a scratch PATH. #324 reuses the
mechanism #313 introduced rather than adding a second one, generalised to
the absent-tool case as oracle.empty_tool_inventory.

Under TAN_PARITY_LIVE=1 both sides genuinely spawn, and compare() REFUSES a
python_env_overrides rather than silently comparing one pinned side against
one unpinned side -- which would be a fresh divergence, not a capture.

PROVENANCE.txt now records the capture host's inventory and the amended
re-capture recipe, because the recipe at the top of that file hard-errors
for these eight by design. Capturing on a host with west installed and the
pin dropped would freeze an answer that only replays on hosts that also have
west, reintroducing exactly the bug #324 closed.
…in the native-sim e2e

The autouse _scrub_sdk_discovery_env fixture isolated ALP_SDK_ROOT,
SOURCE_DATE_EPOCH, HOME and USERPROFILE but left ZEPHYR_BASE inherited from
the developer's shell, so one more discovery input could differ between a
local run and CI. It is scrubbed now.

test_native_sim_e2e's _zephyr_base() read os.environ directly, which would
have defeated that scrub; it reads REAL_ENVIRON instead, matching the idiom
the same file already uses for the real west build subprocess env.

Verified with and without a hostile ZEPHYR_BASE exported: byte-identical
counts either way across the doctor, kconfig and native-sim suites.

This is NOT a fix for #297, which stays open. That issue's diagnosis -- that
an ambient ZEPHYR_BASE makes test_west_resolved_reproduces_and_closes_tan_
cli_123 flaky -- does not survive measurement: find_workspace_venv's upward
.venv walk resolves before the ZEPHYR_BASE read is ever reached, and the
test plants its .venv at the subprocess's own cwd. With a hostile value set,
that test gives 5 passed / 128 deselected, identical to the unset baseline.
The original Windows-side failure remains unreproduced and needs a fresh
repro on Windows.
…ref (#213)

client_payload.sdk_ref was read into a step output and echoed via ::notice::,
which is job-log-only -- it never reached the Actions API display_title field
alp-sdk polls to correlate the run it triggered.

A top-level run-name key surfaces it. The non-dispatch arm is an empty
string, not a formatted event name: GitHub falls back to the event-specific
default title when run-name is empty or whitespace, so push and pull_request
runs keep showing their commit message or PR title. Naming the event there
instead would have overwritten that default on every such run -- a
regression traded for the fix. Same idiom this file already uses one line
away for ref:.

Inert until it reaches the default branch.
@alpCaner alpCaner added bug Something isn't working ci CI workflows, gates and test wiring oracle-parity Divergence from the released Rust oracle; measure by RUNNING the binary python-port Rust-to-Python port of the tan command surface release-blocker Blocks the next release; must land before the tag labels Aug 2, 2026
…numbers (#224)

The gate landed keyed on (file, ABSOLUTE LINE), which reds the build on any
unrelated edit that shifts a declared site. It happened immediately: dev's
fc88ca1 added +69/-18 lines to bootstrap_cmd.py, moving f"bootstrap.{code}"
from :1522 to :1532, and both the `python` and `seam1 -- plan-shape parity`
CI jobs went red on the PR's merge commit while the branch itself was green.

Re-pinning the number was the wrong fix. A gate that must be hand-re-pinned
after every unrelated edit trains reviewers to update the pin mechanically
without reading what changed, which is how a gate stops gating -- the
tan-cli#275 lesson this file already cites.

Keys are now (file, enclosing qualname). Line numbers stay in the error
messages, where a human needs them, and appear in no hand-maintained key.
Proved by measurement: shift bootstrap_cmd.py, gate still passes; plant an
unregistered code, gate still fails naming it.

Re-keying opened a smaller hole, found by review and reproduced: a SECOND
template inside an already-declared function collapses onto one key, leaving
only the scalar EXPECTED_TEMPLATE_COUNT -- whose own failure message says to
bump the count. So each declaration now pins how many sites it covers, and a
mismatch fails naming the qualname, both counts and every matched file:line.
_FORWARDER_SUFFIXES, the third bucket with the same many-to-one shape, gets
the same discipline.

_check_site_counts was a closure and could not be tested. This file states
its own bar -- an assertion nobody has ever watched fail is not proven to
fire -- and met it for its two older assertions. It is extracted and tested
now, including the _ACKNOWLEDGED_CEILINGS half that is empty in production
and had never had a real value driven through it.

One hole is documented rather than closed. Resolving an f-string statically
cannot tell a substituted name bound by the enclosing function's parameter
list from one rebound by a comprehension target of the same spelling, so a
1-for-1 swap at a declared key needs no count bump at all and stays green.
That is a limit, not a missing pin; chasing it means shadowing heuristics
that false-positive on ordinary Python. The module docstring records it as
an acknowledged ceiling with the verbatim repro and names the real backstop:
contract/issue-codes.json is read directly by release.yml when building the
published envelope-contract.json, and alp-sdk-vscode fails open on a code it
does not recognise, so the cost is a silently-ignored issue, not a crash.

Also drops three line-number comments that had themselves rotted (2072,
2061, 2326 -> 2099, 2088, 2363) rather than re-measuring them, in the file
whose whole subject is that line identity decays.
…nstead of asserting it

Milestone v0.6.0's goal is, verbatim, "Full command-surface parity with the
v0.4.1 oracle: model, new-som, monitor, faultdecode, the introspection set,
renode, and the seven entirely-unported verbs." Not one of those verbs
appeared in this file's CASES, which covered --version, a bogus command, a
bare invocation, debug-config, presets, clean and build --plan. The
milestone's central claim had no instrument that could read it.

Nine case-functions (17 node IDs) now cover every named verb, each
established by RUNNING target/debug/tan rather than by reading crates/.

Matching, as live parity cases: explain (overview, unknown-template,
unknown-target), image, renode -- full envelopes byte-identical.

Diverging, each as a documented known-divergence case that pins the
divergence on BOTH sides so it fails if silently fixed or worsened:
model, new-som, monitor, faultdecode, size, run, and all seven deferred
verbs. Several are structural, not cosmetic. faultdecode's exit codes
coincide at 2 while the envelopes differ entirely -- the port re-implemented
it as ARMv8-M register arithmetic that reads no SDK, so it answers
cli.parse-error where the oracle answers faultdecode.failed. new-som's Click
command declares no --format option at all, so --format json is a usage
error rather than an envelope. size's divergence is permanent: two runtimes
rendering the same ENOENT, Rust's `(No such file or directory (os error 2))`
against Python's `([Errno 2] No such file or directory: '<path>')`.

Two harness defects had to be closed for any of this to mean anything.

The support-bundle case first encoded this host's tool inventory, gaining a
third issue under a stripped PATH -- tan-cli#313 and tan-cli#324 reintroduced
in the PR that fixed them. These cases spawn both binaries live every run, so
unlike the frozen-replay cases they can pin PATH SYMMETRICALLY, and now do.
empty_tool_inventory also seeds a real `which`: with a literally empty PATH
the oracle's probe cannot resolve `which` itself and reports every tool
missing because it could not look, which is an artefact, not an answer. It
raises now rather than silently returning to that.

rust_binary() preferred target/release over target/debug. On a tree with a
stale release build that silently measured against the wrong oracle -- forced
over the other four parity files it gave "108 failed, 8 passed", red but
reading as port bugs, with eight cases passing against the wrong binary. It
picks the most recently built profile now, refuses an mtime tie rather than
breaking it silently, and a session-scoped fixture in the new conftest.py
asserts the resolved binary's --version for every file under tests/parity.
Wrongness fails loudly; absence stays a skip, which is each file's own gate
to decide (missing_for_live's rule, tan-cli#272). Resolution happens in the
fixture body, not at conftest import, so a raise cannot abort collection for
the whole suite.
# Conflicts:
#	crates/tan-cli/src/commands/bootstrap/steps.rs
…bootstrap warn count

Merging dev put the tan-cli#224 emit-site gate in one tree with two commits
that landed while this branch was in flight, and it caught both.

fcb2153 (tan-cli#325, `fix(init,generate): confine writes to the project
after symlink resolution`) added `generate.write-escapes-project` --
raised as a GenerateError with ExitCode.WRITE_FAILURE when resolve_confined
finds a target's output path resolving outside the project root -- and
registered it nowhere. That is ungated on both sides of the seam at once:
this repo's registry-driven checks never see it, and the published
envelope-contract.json is built from that same registry, so alp-sdk-vscode
cannot see it either. Registered `reserved`/`consumer: none`, which costs
nothing since a reserved code may still be renamed freely.

It is unregistered on dev right now, not only here -- this registration
fixes it for everyone at merge.

518ac8c (tan-cli#334) split the single `zephyr-base-incompatible` warn into
a found/else pair so the message can name the evidence, taking that helper
from 16 call sites to 17. One code, two sites, already registered. Checked
before bumping, which is the entire point of pinning the count: it forced
the look rather than allowing a reflexive increment.
@alpCaner

alpCaner commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #352, which carries this work rebased onto dev. Closing so the
queue reflects reality — but the rebase was not mechanical, so here is
exactly what happened to it.

Carried over intact

#224, #308, #309, #311, #312, #313, #324, #213, plus #180 / #179 / #139 closed
as already-fixed. All of it, with your reasoning preserved.

One commit dropped

#318's crates/ change. dev already carries the equivalent from #333, in
a stronger form: it keeps the byte-identity marker assertion alongside the
wait_until_spawnable guard, where the version here replaced the marker. Taking
dev's meant crates/ ends up unchanged against dev on #352, which also keeps
the frozen tree frozen.

Two defects found where your work met the merged #336

git merged your #308 and the already-merged #336 cleanly — textually.
The result was wrong twice, and I want to be precise that this is a
composition problem, not a defect in either fix on its own.

1. #308 was silently cancelled. #308 fills ZEPHYR_BASE from the resolved
workspace as a gap filler merged through assemble_slice_env. #336 pops an
inherited ZEPHYR_BASE, and runs after that merge. Its condition tested only
sl.env — the plan's own pins — so it could not see the gap filler and stripped
the value right back out, on precisely the slices #308 exists to serve.

CI would have been green. Every test in test_execute_zephyr_env.py drives a
backend: baremetal slice whose tool is the interpreter itself, so is_west
is False and the pop is never reached. Three tests using tool: "west" are
added; the first fails on the naive composition — verified by reverting the
condition and re-running, not assumed.

2. #309's guard message was deleted. #336's re-wording block sat after
your Zephyr-boilerplate guard and recomputed message unconditionally, so a
guard-failed slice reported failed with message: None — the verdict with its
evidence removed, the exact class #299/#301/#306/#334/#344 are about. That one
was CI-visible: it reds test_the_refusal_message_matches_the_oracle_verbatim.

The pop now keys on the assembled slice_env (plan env plus gap fillers), so
only a genuinely ambient value is dropped and the two fixes compose in order;
#336's re-wording moved into the initial message assembly, leaving your #309
guard the last word.

Windows

Five of #308's unit tests hardcoded POSIX literals against values the code
renders with str(Path(...)) — on Windows Path("/sdk") is \sdk, so all five
failed the required test (windows-latest) gate. Production is correct; the
expectations now derive through the same call and mean what they say on either
platform.

Also: your _fake_west_build_script shim exits 0 without writing the
build/CMakeCache.txt ZEPHYR_BASE: entry a real west build leaves, so #309's
guard correctly failed those slices and masked what #336's assertions measure.
The shim now writes it, honouring #307's injected -d <slice dir>/build (the
child's cwd is the workspace, where the shim itself lives).

Where it is now

#352 also carries #260/#257 (the seven deferred verbs are real commands),
#254/#255/#256, #77, #91, #138, #321, #344, #261, #262, #258, #259, #297, and
your #349/#350/#351. Suite: 2428 passed, 0 failed.

Thanks for #331 — the triage in it is what made the batch tractable.

@alpCaner alpCaner closed this Aug 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working ci CI workflows, gates and test wiring oracle-parity Divergence from the released Rust oracle; measure by RUNNING the binary python-port Rust-to-Python port of the tan command surface release-blocker Blocks the next release; must land before the tag

Projects

None yet

1 participant