Skip to content

feat(pin-move-verify): build the SENDER -- dispatch a proposed tuple to alp-e2e when a PR touches a pin site - #823

Open
alpCaner wants to merge 5 commits into
devfrom
feat/820-pin-move-verify-sender
Open

feat(pin-move-verify): build the SENDER -- dispatch a proposed tuple to alp-e2e when a PR touches a pin site#823
alpCaner wants to merge 5 commits into
devfrom
feat/820-pin-move-verify-sender

Conversation

@alpCaner

@alpCaner alpCaner commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Summary

Builds the SENDER half of ADR-0029's cross-repo pin-move-verify loop
(alp-sdk docs/adr/0029-cross-repo-pins-are-typed-lock-entries-with-a-property-gate.md,
alp-sdk#1474) in this repo. The RECEIVER already exists on alplabai/alp-e2e
PR #1 (.github/workflows/pin-move-verify.yml + alpe2e/pinverify.py +
journeys/build-tuple.sh), proven on two real tuples on E1M-V2N101
(v0.5.1 x v0.15.0 FAILS as expected; v0.6.0-rc1 x v0.16.0-rc1 PASSES).
This PR does not touch alp-sdk or alp-e2e — the sender adapts to the
receiver's existing, already-proven contract.

  • python/scripts/pin_move_verify.py — the pure logic: pin-site detection,
    (tan_ref, sdk_ref, soms) payload construction/validation (mirrors
    alpe2e.pinverify's own _REF/_SKU/_REPO/_SHA patterns and
    check_name byte-for-byte, not a second schema), and the fail-closed
    verdict judge. See its own module docstring for the full pin-site sweep
    and the reasoning behind what's in PIN_SITES and what's deliberately not.
  • .github/workflows/pin-move-verify.yml — one job, no matrix, static
    name: pin-move-verify · sender (the literal string branch protection
    should require once it's safe to — see "What remains unproven" below).
    Detects a touch to a tracked pin site, resolves tan_ref (latest
    published tan-cli release) and sdk_ref (parity.yml's live
    PINNED_SDK_TAG), dispatches to alplabai/alp-e2e, polls for its Check
    Run (bounded, 90 min), and fails closed on anything short of a literal
    success conclusion.
  • python/tests/scripts/test_pin_move_verify.py — 44 tests over the pure
    logic + the CLI subcommands (including two through a real throwaway git
    repo, proving the git diff-based detector end to end offline).
  • changelog.d/820.added.md.

Closes #820.

Pin sites found (the full sweep)

Searched for every literal alp-sdk ref (tag/branch/40-char SHA) this repo
pins against, starting from test_planner_relocation_freshness.py per the
task, then a repo-wide sweep for PINNED_SDK_TAG/PINNED_SDK_COMMIT-shaped
constants and every literal 40-char hex string in .py/.yml/.yaml
(grep -noE '[0-9a-f]{40}' across the repo, then hand-triaged each hit —
several are actions/foo@<sha> version pins or historical commit references
in comments, not live alp-sdk-ref pins).

In PIN_SITES (this workflow dispatches on a touch to any of these):

File Constant(s)
python/tests/gates/test_planner_relocation_freshness.py PINNED_SDK_COMMIT, HAND_PORT_PINNED_SDK_COMMIT, STRICT_LOADERS_PINNED_SDK_COMMIT
.github/workflows/parity.yml PINNED_SDK_TAG (workflow-level env:)
.github/workflows/ci.yml the sdk_parity job's alp-sdk checkout ref: (a literal SHA that file's own comment says MUST move in lockstep with PINNED_SDK_TAG)

Found, but this workflow cannot sensibly cover them (see the module
docstring for the full reasoning, not just the one-liner below):

Site Why not
ZEPHYR_SDK_INSTALL_VERSION (python/tan/commands/doctor_cmd.py) Names a Zephyr SDK toolchain release, not an alp-sdk ref — no toolchain_ref field exists in the dispatch contract, and the receiver's journey never reads this constant. Already guarded by tests/parity/toolchain_lock_parity.py (byte-diff vs metadata/toolchains.json) — ADR-0029's "propagated constant" model, not "compatibility declaration".
contract/fixtures/bootstrap/manifest.json, contract/fixtures/toolchains/toolchains.json, tests/fixtures/kconfig-contract/emit-kconfig.golden.json, python/tan/templates/vendored/** Vendored BYTE COPIES of alp-sdk data, not a ref — nothing to extract into a payload's sdk_ref. Their staleness already tracks PINNED_SDK_TAG (see tests/parity/README.md); a build tuple proves "does it compile", not "are these bytes identical to the vendor's" — the existing *_parity.py byte-diffs are the right tool for that claim.
python/tan/version.py's TAN_VERSION This repo's OWN version, not a reference to another repo. Not a cross-repo pin.

I did not find a SUPPORTED_CLI_VERSION-shaped pin in this repo — that one
lives in alp-sdk-vscode (out of scope for this repo's PR; ADR-0029's
Sequencing step 4 names it as check-cli-pin.mjs's own future work,
consuming this contract's attestations from that repo, not this one).

The tuple this sender proposes, and its honest limits

tan_ref is always the latest published tan-cli release — the
receiver's journey installs it through the documented install.sh
one-liner, which cannot fetch an unreleased PR branch. sdk_ref is read
live from .github/workflows/parity.yml's PINNED_SDK_TAG at the PR's own
head — the value the PR is actually proposing. soms defaults to
E1M-V2N101,E1M-AEN801 (the receiver's own worked cost example, ~10
runner-minutes).

This means a PR that re-audits tan/planner/'s fork against a new alp-sdk
commit (the freshness-gate pins) is verified only indirectly: the dispatch
proves "does the currently-shipping tan still build against the alp-sdk ref
this PR wants to pin the audit against", not "does the code this PR itself
changed build against it" — install.sh-based verification cannot ask the
second question. Stated in full in the module's own docstring rather than
glossed over.

Status check name (for branch protection)

pin-move-verify · sender — one job, no matrix, no ${{ }} in the
name:. Do not require it until "What remains unproven" below is
resolved — see that section.

Gates observed failing (with run URLs)

  1. The fail-closed "no App secret" branch, via the real pull_request
    trigger, not a synthetic unit test.
    I temporarily touched
    .github/workflows/parity.yml's PINNED_SDK_TAG comment (a no-op,
    whitespace-only edit next to the pin — reverted in the very next commit)
    to make detect report touched=true on a real PR push. The run:
    • correctly detected the touch: DETECT_TOUCHED: true, and the error
      annotation names the exact site: This run touches a pin site (.github/workflows/parity.yml)
    • resolved tan_ref = v0.5.1 (the real latest published release) and
      sdk_ref = 88318e759958529fbbd8fe9d481373681c0fa78d (parity.yml's live
      PINNED_SDK_TAG at that commit) and built a valid payload
    • reached "is the App provisioned?" → have=false (this repo genuinely
      has no ALP_CI_APP_ID/ALP_CI_APP_PRIVATE_KEY — confirmed via
      gh secret list --repo alplabai/tan-cli, which shows only
      CARGO_REGISTRY_TOKEN/NPM_TOKEN)
    • failed at "refuse to proceed without the App (fail closed)", exit 1,
      citing ADR-0029 clause 2
    • dispatch/poll/judge correctly never ran (skipped)
    • Run: https://github.com/alplabai/tan-cli/actions/runs/31968392592
      (job pin-move-verify · sender, 95216940107)
      The touch to parity.yml was reverted in the next commit; git diff origin/dev...HEAD -- .github/workflows/parity.yml is empty in the final
      diff.
  2. The "nothing to verify" pass path, via this PR's own natural
    pull_request runs
    (this PR's real diff touches none of PIN_SITES):
    DETECT_TOUCHED: falserelevant=false → every
    dispatch/mint/poll/judge step correctly skips → the job exits 0 in ~7s.
    Runs: https://github.com/alplabai/tan-cli/actions/runs/31968117116 (before
    the throwaway touch) and
    https://github.com/alplabai/tan-cli/actions/runs/31969865719 (after the
    revert, on the final diff).

Both observed runs are on this PR's own Actions tab, job pin-move-verify · sender — nothing here was simulated locally.

Local gates (from python/)

py -3.14 -m pytest tests -q: 27 failed, 4233 passed, 328 skipped, 1
xfailed
(repo floor is >=3.12; the bare python on this box is a stale
3.9/3.11 per the repo's own reference_local_python_314 note). Triaged
mine-vs-base: re-ran the identical command against an unmodified
origin/dev worktree (27 failed, 4189 passed — passed-count differs
only because my branch adds 44 new tests, 4189+44=4233) and diffed the
sorted failing-test-ID lists — byte-identical, 27 == 27, all in
tests/installers/test_installer_release_layout.py (PowerShell/registry/ACL
tests that need a real Windows install context this sandboxed shell doesn't
have) and tests/commands/test_execute*.py /
tests/gates/test_planner_resync.py (west/Zephyr-workspace-environment
tests). None of these files are in this PR's diff. This repo's own CI
(python job + all python -- pytest shard (*) legs, ubuntu/macos/windows,
all with ALP_SDK_ROOT bound where applicable) is green on this PR — see
the Checks tab.

zizmor --min-severity medium --no-online-audits .github/workflows/ (the
repo's own workflow-security gate, zizmor 1.29.0) — clean: No findings to report. Good job! (14 ignored, 54 suppressed). Required one fix mid-review:
the App-token mint step needed permission-contents: write (the exact
grant repository_dispatch needs, per dispatch-tan-parity.yml's own
header) — without it, zizmor's github-app audit (High) correctly flags an
unscoped token inheriting the App's full installation permissions.

What remains unproven, stated plainly

The live dispatch end to end. Neither alplabai/tan-cli (this repo) nor
alplabai/alp-e2e has ALP_CI_APP_ID/ALP_CI_APP_PRIVATE_KEY provisioned
(gh secret list checked on both — confirmed absent on this repo; the
receiver's own workflow header says the same for alp-e2e), and alp-e2e#1 is
still an open, unmerged PR. So this sender has never actually reached the
"mint a token" / "dispatch" / "poll" / "judge success" steps for real — only
the "no secret → fail closed" branch has been observed (see above). To prove
the rest, in order:

  1. A human provisions ALP_CI_APP_ID/ALP_CI_APP_PRIVATE_KEY on
    alplabai/tan-cli (mint step) and merges/provisions them on
    alplabai/alp-e2e (Check-Run-write step, per that receiver's own header).
  2. alp-e2e#1 merges.
  3. A real PR here that touches a pin site (or a manual workflow_dispatch,
    once this workflow file is on dev — GitHub does not expose
    workflow_dispatch for a workflow that only exists on a feature branch,
    confirmed: gh workflow run 404s until the file lands on the default
    branch) should then show: a successful dispatch, a completed Check Run
    named pin-verify · <tan_ref> × <sdk_ref> appearing on the commit within
    the poll window, and this job's own judge step reading success
    PASS. Only after that observed, real green run should this job's name be
    set required in branch protection.

I have not claimed any of #3 happened — it hasn't.

…n, payload construction, fail-closed verdict judge (tan-cli#820)
… touches a pin site, fail closed on anything short of success (tan-cli#820)
@alpCaner alpCaner added enhancement New feature or request ci CI workflows, gates and test wiring labels Aug 16, 2026
…k-run race, mirrored-contract drift, check-run identity, fork PRs, injection, and the sweep/certifiability gaps

Ten findings, addressed:

1. Stale Check Run race (finding 1): poll now captures dispatched_at
   before the POST and filters check-runs on
   (.completed_at // .started_at) >= dispatched_at, so a fast-follow
   push or a "Re-run failed jobs" on an unchanged head can no longer
   read a PREVIOUS dispatch's completed Check Run as this one's verdict.
   Demonstrated locally with the real jq filters against a synthetic
   check-runs fixture: the old filter matches a stale success, the new
   one correctly waits.

2. Pin-site sweep (finding 2): tests/parity/scaffold_byte_parity.py's
   _SDK_DOC_LINK_REF (ADR-0029 observation 7's own example, tan-cli#756/
   #766) is now named explicitly in the module docstring. It is
   currently DEAD (frozen at the retired v0.15.0-rc1 literal, exercised
   only by self_check()'s own hardcoded string, no live
   DELIBERATE_EDITS entry uses it) and, like the vendored tree below,
   only ever changes atomically with PINNED_SDK_TAG -- so it stays out
   of PIN_SITES, documented rather than silently missing.

3. False exclusion rationale (finding 3): the "no literal ref" claim
   was false for python/tan/templates/vendored/**'s own MANIFEST.md
   (it carries explicit Ref:/Commit: lines). Replaced with the true,
   evidence-based reason -- every historical re-vendor of that tree
   landed in the SAME commit as a PINNED_SDK_TAG bump and was
   independently re-verified against the live emit, a self-checking
   mechanism with zero documented drift, unlike ci.yml's ref and the
   freshness gate's hashes, which have each drifted for real at least
   once (tan-cli#485, #639). Applied the same criterion to
   test_planner_relocation_freshness.py and reached the OPPOSITE,
   correct conclusion: it stays IN PIN_SITES because its pins move via
   an independent bot (planner_resync.py), not atomically with
   PINNED_SDK_TAG.

4. Self-disabling test (finding 4): test_pin_sites_docstring_names_
   every_dict_entry now asserts every PIN_SITES path exists on disk
   AND is named verbatim in the module docstring -- a rename now goes
   red instead of the gate silently stopping matching forever.

5. Weaker-question gate (finding 5): judge_polled_check_run's PASS
   message now names the PUBLISHED tan_ref and states plainly this is
   not proof of this PR's own code, so a reader of the green
   $GITHUB_STEP_SUMMARY line sees the caveat, not just the docstring or
   workflow header. Every PIN_SITES entry now carries a CERTIFIABILITY
   note.

6. Mirrored contract drift (finding 6): added MAX_SOMS = 8, mirroring
   alpe2e.pinverify.MAX_SOMS, enforced in build_tuple. The whole
   mirrored block (four regexes + MAX_SOMS + check_name's format) is
   now pinned by MIRRORED_CONTRACT_HASH with a new freshness gate,
   test_pin_move_verify_contract_mirror.py, ALP_E2E_ROOT-gated the same
   way test_planner_relocation_freshness.py gates on ALP_SDK_ROOT.
   tan-cli#835 tracks the next re-audit. Observed to fail: patched a
   local alp-e2e checkout's MAX_SOMS, watched the gate go red, reverted.

7. Check Run identity (finding 7): judge_polled_check_run now takes
   expected_check_name/expected_source_sha and fails closed on a
   mismatch, so an unrelated same-named run (e.g. a hand-run
   workflow_dispatch with a different SoM set) can no longer be graded
   as this dispatch's own verdict.

8. Fork PRs (finding 8): the "refuse to proceed without the App" step
   now branches on head.repo.fork with its own message stating plainly
   that provisioning the secrets never fixes a fork PR (ADR-0029 clause
   5 mandates no secret reaches one) -- corrected from the previous
   message, which implied provisioning was the universal fix.

9. Injection (finding 9): the tanref/sdkref steps now apply parity.yml's
   own whole-string `case` guard (not `grep -qE`, per tan-cli#435)
   before a workflow_dispatch override reaches $GITHUB_OUTPUT/::notice::.

10. Nits (finding 10): check_name is read through env: in the dispatch
    step; alplabai/tan-cli is no longer hardcoded in the poll step or
    pin_move_verify.py's SOURCE_REPO default (both now flow
    ${{ github.repository }} via a new --source-repo flag);
    changelog.d/820.added.md now states the tan_ref consequence, not
    just the fact.

Full suite: 4246 passed, 27 failed, 329 skipped, 1 xfailed -- the 27
failures are byte-identical (diffed sorted test IDs) to an unmodified
origin/dev run, all pre-existing Windows-environment failures (PATH/
installer/taskkill) unrelated to this change.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci CI workflows, gates and test wiring enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Build the pin-move-verify SENDER: dispatch a proposed tuple to alp-e2e whenever a PR touches an alp-sdk-ref pin site

1 participant