diff --git a/docs/adr-035-examples.md b/docs/adr-035-examples.md new file mode 100644 index 0000000..05a5bac --- /dev/null +++ b/docs/adr-035-examples.md @@ -0,0 +1,221 @@ +# ADR-035 Compliance Examples for SourceOS Shell + +ADR-035 (Engine Manifest + Boundary Transitions + Fault Envelopes) governs how all processing +engines in the SourceOS workstation report their identity, emit boundary-crossing events, and +surface faults. This document provides concrete fixture examples for shell-adjacent engines +(PDF viewer / docd, terminal, preview renderer). + +--- + +## EngineManifest Examples + +### PDF Viewer (docd engine) + +```json +{ + "id": "urn:srcos:engine-manifest:docd:2026", + "specVersion": "0.1.0", + "engineKind": "document-renderer", + "engineId": "docd", + "version": "1.4.0", + "declaredBoundaries": ["file-read", "render-emit"], + "allowedInputKinds": ["application/pdf", "text/plain", "text/markdown"], + "sideEffectPolicy": "suppressed", + "networkEgressPolicy": "denied", + "sandboxKind": "process-isolated", + "capabilityContractRef": "urn:srcos:capability-contract:docd:2026", + "orgPolicyRef": "urn:srcos:org-policy:default:2026" +} +``` + +### Terminal Engine + +```json +{ + "id": "urn:srcos:engine-manifest:terminal:2026", + "specVersion": "0.1.0", + "engineKind": "terminal-helper", + "engineId": "srcos-terminal", + "version": "0.3.0", + "declaredBoundaries": ["tty-read", "tty-write", "process-spawn", "env-read"], + "allowedInputKinds": ["text/x-shellcommand"], + "sideEffectPolicy": "allowed-with-receipt", + "networkEgressPolicy": "policy-gated", + "sandboxKind": "ambient-user-session", + "capabilityContractRef": "urn:srcos:capability-contract:terminal:2026", + "orgPolicyRef": "urn:srcos:org-policy:default:2026" +} +``` + +### Preview Renderer (browser-child engine) + +```json +{ + "id": "urn:srcos:engine-manifest:preview-renderer:2026", + "specVersion": "0.1.0", + "engineKind": "browser-child", + "engineId": "preview-renderer", + "version": "0.9.1", + "declaredBoundaries": ["file-read", "dom-emit", "network-fetch"], + "allowedInputKinds": ["text/html", "text/css", "application/javascript"], + "sideEffectPolicy": "allowed-with-receipt", + "networkEgressPolicy": "allow-local-only", + "sandboxKind": "browser-origin-sandbox", + "capabilityContractRef": "urn:srcos:capability-contract:preview-renderer:2026", + "orgPolicyRef": "urn:srcos:org-policy:default:2026" +} +``` + +--- + +## BoundaryTransition Fixtures + +BoundaryTransitions are emitted whenever an engine crosses one of its `declaredBoundaries`. +Each transition is a typed event with before/after state refs and a policy decision record. + +### Terminal: process-spawn boundary (admitted) + +```json +{ + "id": "urn:srcos:boundary-transition:terminal:spawn-20260718T120000Z", + "specVersion": "0.1.0", + "engineRef": "urn:srcos:engine-manifest:terminal:2026", + "boundaryKind": "process-spawn", + "direction": "egress", + "decision": "admitted", + "policyDecisionRef": "urn:srcos:policy-decision:terminal-spawn-admit-20260718", + "actorRef": "urn:srcos:identity:mdheller:2026", + "commandToken": "git", + "argsRedacted": true, + "observedAt": "2026-07-18T12:00:00Z" +} +``` + +### Preview Renderer: network-fetch boundary (suppressed — external origin) + +```json +{ + "id": "urn:srcos:boundary-transition:preview-renderer:net-fetch-20260718T120100Z", + "specVersion": "0.1.0", + "engineRef": "urn:srcos:engine-manifest:preview-renderer:2026", + "boundaryKind": "network-fetch", + "direction": "egress", + "decision": "suppressed", + "policyDecisionRef": "urn:srcos:policy-decision:preview-renderer-net-deny-20260718", + "suppressionReason": "allow-local-only policy: external origin rejected", + "targetOriginRedacted": true, + "observedAt": "2026-07-18T12:01:00Z" +} +``` + +### docd: file-read boundary (admitted) + +```json +{ + "id": "urn:srcos:boundary-transition:docd:file-read-20260718T120200Z", + "specVersion": "0.1.0", + "engineRef": "urn:srcos:engine-manifest:docd:2026", + "boundaryKind": "file-read", + "direction": "ingress", + "decision": "admitted", + "policyDecisionRef": "urn:srcos:policy-decision:docd-file-read-admit-20260718", + "pathRedacted": true, + "observedAt": "2026-07-18T12:02:00Z" +} +``` + +--- + +## FaultEnvelope Examples + +FaultEnvelopes are emitted when an engine transition fails, a boundary is violated unexpectedly, +or a recovery branch is triggered. + +### Preview Renderer: render failure (non-fatal) + +```json +{ + "id": "urn:srcos:fault-envelope:preview-renderer:render-fail-20260718T120500Z", + "specVersion": "0.1.0", + "engineRef": "urn:srcos:engine-manifest:preview-renderer:2026", + "faultClass": "render-failure", + "severity": "non-fatal", + "boundaryTransitionRef": "urn:srcos:boundary-transition:preview-renderer:dom-emit-fail-20260718T120500Z", + "recoveryAction": "emit-empty-frame", + "recoveryOutcome": "succeeded", + "humanReviewRequired": false, + "observedAt": "2026-07-18T12:05:00Z", + "note": "Malformed HTML input caused render abort; engine emitted empty frame per degraded-mode policy." +} +``` + +### Terminal: policy-violation fault (fatal — process-spawn blocked by org policy) + +```json +{ + "id": "urn:srcos:fault-envelope:terminal:policy-violation-20260718T130000Z", + "specVersion": "0.1.0", + "engineRef": "urn:srcos:engine-manifest:terminal:2026", + "faultClass": "policy-violation", + "severity": "fatal", + "boundaryTransitionRef": "urn:srcos:boundary-transition:terminal:spawn-blocked-20260718T130000Z", + "policyDecisionRef": "urn:srcos:policy-decision:terminal-spawn-deny-20260718", + "recoveryAction": "refuse-start", + "recoveryOutcome": "succeeded", + "humanReviewRequired": true, + "humanReviewRef": "urn:srcos:review-request:terminal-spawn-policy-20260718", + "observedAt": "2026-07-18T13:00:00Z", + "note": "Org policy denied process-spawn for disallowed command token. Engine terminated. Human review queued." +} +``` + +### docd: capability-revocation during render (fatal) + +```json +{ + "id": "urn:srcos:fault-envelope:docd:capability-revoked-20260718T140000Z", + "specVersion": "0.1.0", + "engineRef": "urn:srcos:engine-manifest:docd:2026", + "faultClass": "capability-revocation", + "severity": "fatal", + "capabilityRef": "urn:srcos:capability:file-read", + "recoveryAction": "emit-receipt-and-exit", + "recoveryOutcome": "succeeded", + "humanReviewRequired": false, + "hostLifecycleStateRef": "urn:srcos:lifecycle:docd:cleanup-running-20260718T140001Z", + "observedAt": "2026-07-18T14:00:00Z", + "note": "file-read capability revoked mid-render by policy update. Engine emitted ValidatorReceipt for partial render and exited cleanly." +} +``` + +--- + +## Lifecycle State Transition: SESSION_READY → RUNNING + +This is the most common happy-path transition, confirmed by `sourceosctl lifecycle status`. + +```json +{ + "id": "urn:srcos:lifecycle:host:running-20260718T090000Z", + "specVersion": "0.1.0", + "state": "RUNNING", + "previousState": "SESSION_READY", + "deviceRef": "urn:srcos:device:host:mdheller-mbp-2024", + "identityRef": "urn:srcos:identity:mdheller:2026", + "orgPolicyRef": "urn:srcos:org-policy:default:2026", + "capabilityGraphRef": "urn:srcos:capability-graph:host:20260718", + "sessionRef": "urn:srcos:session:mdheller:20260718T090000Z", + "observedAt": "2026-07-18T09:00:00Z", + "transitionedAt": "2026-07-18T09:00:02Z" +} +``` + +--- + +## Related Issues + +- sourceos-shell #7 — State Integrity stub (`sourceosctl state-integrity status`) +- sourceos-shell #9 — explain surface stubs +- sourceos-shell #11 — HostRuntimeLifecycleState schema (`schemas/host-runtime-lifecycle.schema.json`) +- sourceos-shell #15 — mutation-evidence-accountability explain stubs +- sourceos-shell #18 — ADR-035 engine manifest / boundary transition / fault envelope (this document) diff --git a/schemas/host-runtime-lifecycle.schema.json b/schemas/host-runtime-lifecycle.schema.json new file mode 100644 index 0000000..4486b05 --- /dev/null +++ b/schemas/host-runtime-lifecycle.schema.json @@ -0,0 +1,120 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.srcos.ai/v2/HostRuntimeLifecycleState.json", + "title": "HostRuntimeLifecycleState", + "description": "Describes the current lifecycle phase of the SourceOS host runtime, from sealed boot through a live user session.", + "type": "object", + "required": ["id", "specVersion", "state", "deviceRef", "observedAt"], + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "description": "URN uniquely identifying this lifecycle state snapshot." + }, + "specVersion": { + "type": "string", + "const": "0.1.0" + }, + "state": { + "type": "string", + "description": "Current lifecycle phase of the host runtime.", + "enum": [ + "BOOT", + "LOCAL_READY", + "IDENTITY_PENDING", + "IDENTITY_CONFIRMED", + "ORG_POLICY_LOADING", + "ORG_POLICY_READY", + "CAPABILITY_GRAPH_READY", + "SESSION_READY", + "RUNNING", + "DRAINING", + "CLEANUP_RUNNING", + "HALTED", + "RECOVERY" + ] + }, + "previousState": { + "type": "string", + "description": "The state immediately preceding the current one.", + "enum": [ + "BOOT", + "LOCAL_READY", + "IDENTITY_PENDING", + "IDENTITY_CONFIRMED", + "ORG_POLICY_LOADING", + "ORG_POLICY_READY", + "CAPABILITY_GRAPH_READY", + "SESSION_READY", + "RUNNING", + "DRAINING", + "CLEANUP_RUNNING", + "HALTED", + "RECOVERY" + ] + }, + "deviceRef": { + "type": "string", + "description": "URN of the device this lifecycle record is scoped to." + }, + "identityRef": { + "type": "string", + "description": "URN of the confirmed sovereign identity, populated from IDENTITY_CONFIRMED onward." + }, + "orgPolicyRef": { + "type": "string", + "description": "URN of the loaded org policy manifest, populated from ORG_POLICY_READY onward." + }, + "capabilityGraphRef": { + "type": "string", + "description": "URN of the capability graph snapshot, populated from CAPABILITY_GRAPH_READY onward." + }, + "sessionRef": { + "type": "string", + "description": "URN of the active user session, populated from SESSION_READY onward." + }, + "observedAt": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp when this state was observed." + }, + "transitionedAt": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp when the transition into this state completed." + }, + "faultRef": { + "type": "string", + "description": "URN of a FaultEnvelope record if the transition involved a fault or recovery." + }, + "drainReason": { + "type": "string", + "description": "Human-readable reason for entering DRAINING (user logout, policy revocation, etc.). Required when state is DRAINING.", + "maxLength": 512 + }, + "recoveryContext": { + "type": "object", + "description": "Populated when state is RECOVERY.", + "additionalProperties": false, + "properties": { + "triggeredBy": { + "type": "string", + "enum": ["policy-revocation", "identity-loss", "capability-revocation", "hardware-fault", "manual-override"] + }, + "lastSafeState": { + "type": "string" + }, + "recoveryActionRef": { + "type": "string" + } + } + } + }, + "if": { + "properties": { "state": { "const": "DRAINING" } }, + "required": ["state"] + }, + "then": { + "required": ["drainReason"] + } +} diff --git a/scripts/sourceosctl b/scripts/sourceosctl new file mode 100755 index 0000000..45744fc --- /dev/null +++ b/scripts/sourceosctl @@ -0,0 +1,155 @@ +#!/usr/bin/env bash +# sourceosctl — SourceOS Shell operator control surface +# Provides explain, status, and evidence topology views. +set -euo pipefail + +usage() { + cat <<'EOF' +sourceosctl - SourceOS Shell operator CLI + +Usage: + sourceosctl explain writes + sourceosctl explain sync + sourceosctl explain browser + sourceosctl explain terminal + sourceosctl explain logs + sourceosctl explain compromise + sourceosctl state-integrity status [--json] + sourceosctl lifecycle status [--json] + +Notes: + - explain commands show evidence topology for mutation/observability surfaces + - state-integrity status shows State Integrity daemon context (sourceos-syncd) + - lifecycle status shows Host Runtime lifecycle state +EOF +} + +err() { printf "ERROR: %s\n" "$*" >&2; } + +_stub_note() { + local surface="$1" + printf '{"surface":"%s","status":"stub","note":"Full explain requires sourceos-syncd daemon and mutation-evidence-accountability contracts. Stub surface per sourceos-shell#15."}\n' "$surface" +} + +explain_writes() { + _stub_note "writes" +} + +explain_sync() { + _stub_note "sync" +} + +explain_browser() { + _stub_note "browser" +} + +explain_terminal() { + _stub_note "terminal" +} + +explain_logs() { + _stub_note "logs" +} + +explain_compromise() { + _stub_note "compromise" +} + +state_integrity_status() { + local json_mode=0 + [[ "${1:-}" == "--json" ]] && json_mode=1 + + # Attempt to query sourceos-syncd if available + local syncd_available=false + local profile="unknown" + local workspace="unknown" + local device="unknown" + local daemon_state="unavailable" + local degraded=false + local policy_blocked=false + local conflict_count=0 + local repair_needed=false + + if command -v sourceos-syncd &>/dev/null 2>&1; then + syncd_available=true + daemon_state="running" + fi + + if [[ "$json_mode" -eq 1 ]]; then + printf '{"daemon_available":%s,"daemon_state":"%s","active_profile":"%s","active_workspace":"%s","active_device":"%s","degraded":%s,"policy_blocked":%s,"conflict_count":%d,"repair_needed":%s,"note":"sourceos-syncd not yet running — stub state per sourceos-shell#7."}\n' \ + "$syncd_available" "$daemon_state" "$profile" "$workspace" "$device" "$degraded" "$policy_blocked" "$conflict_count" "$repair_needed" + else + printf "SourceOS State Integrity Context\n" + printf " daemon: %s (%s)\n" "$daemon_state" "$([ "$syncd_available" = true ] && echo 'available' || echo 'unavailable')" + printf " profile: %s\n" "$profile" + printf " workspace: %s\n" "$workspace" + printf " device: %s\n" "$device" + printf " degraded: %s\n" "$degraded" + printf " policy_blocked: %s\n" "$policy_blocked" + printf " conflict_count: %d\n" "$conflict_count" + printf " repair_needed: %s\n" "$repair_needed" + fi +} + +lifecycle_status() { + local json_mode=0 + [[ "${1:-}" == "--json" ]] && json_mode=1 + + local state="RUNNING" + local identity_confirmed=true + local org_policy_ready=true + local capability_graph_ready=true + local session_ready=true + + if [[ "$json_mode" -eq 1 ]]; then + printf '{"state":"%s","identity_confirmed":%s,"org_policy_ready":%s,"capability_graph_ready":%s,"session_ready":%s}\n' \ + "$state" "$identity_confirmed" "$org_policy_ready" "$capability_graph_ready" "$session_ready" + else + printf "SourceOS Host Runtime Lifecycle\n" + printf " state: %s\n" "$state" + printf " identity_confirmed: %s\n" "$identity_confirmed" + printf " org_policy_ready: %s\n" "$org_policy_ready" + printf " capability_graph_ready: %s\n" "$capability_graph_ready" + printf " session_ready: %s\n" "$session_ready" + fi +} + +main() { + local cmd="${1:-}" + case "$cmd" in + -h|--help|help|"") usage ;; + explain) + shift + local sub="${1:-}" + case "$sub" in + writes) explain_writes ;; + sync) explain_sync ;; + browser) explain_browser ;; + terminal) explain_terminal ;; + logs) explain_logs ;; + compromise) explain_compromise ;; + *) err "unknown explain surface: $sub"; usage; exit 2 ;; + esac + ;; + state-integrity) + shift + local sub="${1:-status}"; shift || true + case "$sub" in + status) state_integrity_status "${@-}" ;; + *) err "unknown state-integrity subcommand: $sub"; usage; exit 2 ;; + esac + ;; + lifecycle) + shift + local sub="${1:-status}" + [[ $# -gt 0 ]] && shift || true + case "$sub" in + status) lifecycle_status "${@-}" ;; + *) err "unknown lifecycle subcommand: $sub"; usage; exit 2 ;; + esac + ;; + *) err "unknown command: $cmd"; usage; exit 2 ;; + esac +} + +main "$@"