From bd8a33ed352a961a27fe71ff2aedbe786c1bba75 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Tue, 28 Apr 2026 12:42:08 -0400 Subject: [PATCH 01/16] Bootstrap SourceOS carry README --- README.md | 47 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fd82ddc..73e6b06 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,45 @@ -# sourceos-model-carry -SourceOS carry-only contracts for AI service clients, signed model/service references, launch profiles, cache policy, and mutable-model-state refusal gates. +# SourceOS Model Carry + +SourceOS Model Carry defines the on-device carriage layer for governed AI services. + +The purpose of this repository is to let SourceOS work like a polished local-first operating system while keeping AI service lifecycle authority outside the mutable workstation image. + +## Position + +SourceOS may carry: + +- service clients; +- launch profiles; +- signed service references; +- local cache policy; +- fallback references; +- ReleaseSet and BootReleaseSet bindings; +- evidence collectors; +- workstation integration hints. + +SourceOS must not become the authority for model promotion or model lifecycle state. + +## Product goal + +The near-term target is an on-device experience that feels as integrated as macOS, but is more open, auditable, local-first, and mesh-ready. + +That means: + +- fast command-palette and keyboard-first workflow; +- local service discovery; +- offline-safe fallback behavior; +- clear per-service trust and provenance; +- user, project, and agent workspace separation; +- signed service references rather than unmanaged local artifact replacement; +- clean integration with SourceOS shell, boot, and profile systems. + +## Repository scope + +This repository contains: + +- `contracts/` JSON schemas for SourceOS carry objects; +- `examples/` example local AI service profiles; +- `docs/` architecture and workstation integration guidance; +- `tools/` lightweight validation helpers. + +Runtime service implementation belongs in SocioProphet platform service repositories. Lab execution belongs in SociOS Linux lab repositories. From 8fa9f33e0cabfcd94402beea3643a212e17b5163 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Tue, 28 Apr 2026 12:42:42 -0400 Subject: [PATCH 02/16] Add on-device architecture --- docs/ON_DEVICE_ARCHITECTURE.md | 116 +++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 docs/ON_DEVICE_ARCHITECTURE.md diff --git a/docs/ON_DEVICE_ARCHITECTURE.md b/docs/ON_DEVICE_ARCHITECTURE.md new file mode 100644 index 0000000..1f829c3 --- /dev/null +++ b/docs/ON_DEVICE_ARCHITECTURE.md @@ -0,0 +1,116 @@ +# On-Device AI Carry Architecture + +## Objective + +SourceOS should make local AI services feel native, fast, and integrated without turning the operating system image into an unmanaged model-update channel. + +The on-device layer is a carry layer. It carries signed references, clients, launch policy, cache policy, and evidence collectors. It does not own mutable model lifecycle authority. + +## On-device product surfaces + +### Command and launcher surface + +SourceOS should expose AI services through a keyboard-first launcher and command palette. + +Examples: + +- transcribe selected audio; +- OCR selected file or screenshot; +- summarize selected document; +- translate selected text; +- search personal/project corpus; +- run local embedding update for a project cache; +- launch a governed agent workspace. + +### Workspace surface + +Every invocation must be scoped to a workspace: + +- user workspace; +- project workspace; +- agent workspace; +- system workspace. + +System workspace is not a general execution target for AI services. + +### Service discovery surface + +On-device clients discover services through signed carry references. + +A carry reference resolves to: + +- service identity; +- endpoint class; +- allowed launch modes; +- required policy; +- cache posture; +- fallback posture; +- evidence requirements. + +### Offline and degraded mode + +SourceOS may carry offline-safe references and cache policy. It must distinguish: + +- online governed service; +- local service; +- cached fallback; +- unavailable service; +- refused service due to policy. + +A fallback is valid only if it is signed, pinned, and policy-approved. + +## Mac-like but better + +The workstation goal is parity with the integrated feel of macOS, not a clone. + +Required primitives: + +- global command palette; +- consistent keyboard shortcuts; +- service actions on selected files, text, audio, images, and video; +- clipboard and share-sheet style actions through portals; +- local-first index and search; +- clear permission prompts for sensitive actions; +- audit-visible agent and tool activity; +- reproducible profile-driven setup. + +The difference from macOS is that every capability should have a visible service reference, provenance, policy, and evidence path. + +## SourceOS carry boundary + +Allowed: + +- client binaries; +- launchers; +- system integration hooks; +- signed carry references; +- local cache indexes; +- cache manifests; +- evidence collectors; +- workspace bindings. + +Disallowed: + +- unsigned service references; +- ad-hoc artifact replacement; +- unmanaged artifact downloads at boot; +- service promotion from local workstation state; +- agent-controlled model lifecycle changes; +- system-plane mutation for model updates. + +## Integration points + +- `SourceOS-Linux/sourceos-spec`: normative OS object models. +- `SourceOS-Linux/sourceos-shell`: command palette, shell, and user workflow surface. +- `SourceOS-Linux/sourceos-boot`: ReleaseSet and BootReleaseSet integration. +- `SocioProphet/functional-model-surfaces`: functional AI standards. +- `SocioProphet/prophet-platform`: governed platform services. +- `SociOS-Linux/*lab`: lab execution and candidate artifacts. + +## First demo slice + +1. Install SourceOS carry examples for speech, OCR, image, video, translation, and embedding. +2. Expose them through a local service listing command. +3. Validate that all carry refs are signed-reference shaped and do not embed mutable artifact authority. +4. Emit evidence for the service list and policy check. +5. Fail validation for an example that attempts to grant model lifecycle authority to SourceOS. From e26f8c3995f71c573f91901eccc6250bdfe1f4bd Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Tue, 28 Apr 2026 12:43:47 -0400 Subject: [PATCH 03/16] Add speech carry reference example --- examples/speech-carry-ref.json | 42 ++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 examples/speech-carry-ref.json diff --git a/examples/speech-carry-ref.json b/examples/speech-carry-ref.json new file mode 100644 index 0000000..23a99f9 --- /dev/null +++ b/examples/speech-carry-ref.json @@ -0,0 +1,42 @@ +{ + "apiVersion": "modelcarry.sourceos.dev/v1", + "kind": "SourceOSCarryRef", + "metadata": { + "name": "speech-default", + "version": "0.1.0" + }, + "spec": { + "surface": "speech", + "serviceRef": "service://socioprophet/modality/speech/default@0.1.0", + "client": { + "packageRef": "sourceos-client://modality/speech@0.1.0", + "entrypoint": "sourceos-ai speech", + "protocol": "tritrpc" + }, + "launch": { + "workspaceScopes": ["user", "project", "agent"], + "defaultMode": "remote-service", + "requiresNetwork": true + }, + "cache": { + "mode": "metadata-only", + "maxBytes": 104857600, + "pathHint": "~/.local/share/sourceos/model-carry/speech" + }, + "policy": { + "policyRef": "policy://sourceos/model-carry/standard", + "requiresSignedServiceRef": true, + "dataClasses": ["audio", "transcript"] + }, + "evidence": { + "emitInvocationReceipt": true, + "emitPolicyCheck": true, + "receiptSink": "sourceos://evidence/model-carry" + }, + "authority": { + "sourceosRole": "carry-only", + "platformPromotionRequired": true, + "mayReplaceServiceArtifact": false + } + } +} From c0abaa2d37073300b575caae476ff96605f5d5e4 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Tue, 28 Apr 2026 12:44:29 -0400 Subject: [PATCH 04/16] Add OCR carry reference example --- examples/ocr-carry-ref.json | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 examples/ocr-carry-ref.json diff --git a/examples/ocr-carry-ref.json b/examples/ocr-carry-ref.json new file mode 100644 index 0000000..4eba97f --- /dev/null +++ b/examples/ocr-carry-ref.json @@ -0,0 +1,15 @@ +{ + "apiVersion": "modelcarry.sourceos.dev/v1", + "kind": "SourceOSCarryRef", + "metadata": {"name": "ocr-default", "version": "0.1.0"}, + "spec": { + "surface": "ocr", + "serviceRef": "service://socioprophet/modality/ocr/default@0.1.0", + "client": {"packageRef": "sourceos-client://modality/ocr@0.1.0", "entrypoint": "sourceos-ai ocr", "protocol": "tritrpc"}, + "launch": {"workspaceScopes": ["user", "project", "agent"], "defaultMode": "remote-service", "requiresNetwork": true}, + "cache": {"mode": "metadata-only", "maxBytes": 104857600, "pathHint": "~/.local/share/sourceos/model-carry/ocr"}, + "policy": {"policyRef": "policy://sourceos/model-carry/standard", "requiresSignedServiceRef": true, "dataClasses": ["document", "image", "text"]}, + "evidence": {"emitInvocationReceipt": true, "emitPolicyCheck": true, "receiptSink": "sourceos://evidence/model-carry"}, + "authority": {"sourceosRole": "carry-only", "platformPromotionRequired": true, "mayReplaceServiceArtifact": false} + } +} From f23bc6fb6f72e28047e399506583765a7254f995 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Tue, 28 Apr 2026 12:44:55 -0400 Subject: [PATCH 05/16] Add image carry reference example --- examples/image-carry-ref.json | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 examples/image-carry-ref.json diff --git a/examples/image-carry-ref.json b/examples/image-carry-ref.json new file mode 100644 index 0000000..8fb0805 --- /dev/null +++ b/examples/image-carry-ref.json @@ -0,0 +1,15 @@ +{ + "apiVersion": "modelcarry.sourceos.dev/v1", + "kind": "SourceOSCarryRef", + "metadata": {"name": "image-default", "version": "0.1.0"}, + "spec": { + "surface": "image", + "serviceRef": "service://socioprophet/modality/image/default@0.1.0", + "client": {"packageRef": "sourceos-client://modality/image@0.1.0", "entrypoint": "sourceos-ai image", "protocol": "tritrpc"}, + "launch": {"workspaceScopes": ["user", "project", "agent"], "defaultMode": "remote-service", "requiresNetwork": true}, + "cache": {"mode": "metadata-only", "maxBytes": 104857600, "pathHint": "~/.local/share/sourceos/model-carry/image"}, + "policy": {"policyRef": "policy://sourceos/model-carry/standard", "requiresSignedServiceRef": true, "dataClasses": ["image", "metadata"]}, + "evidence": {"emitInvocationReceipt": true, "emitPolicyCheck": true, "receiptSink": "sourceos://evidence/model-carry"}, + "authority": {"sourceosRole": "carry-only", "platformPromotionRequired": true, "mayReplaceServiceArtifact": false} + } +} From f394854ab54b2b7e8b01843279abf595293290d7 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Tue, 28 Apr 2026 12:45:18 -0400 Subject: [PATCH 06/16] Add video carry reference example --- examples/video-carry-ref.json | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 examples/video-carry-ref.json diff --git a/examples/video-carry-ref.json b/examples/video-carry-ref.json new file mode 100644 index 0000000..cfac4c0 --- /dev/null +++ b/examples/video-carry-ref.json @@ -0,0 +1,15 @@ +{ + "apiVersion": "modelcarry.sourceos.dev/v1", + "kind": "SourceOSCarryRef", + "metadata": {"name": "video-default", "version": "0.1.0"}, + "spec": { + "surface": "video", + "serviceRef": "service://socioprophet/modality/video/default@0.1.0", + "client": {"packageRef": "sourceos-client://modality/video@0.1.0", "entrypoint": "sourceos-ai video", "protocol": "tritrpc"}, + "launch": {"workspaceScopes": ["user", "project", "agent"], "defaultMode": "remote-service", "requiresNetwork": true}, + "cache": {"mode": "metadata-only", "maxBytes": 104857600, "pathHint": "~/.local/share/sourceos/model-carry/video"}, + "policy": {"policyRef": "policy://sourceos/model-carry/standard", "requiresSignedServiceRef": true, "dataClasses": ["video", "audio", "transcript", "frames"]}, + "evidence": {"emitInvocationReceipt": true, "emitPolicyCheck": true, "receiptSink": "sourceos://evidence/model-carry"}, + "authority": {"sourceosRole": "carry-only", "platformPromotionRequired": true, "mayReplaceServiceArtifact": false} + } +} From 00669af9e209dfe78b72228a8baaebb888b00a8f Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Tue, 28 Apr 2026 12:45:33 -0400 Subject: [PATCH 07/16] Add translation carry reference example --- examples/translation-carry-ref.json | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 examples/translation-carry-ref.json diff --git a/examples/translation-carry-ref.json b/examples/translation-carry-ref.json new file mode 100644 index 0000000..ec50623 --- /dev/null +++ b/examples/translation-carry-ref.json @@ -0,0 +1,15 @@ +{ + "apiVersion": "modelcarry.sourceos.dev/v1", + "kind": "SourceOSCarryRef", + "metadata": {"name": "translation-default", "version": "0.1.0"}, + "spec": { + "surface": "translation", + "serviceRef": "service://socioprophet/modality/translation/default@0.1.0", + "client": {"packageRef": "sourceos-client://modality/translation@0.1.0", "entrypoint": "sourceos-ai translate", "protocol": "tritrpc"}, + "launch": {"workspaceScopes": ["user", "project", "agent"], "defaultMode": "remote-service", "requiresNetwork": true}, + "cache": {"mode": "metadata-only", "maxBytes": 104857600, "pathHint": "~/.local/share/sourceos/model-carry/translation"}, + "policy": {"policyRef": "policy://sourceos/model-carry/standard", "requiresSignedServiceRef": true, "dataClasses": ["text", "audio", "terminology"]}, + "evidence": {"emitInvocationReceipt": true, "emitPolicyCheck": true, "receiptSink": "sourceos://evidence/model-carry"}, + "authority": {"sourceosRole": "carry-only", "platformPromotionRequired": true, "mayReplaceServiceArtifact": false} + } +} From 512d996619f6f5a169db01d86858d7c9d26585e9 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Tue, 28 Apr 2026 12:45:48 -0400 Subject: [PATCH 08/16] Add embedding carry reference example --- examples/embedding-carry-ref.json | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 examples/embedding-carry-ref.json diff --git a/examples/embedding-carry-ref.json b/examples/embedding-carry-ref.json new file mode 100644 index 0000000..c4c7b92 --- /dev/null +++ b/examples/embedding-carry-ref.json @@ -0,0 +1,15 @@ +{ + "apiVersion": "modelcarry.sourceos.dev/v1", + "kind": "SourceOSCarryRef", + "metadata": {"name": "embedding-default", "version": "0.1.0"}, + "spec": { + "surface": "embedding", + "serviceRef": "service://socioprophet/modality/embedding/default@0.1.0", + "client": {"packageRef": "sourceos-client://modality/embedding@0.1.0", "entrypoint": "sourceos-ai embed", "protocol": "tritrpc"}, + "launch": {"workspaceScopes": ["user", "project", "agent"], "defaultMode": "remote-service", "requiresNetwork": true}, + "cache": {"mode": "artifact-index", "maxBytes": 1073741824, "pathHint": "~/.local/share/sourceos/model-carry/embedding"}, + "policy": {"policyRef": "policy://sourceos/model-carry/standard", "requiresSignedServiceRef": true, "dataClasses": ["text", "document", "vector-index"]}, + "evidence": {"emitInvocationReceipt": true, "emitPolicyCheck": true, "receiptSink": "sourceos://evidence/model-carry"}, + "authority": {"sourceosRole": "carry-only", "platformPromotionRequired": true, "mayReplaceServiceArtifact": false} + } +} From 3b256f76eb1dd4f8f136180e82b0ec5a79fb36e7 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Tue, 28 Apr 2026 12:46:18 -0400 Subject: [PATCH 09/16] Add carry reference validator --- tools/validate_carry_refs.py | 88 ++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 tools/validate_carry_refs.py diff --git a/tools/validate_carry_refs.py b/tools/validate_carry_refs.py new file mode 100644 index 0000000..391f6ac --- /dev/null +++ b/tools/validate_carry_refs.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import json +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +EXAMPLES = ROOT / "examples" +REQUIRED_TOP = {"apiVersion", "kind", "metadata", "spec"} +REQUIRED_SPEC = {"surface", "serviceRef", "client", "launch", "cache", "policy", "evidence", "authority"} +ALLOWED_SURFACES = { + "speech", + "ocr", + "image", + "video", + "translation", + "embedding", + "timeseries", + "graph", + "agent", + "guardrail", + "router", +} + + +def fail(path: Path, message: str) -> int: + print(f"ERROR: {path}: {message}", file=sys.stderr) + return 1 + + +def validate(path: Path) -> int: + data = json.loads(path.read_text()) + missing_top = REQUIRED_TOP - set(data) + if missing_top: + return fail(path, f"missing top-level fields: {sorted(missing_top)}") + if data["apiVersion"] != "modelcarry.sourceos.dev/v1": + return fail(path, "apiVersion must be modelcarry.sourceos.dev/v1") + if data["kind"] != "SourceOSCarryRef": + return fail(path, "kind must be SourceOSCarryRef") + + spec = data["spec"] + missing_spec = REQUIRED_SPEC - set(spec) + if missing_spec: + return fail(path, f"missing spec fields: {sorted(missing_spec)}") + if spec["surface"] not in ALLOWED_SURFACES: + return fail(path, f"unknown surface: {spec['surface']}") + if not spec["serviceRef"].startswith("service://"): + return fail(path, "serviceRef must be a service:// reference") + + policy = spec["policy"] + if policy.get("requiresSignedServiceRef") is not True: + return fail(path, "policy.requiresSignedServiceRef must be true") + + authority = spec["authority"] + if authority.get("sourceosRole") != "carry-only": + return fail(path, "authority.sourceosRole must be carry-only") + if authority.get("platformPromotionRequired") is not True: + return fail(path, "authority.platformPromotionRequired must be true") + if authority.get("mayReplaceServiceArtifact") is not False: + return fail(path, "authority.mayReplaceServiceArtifact must be false") + + launch = spec["launch"] + if "system" in launch.get("workspaceScopes", []): + return fail(path, "system workspace is not an allowed AI carry invocation scope") + + client = spec["client"] + if not client.get("packageRef") or not client.get("entrypoint"): + return fail(path, "client.packageRef and client.entrypoint are required") + + return 0 + + +def main() -> int: + files = sorted(EXAMPLES.glob("*-carry-ref.json")) + if not files: + print("ERROR: no carry reference examples found", file=sys.stderr) + return 1 + rc = 0 + for path in files: + rc = validate(path) or rc + if rc == 0: + print(f"OK: validated {len(files)} SourceOS carry references") + return rc + + +if __name__ == "__main__": + raise SystemExit(main()) From c6e76ba0c71a7fc7cbb2a52d39df41ff92c9d659 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Tue, 28 Apr 2026 12:46:44 -0400 Subject: [PATCH 10/16] Add validation Makefile --- Makefile | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..cc87ba9 --- /dev/null +++ b/Makefile @@ -0,0 +1,4 @@ +.PHONY: validate + +validate: + python3 tools/validate_carry_refs.py From 5761452481479d5baf5c3992b46b4deaf47527ad Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Tue, 28 Apr 2026 14:39:09 -0400 Subject: [PATCH 11/16] Add Go module for sourceos-ai --- go.mod | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 go.mod diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..e69b20c --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/SourceOS-Linux/sourceos-model-carry + +go 1.22 From 92c786c01613a2644ece00137412721cd956c408 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Tue, 28 Apr 2026 14:40:36 -0400 Subject: [PATCH 12/16] Add sourceos-ai CLI skeleton --- cmd/sourceos-ai/main.go | 315 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 315 insertions(+) create mode 100644 cmd/sourceos-ai/main.go diff --git a/cmd/sourceos-ai/main.go b/cmd/sourceos-ai/main.go new file mode 100644 index 0000000..8e1c93a --- /dev/null +++ b/cmd/sourceos-ai/main.go @@ -0,0 +1,315 @@ +package main + +import ( + "encoding/json" + "errors" + "flag" + "fmt" + "os" + "path/filepath" + "sort" + "strings" +) + +var ( + version = "0.1.0-dev" + commit = "unknown" + date = "unknown" +) + +type carryRef struct { + APIVersion string `json:"apiVersion"` + Kind string `json:"kind"` + Metadata struct { + Name string `json:"name"` + Version string `json:"version"` + } `json:"metadata"` + Spec struct { + Surface string `json:"surface"` + ServiceRef string `json:"serviceRef"` + Client struct { + PackageRef string `json:"packageRef"` + Entrypoint string `json:"entrypoint"` + Protocol string `json:"protocol"` + } `json:"client"` + Launch struct { + WorkspaceScopes []string `json:"workspaceScopes"` + DefaultMode string `json:"defaultMode"` + RequiresNetwork bool `json:"requiresNetwork"` + } `json:"launch"` + Cache struct { + Mode string `json:"mode"` + MaxBytes int64 `json:"maxBytes"` + PathHint string `json:"pathHint"` + } `json:"cache"` + Policy struct { + PolicyRef string `json:"policyRef"` + RequiresSignedServiceRef bool `json:"requiresSignedServiceRef"` + DataClasses []string `json:"dataClasses"` + } `json:"policy"` + Evidence struct { + EmitInvocationReceipt bool `json:"emitInvocationReceipt"` + EmitPolicyCheck bool `json:"emitPolicyCheck"` + ReceiptSink string `json:"receiptSink"` + } `json:"evidence"` + Authority struct { + SourceOSRole string `json:"sourceosRole"` + PlatformPromotionRequired bool `json:"platformPromotionRequired"` + MayReplaceServiceArtifact bool `json:"mayReplaceServiceArtifact"` + } `json:"authority"` + } `json:"spec"` +} + +type validationResult struct { + Path string `json:"path"` + Name string `json:"name,omitempty"` + Surface string `json:"surface,omitempty"` + Status string `json:"status"` + Errors []string `json:"errors,omitempty"` +} + +type evidence struct { + Tool string `json:"tool"` + Version string `json:"version"` + Commit string `json:"commit"` + BuildDate string `json:"buildDate"` + Repo string `json:"repo"` + Status string `json:"status"` + Results []validationResult `json:"results,omitempty"` + ServiceRef []string `json:"serviceRefs,omitempty"` +} + +func usage() { + fmt.Fprintf(os.Stderr, `sourceos-ai %s + +Usage: + sourceos-ai --version + sourceos-ai doctor [--refs examples] + sourceos-ai self-test [--refs examples] + sourceos-ai emit-evidence [--refs examples] + sourceos-ai carry list [--refs examples] + sourceos-ai carry validate [--refs examples] + sourceos-ai carry doctor [--refs examples] + +`, version) +} + +func main() { + if len(os.Args) == 1 { + usage() + os.Exit(2) + } + if os.Args[1] == "--version" || os.Args[1] == "version" { + fmt.Printf("sourceos-ai %s commit=%s date=%s\n", version, commit, date) + return + } + + cmd := os.Args[1] + switch cmd { + case "doctor": + refs := parseRefs(os.Args[2:]) + runDoctor(refs) + case "self-test": + refs := parseRefs(os.Args[2:]) + runSelfTest(refs) + case "emit-evidence": + refs := parseRefs(os.Args[2:]) + runEvidence(refs) + case "carry": + runCarry(os.Args[2:]) + default: + usage() + os.Exit(2) + } +} + +func parseRefs(args []string) string { + fs := flag.NewFlagSet("refs", flag.ExitOnError) + refs := fs.String("refs", "examples", "directory containing *-carry-ref.json files") + _ = fs.Parse(args) + return *refs +} + +func runCarry(args []string) { + if len(args) == 0 { + usage() + os.Exit(2) + } + sub := args[0] + refs := parseRefs(args[1:]) + switch sub { + case "list": + runList(refs) + case "validate": + results := validateDir(refs) + printJSON(results) + if hasFailures(results) { + os.Exit(1) + } + case "doctor": + runDoctor(refs) + default: + usage() + os.Exit(2) + } +} + +func runList(refDir string) { + refs, results := loadRefs(refDir) + if hasFailures(results) { + printJSON(results) + os.Exit(1) + } + sort.Slice(refs, func(i, j int) bool { return refs[i].Metadata.Name < refs[j].Metadata.Name }) + items := make([]map[string]any, 0, len(refs)) + for _, ref := range refs { + items = append(items, map[string]any{ + "name": ref.Metadata.Name, + "version": ref.Metadata.Version, + "surface": ref.Spec.Surface, + "serviceRef": ref.Spec.ServiceRef, + "mode": ref.Spec.Launch.DefaultMode, + "authority": ref.Spec.Authority.SourceOSRole, + }) + } + printJSON(map[string]any{"status": "ok", "count": len(items), "items": items}) +} + +func runDoctor(refDir string) { + results := validateDir(refDir) + status := "ok" + if hasFailures(results) { + status = "failed" + } + printJSON(map[string]any{ + "tool": "sourceos-ai", + "version": version, + "status": status, + "checks": results, + }) + if status != "ok" { + os.Exit(1) + } +} + +func runSelfTest(refDir string) { + results := validateDir(refDir) + status := "ok" + if hasFailures(results) { + status = "failed" + } + printJSON(map[string]any{"tool": "sourceos-ai", "selfTest": status, "validatedRefs": len(results)}) + if status != "ok" { + os.Exit(1) + } +} + +func runEvidence(refDir string) { + refs, results := loadRefs(refDir) + status := "ok" + if hasFailures(results) { + status = "failed" + } + serviceRefs := make([]string, 0, len(refs)) + for _, ref := range refs { + serviceRefs = append(serviceRefs, ref.Spec.ServiceRef) + } + sort.Strings(serviceRefs) + printJSON(evidence{ + Tool: "sourceos-ai", + Version: version, + Commit: commit, + BuildDate: date, + Repo: "SourceOS-Linux/sourceos-model-carry", + Status: status, + Results: results, + ServiceRef: serviceRefs, + }) + if status != "ok" { + os.Exit(1) + } +} + +func validateDir(refDir string) []validationResult { + _, results := loadRefs(refDir) + return results +} + +func loadRefs(refDir string) ([]carryRef, []validationResult) { + paths, err := filepath.Glob(filepath.Join(refDir, "*-carry-ref.json")) + if err != nil { + return nil, []validationResult{{Path: refDir, Status: "failed", Errors: []string{err.Error()}}} + } + sort.Strings(paths) + if len(paths) == 0 { + return nil, []validationResult{{Path: refDir, Status: "failed", Errors: []string{"no *-carry-ref.json files found"}}} + } + refs := make([]carryRef, 0, len(paths)) + results := make([]validationResult, 0, len(paths)) + for _, path := range paths { + ref, errs := loadRef(path) + result := validationResult{Path: path, Name: ref.Metadata.Name, Surface: ref.Spec.Surface, Status: "ok"} + if len(errs) > 0 { + result.Status = "failed" + result.Errors = errs + } else { + refs = append(refs, ref) + } + results = append(results, result) + } + return refs, results +} + +func loadRef(path string) (carryRef, []string) { + var ref carryRef + bytes, err := os.ReadFile(path) + if err != nil { + return ref, []string{err.Error()} + } + if err := json.Unmarshal(bytes, &ref); err != nil { + return ref, []string{err.Error()} + } + return ref, validateRef(ref) +} + +func validateRef(ref carryRef) []string { + var errs []string + check := func(cond bool, msg string) { + if !cond { + errs = append(errs, msg) + } + } + check(ref.APIVersion == "modelcarry.sourceos.dev/v1", "apiVersion must be modelcarry.sourceos.dev/v1") + check(ref.Kind == "SourceOSCarryRef", "kind must be SourceOSCarryRef") + check(ref.Metadata.Name != "", "metadata.name is required") + check(ref.Metadata.Version != "", "metadata.version is required") + check(ref.Spec.Surface != "", "spec.surface is required") + check(strings.HasPrefix(ref.Spec.ServiceRef, "service://"), "spec.serviceRef must start with service://") + check(ref.Spec.Client.PackageRef != "", "spec.client.packageRef is required") + check(ref.Spec.Client.Entrypoint != "", "spec.client.entrypoint is required") + check(ref.Spec.Policy.RequiresSignedServiceRef, "spec.policy.requiresSignedServiceRef must be true") + check(ref.Spec.Authority.SourceOSRole == "carry-only", "spec.authority.sourceosRole must be carry-only") + check(ref.Spec.Authority.PlatformPromotionRequired, "spec.authority.platformPromotionRequired must be true") + check(!ref.Spec.Authority.MayReplaceServiceArtifact, "spec.authority.mayReplaceServiceArtifact must be false") + for _, scope := range ref.Spec.Launch.WorkspaceScopes { + check(scope != "system", "system workspace is not an allowed AI carry invocation scope") + } + return errs +} + +func hasFailures(results []validationResult) bool { + for _, result := range results { + if result.Status != "ok" { + return true + } + } + return false +} + +func printJSON(value any) { + enc := json.NewEncoder(os.Stdout) + enc.SetIndent("", " ") + if err := enc.Encode(value); err != nil { + panic(errors.New("failed to encode JSON: " + err.Error())) + } +} From e657ef634af8ff90808d43f1d8a4e716b889a09c Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Tue, 28 Apr 2026 14:43:30 -0400 Subject: [PATCH 13/16] Add sourceos-ai build and release targets --- Makefile | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index cc87ba9..66ee7f9 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,35 @@ -.PHONY: validate +.PHONY: build test validate dist release-dry-run clean -validate: +BIN := sourceos-ai +DIST_DIR := dist +VERSION ?= 0.1.0-dev +COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown) +DATE ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ) +LDFLAGS := -X main.version=$(VERSION) -X main.commit=$(COMMIT) -X main.date=$(DATE) + +build: + mkdir -p bin + go build -ldflags "$(LDFLAGS)" -o bin/$(BIN) ./cmd/sourceos-ai + +test: + go test ./... + +validate: build python3 tools/validate_carry_refs.py + bin/$(BIN) carry validate --refs examples + bin/$(BIN) doctor --refs examples + bin/$(BIN) self-test --refs examples + bin/$(BIN) emit-evidence --refs examples >/tmp/sourceos-ai-evidence.json + +tdist_name = $(BIN)_$(VERSION)_$(shell uname -s | tr A-Z a-z)_$(shell uname -m) + +dist: validate + mkdir -p $(DIST_DIR) + cp bin/$(BIN) $(DIST_DIR)/$(tdist_name) + (cd $(DIST_DIR) && shasum -a 256 $(tdist_name) > $(tdist_name).sha256) + +release-dry-run: dist + @echo "release dry-run complete: $(DIST_DIR)/$(tdist_name)" + +clean: + rm -rf bin $(DIST_DIR) From 43261d17e71b3eb954eee9f454de194b3f2c8c7f Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Tue, 28 Apr 2026 14:44:02 -0400 Subject: [PATCH 14/16] Add sourceos-ai CLI docs --- docs/SOURCEOS_AI_CLI.md | 61 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 docs/SOURCEOS_AI_CLI.md diff --git a/docs/SOURCEOS_AI_CLI.md b/docs/SOURCEOS_AI_CLI.md new file mode 100644 index 0000000..2cd55c5 --- /dev/null +++ b/docs/SOURCEOS_AI_CLI.md @@ -0,0 +1,61 @@ +# sourceos-ai CLI + +`sourceos-ai` is the on-device AI carry client for SourceOS. + +It validates signed-reference-shaped service carry manifests, lists available governed AI service references, and emits local evidence about what the device can invoke. It does not promote models, replace service artifacts, or own mutable model lifecycle state. + +## Command contract + +```bash +sourceos-ai --version +sourceos-ai doctor --refs examples +sourceos-ai self-test --refs examples +sourceos-ai emit-evidence --refs examples +sourceos-ai carry list --refs examples +sourceos-ai carry validate --refs examples +sourceos-ai carry doctor --refs examples +``` + +## Build and validation + +```bash +make build +make test +make validate +make dist +make release-dry-run +``` + +`make validate` runs both the legacy Python carry-ref validator and the compiled `sourceos-ai` validation path. + +## Carry-only invariant + +Every valid carry reference must satisfy: + +- `policy.requiresSignedServiceRef == true` +- `authority.sourceosRole == carry-only` +- `authority.platformPromotionRequired == true` +- `authority.mayReplaceServiceArtifact == false` +- no `system` workspace invocation scope + +This keeps SourceOS in the correct product role: clients, launch profiles, cache policy, signed service references, and evidence collectors only. + +## Prophet CLI delegation + +`prophet-cli` should delegate these command families to `sourceos-ai`: + +```bash +prophet sourceos carry list +prophet sourceos carry validate +prophet sourceos carry doctor +prophet sourceos carry emit-evidence +``` + +## Homebrew target + +The future Homebrew formula should install the `sourceos-ai` binary and run a formula test equivalent to: + +```bash +sourceos-ai --version +sourceos-ai self-test --refs examples +``` From a0501bb1ab7028b6f2f6264af0f3ccea0089806a Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Tue, 28 Apr 2026 14:44:28 -0400 Subject: [PATCH 15/16] Add validation workflow --- .github/workflows/validate.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .github/workflows/validate.yml diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 0000000..61ab575 --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,27 @@ +name: validate + +on: + pull_request: + push: + branches: [main] + +permissions: + contents: read + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.22' + + - name: Validate + run: make validate + + - name: Release dry run + run: make release-dry-run From 2d82c2c8b5a0e639399359a6205afa9eac60eb26 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Tue, 28 Apr 2026 14:45:21 -0400 Subject: [PATCH 16/16] Harden sourceos-ai release target names and checksums --- Makefile | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 66ee7f9..24111fb 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,9 @@ DIST_DIR := dist VERSION ?= 0.1.0-dev COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown) DATE ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ) +GOOS ?= $(shell go env GOOS 2>/dev/null || uname -s | tr A-Z a-z) +GOARCH ?= $(shell go env GOARCH 2>/dev/null || uname -m) +DIST_NAME := $(BIN)_$(VERSION)_$(GOOS)_$(GOARCH) LDFLAGS := -X main.version=$(VERSION) -X main.commit=$(COMMIT) -X main.date=$(DATE) build: @@ -21,15 +24,13 @@ validate: build bin/$(BIN) self-test --refs examples bin/$(BIN) emit-evidence --refs examples >/tmp/sourceos-ai-evidence.json -tdist_name = $(BIN)_$(VERSION)_$(shell uname -s | tr A-Z a-z)_$(shell uname -m) - dist: validate mkdir -p $(DIST_DIR) - cp bin/$(BIN) $(DIST_DIR)/$(tdist_name) - (cd $(DIST_DIR) && shasum -a 256 $(tdist_name) > $(tdist_name).sha256) + cp bin/$(BIN) $(DIST_DIR)/$(DIST_NAME) + (cd $(DIST_DIR) && sha256sum $(DIST_NAME) > $(DIST_NAME).sha256) release-dry-run: dist - @echo "release dry-run complete: $(DIST_DIR)/$(tdist_name)" + @echo "release dry-run complete: $(DIST_DIR)/$(DIST_NAME)" clean: rm -rf bin $(DIST_DIR)