Private Cargo build inputs

Publish locked private Git crates on a host that can read those repositories. The build worker receives only the immutable, checksummed input:

stado release catalog pin-input /path/to/product \
  --name private-cargo-sources --source /path/to/product \
  --revision HEAD --cargo --json

--cargo uses Cargo's real vendoring and includes only the Git packages named by Cargo.lock. It verifies that Cargo manifests and lockfiles agree with the requested committed revision, and checks them again after export. Scratch files live in private-cargo-sources/ of the source checkout's build area under Stado's home (~/.stado/products/builds/<the checkout's path below the home>), never in the checkout, and are removed when publication finishes or fails. No build is started.

The command stores the archive create-only by SHA-256, then atomically writes inputs.private-cargo-sources in the product's .wisent-release.json. Its receipt names the committed source, URI, digest, byte count and manifest. Commit the changed manifest before submitting a release. Repeat publication when a private package name, version or Git revision changes; changing only the consumer's own version does not invalidate an existing input.

Without --cargo, pin-input publishes the requested Git tree as the build reads it, archived under <name>/ and mounted extracted at WISENT_INPUT_<NAME>_DIR: the commit's files, every submodule's files at the commit the tree records for it, and every symlink inside the tree replaced by the file or folder it names, so the worker's extraction holds regular files only. A symlink that leaves the tree or names nothing is refused by name, and so is a submodule whose recorded commit its checkout does not hold (git submodule update --init --recursive first). --path can select repository paths in that mode; a path inside a submodule is refused with the submodule's folder to name instead. Cargo mode refuses --path, a name other than private-cargo-sources, uncommitted Cargo declarations, an empty private-package set and failed vendoring or storage. The Git refusal includes its output; a changed lockfile reports Cargo.lock changed while exporting private sources; nothing was published. An invalid manifest is refused before export.

--swiftpm publishes a Swift package's resolved dependencies for a desktop release that builds offline with swift build --disable-automatic-resolution:

stado release catalog pin-input /path/to/desktop \
  --name swiftpm-cache --source /path/to/desktop --revision HEAD --swiftpm --json

It runs swift package resolve --disable-automatic-resolution with a scratch path of its own, so the resolution is exactly what the committed Package.resolved pins, using this machine's Git credentials for private packages. The archive's entries start at .build/ (checkouts/, repositories/, artifacts/, workspace-state.json) and are written in name order with cleared archive owners and times. Binary artifact paths in the workspace state are relative to that cache, not to the publisher's temporary directory. Archive bytes can differ between resolutions; the pinned SHA-256 identifies the actual published bytes. The input has mount <name>.tar.gz and extract: false. The release recipe restores it before calling Swift:

stado product swift --package-path "$WISENT_SOURCE_DIR" \
  --archive "$WISENT_INPUT_SWIFTPM_CACHE_DIR" restore

restore safely extracts into staging, checks the dependency set and actual Git checkout revisions against the destination's Package.resolved, resolves each binary artifact beneath the new cache, and then places the cache. It does not compile or fetch dependencies. A repeated restore of the same archive checks the checkouts and artifact files and returns reused: true; a different archive or an existing unowned cache is refused instead of overwritten. Use a fresh release source directory for a different input. Unrelated compiler outputs remain in place.

Missing files, unsafe archive members, revision mismatches, publisher-local artifact paths and cleanup failures are reported with the failed path or dependency. Older archives with absolute artifact paths must be republished. Both the publisher and the release worker must support portable Swift inputs; plain tar extraction is not a substitute for restoration. A Package.swift or Package.resolved that differs from the revision, before or after resolving, is refused with nothing was published; a failed resolve reports SwiftPM's own output and exit status. A manifest sandbox refusal such as sandbox_apply: Operation not permitted stops publication with that cause and says what it means and where to go: the shell running pin-input is itself sandboxed (an agent session, for one) and SwiftPM's manifest sandbox cannot nest inside it; run the command from a shell outside the sandbox, or have the Stado service run it, which is unsandboxed - Releases → Publish an immutable build input in Stado Desktop, or POST /api/operator/run on the service's API with X-Stado-Action: operator-command, args ["release","catalog","pin-input",…] and confirmation: RUN_MUTATION. The service's process must be allowed to read the checkout and the source; on macOS that is the folder access the system grants it, and a folder it is not granted answers Operation not permitted from that route as the command wrote it. Stado does not retry with SwiftPM's sandbox disabled or infer permission to weaken isolation from an error message: a resolution made with the sandbox off would publish bytes no sandbox had watched being made. CLI and Desktop show the same refusal; no new input is pinned after a failed resolution. --swiftpm cannot be combined with --cargo, --git-bundle or --path.

In Stado Desktop, Releases → Publish an immutable build input exposes the same checkout, source revision, name and optional paths. Enable Export locked private Cargo dependencies for Cargo mode, or Export the Swift package's committed resolution for SwiftPM mode. The operation uses the selected Stado API host and returns the same receipt or refusal.

Stado Desktop's Products → Restore locked Swift release dependencies uses the same API-host operation, takes the release directory and archive, and shows the restoration receipt or refusal.

tests/release/inputs/swiftpm-input.mjs publishes to an isolated real Stado object store, downloads and verifies the archive, restores it under a different path, and runs SwiftPM against the restored dependencies without compiling. The real package fixture must include an XCFramework and a dependency with a parent commit so the journey checks relocated binary files, every locked checkout revision and rejection of a changed pin. It also checks repeated restoration, missing artifact and checkout refusals, conflicting export modes and absent source/revision refusals. Its sandbox case requires a macOS qualification host that actually refuses SwiftPM's nested manifest sandbox: the journey observes that OS refusal first, then requires publication to preserve its cause and leave the input manifest unchanged. The case runs on a host that exercises that refusal:

STADO_BIN="$CANDIDATE_STADO" \
STADO_TEST_SWIFT_SOURCE="$SWIFT_PACKAGE_CHECKOUT" \
STADO_TEST_SWIFT_REVISION="$COMMITTED_REVISION" \
node tests/release/inputs/swiftpm-input.mjs

Reports under .build/swiftpm-input/run-*/ retain source and binary identities, command output and exit statuses. A command is recorded as running before it starts, so an interrupted run is not mistaken for a pass.

On a release worker, stado product cargo reads WISENT_SOURCE_DIR and WISENT_INPUT_PRIVATE_CARGO_SOURCES_DIR:

stado product cargo --manifest-path "$WISENT_SOURCE_DIR/Cargo.toml" \
  build --locked --release --bins

The shared executor checks the input's package set and source configuration against the lockfile, enables Cargo's directory-source replacement, preserves the lockfile and disables Git transport. Public registry dependencies retain their normal Cargo resolution. Build, check, test, run, metadata and stage share this handling. Outside a release worker they retain canonical-checkout resolution.

A missing input names WISENT_INPUT_PRIVATE_CARGO_SOURCES_DIR; a stale input reports private Cargo sources do not match Cargo.lock with both sets. An unsupported provenance schema or inconsistent source configuration is refused before Cargo starts. Cargo checks the vendored file checksums. Source manifests outside WISENT_SOURCE_DIR are refused. Arguments after Cargo's -- still reach the test or program, not the source-config parser. The publisher and worker must both support this interface; an older publisher's unexpected argument '--cargo' is a refusal, not publication.

Real private-input qualification

From the Stado checkout, with a committed real Cargo product source:

STADO_BIN="$CANDIDATE" STADO_TEST_PRIVATE_SOURCE="$SOURCE" \
STADO_TEST_PRIVATE_REVISION="$REVISION" \
CARGO_HOME="$CARGO_HOME" RUSTUP_HOME="$RUSTUP_HOME" \
node tests/release/inputs/private-cargo-sources.mjs

The journey publishes into an isolated local object store, downloads and hashes the archive, repeats publication, and runs real Cargo metadata against the mounted sources with no private Git cache or transport. It also checks contradictory flags, wrong names, missing inputs, stale package sets and missing packages without changing the source lockfile or a refused manifest. Reports below .build/private-cargo-sources/run-* retain source and binary identity, commands, exit statuses, receipts and observations. This journey does not compile a native binary or qualify a graphical screen. Native product staging and graphical qualification remain separate real runs.

Source: this website