Releases: alplabai/tan-cli
Release list
v0.6.0
The first stable release of the Python tan, and the first that requires — and is gate-verified against — a released alp-sdk. v0.6.0-rc1 was a prerelease nothing upgraded onto, so everything in it reaches users here for the first time.
Highlights
tan renodeis removed, along with all 27 publishedrenode.*issue codes — the one breaking change, details below.- alp-sdk
v0.16.0or newer is now required. tan's planner mirror and every vendored fixture pin to that released tag, so the gates measure tan against the SDK you actually install rather than an unreleased dev commit. - Bootstrap stopped handing Fedora/Rocky/Arch hosts
sudo apt-get install -y cmake— install commands are now selected per package manager, and a gap degrades to a host-neutral hint instead of a guessed package name. tan buildno longer dropsflash_args.slot0_load_addressfor a variant declaringjlink_flash_device: null, and a dual-M55 AEN SoM that declares no per-role<role>_slot0region is refused rather than mis-mapped.tan doctorresolvesgit/py/pythonto absolute paths before spawning instead of by bareargv[0].- A bare
taninvocation is meaningfully faster to start —click.testing,jsonschema, PyYAML andurllib.requestare no longer imported on every run (427 → 279 modules). tan run --flashcan arm the flash confirm gate directly, with a real--confirmflag.- Scaffolded project READMEs pin their doc links to the released SDK tag rather than a moving
main.
Removed
tan renode, its modules, and all 27renode.*issue codes. This is the breaking change in this release:- Any script invoking
tan renode ...must stop; the verb no longer resolves and exits as an unknown command. - Any consumer matching on a
renode.*code inissues[]will never see one again — the codes are unpublished, not merely unused.
- Any script invoking
Changed
tan/core/shapes.py's dedup is finished — eight duplicate definitions are gone, with a gate so the next half-finished dedup cannot ship green.- The Windows
%PATH%walk no longer builds apathlib.Pathper candidate, and an unreadable%PATH%entry is skipped rather than aborting the whole lookup.
Added
- The pin-move-verify sender (ADR-0029 clause 5) — a PR that moves a pin value dispatches the proposed tuple to alp-e2e instead of spell-checking it.
- A clean-container end-to-end run on a schedule, rather than only when something touches it.
- A gate requiring a pinned hand-port to name which tan code it governs, and symbol-level checking of
scripts/alp_cli/explain.py.
Fixed
tan/planner/is re-synced with alp-sdk, closing an emit divergence where tan wrote noCONFIG_SPI=yfor any GD32-bearing SoM while alp-sdk did.- A storage entry can no longer reach
status: okon a defaulted Devicetree label, and every planner site resolves against the project's own metadata root. install.ps1rolls back a failure during the backup renames, not only after them, and no longer reports a successful install that did not happen.tan flash'sexpect_dpidrwrong-board guard round-trips a bare YAML hex, and a--core/--helperfilter matching nothing now fails instead of passing silently.tan size/image/clean/run/validatesurfacesdk.discovery-divergentin the text channel instead of only in JSON.- A
postCommandsstep skipped for a missing tool reachesissues[]rather than vanishing. - A
v*tag can no longer publish while a changelog fragment sits unfolded. - The vendored toolchain lock and bootstrap manifest are re-vendored against released alp-sdk
v0.16.0, and CI now fetches tags for its alp-sdk clone so tag-dependent emit matches what a real checkout produces.
Install
install.sh (macOS/Linux) and install.ps1 (Windows) resolve latest through GitHub and will install this release — unlike v0.6.0-rc1, which was a prerelease they skipped. Asset names, the measured glibc floor, and the attestation verify command are in the ## Release assets block below.
Full detail: CHANGELOG.md · full diff
Release assets
Four archives, each a PyInstaller --onedir freeze of the Python
tan (tan-cli#349 -- was a single-file --onefile freeze; --onedir
fixes a 13-19s macOS startup regression caused by --onefile
re-extracting its runtime on every invocation). Unpack the archive
and run the tan/tan.exe inside; install.sh/install.ps1 do
this for you.
tan-x86_64-pc-windows-msvc.zip-- Windows x64tan-x86_64-apple-darwin.tar.gz/tan-aarch64-apple-darwin.tar.gz-- macOStan-x86_64-unknown-linux-gnu.tar.gz-- Linux x64, frozen on Debian 11.
It requires GLIBC_2.30 or newer -- measured from the
binary's own bundled payload at build time, not assumed from the
build image. Debian 11+ / Ubuntu 20.04+ / RHEL 9+ are comfortably
above it.
There is no arm64 Windows and no arm64 Linux asset in this release,
and no -musl asset. A frozen binary has to be built on the
architecture it runs on, and this release builds on four runners; if
you need an arm64 Linux or arm64 Windows tan, install from source
(pip install ./python) and say so on the issue tracker.
- Every archive +
checksums.txtcarries a GitHub build-provenance
attestation. Verify with:
gh attestation verify <downloaded-file> --repo alplabai/tan-cli
v0.6.0-rc1
The Rust oracle is retired and tan is Python end to end — plus a workflow-injection fix and a sweep through the commands that reported success while doing something else.
This is a release candidate. latest still resolves to v0.5.1, so nothing upgrades onto it — install it deliberately.
Highlights
- The Rust oracle is gone.
crates/tan-core,crates/tan-cli,Cargo.tomlandCargo.lockare deleted;python/tan/is the implementation, not a port. - Workflow-injection fix in
parity.yml— an attacker-controlledrepository_dispatchpayload reached fourrun:blocks. - Every
uses:in.github/workflows/is pinned to a full commit SHA, withpersist-credentials: falseon 31 of 32 checkouts. - Five commands and the Flow D planner stopped reporting success for something they did not do —
flash,doctor,quality,initandmonitor, plus a declared-nulljlink_flash_deviceno longer downgrading Flow D to the SE-UART path. tan doctorgainedscope(hostorproject) on every check, plus alibrariesrow for the project's curated-library selection.tan explain --code <ALP-Bxxx|ALP_ERR_*>— diagnostic-code lookup, ported from alp-sdk.ALP_FLASH_REQUIRE_DPIDR=1makes an unarmed write refuse instead of warn, on every flash method that has a wrong-board guard.- A planner re-sync proposer —
python/scripts/planner_resync.pyopens the port PR when alp-sdk'sscripts/alp_orchestrate/moves.
Removed
- The Rust oracle — see above. Existing
v0.5.xassets are unaffected; this changes the repo, not the CLI's behaviour.
Fixed
tan flashrefuses a run that wrote nothing instead of reportingok: trueand exit 0, and the confirm gate finally has a reachable--confirmflag (#719).tan doctorrefuses a--sdk-rootthat is not an alp-sdk checkout rather than reporting[pass] sdkfor a pathtan buildcorrectly rejects (#727).tan qualityexits non-zero on a[FAIL]ing check, and refuses a profile that selected no checks at all (#721).tan monitoraccepts every port pyserial can open —/dev/serial/by-id/...symlinks andsocket:///rfc2217://URLs, not just whatcomports()enumerates (#569).tan doctor'ssevenZipcheck runs on every Windows host, not only whilezephyrSdkis already failing (#736).- A declared-null
jlink_flash_devicereachesflash_argsas a present key instead of silently downgrading Flow D to the SE-UART path (#734). tan initno longer emits cores that do not exist on the requested SKU, nor copies build output into a new project (#494).- The installers' header comments no longer claim
latestresolves tov0.4.1— it resolves to the newest non-prerelease,v0.5.1today (#749).
Install
Four binaries, one per host, plus checksums.txt and envelope-contract.json. There is no -musl asset — install.sh probes for musl and refuses before downloading rather than failing at exec time.
| Host | Asset |
|---|---|
| Windows x86_64 | tan-x86_64-pc-windows-msvc.zip |
| macOS Intel | tan-x86_64-apple-darwin.tar.gz |
| macOS Apple silicon | tan-aarch64-apple-darwin.tar.gz |
| Linux x86_64 | tan-x86_64-unknown-linux-gnu.tar.gz |
The Linux binary is built inside python:3.12-slim-bullseye (Debian 11). Its measured glibc floor is GLIBC_2.30, scanned over the 63 native files in the frozen payload — not read off the PyInstaller bootloader, which reports a container-invariant GLIBC_2.14 under any build image. It is a glibc binary and will not run on Alpine or another musl distro.
Verify what you downloaded:
gh attestation verify <downloaded-file> --repo alplabai/tan-cli
Because this is a prerelease, install.sh and install.ps1 resolve latest through GitHub and will keep installing v0.5.1 — download this one by hand.
Full detail: CHANGELOG.md · full diff
v0.5.1
Twelve command-surface and contract fixes on top of v0.5.0 — including one where a second project's bootstrap could silently make tan build compile the wrong SDK — plus a README rewritten as a user-facing guide.
Highlights
tan kconfigworks on the workspace tan itself bootstrapped. It resolved neither the workspace venv'swestnor the bootstrappedZEPHYR_BASE, so it failed against the exact environment the quickstart produces. (#453)tan qualityandtan migratecan succeed at all. Neither could pass the west extension's required argument, under any input. (#454)- A second project's bootstrap no longer silently repoints the first.
~/.alp/sdk-defaultis machine-global and last-writer-wins; project A could resolve project B's checkout and build it withok: trueand no warning. (#464) tan debug-configinfers a real hardware target instead of defaulting tonative-hostand writing alaunch.jsonpointing at a binary the build never produces. (#456)tan generate --allis re-runnable. It succeeded once, then always exited 3 because one of nine targets refused to overwrite — leaving the other eight rewritten. (#457)tan renodehonours--project. It resolved the build root from the CWD and its own remedy told you to build the wrong directory. (#470)- Silent failures made audible:
tan pinmuxprinted nothing on exit 2 (#458),tan diffreported "no differences" on aboard.yamlthattan validaterejects (#455), andtan bootstrap --print-envdescribed a workspace bootstrap would never create (#459). - README rewritten from ~4,000 words to under 1,000: quickstart, command table, SDK selection order, JSON envelope contract.
Fixed
- Four
debug-configpreconditions reclassified from exit 5 to exit 2 — user-fixable input was being reported as a tan crash. (#462) bootstrap.workspace-orphan-refusedno longer prints a stringifiedNonefor its destination, or advises dropping a--workspacethe invocation never passed. (#469)contract/issue-codes.jsonregistered two codes twice, one pair disagreeing on severity. JSON duplicate keys are last-wins, so the published severity depended on file order. Deduped, plus a new gate that rejects duplicates. (#467)
Behaviour worth knowing
- On a multi-core SoM,
tan debug-confignow needs--core <name>; without it, it exits 2 withdebug-config.target-kind-ambiguousrather than guessing. tan qualityrequires--profile,tan migraterequires one of--check/--preview/--apply. Both refuse at exit 2 before spawningwest.- When the machine-global default SDK was last written by a different project's bootstrap, commands now emit a
sdk.global-default-foreign-projectwarning instead of resolving silently.
Install
# Linux / macOS
curl -fsSL https://raw.githubusercontent.com/alplabai/tan-cli/main/install.sh | sh# Windows
irm https://raw.githubusercontent.com/alplabai/tan-cli/main/install.ps1 | iex| Platform | Asset |
|---|---|
| Windows x64 | tan-x86_64-pc-windows-msvc.zip |
| Linux x64 (glibc) | tan-x86_64-unknown-linux-gnu.tar.gz |
| macOS x64 | tan-x86_64-apple-darwin.tar.gz |
| macOS arm64 | tan-aarch64-apple-darwin.tar.gz |
The Linux binary requires GLIBC_2.30 or newer, measured from the frozen payload rather than the bootloader. There is no musl asset: a PyInstaller musl freeze runs only on musl distros, so install.sh probes for musl and refuses before downloading rather than failing at exec. Linux arm64 and Windows arm64 have no prebuilt archive — install from source on those hosts.
Verify a download:
gh attestation verify <downloaded-file> --repo alplabai/tan-clichecksums.txt carries the SHA-256 of every asset; envelope-contract.json is the machine-readable command/issue-code contract consumers key off.
Full detail: CHANGELOG.md · full diff
v0.5.0
tan is now a Python program, not a Rust one — the first stable release of that rewrite, and it changes both what you download and what you unpack.
Cumulative since v0.4.1, the last Rust release. The 0.5.0-rc1 … -rc4 prereleases are folded in here; if you never installed one, everything below is new to you.
Highlights
- The binaries are PyInstaller freezes of
python/, notcargobuilds ofcrates/. The Rust tree stays as a frozen reference oracle and ships no asset. - BREAKING — every asset is an archive now, not a raw binary. Unpack before running.
- BREAKING —
tan validate's spawn path exits 2, not 1. Details below; CI and the VS Code extension are affected. tannow plans and executes — the planner relocated out of alp-sdk intotan.- macOS launch is fixed —
--versionwent from 13.25–19.74 s to 0.342 s. - All seven deferred verbs are real commands:
scaffold,completion,diff,pinmux,inspect,trace,support-bundle(#260). tan flashauto-signs an Alif Ensemble slot0 ATOC via SETOOLS, so Flow D no longer needs hand-signing outsidetan(#353).- Several data-loss and wrong-hardware-write defects are fixed — read the next section before upgrading from an RC.
Fixed — data loss, wrong writes, and silent success
These are the reason to upgrade rather than stay on an RC.
tan bootstrap --dry-runmoved the user's alp-sdk checkout and rewrote it (#323). A dry run mutated the workspace.tan initandtan generatefollowed symlinked parents and wrote outside the project.- Flow D could write the wrong file to a board's MRAM. A fix for #289 had set the wrong artefact for the Alif flash path.
- A pending
TBDplaceholder could reach a flasher (#222) — on both the Python and the Rust side. tan initcould pin a customer to the WRONG SDK, permanently (#263).tan buildreportedok: true/exitCode: 0/issues: []when every slice had failed.- Flow D's SETOOLS auto-sign could pass an unsigned ATOC through its own soft-failure guard (#373).
- The macOS release asset shipped with no CA trust anchors at all (#304), so TLS failed everywhere.
- A write could silently replace a hand-filled
.vscode/launch.jsonvalue.
Breaking
tan validate's not-yet-ported spawn path now exits 2 (VALIDATION_FAILURE), not 1 (RUNTIME_FAILURE) (#262).- Before: a
board.yamlwith an unresolvable SDK answeredvalidate.spawn-not-implementedat exit 1, indistinguishable from atancrash. After: exit 2, matching every guard-level refusal. - Who must act: CI branching on
-eq 1specifically stops matching.alp-sdk-vscoderenders exit 2 aswarningand exit 1 aserror, so a stale consumer shows a real validation gap as a warning. - A genuine
tancrash — unreadableboard.yaml, internal exception — is unaffected and still exits 5.
- Before: a
- Release assets gained an archive extension (#349).
- Before:
tan-x86_64-unknown-linux-gnu. After:tan-x86_64-unknown-linux-gnu.tar.gz. Target triples are unchanged. - Who must act: anything building the URL from the bare triple.
alp-sdk-vscodegains its unpack step separately (#268).
- Before:
Also fixed
buildreported a bareterminated with exit code: 1when the Zephyr SDK was missing (#419), andvalidatereportedvalidation failurewhen nothing had been validated (#350).tan doctorexited 4 on every fresh install, and had dropped six checks the oracle emits.tan build/tan flashcould not findweston hosts that had it, and a bootstrapped venv could be invisible to both (#291).- A dangling
$ZEPHYR_BASEsilently dropped a core from a multi-core build. install.shhanded musl hosts a binary that cannot exec; it now probes and refuses first.tan monitorwould have been a dead command in every published binary —pyserialwas not bundled.tan <cmd> | headexited 1 on Linux and macOS where the oracle exits 0.tan/planner/had drifted from alp-sdk'sscripts/alp_orchestrate/, and the test suite did not necessarily test this repo'stan(#423).
Contributors
Release assets
Four archives, each a PyInstaller --onedir freeze of the Python
tan (tan-cli#349 -- was a single-file --onefile freeze; --onedir
fixes a 13-19s macOS startup regression caused by --onefile
re-extracting its runtime on every invocation). Unpack the archive
and run the tan/tan.exe inside; install.sh/install.ps1 do
this for you.
tan-x86_64-pc-windows-msvc.zip-- Windows x64tan-x86_64-apple-darwin.tar.gz/tan-aarch64-apple-darwin.tar.gz-- macOStan-x86_64-unknown-linux-gnu.tar.gz-- Linux x64, frozen on Debian 11.
It requires GLIBC_2.30 or newer -- measured from the
binary's own bundled payload at build time, not assumed from the
build image. Debian 11+ / Ubuntu 20.04+ / RHEL 9+ are comfortably
above it.
There is no arm64 Windows and no arm64 Linux asset in this release,
and no -musl asset. A frozen binary has to be built on the
architecture it runs on, and this release builds on four runners; if
you need an arm64 Linux or arm64 Windows tan, install from source
(pip install ./python) and say so on the issue tracker.
- Every archive +
checksums.txtcarries a GitHub build-provenance
attestation. Verify with:
gh attestation verify <downloaded-file> --repo alplabai/tan-cli
Full detail: CHANGELOG.md · full diff
v0.5.0-rc4
Nine fixes found by running the published v0.5.0-rc3 binary as a customer
would — on real Windows, macOS and Linux hosts, in isolated environments — not
by testing the source. Two of them destroy or misplace a user's files; one
silently drops a core from a multi-core build.
Highlights
tan bootstrap --dry-runmoved your alp-sdk checkout and rewrote your global SDK pointer — a preview flag that relocated a repository (#323).tan init/tan generatefollowed symlinked parents and wrote outside the project, while reporting the in-project path andok: true(#325).- A dangling
$ZEPHYR_BASEsilently dropped a core from a multi-core build, blaming west for a stale environment variable (#336). tan --versionwas not eager —tan --version init --destination <dir>printed the version and created the project (#326).envelope.serialize-failedprintedexitCode: 5while the process exited0, breaking the one invariant every consumer relies on (#327).tan imagerejected helper firmware the SDK actually ships, resolving a repo-relativefirmware_pathonly underbuild/(#330).tan/planner/had drifted from alp-sdk'sscripts/alp_orchestrate/— seven Kconfig symbols missing, and a board alp-sdk refuses reportedok(#320).
Fixed
tan bootstrap --dry-runmoved the user's alp-sdk checkout and rewrote~/.alp/sdk-default. No data was lost — all 4067 files survived andgit logstill resolved — but a preview flag must not move a repository or rewrite a global config;relocate_checkout()now takesdry_runand says "would move" / "would set" (#323).tan initandtan generatefollowed symlinked parents and wrote outside the project while reporting success. A new shared guard (tan/core/fs_confine.py) compares two resolved paths and refuses the whole run rather than partway through; a symlinked project root still works (#325).- A dangling
$ZEPHYR_BASEsilently dropped a core from a multi-core build. Zephyr'sbuildextension makes a separatewest_topdir(self.source_dir)call that#307's cwd pin never covered, so atan initproject — always a sibling of the workspace — fell through to a stale$ZEPHYR_BASEwest trusts with no existence check (#336). tan --versionwas not eager: a version probe executed the following subcommand. Now handled through Typer's ownis_eagermechanism, matching the Rust oracle (#326).envelope.serialize-failedprintedexitCode: 5while the process exited0. The fallback code now reaches the process boundary (#327).tan imagerejected helper firmware the SDK actually ships. Relative paths are tried against both roots in a defined order, and the error names every root tried with its absolute path (#330).sdk list --onlinesplit table rows in half. Whitespace is collapsed before truncatingreleaseNotesSummaryto 60 characters (#316).bootstrap'sINCOMPATIBLE $ZEPHYR_BASEmessage named the verdict but not the cause. The branch now accumulates the observed facts and renders them together; the decision and exit code are unchanged (#334).tan/planner/had drifted from alp-sdk'sscripts/alp_orchestrate/. Onmproc-mailboxthe emitted Zephyr conf fragment was 56 lines against alp-sdk's 63; onrpmsg-imx93alp-sdk refused the board while tan reportedok. Re-synced, and seam1 now compares a legitimate refusal on both sides (#320).
Internal
- Closed the third occurrence of #250's
ETXTBSYrace — two test fixtures wrote an executable and immediatelyexec'd it with no guard, reddening a required check (3 failures in 40 full-suite runs, 0 after) (#318, #333).
Known issues
@alplabai/tanhas never published to npm:NPM_TOKENis a classic token andnpm publishdemands an OTP. Needs an automation token (#233).
Release assets
Four binaries, each a single-file freeze of the Python tan:
tan-x86_64-pc-windows-msvc.exe-- Windows x64tan-x86_64-apple-darwin/tan-aarch64-apple-darwin-- macOStan-x86_64-unknown-linux-gnu-- Linux x64, frozen on Debian 11.
It requires GLIBC_2.30 or newer -- measured from the
binary's own bundled payload at build time, not assumed from the
build image. Debian 11+ / Ubuntu 20.04+ / RHEL 9+ are comfortably
above it.
There is no arm64 Windows and no arm64 Linux asset in this release,
and no -musl asset. A frozen binary has to be built on the
architecture it runs on, and this release builds on four runners; if
you need an arm64 Linux or arm64 Windows tan, install from source
(pip install ./python) and say so on the issue tracker.
- Every binary +
checksums.txtcarries a GitHub build-provenance
attestation. Verify with:
gh attestation verify <downloaded-file> --repo alplabai/tan-cli
Full detail: CHANGELOG.md · full diff
v0.5.0-rc3
Six defects found by running the published v0.5.0-rc2 binary end to end on real Windows and macOS hosts — not by testing the source, and not by trusting a green CI run on a clean runner. Every one of them was invisible to both.
This is a pre-release. It does not take the Latest badge.
Highlights
tan doctorexited 4 on every fresh install — the guaranteed post-bootstrap state was reported as a broken host (#299).- A host where no build slice could run exited 0 — the opposite failure, exposed by fixing the first one (#299).
- The macOS asset could not make an HTTPS request at all — no CA trust anchors in the freeze (#304, release-blocker).
tan bootstrapdied with "Filename too long" on a fresh Windows install whiledoctorhad just called the host fine (#306, release-blocker).doctorandbootstrapstopped recommending a config change that was tried and reverted upstream (#300).- New
clean-host.ymlgate runs the shipped artefact on a host with no SDK, no~/.alpand an empty cwd — the state that hid all six (#278).
Fixed
tan doctorexited 4 on every fresh install.tan bootstrapdeliberately leaveswestoff PATH, so "west in the venv, absent from PATH" is the guaranteed post-bootstrap state — and the state a GUI-launched VS Code is always in (#299).westResolvednow fails when west resolves nowhere.westanswers "is it on bare PATH" and is never fatal;westResolvedanswers "can a slice run at all" and owns the exit code (#299).- macOS releases could not verify any TLS certificate. A PyInstaller freeze bundles its own
sslbut no CA bundle and does not fall back to the platform trust store, so every HTTPS call intan sdk list --onlinefailedCERTIFICATE_VERIFY_FAILEDon the published rc2tan-aarch64-apple-darwinasset (#304).- Requests now build their
SSLContextthroughtruststore(the OS trust store, so a corporate CA in the machine keychain keeps working), falling back tocertifionly if that is unavailable. - The error text no longer asserts "usually a TLS-intercepting proxy or a corporate CA" — there was neither on the reporting host, and that wording sends users hunting in the wrong place.
- Requests now build their
longPathschecked the registry and nothing else. Windows'LongPathsEnabledgoverns manifested Win32 calls; it does nothing for git, whichwest updateuses for every module clone and which refuses a long path unless its owncore.longpathsis set.doctorsaidpassmoments beforebootstrapdied insidehal_nxp'stf-psa-cryptotree (#306).longPathsnow reads both axes and fails — not warns — when the registry says yes and git does not. The remedy names the exact command:git config --global core.longpaths true.tan bootstrapalso forcescore.longpaths=trueon thewest updatechild's environment, so a fresh install gets past the failure rather than only being warned about it.
doctorandbootstrapstopped telling users to raiseprerequisites.pythonMinVersion— the change alp-sdk#1078 tried and reverted. That key is host-universal while the floor is Zephyr's, so raising it refuses a 3.10/3.11 host for a Yocto-only project that builds today (#300).- The
sdkcheck names the tier it resolved through, and names a cwd checkout it did not select, so a report describing a different SDK than the one you are standing in is no longer indistinguishable from a wrong answer (#301).
Added
clean-host.yml— the shipped-artefact gate. Freezestanper platform and runstan --version,tan doctor --format json,tan sdk list --online(a real unmocked GitHub API call, the CA-trust canary) andtan bootstrap --dry-runon a host with no alp-sdk checkout, no~/.alpand an empty cwd — a state no prior job in this repo ever constructed (#278).doctor's envelope is checked for self-consistency —ok/exitCodeagreeing withchecks[], and no<subject>/<subject>Resolvedpair disagreeing. That generalises the #299 shape instead of grepping forwestResolvedby name.
Known issues
tan bootstrapstill refuses the documented quickstart layout (tan.exeandalp-sdk/in one directory) with "holds more than this checkout", and the remedy it offers moves the user's checkout (#302).tan/planner/is behind alp-sdk'sscripts/alp_orchestrate/(#320, blocks v0.5.0 final). Onmproc-mailbox,tan generate's Zephyr conf fragment is 56 lines where alp-sdk emits 63; onrpmsg-imx93, alp-sdk refuses the board as not buildable (SdkRevisionNotBuildable,E1M-NX9101hw_revr1) and tan reportsok.
Release assets
Four binaries, each a single-file freeze of the Python tan:
tan-x86_64-pc-windows-msvc.exe-- Windows x64tan-x86_64-apple-darwin/tan-aarch64-apple-darwin-- macOStan-x86_64-unknown-linux-gnu-- Linux x64, frozen on Debian 11.
It requires GLIBC_2.30 or newer -- measured from the
binary's own bundled payload at build time, not assumed from the
build image. Debian 11+ / Ubuntu 20.04+ / RHEL 9+ are comfortably
above it.
There is no arm64 Windows and no arm64 Linux asset in this release,
and no -musl asset. A frozen binary has to be built on the
architecture it runs on, and this release builds on four runners; if
you need an arm64 Linux or arm64 Windows tan, install from source
(pip install ./python) and say so on the issue tracker.
- Every binary +
checksums.txtcarries a GitHub build-provenance
attestation. Verify with:
gh attestation verify <downloaded-file> --repo alplabai/tan-cli
Full detail: CHANGELOG.md · full diff
v0.5.0-rc2
Everything the first real v0.5.0-rc1 run turned up, plus what reviewing those fixes turned up in turn. Six release-blockers — and one theme: almost every defect was a check measuring a proxy instead of the thing it claimed to measure.
Still a pre-release. Nothing on the stable alp-sdk-vscode channel upgrades onto this. Delivery is unchanged from rc1: the extension's odd-minor pre-release channel, prerelease: true / make_latest: false, and both installers resolve latest, which excludes prereleases.
Highlights
tan buildandtan flashcould not findweston a host that had bootstrapped successfully (#289) — every GUI-launched VS Code, which is the extension's normal environment. New sharedtan/core/venv.pyresolver; five scattered private copies deleted.- A bootstrapped venv could be invisible to build and flash (#291) — creation picked the executable directory from disk, resolution picked it from the host, so a Git Bash
Scripts/venv was created and then not found. typer>=0.12was unbounded across a change in what typer is (#293) — now>=0.26,<0.28, both ends measured by wheel inspection.tan initpinned a relative SDK root later invocations could not resolve (#263).tan bootstrapmoved the alp-sdk checkout before it could refuse (#284), and reportedcomplete.after a step had already failed (#285).doctorhad silently dropped six checks the oracle emits (#286, #290, #294), each reintroducing a closed defect — found by one audit rather than one field at a time.tan buildreportedok: truewhen every slice was skipped (#283), and sat silent for 234 s during CMake configure (#287).
Fixed
- Flow D could write the wrong file to a board's MRAM.
flash_args.atocwas the one input never resolved against the build root, so a relativeatoc:— which the repo's own fixtures use — silently changed which file was flashed.- Root cause worth naming:
grep -rn "FLOW_D" crates/returns nothing. Flow D is Python-only, so the Rust oracle's cwd-safety audit was inherited without ever covering the one backend that writes on-die MRAM.
- Root cause worth naming:
tan bootstrapemitted relative paths a consumer could not resolve —data.sdkRoot,workspaceDir,venvDirandzephyrBaseall came back relative from--sdk-root ./alp-sdk, reintroducing #217.--print-envwas emittingexport ZEPHYR_BASE="./zephyr"under a heading reading "Add to your shell profile" — and a profile is sourced from$HOME.tan bootstrap --print-envwrote to stderr, sotan bootstrap --print-env > env.shleft the file empty while the block still appeared on the terminal.doctorrefused hosts that work — a bootstrapped host with noboard.yamlin cwd exited 4; an inconclusive venv probe failed closed where the oracle fails open; an Apple-silicon Mac on an x86_64 Python was told to build on Linux.NO_COLORwas checked two different ways andfaultdecodediverged from the spec and the oracle (#288).tan bootstrap --non-interactiveexited 2 — the literal first-blink command in the docs.- Smaller: the published asset filename leaking into
--help(#280);python_repron nested mappings (#277); theTBDsentinel (#276); avenv_has_usable_pipflake (#250);zephyr_board.pyescapingPINNED_HASHES(#279); andgetting-started.ymlsmoke-testing the Rusttanwhile the release ships a PyInstaller freeze (#278).
Changed
doctor --buildis accepted and inert. The check set is unconditional now, so there is no build-gated half.doctorgainedsdkProvenanceanddata.missingPrerequisites— two envelope keys that did not exist in rc1. The latter restores the extension's one-click dependency install.--fixis still not accepted (#295).alp-sdk-vscodecallstan bootstrapinstead — accepting the flag as a no-op would give users a Fix button that reports success having repaired nothing.
Requires
alp-sdk v0.15.0-rc1. Pairs with alp-sdk-vscode on the pre-release channel.
Full detail: CHANGELOG.md · full diff
Release assets
Four binaries, each a single-file freeze of the Python tan:
tan-x86_64-pc-windows-msvc.exe-- Windows x64tan-x86_64-apple-darwin/tan-aarch64-apple-darwin-- macOStan-x86_64-unknown-linux-gnu-- Linux x64, frozen on Debian 11.
It requires GLIBC_2.30 or newer -- measured from the
binary's own bundled payload at build time, not assumed from the
build image. Debian 11+ / Ubuntu 20.04+ / RHEL 9+ are comfortably
above it.
There is no arm64 Windows and no arm64 Linux asset in this release,
and no -musl asset. A frozen binary has to be built on the
architecture it runs on, and this release builds on four runners; if
you need an arm64 Linux or arm64 Windows tan, install from source
(pip install ./python) and say so on the issue tracker.
- Every binary +
checksums.txtcarries a GitHub build-provenance
attestation. Verify with:
gh attestation verify <downloaded-file> --repo alplabai/tan-cli
v0.5.0-rc1
The first release in which tan is a Python program: the planner moved into it, so tan now plans and executes, and the four assets are PyInstaller freezes of python/ rather than cargo builds of crates/.
No stable user upgrades onto this release. The tag publishes with prerelease: true / make_latest: false, and both installers resolve latest through GitHub — which excludes prereleases — so install.sh and install.ps1 still fetch the last stable release. SUPPORTED_CLI_VERSION in alp-sdk-vscode moves to 0.5.0-rc1 only on that extension's pre-release channel (extension v0.5.0, an odd minor); stable extension users stay on an even minor pinned at the Rust tan until GA (#268). Everyone else installs by hand.
Highlights
- The planner relocated into
tan. alp-sdk'sscripts/alp_orchestrate/(20 modules, ~6.2k lines) is nowpython/tan/planner/, andtan buildrenders the build plan in-process instead of shellingPYTHONPATH=<sdk>/scripts python -m alp_orchestrate --emit build-plan. - Four PyInstaller assets replace the eight-way cargo matrix. The Linux binary is
-gnuand honestly named: it is frozen inpython:3.12-slim-bullseye(Debian 11, glibc 2.31). tan build --executeruns a plan that arrived via--plan-from.tan debug-configresolves a real J-Link device / pyOCD target id from the SDK, before a project has ever been built.- A pending
TBDplaceholder can no longer reach a flasher (#222) — on both the Python and the Rust side. tan monitorwould have been a dead command in every published binary; themonitorextra is now frozen in.install.shno longer hands a musl host a binary that cannot exec.
Added
tan build --execute— run a plan produced by--plan-from, instead of re-planning it.tan debug-configfillsdevice/targetId/configFilesfrom the SoC's publishedvariants[].debugblock (alp-sdk#1026), and discloses withdebug-config.sdk-identity-overwritewhen doing so replaces a value you filled in by hand.
Changed
scaffold,completion,diff,pinmux,inspectandtraceare not ported yet — see #260 for the deferred set.
Fixed
- A pending
TBDplaceholder reached real flashers (#222).flash_method: TBDfailed safely against the backend registry, butoutput_artefact/firmware_path: TBDresolved to<build_root>/TBDand reached a real J-Link write. One definition (flash_plan.is_pending) now answers it for the whole path; the Rust half fixed the same defect infa_str/fa_str_checked. tan initcould pin a customer to the WRONG SDK, permanently (#263).tan validateanswered "not ported yet" (exit 1) in a fresh project where the shipped binary answersvalidate.board-yaml-missing(exit 2); an empty or non-mappingboard.yamlis now refused at exit 2 instead of reported clean.tan sdk install/tan sdk switchrefused at exit 5 (InternalFailure) where the oracle refuses at exit 1.tan <cmd> | headexited 1 on Linux and macOS where the oracle exits 0. The defect was POSIX, not Windows — an earlier note had it backwards.project.boardYamlnow agrees with the filesystem in both directions (#236, #170) — the field's contract has always read "if found", and it now reportsnullwhen nothing is there.tan build --pristinereports the slices it did not wipe (#183).- Linux and macOS see the manual-install hints the SDK provides for them (#230).
- The npm shim asked for a
-muslLinux asset this release does not publish, so every npm install on Linux x64 requested a 404.
What this RC has and has not been exercised on
- Built and driven end to end on Windows before the tag:
init --from-example peripheral-io/blink->validate --offline->generate --all(9/9 targets), all six bundled--templatestarters, and the packaged-binary conformance suite (7/7). - The macOS and Linux binaries are built by CI and self-verified there, but have not been driven through a real project by hand.
tan buildagainst real silicon is not covered by any of that. Exercising it is what this RC is for.
Release assets
Four binaries, each a single-file freeze of the Python tan:
tan-x86_64-pc-windows-msvc.exe-- Windows x64tan-x86_64-apple-darwin/tan-aarch64-apple-darwin-- macOStan-x86_64-unknown-linux-gnu-- Linux x64, frozen on Debian 11.
It requires GLIBC_2.30 or newer -- measured from the
binary's own bundled payload at build time, not assumed from the
build image. Debian 11+ / Ubuntu 20.04+ / RHEL 9+ are comfortably
above it.
There is no arm64 Windows and no arm64 Linux asset in this release,
and no -musl asset. A frozen binary has to be built on the
architecture it runs on, and this release builds on four runners; if
you need an arm64 Linux or arm64 Windows tan, install from source
(pip install ./python) and say so on the issue tracker.
- Every binary +
checksums.txtcarries a GitHub build-provenance
attestation. Verify with:
gh attestation verify <downloaded-file> --repo alplabai/tan-cli
Full detail: CHANGELOG.md · full diff
v0.4.1
The documented Quickstart works end to end for the first time — and tan bootstrap now exits 1 instead of 0 when it could not install what your next command needs.
Read the breaking change first. This is a patch number, so it does not carry the warning: tan bootstrap used to print bootstrap: complete. and exit 0 after failing to install the Python dependencies its own next step needs. It now prints bootstrap: INCOMPLETE, names which installs failed, and exits 1. Anything scripting that exit code on such a host sees 1 where it saw 0. Pass --allow-partial to accept the workspace as-is. Every phase is still non-fatal and the workspace is still left on disk — only the verdict changed (#220).
First release paired with alp-sdk v0.14.0.
Highlights
tan initcan find the SDKtan bootstrapjust created — the documented Quickstart was broken at step 3 (#218).- The Zephyr SDK finally carries a Fix button, and 7-Zip has a real check instead of one line of prose (#203, #204, #210).
tan doctorprints its remedies without--verboseand reads the debug target fromboard.yamlinstead of hardcodingnative-host(#208).tan bootstrapwill not report a success it cannot back up — breaking, see above (#220).- CI now runs the commands a customer types:
install.sh→bootstrap→doctor→init→build, on a runner that starts with none of it (#207). - Every emitted issue code is registered and gated —
envelope-contract.jsongoes from 4 codes to 68 (#219). - A tag whose CHANGELOG section is missing now fails instead of publishing a one-line stub body (#212).
- Re-vendored at alp-sdk
v0.14.0, macOS prerequisites included (#231).
Changed
- BREAKING —
tan bootstrapexit code. Detailed above.- Before: dependency install fails →
bootstrap: complete., exit0. - After:
bootstrap: INCOMPLETE -- <names> did not install, exit1;--allow-partialrestores exit0. - Applies to
zephyr-requirements,sdk-extrasandeditable-install.pip-upgradeis excluded — the pip already present still installs packages.
- Before: dependency install fails →
- macOS reads its own prerequisite list. alp-sdk v0.14.0 added
xzandwgettoprerequisites.posixplus aprerequisites.macosthat omits them; tan keyed off anis_windowsbool, which would have madetan bootstraprefuse on stock macOS (#231). envelope-contract.jsonnow carries the whole issue-code registry, all three statuses, not a frozen-only subset (#219).tan bootstrapreports absolute paths indata.*and--print-env, matchingproject.rootin the same envelope (#217).
Added
tan debug-config --svd <PATH>— the first and only producer ofLaunchResolution.svd(#197).--allow-partialontan bootstrap— report success despite a failed dependency install (#220).sevenZipcheck intan doctor --build, native Windows only and only while the Zephyr SDK is still absent (#204).
Fixed
- SDK discovery now considers a checkout that is a child of the workspace root — it checked self, siblings and ancestors, never a child, which is exactly where the documented flow puts it (#218).
zephyrSdkreachesmissingPrerequisiteswith a runnablewest sdk install --version 1.0.1 -t arm-zephyr-eabirather than being structurally invisible to consumers (#203, #210).tan doctorno longer hides every remedy it computed behind--verbose(#208).debug-configkeeps the comments in yourlaunch.jsoninstead of reserialising the document (#182).debug-config'sbaremetal-mcudraft carriesconfigFiles/targetId, so OpenOCD no longer gets aserverpathwith nothing to load (#139).- The workspace guard records where it moved your checkout, so the next documented command still resolves (#206).
- The npm shim's version tracks the workspace, and
verify-versionnow fails a tag where it does not (#202).
Install
One-liner — resolves this release, verifies the sha256 against checksums.txt, and installs:
curl -fsSL https://raw.githubusercontent.com/alplabai/tan-cli/main/install.sh | shirm https://raw.githubusercontent.com/alplabai/tan-cli/main/install.ps1 | iexOr pick an asset directly:
| Platform | Asset |
|---|---|
| Linux x86_64 | tan-x86_64-unknown-linux-musl |
| Linux arm64 | tan-aarch64-unknown-linux-musl |
| macOS Intel | tan-x86_64-apple-darwin |
| macOS Apple Silicon | tan-aarch64-apple-darwin |
| Windows x64 | tan-x86_64-pc-windows-msvc.exe |
| Windows arm64 | tan-aarch64-pc-windows-msvc.exe |
crates.io (Rust 1.86+, edition 2024) — published as alp-tan-cli; the installed binary is still tan:
cargo install alp-tan-cli --lockednpm is not available. @alplabai/tan does not exist on the registry at any version — the publish job failed with npm error code EOTP because the configured token requires an interactive one-time password (#233). Use a release asset, the installer, or crates.io.
Release assets
-gnuLinux binaries are cross-built with a pinned glibc 2.31
floor -- they run on Debian 11 (bullseye) / Ubuntu 20.04 and newer.
-muslLinux binaries are fully static and run on any distro/libc.- Every binary +
checksums.txtcarries a GitHub build-provenance
attestation. Verify with:
gh attestation verify <downloaded-file> --repo alplabai/tan-cli
Full detail: CHANGELOG.md · full diff
v0.4.0-rc1
Added
- The JSON envelope vocabulary alp-sdk-vscode gates on is now a frozen,
tested, published contract (#106). The extension matches four issue codes
with===and reads a dozendatafield names behind?? []fallbacks, and
every one of those matches fails open — rename any and the extension does
not error, does not log and does not warn, it silently skips the check or
renders stale data, CI green on both sides. The headline case: rename
data.somsand the New Project wizard falls back to a static catalogue that
carries nocores, so a heterogeneous SoM scaffolds single-core with no IPC.
The reference part E1M-AEN801 is multi-core, so that is the default path, not
an edge case.contract/issue-codes.jsonis the single source for the frozen codes
(bootstrap.windows-unsupported— retired but RESERVED,
bootstrap.yocto-host,bootstrap.prerequisites-missing,
presets.sdk-root-unresolved), gated byfrozen_issue_codesin
crates/tan-cli/tests/contract.rs. The consumer is deliberately NOT
loosened to prefix matching: a prefix match onbootstrap.would swallow
codes it has no verdict for.- Four new golden envelopes extend the existing
contract/envelopes/suite
(12 → 18 tests):presets-no-sdk,presets-heterogeneous-som(ana55
yocto +m33zephyr fixture SoM — the worked example above, made
executable),explain-overview, andexamples-catalog. A case fixture can
now be a directory tree, so a case can carry a syntheticsdk/checkout
and pass--sdk-root ./sdk. doctor --build'sdatakeys get a key-set assertion rather than a golden
(its values are host facts):summary.{pass,warn,fail},nextSteps,
checks[].{name,status}, and the literal check nameworkspace.- Tagged releases now publish
envelope-contract.jsonbeside the
binaries — the frozen codes plus one golden envelope per command family —
so the extension's contract test can diff against a published artefact
instead of a hand-copied fixture. - Two consumer fields stay UNCOVERED and are documented as such in
contract/README.mdrather than quietly omitted:build --materialise's
data.written(needs a resolvable SDK + a Python spawn) andsdk list's
data.releases(network).
tan sdk listcarries GitHub'sdraft/prereleaseflags through (#122).
Both booleans were already in the Releases API responsetanparses but were
dropped before reaching either the JSON envelope or the text table — a
consumer asking "what is the latest SDK?" could not tell a release candidate
apart from a genuine release, with no error and no log line.SdkRelease
now carriesdraft/prerelease(defaultfalsewhen GitHub omits or
misencodes either key, never a reason to drop the release), and the
tan sdk listtable marks a flagged entry with[draft]/[prerelease].
tan does not filter on either flag or add a--include-prereleasesswitch —
the consumer decides what "latest" means; tan's job is only to publish the
fact it already has instead of destroying it. One caveat:fetch_releases
sends noAuthorizationheader, and GitHub returnsdraft: trueentries
only to a caller with push access, so against the publicalp-sdkrepo
[draft]never renders today — it activates the moment a token is added.tan doctor --buildchecksgit,python,dtcandgperf, and every
check can now carry a resolvedversion(#120, #123). Four of the six
host tools a build needs were previously invisible todata.checks[];git
andpythonare checked unconditionally (every backend's build-plan
emission runsalp_project.pythrough both, not just Zephyr's),dtcand
gperfare gated on the Zephyr entry indata.osSet(Yocto/baremetal-only
projects never see them) and — matching the retiredalp doctor's own
_check_dtc/_check_gperf— staywarnrather thanfail.python
reports a version FLOOR, not bare presence: an interpreter below the
manifest'spythonMinVersionfails with its own detail, distinct from "not
found".- Each
data.checks[]entry gains an optionalversion— absent, never
null, when unresolved or not meaningful (zephyrSdk,vendorToolchain)
— reporting whatever tan itself resolved rather than leaving a consumer to
re-probe PATH and risk a second, disagreeing answer.westResolved's
version comes from the SAME workspace-venv resolver its status does,
kept independent ofwest's bare-PATH version so the two rows can never
be attributed to the wrong resolver. missingPrerequisites[].toolfor python is now host-correct —python3
on a served POSIX host,pythonon Windows — matchingprerequisites. install.linux/.macos's own key andtan bootstrap'sposix_refusal
naming for the identical missing tool, instead of alwayspython, which
a POSIX consumer could not re-key back into that same install map.
- Each
- Vendor
board-diagnosticsandiot-starterfrom the SDK scaffold catalog
(#14). Closes out the last two vendorable entries from alp-sdk#864's
scaffold catalog (added by alp-sdk#903):board-diagnosticsnow emits the
SDK's real board self-test app (SoM/SoC identity, RUN operating-point
profile, on-module I2C management-bus scan) for both
E1M-AEN801/E1M-V2N101, andiot-starteremits the SDK's real Wi-Fi +
mqtts://MQTT/TLS telemetry app on the CC3501E bridge —E1M-AEN801only,
matching the SDK catalog's AEN-only + preview status.iot-starternarrows--somtoE1M-AEN801: any other SKU is rejected
withinit.invalid-sombefore a single file is planned, never a silent
fall-back onto the retired hand-written generator.
- The JSON envelope now names which alp-sdk root a command actually
resolved (#110). A new optional top-levelsdk: { root, sourceTier }key
reports the exact path + precedence tier (sdkRootFlag/projectPin/
globalDefault/discovery) the command used — so a consumer (the vscode
extension) can finally tell which SDK produced a result instead of guessing,
especially on the unpinned/first-run path where discovery now walks up to
an enclosing checkout (#101).- Populated from a value RECORDED at the moment one of
tan's three
resolvers actually resolved something, never from a second, fresh
resolution — the three resolvers have different candidate sets, so
re-resolving to fill the envelope could report a path the command never
actually used. - Absent entirely (not
null) when nothing resolved, keeping every
existing contract golden byte-identical.
- Populated from a value RECORDED at the moment one of
tan renode --sim-modeserves the studio hardware-simulator socket contract
(#77, socket half). The flag existed for CLI-surface stability but errored
"not yet ported", so studio had nothing to connect to. It now boots the
--image-bundle's firmware in headless Renode and exposes the two sockets the
gateway needs. The contract was ported from the RETIRED Python
(west alp-renode --sim-mode, deleted inalp-sdk@df312cecunder ADR-0020
Phase 4), not re-derived from issue prose — the prose omits four things the
implementation carries, and all four are honoured: theERR <reason>reply,
theready (timeout <N>s).readiness marker, LOWERCASE0xnnreply hex, and
the SecureSCB->VTOR(0xE000ED08) write the generated boot script needs on
ARMv8-M + TrustZone, whereLoadELFdoes not seed it and the core otherwise
HardFault-storms from address 0.- Both listeners are bound on ephemeral
127.0.0.1ports before the
descriptor names them, so a client that readssim-descriptor.jsonand
connects at once can never race into anECONNREFUSED. They are bound on
port 0 and their assigned ports read back rather than picked-then-rebound,
which removes the Python's bind-then-close TOCTOU window outright. <bundle>/sim-descriptor.jsoncarries exactly the schema's four keys —
control_socket,uart_socket(tcp://127.0.0.1:<port>URIs),
framebuffers,peripherals.- The control socket is line-oriented, one request → one reply, with three
verbs:sysbus ReadBytes <base> <count>(reply normalised from Renode's
bracketed UPPER-case list tocountspace-separated lowercase0xnn
tokens, scoped to the brackets so an echoed command address cannot leak in
as a phantom byte, and a short read is an error never a padded answer);
sysbus WriteBytes <base> <hex…>(expanded to per-bytesysbus WriteByte <base+i>, because Renode's ownWriteBytestakes(bytes, addr)— the
reverse of studio's order); and any other line forwarded verbatim. A
malformed line or monitor fault answersERR <reason>and keeps the
connection, and every reply is flattened to a single line so a client can
never desynchronise. - The
datapayload recordsdescriptor,controlPortanduartPort, so a
JSON consumer reads the descriptor's path and the two ports out of the
envelope instead of assuming<bundle>/sim-descriptor.jsonand parsing them
back out of the file it first has to find. - A CPU that halts on its first instruction fetch fails the run with an
renode.cpu-haltederror issue and exit 1, matching the plain smoke's latch
(issue #64). In sim mode the monitor owns Renode's stdout, so the halt is
latched in the pump thread — a halt landing between two client commands
belongs to no command's collection window, and previously would have
resurfaced at best as anERRon whichever command came next whiletan
still exited 0. Sim mode is exactly where a mis-seeded VTOR shows up this way. - Teardown sends the monitor's
quit, polls up to 1 s for Renode to act on it,
and only then kills — the Python'sterminate()+wait(10)+killon a
shorter budget. Killing immediately after the flush g...
- Both listeners are bound on ephemeral