Skip to content

Commit 8c8a4e9

Browse files
authored
feat(schema): add SyncCycleReceipt schema, examples, and validator (#125)
Immutable receipt emitted after each content sync cycle (plan or apply). Captures the Katello content view before/after versions, per-step execution results, locus, outcome, and audit reference. Wired into the top-level Makefile validate target via validate-sync-cycle-receipts.
1 parent a758169 commit 8c8a4e9

5 files changed

Lines changed: 232 additions & 2 deletions

File tree

Makefile

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
.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
1+
.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
22

3-
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
3+
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
44
@echo "OK: validate"
55

66
validate-control-plane-examples:
@@ -29,3 +29,7 @@ validate-lifecycle-boundary-examples:
2929

3030
validate-svf-contracts:
3131
python3 tools/validate_svf_contracts.py
32+
33+
validate-sync-cycle-receipts:
34+
python3 -m pip install --user jsonschema >/dev/null
35+
python3 tools/validate_sync_cycle_receipts.py
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"id": "urn:srcos:sync-receipt:builder-aarch64-2026-06-16T00:00:00Z-dry",
3+
"type": "SyncCycleReceipt",
4+
"specVersion": "0.1.0",
5+
"cycleId": "cycle-builder-aarch64-20260616-dry",
6+
"engineId": "sourceos.sync.katello-content",
7+
"org": "SocioProphet",
8+
"contentView": "sourceos-builder-aarch64",
9+
"fromVersion": "1.0",
10+
"toVersion": "1.1",
11+
"lifecycleEnv": "dev",
12+
"locus": "local",
13+
"outcome": "dry_run",
14+
"policyGate": "allowed",
15+
"policyReason": "locus 'local' is in ALLOWED_LOCI",
16+
"steps": [
17+
{
18+
"step": "nix copy --from http://127.0.0.1:8101 github:SociOS-Linux/source-os#builder-aarch64",
19+
"status": "dry_run",
20+
"reason": "dry_run=True; would execute nix copy"
21+
},
22+
{
23+
"step": "nixos-rebuild switch --flake github:SociOS-Linux/source-os#builder-aarch64",
24+
"status": "dry_run",
25+
"reason": "dry_run=True; would execute nixos-rebuild switch"
26+
}
27+
],
28+
"nixCacheUrl": "http://127.0.0.1:8101",
29+
"flakeRef": "github:SociOS-Linux/source-os#builder-aarch64",
30+
"durationMs": 0,
31+
"issuedAt": "2026-06-16T00:00:00Z",
32+
"auditId": "urn:srcos:audit:builder-aarch64-2026-06-16T00:00:00Z-dry"
33+
}

examples/sync-cycle-receipt.json

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"id": "urn:srcos:sync-receipt:builder-aarch64-2026-06-16T00:00:00Z-001",
3+
"type": "SyncCycleReceipt",
4+
"specVersion": "0.1.0",
5+
"cycleId": "cycle-builder-aarch64-20260616-001",
6+
"engineId": "sourceos.sync.katello-content",
7+
"org": "SocioProphet",
8+
"contentView": "sourceos-builder-aarch64",
9+
"fromVersion": null,
10+
"toVersion": "1.0",
11+
"lifecycleEnv": "dev",
12+
"locus": "local",
13+
"outcome": "applied",
14+
"policyGate": "allowed",
15+
"policyReason": "locus 'local' is in ALLOWED_LOCI",
16+
"steps": [
17+
{
18+
"step": "nix copy --from http://127.0.0.1:8101 github:SociOS-Linux/source-os#builder-aarch64",
19+
"status": "ok",
20+
"returncode": 0,
21+
"stdout": "copying 12 paths...",
22+
"stderr": ""
23+
},
24+
{
25+
"step": "nixos-rebuild switch --flake github:SociOS-Linux/source-os#builder-aarch64",
26+
"status": "ok",
27+
"returncode": 0,
28+
"stdout": "activating configuration...",
29+
"stderr": ""
30+
}
31+
],
32+
"nixCacheUrl": "http://127.0.0.1:8101",
33+
"flakeRef": "github:SociOS-Linux/source-os#builder-aarch64",
34+
"durationMs": 47200,
35+
"issuedAt": "2026-06-16T00:00:00Z",
36+
"auditId": "urn:srcos:audit:builder-aarch64-2026-06-16T00:00:00Z-001"
37+
}

schemas/SyncCycleReceipt.json

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"$id": "https://schemas.srcos.ai/v2/SyncCycleReceipt.json",
4+
"title": "SyncCycleReceipt",
5+
"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.",
6+
"type": "object",
7+
"required": [
8+
"id",
9+
"type",
10+
"specVersion",
11+
"cycleId",
12+
"engineId",
13+
"org",
14+
"contentView",
15+
"toVersion",
16+
"lifecycleEnv",
17+
"locus",
18+
"outcome",
19+
"steps",
20+
"issuedAt",
21+
"auditId"
22+
],
23+
"additionalProperties": false,
24+
"properties": {
25+
"id": {
26+
"type": "string",
27+
"pattern": "^urn:srcos:sync-receipt:",
28+
"description": "Globally unique receipt identifier."
29+
},
30+
"type": {
31+
"const": "SyncCycleReceipt"
32+
},
33+
"specVersion": {
34+
"type": "string",
35+
"description": "sourceos-spec version this receipt was emitted against."
36+
},
37+
"cycleId": {
38+
"type": "string",
39+
"description": "Correlation ID for the sync cycle; may span plan + apply."
40+
},
41+
"engineId": {
42+
"type": "string",
43+
"pattern": "^sourceos\\.sync\\.",
44+
"description": "SyncEngineManifest engineId that produced this receipt."
45+
},
46+
"org": {
47+
"type": "string",
48+
"description": "Katello organization name."
49+
},
50+
"contentView": {
51+
"type": "string",
52+
"description": "Katello content view name (e.g. sourceos-builder-aarch64)."
53+
},
54+
"fromVersion": {
55+
"type": ["string", "null"],
56+
"description": "Content view version before the sync; null on first sync."
57+
},
58+
"toVersion": {
59+
"type": "string",
60+
"description": "Content view version targeted by this sync."
61+
},
62+
"lifecycleEnv": {
63+
"type": "string",
64+
"description": "Katello lifecycle environment (dev, candidate, stable)."
65+
},
66+
"locus": {
67+
"enum": ["local", "trusted_private", "attested_fog", "burst_cloud"],
68+
"description": "Execution locus at which the sync was authorized."
69+
},
70+
"outcome": {
71+
"enum": ["planned", "dry_run", "applied", "skipped", "denied", "failed"],
72+
"description": "Result of the sync cycle."
73+
},
74+
"policyGate": {
75+
"type": "string",
76+
"description": "Policy gate value from the ContentSyncPlan (allowed, denied, no-op)."
77+
},
78+
"policyReason": {
79+
"type": "string",
80+
"description": "Human-readable reason for the policy gate decision."
81+
},
82+
"steps": {
83+
"type": "array",
84+
"items": {
85+
"type": "object",
86+
"required": ["step", "status"],
87+
"additionalProperties": false,
88+
"properties": {
89+
"step": { "type": "string" },
90+
"status": {
91+
"enum": ["dry_run", "ok", "failed", "skipped", "timeout"]
92+
},
93+
"returncode": { "type": "integer" },
94+
"stdout": { "type": "string" },
95+
"stderr": { "type": "string" },
96+
"reason": { "type": "string" }
97+
}
98+
}
99+
},
100+
"nixCacheUrl": {
101+
"type": "string",
102+
"description": "Pulp content server URL used as the Nix binary cache."
103+
},
104+
"flakeRef": {
105+
"type": "string",
106+
"description": "NixOS flake ref passed to nixos-rebuild."
107+
},
108+
"durationMs": {
109+
"type": "integer",
110+
"minimum": 0,
111+
"description": "Wall-clock duration of the sync execution in milliseconds."
112+
},
113+
"issuedAt": {
114+
"type": "string",
115+
"format": "date-time"
116+
},
117+
"auditId": {
118+
"type": "string",
119+
"pattern": "^urn:srcos:audit:",
120+
"description": "AuditEvent id that records this sync cycle in the append-only audit log."
121+
}
122+
}
123+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#!/usr/bin/env python3
2+
from __future__ import annotations
3+
4+
import json
5+
from pathlib import Path
6+
7+
import jsonschema
8+
9+
ROOT = Path(__file__).resolve().parents[1]
10+
PAIRS = [
11+
(ROOT / "schemas" / "SyncCycleReceipt.json", ROOT / "examples" / "sync-cycle-receipt.json"),
12+
(ROOT / "schemas" / "SyncCycleReceipt.json", ROOT / "examples" / "sync-cycle-receipt.dry-run.json"),
13+
]
14+
15+
16+
def validate_pair(schema_path: Path, example_path: Path) -> None:
17+
schema = json.loads(schema_path.read_text(encoding="utf-8"))
18+
jsonschema.validators.validator_for(schema).check_schema(schema)
19+
example = json.loads(example_path.read_text(encoding="utf-8"))
20+
jsonschema.validate(example, schema)
21+
22+
23+
def main() -> int:
24+
checks: dict[str, bool] = {}
25+
for schema_path, example_path in PAIRS:
26+
validate_pair(schema_path, example_path)
27+
checks[example_path.name] = True
28+
print(json.dumps({"ok": all(checks.values()), "checks": checks}, indent=2, sort_keys=True))
29+
return 0
30+
31+
32+
if __name__ == "__main__":
33+
raise SystemExit(main())

0 commit comments

Comments
 (0)