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:
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:
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
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.