diff --git a/docs/contract-additions/immutable-node-profiles.md b/docs/contract-additions/immutable-node-profiles.md new file mode 100644 index 0000000..cb1b8dd --- /dev/null +++ b/docs/contract-additions/immutable-node-profiles.md @@ -0,0 +1,65 @@ +# Immutable Node Profile Contracts + +This contract addition defines the first SourceOS machine-readable surface for immutable Linux node and agent-runtime substrate profiles. + +## Why this exists + +The platform standards layer defines the general immutable host capability placement grammar. SourceOS needs a contract-native form that downstream implementation repos can validate, render, and emit evidence for. + +The key boundary is: + +- SourceOS immutable nodes are base substrate profiles. +- Agent Machine and AgentPlane are the primary runtime/evidence consumers for node and agent-runtime substrate posture. +- Desktop, shell, and workstation surfaces may display summarized posture and expose operator controls, but they do not own the node substrate. +- Socios capability packs are optional automation commons and must not be required for a base SourceOS node. + +## Added schemas + +| Schema | Purpose | URN prefix | +|---|---|---| +| `ImmutableNodeProfile.json` | Top-level node/agent-runtime substrate profile tying together substrate posture, release refs, host placements, state schemas, validation commands, desktop consumer refs, and optional Socios pack refs. | `urn:srcos:immutable-node-profile:` | +| `HostCapabilityPlacement.json` | Placement declaration for one node substrate capability, agent runtime workload, host extension, or state root. | `urn:srcos:host-capability-placement:` | +| `NodeStateSchema.json` | Durable state root contract with rollback compatibility, mutability posture, and desktop visibility metadata. | `urn:srcos:node-state-schema:` | + +## Added examples + +| Example | Schema | +|---|---| +| `examples/immutablenodeprofile.json` | `schemas/ImmutableNodeProfile.json` | +| `examples/hostcapabilityplacement.json` | `schemas/HostCapabilityPlacement.json` | +| `examples/nodestateschema.json` | `schemas/NodeStateSchema.json` | + +## Validation + +The focused validator is: + +```bash +python3 tools/validate_immutable_node_examples.py +``` + +A future hygiene pass should wire this into the repository `Makefile` and `schemas/README.md` once the contract slice is accepted. + +## Downstream implementation map + +| Downstream repo | Expected role | +|---|---| +| `SourceOS-Linux/sourceos-boot` | Consumes boot/release references and coordinates boot/recovery/install/rollback handoff. | +| `SourceOS-Linux/agent-machine` | Renders and validates systemd, Quadlet, state roots, activation decisions, storage receipts, and runtime evidence. | +| `SourceOS-Linux/sourceos-devtools` | Exposes `sourceosctl immutable-node plan|validate|inspect` operator flows. | +| `SocioProphet/agentplane` | Consumes immutable-node activation/runtime evidence and replayable receipts. | +| `SocioProphet/prophet-platform` | Consumes profiles as deployment/FogStack substrate evidence. | +| `SocioProphet/sociosphere` | Registers topology and cross-repo governance edges. | +| `SourceOS-Linux/sourceos-shell` | Desktop/shell consumer of summarized node posture only. | +| `SociOS-Linux/socios` | Optional automation/personalization commons only after explicit enrollment. | + +## Non-goals + +This tranche does not: + +- implement boot execution; +- mutate host state; +- make Socios mandatory; +- make desktop or shell surfaces the owner of node substrate contracts; +- replace BootReleaseSet; +- add a production node renderer; +- add CI or Makefile wiring yet. diff --git a/examples/hostcapabilityplacement.json b/examples/hostcapabilityplacement.json new file mode 100644 index 0000000..75febae --- /dev/null +++ b/examples/hostcapabilityplacement.json @@ -0,0 +1,21 @@ +{ + "id": "urn:srcos:host-capability-placement:sourceos-supervisor", + "type": "HostCapabilityPlacement", + "specVersion": "2.2.0", + "name": "SourceOS host supervisor", + "placementClass": "image-baked-host-capability", + "authority": "sourceos-base", + "primaryPlane": "node-substrate", + "mandatoryForBaseNode": true, + "requiresEnrollment": false, + "lifecycleCoupling": "host-release", + "pathHints": [ + "/usr/libexec/sourceos/supervisor", + "/usr/lib/systemd/system/sourceos-supervisor.service" + ], + "evidenceRefs": [ + "urn:srcos:release-receipt:sourceos-supervisor-bootstrap" + ], + "policyRef": "urn:srcos:policy:immutable-node-host-supervisor", + "notes": "Base SourceOS node substrate capability. This is not a desktop-owned feature and not a Socios enrollment dependency." +} diff --git a/examples/immutablenodeprofile.json b/examples/immutablenodeprofile.json new file mode 100644 index 0000000..886bd86 --- /dev/null +++ b/examples/immutablenodeprofile.json @@ -0,0 +1,45 @@ +{ + "id": "urn:srcos:immutable-node-profile:m2-asahi-agent-node-dev", + "type": "ImmutableNodeProfile", + "specVersion": "2.2.0", + "name": "M2 Asahi SourceOS agent node dev profile", + "channel": "m2-asahi-dev", + "primaryPlane": "agent-runtime-substrate", + "substrate": { + "strategy": "bootc-first", + "hostMutationPosture": "staged-updates-only", + "sociosRequired": false + }, + "bootReleaseSetRef": "urn:srcos:boot-release-set:m2-asahi-dev", + "releaseSetRef": "urn:srcos:release-set:m2-asahi-dev", + "agentMachineProfileRef": "urn:srcos:agent-machine-profile:m2-asahi-dev", + "agentPlaneRuntimeRef": "urn:srcos:agentplane-runtime:m2-asahi-dev", + "workstationProfileRef": "urn:srcos:workstation-profile:workstation-v0", + "desktopConsumerRefs": [ + "urn:srcos:desktop-consumer:sourceos-settings-node-status", + "urn:srcos:desktop-consumer:sourceos-shell-node-posture" + ], + "hostCapabilityPlacementRefs": [ + "urn:srcos:host-capability-placement:sourceos-supervisor" + ], + "nodeStateSchemaRefs": [ + "urn:srcos:node-state-schema:sourceos-evidence-root" + ], + "optionalSociosCapabilityPackRefs": [ + "urn:socios:capability-pack:automation-commons-opt-in" + ], + "validation": { + "planCommand": "sourceosctl immutable-node plan --profile m2-asahi-agent-node-dev --dry-run", + "validateCommand": "sourceosctl immutable-node validate --profile m2-asahi-agent-node-dev", + "inspectCommand": "sourceosctl immutable-node inspect --profile m2-asahi-agent-node-dev", + "evidenceCommand": "agent-machine release evidence inspect --profile m2-asahi-agent-node-dev" + }, + "policyRefs": [ + "urn:srcos:policy:immutable-node-base", + "urn:srcos:policy:socios-opt-in-required" + ], + "evidenceRefs": [ + "urn:srcos:release-receipt:m2-asahi-dev" + ], + "notes": "Node/agent-runtime substrate profile. Workstation and shell references are consumers only. Optional Socios pack reference does not imply enrollment." +} diff --git a/examples/nodestateschema.json b/examples/nodestateschema.json new file mode 100644 index 0000000..a4cbf4b --- /dev/null +++ b/examples/nodestateschema.json @@ -0,0 +1,16 @@ +{ + "id": "urn:srcos:node-state-schema:sourceos-evidence-root", + "type": "NodeStateSchema", + "specVersion": "2.2.0", + "name": "SourceOS evidence root", + "rootPath": "/var/lib/sourceos/evidence", + "stateClass": "append-only-evidence", + "primaryPlane": "execution-evidence", + "schemaRef": "urn:srcos:release-receipt:sourceos-evidence", + "rollbackCompatibility": "required-n-and-n-minus-1", + "mutability": "append-only", + "owner": "agent-machine", + "evidenceRequired": true, + "desktopVisible": true, + "notes": "Durable evidence root survives host rollback. Desktop surfaces may display summarized posture, but Agent Machine and evidence consumers own the state contract." +} diff --git a/schemas/HostCapabilityPlacement.json b/schemas/HostCapabilityPlacement.json new file mode 100644 index 0000000..6bc1e33 --- /dev/null +++ b/schemas/HostCapabilityPlacement.json @@ -0,0 +1,37 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.srcos.ai/v2/HostCapabilityPlacement.json", + "title": "HostCapabilityPlacement", + "description": "A SourceOS immutable-node placement declaration for one node substrate capability, agent runtime workload, host extension, or durable state root.", + "type": "object", + "additionalProperties": false, + "required": ["id", "type", "specVersion", "name", "placementClass", "authority"], + "properties": { + "id": { "type": "string", "pattern": "^urn:srcos:host-capability-placement:", "description": "Stable URN identifier. Pattern: urn:srcos:host-capability-placement:." }, + "type": { "const": "HostCapabilityPlacement", "description": "Discriminator constant — always HostCapabilityPlacement." }, + "specVersion": { "type": "string", "description": "Spec version of this document, e.g. 2.2.0." }, + "name": { "type": "string", "minLength": 1, "description": "Human-readable capability name." }, + "placementClass": { + "type": "string", + "enum": ["image-baked-host-capability", "sysext-host-capability", "confext-policy-config-capability", "quadlet-bound-service-workload", "quadlet-floating-service-workload", "var-state-object"], + "description": "Canonical immutable-node placement class." + }, + "authority": { + "type": "string", + "enum": ["sourceos-base", "sourceos-boot", "agent-machine", "sourceos-devtools", "agentplane", "prophet-platform", "sourceos-desktop-consumer", "socios-optional-pack"], + "description": "Repo or plane class that owns the implementation boundary for this capability." + }, + "primaryPlane": { + "type": "string", + "enum": ["node-substrate", "agent-runtime-substrate", "boot-recovery", "operator-tooling", "execution-evidence", "platform-runtime", "desktop-consumer", "optional-commons"], + "description": "Primary architectural plane for this capability. Desktop should normally be consumer, not owner." + }, + "mandatoryForBaseNode": { "type": "boolean", "default": false, "description": "Whether the capability is required for a base SourceOS immutable node without Socios enrollment." }, + "requiresEnrollment": { "type": "boolean", "default": false, "description": "Whether activation requires opt-in enrollment, Proof-of-Life, or signed intent." }, + "lifecycleCoupling": { "type": "string", "enum": ["host-release", "extension-release", "service-release", "state-schema", "operator-local"], "description": "Primary lifecycle to which this capability is coupled." }, + "pathHints": { "type": "array", "items": { "type": "string" }, "description": "Expected implementation paths such as /usr/libexec/sourceos, /usr/share/containers/systemd, /var/lib/sourceos, or /var/lib/socios." }, + "evidenceRefs": { "type": "array", "items": { "type": "string" }, "description": "Optional references to evidence records emitted by the capability." }, + "policyRef": { "type": ["string", "null"], "description": "Optional policy identifier or URN governing activation or mutation." }, + "notes": { "type": ["string", "null"], "description": "Human-readable review notes." } + } +} diff --git a/schemas/ImmutableNodeProfile.json b/schemas/ImmutableNodeProfile.json new file mode 100644 index 0000000..84f3dba --- /dev/null +++ b/schemas/ImmutableNodeProfile.json @@ -0,0 +1,50 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.srcos.ai/v2/ImmutableNodeProfile.json", + "title": "ImmutableNodeProfile", + "description": "A SourceOS immutable-node profile for node and agent-runtime substrate contracts. Desktop and workstation surfaces may consume this profile, but they do not own the node substrate.", + "type": "object", + "additionalProperties": false, + "required": ["id", "type", "specVersion", "name", "channel", "substrate"], + "properties": { + "id": { "type": "string", "pattern": "^urn:srcos:immutable-node-profile:", "description": "Stable URN identifier. Pattern: urn:srcos:immutable-node-profile:." }, + "type": { "const": "ImmutableNodeProfile", "description": "Discriminator constant — always ImmutableNodeProfile." }, + "specVersion": { "type": "string", "description": "Spec version of this document, e.g. 2.2.0." }, + "name": { "type": "string", "minLength": 1, "description": "Human-readable immutable node profile name." }, + "channel": { "type": "string", "minLength": 1, "description": "Realization channel such as linux-dev, linux-stable, m2-asahi-dev, or fog-node." }, + "primaryPlane": { "type": "string", "enum": ["node-substrate", "agent-runtime-substrate"], "description": "Primary plane for this profile. ImmutableNodeProfile is not a desktop-owned contract." }, + "substrate": { + "type": "object", + "additionalProperties": false, + "required": ["strategy", "hostMutationPosture", "sociosRequired"], + "properties": { + "strategy": { "type": "string", "enum": ["bootc-first", "ostree-compatible", "rpm-ostree", "coreos-assembler", "nixos-adapter", "other"], "description": "Primary immutable-host realization strategy." }, + "hostMutationPosture": { "type": "string", "enum": ["immutable-runtime", "staged-updates-only", "lab-live-mutation-allowed", "unknown"], "description": "Allowed host mutation posture for this profile." }, + "sociosRequired": { "const": false, "description": "Base SourceOS immutable nodes must not require Socios enrollment. Optional Socios packs are modeled separately." } + } + }, + "bootReleaseSetRef": { "type": ["string", "null"], "description": "Optional BootReleaseSet or boot/recovery handoff reference." }, + "releaseSetRef": { "type": ["string", "null"], "description": "Optional ReleaseSet assignment reference." }, + "agentMachineProfileRef": { "type": ["string", "null"], "description": "Optional Agent Machine or AgentPod profile reference for local runtime realization." }, + "agentPlaneRuntimeRef": { "type": ["string", "null"], "description": "Optional AgentPlane runtime/evidence contract reference that consumes node activation or runtime receipts." }, + "workstationProfileRef": { "type": ["string", "null"], "description": "Optional WorkstationProfile reference for desktop/workstation consumption. This must not make the desktop the owner of the node substrate." }, + "desktopConsumerRefs": { "type": "array", "items": { "type": "string" }, "description": "Optional desktop, shell, settings, or workstation surfaces that may display summarized node posture." }, + "hostCapabilityPlacementRefs": { "type": "array", "items": { "type": "string", "pattern": "^urn:srcos:host-capability-placement:" }, "description": "References to HostCapabilityPlacement objects used by this profile." }, + "nodeStateSchemaRefs": { "type": "array", "items": { "type": "string", "pattern": "^urn:srcos:node-state-schema:" }, "description": "References to NodeStateSchema objects used by this profile." }, + "optionalSociosCapabilityPackRefs": { "type": "array", "items": { "type": "string" }, "description": "Optional Socios capability pack references. Presence does not imply base node dependency or enrollment." }, + "validation": { + "type": "object", + "additionalProperties": false, + "properties": { + "planCommand": { "type": ["string", "null"] }, + "validateCommand": { "type": ["string", "null"] }, + "inspectCommand": { "type": ["string", "null"] }, + "evidenceCommand": { "type": ["string", "null"] } + }, + "description": "Operator-facing dry-run, validation, inspection, and evidence entrypoints. These are usually implemented by sourceosctl and Agent Machine, not desktop code." + }, + "policyRefs": { "type": "array", "items": { "type": "string" }, "description": "Policy references that govern activation, mutation, enrollment, or rollback." }, + "evidenceRefs": { "type": "array", "items": { "type": "string" }, "description": "Evidence or receipt references expected from this node profile." }, + "notes": { "type": ["string", "null"], "description": "Human-readable review notes." } + } +} diff --git a/schemas/NodeStateSchema.json b/schemas/NodeStateSchema.json new file mode 100644 index 0000000..2315a57 --- /dev/null +++ b/schemas/NodeStateSchema.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.srcos.ai/v2/NodeStateSchema.json", + "title": "NodeStateSchema", + "description": "A SourceOS immutable-node durable state schema declaration for rollback-aware node substrate and agent runtime state roots.", + "type": "object", + "additionalProperties": false, + "required": ["id", "type", "specVersion", "name", "rootPath", "stateClass", "rollbackCompatibility"], + "properties": { + "id": { "type": "string", "pattern": "^urn:srcos:node-state-schema:", "description": "Stable URN identifier. Pattern: urn:srcos:node-state-schema:." }, + "type": { "const": "NodeStateSchema", "description": "Discriminator constant — always NodeStateSchema." }, + "specVersion": { "type": "string", "description": "Spec version of this document, e.g. 2.2.0." }, + "name": { "type": "string", "minLength": 1, "description": "Human-readable state root name." }, + "rootPath": { "type": "string", "pattern": "^/var/(lib|cache)/", "description": "Durable or rebuildable state root. Authoritative mutable truth must not live under /usr or /etc." }, + "stateClass": { "type": "string", "enum": ["durable-truth", "append-only-evidence", "replay-checkpoint", "model-cache", "rebuildable-cache", "operator-local"], "description": "Semantic class of the state root." }, + "primaryPlane": { "type": "string", "enum": ["node-substrate", "agent-runtime-substrate", "execution-evidence", "platform-runtime", "desktop-consumer", "optional-commons"], "description": "Primary architectural plane for the state root. Desktop surfaces should usually consume state summaries, not own durable node state." }, + "schemaRef": { "type": ["string", "null"], "description": "Optional schema, contract, or URN reference for records stored in this root." }, + "rollbackCompatibility": { "type": "string", "enum": ["required-n-and-n-minus-1", "best-effort", "not-required-rebuildable", "operator-managed"], "description": "Rollback compatibility expectation for readers and writers of this state root." }, + "mutability": { "type": "string", "enum": ["append-only", "controlled-write", "rebuildable", "ephemeral-cache"], "description": "Expected write posture for this root." }, + "owner": { "type": ["string", "null"], "description": "Owning plane or repository class for this state root." }, + "evidenceRequired": { "type": "boolean", "default": false, "description": "Whether writes to this state root require evidence emission." }, + "desktopVisible": { "type": "boolean", "default": false, "description": "Whether desktop/workstation surfaces may display this state root's summarized posture." }, + "notes": { "type": ["string", "null"], "description": "Human-readable review notes." } + } +} diff --git a/tools/validate_immutable_node_examples.py b/tools/validate_immutable_node_examples.py new file mode 100644 index 0000000..ea37d87 --- /dev/null +++ b/tools/validate_immutable_node_examples.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +"""Validate immutable-node contract examples against their schemas.""" + +from __future__ import annotations + +import json +from pathlib import Path + +import jsonschema + +ROOT = Path(__file__).resolve().parents[1] + +PAIRS = [ + ("schemas/HostCapabilityPlacement.json", "examples/hostcapabilityplacement.json"), + ("schemas/NodeStateSchema.json", "examples/nodestateschema.json"), + ("schemas/ImmutableNodeProfile.json", "examples/immutablenodeprofile.json"), +] + + +def load_json(path: str) -> object: + with (ROOT / path).open("r", encoding="utf-8") as fh: + return json.load(fh) + + +def main() -> None: + for schema_path, example_path in PAIRS: + schema = load_json(schema_path) + example = load_json(example_path) + jsonschema.Draft202012Validator.check_schema(schema) + jsonschema.validate(instance=example, schema=schema) + print(f"OK: {example_path} validates against {schema_path}") + + +if __name__ == "__main__": + main()