Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
23815db
fix(flash): refuse a non-raw slot0 artefact, and split the DPIDR refu…
alpCaner Aug 2, 2026
617cf1e
fix(build): fill the Zephyr env gap, and refuse an os:zephyr slice th…
alpCaner Aug 2, 2026
e33104a
test(gates): catch issue codes assembled by a prefixing helper (#224)
alpCaner Aug 2, 2026
49dff72
test(parity): stop frozen fixtures encoding the capture host's tool i…
alpCaner Aug 2, 2026
aad4290
test: scrub ZEPHYR_BASE in the shared fixture, and read REAL_ENVIRON …
alpCaner Aug 2, 2026
b2d95e4
ci(parity): name the repository_dispatch run from client_payload.sdk_…
alpCaner Aug 2, 2026
f9ff8b3
test(gates): key the issue-code gate on stable identifiers, not line …
alpCaner Aug 2, 2026
a03e323
test(parity): measure the v0.6.0 command surface against the oracle i…
alpCaner Aug 2, 2026
f82c3e5
test(gates): register generate.write-escapes-project, and re-pin the …
alpCaner Aug 2, 2026
4fd495b
fix(build): compose #308's ZEPHYR_BASE fill with #336's pop instead o…
Aug 3, 2026
81d03c4
feat: port the seven deferred commands, and close the doctor --fix co…
Aug 3, 2026
00ff6ff
fix: close the wave-1 verification findings, and stop the suite lying…
Aug 3, 2026
7501057
fix: one shared global-flag registration, and stop the freeze re-extr…
Aug 3, 2026
063c4b1
fix: validate says what it looked for, sdk list stops failing offline…
Aug 3, 2026
a4ef232
fix(flash): resolve the sibling .bin for a slot0 loadbin, and name pr…
Aug 3, 2026
59ee11f
test(e2e): vendor the cross-platform harness, and stop it reporting f…
Aug 3, 2026
1cab240
fix(net): put certifi UNDER truststore so a host with no OS CA store …
Aug 3, 2026
624d2c2
fix: name doctor --build --fix on a clean host, and stop the harness …
Aug 3, 2026
11800b5
chore: pin LF for every tracked file and renormalize the branch (#364)
Aug 3, 2026
13891f1
test(e2e): make the harness able to fail, and pin what it measures (#…
Aug 3, 2026
72485d4
fix: stop the new --fix hint promising an install it cannot perform o…
Aug 3, 2026
92710b7
fix: contain pinmux --family inside the SDK, and stop verify_binary.s…
Aug 3, 2026
6f791cf
fix: name a missing installer instead of skipping silently, and stop …
Aug 3, 2026
83344c8
fix(scaffold): point minimal-app's board.yaml at the CMakeLists west …
Aug 3, 2026
285f376
fix(release): pick the asset shape from checksums.txt, and stop the f…
Aug 3, 2026
8ca12c9
ci(release): keep one verify_binary.sh call site on a relative path (…
Aug 3, 2026
2007e06
fix(support-bundle): match the oracle's verdict and report, and regis…
Aug 3, 2026
e84c690
fix(flash): sign, refuse and diagnose Flow D correctly, without destr…
Aug 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,3 +1,24 @@
# Repo default: LF in the blob AND in the working tree, for every tracked
# file. tan-cli#364: `feat/v06-batch` was authored largely from a Windows box
# whose `core.autocrlf` is `true`, and 111 files came back CRLF-converted --
# `.github/workflows/release.yml`, `python/tan/cli.py`, `doctor_cmd.py`,
# `flash_cmd.py`, the parity fixtures, whole test modules. Every one of them
# then reads as a full-file replacement in review (a 38-line change to
# `bootstrap.py` rendered as 3668 changed lines) and `git diff --check`
# reported 11,788 findings, so the real diff could not be audited at all.
#
# The specific `text eol=lf` pins below predate this and stay: each one records
# WHY that path must be LF regardless of the default (byte-exact vendoring,
# a script that executes on a customer's machine, a gate anchored on `
`).
# This line is the blanket floor under them, not a replacement for them.
#
# Every tracked file in this repo is text -- .py .rs .yaml .txt .md .json .c
# .conf .exit .yml .sh .toml .h .js .zsh .ps1 .lock .fish .env .bash -- so
# `text=auto` has no binary to misdetect here. Adding a binary asset later
# needs its own `-text` line.
* text=auto eol=lf

# Vendored `alp-sdk --emit scaffold` output is baked into the binary via
# include_str! and byte-compared against a fresh LF emit by the cross-repo
# parity gate. Force LF so a Windows CI checkout (autocrlf=true) cannot
Expand Down
76 changes: 52 additions & 24 deletions .github/workflows/clean-host.yml
Original file line number Diff line number Diff line change
Expand Up @@ -219,8 +219,8 @@ jobs:
# A CLEAN venv, not the runner's shared interpreter -- PyInstaller
# bundles whatever its hooks can see (measured 34349423 B dirty vs
# ~13.7 MB clean in release.yml's own header). `.[monitor]` matches
# release.yml/getting-started.yml: an extra a --onefile binary
# advertises in --help must actually be bundled.
# release.yml/getting-started.yml: an extra a frozen binary advertises
# in --help must actually be bundled.
- name: freeze tan (clean venv)
if: ${{ !matrix.container }}
shell: bash
Expand Down Expand Up @@ -255,30 +255,38 @@ jobs:
# The actual gate: --version / doctor / sdk list --online / bootstrap
# --dry-run, on a genuinely clean HOME + empty cwd, both with and
# without $ZEPHYR_BASE. See clean_host_smoke.py's module docstring for
# the full contract; it is the CONSUMER of build_binary.sh's dist/tan
# (or dist/tan.exe), never a rebuild of it.
# the full contract; it is the CONSUMER of build_binary.sh's onedir
# output at dist/tan/tan (or dist/tan/tan.exe), never a rebuild of it.
# tan-cli#349 moved the executable one level deeper (dist/tan/tan[.exe],
# not dist/tan[.exe]) -- this job runs it straight out of that folder
# rather than unpacking the sibling dist/tan.zip/.tar.gz archive, since
# both are byte-identical to what the archive contains and skipping the
# unpack step is one less thing that could go wrong here (the archive
# itself IS covered, separately, by release-asset-smoke below, which has
# no unpacked folder to fall back to since it starts from the download).
#
# `matrix.ext` picks the exact name, NOT `[ -f python/dist/tan ] ||
# BIN=python/dist/tan.exe` (what this used to be): this step's `shell:`
# is Git Bash/MSYS on the Windows runner, and MSYS's `[ -f ]` reports
# TRUE for an extension-less name whenever a same-stem `.exe` exists --
# it resolves the PE lookup transparently, the same way `CreateProcess`
# would. So `[ -f "python/dist/tan" ]` was true with ONLY `tan.exe` on
# disk, the `||` fallback never ran, and the extension-less path was
# handed to `clean_host_smoke.py`, whose `Path.is_file()` has no such
# magic and correctly reported it missing (tan-cli#303 CI finding). The
# matrix already knows the platform at schedule time -- deriving the
# name from it is exact and shell-independent; release.yml uses the
# same `matrix.ext` field for the same reason.
# `matrix.ext` picks the exact name, NOT `[ -f python/dist/tan/tan ] ||
# BIN=python/dist/tan/tan.exe` (what this used to be one level up):
# this step's `shell:` is Git Bash/MSYS on the Windows runner, and
# MSYS's `[ -f ]` reports TRUE for an extension-less name whenever a
# same-stem `.exe` exists -- it resolves the PE lookup transparently,
# the same way `CreateProcess` would. So `[ -f "python/dist/tan/tan" ]`
# was true with ONLY `tan.exe` on disk, the `||` fallback never ran, and
# the extension-less path was handed to `clean_host_smoke.py`, whose
# `Path.is_file()` has no such magic and correctly reported it missing
# (tan-cli#303 CI finding). The matrix already knows the platform at
# schedule time -- deriving the name from it is exact and
# shell-independent; release.yml uses the same `matrix.ext` field for
# the same reason.
- name: clean-host smoke (--version / doctor / sdk list --online / bootstrap --dry-run)
shell: bash
env:
TAN_BIN: python/dist/tan${{ matrix.ext }}
TAN_BIN: python/dist/tan/tan${{ matrix.ext }}
run: |
set -euo pipefail
if [ ! -f "$TAN_BIN" ]; then
echo "::error::expected the freeze at ${TAN_BIN} (matrix ext '${{ matrix.ext }}') but it is missing. Checked only that exact name -- not a [ -f tan ] || [ -f tan.exe ] probe, which MSYS/Git-Bash cannot answer correctly (see the comment above this step)."
ls -la python/dist || echo "python/dist does not exist at all -- the freeze step above did not run or did not produce it."
ls -la python/dist/tan 2>/dev/null || ls -la python/dist || echo "python/dist does not exist at all -- the freeze step above did not run or did not produce it."
exit 1
fi
python python/scripts/clean_host_smoke.py --tan "$TAN_BIN"
Expand All @@ -295,14 +303,20 @@ jobs:
fail-fast: false
matrix:
include:
# Asset names mirror release.yml's contract exactly (tan-cli#349):
# one archive per target, not a raw binary.
- os: windows-latest
asset: tan-x86_64-pc-windows-msvc.exe
asset: tan-x86_64-pc-windows-msvc.zip
ext: .exe
- os: macos-15-intel
asset: tan-x86_64-apple-darwin
asset: tan-x86_64-apple-darwin.tar.gz
ext: ""
- os: macos-15
asset: tan-aarch64-apple-darwin
asset: tan-aarch64-apple-darwin.tar.gz
ext: ""
- os: ubuntu-latest
asset: tan-x86_64-unknown-linux-gnu
asset: tan-x86_64-unknown-linux-gnu.tar.gz
ext: ""
runs-on: ${{ matrix.os }}
timeout-minutes: 10
permissions:
Expand Down Expand Up @@ -337,8 +351,22 @@ jobs:
mkdir -p dl
gh release download "$tag" --repo alplabai/tan-cli \
--pattern "${{ matrix.asset }}" --dir dl --clobber
chmod +x "dl/${{ matrix.asset }}"

# tan-cli#349: the published asset is now an archive of a --onedir
# freeze, not a raw executable -- unpack it before the smoke test can
# run anything. `shutil.unpack_archive` (stdlib, already have Python
# 3.12 from the setup-python step above) picks zip vs tar.gz from the
# extension itself, so this one call covers every leg in the matrix
# without a platform-specific unzip/tar branch. The archive's own
# top-level entry is `tan/` (see build_binary.sh), so the unpacked
# binary lands at dl/unpacked/tan/tan[.exe].
- name: unpack the downloaded archive
shell: bash
run: |
set -euo pipefail
python -c "import shutil; shutil.unpack_archive('dl/${{ matrix.asset }}', 'dl/unpacked')"
chmod +x "dl/unpacked/tan/tan${{ matrix.ext }}"

- name: clean-host smoke against the downloaded asset
shell: bash
run: python python/scripts/clean_host_smoke.py --tan "dl/${{ matrix.asset }}"
run: python python/scripts/clean_host_smoke.py --tan "dl/unpacked/tan/tan${{ matrix.ext }}"
53 changes: 50 additions & 3 deletions .github/workflows/getting-started.yml
Original file line number Diff line number Diff line change
Expand Up @@ -221,9 +221,56 @@ jobs:
/tmp/venv/bin/pip install --quiet ".[monitor]" "pyinstaller>=6.10"
PYTHON=/tmp/venv/bin/python bash scripts/build_binary.sh
'
install -m 0755 python/dist/tan "$HOME/.local/bin/tan"
command -v tan
tan --version
# build_binary.sh always emits a --onedir freeze (tan-cli#349):
# python/dist/tan/ is a DIRECTORY (tan + _internal/), not a single
# executable. What the install.sh step above left at
# $HOME/.local/bin/tan is NOT fixed, though, since tan-cli#356: for
# the LATEST release right now (v0.4.1) install.sh's own
# checksums.txt lookup finds no archive asset, takes the raw-binary
# branch, and installs the raw v0.4.1 executable straight to
# $HOME/.local/bin/tan with NO tan-cli-lib/ at all -- there is then
# no launcher to "swap the library tree" of, and the old
# `cp -r ...tan-cli-lib` + assume-a-launcher-exists version of this
# step left that raw v0.4.1 binary in place, untouched, on PATH.
# Every step below would then run v0.4.1, not this PR, with `tan
# --version` still reporting exit 0 -- the exact silent-pass this
# job's own file header exists to prevent.
#
# So this step no longer assumes a launcher is there to retarget: it
# (re)writes ONE, unconditionally, mirroring install.sh's own
# archive-layout launcher byte for byte -- overwriting a raw
# v0.4.1 executable, a stale launcher, or nothing, alike -- and then
# PROVES the swap took by comparing `tan --version` against this
# checkout's own TAN_VERSION rather than merely checking exit 0
# (which a stale v0.4.1 binary also returns).
rm -rf "$HOME/.local/bin/tan-cli-lib"
cp -r python/dist/tan "$HOME/.local/bin/tan-cli-lib"
chmod +x "$HOME/.local/bin/tan-cli-lib/tan"
cat >"$HOME/.local/bin/tan" <<'LAUNCHER'
#!/bin/sh
# Generated by getting-started.yml, mirroring install.sh's own
# archive-layout launcher (tan-cli#349) -- do not edit by hand.
exec "$HOME/.local/bin/tan-cli-lib/tan" "$@"
LAUNCHER
chmod +x "$HOME/.local/bin/tan"

pr_version="$(sed -n 's/^TAN_VERSION = "\(.*\)"$/\1/p' python/tan/version.py)"
if [ -z "$pr_version" ]; then
echo "::error::could not read TAN_VERSION out of python/tan/version.py -- the pattern this step's sed matches moved." >&2
exit 1
fi
resolved="$(command -v tan)"
if [ "$resolved" != "$HOME/.local/bin/tan" ]; then
echo "::error::'tan' resolves to ${resolved}, not \$HOME/.local/bin/tan -- something earlier on PATH shadows the swapped-in binary, so every step below would still test the wrong tan." >&2
exit 1
fi
actual="$(tan --version)"
expected="tan ${pr_version}"
if [ "$actual" != "$expected" ]; then
echo "::error::the PR-freeze swap did not take: 'tan --version' printed '${actual}', expected '${expected}' (this checkout's python/tan/version.py). Every step below would be exercising a stale release binary instead of this PR -- see tan-cli#356." >&2
exit 1
fi
echo "swapped in: ${actual}"

# ---- 5. the SDK checkout --------------------------------------------

Expand Down
15 changes: 15 additions & 0 deletions .github/workflows/parity.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,21 @@

name: parity

# tan-cli#213: surface `client_payload.sdk_ref` in the Actions API's own
# `display_title` (what `run-name` sets) rather than only a job-log `::notice::`
# (see the "resolve alp-sdk ref" step below) -- alp-sdk's dispatch-confirmation
# poll can then filter on the exact ref it sent instead of "any run created
# after our dispatch epoch", which a concurrent push or other sender also
# satisfies. Only `repository_dispatch` carries `client_payload`; the other
# three triggers below (`push`, `pull_request`, and `workflow_call` from
# release.yml) render an empty string instead, which is NOT a blank/broken
# title -- GitHub's own rule is that an omitted-or-whitespace-only `run-name`
# falls back to the event-specific default (the commit message on `push`, the
# PR title on `pull_request`), and that default is strictly more informative
# than a constant `parity (push)` string would be. Same empty-string-means-
# "use the default" idiom as the `ref:` step below.
run-name: ${{ github.event_name == 'repository_dispatch' && format('parity (sdk {0})', github.event.client_payload.sdk_ref) || '' }}

on:
# Direct pushes to `main` (an admin merge, a hotfix, a back-merge) open no PR,
# so without this the three jobs below never ran on those commits at all --
Expand Down
Loading
Loading