Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.PHONY: validate validate-control-plane-examples validate-nlboot-examples validate-lattice-data-governai-examples validate-ops-history-examples validate-runtime-observability-examples validate-lifecycle-boundary-examples validate-svf-contracts
.PHONY: validate validate-control-plane-examples validate-nlboot-examples validate-lattice-data-governai-examples validate-ops-history-examples validate-runtime-observability-examples validate-lifecycle-boundary-examples validate-svf-contracts validate-sync-cycle-receipts

validate: validate-control-plane-examples validate-nlboot-examples validate-lattice-data-governai-examples validate-ops-history-examples validate-runtime-observability-examples validate-lifecycle-boundary-examples validate-svf-contracts
validate: validate-control-plane-examples validate-nlboot-examples validate-lattice-data-governai-examples validate-ops-history-examples validate-runtime-observability-examples validate-lifecycle-boundary-examples validate-svf-contracts validate-sync-cycle-receipts
@echo "OK: validate"

validate-control-plane-examples:
Expand Down Expand Up @@ -29,3 +29,7 @@ validate-lifecycle-boundary-examples:

validate-svf-contracts:
python3 tools/validate_svf_contracts.py

validate-sync-cycle-receipts:
python3 -m pip install --user jsonschema >/dev/null
python3 tools/validate_sync_cycle_receipts.py
33 changes: 33 additions & 0 deletions examples/sync-cycle-receipt.dry-run.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"id": "urn:srcos:sync-receipt:builder-aarch64-2026-06-16T00:00:00Z-dry",
"type": "SyncCycleReceipt",
"specVersion": "0.1.0",
"cycleId": "cycle-builder-aarch64-20260616-dry",
"engineId": "sourceos.sync.katello-content",
"org": "SocioProphet",
"contentView": "sourceos-builder-aarch64",
"fromVersion": "1.0",
"toVersion": "1.1",
"lifecycleEnv": "dev",
"locus": "local",
"outcome": "dry_run",
"policyGate": "allowed",
"policyReason": "locus 'local' is in ALLOWED_LOCI",
"steps": [
{
"step": "nix copy --from http://127.0.0.1:8101 github:SociOS-Linux/source-os#builder-aarch64",
"status": "dry_run",
"reason": "dry_run=True; would execute nix copy"
},
{
"step": "nixos-rebuild switch --flake github:SociOS-Linux/source-os#builder-aarch64",
"status": "dry_run",
"reason": "dry_run=True; would execute nixos-rebuild switch"
}
],
"nixCacheUrl": "http://127.0.0.1:8101",
"flakeRef": "github:SociOS-Linux/source-os#builder-aarch64",
"durationMs": 0,
"issuedAt": "2026-06-16T00:00:00Z",
"auditId": "urn:srcos:audit:builder-aarch64-2026-06-16T00:00:00Z-dry"
}
37 changes: 37 additions & 0 deletions examples/sync-cycle-receipt.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"id": "urn:srcos:sync-receipt:builder-aarch64-2026-06-16T00:00:00Z-001",
"type": "SyncCycleReceipt",
"specVersion": "0.1.0",
"cycleId": "cycle-builder-aarch64-20260616-001",
"engineId": "sourceos.sync.katello-content",
"org": "SocioProphet",
"contentView": "sourceos-builder-aarch64",
"fromVersion": null,
"toVersion": "1.0",
"lifecycleEnv": "dev",
"locus": "local",
"outcome": "applied",
"policyGate": "allowed",
"policyReason": "locus 'local' is in ALLOWED_LOCI",
"steps": [
{
"step": "nix copy --from http://127.0.0.1:8101 github:SociOS-Linux/source-os#builder-aarch64",
"status": "ok",
"returncode": 0,
"stdout": "copying 12 paths...",
"stderr": ""
},
{
"step": "nixos-rebuild switch --flake github:SociOS-Linux/source-os#builder-aarch64",
"status": "ok",
"returncode": 0,
"stdout": "activating configuration...",
"stderr": ""
}
],
"nixCacheUrl": "http://127.0.0.1:8101",
"flakeRef": "github:SociOS-Linux/source-os#builder-aarch64",
"durationMs": 47200,
"issuedAt": "2026-06-16T00:00:00Z",
"auditId": "urn:srcos:audit:builder-aarch64-2026-06-16T00:00:00Z-001"
}
123 changes: 123 additions & 0 deletions schemas/SyncCycleReceipt.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://schemas.srcos.ai/v2/SyncCycleReceipt.json",
"title": "SyncCycleReceipt",
"description": "Immutable receipt emitted after a content sync cycle is planned or applied. Captures the before/after content view versions, steps executed, outcome, and audit reference. Owned by SourceOS-Linux/sourceos-syncd.",
"type": "object",
"required": [
"id",
"type",
"specVersion",
"cycleId",
"engineId",
"org",
"contentView",
"toVersion",
"lifecycleEnv",
"locus",
"outcome",
"steps",
"issuedAt",
"auditId"
],
"additionalProperties": false,
"properties": {
"id": {
"type": "string",
"pattern": "^urn:srcos:sync-receipt:",
"description": "Globally unique receipt identifier."
},
"type": {
"const": "SyncCycleReceipt"
},
"specVersion": {
"type": "string",
"description": "sourceos-spec version this receipt was emitted against."
},
"cycleId": {
"type": "string",
"description": "Correlation ID for the sync cycle; may span plan + apply."
},
"engineId": {
"type": "string",
"pattern": "^sourceos\\.sync\\.",
"description": "SyncEngineManifest engineId that produced this receipt."
},
"org": {
"type": "string",
"description": "Katello organization name."
},
"contentView": {
"type": "string",
"description": "Katello content view name (e.g. sourceos-builder-aarch64)."
},
"fromVersion": {
"type": ["string", "null"],
"description": "Content view version before the sync; null on first sync."
},
"toVersion": {
"type": "string",
"description": "Content view version targeted by this sync."
},
"lifecycleEnv": {
"type": "string",
"description": "Katello lifecycle environment (dev, candidate, stable)."
},
"locus": {
"enum": ["local", "trusted_private", "attested_fog", "burst_cloud"],
"description": "Execution locus at which the sync was authorized."
},
"outcome": {
"enum": ["planned", "dry_run", "applied", "skipped", "denied", "failed"],
"description": "Result of the sync cycle."
},
"policyGate": {
"type": "string",
"description": "Policy gate value from the ContentSyncPlan (allowed, denied, no-op)."
},
"policyReason": {
"type": "string",
"description": "Human-readable reason for the policy gate decision."
},
"steps": {
"type": "array",
"items": {
"type": "object",
"required": ["step", "status"],
"additionalProperties": false,
"properties": {
"step": { "type": "string" },
"status": {
"enum": ["dry_run", "ok", "failed", "skipped", "timeout"]
},
"returncode": { "type": "integer" },
"stdout": { "type": "string" },
"stderr": { "type": "string" },
"reason": { "type": "string" }
}
}
},
"nixCacheUrl": {
"type": "string",
"description": "Pulp content server URL used as the Nix binary cache."
},
"flakeRef": {
"type": "string",
"description": "NixOS flake ref passed to nixos-rebuild."
},
"durationMs": {
"type": "integer",
"minimum": 0,
"description": "Wall-clock duration of the sync execution in milliseconds."
},
"issuedAt": {
"type": "string",
"format": "date-time"
},
"auditId": {
"type": "string",
"pattern": "^urn:srcos:audit:",
"description": "AuditEvent id that records this sync cycle in the append-only audit log."
}
}
}
33 changes: 33 additions & 0 deletions tools/validate_sync_cycle_receipts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env python3
from __future__ import annotations

import json
from pathlib import Path

import jsonschema

ROOT = Path(__file__).resolve().parents[1]
PAIRS = [
(ROOT / "schemas" / "SyncCycleReceipt.json", ROOT / "examples" / "sync-cycle-receipt.json"),
(ROOT / "schemas" / "SyncCycleReceipt.json", ROOT / "examples" / "sync-cycle-receipt.dry-run.json"),
]


def validate_pair(schema_path: Path, example_path: Path) -> None:
schema = json.loads(schema_path.read_text(encoding="utf-8"))
jsonschema.validators.validator_for(schema).check_schema(schema)
example = json.loads(example_path.read_text(encoding="utf-8"))
jsonschema.validate(example, schema)


def main() -> int:
checks: dict[str, bool] = {}
for schema_path, example_path in PAIRS:
validate_pair(schema_path, example_path)
checks[example_path.name] = True
print(json.dumps({"ok": all(checks.values()), "checks": checks}, indent=2, sort_keys=True))
return 0


if __name__ == "__main__":
raise SystemExit(main())
Loading