Skip to content

Native cli artifacts: stage binaries from the R2 registry + close the verify false-pass#42

Merged
Alexgodoroja merged 6 commits into
mainfrom
feat/cli-artifacts-on-main
Jun 24, 2026
Merged

Native cli artifacts: stage binaries from the R2 registry + close the verify false-pass#42
Alexgodoroja merged 6 commits into
mainfrom
feat/cli-artifacts-on-main

Conversation

@Alexgodoroja

Copy link
Copy Markdown
Collaborator

What

Brings native-cli artifact delivery to main: a cli app can ship per-OS/arch binaries from the Pilot R2 artifact registry, and the generated adapter fetches → sha256-verifies → stages → execs them at install. Also closes the false-pass that let broken bundles publish: verify-submission now fails a submission that declares artifacts but whose built bundle lacks install.json/staging wiring.

This is the feature whose absence on main made published cli bundles broken (no install.json/StageAssets; main's BuildBundle silently dropped the submission's artifacts), and whose absence let verify-submission false-pass.

How it works

  • scaffold (config.go, install.go, scaffold.go, templates): assets (per-OS/arch url+sha256+exec_path, with install order/deps/args) generate install.json + install.sh (0o755) and internal/backend/stage.go (StageAssets). At install the adapter selects the host os/arch asset, fetches it, verifies its sha256, stages a single file or a tar.gz (extracted via host tar with a zip-slip name scan), runs ordered install args, and is idempotent via a .staged/<sha> marker. main rewrites the runner's base command to the staged path. Manifest emits fs.read $APP/install.json, fs.write $APP, and per-host net.dial grants only when assets are present. ResolveAssets is a Kahn topo-sort over deps.
  • publish (submission.go, build.go): Submission.Artifacts (+validateArtifacts, wired into Validate) → ToConfig maps them to scaffold.Asset. BuildBundle copies install.json/install.sh into the shared bundle dir before the per-platform tar loop, so every platform tarball ships the spec.
  • verify gate (cmd/pilot-app/main.go): after BuildBundle, when the submission has artifacts, each built platform bundle must contain install.json (≥1 asset) and a manifest with the fs.write $APP staging grant, else verify-submission FAILS.

Validation

  • go build ./... && go vet ./... && go test ./... all green (incl. cross-compiling e2e tests).
  • pilot-app verify-submission against the real miren submission (4 platforms, 4 tar.gz artifacts) now reports native-delivery (install.json): install.json + staging grant present (4 asset(s)) per platform → VERIFY OK.

Ported onto current main (no reverts)

Kept #34 publisher-pin, #37 verify-submission, #38 app_description/descOr. The verify gate is added to #37's cmdVerifySubmission, not a rewrite.

Tests

  • cmd/pilot-app/verify_staging_test.go: a miren-shaped cli+artifacts submission builds a bundle that passes checkStaging on every platform; a bundle with install.json stripped fails (regression guard against reopening the gap).
  • publish: TestCLIAssetsSubmissionBuildsAndVerifies — full pipeline build ships install.json + the delivery grants.
  • scaffold: install_test.go, TestGeneratedCLIWithAssetsCompiles (asset-aware main + stage.go type-check), r2_e2e_test.go (env-gated live R2 delivery e2e).

Deliberately out of scope

  • The R2 upload plane (internal/publish/r2.go presign client + the cmd/publish-server Artifacts form step). This PR is the consume/stage/verify half — the half that fixes broken bundles + the false-pass. The upload UI is a separate, larger surface.
  • scripts/ab_report.py left untouched (keeps #37's --cases; the branch removed it).
  • .github/workflows/submission-validate.yml left as main's — it already runs verify-submission on rich submissions, so the new gate runs in CI automatically. (The branch's older version reverted that to bundle-pointer verify.)

Add native-binary delivery for cli backends. A cli app may now declare
`assets` (per-OS/arch url + sha256 + exec_path, with install order/deps/args);
the generator emits:

  - install.json — the staging spec the adapter reads at startup
  - install.sh   — a transparent direct-install path (0o755)
  - internal/backend/stage.go (StageAssets) — at install the adapter selects
    the host os/arch asset, fetches it, sha256-verifies it, stages a single
    file or a tar.gz (extracted via host `tar` with a zip-slip name scan),
    runs any ordered install args, and is idempotent via a .staged/<sha>
    marker. main rewrites the runner's base command to the staged path, so
    the host need not have the CLI pre-installed.

config.go gains the Asset struct, HasAssets/AssetName/AssetHosts/PrimaryExecPath,
ResolveAssets (Kahn topo-sort over deps with order+name tiebreak), and
validateAssets (cli-only; known os/arch; https url; 64-hex sha; relative
exec_path under $APP; unique order per platform). The manifest emits the
fs.read $APP/install.json, fs.write $APP, and per-host net.dial grants only
when assets are present. No-asset cli apps are unchanged.
Submission gains an `artifacts` array (SubArtifact, mirroring scaffold.Asset)
with validateArtifacts() wired into Validate() — cli-only, known os/arch,
https R2 url, 64-hex sha, relative exec_path, unique install order per
platform — so a publisher gets server-authoritative errors before any build.
ToConfig now maps artifacts → scaffold.Config.Assets.

BuildBundle copies the generated install.json (and install.sh) into the shared
bundle dir before the per-platform tar loop, so every platform tarball ships
the staging spec. This is the root-cause fix for published cli bundles that
silently lacked install.json: main's BuildBundle dropped the submission's
artifacts entirely.
…ships no install.json

Close the false-pass gap: the catalogue review gate only checks the binary
sha/signature and is blind to install.json, so a submission that declared
`artifacts` but whose build dropped them still passed verify-submission — and
published a bundle with no binary to run.

After BuildBundle, when the submission has artifacts, verify-submission now
asserts per platform that the built tarball contains install.json (with at
least one asset) AND a manifest carrying the fs.write $APP staging grant —
proof the adapter is actually wired for delivery, not just that the spec file
rode along. Missing either fails the build.

Tests:
  - cmd/pilot-app: a miren-shaped cli+artifacts submission builds a bundle
    that passes checkStaging on every platform; a bundle with install.json
    stripped FAILS (regression guard against reopening the gap).
  - publish: TestCLIAssetsSubmissionBuildsAndVerifies — full pipeline build
    ships install.json + the delivery grants.
  - scaffold: install_test.go (install.json/install.sh rendering),
    TestGeneratedCLIWithAssetsCompiles (asset-aware main + stage.go type-check),
    r2_e2e_test.go (env-gated live R2 delivery e2e).
Add docs/R2-ARTIFACT-REGISTRY.md (the canonical, implemented design the code
comments reference) and mark docs/NATIVE-APPS.md as superseded for the delivery
model (by-reference URL → Pilot-hosted R2 bytes). Add scripts/e2e-smolvm.sh,
which uploads a real artifact and sets the PILOT_E2E_ASSET_* env vars that the
env-gated scaffold r2_e2e_test.go consumes.
So the staging PR doesn't reintroduce Go-cased changelog keys in metadata.json
regardless of merge order with #41.
@Alexgodoroja Alexgodoroja merged commit 4e82617 into main Jun 24, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant