Reference

Ownership Layers

The canonical ownership model for ecosystem capability truth, repo-level orchestration, simple runtimes, standalone commands, and native prerequisites.

referenceautomation buildersintermediatestable2026-05-30

Purpose

Use this page to decide where prerequisite truth belongs.

The canonical split is capability-first for toolchains and mediation-first for orchestrators.

The goal is one owner per capability and one explicit repo execution story.

  • toolchains own required ecosystem capability truth.
  • orchestrators own repo-level trust, prepare, and mediated task execution.
  • runtimes own simple unmanaged runtime checks.
  • tools own standalone commands on PATH.
  • native_prerequisites own host-native build bundles and shell activation.

Ownership rule

Pick the highest useful owner and do not repeat the same capability below it.

  • toolchains are capability-first: version says what the repo needs, while fulfillment says how ota may fulfill it on the selected path.
  • orchestrators do not replace toolchains; they model repo-level trust, install, and selected task execution.
  • Current shipped toolchain names are toolchains.rust, toolchains.node, toolchains.java, toolchains.python, toolchains.go, toolchains.ruby, and toolchains.dotnet.
  • Current shipped canonical fulfillment sources are rustup, corepack, sdkman, uv, go, ruby, and dotnet.
  • Legacy toolchain provider is still accepted for compatibility, but it is not the canonical public model.
  • If a declared toolchain owns the capability, do not also declare it under runtimes or tools.

Command behavior

  • ota doctor diagnoses selected toolchains and orchestrators without mutation.
  • ota doctor --json and ota check --json expose additive selected-path toolchains[] evidence so automation can see the chosen fulfillment source, backend, target OS, fulfillment mode, and owned capabilities.
  • ota up --dry-run shows selected toolchains before execution.
  • fulfillment.mode: none means diagnose only; fulfillment.mode: run means ota may provision the selected toolchain on the selected run path.
  • tasks.<name>.execution.orchestrator makes mediated execution first-class instead of burying repo truth in run: mise ... shell strings.
Shipped toolchain mappingyaml
toolchains:  rust:    fulfillment:      source: rustup  node:    fulfillment:      source: corepack  java:    fulfillment:      source: sdkman  python:    fulfillment:      source: uv  go:    fulfillment:      source: go  ruby:    fulfillment:      source: ruby  dotnet:    fulfillment:      source: dotnet

When to use each layer

Toolchain

A managed ecosystem capability such as Rust, Node, Java, Python, Go, Ruby, or .NET.

When to use it

Use it when ota should understand owned runtimes, owned package managers, components, targets, or fulfillment.

Example

Orchestrator

A repo-level manager such as mise that mediates trust, install, and selected task execution.

When to use it

Use it when the selected repo path must run through one declared manager instead of hiding that truth in shell strings.

Example

Runtime

A simple language/runtime version requirement with no managed ecosystem ownership.

When to use it

Use it when a task only needs node >=24, python >=3.12, or another runtime to exist.

Example

Tool

A standalone command on PATH.

When to use it

Use it for Docker, GitHub CLI, jq, psql, or any command not provided by a declared toolchain.

Example

Before and after

Without the canonical model, repo truth often disappears into setup shell strings.

Before: orchestration hidden in shell stringsyaml
tools:  mise: "*" tasks:  setup:    run: mise trust && mise install  server:verify:    run: mise run //server:ci-unit
After: capability + orchestrator truthyaml
toolchains:  node:    version: "24.15.0"    package_managers:      pnpm: "10.33.4"    fulfillment:      source: mise      mode: run orchestrators:  mise:    kind: mise    required: true    config_files:      - mise.toml    activation:      trust: true    prepare:      install: true tasks:  server:verify:    run: //server:ci-unit    execution:      orchestrator:        ref: mise        mode: task

Task requirement examples

Task requirements should select the owner that actually applies to the selected path.

Managed ecosystemyaml
tasks:  setup:    requirements:      toolchains:        - node
Simple runtimeyaml
tasks:  start:    requirements:      runtimes:        node: ">=24"
Standalone commandyaml
tasks:  docker:proof:    requirements:      tools:        docker: ">=27"
Native build bundleyaml
tasks:  install:    requirements:      native:        - node-native-build-tools

Duplication boundaries

Duplicate ownership is invalid contract drift, not extra safety.

  • Reject toolchains.node plus runtimes.node or tools.node on the same contract.
  • Reject toolchains.python plus runtimes.python on the same contract, and reject toolchains.python.package_managers.poetry plus tools.poetry when Poetry ownership is explicit.
  • Reject toolchain-provided components such as rustfmt when they are also modeled as standalone tools.
  • Keep one owner for the selected capability and remove the duplicate location instead of carrying parallel truth.

Fulfillment behavior

Capability truth and fulfillment truth are separate on purpose.

  • ota validate validates contract shape only.
  • ota doctor diagnoses missing or mismatched toolchains without mutation.
  • ota up --dry-run shows the exact fulfillment action before execution.
  • Supported fulfillment.mode values today are only none and run.
  • Use fulfillment.mode: none when ota should only diagnose/check the toolchain truth and never provision it on the selected path.
  • ota up and selected ota run may provision only when fulfillment.mode: run is declared and policy permits it.
  • Use fulfillment.mode: run only when the selected repo path should let ota own fulfillment through the declared source.
  • Default fulfillment should be mode: none, so inspection commands never silently download or mutate.
Strict by defaultyaml
toolchains:  rust:    version: "1.94.0"    components:      - rustfmt    fulfillment:      mode: none
Explicit run-path fulfillmentyaml
toolchains:  rust:    version: "1.94.0"    components:      - rustfmt    fulfillment:      source: rustup      mode: run

Current shipped examples

These examples use only the canonical public model.

Rust (shipped)yaml
toolchains:  rust:    version: "1.94.0"    profile: minimal    components:      - rustfmt      - clippy    targets:      - x86_64-unknown-linux-musl
Node (shipped)yaml
toolchains:  node:    version: "24.15.0"    package_managers:      pnpm: "10.33.4"    fulfillment:      source: corepack      mode: run
Java (shipped)yaml
toolchains:  java:    version: "21"    fulfillment:      source: sdkman      mode: run tools:  maven: "*"
Python (shipped)yaml
toolchains:  python:    version: "3.12"    package_managers:      uv: "*"    fulfillment:      source: uv      mode: run
Go (shipped)yaml
toolchains:  go:    version: "1.24"    fulfillment:      source: go      mode: run
Ruby (shipped)yaml
toolchains:  ruby:    version: "3.3.11"    package_managers:      bundler: "2.5"    fulfillment:      source: ruby      mode: run
.NET (shipped)yaml
toolchains:  dotnet:    version: "9.0"    fulfillment:      source: dotnet      mode: run
Mise orchestrator (shipped)yaml
orchestrators:  mise:    kind: mise    required: true    config_files:      - mise.toml    activation:      trust: true    prepare:      install: true

What this is not

  • not a second task language
  • not a place for arbitrary setup commands
  • not a replacement for services or launch sources
  • not the right home for Docker itself
  • not the right home for OS-native build-tool bundles

Current shipped slice

  • top-level toolchains, top-level orchestrators, and task-scoped requirements.toolchains are supported
  • Rust, Node, Java, Python, Go, Ruby, and .NET are the shipped toolchain names today
  • ota doctor diagnoses missing toolchains and orchestrators without mutation, and Ruby can govern Bundler through toolchains.ruby.package_managers.bundler
  • for toolchains.ruby with fulfillment.mode: run, ota currently uses the selected Ruby to hydrate the declared Bundler lane structurally instead of treating bundle install as shell glue
  • selected ota run / workflow ota up paths can provision shipped fulfillment sources plus fulfillment.source: mise when fulfillment.mode: run is declared and policy allows it
  • duplicate ownership is invalid and fails validation
  • Ota's own contract now uses this model instead of shell-based managed-surface setup