From d05649ddc0259a393960a9411157878e553ae2bc Mon Sep 17 00:00:00 2001 From: Michael Heller <21163552+mdheller@users.noreply.github.com> Date: Sat, 4 Jul 2026 13:42:27 -0400 Subject: [PATCH] feat: maturity spine (repo-maturity.v1 + lab.manifest + canonical functional-service) Add repo.maturity.yaml, lab.manifest.json, and service-manifest/functional-service.v1.json conforming to the canonical SocioProphet/functional-model-surfaces schemas (vendored under schemas/), plus datasets/evals/adapters/training-runs scaffolding, offline validate_spine wired into make validate, and CI with strict jsonschema validation. Coexists with the existing capd/caps capability structure; no capability files touched. --- .github/workflows/validate.yml | 15 +-- Makefile | 7 +- adapters/README.md | 3 + datasets/README.md | 3 + evals/README.md | 3 + lab.manifest.json | 28 +++++ repo.maturity.yaml | 48 +++++++++ schemas/functional-service.schema.json | 97 +++++++++++++++++ schemas/repo-maturity.schema.json | 110 ++++++++++++++++++++ service-manifest/functional-service.v1.json | 54 ++++++++++ tools/validate_maturity.py | 38 +++++++ tools/validate_spine.py | 91 ++++++++++++++++ training-runs/README.md | 3 + 13 files changed, 492 insertions(+), 8 deletions(-) create mode 100644 adapters/README.md create mode 100644 datasets/README.md create mode 100644 evals/README.md create mode 100644 lab.manifest.json create mode 100644 repo.maturity.yaml create mode 100644 schemas/functional-service.schema.json create mode 100644 schemas/repo-maturity.schema.json create mode 100644 service-manifest/functional-service.v1.json create mode 100644 tools/validate_maturity.py create mode 100644 tools/validate_spine.py create mode 100644 training-runs/README.md diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 3bdd2b3..8f8d0cb 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -1,17 +1,20 @@ name: validate - on: push: pull_request: - jobs: validate: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Validate speechlab + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Install strict-validation deps + run: pip install pyyaml jsonschema + - name: make validate (spine + functional-service, offline) run: make validate - - name: Offline smoke test + - name: make smoke (deterministic offline) run: make smoke - - name: Emit SourceOS carry fixture - run: make carry + - name: Strict jsonschema validation of maturity record + run: python3 tools/validate_maturity.py schemas/repo-maturity.schema.json repo.maturity.yaml diff --git a/Makefile b/Makefile index 9900b5f..8fe8aff 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,11 @@ -.PHONY: validate smoke carry +.PHONY: validate validate-spine smoke carry -validate: +validate: validate-spine python3 tools/validate.py +validate-spine: + python3 tools/validate_spine.py + smoke: python3 tools/smoke.py diff --git a/adapters/README.md b/adapters/README.md new file mode 100644 index 0000000..d87fd6d --- /dev/null +++ b/adapters/README.md @@ -0,0 +1,3 @@ +# adapters/ + +Adapter **references** (LoRA/PEFT/prompt adapters) by URN or digest only. No adapter weights are committed. `service-manifest` `model.adapterRefs` cites entries here. Adapters never carry mutable state into SourceOS. diff --git a/datasets/README.md b/datasets/README.md new file mode 100644 index 0000000..fbd67b5 --- /dev/null +++ b/datasets/README.md @@ -0,0 +1,3 @@ +# datasets/ + +Dataset **references and provenance manifests only** — no raw datasets, no model weights, no secrets are committed here. Each entry records source, license, and a content digest so promotion evidence can cite verifiable dataset provenance. diff --git a/evals/README.md b/evals/README.md new file mode 100644 index 0000000..3d3af46 --- /dev/null +++ b/evals/README.md @@ -0,0 +1,3 @@ +# evals/ + +Evaluation fixtures and signed eval results for governed promotion. The offline smoke (`make smoke`) is the M2 gate; richer eval references are cited from `service-manifest/functional-service.v1.json` under `evals.references`. diff --git a/lab.manifest.json b/lab.manifest.json new file mode 100644 index 0000000..67951fd --- /dev/null +++ b/lab.manifest.json @@ -0,0 +1,28 @@ +{ + "schemaVersion": "lab.manifest.v1", + "lab": "speechlab", + "repository": "SociOS-Linux/speechlab", + "modality": "speech", + "boundary": "lab-only: emits governed functional-service manifests and evaluation evidence for downstream platform services; must not self-promote models into production and must not carry mutable model state into SourceOS image builds.", + "surfaces": [ + "speech-transcription", + "speaker-diarization", + "voice-activity-detection", + "audio-evidence-extraction" + ], + "serviceManifests": [ + "service-manifest/functional-service.v1.json" + ], + "governanceHandoff": { + "ledger": "SocioProphet/model-governance-ledger", + "guardrail": "SocioProphet/guardrail-fabric", + "router": "SocioProphet/model-router", + "productSurface": "SocioProphet/holmes", + "sourceosCarry": "SourceOS-Linux/sourceos-model-carry" + }, + "sourceosCarry": { + "allowed": true, + "carriesMutableModelState": false, + "clientRefRequired": true + } +} diff --git a/repo.maturity.yaml b/repo.maturity.yaml new file mode 100644 index 0000000..0dd25a8 --- /dev/null +++ b/repo.maturity.yaml @@ -0,0 +1,48 @@ +schemaVersion: repo-maturity.v1 +repository: SociOS-Linux/speechlab +plane: socios-lab +status: experimental +canonicality: reference +owners: + - SociOS-Linux +maturity: + level: M2 + targetLevel: M3 + evidence: + - README.md states lab purpose, lab-only boundary, and integration points. + - service-manifest/functional-service.v1.json conforms to functional-service.v1. + - lab.manifest.json enumerates 4 governed lab surfaces. + - make validate exits 0 (offline, deterministic). + - CI runs make validate + strict jsonschema validation on every push/PR. +validation: + commands: + - make validate + - make smoke + ciRequired: true + lastKnownStatus: passing +integrations: + - repository: SocioProphet/functional-model-surfaces + relationship: emits functional-service.v1 manifests + required: true + - repository: SocioProphet/model-governance-ledger + relationship: sends promotion evidence + required: true + - repository: SocioProphet/sociosphere + relationship: registers maturity/evidence status + required: true + - repository: SocioProphet/holmes + relationship: provides the speech functional surface to Holmes + required: false + - repository: SocioProphet/model-router + relationship: routable governed surface + required: false + - repository: SocioProphet/guardrail-fabric + relationship: guardrail policy enforcement + required: false + - repository: SourceOS-Linux/sourceos-model-carry + relationship: disabled-by-default SourceOS carry reference + required: false +nextActions: + - Add negative fixtures under fixtures/invalid/ (>=2 per schema) to reach M3. + - Wire promotion evidence into SocioProphet/model-governance-ledger. + - Register maturity record with SocioProphet/sociosphere workspace-inventory. diff --git a/schemas/functional-service.schema.json b/schemas/functional-service.schema.json new file mode 100644 index 0000000..7384bd0 --- /dev/null +++ b/schemas/functional-service.schema.json @@ -0,0 +1,97 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.socioprophet.org/functional-model-surfaces/functional-service.v1.json", + "title": "SocioProphet Functional Service Manifest", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaVersion", + "service", + "function", + "model", + "inputs", + "outputs", + "evals", + "governance", + "sourceosCarry" + ], + "properties": { + "schemaVersion": { "type": "string", "const": "functional-service.v1" }, + "service": { + "type": "object", + "additionalProperties": false, + "required": ["id", "name", "ownerRepository", "status"], + "properties": { + "id": { "type": "string", "pattern": "^[a-z0-9][a-z0-9_.-]+$" }, + "name": { "type": "string" }, + "ownerRepository": { "type": "string", "pattern": "^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$" }, + "status": { "type": "string", "enum": ["draft", "experimental", "candidate", "approved", "deprecated"] }, + "description": { "type": "string" } + } + }, + "function": { + "type": "string", + "enum": [ + "speech", + "ocr", + "image", + "video", + "translation", + "embedding", + "reranking", + "timeseries", + "graph", + "nlp", + "language-intelligence", + "routing", + "guardrail", + "agent-tool" + ] + }, + "model": { + "type": "object", + "additionalProperties": false, + "required": ["modelRef", "mutableStatePolicy"], + "properties": { + "modelRef": { "type": "string" }, + "adapterRefs": { "type": "array", "items": { "type": "string" } }, + "runtime": { "type": "string" }, + "mutableStatePolicy": { "type": "string", "enum": ["forbidden-in-sourceos", "lab-only", "governed-runtime-only"] } + } + }, + "inputs": { "type": "array", "items": { "type": "string" } }, + "outputs": { "type": "array", "items": { "type": "string" } }, + "evals": { + "type": "object", + "additionalProperties": false, + "required": ["required", "references"], + "properties": { + "required": { "type": "boolean" }, + "references": { "type": "array", "items": { "type": "string" } }, + "minimumPromotionGate": { "type": "string" } + } + }, + "governance": { + "type": "object", + "additionalProperties": false, + "required": ["ledgerRequired", "guardrailRequired", "routingRequired"], + "properties": { + "ledgerRequired": { "type": "boolean" }, + "guardrailRequired": { "type": "boolean" }, + "routingRequired": { "type": "boolean" }, + "policyRefs": { "type": "array", "items": { "type": "string" } } + } + }, + "sourceosCarry": { + "type": "object", + "additionalProperties": false, + "required": ["allowed", "carriesMutableModelState", "clientRefRequired"], + "properties": { + "allowed": { "type": "boolean" }, + "carriesMutableModelState": { "type": "boolean", "const": false }, + "clientRefRequired": { "type": "boolean" }, + "launchProfileRefs": { "type": "array", "items": { "type": "string" } } + } + } + } +} diff --git a/schemas/repo-maturity.schema.json b/schemas/repo-maturity.schema.json new file mode 100644 index 0000000..d87d891 --- /dev/null +++ b/schemas/repo-maturity.schema.json @@ -0,0 +1,110 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.socioprophet.org/functional-model-surfaces/repo-maturity.v1.json", + "title": "SocioProphet Repository Maturity Record", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaVersion", + "repository", + "plane", + "status", + "maturity", + "owners", + "canonicality", + "validation", + "integrations" + ], + "properties": { + "schemaVersion": { + "type": "string", + "const": "repo-maturity.v1" + }, + "repository": { + "type": "string", + "pattern": "^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$" + }, + "plane": { + "type": "string", + "enum": [ + "standards", + "runtime", + "governance", + "sourceos-carry", + "socios-lab", + "workspace-governance", + "cli", + "research", + "archive" + ] + }, + "status": { + "type": "string", + "enum": ["active", "experimental", "incubating", "archival", "deprecated"] + }, + "maturity": { + "type": "object", + "additionalProperties": false, + "required": ["level", "targetLevel", "evidence"], + "properties": { + "level": { + "type": "string", + "enum": ["M0", "M1", "M2", "M3", "M4", "M5"] + }, + "targetLevel": { + "type": "string", + "enum": ["M1", "M2", "M3", "M4", "M5"] + }, + "evidence": { + "type": "array", + "items": { "type": "string" } + } + } + }, + "owners": { + "type": "array", + "minItems": 1, + "items": { "type": "string" } + }, + "canonicality": { + "type": "string", + "enum": ["canonical", "reference", "experimental", "archive", "stub"] + }, + "validation": { + "type": "object", + "additionalProperties": false, + "required": ["commands", "ciRequired"], + "properties": { + "commands": { + "type": "array", + "items": { "type": "string" } + }, + "ciRequired": { "type": "boolean" }, + "lastKnownStatus": { + "type": "string", + "enum": ["unknown", "passing", "failing", "not-configured"] + } + } + }, + "integrations": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["repository", "relationship"], + "properties": { + "repository": { + "type": "string", + "pattern": "^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$" + }, + "relationship": { "type": "string" }, + "required": { "type": "boolean" } + } + } + }, + "nextActions": { + "type": "array", + "items": { "type": "string" } + } + } +} diff --git a/service-manifest/functional-service.v1.json b/service-manifest/functional-service.v1.json new file mode 100644 index 0000000..1d8ad21 --- /dev/null +++ b/service-manifest/functional-service.v1.json @@ -0,0 +1,54 @@ +{ + "schemaVersion": "functional-service.v1", + "service": { + "id": "speechlab.holmes.speech-pilot", + "name": "SpeechLab Holmes Speech Pilot", + "ownerRepository": "SociOS-Linux/speechlab", + "status": "experimental", + "description": "Offline deterministic speech functional surface emitted by speechlab for governed promotion." + }, + "function": "speech", + "model": { + "modelRef": "urn:socioprophet:functional-model:speechlab:deterministic-speech:v0", + "adapterRefs": [], + "runtime": "python-stdlib-offline-smoke", + "mutableStatePolicy": "forbidden-in-sourceos" + }, + "inputs": [ + "audio/query", + "audio/corpus" + ], + "outputs": [ + "speech/features", + "ranking/candidates", + "evidence/smoke-receipt" + ], + "evals": { + "required": true, + "references": [ + "examples/smoke-input.json", + "examples/smoke-output.json", + "evidence/smoke-receipt.example.json" + ], + "minimumPromotionGate": "candidate requires signed eval and dataset provenance" + }, + "governance": { + "ledgerRequired": true, + "guardrailRequired": true, + "routingRequired": true, + "policyRefs": [ + "SocioProphet/model-governance-ledger", + "SocioProphet/guardrail-fabric", + "SocioProphet/model-router", + "SocioProphet/holmes" + ] + }, + "sourceosCarry": { + "allowed": true, + "carriesMutableModelState": false, + "clientRefRequired": true, + "launchProfileRefs": [ + "urn:srcos:model-carry:functional-service:speechlab-holmes-pilot" + ] + } +} diff --git a/tools/validate_maturity.py b/tools/validate_maturity.py new file mode 100644 index 0000000..dc57cb0 --- /dev/null +++ b/tools/validate_maturity.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +"""Validate repo.maturity.yaml against the JSON Schema. + +This helper intentionally supports a small YAML subset through PyYAML when available. +""" +import json +import sys +from pathlib import Path + +try: + import jsonschema +except Exception as exc: # pragma: no cover + print(f"jsonschema import failed: {exc}", file=sys.stderr) + sys.exit(2) + +try: + import yaml +except Exception as exc: # pragma: no cover + print(f"PyYAML import failed: {exc}", file=sys.stderr) + sys.exit(2) + + +def main() -> int: + if len(sys.argv) != 3: + print("usage: validate_maturity.py ", file=sys.stderr) + return 2 + schema_path = Path(sys.argv[1]) + doc_path = Path(sys.argv[2]) + schema = json.loads(schema_path.read_text()) + document = yaml.safe_load(doc_path.read_text()) + jsonschema.Draft202012Validator.check_schema(schema) + jsonschema.validate(instance=document, schema=schema) + print(f"ok: {doc_path} validates against {schema_path}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/validate_spine.py b/tools/validate_spine.py new file mode 100644 index 0000000..68608a7 --- /dev/null +++ b/tools/validate_spine.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +"""Offline maturity-spine validator: repo.maturity.yaml (repo-maturity.v1), +lab.manifest.json (lab.manifest.v1), service-manifest/functional-service.v1.json +(functional-service.v1). Zero required deps; PyYAML enables strict maturity checks.""" +import json, re, sys, os + +FUNCTION_ENUM={"speech","ocr","image","video","translation","embedding","reranking", + "timeseries","graph","nlp","language-intelligence","routing","guardrail","agent-tool"} +STATUS_ENUM={"draft","experimental","candidate","approved","deprecated"} +MSP_ENUM={"forbidden-in-sourceos","lab-only","governed-runtime-only"} +PLANE_ENUM={"standards","runtime","governance","sourceos-carry","socios-lab","workspace-governance","cli","research","archive"} +MSTATUS_ENUM={"active","experimental","incubating","archival","deprecated"} +LEVELS={"M0","M1","M2","M3","M4","M5"} +CANON_ENUM={"canonical","reference","experimental","archive","stub"} + +def fail(m): print(f"FAIL: {m}",file=sys.stderr); sys.exit(1) + +def req_files(): + for f in ["repo.maturity.yaml","lab.manifest.json","service-manifest/functional-service.v1.json", + "schemas/repo-maturity.schema.json","schemas/functional-service.schema.json", + "datasets/README.md","evals/README.md","adapters/README.md","training-runs/README.md"]: + if not os.path.exists(f): fail(f"missing required file: {f}") + +def check_schemas(): + for s in ("schemas/repo-maturity.schema.json","schemas/functional-service.schema.json"): + try: json.load(open(s)) + except Exception as e: fail(f"{s} invalid JSON: {e}") + +def check_service(): + d=json.load(open("service-manifest/functional-service.v1.json")) + if d.get("schemaVersion")!="functional-service.v1": fail("service schemaVersion") + s=d.get("service",{}) + for k in ("id","name","ownerRepository","status"): + if k not in s: fail(f"service.{k} required") + if not re.match(r"^[a-z0-9][a-z0-9_.-]+$",s["id"]): fail(f"service.id pattern: {s['id']}") + if s["status"] not in STATUS_ENUM: fail(f"service.status enum: {s['status']}") + if not re.match(r"^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$",s["ownerRepository"]): fail("ownerRepository") + if d.get("function") not in FUNCTION_ENUM: fail(f"function not in canonical enum: {d.get('function')}") + m=d.get("model",{}) + if "modelRef" not in m: fail("model.modelRef required") + if m.get("mutableStatePolicy") not in MSP_ENUM: fail(f"mutableStatePolicy enum: {m.get('mutableStatePolicy')}") + if not isinstance(d.get("inputs"),list) or not isinstance(d.get("outputs"),list): fail("inputs/outputs arrays") + ev=d.get("evals",{}) + if not isinstance(ev.get("required"),bool) or not isinstance(ev.get("references"),list): fail("evals") + g=d.get("governance",{}) + for k in ("ledgerRequired","guardrailRequired","routingRequired"): + if not isinstance(g.get(k),bool): fail(f"governance.{k}") + sc=d.get("sourceosCarry",{}) + if sc.get("carriesMutableModelState") is not False: fail("carriesMutableModelState must be false") + for k in ("allowed","clientRefRequired"): + if not isinstance(sc.get(k),bool): fail(f"sourceosCarry.{k}") + +def check_lab(): + d=json.load(open("lab.manifest.json")) + if d.get("schemaVersion")!="lab.manifest.v1": fail("lab.manifest schemaVersion") + for k in ("lab","repository","modality","boundary","surfaces","serviceManifests"): + if k not in d: fail(f"lab.manifest.{k} required") + if not isinstance(d["surfaces"],list) or not d["surfaces"]: fail("surfaces non-empty array") + if d["modality"] not in FUNCTION_ENUM: fail(f"modality enum: {d['modality']}") + +def check_maturity(): + txt=open("repo.maturity.yaml").read() + try: + import yaml; d=yaml.safe_load(txt) + except Exception: + for key in ("schemaVersion: repo-maturity.v1","repository:","plane:","status:","maturity:", + "owners:","canonicality:","validation:","integrations:"): + if key not in txt: fail(f"repo.maturity.yaml missing {key!r} (install pyyaml for strict)") + return + if d.get("schemaVersion")!="repo-maturity.v1": fail("maturity schemaVersion") + for k in ("repository","plane","status","maturity","owners","canonicality","validation","integrations"): + if k not in d: fail(f"repo.maturity.{k} required") + if d["plane"] not in PLANE_ENUM: fail(f"plane enum: {d['plane']}") + if d["status"] not in MSTATUS_ENUM: fail(f"status enum: {d['status']}") + if d["canonicality"] not in CANON_ENUM: fail(f"canonicality enum: {d['canonicality']}") + mat=d["maturity"] + if mat.get("level") not in LEVELS: fail("maturity.level") + if mat.get("targetLevel") not in (LEVELS-{"M0"}): fail("maturity.targetLevel") + if not isinstance(mat.get("evidence"),list): fail("maturity.evidence array") + if not (isinstance(d["owners"],list) and d["owners"]): fail("owners non-empty") + v=d["validation"] + if not isinstance(v.get("commands"),list) or not isinstance(v.get("ciRequired"),bool): fail("validation") + if not isinstance(d["integrations"],list) or not d["integrations"]: fail("integrations non-empty") + for it in d["integrations"]: + if "repository" not in it or "relationship" not in it: fail("integration repository+relationship") + +def main(): + req_files(); check_schemas(); check_service(); check_lab(); check_maturity() + print("OK: speechlab maturity spine (repo-maturity.v1 + lab.manifest.v1 + functional-service.v1) validated") + +if __name__=="__main__": main() diff --git a/training-runs/README.md b/training-runs/README.md new file mode 100644 index 0000000..6165f36 --- /dev/null +++ b/training-runs/README.md @@ -0,0 +1,3 @@ +# training-runs/ + +Training/eval **run records** (metadata, config hashes, metrics, evidence pointers) — not checkpoints. These records feed promotion evidence to `SocioProphet/model-governance-ledger`.