Found by adversarially verifying the #357 fix
feat/v06-batch. #357's core fix is confirmed sound — the exit-code invariant holds on every return path, ok:true beside an error-severity issue is closed everywhere (not just the measured path), the oracle's check names and order match on all five target/server shapes, inspect.context is key-identical including projectSelected and debuggerExtensions, and the parity test genuinely spawns both binaries against a failing host. These are the gaps around it.
1. MAJOR — the port exits 4 where the oracle exits 0, on a first-run customer host
python/tan/commands/support_bundle_cmd.py:648 feeds exit_code_for(checks) a list containing the Python-only-widened longPaths check (tan-cli#306), which is not one of the oracle's fail axes.
Measured — same project, real PATH, HOME at a fresh scratch dir (no global .gitconfig, the first-run-customer state long_paths_check's own docstring names), registry LongPathsEnabled = 1, git core.longpaths unset:
RUST rc=0 ok=True exitCode=0 issues=[]
PY rc=4 ok=False exitCode=4 issues=[support-bundle.bootstrapManifest:warning,
support-bundle.longPaths:error]
Identical divergence at --target-kind zephyr-mcu --server jlink, zephyr-mcu --server openocd, baremetal-mcu --server pyocd, yocto-userspace --server gdbserver, and native-host. With a developer HOME carrying core.longpaths=true both sides return 0 — which is exactly why it is invisible on our machines and visible on a customer's.
#357's acceptance criterion is "matching the oracle". This fails where the oracle passes, on the one command a customer runs after something else has already broken.
Fix. Drop longPaths from this command's verdict input, or restrict tan-cli#306's fail arm to tan doctor.
2. MAJOR — the parity test structurally cannot see it
python/tests/parity/test_support_bundle_oracle_parity.py:116-117 pins the exit-code assertion on the single scenario where both sides are already 4 (sdkRoot fails on both). Re-running the file's own comparison with --sdk-root <stub> gives RUST 0 / PY 4.
GIT_LONGPATHS_WIDENED (:63) exempts longPaths' status, and the file's docstring asserts that is "NOT an exemption from the comparison this file exists for" — but the exempted check drives exit_code_for, i.e. the exact (rc, ok, exitCode) triple line 117 compares.
Fix. Add a second case with a resolvable SDK, where the oracle returns rc=0.
3. MAJOR — unregistered issue codes are on the wire and the gate cannot see them
python/tests/gates/test_every_issue_code_is_registered.py:919-946 hand-declares this command's value space instead of deriving it — _resolve_helper takes declared["suffixes"] verbatim (:1097-1101).
Measured live emits absent from contract/issue-codes.json (which carries 22 support-bundle.* entries, none of these): support-bundle.sdkRoot (error), support-bundle.jlinkBackend, support-bundle.openocdBackend, support-bundle.pyocdBackend (warning). Also reachable and unregistered: support-bundle.gdb, support-bundle.gdbserverBackend, support-bundle.noneBackend.
So py -3.12 -m pytest tests/gates/ -q reports 11 passed while tan support-bundle puts an unregistered error code on the wire.
Fix. Register them, and re-derive (or re-declare) the suffix set from _debug_doctor_report's check names so the gate cannot drift again.
4. MAJOR — a stale exemption whose subject this diff deleted
The same entry's comment still reads "checks there is doctor_cmd._collect(...)'s own output, reused verbatim (_doctor_section's docstring)". _doctor_section was deleted by this diff.
Fourteen declared suffixes are now unemittable by this command — sdk, hostPython, jlink, pythonFloor, sdkProvenance, setools, sevenZip, venvProvenance, west, westResolved, workspace, zephyrSdk, zephyrVersion, zephyrWorkspace — and their contract/issue-codes.json notes repeat the same dead _doctor_section claim.
It is not an xfail(strict=True), so nothing XPASSes; it just keeps passing. The strict-xfail path was checked too: python/tests/conformance/test_contract_envelopes.py:120's DELIBERATE_DIVERGENCE and NOT_PORTED carry no support-bundle entry (the 5 xfails are all debug-config-preview-*, tan-cli#138), and test_frozen_issue_codes.py:321-339 is scoped to status in ("frozen","reserved") while these rows are pre-consumer. Nothing anywhere catches the dead codes.
5. MINOR — an undeclared divergence in the check list
support_bundle_cmd.py:235-241's _HOST_CHECK_ORDER keeps bootstrapManifest, a key the oracle's bundle does not have. Measured with a resolved SDK whose metadata/bootstrap.json is unreadable: the port emits 10 checks, the oracle 9, with bootstrapManifest: warn inserted before hostPrerequisites.
The parity test's exact name-list assertion (:120) passes only because its scenario resolves no SDK. #357's acceptance says "no deliberate-divergence exemption"; this one is deliberate, documented in the module, and undeclared in the parity file whose docstring claims the check list is "compared element-for-element".
6. MINOR — detail and fix are never compared, and they diverge
test_support_bundle_oracle_parity.py:82's _statuses compares (name, status) only — so the text a maintainer actually reads in the attached bundle is uncompared. Same failing host, oracle vs port:
hostPrerequisites — "Missing required tools: git -> winget install -e --id Git.Git ..." vs "missing from PATH: git, cmake, python, ninja (tan's built-in fallback list)."
longPaths — different headline plus a port-only fix.
codeLLDBExtension — the oracle writes U+2014 EM DASH (bytes \xe2\x80\x94) where support_bundle_cmd.py:258-259 writes --, so that "port of doctor.rs::extension_check's None arm" is not byte-verbatim.
(homePath's difference is the declared redaction policy — not a finding.)
7. MINOR — the unit tests pin a check list that cannot occur in production
python/tests/commands/test_support_bundle_command.py:433-450 pins a 6-name list. Every case monkeypatches doctor_cmd._collect with _clean_checks() (:63-85), which returns only sdk + hostPrerequisites, so _HOST_CHECK_ORDER harvests exactly one name.
It is a genuine guard against the substituted-checklist defect, but no unit test would notice bootstrapManifest, longPaths, zephyrSdkAvailableForHost or homePath entering or leaving the bundle — which is how findings 1 and 5 got through.
Fix. Add one case that runs the real _collect, or assert the 9-name oracle shape.
8. NIT — stale comment
test_support_bundle_command.py:23: "support-bundle is not yet registered in tan.cli.app (the orchestrator's to wire)". It is registered at python/tan/cli.py:34 and :105, and the parity test depends on that.
Counts
py -3.12 -m pytest tests/commands/test_support_bundle_command.py tests/parity/test_support_bundle_oracle_parity.py tests/gates/ tests/conformance/ -q re-run: 395 passed, 3 skipped, 5 xfailed (not the reported 375/3 — the delta is other agents' concurrent additions, green either way). The 3 skips are ALP_SDK_ROOT/ALP_SDK_HAND_PORT_ROOT staleness gates; the 5 xfails are the debug-config-preview-* #138 divergences.
Related: #357, #306, #138.
Found by adversarially verifying the #357 fix
feat/v06-batch. #357's core fix is confirmed sound — the exit-code invariant holds on every return path,ok:truebeside an error-severity issue is closed everywhere (not just the measured path), the oracle's check names and order match on all five target/server shapes,inspect.contextis key-identical includingprojectSelectedanddebuggerExtensions, and the parity test genuinely spawns both binaries against a failing host. These are the gaps around it.1. MAJOR — the port exits 4 where the oracle exits 0, on a first-run customer host
python/tan/commands/support_bundle_cmd.py:648feedsexit_code_for(checks)a list containing the Python-only-widenedlongPathscheck (tan-cli#306), which is not one of the oracle's fail axes.Measured — same project, real PATH,
HOMEat a fresh scratch dir (no global.gitconfig, the first-run-customer statelong_paths_check's own docstring names), registryLongPathsEnabled = 1, gitcore.longpathsunset:Identical divergence at
--target-kind zephyr-mcu --server jlink,zephyr-mcu --server openocd,baremetal-mcu --server pyocd,yocto-userspace --server gdbserver, andnative-host. With a developerHOMEcarryingcore.longpaths=trueboth sides return 0 — which is exactly why it is invisible on our machines and visible on a customer's.#357's acceptance criterion is "matching the oracle". This fails where the oracle passes, on the one command a customer runs after something else has already broken.
Fix. Drop
longPathsfrom this command's verdict input, or restrict tan-cli#306'sfailarm totan doctor.2. MAJOR — the parity test structurally cannot see it
python/tests/parity/test_support_bundle_oracle_parity.py:116-117pins the exit-code assertion on the single scenario where both sides are already 4 (sdkRootfails on both). Re-running the file's own comparison with--sdk-root <stub>givesRUST 0 / PY 4.GIT_LONGPATHS_WIDENED(:63) exemptslongPaths' status, and the file's docstring asserts that is "NOT an exemption from the comparison this file exists for" — but the exempted check drivesexit_code_for, i.e. the exact(rc, ok, exitCode)triple line 117 compares.Fix. Add a second case with a resolvable SDK, where the oracle returns rc=0.
3. MAJOR — unregistered issue codes are on the wire and the gate cannot see them
python/tests/gates/test_every_issue_code_is_registered.py:919-946hand-declares this command's value space instead of deriving it —_resolve_helpertakesdeclared["suffixes"]verbatim (:1097-1101).Measured live emits absent from
contract/issue-codes.json(which carries 22support-bundle.*entries, none of these):support-bundle.sdkRoot(error),support-bundle.jlinkBackend,support-bundle.openocdBackend,support-bundle.pyocdBackend(warning). Also reachable and unregistered:support-bundle.gdb,support-bundle.gdbserverBackend,support-bundle.noneBackend.So
py -3.12 -m pytest tests/gates/ -qreports11 passedwhiletan support-bundleputs an unregistered error code on the wire.Fix. Register them, and re-derive (or re-declare) the suffix set from
_debug_doctor_report's check names so the gate cannot drift again.4. MAJOR — a stale exemption whose subject this diff deleted
The same entry's comment still reads "
checksthere isdoctor_cmd._collect(...)'s own output, reused verbatim (_doctor_section's docstring)"._doctor_sectionwas deleted by this diff.Fourteen declared suffixes are now unemittable by this command —
sdk,hostPython,jlink,pythonFloor,sdkProvenance,setools,sevenZip,venvProvenance,west,westResolved,workspace,zephyrSdk,zephyrVersion,zephyrWorkspace— and theircontract/issue-codes.jsonnotes repeat the same dead_doctor_sectionclaim.It is not an
xfail(strict=True), so nothing XPASSes; it just keeps passing. The strict-xfail path was checked too:python/tests/conformance/test_contract_envelopes.py:120'sDELIBERATE_DIVERGENCEandNOT_PORTEDcarry nosupport-bundleentry (the 5 xfails are alldebug-config-preview-*, tan-cli#138), andtest_frozen_issue_codes.py:321-339is scoped tostatus in ("frozen","reserved")while these rows arepre-consumer. Nothing anywhere catches the dead codes.5. MINOR — an undeclared divergence in the check list
support_bundle_cmd.py:235-241's_HOST_CHECK_ORDERkeepsbootstrapManifest, a key the oracle's bundle does not have. Measured with a resolved SDK whosemetadata/bootstrap.jsonis unreadable: the port emits 10 checks, the oracle 9, withbootstrapManifest: warninserted beforehostPrerequisites.The parity test's exact name-list assertion (
:120) passes only because its scenario resolves no SDK. #357's acceptance says "no deliberate-divergence exemption"; this one is deliberate, documented in the module, and undeclared in the parity file whose docstring claims the check list is "compared element-for-element".6. MINOR —
detailandfixare never compared, and they divergetest_support_bundle_oracle_parity.py:82's_statusescompares(name, status)only — so the text a maintainer actually reads in the attached bundle is uncompared. Same failing host, oracle vs port:hostPrerequisites—"Missing required tools: git -> winget install -e --id Git.Git ..."vs"missing from PATH: git, cmake, python, ninja (tan's built-in fallback list)."longPaths— different headline plus a port-onlyfix.codeLLDBExtension— the oracle writes U+2014 EM DASH (bytes\xe2\x80\x94) wheresupport_bundle_cmd.py:258-259writes--, so that "port ofdoctor.rs::extension_check'sNonearm" is not byte-verbatim.(
homePath's difference is the declared redaction policy — not a finding.)7. MINOR — the unit tests pin a check list that cannot occur in production
python/tests/commands/test_support_bundle_command.py:433-450pins a 6-name list. Every case monkeypatchesdoctor_cmd._collectwith_clean_checks()(:63-85), which returns onlysdk+hostPrerequisites, so_HOST_CHECK_ORDERharvests exactly one name.It is a genuine guard against the substituted-checklist defect, but no unit test would notice
bootstrapManifest,longPaths,zephyrSdkAvailableForHostorhomePathentering or leaving the bundle — which is how findings 1 and 5 got through.Fix. Add one case that runs the real
_collect, or assert the 9-name oracle shape.8. NIT — stale comment
test_support_bundle_command.py:23: "support-bundleis not yet registered intan.cli.app(the orchestrator's to wire)". It is registered atpython/tan/cli.py:34and:105, and the parity test depends on that.Counts
py -3.12 -m pytest tests/commands/test_support_bundle_command.py tests/parity/test_support_bundle_oracle_parity.py tests/gates/ tests/conformance/ -qre-run: 395 passed, 3 skipped, 5 xfailed (not the reported 375/3 — the delta is other agents' concurrent additions, green either way). The 3 skips areALP_SDK_ROOT/ALP_SDK_HAND_PORT_ROOTstaleness gates; the 5 xfails are thedebug-config-preview-*#138 divergences.Related: #357, #306, #138.