ota JSON Output Reference
July 29, 2026 · View on GitHub
This document records the current machine-readable output shapes for ota commands that support --json.
docs/spec is the canonical source of truth. This page is part of that spec
corpus and the public reference pages are derived from it with examples and
operator guidance added where useful.
The goal is stability for humans, CI, editors, and agents.
For the operator guide to the currently shipped assist flow, see assist-workflow.md.
Editor and CI integrations should treat the JSON surfaces in this document as the stable contract and avoid scraping human-readable text output.
Canonical JSON Schema files for the current shipped shapes live in:
- json-schemas/validate.json
- json-schemas/env.json
- json-schemas/execution.json
- json-schemas/execution-topology.json
- json-schemas/proof-runtime.json
- json-schemas/proof-lifecycle.json
- json-schemas/refusal-canary.json
- json-schemas/replay-baseline.json
- json-schemas/replay-baseline-authority.json
- json-schemas/services.json
- json-schemas/tasks.json
- json-schemas/assist-declare-readiness.json
- json-schemas/assist-declare-service.json
- json-schemas/assist-bind-task.json
- json-schemas/assist-declare-env.json
- json-schemas/assist-add-task.json
- json-schemas/assist-normalize.json
- json-schemas/assist-wire-setup.json
- json-schemas/agents.json
- json-schemas/doctor.json
- json-schemas/check.json
- json-schemas/clean.json
- json-schemas/receipt.json
- json-schemas/init.json
- json-schemas/policy-init.json
- json-schemas/up.json
- json-schemas/run-preview.json
- json-schemas/detect.json
- json-schemas/policy-review.json
- json-schemas/workspace-init.json
- json-schemas/workspace-tasks.json
- json-schemas/workspace-execution.json
- json-schemas/workspace-run.json
- json-schemas/workspace-check.json
- json-schemas/workspace-doctor.json
- json-schemas/workspace-explain.json
- json-schemas/workspace-up.json
- json-schemas/diff.json
- json-schemas/explain.json
- json-schemas/github-projection.json
- json-schemas/ci-projection.json
- json-schemas/version.json
General notes
- success output is printed to stdout
ota run <task> --dry-run --jsonkeeps preview JSON on stdout for both ready and blocked previews; only pre-preview command failures fall back to the simpler error envelope- command failures may still use stderr when the command cannot produce its normal JSON result
- some JSON failures include an optional
nextstring when ota can point to one safe follow-up command - execution receipts may also expose additive
next_stepswhen the same follow-up lane is available as ordered machine-readable steps ok: truedoes not always mean zero findings; warning-only diagnosis can still beok: truepathrefers to the resolved contract path as rendered by current CLI path compaction (often cwd-relative such as./ota.yaml)
Which JSON surface to use
- use
ota validate --jsonorota workspace validate --jsonfor contract gating - use
ota --version --jsonwhen you need machine-readable build identity and contract capability support - use
ota env --jsonfor read-only environment inspection and validation - use
ota execution plan --jsonwhen you want the resolved backend, lifecycle, image, and target selection without running anything - use
ota execution topology --jsonwhen you want the declared execution graph for contract or topology inspection without running anything, including reusable readiness probes, reusable runtime surfaces, structured task launch sources, normalized listeners, and attached surface names - use
ota proof runtime --jsonwhen you want one thin clean-machine runtime-proof wrapper that captures the canonical topology, doctor, and up artifacts for a selected runtime path - use
ota ci github render --json,check --json, orsync --jsonwhen you need the deterministic GitHub reusable-workflow projection, its caller binding, and managed-file drift result without scraping YAML or text output - use
ota services --jsonwhen you want the declared managed-service inventory, including manager shape, readiness declaration, endpoint projections, and dependencies - use
ota assist declare-readiness --jsonwhen you want a deterministic readiness proposal or apply result without scraping review text - use
ota assist declare-service --jsonwhen you want a deterministic managed-service proposal or apply result without scraping review text - use
ota assist bind-task --jsonwhen you want a deterministic target-binding proposal or apply result without scraping review text - use
ota assist declare-env --jsonwhen you want a deterministic env proposal or apply result without scraping review text - use
ota assist add-task --jsonwhen you want a deterministic new-task proposal or apply result without scraping review text - use
ota assist normalize --jsonwhen you want a deterministic canonical-setup normalization proposal or apply result without scraping review text - use
ota assist wire-setup --jsonwhen you want a deterministic setup-wiring proposal or apply result without scraping review text - use
ota workspace execution plan --jsonwhen you want per-repo execution resolution across a workspace without running anything - use
ota agents --jsonwhen you want a repo-localAGENTS.mdexport preview or sync report - use
ota skills install --json --agent <agent>when you want the installed first-party skill target path - use
ota doctor --jsonorota workspace doctor --jsonfor readiness diagnosis and blocking findings - use
ota policy init --jsonwhen you want the starter org policy pack preview or write result - use
ota policy review --jsonwhen you need policy-authority review over a repo contract - use
ota workspace explain --jsonwhen you want an ordered workspace remediation plan - use
ota workspace tasks --jsonwhen you want workspace inventory and task availability - use
ota workspace list --jsonwhen you want lightweight workspace inventory and readiness - use
ota workspace check --jsonwhen you want checks-only workspace readiness with a roll-up summary - use
ota receipt --jsonwhen you want a read-only repo receipt artifact - use
ota clean --jsonorota clean --stale --jsonwhen you want deterministic cleanup reports or structured cleanup failure details instead of scraping text receipts - use
ota up --jsonorota workspace up --jsonwhen you want preparation or readiness roll-up data - use
ota run <task> --dry-run --jsonwhen you want a repo task execution preview without starting dependencies, processes, or containers - use
ota workspace run --jsonwhen you want coordinated multi-repo execution roll-up data and receipts - use
ota workspace run --json --progress-json,ota workspace up --json --progress-json, orota workspace refresh --json --progress-jsonwhen you also need live machine-readable workspace progress events on stderr before the final stdout JSON report arrives - use
ota workspace receipt --jsonwhen you want a read-only workspace receipt artifact - use
ota diff --jsonorota explain --jsonwhen you want contract change impact or remediation planning
Editor and IDE contract rules
Editor and IDE consumers should prefer the smallest stable fields for the job instead of parsing human text output:
ota validate --jsonandota workspace validate --json: useok,summary.error_count,errorsorerror, andnextota --version --json: usesemver,version,source_build,commit,dirty,schema_version, andcontract_capabilities[]ota agents --json: useok,path,output,written,mode, andcontentota skills install --json: useok,skill,agent, andpathota execution plan --json: usecontract_identity,declared_execution,resolved, andoverridesota execution topology --json: usecontract_identity,declared_execution,shared_backends,readiness_probes,surfaces,services, andtasksota run <task> --dry-run --json: usesummary,resolved,requested_task,env,toolchains, andplan; repo-level run JSON is currently preview-only and requires--dry-runota proof runtime --json: usemode,workflow,phase,summary, andartifacts; inspect the referenceddoctor.jsonandtopology.jsonartifacts instead of expecting the proof wrapper to duplicate those payloadsota ci github <render|check|sync> --json: useok,operation,projection.identity,projection.workflow,projection.task,projection.merge_check_ids,output_path,caller_path, andmutated; on failure use stablecodeandmessage, not text renderingota services --json: useservices, groupedmemberswhen present, and nestedmanager,readiness,endpoints, anddepends_on; readiness may includeendpoint, and endpoint objects may includecontextwhen the endpoint name and execution context differota assist declare-readiness --json: usemode,subject,inputs.style,changes,validation, andnextota assist declare-service --json: usemode,subject,inputs,changes,validation, andnextota assist bind-task --json: usemode,subject.task,subject.target,inputs,changes,validation, andnextota assist declare-env --json: usemode,subject,inputs,changes,validation, andnextota assist add-task --json: usemode,subject.task,inputs,changes,validation, andnextota assist normalize --json: usemode,subject.task,subject.into,changes,validation, andnextota assist wire-setup --json: usemode,subject.task,inputs,changes,validation, andnextota workspace execution plan --json: use the top-levelsummary, per-reporesolved, per-repoerror/next, and optional top-leveloverridesota doctor --jsonandota workspace doctor --json: use the top-levelsummary,finding_groupswhen present, per-repofindings, per-repoprimary_blockerwhen present, andexecution; repo doctor also includesmodeota workspace explain --json: use the top-levelsummary, top-level orderedactions, per-repo groupedactions, and per-repostepswith stable codesota workspace tasks --json: use the top-levelsummary, per-repotasks, and dependency orderota workspace list --json: use the top-levelsummary, per-repo readiness, and contract presenceota workspace check --json: use the top-levelsummary, per-repo findings, and per-repoprimary_blockerwhen presentota receipt --json: use the top-levelsummary,receipt, andfindingsota run --json,ota up --json,ota workspace run --json, andota workspace up --json: inspectreceipt.host_service_cleanup[]when you need machine-readable evidence that ota attempted interrupt-driven host-managed service cleanup and whether each stop succeeded or failedota up --json: inspect additivegovernance.crossingandreceipt.crossingwhen the selected workflow crossed a heavier audited execution boundary and the operator supplied intent with--reason; the current shipped record keepsrequirement_source: derivedhonest instead of pretending contract-declared grant or approval truth, andcrossing.evidence_classesdistinguishes caller-asserted fields such asreasonfrom runner-derived or runner-attested fieldsota run <task> --dry-run --jsonandota up --json: inspect additivegovernance.post_execution.not_run_reasonandgovernance.post_execution.crossing_record_statewhen you need phase-accurate non-run and crossing-evidence posture instead of inferring from null fields alone. A preflight refusal remainspost_execution.state: not_runbecause execution never began, but it setsrefusal_occurred: trueand carries the same refusal reason and record as preflight.- the same
governance.post_execution.decision_basis[]surface now carries the cited post-execution evidence-state basis when ota can decompose it honestly, such asnot_run:preview_only,not_run:preflight_refusal,evidence:receipt_present,evidence:proof_present, orcrossing_record:attached ota clean --jsonandota clean --stale --json: use cleanup counters andqueried_engineson success; on classified cleanup failures usesummary,reason, orderednextsteps, and the matching structured lane: engine/resource failures exposeengine,resource_kind,resource_name, anddetails, while active execution cleanup barriers exposeregistry_path, typedreasons[],active_execution_count, andowners[]; generic repo-state failures still fall back tosummarypluserrorota up --jsonandota workspace up --json: use the top-levelsummary,receipt, and per-repo results; workspace repo results may also include additivenext/next_steps- repo-target
ota up --jsonmay also include additivegovernance.crossingwhen the selected workflow execution crossed a heavier audited boundary; this mirrors the same ota-authored record attached toreceipt.crossing, including field-levelevidence_classes ota workspace run --json: use the top-levelsummary,receipt, and per-repo results; repo results may also include additivenext/next_stepsota workspace run --json --progress-json,ota workspace up --json --progress-json, andota workspace refresh --json --progress-json: consume newline-delimited progress events from stderr for live repo transitions, then consume the final stdout JSON report as the canonical roll-up artifactota workspace receipt --json: use the top-levelsummary,receipt, and per-repo resultsota diff --json: use the readiness-impact summary and semanticchanges[]; archived receipt JSON and archived.ota/contracts/...snapshot JSON are also valid diff inputs; additivebase_input/target_inputidentify which semantic input kind each side resolved to and publishsnapshot_pathwhen a receipt-side diff resolves through.ota/contracts/...ota explain --json: use groupedactionsfor the ordered remediation plan andstepsfor stable finding-level detail
Hosted CI can use the same fields as annotations or check-run summaries:
summary.primary_blockerwhen present, for the headlinefindings[]or per-repofindings[]as the annotation streamfinding_groups[]when present, for grouped human-facing remediation summaries onlyseverityto decide blocking versus warning annotationswhyfor the annotation body
ota --version --json
Purpose: expose machine-readable build identity and the contract capability surface this binary supports without scraping human version text.
Current shape:
{
"ok": true,
"semver": "1.6.16",
"version": "v1.6.16",
"source_build": false,
"commit": null,
"dirty": false,
"schema_version": 1,
"contract_capabilities": [
{
"id": "toolchains",
"introduced_in": "1.6.15"
},
{
"id": "execution.contexts.only_on",
"introduced_in": "1.6.15"
},
{
"id": "metadata.ota.minimum_version",
"introduced_in": "1.6.15"
},
{
"id": "tasks.effects.writes",
"introduced_in": "1.6.15"
},
{
"id": "tasks.effects.network",
"introduced_in": "1.6.15"
},
{
"id": "tasks.effects.network_kind",
"introduced_in": "1.6.16"
},
{
"id": "tasks.effects.external_state",
"introduced_in": "1.6.15"
},
{
"id": "tasks.action.ensure_env_file",
"introduced_in": "1.6.16"
},
{
"id": "tasks.action.ensure_file",
"introduced_in": "1.6.16"
},
{
"id": "tasks.action.ensure_directory",
"introduced_in": "1.6.16"
},
{
"id": "tasks.action.ensure_git_checkout",
"introduced_in": "1.6.22"
},
{
"id": "tasks.action.ensure_git_template",
"introduced_in": "1.6.23"
},
{
"id": "tasks.action.ensure_git_checkout.remotes",
"introduced_in": "1.6.23"
},
{
"id": "tasks.action.ensure_git_checkouts",
"introduced_in": "1.6.23"
},
{
"id": "tasks.action.ensure_container_network",
"introduced_in": "1.6.21"
},
{
"id": "tasks.action.build_container_image",
"introduced_in": "1.6.24"
},
{
"id": "tasks.action.reset_compose_service_volume",
"introduced_in": "1.6.22"
},
{
"id": "tasks.action.ensure_bundle",
"introduced_in": "1.6.17"
},
{
"id": "checks.changed_files",
"introduced_in": "1.6.16"
},
{
"id": "tasks.when.checks",
"introduced_in": "1.6.17"
},
{
"id": "services.readiness.compose_health",
"introduced_in": "1.6.16"
},
{
"id": "tasks.runtime.readiness.signal_probes",
"introduced_in": "1.6.16"
},
{
"id": "agent.posture",
"introduced_in": "1.6.15"
},
{
"id": "agent.exceptions.sensitive_writes",
"introduced_in": "1.6.15"
},
{
"id": "native_prerequisites.visual_studio",
"introduced_in": "1.6.15"
},
{
"id": "native_prerequisites.requires",
"introduced_in": "1.6.15"
}
]
}
Notes:
semver,commit,dirty, andsource_buildidentify the exact binaryschema_versionis the current contract schema generation this binary speaksschema_versionmoves only when ota changes the machine-readable contract generation or compatibility interpretation in a way that is not just additivecontract_capabilities[]is additive and lists high-signal contract features this binary understands, including the Ota version where each feature first shipped- additive contract feature support should extend
contract_capabilities[]without changingschema_version
ota validate --json
Success:
{
"ok": true,
"path": "/abs/path/to/ota.yaml",
"summary": {
"error_count": 0,
"warn_count": 0
},
"warnings": [],
"warning_details": []
}
When validate warnings exist, warnings[] remains the backward-compatible string lane and
warning_details[] carries the stable machine-readable advisory objects with:
code, category, owner, severity, summary, why, and next.
Some advisory families also carry additive provenance. For selected dependency-plane boundary
advisories, that includes provenance.parent_backend_selection_source and
provenance.dependency_backend_selection_source, so automation can see whether the boundary came
from a task default mode, inherited parent backend, override, or another execution-selection lane
without scraping the summary text.
Failure shape can also include:
next: optional safe follow-up command, used for trust-sensitive refusal and review-first flowssummary.error_count: stable machine-facing count of validation errors or load failures
Failure:
{
"ok": false,
"path": "/abs/path/to/ota.yaml",
"summary": {
"error_count": 2,
"warn_count": 0
},
"errors": ["..."]
}
Or:
{
"ok": false,
"path": "/abs/path/to/ota.yaml",
"summary": {
"error_count": 1,
"warn_count": 0
},
"error": "..."
}
ota env --json
Success:
{
"ok": true,
"path": "/abs/path/to/ota.yaml",
"workflow": "docker-build",
"profile": "docker-build",
"summary": {
"contract_count": 3,
"source_count": 2,
"source_issue_count": 0,
"task_count": 1,
"resolved_count": 3,
"missing_count": 0,
"invalid_count": 0
},
"sources": [
{
"kind": "properties",
"path": "app.properties",
"label": "properties:app.properties",
"must_exist": false,
"status": "loaded"
},
{
"kind": "json",
"path": "env/runtime.json",
"label": "json:env/runtime.json",
"must_exist": true,
"status": "loaded"
},
{
"kind": "yaml",
"path": "env/runtime.yaml",
"label": "yaml:env/runtime.yaml",
"must_exist": false,
"status": "loaded"
}
],
"rendered_artifacts": [
{
"path": ".env.docker-build",
"kind": "dotenv",
"includes": ["DISCORD_TOKEN", "DOCS_SITE_BASE_URL"],
"exists": true
}
],
"env": [
{
"name": "DISCORD_TOKEN",
"kind": "contract",
"required": true,
"value": "***",
"source": "properties:app.properties",
"source_kind": "properties",
"source_path": "app.properties",
"source_status": "loaded",
"source_label": "properties:app.properties",
"status": "resolved"
},
{
"name": "DOCS_SITE_BASE_URL",
"kind": "contract",
"required": true,
"value": "https://docs.internal.example",
"source": "org policy",
"status": "resolved"
},
{
"name": "CI",
"kind": "task",
"required": false,
"value": "true",
"source": "task",
"status": "task"
}
]
}
When a workflow is selected, success output may also include workflow, profile, and
rendered_artifacts. rendered_artifacts reports workflow-owned env materialization such as a
rendered dotenv file, including its repo-relative path, artifact kind, ordered includes, and
whether that artifact currently exists on disk.
When a declared source is missing or invalid, sources carries additive source metadata:
kind, path, label, status, optional detail, and optional next. Resolved env entries
loaded from declared sources also carry additive source_kind, source_path, source_status, and
source_label fields. Missing or invalid required env values remain visible in env with status
such as missing or invalid. The kind field is an explicit curated source kind such as
dotenv, properties, json, yaml, or toml.
When --task is set, env[].required reflects the selected task path, not only repo-global
env.vars.<name>.required. A top-level env var that is optional repo-wide may still report
required: true in task-scoped output when the selected task/workflow closure references it from
tasks.<name>.requirements.env.
Canonical source status values are:
loadedmissing_optionalmissing_requiredparse_failedinvalid_structurecollision
When --task is set, the payload includes the task name:
{
"ok": true,
"path": "/abs/path/to/ota.yaml",
"task": "test",
"summary": {
"contract_count": 2,
"source_count": 1,
"source_issue_count": 0,
"task_count": 1,
"resolved_count": 2,
"missing_count": 0,
"invalid_count": 0
},
"sources": [],
"env": []
}
Failure:
{
"ok": false,
"path": "/abs/path/to/ota.yaml",
"task": "test",
"error": "task `test` is not defined in ota.yaml"
}
ota execution plan --json
When the repo declares workflows, ota execution plan --json may include additive top-level
workflow and task fields. workflow mirrors the selected canonical operational path, and
task names the concrete workflow run task that drove execution planning, or the workflow setup
task when the workflow does not declare a run phase. workflow.prepare_task and
workflow.prepare_action are additive path context only; they do not replace the concrete
execution task because host bootstrap is not the selected runtime identity. The workflow object
may also include additive notes and readiness_probes when the selected workflow declares notes
or references reusable named probes.
When the selected workflow owns a rendered env artifact, success output also includes
workflow_env_artifacts[] with the artifact path, kind, profile, ordered includes,
current exists state, and consuming task/service lanes.
Success:
{
"ok": true,
"path": "/abs/path/to/ota.yaml",
"contract": "/abs/path/to/ota.yaml",
"workflow": {
"name": "app",
"intent": "local_development",
"notes": "Use this workflow as the operator-first local path.\n",
"prepare_task": "setup:env:local",
"setup_task": "setup",
"run_task": "dev",
"required_services": ["postgres"],
"readiness_checks": ["app-health"],
"readiness_probes": ["app-ready"],
"exposes": ["http://127.0.0.1:5678"]
},
"task": "dev",
"workflow_env_artifacts": [
{
"path": ".env.docker-build",
"kind": "dotenv",
"profile": "docker-build",
"includes": ["DATABASE_URL"],
"exists": true,
"consumers": ["task:dev", "service:postgres"]
}
],
"contract_identity": {
"version": 1,
"project": {
"name": "ota"
},
"counts": {
"runtimes": 0,
"tools": 1,
"env": 1,
"services": 0,
"checks": 1,
"tasks": 4
}
},
"declared_execution": {
"preferred": "container",
"supported": ["native", "container"],
"lifecycle": "ephemeral",
"backends": {
"container": {
"image": "rust:1.94-bookworm"
}
}
},
"resolved": {
"backend": "container",
"backend_source": "contract preferred",
"lifecycle": "ephemeral",
"lifecycle_source": "contract lifecycle",
"image": "rust:1.94-bookworm",
"engine_candidates": ["docker", "podman"],
"target_strategy": "ephemeral per-run container"
}
}
Notes:
governance.preflight.proof_expected: trueonota up --jsonmeans the selected workflow is a proof-owning readiness lane, not just a receipt-only execution lane- when the executed
upresult still carries resolved preflight governance from the selected workflow lane,governance.preflightpreserves the same crossing posture fields used by preview and harness export, includingcrossing_required,crossing_classification, andcrossing_boundary_familywhen ota can recover them honestly governance.post_execution.proof_present: truemeans theuppipeline reached proof/readiness evidence emission during executiongovernance.post_execution.decision_basis[]is the additive machine-readable citation set for the current post-execution evidence posture:- non-run basis such as
not_run:preview_only,not_run:preflight_blocked, ornot_run:preflight_refusal - evidence basis such as
evidence:receipt_presentorevidence:proof_present - crossing-record basis such as
crossing_record:attached,crossing_record:suppressed_by_refusal, orcrossing_record:not_required
- non-run basis such as
Failure:
{
"ok": false,
"path": "/abs/path/to/ota.yaml",
"errors": ["..."]
}
ota execution topology --json
Execution topology inspection stays read-only. It reports the declared execution surface that a contract or topology viewer can render without starting tasks or services.
Notes:
- top-level
readiness_probesis present when the contract declares reusable readiness probes; it exposes the canonical literal-URL or topology-derived source plus the declared HTTP/TCP request contract for each probe - top-level
surfacesis present when the contract declares reusable runtime surfaces; it exposes the declared kind, port, optional label, optional purpose, optional visibility, optional path, and optional readiness contract for each reusable surface tasks[*].env_filesis present when one task owns ordered task-local dotenv overlays for execution; these overlays are execution-only inputs, not rootenv.sourcestasks[*].adapter_inputs.compose.cwdis present when one task owns the adapter working directory fordocker compose; Ota enters that directory at runtime and reprojects declared compose file inputs relative to ittasks[*].adapter_inputs.compose.env_filesis present when one task owns ordered compose interpolation files; Ota projects these through adapter-aware runtime input rather than pretending they are process dotenv overlaystasks[*].adapter_inputs.compose.filesandtasks[*].adapter_inputs.compose.project_nameare present when one task owns compose file selection or project naming; Ota projects these through adapter-aware runtime input instead of shell-f/-pflagstasks[*].adapter_inputs.bake.cwdandtasks[*].adapter_inputs.bake.filesare present when one task owns Bake adapter root or Bake file selection; Ota projects these through adapter-aware runtime input instead of shellcd ... &&/-fgluetasks[*].adapter_inputs.helm.cwd,.values_files,.chart,.release_name, and.namespaceare present when one task owns Helm adapter root, values-file selection, chart selection, release naming, or namespace truth; Ota projects these through adapter-aware runtime input instead of shellcd ... && helm ..., chart positionals, or--namespacetasks[*].launchis present when one task uses structuredlaunchinstead of shellrunorscript; it exposes the launch kind plus the structured command or packaged container metadatatasks[*].command.interactionis the resolved interaction posture for a structuredcommand:task after defaulting. Consumers should treat it as one ofauto,forbidden, orrequired.- for a selected structured
command:task,ota run --dry-run --jsonadditionally publishesinteraction.posture,interaction.resolution, andinteraction.terminal_available; resolution is one ofterminal_passthrough,piped, orrefusedfor that invocation boundary. Other task-body families omitinteractionrather than inventing a posture they do not own. - task-target probe entries also expose
target.observerandtarget.resolution_plane; the default command-host slice reportscommand_host, while observer-backed task probes report the named task plane they resolve through tasks[*].runtime.readiness.probeis present when one task runtime reuses a namedreadiness.probes.<name>declaration instead of declaring inline HTTP/TCP readiness transport; the named probe itself may be literal-URL-backed or target-backedservices[*].readiness.probeis present when one managed service reuses a namedreadiness.probes.<name>declaration instead of declaring inline structured readiness transport; the named probe itself may be literal-URL-backed or target-backed
Success:
{
"ok": true,
"path": "/abs/path/to/ota.yaml",
"contract": "/abs/path/to/ota.yaml",
"contract_identity": {
"version": 1,
"project": {
"name": "topology-demo",
"type": "application"
}
},
"declared_execution": {
"default_context": "development:ctx"
},
"shared_backends": [
{
"name": "workbench",
"scope": "local",
"backend": "container",
"lifecycle": "persistent",
"context": "development:ctx",
"fulfillment": "run"
}
],
"readiness_probes": {
"app-ready": {
"kind": "http",
"target": {
"kind": "task",
"name": "api",
"listener": "backend",
"address_view": "host",
"observer": {
"kind": "command_host"
},
"resolution_plane": "command_host"
},
"method": "GET",
"path": "/health",
"success": {
"status": [200]
},
"timeout_ms": 10000
}
},
"surfaces": {
"backend": {
"kind": "http",
"port": 3000,
"label": "Backend API",
"purpose": "Primary local application API",
"visibility": "internal",
"path": "/",
"readiness": {
"kind": "http",
"path": "/health"
}
}
},
"services": [],
"tasks": [
{
"name": "api",
"launch": {
"kind": "command",
"exe": "npx",
"args": ["vite", "--host", "127.0.0.1", "--port", "3000"]
},
"runtime": {
"kind": "service",
"backend_binding": "workbench",
"readiness": {
"kind": "http",
"listener": "backend",
"path": "/health"
},
"attached_surfaces": ["backend"],
"surface_attachments": {
"backend": {
"uses_defaults": false,
"bind": {
"address": "0.0.0.0",
"port_mode": "fixed",
"port_value": 3000
},
"project": {
"host": {
"address": "127.0.0.1",
"port_mode": "fixed",
"port_value": 3001,
"primary": true
}
}
}
},
"listeners": {
"backend": {
"protocol": "http",
"bind_address": "0.0.0.0",
"bind_port_mode": "fixed",
"bind_port_value": 3000,
"host_projection": {
"address": "127.0.0.1",
"port_mode": "fixed",
"port_value": 3001,
"primary": true
}
}
}
}
},
{
"name": "web",
"targets": [
{
"name": "api",
"kind": "service",
"activation_mode": "ensure_ready",
"service": {
"task": "api",
"listener": "http",
"address_view": "host"
}
}
]
}
]
}
Failure:
{
"ok": false,
"path": "/abs/path/to/ota.yaml",
"errors": ["..."]
}
ota ci projection --json and ota ci github <render|check|sync> --json
ota ci projection --workflow <name> --mode <native|container|remote> --target-os <linux|macos|windows> --json emits the canonical
provider-neutral governance projection; its schema is ci-projection.json.
GitHub projection commands consume that object. render has
mutated: false and returns the generated YAML under projection.rendered; an optional output path
is metadata only. check compares the marked managed file and caller reference without writing.
sync may set mutated: true only after it has verified the caller's structural binding and
the existing target is absent or Ota-owned.
Success:
{
"ok": true,
"operation": "check",
"projection": {
"projection": {
"workflow": "verify",
"task": "verify",
"run_execution": "finite_task",
"mode": "native",
"target_os": "linux",
"refusal_canaries": [
{
"kind": "task",
"target": "publish",
"merge_check_id": "ota.refusal-canary.task.publish"
}
],
"toolchains": [
{ "name": "go", "source": "go", "version": "1.26", "execution_scopes": ["native"] }
],
"bootstrap": { "source_kind": "version", "source_identity": "v1.6.24" },
"proof_required": false,
"governance": {
"agent_admission": {
"decision": "allow",
"basis": ["agent_closure_admitted"]
}
},
"identity": "sha256:..."
},
"runner": "ubuntu-latest",
"provider_checks": [
{
"merge_check_id": "ota.verify.verify",
"provider_check_name": "ota.verify.verify (linux/native)"
}
],
"content_identity": "sha256:...",
"render_identity": "sha256:..."
},
"output_path": ".github/workflows/ota-governance.yml",
"caller_path": ".github/workflows/ci.yml",
"binding_identity": "sha256:...",
"mutated": false
}
Failure:
{
"ok": false,
"code": "managed_output_stale",
"message": "..."
}
managed_output_unowned, caller_projection_reference_mismatch, and normalized
.github/workflows-path refusal are separate failures. render_identity binds the canonical
projection to the GitHub adapter version, normalized adapter inputs, and generated content;
binding_identity additionally binds that render to the parsed caller reference. A caller or output mismatch never triggers
a silent synchronization.
Generated lanes always follow the contract's agent-safe admission path. A proof claim qualifies
evidence breadth; it does not authorize an unsafe execution lane. For an admitted proof-required
workflow, the generated adapter uses ota proof runtime as the sole real execution lane.
semantic_contract_identity is the same normalized semantic snapshot identity carried by Ota
receipts. bootstrap carries the portable contract-owned bootstrap posture, and proof_claim
names the declared proof requirement. A denied provider-neutral projection still returns its
evaluated projection plus a typed refusal in JSON, so CI consumers can inspect the exact
identity, decision, and basis rather than infer them from an error string.
projection.toolchains[] is the normalized, selected-closure toolchain requirement for the target
OS. It is contract truth, not a runner-image assumption. Provider adapters may render supported
setup steps from it; unsupported required sources fail render rather than becoming ambient setup.
projection.refusal_canaries[] is the canonical contract-owned negative-control set for the
selected CI lane. Each unique entry carries kind, target, and a stable merge_check_id that
maps directly to one provider check; adapters run the corresponding Ota --expect-refusal command
and let its typed exit semantics enforce the control. A provider adapter must not substitute
shell/JQ assertions or contract-authored refusal reasons.
GitHub exposes that mapping in projection.provider_checks[]. merge_check_id stays canonical and
scope-free; provider_check_name adds the selected target OS and execution mode, preventing the
same canonical lane from collapsing when a caller runs it across multiple platforms.
ota proof lifecycle --json
Lifecycle proof records one runner-owned service transition transaction. ok reports whether the
selected transaction completed and cleanup reached its declared terminal boundary; consumers must
read proof_verdict with required not_proved[] before treating it as application or repo proof.
modeislifecycle-proof,phaseislifecycle, andstage_familyisproof.- A concurrent lifecycle transaction is rejected before a manager observation or cleanup lease;
its finalization is
not_runand its terminal record remains an unsuccessful, lease-free transaction. - Each
services[]record is bound to the same transaction identity and names the observed pre-existing state, cleanup lease, ownership, and start/readiness/teardown outcomes. - An isolated-boundary record additionally carries runner-owned
boundary_identity. The archive scope carries the same identity;boundary_terminatedis invalid unless both point to the exact container session Ota created and later removed. startandteardowncommand outcomes are runner-attested. Readiness and manager-state observations are derived from the declared service manager; an omitted readiness remainsnot_declared, never a positive state claim. Phase-specific transition states are enforced: command phases cannot emit a readiness or boundary state, andboundary_terminatedis always an attested teardown assertion. An isolated removal failure is also attested against its exact boundary identity and leaves finalization incomplete; a manager-state failure remains derived.- When a workflow declares an assertion,
assertioncarries its task, terminal state, and exit code when available. Failed or interrupted assertions also carry bounded runner-captured stdout/stderr tails with declared secret values redacted.output_truncated: truemeans one or both tails were limited to 8 KiB; these diagnostics explain the transaction result but do not widen lifecycle proof into application-output proof. - A successful lifecycle transition is always
passed_with_unproven_boundaries, never barepassed. Ota always emitsapplication_output_not_provedandbroader_repo_completion_not_provedunless a future dedicated output-proof carrier establishes broader truth. - When a selected service has no declared readiness or manager-owned start-state observation,
Ota also emits obligation-scoped
service_started_state_not_proved; a successful start command alone is never promoted into an observed started state. ota proof lifecycle --json --archiveaddsarchive.identityandarchive.path. The immutable local record binds the semantic contract snapshot and source identity when available, selected member/workflow/services plus the snapshot-derived dependency closure and teardown authority, transaction, complete service records, terminal verdict and finalization, exact isolated-boundary identity when used, and effective backend/mode/provider/lifecycle/target/target OS. Its content-addressed filename, content identity, contract identity, and archived semantic snapshot reference are verified before Ota accepts the record as locally well-formed. It does not establish application-output proof, CI eligibility, claim assurance, replay, or broader repo completion.--agent,--mode <native|container|remote>, and--member <name>reuse the selected workflow's agent admission, task-mode resolution, and monorepo target loading. Service-manager commands retain their declared manager boundary; the mode applies to workflow prerequisite and assertion tasks rather than pretending that a host manager was relocated.finalization.state: completed_after_interruptionandafter_interruption: truerecord that the runner completed its reverse finalizer after an interrupted start, readiness observation, or assertion; each service record retains the corresponding typedinterruptedtransition and teardown evidence.incomplete_after_interruptionpreserves both facts when a teardown interruption also leaves manager-state cleanup unproved.
The command schema is proof-lifecycle.json; archived records use proof-lifecycle-archive.json.
ota baseline record|promote --json
The command-result schema is replay-baseline.json. The portable recorded attestation and committed authority-manifest schema is replay-baseline-authority.json.
state: recordedmeans Ota executed the declared producer successfully and emitted one content-addressed attestation bound to the archived execution receipt, exact task scope, and resolved backend/lifecycle. It does not select that record for replay.state: promotedmeans an explicit caller selected one previously recorded attestation and Ota atomically wrote the declared portable authority manifest. The manifest embeds that immutable attestation, so a fresh clone can inspect the selected record without local.otaarchive retention. Itstrust_root: scm_reviewdeclares repository review as the external authority boundary; Ota does not verify reviewer inclusion or signer provenance.attestation_identityalways identifies the selected Ota-recorded attestation.promotion_identityis present only after promotion.ok: falsehascode: replay_baseline_operation_failed; no partial producer output, newest record, or handwritten digest is implicitly promoted.
Consumers validate the portable promoted authority and its complete output identity set before execution. The record and promotion JSON do not claim that a human reviewed the change; the manifest's explicit SCM-review trust root identifies the external authority boundary only.
ota proof runtime --json
Runtime proof stays intentionally thin. It proves one selected runtime path and captures the real declared artifacts that remain canonical:
topology.jsonfromota execution topology --jsondoctor.jsonfromota doctor --jsonup.logfrom the repo-level runtime-preparation lane
Notes:
modeis alwaysruntime-proofworkflowis present when the proof targeted one explicit or effective workflowphasestays machine-stable and uses the proof phase keyspreconditions,prepare,setup,services,run,readiness,cleanup, andinterruptedstage_familyis alwaysproof, so CI and agents can classify this wrapper without inferring from proof phase namesokis execution/readiness success only. It is necessary but insufficient for a proof claim: consumers must interpretproof_verdicttogether withnot_proved[]before treating a selected lane as a proof result.proof_verdictis the terminal evaluation of this proof carrier:passedmeans the selected runtime path passed with no declared boundary,passed_with_unproven_boundariesmeans the path passed but must not be over-read beyond its published boundary, andfailedmeans selected-lane execution or readiness evaluation failed; parse/load failures do not enter this carrierproof_scopeis the first canonical machine-readable boundary for this carrier; it names the covered runtime-path lane and keeps narrow proof from being over-read as broader repo truthexecution_boundaryis additive runner-authored prerequisite provenance. Itsidentitybinds the sorted asserted-target and derivation-input closures, declared artifact/producer ownership, prerequisite records (including a verified precondition identity when state is reused), and ordered causal edges.target_freshnessis independent fromderivation_posture:unknownmeans Ota did not witness the complete precondition, materialization, and assertion chain; it must not be treated as a cold start. The first runner-attested paths cover nativeensure_virtualenvfollowed by a runner-recorded native.venv/bin/*consumer, and frozen native pnpm hydration followed by a declared localpnpm execor package-script consumer. The pnpm identity joins its generatednode_modules/.modules.yamllayout marker to the declaredpnpm-lock.yaml; it is not a claim to hash the entire mutable dependency tree. Both emitasserted_at, notconsumed: Ota observed the recovered identity at the selected executable or local package-resolution boundary, but does not claim adapter-instrumented process-level use. Other filesystem artifacts, container paths, Windows virtualenv executables, other Node package-manager layouts, volumes, caches, and provider state remainunknownuntil Ota can attest the same complete chain.dependency_evidence[]is additive positive seam evidence for the selected runtime path. The current first carrier is intentionally narrow: Ota emitslevel: reachableonly when a declared service seam is also part of the selected workflow's required-service closure and Ota has a structured readiness owner for that selected service. This is runner-derived reachability evidence, not proof of exercised interaction across the seam.dependency_evidence[].interaction_attemptedis additive caller-side-only seam evidence. Ota emits it when the selected proof lane clearly attempted the dependency interaction from the caller side, but still lacks independent dependency-side or round-trip evidence to promote that seam beyondnot_proved.dependency_evidence[].observation.originnames where the seam evidence came from. Current shipped reachable records usedependency_sidefor manager-owned readiness such ascompose_healthandsystemd_active, andround_trip_effectfor probe-owned readiness such ashttpandtcp; current shipped attempted records usecaller_side.dependency_evidence[].observation.evidence_classis the V11.9 authority class for the observation itself. Current shipped reachable records arederived.seam_observations[]records each declared marker-bound producer/observer pair. Ota injects the opaque marker into the declared producer task, but never passes it to the observer. The observer must write a transient JSON attestation after recovering that marker through the dependency. Only a matching transaction id, observation id, and marker emits an attesteddependency_evidence[].level: exercisedrecord withobservation.origin: round_trip_effect. Receipts retain non-secret transaction and attestation digests; the marker and transient attestation are removed after verification. Failures leave the seam explicitly not proved.- Text output renders the same
Dependency EvidencebeforeProof Boundaries, including the level, observation origin, and authority class. A caller-side-only attempt is rendered as not independently exercised; it does not become a green seam claim in human output. negative_controlis optional canonical boundary-attested evidence from one explicitly selectedworkflows.<name>.proof.negative_controls[]task. It names its greenobligation_id, carries the matching prooftransaction_id, contract-declaredintervention, and publishesstatus: unrun | invalid | validated. Failed controls also carry a runner-derivedfailure_modewhen Ota can classify one.nonzero_exit_observedandunclassified_nonzeroare bounded evidence, not evidence that the declared dependency caused the failure. Onlyvalidatedwithoutcome: expected_obligation_failed,failure_mode: expected_missing_effect, and a matching failure-attestation digest may promote the exactdependency_evidence[].proof_obligation_idrecord tofault_tested. The nesteddependency_evidence[].negative_controlobject carriesevidence_class: derivedas a self-describing projection of this canonical record, never a second source of truth.unexpected_success,control_could_not_run, stale evidence, and unrelated non-zero exits areinvalidorunrunand fail the selected control proof.not_proved[]is relative to that declared runtime-path scope, not free-floating commentary; Ota emitsfunctional_runtime_not_provedwhen proof fell back to a setup-only lane,dependency_exercise_not_provedfor each declared service seam in the selected closure when Ota has not independently observed a contract-specific interaction across it,external_network_path_not_provedwhen an adjacent declared workflow owns an explicit external integration-test path for the selected lane's declared external state, anddependency_causality_not_provedwhen a seam was exercised but no matching negative control validated causal dependency necessity, anddependency_output_shaping_not_provedfor every marker-bound seam obligation: an exercised or fault-tested seam proves only its declared seam obligation, never broader application-output shaping, andbroader_repo_completion_not_provedas the scope-derived remainder outside this runtime slicenot_proved[].declared_by_workflowsnames the adjacent contract workflows that establish a contract-derived exclusion; scope-derived exclusions omit itnot_proved[].dependency_idandnot_proved[].declared_by_tasksidentify a declared service seam that remains unproved. This is deliberately not evidence that the service is unused or unreachable; it prevents a green runtime proof from being over-read as an exercised dependency.not_proved[].proof_obligation_idappears when a boundary is narrower than its dependency. Every marker-bound seam retainsdependency_output_shaping_not_provedfor the same obligation; absence is reserved for a future explicit output-proof carrier. Ota may prove dependency exercise or necessity for that seam, not that the dependency shaped a broader application output.- when Ota has only caller-side seam evidence, the paired
dependency_exercise_not_provedboundary tightensreasonfrom generic missing evidence tocaller_side_only_evidence artifact_routing[]points at the proof artifact bundle this wrapper governs, such asproof_runtime_json,proof_topology,proof_doctor, andproof_up_logota proof runtime --json --archiveadditionally writes one immutable proof-owned record under.ota/proof/archives/and returns additivearchive.identityandarchive.path. The archive is content-addressed and binds the terminal proof JSON to an archived semantic contract snapshot, clean Git source identity when available, resolved workflow/task/backend/provider/lifecycle scope, and explicitreplay_posture: witness_only. The mutable.ota/proof/<lane>/working bundle remains supporting evidence, not a replay-grade witness. Archive consumers also verify thatcontract_snapshot_refresolves to the same content-addressed snapshot ascontract_snapshot_hashbefore using the record as assurance evidence. The archive retains the exact sameexecution_boundaryrecord and identity as the emitted proof output.summaryreuses the doctor verdict/count shape instead of inventing a second readiness dialectartifactspoints at the captured canonical payloads; machine consumers should inspect those files directly when they need the full topology or doctor surfaceworkflow_env_artifactsis additive and reports workflow-owned rendered env files plus the consuming task/service lanes that proof exercised- when the selected run task is a service launcher that exits successfully before the runtime is
actually ready, such as
docker compose up -d, proof keeps using the declared readiness budget instead of collapsing that launcher exit into an immediate proof failure likely_causeis optional and appears only when ota can derive a higher-confidence runtime-drift hint from captured proof logs; treat it as advisory, not a replacement fordoctor.jsonorup.loglikely_cause_evidenceis optional and publishes the machine-readable root-cause signal behindlikely_cause; current shipped kinds areauth_credential_failure,dns_service_name_resolution_failure,readiness_target_mismatch,loopback_service_drift,missing_env,install_or_toolchain_failure,bind_conflict, anddetached_run_outputlikely_cause_evidencecan now publish additive target fields such aslistener,declared_target, andobserved_targetwhen ota can compare the declared proof endpoint with the runtime endpoint it observed in proof artifactscleanup_failureis optional and appears when ota can classify proof-boundary cleanup failure truth structurally; it keeps cleanup reason, owned resource lane, and next steps machine-readable without forcing automation back onto prose parsing- cleanup failures still surface through top-level
error,failure_class: cleanup_failure, andnextso existing consumers remain compatible while richer cleanup evidence stays additive - high-confidence runtime-drift hints upgrade readiness-shaped failures to
failure_class: config_driftso automation can distinguish probable contract/config drift from generic slow-start timeouts - high-confidence port/listener collisions upgrade readiness-shaped failures to
failure_class: bind_conflictso automation can distinguish address-in-use failures from generic startup noise - high-confidence install/hydration/compiler failures can now surface structured
likely_cause_evidence.kind: install_or_toolchain_failurefrom capturedup.log, so the proof lane does not reduce package-manager/toolchain breaks back to prose-only diagnostics - high-confidence missing-config failures can now surface structured
likely_cause_evidence.kind: missing_env, including the missing variable name when Ota can recover it from capturedup.log - high-confidence runtime endpoint mismatches can now surface structured
likely_cause_evidence.kind: readiness_target_mismatch, including the declared readiness target and the observed runtime target when Ota can recover both from proof artifacts - high-confidence dns/service-name resolution failures can now surface structured
likely_cause_evidence.kind: dns_service_name_resolution_failure, including the unresolved host name when Ota can recover it from proof logs - high-confidence auth/credential failures can now surface structured
likely_cause_evidence.kind: auth_credential_failure, including the named backend service when Ota can recover it from proof logs - timeout-only failures without a blocking primary doctor finding are normalized to
failure_class: readiness_timeout - signal-terminated proof runs are normalized to
failure_class: interrupted - pre-runtime blockers are normalized to phase
preconditionswithfailure_class: precondition_blocked
Success:
{
"ok": true,
"path": "/abs/path/to/ota.yaml",
"mode": "runtime-proof",
"workflow": "app",
"phase": "readiness",
"stage_family": "proof",
"proof_verdict": "passed_with_unproven_boundaries",
"proof_scope": {
"kind": "runtime_path",
"proof_class": "slice_proof",
"workflow": "app",
"task": "serve",
"intent": "packaged_runtime"
},
"dependency_evidence": [
{
"dependency_id": "service:postgres",
"proof_obligation_id": "postgres-marker",
"level": "fault_tested",
"observation": {
"origin": "round_trip_effect",
"evidence_class": "attested"
},
"declared_by_tasks": ["serve"],
"declared_by_workflows": ["app"],
"negative_control": {
"status": "validated",
"same_obligation": true,
"failure_mode": "expected_missing_effect",
"failure_attestation_digest": "sha256:..."
}
},
{
"dependency_id": "service:postgres",
"interaction_attempted": true,
"observation": {
"origin": "caller_side",
"evidence_class": "derived"
},
"declared_by_tasks": ["serve"],
"declared_by_workflows": ["app"]
}
],
"negative_control": {
"id": "postgres-unavailable",
"dependency_id": "service:postgres",
"obligation_id": "postgres-marker",
"transaction_id": "sha256:...",
"control_task": "verify:postgres-unavailable",
"intervention": {
"kind": "dependency_endpoint_override",
"id": "postgres-unavailable"
},
"expected_failure": "dependency_unavailable",
"outcome": "expected_obligation_failed",
"status": "validated",
"failure_mode": "expected_missing_effect",
"proof_scope_ref": "workflow:app/negative_control:postgres-unavailable",
"evidence_class": "attested",
"failure_attestation_digest": "sha256:...",
"exit_code": 1
},
"not_proved": [
{
"kind": "dependency_exercise_not_proved",
"relative_to": "runtime_path",
"source": "contract_lane",
"dependency_id": "service:postgres",
"reason": "caller_side_only_evidence",
"declared_by_tasks": ["serve"],
"declared_by_workflows": ["app"]
},
{
"kind": "broader_repo_completion_not_proved",
"relative_to": "runtime_path",
"source": "proof_scope"
}
],
"summary": {
"verdict": "ready",
"agent_verdict": "ready",
"error_count": 0,
"warn_count": 0,
"info_count": 0
},
"artifacts": {
"topology": "./.ota/proof/app/topology.json",
"doctor": "./.ota/proof/app/doctor.json",
"up_log": "./.ota/proof/app/up.log"
},
"workflow_env_artifacts": [
{
"path": ".env.docker-build",
"kind": "dotenv",
"profile": "docker-build",
"includes": ["DATABASE_URL"],
"exists": true,
"consumers": ["task:app", "service:postgres"]
}
]
}
Blocked:
{
"ok": false,
"path": "/abs/path/to/ota.yaml",
"mode": "runtime-proof",
"workflow": "docker",
"phase": "services",
"summary": {
"verdict": "not_ready",
"agent_verdict": "ready",
"error_count": 1,
"warn_count": 0,
"info_count": 0,
"primary_blocker": {
"severity": "error",
"summary": "Service readiness failed: backend",
"why": "readiness probe did not report success before timeout",
"next": "inspect `./.ota/proof/docker/up.log`, then rerun `ota doctor --workflow docker .`"
}
},
"artifacts": {
"topology": "./.ota/proof/docker/topology.json",
"doctor": "./.ota/proof/docker/doctor.json",
"up_log": "./.ota/proof/docker/up.log"
},
"failure_class": "config_drift",
"likely_cause": "likely config drift: the runtime is still targeting Redis on loopback (127.0.0.1:6379) inside a multi-service startup path; move that host binding into a workflow-scoped env overlay, rendered workflow env artifact, task `env_files`, or compose `manager.env_file` instead of `127.0.0.1` / `localhost`",
"likely_cause_evidence": {
"kind": "loopback_service_drift",
"artifact": "./.ota/proof/docker/up-detached-run.log",
"signal": "connection_refused",
"service": "Redis",
"host": "127.0.0.1",
"port": 6379
}
}
Cleanup-classified failure:
{
"ok": false,
"path": "/abs/path/to/ota.yaml",
"mode": "runtime-proof",
"workflow": "app",
"phase": "cleanup",
"summary": {
"verdict": "ready",
"agent_verdict": "ready",
"error_count": 0,
"warn_count": 0,
"info_count": 0
},
"artifacts": {
"topology": "./.ota/proof/app/topology.json",
"doctor": "./.ota/proof/app/doctor.json",
"up_log": "./.ota/proof/app/up.log"
},
"failure_class": "cleanup_failure",
"error": "cleanup failed",
"cleanup_failure": {
"summary": "Cleanup failed",
"error": "cleanup failed",
"why": "`ota clean` could not stop host-managed service `redis` before cleaning repo state.",
"next": ["inspect the stop command for `redis` and rerun `ota clean`"],
"reason": "other",
"engine": "host",
"action": "stop",
"resource_kind": "host_service",
"resource_name": "redis",
"details": "stop command exited with code 1 (permission denied)"
},
"next": "run `ota clean /abs/path/to/ota.yaml` to remove the remaining runtime state, then rerun proof"
}
ota services --json
Services inspection stays read-only. It reports the declared managed-service inventory without starting or probing those services.
Notes:
- top-level
serviceslists declared repo services in deterministic order membersis present when a monorepo root request includes grouped member service summaries- canonical service docs focus on
producer,manager, projectedendpoints, structuredreadiness, anddepends_on; compatibility-mode fields may still appear in output when older contracts are inspected
Success:
{
"ok": true,
"path": "/abs/path/to/ota.yaml",
"services": [
{
"name": "postgres",
"required": true,
"manager": {
"kind": "compose",
"name": "local",
"file": "compose.yaml",
"service": "postgres"
},
"readiness": {
"kind": "compose_health"
},
"depends_on": []
}
]
}
Failure:
{
"ok": false,
"path": "/abs/path/to/ota.yaml",
"errors": ["..."]
}
ota assist declare-readiness --json
Assist readiness output reports one deterministic proposal or apply result for an existing task runtime service or managed service.
Success:
{
"ok": true,
"path": "/abs/path/to/ota.yaml",
"mode": "preview",
"operation": "declare-readiness",
"subject": {
"service": "api"
},
"inputs": {
"endpoint": "host_3000",
"style": "http"
},
"assumptions": [
"service `api` already declares endpoint `host_3000`",
"structured readiness should anchor to endpoint `host_3000` in context `host`"
],
"changes": [
{
"path": "services.api.readiness",
"action": "set",
"before": null,
"after": {
"kind": "http",
"from": "host",
"endpoint": "host_3000",
"method": "GET",
"path": "/health"
}
}
],
"diff": "services.api.readiness\n- <absent>\n+ kind: http ...",
"validation": [
"ota validate /abs/path/to/ota.yaml",
"ota doctor /abs/path/to/ota.yaml"
],
"next": "rerun with `ota assist declare-readiness --service api --endpoint host_3000 --style http --write /abs/path/to/ota.yaml` to apply this readiness change"
}
Notes:
pathis the resolved repo contract pathmemberis present only when--membertargeted a merged monorepo member contractsubjectcontains exactly one selector key today:taskorserviceinputs.endpointis present only when--endpointselected one explicit managed-service projectionchanges[*].beforeis present when assist is refining or replacing an existing readiness block, including legacy top-level readiness shapeschanges[*].afteruses the canonical readiness contract shape Ota would writemodeispreviewby default andwritewhen--writesucceededinputs.stylemay also becompose-healthfor compose-managed--servicetargets
Failure:
{
"ok": false,
"path": "/abs/path/to/ota.yaml",
"operation": "declare-readiness",
"subject": {
"task": "dev"
},
"why": "task `dev` has multiple readiness candidate listeners; assist cannot pick one safely",
"next": "rerun with `--style <spring-http|http|tcp>` after narrowing the runtime surface"
}
ota assist declare-service --json
Assist service output reports one deterministic proposal or apply result for one top-level managed service declaration.
Success:
{
"ok": true,
"path": "/abs/path/to/ota.yaml",
"mode": "preview",
"operation": "declare-service",
"subject": {
"service": "postgres"
},
"inputs": {
"manager": "compose",
"endpoint": "web",
"endpoint_context": "host",
"address": "127.0.0.1",
"port": "5432",
"required": "false",
"compose_file": "docker-compose.yml",
"compose_service": "postgres",
"style": "tcp"
},
"assumptions": [
"service `postgres` will be created under `services`",
"endpoint `host` is the service projection boundary",
"manager `compose` is the service owner"
],
"changes": [
{
"path": "services.postgres",
"action": "set",
"before": null,
"after": {
"required": false,
"manager": {
"kind": "compose",
"name": "local",
"file": "docker-compose.yml",
"service": "postgres"
},
"endpoints": {
"web": {
"context": "host",
"address": "127.0.0.1",
"port": 5432
}
},
"readiness": {
"from": "host",
"endpoint": "web",
"kind": "tcp"
}
}
}
],
"diff": "services.postgres\n- <absent>\n+ required: false ...",
"validation": [
"ota validate /abs/path/to/ota.yaml",
"ota doctor /abs/path/to/ota.yaml"
],
"next": "rerun with `ota assist declare-service --name postgres --manager compose --endpoint web --endpoint-context host --address 127.0.0.1 --port 5432 --compose-file docker-compose.yml --compose-service postgres --style tcp --write /abs/path/to/ota.yaml` to apply this service change"
}
Notes:
subject.serviceis the targeted top-level managed service nameinputsrecords the explicit or defaulted service declaration inputs used to build the proposalinputs.endpoint_contextis present when assist is authoring an endpoint whose identity and execution context differchanges[*].beforeis present when assist is refining an existing service blockchanges[*].afteris the exact service block Ota would write
Failure:
{
"ok": false,
"path": "/abs/path/to/ota.yaml",
"operation": "declare-service",
"subject": {
"service": "postgres"
},
"why": "service `postgres` needs an explicit manager kind",
"next": "rerun with `--manager compose` or `--manager host`"
}
ota assist bind-task --json
Assist target-binding output reports one deterministic proposal or apply result for
tasks.<consumer>.targets.<name>.
Success:
{
"ok": true,
"path": "/abs/path/to/ota.yaml",
"mode": "preview",
"operation": "bind-task",
"subject": {
"task": "smoke",
"target": "api"
},
"inputs": {
"to": "dev:http",
"address_view": "topology",
"activation": "manual"
},
"assumptions": [
"a new target binding will be created under `tasks.smoke.targets.api`",
"task `smoke` will resolve `api` through producer task `dev` listener `http`",
"ota will resolve this edge with `address_view: topology`",
"`activation.mode` will be `manual`"
],
"changes": [
{
"path": "tasks.smoke.targets.api",
"action": "set",
"before": null,
"after": {
"service": {
"task": "dev",
"listener": "http",
"address_view": "topology"
},
"activation": {
"mode": "manual"
}
}
}
],
"diff": "tasks.smoke.targets.api\n- <absent>\n+ service: ...",
"validation": [
"ota validate /abs/path/to/ota.yaml",
"ota execution topology /abs/path/to/ota.yaml"
],
"next": "rerun with `ota assist bind-task --task smoke --target api --to dev:http --address-view topology --activation manual --write /abs/path/to/ota.yaml` to apply this task binding"
}
Notes:
subject.taskandsubject.targetidentify the consumer task and the target key being changedinputs.tois normalized to the explicit<producer>:<listener>shape even when preview inferred the listener from a single-listener producerchanges[*].beforeis present when assist is refining an existing target bindingchanges[*].afteris the exact target block Ota would write, includingactivation.modeand any explicitoverride_input- validation includes
ota execution topologybecause target bindings change the declared execution graph directly
Failure:
{
"ok": false,
"path": "/abs/path/to/ota.yaml",
"operation": "bind-task",
"subject": {
"task": "smoke",
"target": "api"
},
"why": "producer task `dev` declares multiple listeners, so assist cannot pick one safely",
"next": "rerun with `--to <task>:<listener>` after checking `ota execution topology`"
}
ota assist declare-env --json
Assist env output reports one deterministic proposal or apply result for one root env requirement, one declared env source, or one explicit task-local env override.
Success:
{
"ok": true,
"path": "/abs/path/to/ota.yaml",
"mode": "preview",
"operation": "declare-env",
"subject": {
"kind": "root_var",
"name": "APP_PORT"
},
"inputs": {
"required": "true",
"default": "8080"
},
"assumptions": [
"root env requirement `APP_PORT` will be declared under `env.vars`"
],
"changes": [
{
"path": "env.vars.APP_PORT",
"action": "set",
"before": null,
"after": {
"required": true,
"default": "8080"
}
}
],
"diff": "env.vars.APP_PORT\n- <absent>\n+ required: true ...",
"validation": [
"ota validate /abs/path/to/ota.yaml",
"ota env /abs/path/to/ota.yaml",
"ota doctor /abs/path/to/ota.yaml"
],
"next": "rerun with `ota assist declare-env --name APP_PORT --required true --default 8080 --write /abs/path/to/ota.yaml` to apply this env change"
}
Notes:
subject.kinddistinguishesroot_var,source, andtask_envsubject.taskis present only for task-local env writessubject.source_kindandsubject.source_pathare present only for declared env sourcesinputsrecords only the explicit assist inputs supplied for that one mutation- validation uses
ota envorota env --task <name>because env declaration changes should be reviewed through the same read path Ota already trusts
Failure:
{
"ok": false,
"path": "/abs/path/to/ota.yaml",
"operation": "declare-env",
"subject": {
"task": "smoke",
"name": "API_BASE"
},
"why": "task-local env declaration needs `--value`",
"next": "rerun with `--task <name> --name <ENV> --value <value>`"
}
ota assist add-task --json
Assist add-task output reports one deterministic proposal or apply result for one new
tasks.<name> declaration.
Success:
{
"ok": true,
"path": "/abs/path/to/ota.yaml",
"mode": "preview",
"operation": "add-task",
"subject": {
"task": "dev"
},
"inputs": {
"kind": "service",
"run": "npm run dev",
"internal": "false",
"listener": "http",
"protocol": "http",
"address": "127.0.0.1",
"port": "3000"
},
"assumptions": [
"assist adds only one new task and does not infer env, targets, or readiness in this slice",
"service task creation only declares one fixed listener and matching host projection; declare readiness separately if the app needs deeper truth"
],
"changes": [
{
"path": "tasks.dev",
"action": "set",
"before": null,
"after": {
"run": "npm run dev",
"runtime": {
"kind": "service",
"listeners": {
"http": {
"protocol": "http",
"bind": {
"address": "127.0.0.1",
"port": {
"mode": "fixed",
"value": 3000
}
},
"project": {
"host": {
"address": "127.0.0.1",
"port": {
"mode": "fixed",
"value": 3000
},
"primary": true
}
}
}
}
}
}
}
],
"diff": "tasks.dev\n- null\n+ run: npm run dev ...",
"validation": [
"ota validate /abs/path/to/ota.yaml",
"ota tasks /abs/path/to/ota.yaml",
"ota execution topology /abs/path/to/ota.yaml"
],
"next": "rerun with `ota assist add-task --name dev --kind service --run 'npm run dev' --internal false --listener http --protocol http --address 127.0.0.1 --port 3000 --write /abs/path/to/ota.yaml` to apply this task change"
}
Notes:
subject.taskis always the newly created task nameinputs.kindis one ofcommand,service,setup,check, orsandboxinputs.runorinputs.scriptrecords the explicit execution body; sandbox can use the boundedecho sandboxstarter bodyinputs.listener,inputs.protocol,inputs.address, andinputs.portappear only forservicechanges[0].beforeis alwaysnullbecause this slice creates only new tasks
Failure:
{
"ok": false,
"path": "/abs/path/to/ota.yaml",
"operation": "add-task",
"subject": {
"task": "smoke"
},
"why": "task `smoke` is already declared",
"next": "choose a new task name, or use `ota tasks` to inspect the current inventory"
}
ota assist normalize --json
Assist normalize output reports one deterministic proposal or apply result for moving one existing
task into the canonical tasks.setup slot.
Success:
{
"ok": true,
"path": "/abs/path/to/ota.yaml",
"mode": "preview",
"operation": "normalize",
"subject": {
"task": "bootstrap",
"into": "setup"
},
"inputs": {
"into": "setup"
},
"assumptions": [
"`tasks.bootstrap` will move into the canonical `tasks.setup` slot",
"`tasks.setup` will be normalized to `internal: true` so setup stays an `ota up` support task by default"
],
"changes": [
{
"path": "tasks.bootstrap",
"action": "delete",
"before": {
"run": "npm install"
},
"after": null
},
{
"path": "tasks.setup",
"action": "set",
"before": null,
"after": {
"run": "npm install",
"internal": true
}
}
],
"diff": "normalize task bootstrap\n- run: npm install\n+ run: npm install ...",
"validation": [
"ota validate /abs/path/to/ota.yaml",
"ota up --dry-run /abs/path/to/ota.yaml",
"ota doctor /abs/path/to/ota.yaml"
],
"next": "rerun with `ota assist normalize --task bootstrap --into setup --write /abs/path/to/ota.yaml` to apply this normalization"
}
Notes:
subject.intois currently fixed tosetupin the shipped slicechanges[0]records deletion of the originaltasks.<name>slotchanges[1]records creation of the canonicaltasks.setupslot- apply removes the original
tasks.<name>entry and writes the moved declaration undertasks.setup
Failure:
{
"ok": false,
"path": "/abs/path/to/ota.yaml",
"operation": "normalize",
"subject": {
"task": "bootstrap"
},
"why": "the contract already declares `tasks.setup`",
"next": "use `ota assist wire-setup` to refine setup instead of normalizing another task into it"
}
ota assist wire-setup --json
Assist setup output reports one deterministic proposal or apply result for tasks.setup.
Success:
{
"ok": true,
"path": "/abs/path/to/ota.yaml",
"mode": "preview",
"operation": "wire-setup",
"subject": {
"task": "setup"
},
"inputs": {
"run": "npm install",
"services": "postgres"
},
"assumptions": [
"a new `tasks.setup` declaration will be created",
"new setup tasks default to `category: setup` and `internal: true` unless overridden",
"setup will execute through a single `run` command",
"`setup.requires_services` will define the pre-setup service phase: `postgres`"
],
"changes": [
{
"path": "tasks.setup",
"action": "set",
"before": null,
"after": {
"category": "setup",
"internal": true,
"run": "npm install",
"requires_services": ["postgres"]
}
}
],
"diff": "tasks.setup\n- <absent>\n+ category: setup ...",
"validation": [
"ota validate /abs/path/to/ota.yaml",
"ota up --dry-run /abs/path/to/ota.yaml",
"ota doctor /abs/path/to/ota.yaml"
],
"next": "rerun with `ota assist wire-setup --run 'npm install' --service postgres --write /abs/path/to/ota.yaml` to apply this setup change"
}
Notes:
subject.taskis alwayssetupinputsrecords only the explicit setup inputs the operator passed to assistchanges[*].beforeis present when assist is refining an existingtasks.setupblockchanges[*].afteris the exact setup block Ota would write, includingsetup.requires_servicesordering when that input was provided- validation includes
ota up --dry-runbecause setup wiring changes phased repo preparation behavior directly
Failure:
{
"ok": false,
"path": "/abs/path/to/ota.yaml",
"operation": "wire-setup",
"subject": {
"task": "setup"
},
"why": "creating `tasks.setup` needs an explicit `--run`, `--script`, or copy action body",
"next": "rerun with `--run '<command>'`, `--script '<body>'`, or `--copy-from <source> --copy-to <target>` to declare the setup task"
}
ota workspace execution plan --json
Workspace execution planning stays read-only, but reports one resolved or unresolved execution
decision per selected repo. Repo items may include additive workflow and task whenever
workflow-aware planning selected a canonical repo path, whether that came from
repos.<name>.workflow or the repo contract's own default workflow.
{
"ok": true,
"path": "/abs/path/to/ota.workspace.yaml",
"mode": "execution-plan",
"summary": {
"repo_count": 2,
"resolved_count": 1,
"unresolved_count": 1,
"required_unresolved_count": 1,
"not_acquired_count": 0,
"missing_contract_count": 0
},
"overrides": {
"backend": "container",
"lifecycle": "ephemeral"
},
"repos": [
{
"name": "api",
"path": "/abs/path/to/services/api",
"contract_path": "/abs/path/to/services/api/ota.yaml",
"required": true,
"acquired": true,
"status": "RESOLVED",
"workflow": "backend",
"task": "dev",
"contract_identity": {
"version": 1,
"project": {
"name": "api"
},
"counts": {
"runtimes": 0,
"tools": 0,
"env": 0,
"services": 0,
"checks": 0,
"tasks": 1
}
},
"declared_execution": {
"preferred": "remote",
"supported": ["remote"],
"lifecycle": "ephemeral",
"backends": {
"remote": {
"provider": "ssh",
"target": "user@host",
"cwd": "/srv/api"
}
}
},
"resolved": {
"backend": "remote",
"backend_source": "contract preferred",
"lifecycle": "ephemeral",
"lifecycle_source": "contract lifecycle",
"provider": "ssh",
"target": "user@host",
"cwd": "/srv/api",
"target_strategy": "remote target"
}
},
{
"name": "db",
"path": "/abs/path/to/services/db",
"contract_path": "/abs/path/to/services/db/ota.yaml",
"required": true,
"acquired": true,
"status": "UNRESOLVED",
"error": "`ota execution plan` requires `execution.backends.container.image` for container execution",
"next": "repair `/abs/path/to/services/db/ota.yaml` so the selected execution mode is runnable, then rerun `ota workspace execution plan`"
}
]
}
ota tasks --json
Success:
{
"ok": true,
"path": "/abs/path/to/ota.yaml",
"workflow": {
"name": "app",
"intent": "local_development",
"notes": "Use this path when validating readiness and preparing local app runs.\n",
"prepare_task": "setup:env:local",
"setup_task": "setup",
"run_task": "dev",
"required_services": ["postgres"],
"readiness_checks": ["app-health"],
"readiness_probes": ["app-ready"],
"exposes": ["http://127.0.0.1:5678"]
},
"agent": {
"entrypoint": "setup",
"safe_tasks": ["setup", "test"],
"verify_after_changes": ["test"],
"writable_paths": ["src", "docs"],
"protected_paths": ["Cargo.lock", "LICENSE"],
"inferred_boundary_reviewed": false,
"bootstrap": {
"ota": {
"note": "Only install ota if it is missing and installation is approved.",
"source": {
"kind": "version",
"version": "v1.6.16",
"deterministic": true,
"pressure_only": false
},
"sh": "curl -fsSL https://dist.ota.run/install.sh | OTA_VERSION=v1.6.16 sh",
"powershell": "$env:OTA_VERSION='v1.6.16'; irm https://dist.ota.run/install.ps1 | iex"
}
}
},
"capability_profile": {
"actor_mode": "agent",
"writable_paths": ["src", "docs"],
"protected_paths": ["Cargo.lock", "LICENSE"],
"callable_tasks": [
{
"lane_id": "task:test",
"lane_kind": "task",
"name": "test",
"command": "ota run test --agent",
"environment_boundary": {
"kind": "task",
"backend": "native",
"default_mode": "native"
},
"preflight": {
"state": "allowed",
"review_required": false,
"declared_safe_for_agent": true,
"effective_safe_for_agent": true,
"crossing_required": false,
"crossing_classification": "routine",
"receipt_expected": true,
"proof_expected": true
}
}
],
"refused_tasks": [
{
"lane_id": "task:setup",
"lane_kind": "task",
"name": "setup",
"command": "ota run setup --agent",
"environment_boundary": {
"kind": "task",
"backend": "native",
"default_mode": "native"
},
"preflight": {
"state": "refused",
"review_required": true,
"declared_safe_for_agent": false,
"effective_safe_for_agent": false,
"refusal_reason_family": "requested_task_not_safe",
"refusal": {
"reason_family": "requested_task_not_safe",
"boundary_family": "agent_safety_boundary",
"closure_status": "unsafe",
"requested_task": "setup",
"blocked_task": "setup",
"evidence_class": "derived"
},
"receipt_expected": true,
"proof_expected": false
}
}
]
},
"tasks": [
{
"name": "setup",
"use": {
"human": "ota run setup --base-url <value>",
"agent": {
"callable": false,
"reason": "not_safe"
}
},
"description": "Prepare mixed repo dependencies",
"notes": "Use this after cloning the repo.\n",
"kind": "sequence",
"prepare": {
"kind": "sequence",
"steps": [
{
"kind": "dependency_hydration",
"medium": "package_dependencies",
"source_kind": "node_package_manager",
"cwd": ".",
"manager": "pnpm",
"mode": "install",
"frozen_lockfile": true
},
{
"kind": "dependency_hydration",
"medium": "package_dependencies",
"source_kind": "uv",
"cwd": "api",
"manager": "uv",
"mode": "sync"
}
]
},
"env": {
"JAVA_HOME": "/opt/jdk-21"
},
"inputs": {
"base_url": {
"required": true
}
},
"effects": {
"writes": ["node_modules", ".venv"],
"network": true,
"network_kind": "dependency_hydration"
},
"depends_on": [],
"requires_services": ["postgres"],
"requires_artifacts": [],
"safe_for_agent": false,
"effective_safe_for_agent": false
},
{
"name": "test",
"use": {
"human": "ota run test",
"agent": {
"callable": true,
"command": "ota run test --agent"
},
"modes": [
{
"mode": "container",
"default": false,
"availability": "unavailable",
"reason": "not_supported_by_task",
"human": {
"callable": false,
"reason": "not_supported_by_task"
},
"agent": {
"callable": false,
"reason": "not_supported_by_task"
}
},
{
"mode": "native",
"default": true,
"availability": "supported",
"human": {
"callable": true,
"command": "ota run test"
},
"agent": {
"callable": true,
"command": "ota run test --agent"
}
}
]
},
"kind": "command",
"command": {
"exe": "uv",
"args": ["run", "pytest"]
},
"depends_on": ["setup"],
"requires_services": [],
"requires_artifacts": [],
"safe_for_agent": true,
"effective_safe_for_agent": true
}
]
}
When ota can explain a refusal from the execution decision site, governance output now carries the
legacy refusal_reason_family plus additive structured refusal truth. That record keeps the
requested lane, blocked lane, closure posture, boundary family, optional closure path, and the
evidence class together so machine consumers do not have to reconstruct the refusal from flat
status fields.
ota tasks --json may also include additive top-level capability_profile, a derived
harness-facing agent surface built from the same closure-aware safety and refusal truth that powers
ota run --agent.
tasks[].requires_artifacts names generated artifacts consumed by that task. Their producer,
declared output paths, and source inputs remain canonical under top-level artifacts in the
contract; a non-empty list means the task must directly depend on each artifact producer.
Each task summary now also carries a canonical use object:
use.humananduse.agentremain compatibility projections for the selected default modeuse.modes[]is the canonical task-mode matrix for machine consumers; it listscontainer,native, and when advertisedremotein a stable order- every
use.modes[]entry carriesdefault,availability, and separate human/agent callable truth, including the exact command when callable - aggregate task entries derive mode availability from their concrete dependency closure: a mode is supported only when every executable member supports that backend on the current platform
Receipt evaluated_inputs[] also includes task-declared replay_inputs captured before execution.
These records use kind: static_file and input_class: declared_replay_input; matching identities
only prove the named file held still and remain narrowing evidence rather than a hermetic replay
claim. When the contract declares expected_identity, the record retains both the observed
identity and expected_identity. A missing or mismatched pin does not begin execution: Ota emits
a blocked preconditions receipt with failure_origin: replay_input_identity_missing or
replay_input_identity_mismatch, one evaluated input containing the expected identity, the
observed identity when readable, plus execution_started: false, and the matching
OTA_REPLAY_INPUT_IDENTITY_MISSING or
OTA_REPLAY_INPUT_IDENTITY_MISMATCH finding.
When an active org policy requires replay-input identities for the selected lane, Doctor, run
preview, and admission-produced run/up execution or refusal receipts include additive
replay_input_policy. Generic ota receipt --json readiness receipts do not reconstruct a prior
admission decision after execution. The policy record carries:
subject: the selected task or workflow;required,coverage, and the deriveddecision(allow,review, ordeny);applicable_rules[]: every matching task/workflow rule, its own selected closure, coverage, decision, and typed insufficiency reasons;inputs[]: one task-qualified record per observed declared input, including expected and observed identities where available; andunknown_selectors[]: contextual policy selectors that do not resolve against the loaded contract.
review is not an allowed execution result in this slice. Both review and deny refuse before
native prerequisite provisioning, dependency hydration, or task startup. An unavailable
observation fails closed. An unreadable or mismatched declared pin always derives deny,
regardless of a rule's on_insufficient value, and its blocked receipt retains the active policy
record. Agent safety, claim assurance, replay admission, Doctor/provisioning findings, proof, and
receipt policy evidence consume one loaded policy snapshot for the command. Runtime proof pins the
same authority for its detached child. CI projection evaluates all governance domains from one
loaded snapshot and carries only the active semantic policy identity,
applicable rule identities, the canonical execution closure including recursive outcome hooks,
and unresolved selector identities; provider execution recomputes observed identities after
checkout rather than trusting the machine that rendered the projection.
An active policy source that cannot be loaded refuses as
replay_input_policy_unavailable; it never degrades to absent policy. Task and workflow
admission includes recursive outcome-hook execution edges, so hook replay inputs and matching task
rules are observed and enforced before the parent begins.
ota proof runtime captures one replay-input preflight over the full selected proof closure,
including post-readiness seam observers and the selected negative-control task, and admits it
before creating proof artifacts or spawning the child runtime. It then reuses that preflight across
readiness diagnosis and the emitted Doctor artifact; the artifact does not re-read the checkout
after runtime execution.
ota proof lifecycle evaluates the exact workflow prerequisite-plus-assertion closure before
creating its transaction or running any task, service command, readiness observation, or
assertion. Both proof commands emit execution_started: false with typed hard-pin and policy
evidence when admission refuses. Aggregate monorepo Doctor JSON carries the same canonical
replay_input_policy record inside each applicable members[] result rather than dropping the
member's policy identity and observed-input status.
When an active org policy pack participates in the selected lane, receipt capture also adds
kind: policy_ruleset_identity with input_class: policy_ruleset_identity so replay can treat
policy/ruleset drift as named input drift instead of leaving governance movement ambient.
When the selected lane actually resolves a declared env source, receipt capture also adds
kind: env_source_identity with input_class: declared_env_source_identity. This hashes the
source file itself, never the resolved env values, so replay can classify declared env-source drift
as named input drift without leaking secrets or overclaiming process-env identity.
Receipt witnessed_observations.query_traces[] carries contract-declared historical query evidence
separately from evaluated_inputs[]. Each trace is evidence_class: attested, retains its source
path, source SHA-256 identity, and per-subject/per-run identity records, and summarizes divergent subjects. Query
identities are observed behavior, not current-run decision inputs; a divergence identifies a
changed query shape without claiming model causality or a negative-control result.
A baseline producer receipt carries its own Ota-recorded attestation reference in
witnessed_observations.replay_baseline_recordings[]. Each record is evidence_class: attested
and binds the artifact, producer task, actual scope/backend/lifecycle, attestation identity, and
local archive path. Promotion verifies this reference and the receipt evidence identity before it
can select the attestation; replay consumer receipts carry the separate promoted authority below.
The attestation also requires a V11.16 execution-boundary graph identity and identities for both
selected closures. An empty graph is explicit unknown runner evidence, not an omitted claim that
the producer had no material prerequisites.
When a selected task consumes an artifact as replay, receipt evaluated_inputs[] emits
kind: promoted_replay_baseline and input_class: promoted_replay_baseline only after Ota has
verified the current declared outputs against the portable promoted authority manifest. Its
artifact_lineage.replay_authority binds the receipt to the manifest path, explicit SCM-review
trust root, selected attestation identity, promotion identity, and declared consumption posture.
read_only means the selected closure ran with a runner-owned snapshot outside the writable
workspace. verify_unchanged means Ota verified the complete output set before and after
execution; replay_artifact_mutation_detected reports a changed baseline rather than claiming
the write was refused. This is selected replay authority, not a claim that the regeneration
producer ran in the replay lane.
For selected structured hydration lanes, ota up --json also emits a typed
receipt.evaluated_inputs[] record with kind: hydration_provenance. Its runner-derived nested
detail retains the contract-owned declared posture separately from the execution-time resolved
posture. For .NET restore and uv, the resolved record may include typed
source_identities[], or an explicit unavailable resolution when source selection would require
ambient configuration. uv additionally carries its declared offline posture; cache-only
execution does not turn an undeclared cache source into resolved provenance.
For uv mode: pip_local_project, the same record carries local_project: the declared path,
editable posture, extras, and ordered groups together with runner-observed manifest and optional
lockfile identities. source_identity is present only when Ota can recover a clean Git identity
for that local project; source_identity_error keeps a dirty or unavailable source explicit
instead of treating the manifest hash as a source pin.
Consumers must not replace this captured input with a later config-file read when evaluating the
execution receipt. During replay, an unavailable hydration resolution remains narrowing evidence
and cannot make a lane hermetic. For an editable local project, the named dependency-resolution
class is acquitting only when both receipts carry resolved hydration posture plus matching declared
lockfile and clean source identities; a manifest identity alone only narrows the comparison.
- unavailable local planes remain explicit with
availability: "unavailable"andreason: "not_supported_by_task"; this describes contract support, not current machine readiness, which remains the responsibility ofota doctorandota run --dry-run use.modes[].agent.callable: falsewithreason: "not_safe"means the mode is declared but its full task closure is not agent-callable, without forcing consumers to scrape human text
Each task capability entry carries:
- stable lane identity via
lane_idandlane_kind - the canonical agent invocation in
command - an additive
environment_boundary - canonical
preflightgovernance state - an additive
sandbox_policyfor the first compiled runtime target,codex_local - additive closure effect posture in
effectswhen the selected task path owns network, write, adapter, or external-state behavior
sandbox_policy is still intentionally narrow in this first slice:
filesystem.state: "compiled"means ota could derive a read-only repo posture plus writable/protected carve-outs from declaredagent.writable_pathsandagent.protected_pathsfilesystem.source: "execution.runtime_boundary","workflows.<name>.runtime_boundary", or"tasks.<name>.runtime_boundary"means the lane publishes canonical runtime-boundary truth and ota is compiling that selected-path owner directly instead of falling back to derived agent boundary posturefilesystem.state: "unavailable"means the lane does not yet carry enough declared agent boundary truth for ota to claim a trustworthy writable-mount policynetwork.default: "deny"withscope: "none"means the lane does not declare network use on its effect surfacenetwork.default: "allow"withscope: "broad"means the lane declares network use, but ota is still honestly compiling only the broad effect-owned posture here rather than a host or destination allowlistnetwork.scope: "targeted"plusoutbound_targets[]means the selected lane declares explicit runtime-boundary target truthoutbound_targets[].destination_constraintmeans the lane declares narrower effective destination truth beyond the first-hop host; ota now preserves:- constraint
kind - constrained
values[] source_postureenforcement- optional
shared_pin.refplusshared_pin.freshnesswhen the lane consumes pinned shared destination truth
- constraint
- top-level
network.enforcementstill describes the broad compiled lane posture; per-target destination constraints can carry stronger app/runtime enforcement truth than the coarse lane default
Refused task capability entries may also carry additive blocked_task and closure_path when a
declared-safe task is refused because its reachable closure leaves the safe surface.
Root monorepo summary output can also include grouped member results:
{
"ok": true,
"path": "/abs/path/to/ota.yaml",
"tasks": [],
"members": [
{
"member": "api",
"workflow": {
"name": "app",
"run_task": "test",
"required_services": [],
"readiness_checks": [],
"readiness_probes": [],
"exposes": []
},
"tasks": [
{
"name": "test",
"kind": "run",
"run": "cargo test",
"notes": "Use this to verify the code before merging.\n",
"env": {
"BASE_URL": "http://localhost:8080"
},
"inputs": {
"mode": {
"default": "live"
}
},
"effects": {
"network": true
},
"depends_on": [],
"requires_services": ["postgres"],
"safe_for_agent": false,
"effective_safe_for_agent": false
}
]
}
]
}
Each task may also include additive prepare when the resolved task body is first-class setup
instead of shell run / script. Sequence prepares keep a nested steps[] tree, while
dependency-hydration prepares expose structural fields such as medium, source_kind, cwd,
manager, and mode.
Each task may also include additive effects when the contract declares durable repo writes
(writes), workspace/sibling writes (workspace_writes), a connectivity dependency (network),
an optional network lane classification (network_kind), or out-of-repo mutation
(external_state).
When the repo declares workflows, ota tasks --json includes an additive top-level workflow
object for the default workflow, and member summaries may include the same additive field.
ota workflows --json
Workflow inventory stays read-only. It reports declared repo workflows without falling back to the full task inventory.
Success:
{
"ok": true,
"path": "/abs/path/to/ota.yaml",
"default": "app",
"capability_profile": {
"actor_mode": "agent",
"callable_workflows": [
{
"lane_id": "workflow:quickstart",
"lane_kind": "workflow",
"name": "quickstart",
"command": "ota up --workflow quickstart --agent",
"environment_boundary": {
"kind": "workflow",
"backend": "container",
"context": "app",
"primary_task": "preview:quickstart"
},
"preflight": {
"state": "allowed",
"review_required": false,
"declared_safe_for_agent": true,
"effective_safe_for_agent": true,
"crossing_required": false,
"crossing_classification": "routine",
"receipt_expected": true,
"proof_expected": true
}
}
]
},
"workflows": [
{
"name": "quickstart",
"use": {
"human": "ota up --workflow quickstart",
"agent": {
"callable": true,
"command": "ota up --workflow quickstart --agent"
}
},
"intent": "quickstart",
"description": "Structured packaged command path",
"notes": "Use this path for local container-backed previews.\n",
"prepare_task": "setup:env:local",
"setup_task": "setup",
"run_task": "preview:quickstart",
"run_task_launch": {
"kind": "command",
"exe": "npx",
"args": ["vite", "--host", "127.0.0.1", "--port", "3000"]
},
"required_services": ["postgres"],
"readiness_checks": [],
"readiness_probes": [],
"readiness_surfaces": ["backend"],
"signal_readiness_checks": [],
"signal_readiness_probes": [],
"signal_readiness_surfaces": [],
"exposes": ["http://127.0.0.1:5678/"],
"expose_surfaces": ["backend"],
"default": true
},
{
"name": "backend",
"use": {
"human": "ota up --workflow backend",
"agent": {
"callable": false,
"reason": "not_safe"
}
},
"run_task": "backend",
"required_services": [],
"readiness_checks": [],
"readiness_probes": [],
"readiness_surfaces": ["backend"],
"signal_readiness_checks": [],
"signal_readiness_probes": [],
"signal_readiness_surfaces": [],
"exposes": ["http://127.0.0.1:5678/"],
"expose_surfaces": ["backend"],
"default": false
}
]
}
Notes:
- root success includes
ok,path, optionaldefault, andworkflows - each workflow summary carries a canonical
useobject with explicit human and agent invocations; non-callable agent lanes publishcallable: falseinstead of forcing consumers to infer that from separate safety fields - root success may also include additive
capability_profile, a derived harness-facing workflow surface for agent mode capability_profile.callable_workflows[]andcapability_profile.refused_workflows[]publish exact workflow lane identity, canonicalota up --workflow ... --agentcommands, workflow environment boundaries, and canonical preflight governance state without requiring a harness to scrape human output or guess workflow closure safety- workflow capability entries may also include additive
sandbox_policyfor the first compiled runtime target,codex_local, using the same semantics as task capability entries: filesystem policy compiles from declared runtime-boundary or derived agent boundary truth when present, and network policy currently compiles as either effect-owneddeny/none/allow/broador targeted runtime-boundaryoutbound_targets[] - workflow capability entries publish
preflight.proof_expected: truebecauseota upis a proof-owning lane: the selected workflow is expected to drive readiness or runtime-proof evidence when executed - workflow capability and run-preview preflight entries can also publish thin audited-crossing
posture:
crossing_required: falsewithcrossing_classification: "routine"means the lane stays inside the routine default-safe pathcrossing_required: truewithcrossing_classification: "escalated"means the lane is allowed but crosses a heavier execution boundary such as an unsafe task closurecrossing_boundary_familynames the crossed boundary when ota can recover it honestly- refused lanes do not publish crossing posture yet because no crossing is allowed
preflight.decision_basis[]is the additive machine-readable citation set for the current governance outcome:- safety gates such as
agent_safe_closure,unsafe_closure, ordeclared_safe_closure_unsafe - refusal basis such as
refusal:requested_task_not_safe - crossing gates such as
crossing_not_required:routineorcrossing_required:unsafe_task:escalated - each entry carries stable
id,family, andevidence_classinstead of relying on prose
- safety gates such as
- authoritative crossing records in
receipt.crossingandgovernance.crossingcan also publish additiveevidence_classes:assertedmeans the caller supplied the value, such as free-textreasonderivedmeans ota resolved the value from contract truth and decision inputs, such as lane, boundary, or classificationattestedmeans ota recorded the field at the decision boundary itself, such asreason_present,principal_attribution_state, or attachment state
- refused workflow entries may also carry additive
blocked_taskandclosure_pathwhen the selected workflow reaches a non-safe task in its prepare/setup/run/attach closure - each workflow entry includes additive fields only when declared or resolved:
intentdescriptionnotesprepare_taskprepare_actionsetup_taskrun_taskrun_task_launchrequired_servicesreadiness_checksreadiness_probesreadiness_surfacessignal_readiness_checkssignal_readiness_probessignal_readiness_surfacesexposesexpose_surfacesdefault
exposescontains resolved URL strings;expose_surfacespreserves the named surface refs that produced themrequired_servicesincludes both workflow-declaredservices.requiredand transitive task-levelrequires_servicesfrom the selected workflow task closurerun_task_launchpreserves the selected run task's structured launch source when that workflow path runs throughlaunchinstead of shellrunorscript- when the target is a monorepo root and members are requested, success may include additive
top-level
members, each withmember, optionaldefault, andworkflows
Failure:
{
"ok": false,
"path": "/abs/path/to/ota.yaml",
"error": "contract path does not exist: /abs/path/to/ota.yaml"
}
ota doctor --json
{
"ok": true,
"path": "/abs/path/to/ota.yaml",
"summary": {
"error_count": 0,
"warn_count": 1,
"info_count": 0
},
"workflow": {
"name": "app",
"intent": "local_development",
"prepare_task": "setup:env:local",
"setup_task": "setup",
"run_task": "dev",
"required_services": ["postgres"],
"readiness_checks": ["app-health"],
"readiness_probes": ["app-ready"],
"exposes": ["http://127.0.0.1:5678"]
},
"agent": {
"entrypoint": "setup",
"verify_after_changes": ["test"],
"protected_paths": ["ota.yaml"],
"inferred_boundary_reviewed": false,
"bootstrap": {
"ota": {
"note": "Only install ota if it is missing and installation is approved.",
"source": {
"kind": "version",
"version": "v1.6.16",
"deterministic": true,
"pressure_only": false
},
"sh": "curl -fsSL https://dist.ota.run/install.sh | OTA_VERSION=v1.6.16 sh",
"powershell": "$env:OTA_VERSION='v1.6.16'; irm https://dist.ota.run/install.ps1 | iex"
}
}
},
"governance": {
"required_verification_lanes": [
{
"merge_check_id": "ota.verify.verify",
"lane_task": "verify",
"lane_kind": "aggregate",
"contract_sources": ["workflows.verify.run.task"],
"evidence_classes": {
"merge_check_id": "derived",
"lane_task": "derived",
"lane_kind": "derived",
"contract_sources": "derived"
}
}
],
"merge_gate": {
"state": "projected",
"blocking": false,
"required_lane_count": 1,
"drift_lane_count": 0,
"evidence_classes": {
"state": "derived",
"blocking": "derived",
"required_lane_count": "derived",
"drift_lane_count": "derived",
"decision_inputs": "derived",
"replay": "derived"
},
"decision_basis": [
{
"id": "projection:ota.verify.verify",
"family": "required_lane",
"evidence_class": "derived",
"detail": "verify"
}
],
"decision_inputs": [
{
"id": "decision_owner:doctor_merge_gate_summary",
"family": "decision_owner",
"evidence_class": "derived",
"replay_class": "pinned"
},
{
"id": "required_lane_count:1",
"family": "merge_gate",
"evidence_class": "derived",
"replay_class": "witnessed"
},
{
"id": "drift_lane_count:0",
"family": "merge_gate",
"evidence_class": "derived",
"replay_class": "witnessed"
}
],
"replay": {
"status": "satisfied"
},
"lanes": [
{
"merge_check_id": "ota.verify.verify",
"lane_task": "verify",
"lane_kind": "aggregate",
"state": "projected",
"blocking": false,
"evidence_classes": {
"merge_check_id": "derived",
"lane_task": "derived",
"lane_kind": "derived",
"state": "derived",
"blocking": "derived",
"decision_inputs": "derived",
"replay": "derived",
"contract_sources": "derived",
"provider_sources": "derived"
},
"decision_basis": [
{
"id": "projection:ota.verify.verify",
"family": "required_lane",
"evidence_class": "derived",
"detail": "verify"
}
],
"decision_inputs": [
{
"id": "decision_owner:doctor_merge_gate_lane",
"family": "decision_owner",
"evidence_class": "derived",
"replay_class": "pinned"
},
{
"id": "merge_check_id:ota.verify.verify",
"family": "merge_lane",
"evidence_class": "derived",
"replay_class": "pinned"
},
{
"id": "drift_detected:false",
"family": "provider_drift",
"evidence_class": "derived",
"replay_class": "witnessed"
}
],
"replay": {
"status": "satisfied"
},
"contract_sources": ["workflows.verify.run.task"],
"provider_sources": []
}
]
}
},
"provisioning": {
"allowed": [
{
"kind": "runtime",
"name": "java",
"requested_version": "22",
"normalized_requirement": ">=22.0.0 <23.0.0",
"package": "openjdk-22-jdk",
"source": "org-mirror",
"approved_version": "22",
"policy_match": "22",
"blocked_reason": null
},
{
"kind": "tool",
"name": "maven",
"requested_version": "3.9",
"normalized_requirement": ">=3.9.0 <3.10.0",
"source": "approved-manager",
"approved_version": "3.9",
"policy_match": "3.9",
"blocked_reason": null
}
],
"blocked": [],
"actions": [
{
"kind": "select_source",
"target_kind": "runtime",
"name": "java",
"requested_version": "22",
"normalized_requirement": ">=22.0.0 <23.0.0",
"package": "openjdk-22-jdk",
"source": "org-mirror",
"approved_version": "22",
"policy_match": "22"
},
{
"kind": "select_source",
"target_kind": "tool",
"name": "maven",
"requested_version": "3.9",
"normalized_requirement": ">=3.9.0 <3.10.0",
"source": "approved-manager",
"approved_version": "3.9",
"policy_match": "3.9"
}
]
},
"findings": [
{
"code": "OTA_TASKS_MISSING",
"category": "contract",
"owner": "repo_contract",
"severity": "warn",
"summary": "...",
"why": "...",
"next": "...",
"evidence": {
"observed": "...",
"expected": "...",
"source": "...",
"checked_at": "...",
"command": "...",
"path": "..."
},
"metadata": {
"governance": {
"merge_check_id": "ota.verify.test",
"lane_task": "test",
"lane_kind": "task",
"provider_sources": [
".github/workflows/ci.yml#jobs.verify.steps[0].run"
]
}
}
}
]
}
Finding objects always include stable identity fields:
code, category, owner, and evidence.
Contract-claim assurance
ota doctor --json may include additive top-level claim_assurance[]. This is the first carrier
for V11.14 contract-claim assurance. Each record uses structured subject identity instead of a
delimiter-encoded identifier:
{
"subject": { "kind": "task", "name": "setup" },
"family": "agent_safety",
"declaration": { "value": "safe", "evidence_class": "asserted" },
"closure": { "status": "safe", "evidence_class": "derived" },
"assurance": {
"status": "unknown",
"coverage": ["contract_declaration", "execution_closure"],
"gaps": ["non_self_origin_evidence"]
},
"policy": {
"decision": "allow",
"basis": ["default_compatibility"],
"evidence_class": "derived"
}
}
The fields deliberately carry separate truths:
declarationis the maintainer assertion.closureis Ota's derived V11.3 execution-closure result.assuranceis policy-independent evidence posture:supported,contradicted, orunknown. Contract declaration and closure alone do not independently corroborate a safety claim, so the first carrier emitsunknownuntil a later assurance source supplies matching evidence. A deterministic typed-action conflict is insteadcontradicted. For example, a declared-safereset_compose_service_volumeaction without the matchingeffects.adapter_state: [compose_volume:<volume>]emits a cited contradiction fromtask.action; Ota does not make the equivalent claim for opaque shell text.policyis the derived policy decision over that canonical record. The initial default remainsallowfor compatibility and does not changeota run --agentadmission behavior.
The current carrier emits agent-safety records for tasks declared safe and proof-breadth records
for workflows that declare runtime seam proof or workflows.<name>.proof.claim: bounded. A
bounded claim declares an archive-backed proof lane without inventing dependency-seam evidence. A
proof-breadth record is supported only when Ota finds
a content-addressed runtime_proof archive with matching semantic contract snapshot, clean source
identity, resolved execution scope, and replay_posture: witness_only. A matching failed proof is
contradicted; absent, stale, changed-source, or scope-mismatched archives remain unknown.
Consumers must not infer absent declared-effects records as support or contradiction.
When an active org policy pack declares policies.agent.claim_assurance.<family>, policy is
derived from that requirement. For example, a policy requiring agent_safety.minimum_status: supported changes an otherwise unknown record to policy.decision: deny; it does not relabel
the assurance itself as contradicted. Agent-mode runner admission consumes this same record when
the strict policy lane is enabled; ordinary agent execution remains backward-compatible without
an assurance requirement.
ota doctor --json may also include additive top-level governance.required_verification_lanes
when the contract already declares merge-relevant CI verification truth. Ota projects these lanes
from workflows.* with intent: ci_verification or legacy ci_validation, and falls back to
agent.verify_after_changes only when no explicit CI verification workflows are declared. Each
projected lane carries the same canonical merge_check_id identity that CI verification drift
findings use. required_verification_lanes[*].evidence_classes now makes that provenance
explicit; the current shipped lane identity and source fields are all derived.
ota doctor --json may also include additive governance.merge_gate, a first machine-readable
merge-oriented governance verdict built from the same projected lanes and CI drift metadata. This
surface stays honest:
state: projectedmeans Ota has projected the canonical required lanes but is not claiming full provider alignment from current recovery alonestate: drift_detectedmeans one or more projected lanes already have CI drift findings attached and should be treated as merge-blocking until the workflow wiring is reconcileddecision_basis[]now carries the cited merge-gate basis instead of leavingstateas a pure verdict:projection:<merge_check_id>means the lane is projected from contract-owned CI truthdrift:<merge_check_id>means the same canonical lane already has attached CI drift metadata
evidence_classesnow makes the field-level provenance explicit for the authoritative merge verdict itself; the current shippedstate,blocking, and lane-count fields are allderived- merge-facing governance now also carries additive
decision_inputs[]with stabledecision_owner:<stable-id>mechanism identity on both the summary and per-lane records, so downstream consumers can link the projected merge verdict back to one canonical doctor-side decision owner without inventing a second merge model - both the summary and each lane also carry
replay, which reconciles the emitted merge-facing verdict back to those cited decision inputs and reportssatisfied,mismatch, orunavailablewithout inventing a second merge-only command surface - that replay lane now also checks the trust-sensitive cited-input classes on merge governance, so
inputs like
decision_owner,merge_check_id, or drift observations cannot silently downgrade from their expectedpinned/witnessedposture without flipping replay tomismatch - each
lanes[]entry carries the same canonicalmerge_check_id, lane identity, and any recoveredprovider_sourcesfrom CI drift findings; per-lanedecision_basis[]repeats the same cited lane basis at lane scope for downstream consumers that do not want to re-infer lane posture from the summary alone, and per-laneevidence_classesnow makes the provenance of lane identity, state, blocking, and recovered source fields explicit
Finding objects may also include additive policy context keys when policy-aware diagnosis is surfaced:
policy_outcome, policy_reason, policy_source, install_scope, and mutation_allowed.
These keys are optional and backward-compatible.
CI verification drift findings may also include additive metadata.governance with a canonical
Ota-owned merge_check_id, the owning verifier lane_task, lane_kind (task or aggregate),
and the currently recovered provider source locations. This is the first stable machine-readable
merge-check identity layer for CI/merge consumers; workflow/job/check display names remain provider
render targets, not canonical truth.
When ota can trace the diagnosis source, finding objects may also include provenance and
provenance_key. Current shipped provenance keys include repo_contract, org_policy, and
repo_signals.
When doctor detects a managed-ecosystem opportunity that Ota does not yet ship as a toolchain
provider, the finding may also include an additive toolchain_opportunity object with
ecosystem, fallback_runtime, fallback_tools, candidate_providers, shipped, and
agent_note. This object is meant for editors and agents; the human-facing terminal finding keeps
the fallback guidance user-safe and does not have to expose provider-candidate wording directly.
Current shipped providers cover Rust, Node, Java, and uv-backed Python, so this additive object is
only expected when a future unsupported managed ecosystem is diagnosed.
When the repo declares runtimes or tools and policy provides approved sources for them,
ota doctor --json may also include a top-level provisioning object. That object is a read-only
plan with provisionable entries for targets policy approves and blocked entries for declared
targets that policy does not currently approve. It exists so humans and agents can see what would
be provisionable later without mutating the machine today.
When that plan exists, ota doctor --json also includes a top-level provisioning_request
object. It is the backend intake form and carries only the selected actions from the read-only
plan, so an installer backend can consume the request without re-deriving policy decisions from
the diagnostic payload.
Provisioning plan entries and request actions may also include additive semver-audit fields:
normalized_requirement, resolved_version, policy_match, and package. normalized_requirement
captures the semver intent ota matched against policy, policy_match records the exact approved
policy entry that authorized the request, and resolved_version appears only when policy provided
an explicit concrete version for deterministic installation. package is the backend install
identifier when policy specifies one (for example openjdk-22-jdk for apt). Range-only policy
approval does not invent a concrete install version, and when resolved_version is absent ota
continues to pass the original requested_version to the backend.
ota doctor --json and ota workspace doctor --json may also include a top-level
finding_groups array when the output contains repeated-action groups. Each entry includes a
stable semantic action_key derived from the grouped action class, plus the human-facing action_title,
action_next, and count. The grouped metadata is additive only; each findings[] entry remains
unchanged for machine consumers.
ota workspace doctor --json uses the same finding shape for per-repo findings, so the same
additive policy keys may appear there as well. When a repo declares execution metadata, the shared
execution.env array may include policy provenance with source values such as org policy
or workspace policy.
When a workspace repo declares runtimes or tools and policy provides approved sources for them,
ota workspace doctor --json may also include the same read-only provisioning plan on the
per-repo item. That plan uses the same allowed, blocked, and actions entries as repo-level
ota doctor --json, so editors and hosted validation can inspect the same future provisioning
signal without mutating anything. The action kinds are reserved for select_source, install,
and verify so the shape can grow without a breaking redesign.
ota doctor --json may also include an execution object when the contract declares execution
metadata that editors and remote-runner tooling can consume. Each execution.env entry may also
include an additive policy field when an approved policy value is available for that env key.
ota doctor --json may also include a top-level toolchains array for the selected workflow/task
path. Each entry records the selected toolchain name, provider, effective backend, target OS,
version, fulfillment mode, required flag, owned runtime, and any owned tools/components/targets
that ota is reasoning about on that selected path. Receipt-bearing execution surfaces may also add
fulfilled and commands[] when ota actually ran provider fulfillment commands on that execution
path. This is additive execution evidence; it does not
replace contract validation or finding-level detail.
ota doctor --json also includes a top-level summary object with finding counts and
machine-readable verdict / agent_verdict values so hosted validation and editor tooling do
not need to recompute them. When there is at least one finding, the summary may also include
primary_blocker with the highest-priority blocker details so CI and editors can answer the
question “what should I fix first?” without scanning the full list. When that blocker maps back to
a finding with stable identity, summary.primary_blocker.code is included additively alongside the
same blocker text and provenance fields.
For the main structured doctor finding families, findings[].code, findings[].category, and
findings[].owner are now emitted from explicit finding identity instead of being re-derived from
rendered English summary text. That includes contract advisories plus the main service, check,
runtime/tool, env-value and env-source, native prerequisite, backend/remote topology, workflow
probe/surface readiness, policy, repo-hygiene, and contract-drift finding lanes.
That identity surface is now guarded in CI: representative policy, workflow, service, env,
provisioning, and remote findings are contract-tested in JSON form, and shipped doctor findings are
rejected in test if they are introduced without explicit identity metadata.
The shipped code catalog and its published category / owner / provenance_key surfaces are
also synced into doctor-finding-reference.md.
Policy-backed version-rule and strict-version findings also preserve the same org_policy
provenance and policy_* metadata as the older policy blocker and provisioning lanes.
When the repo contract declares an agent block, the additive agent summary can also include
inferred_boundary_reviewed. false means the current writable and protected boundary still comes
from starter or detector inference and has not been confirmed by the repo author yet.
ota doctor --json also includes a top-level mode string. It is native for host readiness
diagnosis, container when the report was produced with ota doctor --mode container, and
remote when the report was produced with ota doctor --mode remote, so consumers can tell which
execution context the findings describe without inferring it from the CLI invocation.
When the repo signals no longer match the declared contract, ota doctor --json may include
warning findings that describe the drift and point back to ota detect --merge --dry-run for the
comparison preview. Drift findings also include optional owner_kind, ownership, and
provenance fields so CI and editors can classify the mismatch as a repo-contract issue and
trace the source of the comparison. When that drift provenance is present, owner_kind is
currently merged and provenance_key is repo_signals.
ota doctor --json may also include an extensions object when the contract declares top-level
extension data. Each entry is a typed adapter descriptor with kind, command, and
api_version, plus optional description and config. Supported kinds today are
check_provider, export_provider, and backend_provider. The field is parsed and preserved for
discovery, and ota extensions --run <name> can execute one explicitly named check_provider
descriptor with api_version: 1; ota extensions --publish <name> can execute one explicitly
named export_provider descriptor with api_version: 1. backend_provider is discoverable in the
JSON output and can be selected by execution.backends.remote.provider for custom remote
execution. Backend providers receive a structured request on stdin and in
OTA_BACKEND_PROVIDER_REQUEST_JSON, then return a structured JSON response on stdout.
When the repo declares workflows, ota doctor --json includes an additive top-level workflow
object for the default workflow so editors and automation can reason about the canonical repo path
without inferring it from task names. That workflow summary may also include additive
notes and readiness_probes when the workflow declares them or references reusable named
probes.
ota run <task> --dry-run --json exposes selected task-path toolchains at top level in
toolchains[]. Receipt-bearing execution surfaces such as ota receipt --json and ota up --json may also include additive receipt.toolchains[] entries with the same toolchain evidence
shape. Use those entries when you need to know which selected provider-backed ecosystem ota
checked or fulfilled on the recorded execution path, instead of inferring that from human text or
standalone runtime/tool fields. When fulfillment actually ran, receipt.toolchains[] can also
include fulfilled: true plus additive commands[] entries with the exact provider commands ota
executed for that toolchain during the recorded run path.
ota run --dry-run --json
ota run <task> --dry-run --json is the read-only repo execution preview surface. It resolves the
same selected task path, execution backend, env requirements, toolchains, native prerequisites,
dependency order, and preview actions that text RUN PREVIEW uses, but it does not execute setup,
dependencies, containers, or task processes.
Every full preview payload therefore carries execution_started: false. When an explicit
execution option is unsupported, ok is false, preview_status is BLOCKED, overrides retains
the requested value, and summary.primary_blocker.code identifies the rejected option family
without implying that the requested backend, boundary, publication, resource limit, or dependency
policy started.
When that selected path also carries direct provisioning truth, the payload includes additive
top-level provisioning and provisioning_request fields using the same machine-readable shape as
ota doctor --json, so agents do not need to re-derive selected-path host fulfillment from
free-form requirement_lines.
The published schema for this surface is json-schemas/run-preview.json. It covers single-target ready or blocked previews, aggregate member previews, and the simpler pre-preview error envelope.
Repo-level ota run --json is currently not a mutating execution receipt surface. Use
ota run <task> --dry-run --json for planning, ota receipt --json for repo readiness receipts,
and ota workspace run --json for coordinated multi-repo execution receipts.
preview_status is the operator-facing preview label for this dry-run surface. It is
RUNNABLE, RUNNABLE WITH WARNINGS, or BLOCKED. Keep using summary.verdict for the canonical
shared readiness verdict.
governance is the compact machine-readable execution-governance summary for this selected task
path. Its effect and sandbox fields are derived from the full selected task closure, including
aggregate members and dependencies, rather than only the requested task node. It keeps the
fast-consumption fields together so CI and agents do not have to reconstruct the selected safety
posture from requested_task, effect declarations, and mode branches separately.
When the selected lane also carries compiled runtime-boundary truth, governance.sandbox_policy
now repeats the same first-target codex_local sandbox profile published by ota tasks --json,
so execution-facing preview consumers do not have to call a second command just to recover
filesystem or outbound boundary posture.
{
"ok": true,
"path": "/abs/path/to/ota.yaml",
"contract": "/path/to/ota.yaml",
"task": "ci",
"dry_run": true,
"execution_started": false,
"preview_status": "RUNNABLE",
"summary": {
"verdict": "ready",
"agent_verdict": "ready",
"error_count": 0,
"warn_count": 0,
"info_count": 0
},
"contract_identity": {
"version": 1,
"project": {
"name": "demo"
},
"counts": {
"runtimes": 0,
"tools": 0,
"env": 0,
"services": 0,
"checks": 0,
"tasks": 1
}
},
"resolved": {
"backend": "native",
"backend_source": "task",
"target_strategy": "host process"
},
"requested_task": {
"name": "ci",
"kind": "run",
"run": "npm test",
"depends_on": [],
"requires_services": [],
"after_success": [],
"after_failure": [],
"after_always": [],
"safe_for_agent": false,
"effective_safe_for_agent": false
},
"requested_context": "host",
"selected_context": "host",
"env_summary": {
"contract_count": 0,
"source_count": 0,
"source_issue_count": 0,
"task_count": 1,
"resolved_count": 0,
"missing_count": 0,
"invalid_count": 0
},
"env": [
{
"name": "OTA_WORKSPACE",
"kind": "task",
"required": false,
"value": "/path/to/workspace",
"source": "execution",
"status": "task"
}
],
"provisioning_request": {
"actions": [
{
"kind": "install",
"target_kind": "tool",
"name": "helm",
"requested_version": ">=3.8",
"package": "helm",
"source": "brew",
"source_config": {
"tap_name": "vendor/tap",
"tap_url": "https://github.com/vendor/homebrew-tap"
}
}
]
},
"governance": {
"safety_posture": "review_required",
"review_required": true,
"effective_safe_for_agent": false,
"default_mode": "native",
"runnable_modes": [
{
"mode": "native",
"default": true,
"command": "ota run ci"
}
],
"network": false,
"receipt_follow_up_command": "ota receipt --json --archive",
"evaluation": {
"preflight": {
"state": "warning_only",
"review_required": true,
"declared_safe_for_agent": false,
"effective_safe_for_agent": false,
"crossing_required": true,
"crossing_classification": "escalated",
"crossing_boundary_family": "unsafe_task",
"decision_inputs": [
{
"id": "task:ci",
"family": "lane",
"evidence_class": "derived",
"replay_class": "pinned",
"detail": "kind=task"
},
{
"id": "actor_mode:human",
"family": "actor_mode",
"evidence_class": "derived",
"replay_class": "pinned"
},
{
"id": "decision_owner:task_governance_preflight",
"family": "decision_owner",
"evidence_class": "derived",
"replay_class": "pinned"
},
{
"id": "declared_safe_for_agent:false",
"family": "safety_declaration",
"evidence_class": "derived",
"replay_class": "pinned"
},
{
"id": "effective_safe_for_agent:false",
"family": "effective_safety",
"evidence_class": "derived",
"replay_class": "pinned"
},
{
"id": "doctor_verdict:risky",
"family": "readiness_gate",
"evidence_class": "derived",
"replay_class": "witnessed"
},
{
"id": "receipt_expected:true",
"family": "evidence_expectation",
"evidence_class": "derived",
"replay_class": "pinned"
},
{
"id": "proof_expected:false",
"family": "evidence_expectation",
"evidence_class": "derived",
"replay_class": "pinned"
}
],
"replay": {
"status": "satisfied"
},
"evidence_classes": {
"state": "derived",
"review_required": "derived",
"declared_safe_for_agent": "derived",
"effective_safe_for_agent": "derived",
"crossing_required": "derived",
"crossing_classification": "derived",
"crossing_boundary_family": "derived",
"decision_inputs": "derived",
"replay": "derived",
"receipt_expected": "derived",
"proof_expected": "derived"
},
"receipt_expected": true,
"proof_expected": false
},
"post_execution": {
"state": "not_run",
"execution_attempted": false,
"refusal_occurred": false,
"decision_inputs": [
{
"id": "execution_attempted:false",
"family": "execution_observation",
"evidence_class": "derived",
"replay_class": "witnessed"
},
{
"id": "not_run_reason:preview_only",
"family": "execution_observation",
"evidence_class": "derived",
"replay_class": "witnessed"
},
{
"id": "receipt_present:false",
"family": "receipt_observation",
"evidence_class": "attested",
"replay_class": "witnessed"
},
{
"id": "proof_expected:false",
"family": "proof_expectation",
"evidence_class": "derived",
"replay_class": "pinned"
},
{
"id": "proof_present:false",
"family": "proof_observation",
"evidence_class": "derived",
"replay_class": "witnessed"
},
{
"id": "crossing_record_state:deferred_until_execution",
"family": "crossing_observation",
"evidence_class": "derived",
"replay_class": "witnessed"
},
{
"id": "decision_owner:preview_post_execution_evidence",
"family": "decision_owner",
"evidence_class": "derived",
"replay_class": "pinned"
}
],
"replay": {
"status": "satisfied"
},
"decision_basis": [
{
"id": "not_run:preview_only",
"family": "execution_outcome",
"evidence_class": "derived"
},
{
"id": "evidence:proof_not_required",
"family": "evidence_gate",
"evidence_class": "derived"
},
{
"id": "crossing_record:deferred_until_execution",
"family": "crossing_evidence",
"evidence_class": "derived"
}
],
"evidence_classes": {
"state": "derived",
"execution_attempted": "derived",
"refusal_occurred": "derived",
"crossing_record_state": "derived",
"decision_inputs": "derived",
"replay": "derived",
"receipt_present": "attested",
"proof_present": "derived"
},
"receipt_present": false,
"proof_present": false
}
}
},
"plan": {
"dependency_chain": ["ci"],
"dependency_steps": [
{
"task": "ci",
"backend": "native",
"context": "host",
"backend_selection_source": "task context"
}
],
"actions": ["would execute `npm test` on the host"]
}
}
ota up --dry-run --json uses the same execution_started: false invariant. If a selected
workflow task cannot honor an explicit execution option, the preview is blocked before prepare,
setup, or run phases and blockers[].identity.code identifies the refused option family.
Use this when a human or agent needs the selected run plan before execution:
resolvedis the selected backend/lifecycle/image/provider planrequested_taskis the selected task body after contract validationrequested_contextis the task-declared context (when present)selected_contextis the resolved execution context ota will apply for this previewenv_summary,sources, andenvshow the selected env state and blockersgovernanceis the compact CI/agent-friendly summary for the selected lane:safety_posture,review_required, closure-aware effective safety, effectivedefault_mode, runnable mode commands, selected effect surface, and the next durable receipt commandgovernance.evaluationis the canonical phase-labeled machine surface for this lane:preflight.statetells consumers whether the selected path isallowed,warning_only,blocked, orrefusedbefore execution, whilepost_execution.statekeeps execution evidence separate asnot_runor a satisfied evidence state after execution surfaces exist. A preflight refusal is represented there asnot_runwithrefusal_occurred: trueandnot_run_reason: preflight_refusal, never as fabricated attempted execution.governance.evaluation.preflight.decision_basis[]publishes the stable cited gate or refusal basis behind that preflight posture so CI or harness consumers do not have to infer it from human stringsgovernance.evaluation.preflight.decision_inputs[]publishes the replay-grade cited inputs that posture depended on;replay_class: "pinned"means the input should be reusable for authoritative replay without silently re-reading ambient state, whilereplay_class: "witnessed"means the input came from observed execution evidence rather than a pinned selector- the first shipped mechanism-tripwire on that same surface is
decision_owner:<stable-id>with familydecision_ownerandreplay_class: "pinned", so the emitted governance record can still link back to the stable decision owner without inventing a second governance model governance.evaluation.preflight.replay.statuspublishes whether ota can re-derive that preflight verdict from the cited inputs it just emitted:satisfied,mismatch, orunavailable- replay now also checks the trust-sensitive cited-input classes on that same canonical surface,
so a selector that should be
pinnedor receipt evidence that should beattestedcannot silently degrade into weaker machine truth without flipping replay tomismatch governance.evaluation.preflight.evidence_classespublishes field-level provenance for the authoritative preflight verdict, distinguishing ota-derived boundary truth from runner-attested attachment stategovernance.evaluation.post_execution.decision_basis[]publishes the stable cited evidence or non-run basis behindpost_execution.state, so consumers can distinguish receipt/proof satisfaction from preview-only, blocked, or refusal-suppressed execution without scraping prosegovernance.evaluation.post_execution.decision_inputs[]publishes the replay-grade cited execution and evidence inputs behind that post-execution verdict, again distinguishing reusable pinned selectors from witnessed observations- post-execution uses the same additive
decision_owner:<stable-id>cited input so preview-only evidence and up-result evidence can publish a narrow mechanism identity on the same canonical record governance.evaluation.post_execution.replay.statusdoes the same for the post-execution evidence verdict, so consumers can tell whether the emitted outcome still reconciles to the cited decision inputs instead of trusting a flatter claimgovernance.evaluation.post_execution.evidence_classesdoes the same for the post-execution evidence record, so downstream consumers can tell which fields are derived versus boundary- attested- when post-execution already knows the evidence-state reason, it now cites that directly:
evidence:proof_present,evidence:proof_missing,evidence:proof_not_required,receipt_status:<status>, andcrossing_record:<state>are all stable machine-readable basis entries instead of flatter implicit outcomes ota run <task> --dry-run --json --agentnow reflects the enforced runner boundary ingovernance.evaluation.preflight: unsafe selected tasks or unsafe reachable closures publishstate: "refused"and return a blocked preview instead of looking runnable in JSONota run --agent --expect-refusal --json <task>is the machine-readable negative-control surface. It emitsstatus: "refused_as_expected"withcanary.kind,canary.target,canary.execution_started: false, the derived refusal record, and the blocked receipt. If the target is admitted it emitsstatus: "refusal_not_observed". A policy-only refusal emitsstatus: "wrong_refusal_boundary": it is still blocked, but it does not prove the safety closure boundary. Both exit non-zero and recordexecution_started: false; consumers must treat either as enforcement drift. Its schema is refusal-canary.json. The top-levelokreports whether the canary assertion passed; nestedreceipt.okremainsfalsebecause agent execution was blocked.requested_task.safe_for_agentis the declared contract safe membership, whilerequested_task.effective_safe_for_agentreflects whether the reachable dependency/workflow closure remains agent-safegovernance.safety_posturemay bedeclared_safe_closure_unsafewhen the top-level task is declared safe but reaches review-required closureartifact_routing[]is the additive artifact guide for this selected lane; it points to the next receipt/proof artifact or capture command using typedrole,kind, andstage_familytoolchains[]keeps toolchain-owned capabilities on the toolchain instead of duplicating them as standalone runtime/tool evidenceplan.dependency_chainis the ordered task graph ota would executeplan.dependency_steps[]adds machine-readable backend provenance per planned task step, including selected backend, selected context, parent task, and whether the backend came fromoverride,task default mode,task context,mode context,mode branch support,default context,contract preferred,default, orinherited parent backend- when a dependency step is a structured setup lane,
plan.dependency_steps[].preparecarries the same strict machine-readable prepare summary used by task/workflow discovery. It retains the selected source kind, working directory, Compose file and env-file sets, package filter/groups, browser targets, and hydration posture, including additivedeclared_hydration_provenanceandresolved_hydration_provenancefor hydration-owned source posture such as.NET restoreconfig_file, explicitsources[], or uv local-project declaration; the declared record is contract truth, while the resolved record can add parsedsource_identities[]with stable feedurland, when config-backed, its declaredname, plusresolution: resolved|unavailableandresolution_errorwhen Ota could not recover the config-backed source set honestly; anambient_defaultposture is intentionallyunavailableuntil the contract declares a config file or explicit source URLs plan.requirement_lines,plan.actions, andplan.notesshow what ota would check, activate, provision, or run- exit
0means the preview is actionable; exit1means the preview is blocked by contract, env, or execution-plan problems - blocked previews still use the full preview envelope on stdout so automation can read
summary.primary_blockerwithout scraping stderr
ota policy review --json
ota policy review --json is the policy-authority view over a repo contract. It is read-only and
keeps the active policy source/path explicit so editors and CI can tell whether the repo contract
or the org policy boundary needs to move.
{
"ok": false,
"path": "/abs/path/to/ota.yaml",
"policy_source": "repo policy",
"policy_path": "./.ota/org-policy.yaml",
"summary": {
"ok": false,
"error_count": 1,
"warn_count": 1,
"info_count": 0
},
"finding_groups": [
{
"action_key": "policy-provisioning-declared",
"action_title": "Review active policy surfaces",
"action_next": "use `ota policy review` to inspect the active policy source, or keep these approved sources in mind when provisioning or bootstrap needs a governed path",
"count": 1
}
],
"findings": [
{
"code": "OTA_POLICY_PACK_VIOLATION",
"category": "policy",
"owner": "org_policy",
"severity": "error",
"summary": "Repo does not satisfy org policy pack",
"why": "...",
"next": "...",
"evidence": {
"observed": "...",
"expected": "...",
"source": "...",
"checked_at": "...",
"command": "...",
"path": "..."
}
}
]
}
The optional policy payload mirrors the loaded policy pack when ota can read it. Consumers that
need the authoritative boundary can inspect policy_source and policy_path first, then read the
findings and grouped actions to decide whether the repo contract or the org policy pack should be
updated.
ota policy init --json
ota policy init --json is the conservative starter-policy surface. It either previews or writes a
minimal valid .ota/org-policy.yaml without guessing org rules, provisioning approvals, or policy
intent.
{
"ok": true,
"path": "/abs/path/to/.ota/org-policy.yaml",
"written": false,
"mode": "policy",
"preset": "agent",
"config": {
"policies": {
"agent": {
"require_safe_tasks": true,
"require_writable_paths": true
},
"exports": {
"require_agents_md": true
}
}
}
}
Current JSON fields:
okpathwrittenmode(policy)- optional
preset(required-sections,provisioning, oragent) config- failure responses include
error - overwrite refusals may include
next
Failure example:
{
"ok": false,
"path": "/abs/path/to/.ota/org-policy.yaml",
"written": false,
"mode": "policy",
"error": "`./.ota/org-policy.yaml` already exists; refusing to overwrite the existing policy pack",
"next": "ota policy /abs/path/to/repo"
}
ota workspace doctor --json may include the same execution object on each repo item when the
underlying repo contract declares execution metadata, including env provenance for inherited
workspace policy values.
ota workspace doctor --json may also include the same extensions object on each repo item when
the underlying repo contract declares it. The descriptor shape matches ota doctor --json.
Each repo item may also include additive primary_blocker with that repo's current highest-priority
finding (severity, summary, why, next, optional code, and optional provenance fields).
ota workspace doctor --json also includes a top-level summary object with repo and finding
counts for hosted validation and editor consumers. The workspace summary also carries
verdict / agent_verdict values. When there is at least one finding, the summary may also
include primary_blocker with the highest-priority blocker details and the repo name that owns it.
When that blocker maps back to a stable finding identity, the workspace summary also includes
additive primary_blocker.code.
{
"ok": false,
"path": "/abs/path/to/ota.workspace.yaml",
"summary": {
"repo_count": 1,
"ready_count": 0,
"not_ready_count": 1,
"verdict": "not_ready",
"agent_verdict": "not_ready",
"error_count": 1,
"warn_count": 0,
"info_count": 0,
"primary_blocker": {
"repo": "web",
"severity": "error",
"summary": "Repo not acquired: web",
"why": "...",
"next": "...",
"code": "OTA_WORKSPACE_REPO_NOT_ACQUIRED"
}
},
"repos": [
{
"name": "web",
"path": "/abs/path/to/apps/web",
"contract_path": "/abs/path/to/apps/web/ota.yaml",
"workflow": "app",
"required": true,
"ok": false,
"execution": {
"preferred": "native",
"supported": ["native"],
"lifecycle": "persistent",
"env": [
{
"name": "OTA_TEST_SHARED",
"required": true,
"policy": "workspace-policy",
"source": "workspace policy"
}
]
},
"findings": [
{
"severity": "error",
"summary": "Repo not acquired: web",
"why": "...",
"next": "..."
}
]
}
]
}
ota workspace list --json also includes a top-level summary object with repo inventory counts
for editor, CI, and hosted preflight tooling. Its per-repo execution object mirrors workspace
doctor execution metadata, including env provenance when the repo contract declares execution env
requirements.
Root monorepo summary output can also include grouped member findings under members.
Doctor JSON findings also include remote target-shape warnings when relevant, such as suspicious
ssh/tsh targets without user@host or kubectl targets that do not start with pod/.
ota explain --json
Explain JSON separates the grouped remediation plan from the detailed finding list:
actionsis the ordered grouped plan and is the best machine-readable "what should I do first?" surfacestepskeeps the finding-level detail with stable codes for deeper drill-in
Both actions and steps stay deterministic. Explain steps may also include provenance and
provenance_key when ota can trace the diagnosis source for the underlying finding. When a
finding already carries explicit identity, steps[].code preserves that stable code directly
instead of re-deriving advisory identity from rendered summary text.
{
"ok": false,
"path": "/abs/path/to/ota.yaml",
"summary": {
"error_count": 1,
"warn_count": 1,
"info_count": 0,
"step_count": 2
},
"actions": [
{
"order": 1,
"action_key": "tasks-missing",
"action_title": "Add at least one declared task to the contract",
"severity": "error",
"count": 1,
"why": "...",
"next": "run `ota detect --dry-run .` to review inferred tasks before writing one"
}
],
"steps": [
{
"order": 1,
"code": "OTA_TASKS_MISSING",
"severity": "error",
"summary": "No tasks defined in contract",
"why": "...",
"next": "...",
"provenance": "org policy",
"provenance_key": "org_policy"
}
]
}
ota workspace explain --json
Workspace explain now exposes two ordered action surfaces:
- top-level
actionsfor the grouped workspace plan with explicit repo ownership - per-repo
actionsfor the grouped ordered remediation plan stepsfor the finding-level detail
Workspace explain steps may also include provenance and provenance_key when ota can trace the
diagnosis source for the underlying finding.
{
"ok": false,
"path": "/abs/path/to/ota.workspace.yaml",
"summary": {
"repo_count": 2,
"ready_count": 0,
"not_ready_count": 2,
"error_count": 2,
"warn_count": 0,
"info_count": 0,
"step_count": 2
},
"actions": [
{
"repo": "api",
"path": "/abs/path/to/api",
"contract_path": "/abs/path/to/api/ota.yaml",
"required": true,
"order": 1,
"action_key": "tasks-missing",
"action_title": "Add at least one declared task to the contract",
"severity": "error",
"count": 1,
"why": "...",
"next": "run `ota detect --dry-run .` to review inferred tasks before writing one"
}
],
"repos": [
{
"name": "api",
"path": "/abs/path/to/api",
"contract_path": "/abs/path/to/api/ota.yaml",
"required": true,
"ok": false,
"summary": {
"error_count": 1,
"warn_count": 0,
"info_count": 0,
"step_count": 1
},
"actions": [
{
"order": 1,
"action_key": "tasks-missing",
"action_title": "Add at least one declared task to the contract",
"severity": "error",
"count": 1,
"why": "...",
"next": "run `ota detect --dry-run .` to review inferred tasks before writing one"
}
],
"steps": [
{
"order": 1,
"code": "OTA_TASKS_MISSING",
"severity": "error",
"summary": "No tasks defined in contract",
"why": "...",
"next": "...",
"provenance": "org policy",
"provenance_key": "org_policy"
}
]
}
]
}
ota workspace validate --json
ota workspace validate --json uses the same success/failure shape as ota validate --json,
but path refers to the resolved ota.workspace.yaml.
Success:
{
"ok": true,
"path": "/abs/path/to/ota.workspace.yaml",
"summary": {
"error_count": 0
}
}
Failure:
{
"ok": false,
"path": "/abs/path/to/ota.workspace.yaml",
"summary": {
"error_count": 1
},
"errors": ["..."]
}
ota workspace init --json and ota workspace detect --json
Success:
{
"ok": true,
"path": "/abs/path/to/ota.workspace.yaml",
"written": false,
"mode": "scaffold",
"config": {
"version": 1,
"workspace": {
"name": "ota-workspace"
},
"repos": {
"web": {
"path": "apps/web",
"required": true
}
}
},
"provenance": [
{
"field": "workspace.name",
"provenance": "workspace-derived",
"provenance_key": "workspace_scaffold",
"source": "workspace-root-directory"
},
{
"field": "repos.web.path",
"provenance": "workspace-derived",
"provenance_key": "workspace_scaffold",
"source": "workspace-discovery"
},
{
"field": "repos.web.required",
"provenance": "template-derived",
"provenance_key": "template_derived",
"source": "ota.workspace.init#repo_required_default"
}
],
"included": [
{
"name": "web",
"path": "apps/web"
}
],
"missing_contract": [],
"comparison": {
"existing_contract": true,
"additions": [
{
"name": "api",
"path": "services/api"
}
]
}
}
Failure:
{
"ok": false,
"path": "/abs/path/to/ota.workspace.yaml",
"written": false,
"mode": "scaffold",
"error": "..."
}
Failure shape can also include:
next: optional safe follow-up command when overwrite is refused
provenance is the per-field source map for the generated workspace scaffold or merged workspace contract:
workspace-derivedentries useprovenance_key: "workspace_scaffold"for fields taken from workspace root naming or discovered repo contractsworkspace-declaredentries useprovenance_key: "workspace_contract"for fields preserved from an existingota.workspace.yamlduring merge preview or merge writetemplate-derivedentries cover scaffold defaults such asversionand the defaultrequired: truerepo policysourcetells you whether a field came from the workspace root directory, workspace discovery, the existing workspace contract, or an explicit workspace scaffold default
ota workspace doctor --json
{
"ok": false,
"path": "/abs/path/to/ota.workspace.yaml",
"summary": {
"repo_count": 1,
"ready_count": 0,
"not_ready_count": 1,
"error_count": 1,
"warn_count": 0,
"info_count": 0
},
"repos": [
{
"name": "web",
"path": "/abs/path/to/apps/web",
"contract_path": "/abs/path/to/apps/web/ota.yaml",
"required": true,
"ok": false,
"primary_blocker": {
"severity": "error",
"summary": "Repo not acquired: web",
"why": "...",
"next": "...",
"code": "OTA_WORKSPACE_REPO_NOT_ACQUIRED"
},
"findings": [
{
"severity": "error",
"summary": "Repo not acquired: web",
"why": "...",
"next": "..."
}
]
}
]
}
When ota workspace doctor --json --progress-json is used, Ota also emits live workspace progress
events as compact one-line JSON on stderr. Those progress events use the same
workspace_progress event shape as ota workspace run|up|refresh --json --progress-json, while
the final doctor report remains the single JSON document on stdout.
When a workspace repo declares runtimes or tools and policy provides approved sources for them,
the per-repo item may also include the same provisioning diagnostics bundle as repo-level
doctor output. When policy declares adapter bootstrap sources, the per-repo item may also
include the same adapter_bootstrap diagnostics bundle. Both bundles carry the read-only plan
and backend intake request together, so workspace consumers can inspect the same future
provisioning signals without mutating anything.
When the workspace contract pins repos.<name>.workflow, each repo item may also include that
selected workflow name as additive workflow.
ota workspace tasks --json
{
"ok": true,
"path": "/abs/path/to/ota.workspace.yaml",
"summary": {
"repo_count": 1,
"ready_count": 1,
"not_ready_count": 0,
"acquired_count": 1,
"missing_contract_count": 0
},
"repos": [
{
"name": "api",
"path": "/abs/path/to/services/api",
"contract_path": "/abs/path/to/services/api/ota.yaml",
"workflow": "app",
"required": true,
"acquired": true,
"depends_on": ["db"],
"tasks": [
{
"name": "env-local",
"description": "Create a local env overlay when missing.",
"kind": "copy_if_missing",
"action": {
"kind": "copy_if_missing",
"from": ".env.example",
"to": ".env.local"
},
"depends_on": [],
"after_success": [],
"after_failure": [],
"after_always": []
},
{
"name": "setup",
"description": "Install repo dependencies.",
"kind": "run",
"run": "pnpm install",
"effects": {
"writes": ["node_modules"],
"network": true
},
"depends_on": ["env-local"],
"after_success": ["verify-lockfile"],
"after_failure": [],
"after_always": ["cleanup-temp"]
},
{
"name": "quickstart",
"description": "Run the packaged preview command.",
"kind": "command",
"launch": {
"kind": "command",
"exe": "npx",
"args": ["vite", "--host", "127.0.0.1", "--port", "3000"]
},
"depends_on": [],
"after_success": [],
"after_failure": [],
"after_always": []
}
]
}
]
}
Non-acquired repos keep acquired: false and tasks: []. Each task report can also carry
effects, requires_services, after_success, after_failure, and after_always so
automation can see the same repo writes, connectivity needs, external-state mutations, service
requirements, and post-outcome task graph that ota run executes. Structured task launch is
additive through tasks[*].launch when the repo task uses command or packaged-container launch.
Structured task actions are additive through tasks[*].action when the repo task uses a
first-class setup action such as copy_if_missing.
When the workspace contract pins repos.<name>.workflow, each repo item may also include additive
workflow.
ota workspace list --json
{
"ok": true,
"path": "/abs/path/to/ota.workspace.yaml",
"repos": [
{
"name": "api",
"path": "/abs/path/to/services/api",
"contract_path": "/abs/path/to/services/api/ota.yaml",
"workflow": "app",
"contract_present": true,
"required": true,
"acquired": true,
"status": "READY",
"execution": {
"preferred": "remote",
"supported": ["remote"],
"lifecycle": "ephemeral",
"env": [
{
"name": "AWS_PROFILE",
"required": true,
"policy": "workspace-policy",
"source": "workspace policy"
}
],
"backends": {
"remote": {
"provider": "ssh",
"target": "user@host",
"cwd": "/workspace"
}
}
},
"depends_on": ["db"]
}
]
}
When the workspace contract pins repos.<name>.workflow, each repo item may also include that
selected workflow name as additive workflow.
ota workspace run --json
{
"ok": true,
"path": "/abs/path/to/ota.workspace.yaml",
"task": "setup",
"summary": {
"error_count": 0,
"warn_count": 0,
"info_count": 0,
"step_count": 1,
"repo_count": 1,
"ready_count": 1,
"not_ready_count": 0
},
"receipt": {
"ok": true,
"path": "/abs/path/to/ota.workspace.yaml",
"scope": "workspace",
"contract": "/abs/path/to/ota.workspace.yaml",
"contract_identity": {
"version": 1,
"project": {
"name": "ota-dev",
"type": "workspace"
},
"counts": {
"runtimes": 0,
"tools": 0,
"env": 0,
"services": 0,
"checks": 0,
"tasks": 0,
"repos": 1,
"policies": 0
}
},
"workspace": "ota-dev",
"env_sources": [
{
"name": "OTA_TEST_SHARED",
"value": "workspace-policy",
"source": "workspace policy"
}
],
"steps": [
{
"order": 1,
"label": "web",
"status": "READY",
"detail": "task `setup`"
}
],
"summary": {
"error_count": 0,
"warn_count": 0,
"info_count": 0,
"step_count": 1,
"repo_count": 1,
"ready_count": 1,
"not_ready_count": 0
}
},
"repos": [
{
"name": "web",
"path": "/abs/path/to/apps/web",
"contract_path": "/abs/path/to/apps/web/ota.yaml",
"workflow": "app",
"required": true,
"ok": true,
"status": "READY",
"task": "setup",
"findings": []
}
]
}
receipt mirrors the workspace execution roll-up, keeps backend-aware execution metadata on the
same surface as the repo-level execution commands, and includes additive
receipt.contract_identity with workspace name/type plus compact workspace repo and policy counts.
Optional per-repo fields:
nextnext_stepsexit_codestdoutstderrenv_sources
When you add --progress-json to ota workspace run --json, ota workspace up --json, or
ota workspace refresh --json, ota keeps the final roll-up JSON report on stdout and emits
newline-delimited progress events on stderr.
{
"event": "workspace_progress",
"command": "workspace.run",
"workspace": "ota-dev",
"status": "RUN",
"repo": "api",
"phase": "run",
"stage_family": "verify",
"tail": "workspace:entrypoint -> api:tests:contract",
"task": "workspace:entrypoint",
"repo_task": "api:tests:contract",
"dependency": null
}
eventis alwaysworkspace_progresscommandidentifies the workspace command emitting the event, such asworkspace.doctor,workspace.check,workspace.diff,workspace.status,workspace.receipt,workspace.run,workspace.up, orworkspace.refresh, so machine consumers do not need out-of-band stream contextworkspaceis the resolved workspace namestatusis the live transition label such asACQUIRE,RUN,READY,BLOCKED,WARN,TASK FAILED,ACQUIRE FAILED,REFRESH, orREFRESH PREVIEWrepois the workspace repo namephaseis the command-local operational lane such asacquisition,run,prepare,refresh,doctor,check,diff,status, orreceipt, so consumers do not need to infer intent from thecommandfield alonestage_familycarries the broader governance family for the event such asprepare,setup,verify, orreceipttailcarries the same operator-facing suffix as the text progress line when one existstaskandrepo_taskare populated for workspace-run task dispatch so agents can see the requested workspace task and the resolved repo-local task separately; those fields stay present on terminal repo-task events such asREADYandTASK FAILED, not only the initialRUNdependencyis populated for blocked transitions when a repo is waiting on a failed dependency; blocked workspace-run events also keeptaskandrepo_taskwhen ota already knows the mapped repo-local task
ota workspace check --json
ota workspace check --json uses the same finding shape as ota workspace doctor --json,
including additive finding_groups and per-repo primary_blocker when present:
{
"ok": false,
"path": "/abs/path/to/ota.workspace.yaml",
"summary": {
"repo_count": 1,
"ready_count": 0,
"not_ready_count": 1,
"verdict": "not_ready",
"agent_verdict": "not_ready",
"error_count": 1,
"warn_count": 0,
"info_count": 0,
"primary_blocker": {
"repo": "web",
"severity": "error",
"summary": "Check failed: health-check",
"why": "...",
"next": "...",
"code": "OTA_CHECK_FAILED"
}
},
"repos": [
{
"name": "web",
"path": "/abs/path/to/apps/web",
"contract_path": "/abs/path/to/apps/web/ota.yaml",
"required": true,
"ok": false,
"primary_blocker": {
"severity": "error",
"summary": "Check failed: health-check",
"why": "...",
"next": "...",
"code": "OTA_CHECK_FAILED"
},
"findings": [
{
"severity": "error",
"summary": "Check failed: health-check",
"why": "...",
"next": "..."
}
]
}
]
}
When ota workspace check --json --progress-json is used, Ota also emits live workspace progress
events as compact one-line JSON on stderr. Those progress events use the same
workspace_progress event shape as ota workspace doctor|run|up|refresh --json --progress-json,
while the final workspace-check report remains the single JSON document on stdout.
summary mirrors the workspace doctor roll-up so hosted gates can read the same repo and finding
counts from checks-only output. When one repo has several findings, additive primary_blocker
identifies the repo's current highest-priority next move without forcing consumers to choose one
from the full list themselves.
ota init --json
{
"ok": true,
"path": "/abs/path/to/ota.yaml",
"written": false,
"mode": "detected",
"config": {
"version": 1
},
"inferred": [
{
"field": "project.name",
"type": "project",
"source_class": "environment_toolchain",
"value": "ota-app",
"source": "package.json#name",
"signal": "config",
"confidence": "high"
}
],
"provenance": [
{
"field": "project.name",
"provenance": "detector-inferred",
"provenance_key": "repo_signals",
"source": "package.json#name",
"confidence": "high"
},
{
"field": "agent.bootstrap.ota.sh",
"provenance": "template-derived",
"provenance_key": "template_derived",
"source": "ota.init#starter_agent_bootstrap"
}
]
}
mode is:
blankfor the minimal starter pathdetectedfor detector-led starter outputpackfor an explicit starter-pack preview or writecatalogforota init --packs --json
When task inference is confident enough to write, config.tasks.<name>.notes may also be
present and point at the matching ota run <task> command.
Each inferred[*] entry now carries additive metadata for human and machine consumers:
typeis one ofproject,runtime,tool,env,service,check,task,agent, orfieldsource_classis one ofenvironment_toolchain,task_command,runtime_service,ci_verification,agent_boundary,workspace_bootstrap, orheuristicsignalis one ofconfig,script,lockfile,file,template, orconvention- task-shaped entries can also include
agent_safe(yes,no,unknown) andagent_signal(verification_candidateorbootstrap_candidate) when ota can classify the task for agent workflows
In dry-run preview mode, config matches the starter contract ota would review or write,
including derived starter defaults such as a minimal agent block when ota can infer one
safely.
When mode is pack, the payload also includes pack with the selected built-in starter pack
name. Pack-generated tasks can carry short description fields, optional pack_options records
the selected starter-specific knobs such as Node package manager or Python test runner, and
provenance records those fields as template-derived with the selected
ota.init#starter_pack... variant source while keeping directory-derived values such as
project.name traced to ota.init#directory_name.
{
"pack": "node",
"pack_options": {
"package_manager": "npm"
},
"config": {
"tasks": {
"setup": {
"run": "npm install"
}
}
},
"provenance": [
{
"field": "tasks.setup.run",
"provenance": "template-derived",
"provenance_key": "template_derived",
"source": "ota.init#starter_pack.node.package_manager.npm"
}
]
}
When explicit pack mode disagrees with strong detected repo signals, ota adds pack_advisory
without changing the selected pack or merging detector output into the starter:
{
"pack": "python",
"pack_advisory": {
"selected_pack": "python",
"suggested_pack": "node",
"selected_pack_score": 0,
"suggested_pack_score": 3,
"score_gap": 3,
"summary": "stronger distinct repo signals favor `node` over the selected pack `python`",
"signals": ["package.json"],
"signal_details": [
{
"signal": "package.json",
"weight": 3
}
],
"next": "ota init --pack node --dry-run ."
}
}
selected_pack_score and suggested_pack_score show the distinct-signal strength ota saw for
each pack, score_gap shows how far the suggested pack leads, signal_details preserves the
weighted signal markers behind the flat signals list for the suggested pack, and
selected_signal_details does the same for any incidental signals that still matched the
explicitly selected pack.
When the repo clearly looks like a managed ecosystem that ota does not ship as a toolchain yet,
ota init --json also adds toolchain_opportunities as additive agent-facing guidance. Terminal
text stays user-safe and only says to keep the current runtimes / tools fallback model for
now; provider-candidate detail stays in JSON. When ota already ships the ecosystem owner, the
starter config uses toolchains.<name> directly instead of adding a fallback opportunity. For
example, Python repos with uv.lock now converge directly on toolchains.python with the
canonical structured fulfillment model, so they no longer emit fallback Python opportunity
guidance. The same ownership promotion now applies to detected Go, Ruby, and .NET ecosystems,
yielding toolchains.go, toolchains.ruby, and toolchains.dotnet instead of split
runtime/tool declarations.
provenance is the per-field source map for the starter contract:
- detector-backed fields use
provenance: "detector-inferred"andprovenance_key: "repo_signals" - starter-only defaults use
provenance: "template-derived"andprovenance_key: "template_derived" - detector-backed entries also copy
sourceandconfidencefrom the matchinginferred[*] - template-derived entries use an
ota.init#...source label so automation can distinguish starter defaults from repo evidence
Failure example:
{
"ok": false,
"path": "./ota.yaml",
"written": false,
"error": "`./ota.yaml` already exists; ota init is only for repos without an ota contract\n\nNext:\n▸ review the existing contract with `ota validate`\n▸ review the existing contract with `ota doctor`\n▸ compare detected repo signals with `ota detect --merge --dry-run`\n▸ apply detected add-only high-confidence fields now with `ota detect --merge`",
"next": "ota detect --merge --dry-run"
}
ota init --packs --json lists the available built-in starter packs without previewing one
contract. The current catalog includes node, python, ruby, go, rust, dotnet,
php-composer, java-maven, and java-gradle:
{
"ok": true,
"mode": "catalog",
"packs": [
{
"name": "node",
"summary": "Conventional Node starter with toolchain-owned Node and first-class package-manager setup, dev, and test tasks.",
"when": "Use this for repo-level Node apps or services that need an explicit JavaScript starter instead of detector-led init. The default path keeps Node ownership under `toolchains.node`, seeds first-class package-manager hydration for `setup`, and you can override the package manager with `--package-manager` when the repo is intentionally npm-, yarn-, or bun-based.",
"command": "ota init --pack node",
"next": "ota init --pack node --dry-run .",
"does_not_infer": [
"the repo's package manager unless `--package-manager` says so",
"repo-specific script names or extra task variants beyond the seeded `setup`, `dev`, and `test` loop"
],
"options": [
{
"flag": "--package-manager",
"summary": "Choose the package manager used for setup and script execution.",
"default": "pnpm",
"values": ["npm", "pnpm", "yarn", "bun"]
}
],
"seeds": {
"toolchains": ["node"],
"runtimes": [],
"tools": [],
"checks": [],
"tasks": ["setup", "dev", "test"]
}
},
{
"name": "ruby",
"summary": "Conventional Ruby starter with toolchain-owned Ruby and Bundler-driven setup/test tasks.",
"when": "Use this for Ruby repos that should start from `toolchains.ruby` ownership and the standard Bundler loop without relying on detector-led init.",
"command": "ota init --pack ruby",
"next": "ota init --pack ruby --dry-run .",
"does_not_infer": [
"framework-specific commands (for example Rails, Sinatra, or Hanami server entrypoints) beyond the seeded Bundler setup/test surface",
"repo-specific test wrappers or flags beyond the seeded `bundle exec rake test` command"
],
"seeds": {
"toolchains": ["ruby"],
"runtimes": [],
"tools": [],
"checks": [],
"tasks": ["setup", "test"]
}
},
{
"name": "go",
"summary": "Conventional Go starter with first-class module hydration, build, and test tasks.",
"when": "Use this for Go module repos that should start from toolchain-owned Go module hydration plus the standard `go build` and `go test` flow without relying on detector-led init.",
"command": "ota init --pack go",
"next": "ota init --pack go --dry-run .",
"does_not_infer": [
"workspace layout, code generation, or custom build flags beyond the standard module download/build/test loop"
],
"seeds": {
"toolchains": ["go"],
"runtimes": [],
"tools": [],
"checks": [],
"tasks": ["setup", "build", "test"]
}
},
{
"name": "rust",
"summary": "Conventional Rust starter with toolchain-owned Rust plus first-class Cargo hydration for `setup`, followed by build and test tasks.",
"when": "Use this for Rust repos that should start from `toolchains.rust` ownership, first-class setup hydration through `prepare.kind: dependency_hydration`, and the standard `cargo build` / `cargo test` loop without relying on detector-led init.",
"command": "ota init --pack rust",
"next": "ota init --pack rust --dry-run .",
"does_not_infer": [
"workspace members, feature flags, or custom cargo aliases beyond the standard fetch/build/test loop"
],
"seeds": {
"toolchains": ["rust"],
"runtimes": [],
"tools": [],
"checks": [],
"tasks": ["setup", "build", "test"]
}
},
{
"name": "dotnet",
"summary": "Conventional .NET starter with toolchain-owned .NET plus first-class `dotnet_restore` hydration for `setup`, followed by build and test tasks.",
"when": "Use this for .NET repos that should start from `toolchains.dotnet` ownership, first-class setup hydration through `prepare.kind: dependency_hydration`, and the standard `dotnet build` / `dotnet test` loop without relying on detector-led init.",
"command": "ota init --pack dotnet",
"next": "ota init --pack dotnet --dry-run .",
"does_not_infer": [
"solution-specific target selection, test filtering, or custom dotnet CLI flags beyond the standard restore/build/test loop"
],
"seeds": {
"toolchains": ["dotnet"],
"runtimes": [],
"tools": [],
"checks": [],
"tasks": ["setup", "build", "test"]
}
},
{
"name": "php-composer",
"summary": "Conventional PHP starter for Composer-managed repos with Composer install and optional existing test-script reuse.",
"when": "Use this for Composer-managed PHP repos that should start from `composer install` and, when the repo already declares `scripts.test`, the existing Composer test script without relying on detector-led init.",
"command": "ota init --pack php-composer",
"next": "ota init --pack php-composer --dry-run .",
"does_not_infer": [
"framework-specific entrypoints, web server commands, or whether the repo uses phpunit, pest, artisan, or another test wrapper unless the repo already declares a Composer `scripts.test` entry"
],
"seeds": {
"toolchains": [],
"runtimes": ["php"],
"tools": ["composer"],
"checks": ["php-installed", "composer-installed"],
"tasks": ["setup"]
}
},
{
"name": "java-maven",
"summary": "Conventional Java starter for Maven-driven repos with first-class Maven hydration for `setup`, plus build and test lifecycles, preferring `mvnw` when the repo already ships it.",
"when": "Use this when the repo is intentionally Maven-based and you want an explicit Java starter without relying on repo detection. If `mvnw` already exists, ota uses wrapper-owned `prepare.kind: dependency_hydration` instead of requiring a global Maven install.",
"command": "ota init --pack java-maven",
"next": "ota init --pack java-maven --dry-run .",
"does_not_infer": [
"multi-module reactor details, plugin goals, or org-specific wrapper/bootstrap scripts beyond the standard Maven build/test loop"
],
"seeds": {
"toolchains": ["java"],
"runtimes": [],
"tools": [],
"checks": [],
"tasks": ["setup", "build", "test"]
}
},
{
"name": "java-gradle",
"summary": "Conventional Java starter for Gradle-driven repos with first-class Gradle hydration for `setup`, plus build and test lifecycles, preferring `gradlew` when the repo already ships it.",
"when": "Use this when the repo is intentionally Gradle-based and you want an explicit Java starter without relying on repo detection. If `gradlew` already exists, ota uses wrapper-owned `prepare.kind: dependency_hydration` instead of requiring a global Gradle install.",
"command": "ota init --pack java-gradle",
"next": "ota init --pack java-gradle --dry-run .",
"does_not_infer": [
"multi-project build logic, plugin conventions, or org-specific Gradle bootstrap beyond the standard build/test loop"
],
"seeds": {
"toolchains": ["java"],
"runtimes": [],
"tools": [],
"checks": [],
"tasks": ["setup", "build", "test"]
}
}
]
}
Each catalog entry now carries does_not_infer so automation can explain the deliberate boundary of
each starter pack instead of assuming the pack will absorb repo-specific workflow details.
Each catalog entry keeps the operator guidance machine-readable:
commandis the exact pack-selection commandnextis the safe dry-run preview command to review before writingseedslists the unconditional starter fields, including shippedtoolchainsowners when a pack now starts from a managed ecosystem contract instead of separateruntimes/tools
ota check --json
ota check --json uses the same finding shape as ota doctor --json, including additive
finding_groups when present. It may also include the same additive top-level workflow
summary and toolchains[] evidence for the selected workflow path, including workflow
readiness_probes, readiness_surfaces, signal_readiness_*, and expose_surfaces when declared:
{
"ok": false,
"path": "/abs/path/to/ota.yaml",
"workflow": {
"name": "app",
"notes": "Use this for the primary readiness path.\n",
"run_task": "dev",
"required_services": ["postgres"],
"readiness_checks": ["app-health"],
"readiness_probes": ["app-ready"],
"readiness_surfaces": ["backend"],
"signal_readiness_checks": [],
"signal_readiness_probes": [],
"signal_readiness_surfaces": [],
"exposes": []
},
"findings": [
{
"severity": "error",
"summary": "...",
"why": "...",
"next": "..."
}
]
}
Root monorepo summary output can also include grouped member findings under members.
ota up --json
ota up --json has two failure classes:
- execution reached the
uppipeline: returnsUpStatus(status,phase, additivegovernance,findings,receipt, optionalservice/task/exit_code) - contract load/validation failed before the
uppipeline: returnsValidateFailureshape (ok,path, and eithererrorsorerror)
When an audited crossing actually occurs, governance.crossing and receipt.crossing carry the
same ota-authored record. That record now includes additive evidence_classes so downstream
consumers can tell which fields were caller-asserted, ota-derived from the decision inputs, or
runner-attested at the decision site itself.
{
"ok": true,
"path": "/abs/path/to/ota.yaml",
"status": "READY",
"phase": "post-up diagnosis",
"governance": {
"preflight": {
"state": "allowed",
"crossing_required": false,
"crossing_classification": "routine",
"decision_inputs": [
{
"id": "workflow:verify",
"family": "lane",
"evidence_class": "derived",
"replay_class": "pinned",
"detail": "kind=workflow"
},
{
"id": "actor_mode:human",
"family": "actor_mode",
"evidence_class": "derived",
"replay_class": "pinned"
},
{
"id": "doctor_verdict:ready",
"family": "readiness_gate",
"evidence_class": "derived",
"replay_class": "witnessed"
},
{
"id": "receipt_expected:true",
"family": "evidence_expectation",
"evidence_class": "derived",
"replay_class": "pinned"
},
{
"id": "proof_expected:true",
"family": "evidence_expectation",
"evidence_class": "derived",
"replay_class": "pinned"
}
],
"replay": {
"status": "satisfied"
},
"evidence_classes": {
"state": "derived",
"crossing_required": "derived",
"crossing_classification": "derived",
"decision_inputs": "derived",
"replay": "derived",
"receipt_expected": "derived",
"proof_expected": "derived"
},
"receipt_expected": true,
"proof_expected": true
},
"post_execution": {
"state": "evidence_missing",
"execution_attempted": true,
"refusal_occurred": false,
"decision_inputs": [
{
"id": "execution_attempted:true",
"family": "execution_observation",
"evidence_class": "derived",
"replay_class": "witnessed"
},
{
"id": "receipt_present:true",
"family": "receipt_observation",
"evidence_class": "attested",
"replay_class": "witnessed"
},
{
"id": "proof_expected:true",
"family": "proof_expectation",
"evidence_class": "derived",
"replay_class": "pinned"
},
{
"id": "receipt_status:ready",
"family": "receipt_observation",
"evidence_class": "attested",
"replay_class": "witnessed"
},
{
"id": "proof_present:false",
"family": "proof_observation",
"evidence_class": "derived",
"replay_class": "witnessed"
},
{
"id": "crossing_record_state:not_required",
"family": "crossing_observation",
"evidence_class": "derived",
"replay_class": "witnessed"
}
],
"replay": {
"status": "satisfied"
},
"decision_basis": [
{
"id": "evidence:receipt_present",
"family": "evidence_gate",
"evidence_class": "attested"
},
{
"id": "receipt_status:ready",
"family": "receipt_evidence",
"evidence_class": "attested"
},
{
"id": "evidence:proof_missing",
"family": "evidence_gate",
"evidence_class": "derived"
},
{
"id": "crossing_record:not_required",
"family": "crossing_evidence",
"evidence_class": "derived"
}
],
"evidence_classes": {
"state": "derived",
"execution_attempted": "derived",
"refusal_occurred": "derived",
"crossing_record_state": "derived",
"decision_inputs": "derived",
"replay": "derived",
"receipt_present": "attested",
"proof_present": "derived",
"receipt_status": "attested"
},
"receipt_present": true,
"proof_present": false,
"receipt_status": "ready"
}
},
"findings": [],
"receipt": {
"ok": true,
"path": "/abs/path/to/ota.yaml",
"scope": "repo",
"contract": "/abs/path/to/ota.yaml",
"contract_identity": {
"version": 1,
"project": {
"name": "ota",
"type": "application"
},
"metadata": {
"owner": "ota"
},
"execution": {
"preferred": "container",
"lifecycle": "ephemeral",
"supported": ["native", "container"],
"image": "rust:1.94-bookworm"
},
"counts": {
"runtimes": 0,
"tools": 0,
"env": 0,
"services": 0,
"checks": 0,
"tasks": 1
}
},
"backend": "native",
"steps": [
{
"order": 1,
"label": "post-up diagnosis",
"status": "READY"
}
],
"summary": {
"error_count": 0,
"warn_count": 0,
"info_count": 0,
"step_count": 1
}
}
}
Optional fields:
governance: the canonical machine-readable governance verdict for the selecteduppath;preflightkeeps boundary/block/refusal semantics distinct frompost_execution, which reports what evidence actually exists after the attempteduplane; additivepreflight.decision_basis[]carries the cited safety/refusal/crossing basis for the selected lane, while additivepost_execution.decision_basis[]carries the cited non-run, evidence, and crossing-record basis for the resulting evidence state- additive
preflight.decision_inputs[]andpost_execution.decision_inputs[]publish the replay-grade cited inputs behind those authoritative governance records;replay_class: "pinned"marks reusable pinned selectors, whilereplay_class: "witnessed"marks observed execution or evidence inputs - additive
preflight.replay.statusandpost_execution.replay.statuspublish whether ota can re-derive those emitted governance verdicts from the cited inputs it just recorded - additive
preflight.evidence_classesandpost_execution.evidence_classespublish field-level provenance on those authoritative governance records, so consumers can distinguish ota-derived decision truth from boundary-attested receipt attachment truth post_execution.stateis no longer a flatter success-only placeholder when ota already knows the evidence result: if execution happened but the expected proof bar was not met, ota emitsevidence_missingand cites that exact basis inpost_execution.decision_basis[]- preview
governance.sandbox_policymay also be present onota up --json --dry-runwhen the selected workflow path carries compilable runtime-boundary truth; it repeats the same firstcodex_localsandbox target shape used by task and workflow discovery so preview consumers can recover writable-path and outbound-target posture directly from the selecteduplane receipt: execution receipt for the executed repoupphase, including additivereceipt.contract_identity; monorepo aggregate output keeps groupedmembersresults instead of a top-level receiptreplay: present whenota upwas invoked with--replay-baseline <selection>; this carries the resolved baseline pointer (source,selection_path,archive_path,promoted_at,ok, archivedscope,last_known_good), the selected lane scope, execution-authored replay posture (replay_verified,replay_failed, orreplay_unavailable), hermeticity, optionalfailure_kind, a concise reason, the same compact comparison block used by receipt diff, and grouped introduced/resolved/unchanged countsplan.dependency_steps[]: additive selected setup/dependency-plane preview for the chosenuppath; when a planned step is a structured hydration lane,prepare.declared_hydration_provenanceandprepare.resolved_hydration_provenancepublish the selected source posture, whileprepare.declared_uv_local_projectandprepare.resolved_uv_local_projectkeep declared and runner-observed local-project identity separate, on the same canonical carrier; the declared record preserves contract-ownedconfig_fileor explicitsources[], while the resolved record adds parsed config-backedsource_identities[]plusresolution: resolved|unavailableand an honestresolution_errorwhen source recovery fails; explicit URL overrides retain URL identity without inventing a feed name; ambient default source selection remainsunavailablebecause user/global NuGet configuration is not contract-ownedreceipt.dependency_steps: additive executed dependency-plane provenance for task-backed phases, using the sametask/backend/ optionalcontext/ optionalparent_task/backend_selection_sourceshape as previewplan.dependency_steps[]receipt.native_prerequisites: additive selected native prerequisite detail for the executed native task/setup path, including provisioning guidance and any applied native activationservice: present when a required service start command failstask: present when a task failure is reportedexit_code: present when a child command failure is reportedmembers: present on monorepo-root aggregate output with grouped member readiness results
Replay notes:
replay.comparison.artifact_trust[]only covers artifacts captured by both the archived baseline and the current execution receipt- matching
semantic_contract_snapshotis acquitting for contract truth only - matching
env_source_identityis acquitting for the named declared env-source file class only - matching task-declared
replay_inputsremain narrowing evidence, so replay may bereplay_verifiedwithhermeticity: partly_ambient - matching
policy_ruleset_identityis acquitting for the named policy/ruleset class only - informational findings remain visible in
findings[]and the grouped diff counts, but do not by themselves make an otherwise ready replay witness stale when the archived and current execution boundaries match; replay drift remains sensitive to execution failure plus introduced or resolved warning/error findings - a baseline from a different workflow,
backend,provider,target, orlifecycleis not replay-comparable for the selected lane: Ota emitsreplay_unavailablewithfailure_kind: baseline_scope_mismatchrather than promoting that archive tolast_known_good; comparereplay.baseline.scopewithreplay.scopeinstead of inferring the mismatch from text replay.failure_kindis emitted only when replay is not verified:baseline_unavailablebaseline_scope_mismatchsemantic_contract_driftnamed_input_drifthidden_input_suspicionwitness_mismatch
replay.baseline.last_known_goodis the replay-derived status of the selected baseline:replay_verifiedstale_witnessunavailable
Phase values:
preconditions: prerequisite diagnosis or host/runtime gating blocked before prepare or setupprepare: workflow prepare execution failedsetup: setup task execution failedservices: required service start or readiness failedrun: selected workflow run task exited before readiness was confirmedreadiness: the runtime started, but readiness probes, surfaces, or readiness checks still failed or were the final proof-success boundarycleanup: runtime proof cleanup failed after artifact captureinterrupted: runtime proof was interrupted by a signal
Example service-start failure:
{
"ok": false,
"path": "/abs/path/to/ota.yaml",
"status": "SERVICE START FAILED",
"phase": "services",
"findings": [],
"service": "postgres",
"exit_code": 9
}
Example contract-validation failure (before up execution starts):
{
"ok": false,
"path": "/abs/path/to/ota.yaml",
"errors": [
"tasks.build.run must not be empty"
]
}
ota up --dry-run --json is the read-only execution-plan preview surface for ota up.
{
"ok": true,
"path": "/abs/path/to/ota.yaml",
"dry_run": true,
"status": "READY WITH WARNINGS",
"preview_status": "RUNNABLE WITH WARNINGS",
"phase": "preview",
"summary": {
"verdict": "risky",
"agent_verdict": "not_ready",
"error_count": 0,
"warn_count": 1,
"info_count": 0
},
"contract_identity": {
"version": 1,
"project": {
"name": "ota"
},
"execution": {
"preferred": "native",
"lifecycle": "ephemeral"
},
"counts": {
"runtimes": 0,
"tools": 0,
"env": 0,
"services": 0,
"checks": 0,
"tasks": 1
}
},
"execution": {
"backend": "native",
"lifecycle": "ephemeral",
"task": "setup"
},
"plan": {
"actions": [
"run task `setup`",
"re-check repo readiness"
]
}
}
Current preview JSON fields:
okpathdry_runstatusphase(preview)summarywith the shareddoctor/checkverdict model; warning-only previews keepok: truewhile surfacingsummary.verdict: "risky"contract_identitywith the declared project, selected metadata, execution intent, and compact contract countsexecution.backendexecution.lifecyclewhen one is selectedexecution.imagewhen container execution is selectedexecution.targetwhen the selected execution context has a real named targetexecution.taskwhenupwould runsetupplan.actionsplan.skippedblockers
Use ota up --dry-run --json when you need the selected backend and lifecycle plus the action and
skip plan without provisioning, starting services, or writing repo files. See
up-preview.md for the preview contract.
ota receipt --json
ota receipt --json is the read-only repo receipt artifact. It runs the same readiness scan as
repo diagnosis in the selected execution context, packages the result as an execution receipt, and
keeps the findings array alongside the receipt for CI and archival consumers. Contract
load/validation failures still emit the shared ValidateFailure JSON shape on stdout. When the
selected or effective workflow owns a rendered env artifact, receipt JSON keeps that under
receipt.workflow_env_artifacts[].
{
"ok": true,
"path": "/abs/path/to/ota.yaml",
"mode": "receipt",
"archive_path": "/abs/path/to/.ota/receipts/repo-receipt-20260411-113015-042Z.json",
"summary": {
"error_count": 0,
"warn_count": 0,
"info_count": 0,
"step_count": 1
},
"receipt": {
"ok": true,
"path": "/abs/path/to/ota.yaml",
"scope": "repo",
"contract": "/abs/path/to/ota.yaml",
"contract_identity": {
"version": 1,
"project": {
"name": "receipt-demo"
},
"counts": {
"runtimes": 0,
"tools": 0,
"env": 0,
"services": 0,
"checks": 0,
"tasks": 1
}
},
"contract_snapshot_hash": "sha256:5dc5c7f6e0bf...",
"contract_snapshot_ref": ".ota/contracts/sha256-5dc5c7f6e0bf....json",
"assumption_set_hash": "sha256:8b7e5f1c3a0d...",
"evaluated_inputs": [
{
"id": "pnpm-lock.yaml",
"kind": "lockfile",
"input_class": "declared_dependency_resolution",
"identity": "sha256:4ed6a1..."
},
{
"id": "compose_image:compose.yaml#services.database",
"kind": "container_image_digest",
"input_class": "selected_runtime_artifact",
"identity": "sha256:8b7e5f..."
}
],
"backend": "native",
"workflow_env_artifacts": [
{
"path": ".env.docker-build",
"kind": "dotenv",
"profile": "docker-build",
"includes": ["DATABASE_URL"],
"exists": true,
"consumers": ["task:build", "service:postgres"]
}
],
"steps": [
{
"order": 1,
"label": "readiness",
"stage_family": "proof",
"status": "READY"
}
],
"summary": {
"error_count": 0,
"warn_count": 0,
"info_count": 0,
"step_count": 1
}
},
"findings": []
}
Current receipt JSON fields:
okpathmode(receipt)archive_path(when--archiveis set)artifact_routingsummaryreceiptfindings
The nested receipt object can also include:
contract_identitywith the declared project, selected metadata, execution intent, and compact contract countssteps[*].stage_familywith the broad execution-governance family for that recorded step:prepare,setup,verify,proof, orreceiptartifact_routing[]with the receipt/proof/snapshot artifact guide for the current receipt lane, including which artifact to inspect now and which archived artifact to keep when availablecontract_snapshot_hashwith the normalized semantic contract snapshot identity used for this receipt; the hash is content-addressed and stable across formatting-only contract editscontract_snapshot_refwhen Ota archived the normalized snapshot under.ota/contracts; plain read-only receipt JSON can still emit the hash without emitting a local archive refassumption_set_hashwith the canonical extracted assumption-set identity used for this receipt; the hash is derived from the normalized semantic path/value map rather than the raw archived snapshot fileevaluated_inputs[]with execution inputs captured while Ota authored the receipt. Shipped records include clean git HEAD source identity assource:git_headwhen the selected repo worktree is inside git and has no uncommitted changes; declared Node lockfile identities:pnpm-lock.yamlfor frozen pnpm andpackage-lock.jsonor authoritativenpm-shrinkwrap.jsonfornpm ci;runtime:nodecommand version evidence for those selected typed lanes; and static digest-pinned Compose images for explicitly selected services and their declareddepends_onclosure in explicitly declared Compose files. It also includes task-declared replay inputs with distinct machine classes: genericstatic_file,presentation_profile, andcomparator_profile. Ota never re-reads these identities later. Mutable tags, interpolated image references, inferred Compose files, unrelated stack services, ambient environment, and external-world inputs remain outside this evidence.witnessed_observations.query_traces[]when a selected task closure declares a JSONL query trace underwitnessed_observations.query_traces. These are attested historical observations, not evaluated inputs. The nested summary reports subjects, records, and divergent subjects so consumers can distinguish stable query identity from observed variation without over-reading it as a current-run replay decision.- a typed
evaluated_inputs[]hydration_provenancerecord whenota upselects a structured hydration lane with source posture. Its runner-derived nested detail keeps contract-declared source posture separate from the resolved execution-time identity. For uv local-project hydration it also records manifest, optional lockfile, and clean source identity separately; it is not a historical query observation or a substitute for a later config-file read. - the same selected Node hydration lane can also record
runtime:nodewith the contract-localnode --versionobserved while authoring the receipt. This is intentionally a runtime-version observation, not an executable or image digest. backendworkflow_env_artifactswhen the selected or effective workflow owns one rendered env artifact; each entry reportspath,kind,profile,includes,exists, and the consuming task/service laneslifecycleimagewhen container execution is selectedtargetwhen the recorded execution phase had a real named target, such as a persistent container, a named ephemeral task or diagnosis container, or a remote targetnative_prerequisiteswhen the selected execution path uses native task prerequisites; each entry can include additivecheck,activation,requires,provisioning, andnotefieldsnative_prerequisites[*].requiresrecords runtime, tool, toolchain, env, and check dependencies that came fromnative_prerequisites.<name>.platforms.<os>.requires, with asourcemarker so automation does not confuse native-bundle dependencies with direct task requirementsnative_prerequisites[*].activation.appliedtells you whether this command actually ran inside the declared native activation; preview and read-only receipt paths can still report the declared activation withapplied: falseexecution_conflict.reasons[]when the recorded failure was blocked by active execution ownership; entries use typed identities such asactive_execution_present,host_service,compose_project,persistent_backend_family,env_materialization_path, andservice_task
ok mirrors the current repo receipt readiness result, so blocked repo receipts still return the
receipt success shape with ok: false.
When --archive --promote-baseline is set, the receipt success shape also includes:
promoted_baseline.pathpromoted_baseline.archive_pathpromoted_baseline.promoted_at
ota receipt --json --snapshot
ota receipt --json --snapshot reads one archived normalized semantic contract snapshot without
forcing operators to infer that truth indirectly through ota diff or receipt-correlation output.
Selection supports:
latest: newest valid archived repo receipt for the same contract under.ota/receiptspromoted: the explicit promoted repo baseline pointer under.ota/receipts/repo-baseline.json- an explicit archived repo receipt JSON file path
- an explicit archived normalized snapshot JSON file path under
.ota/contracts
{
"ok": true,
"path": "/abs/path/to/ota.yaml",
"mode": "snapshot",
"summary": {
"input_count": 1,
"assumption_count": 3
},
"source": "latest",
"selection_kind": "receipt_archive",
"archive_path": ".ota/receipts/repo-receipt-20260621-101010-123Z.json",
"archived_at": "2026-06-21T10:10:10.123Z",
"snapshot_hash": "sha256:5dc5c7f6e0bf...",
"assumption_set_hash": "sha256:9ab21d35d3ef...",
"snapshot_path": ".ota/contracts/sha256-5dc5c7f6e0bf....json",
"contract": {
"contract": "/abs/path/to/ota.yaml",
"contract_identity": "ota.yaml"
},
"snapshot": {
"version": 1,
"project": {
"name": "receipt-demo"
},
"tasks": {
"setup": {
"run": "echo ready"
}
}
}
}
Snapshot JSON fields:
okpathmode(snapshot)summary.input_countsummary.assumption_countsource(latest,promoted, orfile)selection_kind(receipt_archiveorsnapshot_archive)selection_pathwhen the caller selected an explicit file or promoted pointerarchive_pathwhen the resolved selection came from a receipt archive or direct snapshot filearchived_atandpromoted_atwhen the resolved selection came through archived receipt statesnapshot_hashassumption_set_hashsnapshot_pathcontract.contractwhen the resolved receipt carried the source contract pathcontract.contract_identitywhen the resolved receipt carried compact contract identitysnapshotwith the normalized archived semantic contract JSON
ota receipt --json --baseline
ota receipt --json --baseline compares the current repo receipt against either:
promoted: the explicit promoted repo baseline under.ota/receipts/repo-baseline.jsonlatest: the newest valid archived repo receipt for the same contract under.ota/receipts- an explicit repo receipt JSON file path
The compare path is read-only. It does not rerun the baseline receipt, does not archive the
current receipt automatically, and exits 0 when comparison succeeds even if the current receipt
or baseline receipt is not ready. Add --fail-on-new-blockers when you want compare mode to exit
1 after a successful diff whenever the current receipt introduces new blocker findings.
{
"ok": false,
"path": "/abs/path/to/ota.yaml",
"mode": "diff",
"baseline": {
"source": "promoted",
"selection_path": "/abs/path/to/.ota/receipts/repo-baseline.json",
"archive_path": "/abs/path/to/.ota/receipts/repo-receipt-20260412-101010-123Z.json",
"archived_at": "2026-04-12T10:10:10.123Z",
"promoted_at": "2026-04-12T10:20:30.456Z",
"contract_identity": "ota.yaml",
"contract_snapshot_hash": "sha256:1111111111111111111111111111111111111111111111111111111111111111",
"contract_snapshot_ref": ".ota/contracts/sha256-1111111111111111111111111111111111111111111111111111111111111111.json",
"assumption_set_hash": "sha256:3333333333333333333333333333333333333333333333333333333333333333",
"contract_identity_details": {
"project": {
"name": "receipt-diff"
},
"counts": {
"tasks": 1
}
},
"ok": false,
"contract": "/abs/path/to/ota.yaml",
"backend": "native",
"summary": {
"error_count": 2,
"warn_count": 0,
"info_count": 0,
"step_count": 1
}
},
"current": {
"ok": false,
"contract": "/abs/path/to/ota.yaml",
"contract_identity": "ota.yaml",
"contract_snapshot_hash": "sha256:2222222222222222222222222222222222222222222222222222222222222222",
"assumption_set_hash": "sha256:4444444444444444444444444444444444444444444444444444444444444444",
"contract_identity_details": {
"project": {
"name": "receipt-diff"
},
"counts": {
"env": 1,
"tasks": 1
}
},
"backend": "native",
"summary": {
"error_count": 2,
"warn_count": 0,
"info_count": 0,
"step_count": 1
}
},
"summary": {
"baseline_ok": false,
"current_ok": false,
"comparison": {
"baseline_identity_label": "ota.yaml",
"current_identity_label": "ota.yaml",
"identity_changed": false,
"readiness_change": "unchanged",
"correlation": "likely_related",
"contract_snapshot_changed": true
},
"introduced": {
"count": 1,
"error_count": 1,
"warn_count": 0,
"info_count": 0
},
"resolved": {
"count": 1,
"error_count": 1,
"warn_count": 0,
"info_count": 0
},
"unchanged": {
"count": 1,
"error_count": 1,
"warn_count": 0,
"info_count": 0
}
},
"contract_changes": [
{
"path": "env.vars.OTA_BASELINE_REQUIRED.required",
"status": "add",
"category": "env",
"risk": "medium"
}
],
"likely_related_changes": [
{
"path": "env.vars.OTA_BASELINE_REQUIRED.required",
"status": "add",
"category": "env",
"risk": "medium"
}
],
"gate": {
"rule": "fail_on_new_blockers",
"passed": false,
"new_blocker_count": 1,
"blocking_summary": "Missing environment variable: OTA_BASELINE_REQUIRED",
"blocking_next": "set `OTA_BASELINE_REQUIRED`, then rerun `ota doctor`",
"blocking_provenance": "repo contract",
"blocking_provenance_key": "repo_contract"
},
"introduced": [
{
"summary": "Missing environment variable: OTA_BASELINE_REQUIRED"
}
],
"resolved": [
{
"summary": "Missing tool: old-tool"
}
],
"unchanged": [
{
"summary": "No tasks defined in contract"
}
]
}
Current receipt diff JSON fields:
ok(current receipt readiness, not diff success/failure)pathmode(diff)baseline.source(promoted,latest, orfile)baseline.selection_pathwhen compare selection came from a promoted baseline pointer or explicit file pathbaseline.archive_pathbaseline.archived_at(when the baseline file name encodes an archived timestamp)baseline.promoted_atwhen compare selection came from a promoted baseline pointerbaseline.contract_identitywhen ota can resolve the repo-local contract identity for the selected baselinebaseline.contract_snapshot_hashwhen the archived baseline carries normalized semantic contract identitybaseline.contract_snapshot_refwhen the archived baseline points at a normalized archived contract snapshot under.ota/contractsbaseline.assumption_set_hashwhen the archived baseline carries canonical extracted assumption-set identitybaseline.evaluated_inputs[]when the archived baseline captured immutable evaluated inputsbaseline.contract_identity_detailswith the compact declared contract identity when the archived receipt recorded itbaseline.okbaseline.contractbaseline.backend/baseline.lifecyclewhen recordedbaseline.summarycurrent.okcurrent.contractcurrent.contract_identitywith the current repo-local contract identitycurrent.contract_snapshot_hashwith the normalized semantic contract hash for the current in-memory contract truth, even when the current receipt is not archivedcurrent.assumption_set_hashwith the canonical extracted assumption-set identity for the current in-memory contract truthcurrent.evaluated_inputs[]with immutable inputs captured while Ota authored the current receiptcurrent.contract_identity_detailswith the compact declared contract identity for the current receiptcurrent.backend/current.lifecyclewhen recordedcurrent.summarysummary.baseline_oksummary.current_ok- additive
summary.comparisonwith baseline/current identity labels plus compactidentity_changed,readiness_change, andcontract_snapshot_changeddrift signals summary.comparison.replaynames the selected workflow/runtime scope and replay trust posture. A plainota receipt --baselinecomparison is alwayswitness_onlywithhermeticity: unassessed: it compares captured witnesses but does not execute the lane, so it must not be read as replay verification.summary.comparison.artifact_trust[]only for artifacts captured on both sides; each record names its input class, immutable identities, comparison state, and runner-derived trust role. The first shipped record issemantic_contract_snapshot, which isacquittingonly for the namedcontract_truthclass when its identities match, never for dependency, environment, runtime, or external-world inputs that the receipts did not capture. A matching cleansource:git_headrecord isacquittingonly for the namedsource_identityclass; dirty or non-git worktrees do not emit that record. A matching captured typed Node lockfile is likewiseacquittingonly fordeclared_dependency_resolution. A matching static digest-pinned Compose image isacquittingonly for the namedselected_runtime_artifactinput; it does not clear other runtime, environment, or external state classes. A matchingpresentation_profilereplay input isacquittingonly for the declaredexecution_presentation_profileclass on that lane. A matchingcomparator_profilereplay input isnarrowingforcomparator_semantics: it proves the comparator definition held still, but not that the whole runtime presentation was hermetic. A matchingruntime:noderecord isnarrowingforselected_runtime_version: it rules out a reported Node version change, but does not prove binary, image, host, environment, or external state identity.summary.comparison.correlationwith an explicit advisory verdict:likely_relatedwhen ota can correlate newly introduced blocker findings to specific semantic contract changespossibly_relatedwhen ota cannot recover a strong direct match but the newly introduced blocker and contract drift still overlap in the same broad contract familyno_clear_correlationwhen ota cannot recover even that coarse overlap honestly
summary.introducedsummary.resolvedsummary.unchanged- additive
contract_changes[]with semantic normalized contract diff entries between the archived baseline snapshot and the current contract truth when the baseline carriesreceipt.contract_snapshot_ref - additive
likely_related_changes[]when ota can correlate one or more newly introduced blocker findings to those semantic contract changes- ordering prefers the sharpest declared semantic owner or named reference ota can recover
honestly, for example reusable
surfaces.<name>orreadiness.probes.<name>, before broader adjacent workflow-family drift - when several nearby semantic changes tie on directness, ota then prefers the change whose governance stage best matches the failure lane, so check failures prefer verify-lane drift, setup blockers prefer prepare-lane drift, and readiness/proof failures prefer proof-lane drift
- ordering prefers the sharpest declared semantic owner or named reference ota can recover
honestly, for example reusable
gate.rule,gate.passed, andgate.new_blocker_countwhen--fail-on-new-blockersis active- additive
gate.blocking_summary,gate.blocking_next, and provenance fields when the gate is blocked by at least one newly introduced error introduced[]resolved[]unchanged[]
ota receipt --json --history
ota receipt --json --history is the read-only archive index for repo receipts already written to
.ota/receipts. It does not rerun diagnosis; it lists archived receipt files newest first.
{
"ok": true,
"path": "/abs/path/to/repo",
"mode": "history",
"summary": {
"archive_count": 2,
"invalid_archive_count": 1
},
"archives": [
{
"archive_path": "/abs/path/to/.ota/receipts/repo-receipt-20260412-091512-142Z.json",
"archived_at": "2026-04-12T09:15:12.142Z",
"ok": false,
"contract": "/abs/path/to/ota.yaml",
"backend": "native",
"summary": {
"error_count": 1,
"warn_count": 0,
"info_count": 0,
"step_count": 1
}
}
],
"invalid_archives": [
{
"archive_path": "/abs/path/to/.ota/receipts/repo-receipt-20260412-090000-000Z.json",
"error": "failed to parse receipt archive `./.ota/receipts/repo-receipt-20260412-090000-000Z.json`: EOF while parsing a value at line 1 column 10"
}
]
}
Current receipt history JSON fields:
okpath(resolved repo boundary for the archive read)mode(history)summary.archive_countsummary.invalid_archive_countarchives[]archives[].archive_patharchives[].archived_atarchives[].okarchives[].contractarchives[].backend(when the archived receipt recorded one)archives[].provider(when the archived receipt recorded one)archives[].lifecycle(when the archived receipt recorded one)archives[].cwd(when the archived receipt recorded one)archives[].summaryinvalid_archives[]when malformed archive files were skippedinvalid_archives[].archive_pathinvalid_archives[].error
When --member <name> is set against a monorepo root, receipt.contract points at the selected
member contract path while the readiness findings reflect the merged member target.
When the receipt comes from remote execution, receipt.provider names the remote transport,
receipt.target names the resolved remote boundary, and receipt.cwd carries the declared remote
working directory when one exists.
Use ota receipt --json when you need a deterministic repo-local artifact for the current
readiness state without provisioning, starting services, or writing repo files.
Add --archive to persist the JSON receipt in .ota/receipts for later audit; ota keeps
the newest 50 archives.
ota clean --json
ota clean --json reports repo-scoped cleanup counters for the selected contract target. When
the target is a monorepo root with workspace members, ota returns a root cleanup report plus
member reports. When cleanup fails, ota returns either a classified cleanup failure with
machine-readable engine and reason fields, or a generic repo-state failure when ota cannot
honestly classify the issue as a cleanup engine/resource problem.
Single-target success:
{
"ok": true,
"path": "./ota.yaml",
"summary": {
"removed_current_persistent_containers": 1,
"removed_drift_persistent_containers": 0,
"removed_drift_attached_containers": 0,
"removed_current_dependency_isolation_volumes": 1,
"removed_drift_dependency_isolation_volumes": 0,
"skipped_ambiguous_persistent_containers": 0,
"skipped_ambiguous_dependency_isolation_volumes": 0,
"stopped_host_services": 0,
"total_removed": 2,
"total_skipped_ambiguous": 0
},
"host_services": [],
"queried_engines": [
"docker"
]
}
Monorepo root success:
{
"ok": true,
"path": "./ota.yaml",
"workspace": {
"root": {
"ok": true,
"path": "./ota.yaml",
"summary": {
"removed_current_persistent_containers": 0,
"removed_drift_persistent_containers": 0,
"removed_drift_attached_containers": 0,
"removed_current_dependency_isolation_volumes": 0,
"removed_drift_dependency_isolation_volumes": 0,
"skipped_ambiguous_persistent_containers": 0,
"skipped_ambiguous_dependency_isolation_volumes": 0,
"stopped_host_services": 0,
"total_removed": 0,
"total_skipped_ambiguous": 0
},
"host_services": [],
"queried_engines": [
"docker"
]
},
"members": [
{
"member": "api",
"report": {
"ok": true,
"path": "./ota.yaml#api",
"summary": {
"removed_current_persistent_containers": 1,
"removed_drift_persistent_containers": 0,
"removed_drift_attached_containers": 0,
"removed_current_dependency_isolation_volumes": 0,
"removed_drift_dependency_isolation_volumes": 0,
"skipped_ambiguous_persistent_containers": 0,
"skipped_ambiguous_dependency_isolation_volumes": 0,
"stopped_host_services": 0,
"total_removed": 1,
"total_skipped_ambiguous": 0
},
"host_services": [],
"queried_engines": [
"docker"
]
}
}
]
}
}
Failure:
{
"ok": false,
"path": "./ota.yaml",
"summary": "Container engine unavailable",
"error": "task `clean` could not list dependency-isolation volume `dev.ota.repo=repo-1` using container engine `podman`: Cannot connect to Podman",
"why": "`ota clean` needs Podman to remove dependency-isolation repo state for `dev.ota.repo=repo-1`, but Podman is not reachable.",
"next": [
"start Podman and rerun `ota clean`",
"run `podman system connection list`",
"if needed, run `podman machine init` and `podman machine start`"
],
"reason": "engine_unavailable",
"engine": "podman",
"action": "list",
"resource_kind": "dependency_isolation_volume",
"resource_name": "dev.ota.repo=repo-1",
"details": "unable to connect to Podman socket: dial tcp 127.0.0.1:57990: connect: connection refused"
}
Generic repo-state failure:
{
"ok": false,
"path": "./ota.yaml",
"summary": "Cleanup failed",
"error": "projection `dev.http.host.port` must declare either `fixed` or `auto`"
}
ota clean --stale --json
ota clean --stale --json is contract-free. It reports exited ota-managed containers that match
the local cleanup scan and tells automation whether the command removed them or only previewed
them. If no local container engine can be queried, ota returns the same structured cleanup failure
shape used by ota clean --json instead of this success shape.
{
"ok": true,
"scope": "stale",
"dry_run": false,
"engines": [
"docker"
],
"summary": {
"matched_count": 2,
"removed_count": 2,
"would_remove_count": 0
},
"containers": [
{
"engine": "docker",
"name": "ota-a6be4471a4598386",
"ownership": "label"
},
{
"engine": "docker",
"name": "ota-legacydeadbeef",
"ownership": "legacy_name"
}
]
}
ownership is label for containers matched through ota's management labels and
legacy_name for older ota-* containers that predate labels.
ota detect --json
ota detect --merge --json --dry-run currently uses the same success shape as ota detect --json --dry-run, but requires an existing contract and includes comparison.
ota detect --merge --json uses the same success shape with:
written: truewhen additive high-confidence fields were appliedwritten: falsewhen there was nothing eligible to addconfigis the detected candidate contract for preview-only results; when a detect write mode succeeds (--write,--merge, or--rewritewithwritten: true),configis the exact contract ota wrote to disk, includingmetadata.ota.detect.field_ownershipandmetadata.ota.detect.field_admissionconfig.metadata.ota.detect.field_admission[*]isdirectwhen ota wrote the field from direct high-confidence detector evidence andpromotedwhen ota admitted the field through the conservative detect-write promotion policyconfig.metadata.ota.detect.field_source_class[*]records the detector-governance class ota associated with each detect-owned field it wrote, such astask_commandorenvironment_toolchaincomparisondescribing detected adds and updates against the existing contractcomparison.removalsdescribing stale contract fields that are no longer detected in the repocomparison.changes[*].ownershipisrepo_signalsfor add candidates andrepo_contractfor updates against existing fieldscomparison.removals[*].ownershipisrepo_contractbecause those entries describe stale declared contract datacomparison.changes[*].owner_kindisdetectedfor add candidates,manualfor default hand-authored existing fields, andmergedwhen ota previously wrote the field and recorded it undermetadata.ota.detect.field_ownershipcomparison.removals[*].owner_kindismergedon normal drift surfaces, while rewrite preview can also surfacemanualremovals because a full replacement would drop those fieldscomparison.*.provenancepreserves the stable machine labelrepo_signalscomparison.*.provenance_keyis the stable machine labelrepo_signalscomparison.changes[*].source,comparison.changes[*].source_class, andcomparison.changes[*].confidencecopy the detector evidence for that proposed add or update so consumers do not need to join back toinferred[*]comparisonmay include lower-confidence add candidates that remain preview-onlytoolchain_opportunitiesappears only when repo signals strongly suggest a managed ecosystem that ota still models through lower-levelruntimes/toolsdeclarations because no shipped provider contract owns it yet
{
"ok": true,
"path": "/abs/path/to/ota.yaml",
"written": false,
"config": {
"version": 1
},
"comparison": {
"existing_contract": true,
"changes": [
{
"field": "project.name",
"status": "update",
"existing": "existing",
"detected": "ota-web",
"owner_kind": "manual",
"ownership": "repo_contract",
"provenance": "repo_signals",
"provenance_key": "repo_signals",
"source": "package.json#name",
"confidence": "high"
}
],
"removals": [
{
"field": "tools.cargo",
"existing": "1.78",
"owner_kind": "merged",
"ownership": "repo_contract",
"provenance": "repo_signals",
"provenance_key": "repo_signals"
}
]
},
"inferred": [
{
"field": "runtimes.node",
"value": "22",
"source": ".nvmrc",
"confidence": "high"
}
]
}
In conservative mixed-repo or legacy-repo cases, comparison.changes can still include add
entries while written remains false. That means ota found possible additions, but none were
eligible for automatic merge under the current high-confidence-only rule.
Failure example:
{
"ok": false,
"path": "./ota.yaml",
"written": false,
"error": "`./ota.yaml` already exists; refusing to overwrite an existing contract\n\nNext:\n▸ review detected changes with `ota detect --merge --dry-run .`",
"next": "ota detect --merge --dry-run ."
}
ota workspace up --json
{
"ok": true,
"path": "/abs/path/to/ota.workspace.yaml",
"summary": {
"error_count": 0,
"warn_count": 0,
"info_count": 0,
"step_count": 4
},
"receipt": {
"ok": true,
"path": "/abs/path/to/ota.workspace.yaml",
"scope": "workspace",
"contract": "/abs/path/to/ota.workspace.yaml",
"contract_identity": {
"version": 1,
"project": {
"name": "ota-dev",
"type": "workspace"
},
"counts": {
"runtimes": 0,
"tools": 0,
"env": 0,
"services": 0,
"checks": 0,
"tasks": 0,
"repos": 1,
"policies": 0
}
},
"workspace": "ota-dev",
"env_sources": [
{
"name": "OTA_TEST_SHARED",
"value": "workspace-policy",
"source": "workspace policy"
}
],
"steps": [
{
"order": 1,
"label": "web",
"status": "READY",
"detail": "service `db`"
}
],
"summary": {
"error_count": 0,
"warn_count": 0,
"info_count": 0,
"step_count": 4
}
},
"repos": [
{
"name": "web",
"path": "/abs/path/to/apps/web",
"contract_path": "/abs/path/to/apps/web/ota.yaml",
"required": true,
"ok": true,
"status": "READY",
"phase": "post-up diagnosis",
"findings": []
}
]
}
ota workspace refresh --json uses the same workspace roll-up shape, but reports refresh
status for existing repos instead of bootstrap status for missing ones. It switches mode
between "preview" and "refresh", and the dedicated schema is workspace-refresh.json.
It always includes the shared workspace receipt, and in preview mode it does not mutate repo state.
Workspace repo items may also include additive next and next_steps when ota can name that
repo's current follow-up lane directly.
ota workspace diff --json uses a read-only workspace diff roll-up. It reports local git
state against the declared source ref or upstream branch, includes per-repo status,
drift_kind, target_source, branch, head, target_ref, ahead, behind, and dirty
fields, and adds "mode": "diff". Additive top-level next and next_steps are present when
ota can name the safest acquisition or refresh follow-up lane directly, and per-repo items can
also carry additive next and next_steps. summary now also breaks the previously collapsed
missing and unresolved buckets into additive missing_repo_count, missing_contract_count,
target_unavailable_count, and comparison_unresolved_count.
When ota workspace diff --json --progress-json is used, Ota also emits live workspace progress
events as compact one-line JSON on stderr. Those progress events use the same
workspace_progress event shape as ota workspace doctor|check|status|receipt|run|up|refresh --json --progress-json,
use status for the repo diff verdict such as MATCH, DIRTY, or UNRESOLVED, and use tail
for the machine drift_kind, while the final workspace-diff report remains the single JSON
document on stdout.
ota workspace status --json uses the operational workspace roll-up. It reports readiness and
local git drift together, includes per-repo ready, readiness_status, drift_status,
drift_kind, target_source, branch, head, target_ref, ahead, behind, and dirty
fields, and adds "mode": "status". Additive top-level next and next_steps are present when
ota can name the safest doctor, acquisition, or refresh follow-up lane directly, and per-repo
items can also carry additive next, next_steps, and workflow. target_source is declared_ref when
the comparison target came from repos.<name>.source.ref and upstream_branch when ota fell
back to the repo's configured upstream branch. summary now also breaks the previously collapsed
missing and unresolved buckets into additive missing_repo_count, missing_contract_count,
target_unavailable_count, and comparison_unresolved_count.
When ota workspace status --json --progress-json is used, Ota also emits live workspace progress
events as compact one-line JSON on stderr. Those progress events use the same
workspace_progress event shape as ota workspace doctor|check|run|up|refresh --json --progress-json,
and use tail for the repo drift state such as MATCH, DIRTY, or UNRESOLVED, while the
final workspace-status report remains the single JSON document on stdout.
ota workspace execution plan --json uses a read-only execution roll-up. It reports one
resolved or unresolved execution decision per selected repo, includes per-repo
contract_identity, declared_execution, resolved, error, and next fields when present,
adds additive per-repo workflow and task when workflow planning is selected, and adds
"mode": "execution-plan".
ota workspace receipt --json uses the same scan as status, but packages the result as a
receipt artifact. It records the same readiness and drift detail, adds "mode": "receipt", and
keeps the receipt object available for CI or archive consumers. When --archive is set,
the output also includes archive_path pointing at the persisted receipt JSON.
When ota workspace receipt --json --progress-json is used, Ota also emits live workspace
progress events as compact one-line JSON on stderr. Those progress events use the same
workspace_progress event shape as ota workspace doctor|check|status|run|up|refresh --json --progress-json,
and because receipt reuses the same scan as workspace status, tail carries the repo drift
state such as MATCH, DIRTY, or UNRESOLVED, while the final workspace-receipt report remains
the single JSON document on stdout.
summary mirrors the top-level execution receipt summary and lets hosted consumers read the roll-up
without opening receipt first.
receipt.contract_identity uses the same compact identity block as repo execution receipts, but
identifies the workspace contract with project.type = "workspace" and workspace-level repos /
policies counts.
When an execution receipt includes next, additive receipt.next_steps carries the same
follow-up lane as an ordered string array so agents and CI do not need to split the human review
string themselves.
When a receipt is tied to a selected task path, additive receipt.dependency_steps[] records the
executed dependency-plane truth for each task step using the same structured shape as preview
plan.dependency_steps[], so archived receipts preserve which backend-selection lane actually won
during execution instead of only the flattened task order.
Optional per-repo fields:
servicetaskexit_codestdoutstderrenv_sourcesreadyreadiness_statusdrift_statusbranchheadtarget_refaheadbehinddirtymode(previewforota workspace refresh --dry-run,diffforota workspace diff --json,statusforota workspace status --json,receiptforota workspace receipt --json,execution-planforota workspace execution plan --json)
Example acquisition/setup failure:
{
"ok": false,
"path": "/abs/path/to/ota.workspace.yaml",
"repos": [
{
"name": "web",
"required": true,
"ok": false,
"status": "ACQUIRE FAILED",
"phase": "acquisition",
"findings": [
{
"severity": "error",
"summary": "Repo acquisition failed: web",
"why": "...",
"next": "..."
}
],
"exit_code": 128,
"stderr": "fatal: ..."
}
]
}
Example with inferred Docker Compose services:
{
"ok": true,
"path": "/abs/path/to/ota.yaml",
"written": false,
"config": {
"version": 1,
"project": {
"name": "docker-legacy"
},
"services": {
"db": {
"manager": {
"kind": "compose",
"name": "docker-legacy",
"file": "docker-compose.yml",
"service": "db"
},
"readiness": {
"kind": "compose_health"
}
}
}
},
"inferred": [
{
"field": "services.db.manager.kind",
"type": "service",
"value": "compose",
"source": "docker-compose.yml#services.db",
"signal": "config",
"confidence": "high"
},
{
"field": "services.db.manager.name",
"type": "service",
"value": "docker-legacy",
"source": "directory-name",
"signal": "convention",
"confidence": "high"
},
{
"field": "services.db.readiness.kind",
"type": "service",
"value": "compose_health",
"source": "docker-compose.yml#services.db.healthcheck.test",
"signal": "config",
"confidence": "high"
}
]
}
Example with inferred Docker Compose host topology from one deterministic host-published TCP port candidate:
{
"ok": true,
"path": "/abs/path/to/ota.yaml",
"written": false,
"config": {
"version": 1,
"project": {
"name": "ota-containerized-web"
},
"execution": {
"default_context": "host",
"contexts": {
"host": {
"backend": "native"
}
}
},
"services": {
"web": {
"manager": {
"kind": "compose",
"name": "docker-heavy-node",
"file": "docker-compose.yml",
"service": "web"
},
"endpoints": {
"host": {
"address": "127.0.0.1",
"port": 3000
}
},
"readiness": {
"from": "host",
"kind": "tcp"
}
}
}
},
"inferred": [
{
"field": "execution.default_context",
"type": "execution",
"value": "host",
"source": "docker-compose.yml#services.web.ports[0]",
"signal": "config",
"confidence": "high"
},
{
"field": "execution.contexts.host.backend",
"type": "execution",
"value": "native",
"source": "docker-compose.yml#services.web.ports[0]",
"signal": "config",
"confidence": "high"
},
{
"field": "services.web.readiness.kind",
"type": "service",
"value": "tcp",
"source": "docker-compose.yml#services.web.ports[0]",
"signal": "config",
"confidence": "high"
}
]
}
When a Compose service exposes multiple deterministic host-published TCP port candidates, Ota now
keeps the inferred host execution slice and emits named endpoints such as
services.<name>.endpoints.host_3000.context: host, while intentionally omitting
services.<name>.readiness.endpoint when no single truthful readiness target can be selected.