diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 0000000..51a7623 --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,16 @@ +name: validate +on: + push: + pull_request: +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Install strict-validation deps + run: pip install jsonschema + - name: make validate + run: make validate diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..23fff8d --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +__pycache__/ +*.py[cod] +.terraform/ +*.tfstate +*.tfstate.* +.env diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e08e9be --- /dev/null +++ b/Makefile @@ -0,0 +1,4 @@ +.PHONY: validate + +validate: + python3 tools/validate_examples.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..2a36a20 --- /dev/null +++ b/README.md @@ -0,0 +1,38 @@ +# sourceos-build + +Canonical **SourceOS build substrate**: infrastructure-as-code, build orchestration +(Tekton / Argo CD), typed `BuildRequest` / `BuildReceipt` schemas, and artifact build +receipts for the SourceOS / SociOS estate. + +## Boundary (what this repo is — and is not) + +**Ships:** IaC skeletons (Terraform, Ansible), CD/pipeline skeletons (Argo CD, Tekton), +JSON Schemas for build requests and receipts, example payloads, and a local schema +validation target. + +**Does NOT ship:** product or runtime application code, model weights, datasets, secrets, +registry credentials, or Foreman/Katello credentials. This is not a `sourceos-platform` +monorepo and does not absorb `agentplane`, `workstation-contracts`, or `socios` +responsibilities. `socios` remains the opt-in automation/orchestration commons that +*drives* this substrate. + +## Layout + +| Path | Purpose | +|------|---------| +| `schemas/sourceos/` | `build-request.v0.1` and `build-receipt.v0.1` JSON Schemas | +| `examples/` | Example `BuildRequest` / `BuildReceipt` payloads (validate against the schemas) | +| `terraform/` | Terraform skeleton (build infra) | +| `ansible/` | Ansible skeleton (build host config) | +| `argocd/` | Argo CD Application skeleton | +| `tekton/` | Tekton pipeline + task skeleton | +| `tools/validate_examples.py` | Validates the example payloads against the schemas | +| `docs/adr/` | Architecture decision records | + +## Validate + +``` +make validate +``` + +Offline, deterministic; no network or credentials required. diff --git a/ansible/README.md b/ansible/README.md new file mode 100644 index 0000000..2b0c13f --- /dev/null +++ b/ansible/README.md @@ -0,0 +1,3 @@ +# ansible/ + +Ansible skeleton for configuring SourceOS build hosts. Skeleton only — no inventory secrets, no vault contents committed. diff --git a/ansible/inventory.example.ini b/ansible/inventory.example.ini new file mode 100644 index 0000000..4bf06a1 --- /dev/null +++ b/ansible/inventory.example.ini @@ -0,0 +1,2 @@ +[build] +# build-host-1 ansible_host=REPLACE_ME diff --git a/ansible/playbook.yml b/ansible/playbook.yml new file mode 100644 index 0000000..b2362a3 --- /dev/null +++ b/ansible/playbook.yml @@ -0,0 +1,9 @@ +--- +# SourceOS build host configuration — skeleton (no secrets). +- name: Configure SourceOS build host + hosts: build + become: true + tasks: + - name: Placeholder — install build toolchain + ansible.builtin.debug: + msg: "Skeleton task; real roles wired per environment." diff --git a/argocd/application.yaml b/argocd/application.yaml new file mode 100644 index 0000000..f89e6c9 --- /dev/null +++ b/argocd/application.yaml @@ -0,0 +1,18 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: sourceos-build + namespace: argocd +spec: + project: default + source: + repoURL: https://github.com/SociOS-Linux/sourceos-build + targetRevision: main + path: tekton + destination: + server: https://kubernetes.default.svc + namespace: sourceos-build + syncPolicy: + automated: + prune: false + selfHeal: false diff --git a/docs/adr/ADR-0001-sourceos-build-substrate.md b/docs/adr/ADR-0001-sourceos-build-substrate.md new file mode 100644 index 0000000..d11a391 --- /dev/null +++ b/docs/adr/ADR-0001-sourceos-build-substrate.md @@ -0,0 +1,32 @@ +# ADR-0001: SourceOS build substrate as a dedicated repository + +- Status: Accepted +- Date: 2026-07-04 + +## Context + +SourceOS risked becoming a monolithic platform repository. Build orchestration, IaC, +and artifact provenance were being scattered into product repositories and into `socios`. + +## Decision + +Create `SociOS-Linux/sourceos-build` as the canonical build substrate. It holds: + +- IaC (Terraform, Ansible) for build infrastructure; +- build orchestration (Tekton pipelines, Argo CD applications); +- typed `BuildRequest` / `BuildReceipt` schemas and example payloads; +- artifact build receipts (provenance + digests). + +`socios` remains the opt-in automation/orchestration commons that *drives* this substrate +by emitting `BuildRequest` documents and consuming `BuildReceipt` documents. + +## Consequences + +- Clear separation: substrate (this repo) vs. orchestration commons (`socios`) vs. product/runtime. +- Downstream consumers depend on stable schema paths (`schemas/sourceos/*.schema.json`). +- No secrets, credentials, weights, or datasets are committed here. + +## Non-goals + +- Recreating a `sourceos-platform` monorepo. +- Moving `agentplane`, `workstation-contracts`, or `socios` responsibilities into this repo. diff --git a/examples/build-receipt.example.json b/examples/build-receipt.example.json new file mode 100644 index 0000000..57e622d --- /dev/null +++ b/examples/build-receipt.example.json @@ -0,0 +1,27 @@ +{ + "schemaVersion": "build-receipt.v0.1", + "buildId": "build.sourceos-smoke-runner.0001", + "requestRef": "req.socios.sourceos-smoke-runner.2026-07-04", + "status": "succeeded", + "artifacts": [ + { + "name": "sourceos-smoke-runner", + "type": "container-image", + "digest": "sha256:0000000000000000000000000000000000000000000000000000000000000000", + "ref": "registry.example/sourceos/smoke-runner@sha256:0000000000000000000000000000000000000000000000000000000000000000" + } + ], + "provenance": { + "builder": "tekton://sourceos-build/pipeline-build-image", + "sourceCommit": "0000000000000000000000000000000000000000", + "slsaLevel": 1 + }, + "signatures": [ + { + "keyId": "sourceos-build-signing-v0", + "algorithm": "ed25519", + "value": "" + } + ], + "completedAt": "2026-07-04T00:05:00Z" +} diff --git a/examples/build-request.example.json b/examples/build-request.example.json new file mode 100644 index 0000000..d08c8f8 --- /dev/null +++ b/examples/build-request.example.json @@ -0,0 +1,24 @@ +{ + "schemaVersion": "build-request.v0.1", + "requestId": "req.socios.sourceos-smoke-runner.2026-07-04", + "requestedBy": "SociOS-Linux/socios", + "source": { + "repository": "SociOS-Linux/socios", + "ref": "refs/heads/main", + "commit": "0000000000000000000000000000000000000000" + }, + "target": { + "artifactType": "container-image", + "platform": "linux/amd64", + "outputRef": "registry.example/sourceos/smoke-runner" + }, + "buildConfig": { + "contextPath": "images/sourceos-smoke-runner" + }, + "evidenceRequirements": { + "digestRequired": true, + "provenanceRequired": true, + "signatureRequired": true + }, + "createdAt": "2026-07-04T00:00:00Z" +} diff --git a/schemas/sourceos/build-receipt.v0.1.schema.json b/schemas/sourceos/build-receipt.v0.1.schema.json new file mode 100644 index 0000000..c20f8c4 --- /dev/null +++ b/schemas/sourceos/build-receipt.v0.1.schema.json @@ -0,0 +1,117 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.socioprophet.org/sourceos-build/build-receipt.v0.1.json", + "title": "SourceOS BuildReceipt", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaVersion", + "buildId", + "requestRef", + "status", + "artifacts", + "provenance" + ], + "properties": { + "schemaVersion": { + "type": "string", + "const": "build-receipt.v0.1" + }, + "buildId": { + "type": "string", + "pattern": "^[a-z0-9][a-z0-9_.:-]+$" + }, + "requestRef": { + "type": "string", + "description": "requestId of the BuildRequest this fulfills" + }, + "status": { + "type": "string", + "enum": [ + "succeeded", + "failed", + "rejected" + ] + }, + "artifacts": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "name", + "type", + "digest" + ], + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "container-image", + "iso", + "package", + "tarball" + ] + }, + "digest": { + "type": "string", + "pattern": "^sha256:[a-f0-9]{64}$" + }, + "ref": { + "type": "string" + } + } + } + }, + "provenance": { + "type": "object", + "additionalProperties": false, + "required": [ + "builder", + "sourceCommit" + ], + "properties": { + "builder": { + "type": "string" + }, + "sourceCommit": { + "type": "string" + }, + "slsaLevel": { + "type": "integer", + "minimum": 0, + "maximum": 4 + } + } + }, + "signatures": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "keyId", + "algorithm", + "value" + ], + "properties": { + "keyId": { + "type": "string" + }, + "algorithm": { + "type": "string" + }, + "value": { + "type": "string" + } + } + } + }, + "completedAt": { + "type": "string" + } + } +} diff --git a/schemas/sourceos/build-request.v0.1.schema.json b/schemas/sourceos/build-request.v0.1.schema.json new file mode 100644 index 0000000..d1f87b8 --- /dev/null +++ b/schemas/sourceos/build-request.v0.1.schema.json @@ -0,0 +1,100 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.socioprophet.org/sourceos-build/build-request.v0.1.json", + "title": "SourceOS BuildRequest", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaVersion", + "requestId", + "requestedBy", + "source", + "target", + "evidenceRequirements" + ], + "properties": { + "schemaVersion": { + "type": "string", + "const": "build-request.v0.1" + }, + "requestId": { + "type": "string", + "pattern": "^[a-z0-9][a-z0-9_.:-]+$" + }, + "requestedBy": { + "type": "string", + "description": "actor or driving repo, e.g. SociOS-Linux/socios" + }, + "source": { + "type": "object", + "additionalProperties": false, + "required": [ + "repository", + "ref" + ], + "properties": { + "repository": { + "type": "string", + "pattern": "^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$" + }, + "ref": { + "type": "string" + }, + "commit": { + "type": "string" + } + } + }, + "target": { + "type": "object", + "additionalProperties": false, + "required": [ + "artifactType", + "platform" + ], + "properties": { + "artifactType": { + "type": "string", + "enum": [ + "container-image", + "iso", + "package", + "tarball" + ] + }, + "platform": { + "type": "string" + }, + "outputRef": { + "type": "string" + } + } + }, + "buildConfig": { + "type": "object" + }, + "evidenceRequirements": { + "type": "object", + "additionalProperties": false, + "required": [ + "digestRequired", + "provenanceRequired", + "signatureRequired" + ], + "properties": { + "digestRequired": { + "type": "boolean" + }, + "provenanceRequired": { + "type": "boolean" + }, + "signatureRequired": { + "type": "boolean" + } + } + }, + "createdAt": { + "type": "string" + } + } +} diff --git a/tekton/pipeline.yaml b/tekton/pipeline.yaml new file mode 100644 index 0000000..a0ea891 --- /dev/null +++ b/tekton/pipeline.yaml @@ -0,0 +1,20 @@ +apiVersion: tekton.dev/v1 +kind: Pipeline +metadata: + name: pipeline-build-image +spec: + params: + - name: source-repo + - name: source-ref + - name: output-ref + tasks: + - name: build-and-emit-receipt + taskRef: + name: task-build-image + params: + - name: source-repo + value: $(params.source-repo) + - name: source-ref + value: $(params.source-ref) + - name: output-ref + value: $(params.output-ref) diff --git a/tekton/task-build-image.yaml b/tekton/task-build-image.yaml new file mode 100644 index 0000000..d27cc2b --- /dev/null +++ b/tekton/task-build-image.yaml @@ -0,0 +1,14 @@ +apiVersion: tekton.dev/v1 +kind: Task +metadata: + name: task-build-image +spec: + params: + - name: source-repo + - name: source-ref + - name: output-ref + steps: + - name: build + image: gcr.io/kaniko-project/executor:latest + script: | + echo "Skeleton build step — emits a build-receipt.v0.1 with the resolved sha256 digest." diff --git a/terraform/README.md b/terraform/README.md new file mode 100644 index 0000000..605e223 --- /dev/null +++ b/terraform/README.md @@ -0,0 +1,3 @@ +# terraform/ + +Terraform skeleton for SourceOS build infrastructure. Skeleton only — no state, no credentials, no provider secrets committed. Fill in backend + providers per environment out-of-band. diff --git a/terraform/main.tf b/terraform/main.tf new file mode 100644 index 0000000..130ef71 --- /dev/null +++ b/terraform/main.tf @@ -0,0 +1,7 @@ +# SourceOS build substrate — Terraform skeleton (no backend/credentials committed). +terraform { + required_version = ">= 1.5.0" +} + +# Providers and backend are configured per-environment out-of-band. +# Example resource placeholders (build namespace, artifact bucket) go here. diff --git a/terraform/variables.tf b/terraform/variables.tf new file mode 100644 index 0000000..76192dc --- /dev/null +++ b/terraform/variables.tf @@ -0,0 +1,10 @@ +variable "environment" { + type = string + description = "Target environment (e.g. dev, prod)." +} + +variable "build_namespace" { + type = string + description = "Kubernetes namespace for build pipelines." + default = "sourceos-build" +} diff --git a/tools/validate_examples.py b/tools/validate_examples.py new file mode 100644 index 0000000..f273b8c --- /dev/null +++ b/tools/validate_examples.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +"""Validate example payloads against the sourceos-build JSON Schemas. +Uses jsonschema if available; otherwise a zero-dep structural check.""" +import json, re, sys, os + +PAIRS=[("schemas/sourceos/build-request.v0.1.schema.json","examples/build-request.example.json"), + ("schemas/sourceos/build-receipt.v0.1.schema.json","examples/build-receipt.example.json")] + +def fail(m): print(f"FAIL: {m}",file=sys.stderr); sys.exit(1) + +def structural(schema,doc): + for k in schema.get("required",[]): + if k not in doc: fail(f"{doc.get('schemaVersion','?')}: missing required '{k}'") + sv=schema.get("properties",{}).get("schemaVersion",{}).get("const") + if sv and doc.get("schemaVersion")!=sv: fail(f"schemaVersion must be {sv}") + +def main(): + try: + import jsonschema; strict=True + except Exception: + strict=False + for s,d in PAIRS: + if not (os.path.exists(s) and os.path.exists(d)): fail(f"missing {s} or {d}") + schema=json.load(open(s)); doc=json.load(open(d)) + if strict: + jsonschema.Draft202012Validator.check_schema(schema) + jsonschema.validate(doc,schema) + else: + structural(schema,doc) + print(f"ok: {d} validates against {s}" + ("" if strict else " (structural; install jsonschema for strict)")) + print("OK: sourceos-build example payloads validated") + +if __name__=="__main__": raise SystemExit(main())