Ports

A port is a trait a step asks for and never implements (ADR 0002): ks-core declares the traits in crates/core/src/ports/, an adapter crate outside this workspace is the only code that knows how to answer one for a particular vendor, and HarnessBuilder::build hands each step only the ports it declared. The module's own documentation carries the inventory of which ports exist today — this page does not repeat it.

What this page records is the contract version. ks_core::STEP_API versions the port traits alongside the StepKind trait (ADR 0014), and the harness refuses to compose a step built against a different one. A fingerprint of the port trait signatures is pinned against STEP_API in crates/testing/tests/compatibility.rs, so changing a trait without bumping the version fails CI rather than landing as a silent break. The generated compatibility table is COMPATIBILITY.md.

Adding a port, adding a method to one, or changing a signature is a contract change: bump STEP_API in crates/core/src/version.rs, add a ## STEP_API entry below, update the pinned digest in the compatibility test, and regenerate COMPATIBILITY.md with KS_BLESS=1 cargo test -p ks-testing --test compatibility. Reformatting and comment edits do not need any of that — the fingerprint normalises them away.

Port reference

One row per port trait, verified against crates/core/src/ports/. In Port is the question that matters to a step author, and it is checked here against the Port enum and the define_ports! list in crates/core/src/step.rs, which must agree in order: a step can only declare a port that has a variant. The kit is the port's conformance function in ks-testing — most are named conformance, but the two supply-chain ones are registry_conformance and signer_conformance, and a few take extra arguments beyond the trait (cluster::conformance a name and a manifest, iac::conformance a directory). — means the module ships fakes but no kit. The implementor is the in-tree impl of the trait.

Ports a step can declare

TraitForIn PortKitImplementor
RunContextwho and where a run isyesrun_context::conformanceks-cli's GithubActionsRunContext
Secretsresolving named secrets, redacting valuesyessecrets::conformancecore's ActorGatedSecrets, the engine's PolicySecrets
ImageBuilderbuilding a container imageyes——
Registrypushing and pulling by digestyessupply_chain::registry_conformanceks-oci-distribution
Signersigning, attestations, digestsyessupply_chain::signer_conformance—
IacToolplanning and applying infrastructureyesiac::conformanceks-tofu-cli
HttpClientbounded, policy-checked HTTPyeshttp::conformanceks-cli's TcpHttpClient
Clocktime and sleepingyesclock::conformanceks-cli's SystemClock
IdGenminting fresh run idsyesid_gen::conformance—
ScriptHostrunning TypeScript escape-hatch stepsyesscript_host::conformanceks-deno-script-host
ApprovalChannelasking a team for approvalyesapproval::conformance, which takes the channel, a Directory, a team and an approver, and so covers Directory tooks-hosted-approvals, ks-github
Clusterapplying manifests and watching rolloutsyescluster::conformance, which takes a name and a manifestks-helm-cli
RemoteShellcommands on a host the harness does not ownyesremote_shell::conformance—
FunctionHostpublishing a version, shifting trafficyesfunction_host::conformance—
DecisionModelasking a model a judgement it cannot be handedyesdecision_model::conformanceks-jev

Engine- and CLI-only ports

No Port variant, so no step can hold one and STEP_API does not move when one changes. The Implementor column says which crate ships the one that runs in production.

TraitForImplementorKit
BlocksIndexthe metadata-only blocks indexCLI's HostedBlocksIndex—
BlockSourceblock content, resolved and fetched by digest—block_source::conformance
PolicyStorethe org baseline policy, read from outside the repository—policy::conformance
RunLogthe append-only record of what a run didCLI's JsonlRunLog—
StateStorea parked run's state and its saved plansengine's LocalStateStorestate_store::conformance
ApprovalInboxthe approver's side: what is waiting for me, and here is my answer—approval::inbox_conformance, which takes a channel and an inbox

health is not a port: crates/core/src/ports/health.rs holds only the Health / HealthReport vocabulary the deploy-target ports report in.

Writing an adapter? STEP-AUTHORING.md has the crate shape and the test-file pattern; changing one of the traits above? The changelog below is where it goes.

Changelog

Newest first, one heading per contract version, dated.

STEP_API 6

STEP_API 5

STEP_API 4

STEP_API 3

STEP_API 2

STEP_API 1