ic-host-process 0.23.1

Native process observations, admitted execution and Git provenance
Documentation

IC Host Tooling

Host-side Rust libraries for artifact inspection, local filesystem safety, admitted process execution and IC-specific format handling.

Crate Implemented source ownership
ic-host-artifacts Bounded streams and hashing writers, exact byte comparison, SHA-256, gzip encoding/decoding, generic Wasm facts and custom-section encoding
ic-host-fs Regular/no-follow reads, missing-suffix path resolution, streamed durable publication, private files and observed descriptor locks
ic-host-process Executable resolution, digest/version admission, bounded capture, explicit child/group cleanup and Git observations
ic-host-text Bounded line normalization, terminal-safe escaping, Unicode display-width tables and progress frames
ic-host-tools Candid extraction, optional service and IC export facts, ICP response decoding and IC resource reports

These run locally outside canisters. The ic-host prefix identifies their ecosystem; the generic crates have no IC runtime dependency. Gzip and Wasm support are optional features of ic-host-artifacts. Filesystem/process consumers select the small default artifact dependency. For test fixtures, opt in to ic-host-fs's test-support feature in a dev-dependency and use testing::create_fixture_directory(parent, prefix). It allocates beneath an existing physical parent, skips occupied names and returns the retained path. Callers own labels, custody and explicit cleanup; the allocator supplies no Drop guard or application lifecycle. For bounded synthetic HTTP peers, opt in to ic-host-process's test-support feature and use testing::http::{capture_http_request, read_http_request_line, read_http_request_with_line}. Whole capture retains partial bytes and typed failures; callers select required or optional Content-Length and own responses, byte budgets, deadlines and evidence files. With wasm, wasm::encode_custom_section(CustomSection { name, data }, max_bytes) encodes one complete custom section with an explicit output-byte allowance. Callers supply exact names/data and own module assembly, aggregate budgets, duplicates, append order and subsequent Wasm validation. With the same wasm feature, wasm::code_statistics(bytes, module_bytes) counts encoded indirect calls, including tail calls, and data payload bytes without a subprocess. These counts differ from encoded section sizes and need not match optimizer reachability metrics. Type/feature validation and report policy remain caller-owned; the structural wasm::inspect contract is unchanged. ic-host-tools has no default features. Response decoding is always available without artifact/filesystem/process/text dependencies or the Candid parser. Enable candid-extraction explicitly for the Unix candid::extraction API. Extraction returns bounded raw process evidence and source/tool identities. Call ic_host_text::normalize::normalize_lines explicitly when your consumer needs normalized text, selecting its output-byte allowance separately. The optional ic-interface feature adds interface::exported_methods over generic Wasm facts without the Candid parser. Enable candid-service for parser-backed candid::service::inspect and shared method identities, without the artifact crate or Wasm parser. Select both features to compare Candid services with Wasm exports; candid-service no longer enables ic-interface implicitly. Neither feature introduces Host filesystem/process dependencies on its own. The parser remains absent from the extraction graph. See the 0.21 feature migration, 0.17 feature migration, 0.16 extraction migration and 0.15 API usage. The optional ic-limits feature compares Wasm facts with explicitly selected, revision-bound IC resource limits, without filesystem/process dependencies when defaults are disabled. See the 0.5 contract changes.

The optional tools certificate feature exposes certificate::{decode_certificate, decode_hash_tree} with explicit input-byte and CBOR-depth limits. It returns owned ic-certification values and rejects trailing data, malformed trees and invalid certificate fields. Parsing authenticates nothing: callers retain Agent verification, trust roots, combined evidence budgets, witness commitments and application leaf meanings. The feature adds no Agent transport, filesystem/process execution or Candid parser.

ic-host-tools::response::decode accepts explicitly selected JSON envelopes or plain hex. CLI presentation labels, preambles and any broader whitespace policy belong to callers; the codec does not infer or strip them. See the 0.22 response migration.

ic-host-text::escape::terminal_text makes control and bidirectional formatting characters visible without choosing output or colour. It returns Cow<str>, borrowing safe input; use .into_owned() when independent ownership is required. table::render accepts headers, fixed-column rows, alignments and explicit Options { column_gap, header_separator }, returning a String. It measures escaped cells in Unicode display columns. Reports retain their row/header/column decisions; CLIs retain stdout, terminal detection, colour and progress lifecycle. width::fit_line escapes controls and clips with an ellipsis to caller-selected columns, using conservative scalar widths. It preserves UTF-8 boundaries without promising grapheme-cluster boundaries. Clipping consumes escapes as needed without allocating the complete escaped line. progress::ProgressLine returns escaped update, finish and clear frames with scalar-column padding. Callers prevent wrapping and own writing, flushing, output failures and cleanup timing; dropping the helper performs no IO. The crate depends only on the Unicode-width library, with no other Host or IC dependencies.

ic_host_text::normalize::normalize_lines(bytes, max_bytes) separately admits UTF-8, trims each line's trailing Unicode whitespace and emits LF endings while preserving blank lines. It bounds both input and normalized output and reserves storage fallibly. It does not escape controls or validate a document grammar. See the 0.23 import migration.

The compiled IC resource-report example inspects once and compares code-body bytes, defined functions and all globals against the explicitly selected reference revision. Run it with:

cargo run --locked --offline -p ic-host-tools --no-default-features --features ic-limits \
  --example inspect_install_limits -- module.wasm 20000000 1000 10000 1000 15000000 --json

The numeric arguments select the hard raw-byte, section, export and custom-section bounds, followed by a soft raw-byte warning threshold. Text and JSON share the same failure exit decision; warnings alone do not reject. This is a composition recipe, not complete Wasm validation or evidence of deployed subnet limits.

tool::capture_command accepts a caller-configured std::process::Command when the consumer owns executable admission. It shares bounded capture and direct-child cleanup with AdmittedTool, whose exact digest/version checks remain in place. ToolError::evidence and execution_error borrow retained diagnostics without copying output; callers own presentation and recovery. tool::format_failure_evidence(context, &error) explicitly renders status, lossy stdout/stderr and cleanup failures once when raw diagnostics are requested. Callers retain terminal escaping and redaction; ordinary error display omits captures. Capture and communication return CompletedExecution with a required exit status, including nonzero and signalled exits. Use require_success() when zero exit is required. Interrupted execution retains optional status in ExecutionEvidence.

child::OwnedChild::spawn(command, cleanup) starts a caller-configured command in a new owned process group. It preserves command IO and supports polling, waiting, explicit termination and cleanup during unwinding. Callers retain admission, readiness, cancellation and application lifecycle policy. For deliberate background startup, poll_exit retains cleanup ownership through result admission; explicit handoff then reaps a successful leader without stopping the background group. See the handoff contract and the child cleanup contract for exclusive ownership and descendant limitations.

Unix custody::CommandCustody instead retains a caller-selected sidecar lock through one command's inherited descriptor and cooperating descendants. Persist its FileIdentity with original intent before spawn, selecting the descriptor environment name explicitly. finish closes owner custody and probes exact existing-sidecar quiescence using caller-selected grace and polling. A missing or replaced sidecar never proves quiescence. QuiescenceGuard cannot spawn and explicitly unlocks on drop. Callers own namespace stability, descendant descriptor preservation, output/reaping, records, remote effects and recovery; no sidecar is deleted and a failed spawn consumes the local allowance. Group cleanup and inherited custody are distinct contracts.

For OwnedChild::spawn, select CleanupPolicy::KillAndWait for immediate KILL with synchronous reaping, or CleanupPolicy::TermThenKill for caller-owned TERM grace and bounded reaping. Communication failures and Drop use that same explicit policy. Reap timeout retains the owner for explicit recovery and does not start a background reaper. See the bounded cleanup contract.

tool::capture_group_command combines bounded stdout/stderr capture with that group cleanup for commands whose descendants must be signalled on exit or failure. Ordinary capture_command retains direct-child cleanup. Both share one capture engine; callers retain budgets, admission and external-effect recovery.

OwnedChild::spawn_direct preserves inherited or caller-selected process groups for foreground communication and cleans up only the direct child. It uses the same I/O engine; consumers retain terminal control, interruption handling and descendant lifetime, including any descendant-held output pipes.

tool::communicate_child_with_observer adds live stdout/stderr observation to communicate_child for an existing owner. Each stream chooses OutputLimit::Terminate(bytes) to fail on overflow or OutputLimit::Truncate(bytes) to forward all drained output while retaining only a bounded prefix. The cancellation callback can also emit caller-scheduled heartbeats while silent. Callback unwinding attempts cleanup even when the borrowed owner survives the caller's panic handler. Output observers return io::Result<()>; a sink error retains its original cause and triggers the same child cleanup without a caller-side cancellation workaround.

All capture, communication and admission functions accept one OutputLimits type with stdout, stderr and timeout: Option<Duration>. Select None for long builds without an elapsed-time deadline, or Some(duration) for a positive deadline. Retention and cleanup still apply; communication also polls cancellation. Version, Git and Candid admission reject truncated captures. Other consumers can use CompletedExecution::require_complete before parsing retained output.

Examples live with their owners: ic-host-artifacts has hash_serialized_json; ic-host-fs has inspect_regular_file and the artifact/file composition inspect_gzip; ic-host-process has inspect_git and resolve_tool. ic-host-tools retains extract_candid, inspect_install_limits and inspect_response; the response example runs with --no-default-features. Example-only dependencies do not widen the tools crate's production feature graph.

AdmittedTool::admit_version accepts a VersionSpec for caller-trusted installed tools without a published binary digest. It records the installed identity, checks the exact version and rejects byte drift before later execution. That observed identity is not authentication; installation provenance stays with the caller. admit and admit_no_follow take the same VersionSpec with a separate, mandatory digest pin, checked before executing the version probe. The 0.20 upgrade notes describe the pinned-admission signature change.

Filesystem reads live under ic_host_fs::read, including optional bounded no-follow reads and typed private-file admission. Durable publication and locks remain under durable. Upgrading from 0.3 requires the 0.4 consumer changes.

On Unix, read::open_directory_no_follow opens an explicit root capability; read::open_regular_file_beneath selects a regular file relative to that borrowed descriptor without following symlink components. read::open_entry_beneath also admits a directory descriptor for a caller-owned tree walk. Use read_opened_file to read a selected regular inode under an explicit byte allowance. Root custody, content validation, deadlines and concurrent writers remain caller-owned.

On Unix, read::hash_file_no_follow(path, max_bytes) hashes regular files with constant working memory, rejecting final symlinks and opened special files. It shares the bounded stream hasher and preserves typed errors. Callers retain trusted ancestors, concurrent-writer custody and any exact expected-length check.

durable::open_regular_lock_file_with_parents prepares an admitted regular file without acquiring a lock. Consumers then select lock_shared, lock_exclusive, try_lock_exclusive or lock_exclusive_with_wait on its close-on-exec descriptor. The immediate exclusive attempt returns native WouldBlock on contention and performs no waiting or retry. Keep the descriptor alive through the protected operation; callers retain polling, clone lifetime and explicit unlock policy. Existing lock files need no staging or parent write permission; durable creation runs only when the entry is missing. The 0.13 migration replaces the fixed-cadence pathname progress helper with this composition; callers keep polling and reporting policy. The 0.23 migration removes the two combined pathname open-and-lock shortcuts in favor of this explicit preparation/acquisition boundary.

durable::open_private_regular_lock_file instead creates a missing sidecar with owner-only permissions in an existing parent. It preserves existing bytes, inode and permissions, returns an unlocked close-on-exec descriptor, and performs no durability syncs. Callers retain locking, trusted ancestors and descriptor custody.

durable::write_named_with lets an external tool write to an owned absolute staging path with explicit WriteOptions, then shares the normal durable publication engine. Callers validate bounded output before success and retain typed producer/cleanup errors. See the current publication contract before adopting it.

durable::write_validated_with closes the staging writer before a caller's prepublication admission callback, enabling executable probes while preserving the working destination on rejection. See the closed-writer contract.

durable::write_with(path, options, producer) retains serializer errors while streaming through the same engine, with explicit replace/create-only options and file permissions. durable::write_bytes(path, options, bytes) shares that engine and returns NamedWriteError<io::Error>, preserving publication state and separate cleanup failures. Select mode and permissions explicitly; see the 0.22 migration. durable::write_existing_parent_with instead refuses missing parents and opens the selected parent once as a directory. Its _validated_with variant admits the producer's result before publication. Parent aliases are followed and the held directory stays selected across renames; callers retain namespace custody. durable::write_at_with borrows an admitted directory descriptor so publication stays anchored to it even when its original path moves. Consumers retain path admission, budgets and recovery; see the publication contract.

path::canonicalize_allow_missing(path, base, max_symlink_depth) resolves existing components and dangling symlinks while normalizing a missing suffix. Choose the simultaneously active missing-target depth explicitly; usize::MAX preserves unlimited expansion. This supplies no root confinement or filesystem deadline. See the 0.23 resolver migration.

With the artifact gzip feature, hash_gzip and hash_gzip_or_raw identify decoded payloads without a full decoded allocation. gzip_matches compares decoded bytes exactly with an expected slice. All retain explicit input/payload bounds and strict single-member integrity checks; see the gzip contract.

The five crates support Linux x86-64 and macOS 15 on Intel and Apple Silicon, with Rust 1.88.0 as their MSRV. See host qualification for validation commands and native evidence requirements.

For development, prepare the toolchain selected in rust-toolchain.toml and the bootstrap prerequisites and shell PATH, then run:

make install-tools
make tools-check
make install-hooks
make test PACKAGE=ic-host-artifacts

Setup is explicit; tools-check verifies prepared tools offline. Hook installation separately activates the repository-local formatter. Shared Tooling owns these common commands and executable installation; this workspace owns the five libraries, package checks and publication policy.

Use make check, make clippy, make docs-check, make test or make msrv with PACKAGE=<crate> for focused work. Follow the shared validation policy for when to select those checks or full make ci msrv-workspace validation. The host guide describes feature-specific and hook checks; make help lists the available commands.

Git release and crates.io publication are separate operations; a tag does not establish registry availability. Read the release and publication guide for prerequisites, explicit command effects and retained-evidence recovery. Consumers adopt and qualify registry selections independently.

Read the extraction contract, the handoff and agent rules. Human and agent contributions follow the PR contribution rules.

Bounded Wasm inspection CLI

cargo run -p ic-host-wasm-inspect --locked -- input.wasm 1048576 1000 1000 1000

The standalone application composes artifact/filesystem owners, keeping the five library boundaries and response-only defaults unchanged. It prints exact SHA-256 and raw/structural facts after complete admission; budgets and interpretation stay caller-owned. It executes no Wasm and estimates no cycles or instructions. Select the published package, version and binary through the standard Shared Tooling Cargo installer; no extra feature selection is required. Append --expect-sha256 SHA256 for digest verification before reporting, or --details for export/custom-section records after the TSV summary. These options replace the retired ic-host-fs inspect_artifact example; default CLI output is unchanged. See the inspector guide.