facett-ops/Cargo.toml
[package]
name = "facett-ops"
description = "facett's non-build verbs — `cargo ops <cmd>`: publish-pip, publish-pypi, publish-holger, verify-install, verify-demo-install, streamlit, test-demo, demo-wasm, deploy-pages, panel-wasm. Split out of xtask so xtask carries ONE dependency (the recipe) and builds nothing but rows (THE LAW: xtask is a THIN DOOR). A plain workspace bin nothing loads as a plugin, so it may carry the heavy deps (draupnir/bollard, zip, znippy) the recipe must not."
version.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
publish = false
[lib]
name = "facett_ops"
path = "src/lib.rs"
[[bin]]
name = "facett-ops"
path = "src/main.rs"
[dependencies]
# facett's DECLARATION, and the only way this crate reaches `nornir-build-thing`
# (re-exported). `demo-wasm`/`panel-wasm` build the recipe's ROW through
# `nornir_build_thing::container_road` (features `cargo-container` + `wasm-stage`),
# the same writer `cargo xtask build` uses.
facett-build-thing = { path = "../facett-build-thing", version = "0.1.0", features = ["wasm-stage", "cargo-container"] }
# The gh-pages flow (`demo_wasm`/`deploy_pages` = the container road + the page
# shell + the orphan-branch push) lives ONCE in the edda leaf `nornir-pages`, the
# generic xtask primitives in `nornir-xtask-support` (LAW #5).
nornir-pages = { version = "0.1", path = "../../edda/crates/nornir-pages" }
nornir-xtask-support = { version = "0.1", path = "../../edda/crates/nornir-xtask-support" }
# `panel-wasm` writes the loader page with the harness's OWN `IndexHtml` renderer
# rather than a copy (LAW #5). Native-only here — the wasm deps are target-gated.
facett-wasm-demo = { version = "0.2.0", path = "../facett-wasm-demo" }
# ── the containerised pip build (`cargo xtask publish-pip`) ──────────────────
#
# Rickard's rule for this lane: the wheel is built IN A CONTAINER, and no python
# runs on the host. `draupnir` is HOW — the constellation's one OCI door, the
# podman/Docker REST API over bollard. `backend-oci` is not a default feature, so
# it is named here; without it draupnir answers `Error::Unsupported` honestly
# rather than pretending.
#
# This is the SAME road knut's SparkServer (`server.rs:44`) and dwarves'
# `runa_backend.rs:22` already take. There is deliberately no
# `Command::new("podman")` in this crate and no shell script beside it — the one
# that used to live at `pkg-facet-wrapped/publish_to_holger.sh` is being replaced
# by this dependency, not wrapped by it.
draupnir = { version = "0.2", path = "../../draupnir", default-features = false, features = ["backend-oci"] }
# ── reading the wheel back (`pip::verify_wheel`) ─────────────────────────────
#
# A wheel is a zip whose `RECORD` is a sha256 manifest of itself, so proving one is
# intact is exactly: open the zip, hash every member, compare. `zip = "8"` is the
# constellation's line (znippy-zip-core, holger's bench-scenarios, ljar) rather
# than a fourth choice; sha2 and base64 are already in this workspace's graph.
# `base64` because PEP 376 spells the hash `sha256=<urlsafe-b64, unpadded>`.
# The clean-install verdict reads the container's JSON report lines and counts the
# components in the wheel's own manifests.
# (2026-10-03, finish-bump) zip 2 -> 8, sha2 0.10 -> 0.11, base64 0.22 -> 0.23 with
# the rest of the constellation. This crate only READS zips (wheels are written by
# the container's build backend); the zip writers are the tests' fixtures.
serde_json = { workspace = true }
zip = { version = "9.0.5", default-features = false, features = ["deflate"] }
sha2 = "0.11"
base64 = "0.23"
# ── publishing to holger's PEP 503 index (`cargo xtask publish-holger`) ────
#
# znippy as a LIBRARY, not a binary. `publish_to_holger.sh` shelled out to
# `znippy-cli` and `holger-server` executables that had to already exist — and
# MEASURED on oden 2026-09-05, NEITHER did, so the script's happy path was not
# runnable on this box and its failure would have been "No such file or
# directory" about a binary, from a script whose job is publishing a wheel.
# Linking the packer removes that whole class of failure.
#
# ⚠ PINNED TO THE **REGISTRY**, NOT THE SIBLING CHECKOUT — and the `=` is deliberate.
#
# MEASURED 2026-09-05: an archive written by the LOCAL ~/git/znippy (compress 0.9.14,
# common 0.9.15) is UNREADABLE by the holger-server on this box:
#
# repository 'pip' DISABLED — failed to open znippy archive … concat sub-indexes:
# Invalid argument error: It is not possible to concatenate arrays of different
# data types (UInt32, UInt8).
#
# The archive is not corrupt — it extracts byte-identically through its own writer's
# decompressor, which is exactly why row 1b's round-trip check passed it. The
# on-disk sub-index schema changed between znippy-compress 0.9.13 and 0.9.14, and
# holger reads with 0.9.13. holger's own manifest dates the split: on 2026-08-23 it
# removed its znippy path patches because "registry 0.9.13 == local 0.9.13", and the
# local checkout has moved since.
#
# So facett writes with what the CONSUMER reads. Publishing an archive only an
# unpublished library can open is not "ready for release" — it is a private format.
# These pins come off when znippy 0.9.14+ is published AND holger is rebuilt on it;
# A `znippy_matches_the_reader` test is owed (it was named here and never written).
znippy-compress = "0.10.0"
znippy-common = "0.10.0"
# The python archive-type plugin, taken from the LEAF crate that defines it rather
# than through `znippy-cli`. Same `NativePythonPlugin` the CLI's
# `handlers::find_handler("python")` returns — one writer for what a python archive
# is — but the CLI's handler registry also pulls its wasm plugin HOST, which put
# cranelift + wasmtime into this xtask's graph for one function call. The leaf is
# the same code without the engine.
# `host-decompressors` is what compiles `NativePythonPlugin` at all — the crate's
# default feature set is the filename parser only. Without it the type does not
# exist and the build fails by name, which is the right shape.
znippy-plugin-python = { version = "0.10.0", features = ["host-decompressors"] }
# The archive is EXTRACTED BACK and compared byte for byte before a publish is
# called done — see `holger::verify_archive_round_trip`. A compressor exiting 0 and
# writing a non-zero file is a state round-trip, not an applied output.
znippy-decompress = "0.10.0"
# ── uploading to PyPI (`cargo ops publish-pypi`) ─────────────────────────────
#
# The warehouse legacy upload API, in Rust (no twine, no python on the host). `ureq`
# 3 + rustls is the constellation's line for a blocking HTTPS client (edda's
# nornir-release-core polls the crates.io index with exactly this), `blake2` gives
# the `blake2_256_digest` warehouse records beside sha256, `flate2` + `tar` read an
# sdist's PKG-INFO, and `pulldown-cmark` renders the markdown readme the way PyPI
# will show it (the `twine check` half of `--dry-run`).
# (2026-10-03, finish-bump) ureq 2 -> 3. `rustls` = ring + the webpki roots, what
# ureq 2's `tls` was; no gzip (ureq 2 had none either). `pypi::agent` switches back
# the ureq 3 defaults that differ (env proxy, 4xx/5xx as errors).
ureq = { version = "3", default-features = false, features = ["rustls"] }
blake2 = "0.10"
flate2 = "1"
tar = "0.4"
pulldown-cmark = { version = "0.13", default-features = false, features = ["html"] }
# `tests/wheel_verify.rs` and `tests/install_judgement.rs` hand the verifier wheels
# carrying the REAL registry's manifest; `zip`/`sha2`/`base64` above are reachable
# from integration tests already, so they are not repeated.
[dev-dependencies]
facet-wrapped = { version = "0.2.0", path = "../facet-wrapped" }