diff --git a/docs/adr/ADR-0008-local-first-release-and-enrollment-contract-family.md b/docs/adr/ADR-0008-local-first-release-and-enrollment-contract-family.md new file mode 100644 index 0000000..25756c9 --- /dev/null +++ b/docs/adr/ADR-0008-local-first-release-and-enrollment-contract-family.md @@ -0,0 +1,72 @@ +# ADR-0008 — Local-first release and enrollment contract family + +Status: Proposed + +## Context + +ADR-0007 reserved the local-first control-node and image-promotion seam in `SourceOS-Linux/sourceos-spec`. + +What remains missing is the first machine-readable contract family needed to make that seam executable for the thin local-first lifecycle slice: + +- assign a release to a device or cohort +- optionally authorize a boot/install or recovery path +- enroll a device or session against that assignment +- realize operator/user experience and isolation posture +- emit a post-apply fingerprint suitable for compliance and rollback decisions + +Without a canonical object family here, downstream repos risk re-inventing overlapping structures for release assignment, enrollment, host realization, and attestation. + +## Decision + +Reserve and introduce the first local-first control-plane contract family in this repository. + +The initial object family is: + +- `ExperienceProfile` +- `IsolationProfile` +- `ReleaseSet` +- `BootReleaseSet` +- `EnrollmentToken` +- `Fingerprint` + +These objects define the minimum SourceOS local-first lifecycle seam for: + +1. profile selection +2. release assignment +3. boot/recovery authorization when needed +4. enrollment and one-time redemption semantics +5. post-apply attestation and compliance comparison + +## Repo ownership split + +This repository owns: + +- machine-readable schemas +- normative object semantics +- example payloads for the local-first lifecycle slice + +This repository does not own: + +- runtime execution enforcement (`SocioProphet/agentplane`) +- workspace/fabric choreography (`SocioProphet/sociosphere`) +- transport binding (`SocioProphet/TriTRPC`) + +## Consequences + +### Positive + +- gives SourceOS a canonical typed object family for local-first release control +- avoids parallel release/enrollment/fingerprint vocabularies appearing downstream +- creates a stable seam for `agentplane` and other consumers + +### Constraints + +- this tranche is intentionally minimal and additive +- it does not yet define the full audit-event extension family +- it does not yet standardize every possible config-source shape + +## Follow-on work + +1. Bind `ReleaseSet`, `BootReleaseSet`, `EnrollmentToken`, and `Fingerprint` into `SocioProphet/agentplane` runtime evidence surfaces. +2. Add any required transport-safe references in `SocioProphet/TriTRPC` only after object semantics stabilize. +3. Add broader `ConfigSource` and lifecycle event families as follow-on contracts if the thin slice proves sound. diff --git a/examples/control-plane/README.md b/examples/control-plane/README.md new file mode 100644 index 0000000..82be895 --- /dev/null +++ b/examples/control-plane/README.md @@ -0,0 +1,22 @@ +# Control-plane examples — local-first release family + +This directory contains example payloads for the first local-first SourceOS control-plane contract family. + +## Included examples + +- `experience-profile.sample.json` +- `isolation-profile.sample.json` +- `release-set.sample.json` +- `boot-release-set.sample.json` +- `enrollment-token.sample.json` +- `fingerprint.sample.json` +- `incident.freeze.sample.json` (existing) + +These examples are intended to support the thin local-first lifecycle slice: + +1. choose experience posture +2. choose isolation posture +3. assign a release +4. optionally authorize boot/recovery +5. redeem an enrollment token +6. emit a post-apply fingerprint diff --git a/examples/control-plane/boot-release-set.sample.json b/examples/control-plane/boot-release-set.sample.json new file mode 100644 index 0000000..ef39fc0 --- /dev/null +++ b/examples/control-plane/boot-release-set.sample.json @@ -0,0 +1,13 @@ +{ + "boot_release_set_id": "brs_local_0001", + "base_release_set_ref": "rs_local_0001", + "boot_mode": "recovery", + "status": "ready", + "artifacts": { + "kernel_ref": "artifact://boot/kernel/local-0001", + "initrd_ref": "artifact://boot/initrd/local-0001", + "rootfs_ref": "artifact://boot/rootfs/local-0001" + }, + "created_at": "2026-04-15T22:05:00Z", + "notes": "Recovery boot artifacts for the assigned local release." +} diff --git a/examples/control-plane/enrollment-token.sample.json b/examples/control-plane/enrollment-token.sample.json new file mode 100644 index 0000000..c02cbbb --- /dev/null +++ b/examples/control-plane/enrollment-token.sample.json @@ -0,0 +1,15 @@ +{ + "token_id": "tok_local_0001", + "purpose": "boot", + "audience": { + "subject_kind": "device", + "subject_id": "m2-control-node-01" + }, + "release_set_ref": "rs_local_0001", + "boot_release_set_ref": "brs_local_0001", + "one_time_use": true, + "issued_at": "2026-04-15T22:06:00Z", + "expires_at": "2026-04-15T23:06:00Z", + "status": "issued", + "notes": "One-time local recovery boot token." +} diff --git a/examples/control-plane/experience-profile.sample.json b/examples/control-plane/experience-profile.sample.json new file mode 100644 index 0000000..130a488 --- /dev/null +++ b/examples/control-plane/experience-profile.sample.json @@ -0,0 +1,13 @@ +{ + "experience_profile_id": "xp_local_0001", + "profile_name": "Local desktop profile", + "shell": "zsh", + "desktop": "gnome", + "surfaces": ["cli", "gui", "browser"], + "defaults": { + "editor": "nvim", + "theme": "dark", + "accessibility_profile": null, + "workspace_layout": "default" + } +} diff --git a/examples/control-plane/fingerprint.sample.json b/examples/control-plane/fingerprint.sample.json new file mode 100644 index 0000000..fd480b3 --- /dev/null +++ b/examples/control-plane/fingerprint.sample.json @@ -0,0 +1,24 @@ +{ + "fingerprint_id": "fp_local_0001", + "subject": { + "kind": "device", + "id": "m2-control-node-01" + }, + "release_set_ref": "rs_local_0001", + "experience_profile_ref": "xp_local_0001", + "isolation_profile_ref": "iso_local_0001", + "observed_at": "2026-04-15T22:10:00Z", + "integrity": { + "boot_release_set_ref": "brs_local_0001", + "image_ref": "oci://registry.example/sourceos/node-commander:2026.04.15-rc1", + "store_closure_hash": "sha256:example-closure-hash", + "boot_entry_label": "SourceOS Recovery" + }, + "compliance": { + "status": "compliant", + "notes": "Observed state matches the assigned local release." + }, + "evidence_refs": [ + "artifact://evidence/fingerprint/fp_local_0001" + ] +} diff --git a/examples/control-plane/isolation-profile.sample.json b/examples/control-plane/isolation-profile.sample.json new file mode 100644 index 0000000..ac7dc40 --- /dev/null +++ b/examples/control-plane/isolation-profile.sample.json @@ -0,0 +1,10 @@ +{ + "isolation_profile_id": "iso_local_0001", + "profile_name": "Local mediated profile", + "execution_mode": "container", + "network_mode": "filtered", + "filesystem_mode": "mediated", + "secret_mode": "brokered", + "allowed_connector_classes": ["localfs", "git", "oci-registry"], + "notes": "Default isolation posture for local release application." +} diff --git a/examples/control-plane/release-set.sample.json b/examples/control-plane/release-set.sample.json new file mode 100644 index 0000000..dfd073e --- /dev/null +++ b/examples/control-plane/release-set.sample.json @@ -0,0 +1,24 @@ +{ + "release_set_id": "rs_local_0001", + "release_version": "2026.04.15-rc1", + "status": "assigned", + "source": { + "git_ref": "refs/heads/main", + "git_commit": "8945f21" + }, + "targets": [ + { + "target_kind": "device", + "target_id": "m2-control-node-01" + } + ], + "experience_profile_ref": "xp_local_0001", + "isolation_profile_ref": "iso_local_0001", + "artifacts": { + "store_closure_hash": "sha256:example-closure-hash", + "image_ref": "oci://registry.example/sourceos/node-commander:2026.04.15-rc1", + "boot_release_set_ref": "brs_local_0001" + }, + "created_at": "2026-04-15T22:00:00Z", + "notes": "First local-first release assignment sample." +} diff --git a/schemas/control-plane/README.md b/schemas/control-plane/README.md new file mode 100644 index 0000000..b5a836d --- /dev/null +++ b/schemas/control-plane/README.md @@ -0,0 +1,31 @@ +# Control-plane schema tranche — local-first release family + +This directory contains the first machine-readable contract family for the local-first SourceOS lifecycle slice. + +## Included schemas + +- `experience-profile.schema.json` +- `isolation-profile.schema.json` +- `release-set.schema.json` +- `boot-release-set.schema.json` +- `enrollment-token.schema.json` +- `fingerprint.schema.json` +- `incident-events.schema.json` (existing) + +## Intent + +These schemas define the minimum typed seam for: + +1. selecting operator/user experience posture +2. selecting isolation posture +3. assigning a release to a device, cohort, or site +4. authorizing boot/recovery/install flows where required +5. redeeming one-time enrollment tokens +6. emitting post-apply fingerprints for compliance and rollback decisions + +## Downstream consumers + +- `SocioProphet/agentplane` +- `SocioProphet/sociosphere` (by reference where needed) +- `SocioProphet/TriTRPC` (transport-safe refs after stabilization) +- SourceOS realization and installer lanes diff --git a/schemas/control-plane/boot-release-set.schema.json b/schemas/control-plane/boot-release-set.schema.json new file mode 100644 index 0000000..be73f52 --- /dev/null +++ b/schemas/control-plane/boot-release-set.schema.json @@ -0,0 +1,40 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://socioprophet.org/schemas/control-plane/boot-release-set.schema.json", + "title": "BootReleaseSet", + "description": "Boot/install or recovery artifacts associated with a local-first release assignment.", + "type": "object", + "additionalProperties": false, + "required": [ + "boot_release_set_id", + "base_release_set_ref", + "boot_mode", + "status", + "artifacts", + "created_at" + ], + "properties": { + "boot_release_set_id": { "type": "string", "minLength": 1 }, + "base_release_set_ref": { "type": "string", "minLength": 1 }, + "boot_mode": { + "type": "string", + "enum": ["installer", "recovery", "ephemeral", "bootstrap"] + }, + "status": { + "type": "string", + "enum": ["draft", "ready", "retired"] + }, + "artifacts": { + "type": "object", + "additionalProperties": false, + "required": ["kernel_ref", "initrd_ref", "rootfs_ref"], + "properties": { + "kernel_ref": { "type": "string", "minLength": 1 }, + "initrd_ref": { "type": "string", "minLength": 1 }, + "rootfs_ref": { "type": "string", "minLength": 1 } + } + }, + "created_at": { "type": "string", "format": "date-time" }, + "notes": { "type": ["string", "null"] } + } +} diff --git a/schemas/control-plane/enrollment-token.schema.json b/schemas/control-plane/enrollment-token.schema.json new file mode 100644 index 0000000..607b355 --- /dev/null +++ b/schemas/control-plane/enrollment-token.schema.json @@ -0,0 +1,46 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://socioprophet.org/schemas/control-plane/enrollment-token.schema.json", + "title": "EnrollmentToken", + "description": "One-time or short-lived token used to enroll or authorize a local-first boot or recovery flow.", + "type": "object", + "additionalProperties": false, + "required": [ + "token_id", + "purpose", + "audience", + "issued_at", + "expires_at", + "one_time_use", + "status" + ], + "properties": { + "token_id": { "type": "string", "minLength": 1 }, + "purpose": { + "type": "string", + "enum": ["enroll", "boot", "repair", "recovery"] + }, + "audience": { + "type": "object", + "additionalProperties": false, + "required": ["subject_kind"], + "properties": { + "subject_kind": { + "type": "string", + "enum": ["device", "user", "operator", "session"] + }, + "subject_id": { "type": ["string", "null"] } + } + }, + "release_set_ref": { "type": ["string", "null"] }, + "boot_release_set_ref": { "type": ["string", "null"] }, + "one_time_use": { "type": "boolean" }, + "issued_at": { "type": "string", "format": "date-time" }, + "expires_at": { "type": "string", "format": "date-time" }, + "status": { + "type": "string", + "enum": ["issued", "redeemed", "expired", "revoked"] + }, + "notes": { "type": ["string", "null"] } + } +} diff --git a/schemas/control-plane/experience-profile.schema.json b/schemas/control-plane/experience-profile.schema.json new file mode 100644 index 0000000..1b7eef5 --- /dev/null +++ b/schemas/control-plane/experience-profile.schema.json @@ -0,0 +1,47 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://socioprophet.org/schemas/control-plane/experience-profile.schema.json", + "title": "ExperienceProfile", + "description": "Local-first operator or user experience posture for a SourceOS host or release assignment.", + "type": "object", + "additionalProperties": false, + "required": [ + "experience_profile_id", + "profile_name", + "shell", + "desktop", + "surfaces", + "defaults" + ], + "properties": { + "experience_profile_id": { "type": "string", "minLength": 1 }, + "profile_name": { "type": "string", "minLength": 1 }, + "shell": { + "type": "string", + "enum": ["bash", "zsh", "fish", "none"] + }, + "desktop": { + "type": "string", + "enum": ["gnome", "cosmic", "none", "custom"] + }, + "surfaces": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "string", + "enum": ["cli", "gui", "browser", "agent"] + } + }, + "defaults": { + "type": "object", + "additionalProperties": false, + "properties": { + "editor": { "type": ["string", "null"] }, + "theme": { "type": ["string", "null"] }, + "accessibility_profile": { "type": ["string", "null"] }, + "workspace_layout": { "type": ["string", "null"] } + } + } + } +} diff --git a/schemas/control-plane/fingerprint.schema.json b/schemas/control-plane/fingerprint.schema.json new file mode 100644 index 0000000..bf84383 --- /dev/null +++ b/schemas/control-plane/fingerprint.schema.json @@ -0,0 +1,61 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://socioprophet.org/schemas/control-plane/fingerprint.schema.json", + "title": "Fingerprint", + "description": "Post-apply attestation snapshot used to compare realized state against a local-first release assignment.", + "type": "object", + "additionalProperties": false, + "required": [ + "fingerprint_id", + "subject", + "release_set_ref", + "observed_at", + "compliance" + ], + "properties": { + "fingerprint_id": { "type": "string", "minLength": 1 }, + "subject": { + "type": "object", + "additionalProperties": false, + "required": ["kind", "id"], + "properties": { + "kind": { + "type": "string", + "enum": ["device", "host", "session"] + }, + "id": { "type": "string", "minLength": 1 } + } + }, + "release_set_ref": { "type": "string", "minLength": 1 }, + "experience_profile_ref": { "type": ["string", "null"] }, + "isolation_profile_ref": { "type": ["string", "null"] }, + "observed_at": { "type": "string", "format": "date-time" }, + "integrity": { + "type": "object", + "additionalProperties": false, + "properties": { + "boot_release_set_ref": { "type": ["string", "null"] }, + "image_ref": { "type": ["string", "null"] }, + "store_closure_hash": { "type": ["string", "null"] }, + "boot_entry_label": { "type": ["string", "null"] } + } + }, + "compliance": { + "type": "object", + "additionalProperties": false, + "required": ["status"], + "properties": { + "status": { + "type": "string", + "enum": ["compliant", "drifted", "unknown", "failed"] + }, + "notes": { "type": ["string", "null"] } + } + }, + "evidence_refs": { + "type": "array", + "items": { "type": "string" }, + "default": [] + } + } +} diff --git a/schemas/control-plane/isolation-profile.schema.json b/schemas/control-plane/isolation-profile.schema.json new file mode 100644 index 0000000..0aa7c9e --- /dev/null +++ b/schemas/control-plane/isolation-profile.schema.json @@ -0,0 +1,42 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://socioprophet.org/schemas/control-plane/isolation-profile.schema.json", + "title": "IsolationProfile", + "description": "Isolation posture for a SourceOS host or release assignment.", + "type": "object", + "additionalProperties": false, + "required": [ + "isolation_profile_id", + "profile_name", + "execution_mode", + "network_mode", + "filesystem_mode", + "secret_mode" + ], + "properties": { + "isolation_profile_id": { "type": "string", "minLength": 1 }, + "profile_name": { "type": "string", "minLength": 1 }, + "execution_mode": { + "type": "string", + "enum": ["host", "container", "microvm", "remote"] + }, + "network_mode": { + "type": "string", + "enum": ["offline", "local_only", "filtered", "full"] + }, + "filesystem_mode": { + "type": "string", + "enum": ["sealed", "mediated", "workspace_only", "broad"] + }, + "secret_mode": { + "type": "string", + "enum": ["none", "brokered", "direct"] + }, + "allowed_connector_classes": { + "type": "array", + "items": { "type": "string" }, + "default": [] + }, + "notes": { "type": ["string", "null"] } + } +} diff --git a/schemas/control-plane/release-set.schema.json b/schemas/control-plane/release-set.schema.json new file mode 100644 index 0000000..5972a0d --- /dev/null +++ b/schemas/control-plane/release-set.schema.json @@ -0,0 +1,61 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://socioprophet.org/schemas/control-plane/release-set.schema.json", + "title": "ReleaseSet", + "description": "A local-first release assignment bundle for a device, cohort, site, or profile target.", + "type": "object", + "additionalProperties": false, + "required": [ + "release_set_id", + "release_version", + "status", + "source", + "targets", + "experience_profile_ref", + "isolation_profile_ref", + "created_at" + ], + "properties": { + "release_set_id": { "type": "string", "minLength": 1 }, + "release_version": { "type": "string", "minLength": 1 }, + "status": { + "type": "string", + "enum": ["draft", "assigned", "active", "superseded"] + }, + "source": { + "type": "object", + "additionalProperties": false, + "required": ["git_ref", "git_commit"], + "properties": { + "git_ref": { "type": "string", "minLength": 1 }, + "git_commit": { "type": "string", "minLength": 7 } + } + }, + "targets": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": ["target_kind", "target_id"], + "properties": { + "target_kind": { "type": "string" }, + "target_id": { "type": "string", "minLength": 1 } + } + } + }, + "experience_profile_ref": { "type": "string", "minLength": 1 }, + "isolation_profile_ref": { "type": "string", "minLength": 1 }, + "artifacts": { + "type": "object", + "additionalProperties": false, + "properties": { + "store_closure_hash": { "type": ["string", "null"] }, + "image_ref": { "type": ["string", "null"] }, + "boot_release_set_ref": { "type": ["string", "null"] } + } + }, + "created_at": { "type": "string", "format": "date-time" }, + "notes": { "type": ["string", "null"] } + } +}