Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ DECIDED_AT := 2026-05-04T12:51:00Z
PYCLI := PYTHONPATH=src $(PYTHON) -m agent_machine.cli
PYMOD := PYTHONPATH=src $(PYTHON) -m

validate: validate-no-merge-duplication validate-json validate-yaml validate-quadlet validate-render validate-evidence validate-governance validate-policy-fabric validate-agent-registry validate-superconscious-runtime-plan validate-activation validate-attestation validate-supply-chain validate-release-bundle validate-sourceos-projections validate-package validate-cli validate-formula validate-runtime-install-receipts validate-artifact-digest-honesty validate-consent-before-staging validate-inference-receipt
validate: validate-no-merge-duplication validate-json validate-yaml validate-quadlet validate-render validate-evidence validate-governance validate-policy-fabric validate-agent-registry validate-superconscious-runtime-plan validate-activation validate-attestation validate-supply-chain validate-release-bundle validate-sourceos-projections validate-package validate-cli validate-formula validate-runtime-install-receipts validate-artifact-digest-honesty validate-consent-before-staging validate-inference-receipt validate-adapter-ipc

validate-adapter-ipc:
$(PYTHON) scripts/validate-adapter-ipc.py

validate-no-merge-duplication:
$(PYTHON) scripts/validate-no-merge-duplication.py
Expand Down
166 changes: 166 additions & 0 deletions contracts/adapter-ipc.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://schemas.sourceos.dev/agent-machine/adapter-ipc.schema.json",
"title": "AdapterIpcMessage",
"description": "M2 Adapter IPC v0.1 — JSON-over-stdio (NDJSON) messages between the contract-runner and a backend adapter subprocess. Each stdin/stdout line is exactly one of these objects. Provenance: 'M2 Adapter IPC Spec v0.1' + 'M2 Protocol Versioning & Compatibility Spec v0.1' (SourceOS Spec intake 2026-07-31). stderr carries human logs only and is not modelled here.",
"type": "object",
"properties": {},
"oneOf": [
{ "$ref": "#/$defs/requestEnvelope" },
{ "$ref": "#/$defs/responseEnvelope" }
],
"$defs": {
"correlationId": {
"type": "string",
"minLength": 1,
"description": "uuid-or-monotonic correlation id; a response MUST echo the request id."
},
"protocolVersion": {
"type": "string",
"pattern": "^[0-9]+\\.[0-9]+$",
"description": "MAJOR.MINOR semantic protocol version."
},
"op": {
"type": "string",
"enum": [
"hello",
"info",
"lock_validate",
"lock_hash",
"env_realize",
"task_run",
"deps_inventory",
"lock_update",
"env_shell"
]
},
"role": {
"type": "string",
"enum": ["dev", "build", "ci", "release", "audit"]
},
"errorCode": {
"type": "string",
"description": "Canonical error-code registry (Protocol Versioning Spec v0.1 §4.2). Unknown codes MUST be rejected.",
"enum": [
"E_PROTOCOL_INCOMPATIBLE",
"E_HELLO_REQUIRED",
"E_CAPABILITY_MISSING",
"E_LOCK_MISSING",
"E_LOCK_INVALID",
"E_LOCK_HASH_FAILED",
"E_LOCK_UPDATE_DENIED",
"E_ENV_REALIZE_FAILED",
"E_ENV_FLOATING_DISALLOWED",
"E_ENV_NOT_FOUND",
"E_TASK_UNKNOWN",
"E_TASK_FAILED",
"E_TASK_TIMEOUT",
"E_CMD_FAILED",
"E_PATH_ESCAPE",
"E_WRITE_DENIED",
"E_DEPS_INVENTORY_FAILED",
"E_LICENSE_EVIDENCE_MISSING",
"E_UNSUPPORTED",
"E_INTERNAL"
]
},
"errorObject": {
"type": "object",
"required": ["code", "message"],
"properties": {
"code": { "$ref": "#/$defs/errorCode" },
"message": { "type": "string", "minLength": 1 },
"details": { "type": "object" }
},
"additionalProperties": true
},
"laneConfig": {
"type": "object",
"required": ["lane_id", "platform", "arch", "backend", "lock_ref", "containerized"],
"properties": {
"lane_id": { "type": "string", "minLength": 1 },
"platform": { "type": "string", "enum": ["macos", "linux"] },
"arch": { "type": "string", "enum": ["arm64", "amd64"] },
"backend": { "type": "string", "enum": ["pixi", "nix", "devbox", "mise", "bazel"] },
"lock_ref": { "type": "string", "minLength": 1 },
"containerized": { "type": "boolean" },
"container": {
"type": "object",
"required": ["image", "runtime"],
"properties": {
"image": {
"type": "string",
"pattern": "^.+@sha256:[0-9a-f]{64}$",
"description": "Container image pinned by digest (name@sha256:<hex64>)."
},
"runtime": { "type": "string", "enum": ["podman", "docker"] },
"mount_repo_to": { "type": "string" },
"workdir": { "type": "string" }
},
"additionalProperties": true
}
},
"additionalProperties": true,
"allOf": [
{
"if": { "properties": { "containerized": { "const": true } } },
"then": { "required": ["container"] }
}
]
},
"requestEnvelope": {
"type": "object",
"required": ["id", "op"],
"properties": {
"id": { "$ref": "#/$defs/correlationId" },
"op": { "$ref": "#/$defs/op" },
"lane": { "$ref": "#/$defs/laneConfig" },
"role": { "$ref": "#/$defs/role" },
"task": { "type": "string" },
"ci_mode": { "type": "boolean" },
"protocol_version": { "$ref": "#/$defs/protocolVersion" },
"runner": {
"type": "object",
"properties": {
"name": { "type": "string" },
"version": { "type": "string" },
"ci_mode": { "type": "boolean" }
},
"additionalProperties": true
},
"required_capabilities": { "type": "array", "items": { "type": "string" } },
"optional_capabilities": { "type": "array", "items": { "type": "string" } },
"params": { "type": "object" }
},
"additionalProperties": true,
"allOf": [
{
"if": { "properties": { "op": { "const": "hello" } }, "required": ["op"] },
"then": { "required": ["protocol_version"] }
}
]
},
"responseEnvelope": {
"type": "object",
"required": ["id", "ok", "errors"],
"properties": {
"id": { "$ref": "#/$defs/correlationId" },
"ok": { "type": "boolean" },
"result": { "type": ["object", "null"] },
"errors": { "type": "array", "items": { "$ref": "#/$defs/errorObject" } },
"warnings": { "type": "array", "items": { "type": "string" } }
},
"additionalProperties": true,
"allOf": [
{
"if": { "properties": { "ok": { "const": false } }, "required": ["ok"] },
"then": { "properties": { "errors": { "minItems": 1 } } }
},
{
"if": { "properties": { "ok": { "const": true } }, "required": ["ok"] },
"then": { "properties": { "errors": { "maxItems": 0 } } }
}
]
}
}
}
42 changes: 42 additions & 0 deletions docs/architecture/adapter-ipc-contract.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# M2 Adapter IPC Contract (JSON-over-stdio)

The contract-runner talks to backend adapters (pixi/nix/devbox/mise/bazel) over a
stable **JSON-over-stdio (NDJSON)** protocol: one JSON object per line on stdin
(requests) and stdout (responses); stderr is human logs only. This keeps the runner
small and lets adapters be written in any OSS language.

## Where it lives

| Artifact | Path |
|---|---|
| Schema (draft 2020-12) | `contracts/adapter-ipc.schema.json` |
| Conformance fixtures (accept) | `fixtures/protocol/*.ndjson` |
| Conformance fixtures (reject) | `fixtures/protocol/_reject/*.ndjson` |
| Validator (teeth) | `scripts/validate-adapter-ipc.py` (`make validate-adapter-ipc`) |

## What the contract enforces

- **Envelopes.** A message is exactly one of a *request* (`op`) or a *response* (`ok`),
each carrying a non-empty string `id`; a response MUST echo its request's `id`.
- **Operations.** `op ∈ {hello, info, lock_validate, lock_hash, env_realize, task_run,
deps_inventory, lock_update, env_shell}`.
- **Handshake.** A `hello` request MUST declare a `MAJOR.MINOR` `protocol_version`.
- **ok/errors coupling.** `ok:false` ⇒ non-empty `errors`; `ok:true` ⇒ empty `errors`.
- **Error registry.** Every error `code` MUST be one of the canonical codes
(Protocol Versioning Spec §4.2); unknown codes are rejected.
- **Lane pinning.** A containerized lane MUST pin its image by digest
(`name@sha256:<hex64>`; SHA-256 is FIPS 180-4).

The validator proves the teeth both ways: every accept fixture validates, and every
reject fixture fails at least one check (a reject that passes fails the run).

## Provenance

Distilled from `M2 Adapter IPC Spec v0.1 (JSON-over-stdio, Subprocess Plugins)` +
`M2 Protocol Versioning & Compatibility Spec v0.1`, SourceOS Spec intake 2026-07-31
(source SHA-256 `59caac80044f046c4b5f37068ba70ab79c336b973f6714f12cdd91c5e7efd9f7`).

Deferred to follow-up (not in this slice): the fixture-replay **test harness** that
runs fixtures against a live adapter subprocess; capability-negotiation runtime
(`missing_required` fail-fast); the reference `pixi` adapter; MAJOR-bump
length-prefixed framing for streaming payloads.
2 changes: 2 additions & 0 deletions fixtures/protocol/_reject/bad_protocol_version.ndjson
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# expect: hello protocol_version must match MAJOR.MINOR
{"id":"r6","op":"hello","protocol_version":"one-point-oh","runner":{"name":"contract-runner","version":"0.1.0"}}
2 changes: 2 additions & 0 deletions fixtures/protocol/_reject/container_image_unpinned.ndjson
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# expect: containerized lane must pin container image by sha256 digest
{"id":"r8","op":"lock_hash","lane":{"lane_id":"linux-amd64-container","platform":"linux","arch":"amd64","backend":"pixi","lock_ref":"workstation.lock/pixi.linux-amd64.lock","containerized":true,"container":{"image":"debian:latest","runtime":"podman"}},"params":{}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# expect: hello request must declare protocol_version
{"id":"r7","op":"hello","runner":{"name":"contract-runner","version":"0.1.0"}}
2 changes: 2 additions & 0 deletions fixtures/protocol/_reject/missing_id.ndjson
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# expect: every message must carry a string id for correlation
{"ok":true,"result":{},"errors":[],"warnings":[]}
2 changes: 2 additions & 0 deletions fixtures/protocol/_reject/ok_false_empty_errors.ndjson
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# expect: ok=false must carry a non-empty errors array
{"id":"r1","ok":false,"result":null,"errors":[],"warnings":[]}
2 changes: 2 additions & 0 deletions fixtures/protocol/_reject/ok_true_with_errors.ndjson
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# expect: ok=true must carry an empty errors array
{"id":"r2","ok":true,"result":{"sha256":"0000000000000000000000000000000000000000000000000000000000000000"},"errors":[{"code":"E_INTERNAL","message":"should not be here"}],"warnings":[]}
2 changes: 2 additions & 0 deletions fixtures/protocol/_reject/unknown_error_code.ndjson
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# expect: error code not in the canonical registry
{"id":"r3","ok":false,"result":null,"errors":[{"code":"E_MADE_UP","message":"not a registered code"}],"warnings":[]}
2 changes: 2 additions & 0 deletions fixtures/protocol/_reject/unknown_op.ndjson
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# expect: op not in the allowed operation set
{"id":"r5","op":"delete_everything","params":{}}
2 changes: 2 additions & 0 deletions fixtures/protocol/deps_inventory_ok.ndjson
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{"id":"5","op":"deps_inventory","role":"ci","ci_mode":true,"lane":{"lane_id":"linux-amd64-container","platform":"linux","arch":"amd64","backend":"pixi","lock_ref":"workstation.lock/pixi.linux-amd64.lock","containerized":false},"params":{}}
{"id":"5","ok":true,"result":{"deps_path":".workstation/state/deps/20251220T2210Z/deps.json"},"errors":[],"warnings":[]}
2 changes: 2 additions & 0 deletions fixtures/protocol/hello_incompatible.ndjson
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{"id":"hello-9","op":"hello","protocol_version":"1.0","runner":{"name":"contract-runner","version":"0.1.0","ci_mode":true},"required_capabilities":["task_run"],"params":{"strict":true}}
{"id":"hello-9","ok":false,"result":null,"errors":[{"code":"E_PROTOCOL_INCOMPATIBLE","message":"Adapter does not support runner protocol_version 1.0","details":{"runner_requested":"1.0","adapter_supported":["0.9"]}}],"warnings":[]}
2 changes: 2 additions & 0 deletions fixtures/protocol/hello_ok.ndjson
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{"id":"hello-1","op":"hello","protocol_version":"1.0","runner":{"name":"contract-runner","version":"0.1.0","ci_mode":true},"required_capabilities":["task_run","lock_hash","lock_validate"],"optional_capabilities":["deps_inventory"],"params":{"strict":false}}
{"id":"hello-1","ok":true,"result":{"adapter":{"name":"pixi","version":"0.28.0","license":"MIT"},"protocol":{"min":"1.0","max":"1.0","supported":["1.0"]},"capabilities":{"supported":["task_run","lock_validate","lock_hash","env_realize"],"missing_required":[]},"config":{"pattern":"P1","container_execution":"runner_orchestrated"}},"errors":[],"warnings":[]}
2 changes: 2 additions & 0 deletions fixtures/protocol/lock_hash_ok.ndjson
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{"id":"2","op":"lock_hash","lane":{"lane_id":"linux-amd64-container","platform":"linux","arch":"amd64","backend":"pixi","lock_ref":"workstation.lock/pixi.linux-amd64.lock","containerized":true,"container":{"image":"debian@sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef","runtime":"podman","mount_repo_to":"/repo","workdir":"/repo"}},"role":"ci","ci_mode":true,"params":{}}
{"id":"2","ok":true,"result":{"sha256":"0000000000000000000000000000000000000000000000000000000000000000"},"errors":[],"warnings":[]}
2 changes: 2 additions & 0 deletions fixtures/protocol/task_run_fail.ndjson
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{"id":"4","op":"task_run","task":"build","role":"ci","ci_mode":true,"lane":{"lane_id":"linux-amd64-container","platform":"linux","arch":"amd64","backend":"pixi","lock_ref":"workstation.lock/pixi.linux-amd64.lock","containerized":true,"container":{"image":"debian@sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef","runtime":"podman"}},"params":{"timeout_seconds":1800}}
{"id":"4","ok":false,"result":null,"errors":[{"code":"E_TASK_FAILED","message":"build task exited non-zero","details":{"exit_code":2}}],"warnings":[]}
2 changes: 2 additions & 0 deletions fixtures/protocol/task_run_ok.ndjson
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{"id":"3","op":"task_run","task":"test","role":"ci","ci_mode":true,"lane":{"lane_id":"linux-amd64-container","platform":"linux","arch":"amd64","backend":"pixi","lock_ref":"workstation.lock/pixi.linux-amd64.lock","containerized":true,"container":{"image":"debian@sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef","runtime":"podman"}},"params":{"timeout_seconds":1800}}
{"id":"3","ok":true,"result":{"exit_code":0,"duration_ms":12345,"logs":{"stdout":".workstation/state/logs/20251220T2210Z/test.stdout","stderr":".workstation/state/logs/20251220T2210Z/test.stderr"}},"errors":[],"warnings":[]}
Loading
Loading