Skip to content

Commit 768457d

Browse files
Pigbibicodex
andcommitted
feat: add immutable deployment bundle contract
Co-Authored-By: Codex <noreply@openai.com>
1 parent 0fd5449 commit 768457d

3 files changed

Lines changed: 486 additions & 0 deletions

File tree

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
#!/usr/bin/env python3
2+
"""Validate the local-only immutable QSL DeploymentBundle v1 contract."""
3+
4+
from __future__ import annotations
5+
6+
import argparse
7+
import hashlib
8+
import json
9+
import math
10+
import re
11+
import sys
12+
from datetime import datetime
13+
from pathlib import Path
14+
from typing import Any, Mapping
15+
16+
SCHEMA_ID = "qsl.deployment_bundle.v1"
17+
_DIGEST_ALGORITHM = "sha256"
18+
_IDENTITY_PATTERN = re.compile(r"^[a-z][a-z0-9]*(?:[._-][a-z0-9]+)*$")
19+
_REVISION_PATTERN = re.compile(r"^[0-9a-f]{40}$")
20+
_SHA256_PATTERN = re.compile(r"^[0-9a-f]{64}$")
21+
_TIMESTAMP_PATTERN = re.compile(r"^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$")
22+
_FORBIDDEN_KEY_PATTERN = re.compile(
23+
r"credential|secret|token|password|cookie|jwt|private|access[_-]?key|broker|account|order|capital|"
24+
r"activation|apply|runtime|configured|live[_-]?ready|promotion|matched|fill",
25+
re.IGNORECASE,
26+
)
27+
_URL_PATTERN = re.compile(r"[a-z][a-z0-9+.-]*://", re.IGNORECASE)
28+
_REQUIRED_FIELDS = {
29+
"schema",
30+
"bundle_id",
31+
"created_at",
32+
"digest_algorithm",
33+
"strategy",
34+
"profile",
35+
"config",
36+
"evidence",
37+
"target",
38+
"dependencies",
39+
"bundle_sha256",
40+
}
41+
_REQUIRED_ARTIFACT_FIELDS = {"id", "revision", "artifact_sha256"}
42+
43+
44+
class BundleValidationError(ValueError):
45+
"""Raised when an input is not a valid immutable deployment bundle."""
46+
47+
48+
def _fail(message: str) -> None:
49+
raise BundleValidationError(message)
50+
51+
52+
def _reject_non_finite_or_null(value: Any, path: str = "bundle") -> None:
53+
if value is None:
54+
_fail(f"{path} must not be null")
55+
if isinstance(value, float) and not math.isfinite(value):
56+
_fail(f"{path} contains a non-finite number")
57+
if isinstance(value, Mapping):
58+
for key, child in value.items():
59+
if not isinstance(key, str):
60+
_fail(f"{path} contains a non-string key")
61+
_reject_non_finite_or_null(child, f"{path}.{key}")
62+
elif isinstance(value, list):
63+
for index, child in enumerate(value):
64+
_reject_non_finite_or_null(child, f"{path}[{index}]")
65+
66+
67+
def _reject_forbidden_material(value: Any, path: str = "bundle") -> None:
68+
if isinstance(value, Mapping):
69+
for key, child in value.items():
70+
if _FORBIDDEN_KEY_PATTERN.search(key):
71+
_fail(f"{path}.{key} is forbidden in a deployment bundle")
72+
_reject_forbidden_material(child, f"{path}.{key}")
73+
elif isinstance(value, list):
74+
for index, child in enumerate(value):
75+
_reject_forbidden_material(child, f"{path}[{index}]")
76+
elif isinstance(value, str) and _URL_PATTERN.search(value):
77+
_fail(f"{path} contains a forbidden URL")
78+
79+
80+
def _expect_object(value: Any, path: str) -> Mapping[str, Any]:
81+
if not isinstance(value, Mapping):
82+
_fail(f"{path} must be an object")
83+
return value
84+
85+
86+
def _expect_exact_keys(value: Mapping[str, Any], expected: set[str], path: str) -> None:
87+
missing = sorted(expected - set(value))
88+
unknown = sorted(set(value) - expected)
89+
if missing:
90+
_fail(f"{path} missing required field(s): {', '.join(missing)}")
91+
if unknown:
92+
_fail(f"{path} has unknown field(s): {', '.join(unknown)}")
93+
94+
95+
def _expect_identity(value: Any, path: str) -> str:
96+
if not isinstance(value, str) or not _IDENTITY_PATTERN.fullmatch(value):
97+
_fail(f"{path} must be a lowercase immutable identity")
98+
return value
99+
100+
101+
def _expect_revision(value: Any, path: str) -> str:
102+
if not isinstance(value, str) or not _REVISION_PATTERN.fullmatch(value):
103+
_fail(f"{path} must be a lowercase 40-character revision")
104+
return value
105+
106+
107+
def _expect_sha256(value: Any, path: str) -> str:
108+
if not isinstance(value, str) or not _SHA256_PATTERN.fullmatch(value):
109+
_fail(f"{path} must be a lowercase SHA-256 digest")
110+
return value
111+
112+
113+
def _expect_timestamp(value: Any) -> str:
114+
if not isinstance(value, str) or not _TIMESTAMP_PATTERN.fullmatch(value):
115+
_fail("created_at must be an RFC3339 UTC timestamp with whole seconds")
116+
try:
117+
datetime.strptime(value, "%Y-%m-%dT%H:%M:%SZ")
118+
except ValueError as exc:
119+
raise BundleValidationError("created_at must be a valid calendar timestamp") from exc
120+
return value
121+
122+
123+
def _validate_artifact_identity(value: Any, path: str, *, strategy: bool = False) -> Mapping[str, Any]:
124+
identity = _expect_object(value, path)
125+
expected = _REQUIRED_ARTIFACT_FIELDS | ({"source_id"} if strategy else set())
126+
_expect_exact_keys(identity, expected, path)
127+
_expect_identity(identity["id"], f"{path}.id")
128+
if strategy:
129+
_expect_identity(identity["source_id"], f"{path}.source_id")
130+
_expect_revision(identity["revision"], f"{path}.revision")
131+
_expect_sha256(identity["artifact_sha256"], f"{path}.artifact_sha256")
132+
return identity
133+
134+
135+
def _validate_shape(bundle: Any) -> Mapping[str, Any]:
136+
_reject_non_finite_or_null(bundle)
137+
_reject_forbidden_material(bundle)
138+
root = _expect_object(bundle, "bundle")
139+
_expect_exact_keys(root, _REQUIRED_FIELDS, "bundle")
140+
if root["schema"] != SCHEMA_ID:
141+
_fail(f"schema must be {SCHEMA_ID}")
142+
_expect_identity(root["bundle_id"], "bundle_id")
143+
_expect_timestamp(root["created_at"])
144+
if root["digest_algorithm"] != _DIGEST_ALGORITHM:
145+
_fail("digest_algorithm must be sha256")
146+
strategy = _validate_artifact_identity(root["strategy"], "strategy", strategy=True)
147+
_validate_artifact_identity(root["profile"], "profile")
148+
_validate_artifact_identity(root["config"], "config")
149+
_validate_artifact_identity(root["evidence"], "evidence")
150+
target = _expect_object(root["target"], "target")
151+
_expect_exact_keys(target, {"id", "platform_id"}, "target")
152+
_expect_identity(target["id"], "target.id")
153+
_expect_identity(target["platform_id"], "target.platform_id")
154+
dependencies = _expect_object(root["dependencies"], "dependencies")
155+
_expect_exact_keys(dependencies, {"qpk", "strategy", "pipeline", "platform"}, "dependencies")
156+
for name in ("qpk", "strategy", "pipeline", "platform"):
157+
_validate_artifact_identity(dependencies[name], f"dependencies.{name}")
158+
if strategy["source_id"] != dependencies["strategy"]["id"]:
159+
_fail("strategy.source_id must match dependencies.strategy.id")
160+
if strategy["revision"] != dependencies["strategy"]["revision"]:
161+
_fail("strategy.revision must match dependencies.strategy.revision")
162+
if target["platform_id"] != dependencies["platform"]["id"]:
163+
_fail("target.platform_id must match dependencies.platform.id")
164+
_expect_sha256(root["bundle_sha256"], "bundle_sha256")
165+
return root
166+
167+
168+
def canonical_json(bundle: Mapping[str, Any]) -> str:
169+
"""Return the deterministic JSON representation with only the self hash omitted."""
170+
if not isinstance(bundle, Mapping):
171+
_fail("bundle must be an object")
172+
content = dict(bundle)
173+
content.pop("bundle_sha256", None)
174+
try:
175+
return json.dumps(content, sort_keys=True, separators=(",", ":"), ensure_ascii=True, allow_nan=False)
176+
except (TypeError, ValueError) as exc:
177+
raise BundleValidationError("bundle cannot be represented as canonical JSON") from exc
178+
179+
180+
def calculate_bundle_sha256(bundle: Mapping[str, Any]) -> str:
181+
return hashlib.sha256(canonical_json(bundle).encode("utf-8")).hexdigest()
182+
183+
184+
def validate_bundle(bundle: Any) -> Mapping[str, Any]:
185+
"""Fail closed unless the exact immutable content matches its declared digest."""
186+
root = _validate_shape(bundle)
187+
expected = calculate_bundle_sha256(root)
188+
if root["bundle_sha256"] != expected:
189+
_fail("bundle_sha256 mismatch")
190+
return root
191+
192+
193+
def _reject_duplicate_pairs(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
194+
result: dict[str, Any] = {}
195+
for key, value in pairs:
196+
if key in result:
197+
_fail(f"duplicate JSON key: {key}")
198+
result[key] = value
199+
return result
200+
201+
202+
def parse_bundle_json(text: str) -> Mapping[str, Any]:
203+
try:
204+
value = json.loads(text, object_pairs_hook=_reject_duplicate_pairs, parse_constant=lambda _: _fail("non-finite JSON value"))
205+
except json.JSONDecodeError as exc:
206+
raise BundleValidationError("invalid JSON") from exc
207+
return validate_bundle(value)
208+
209+
210+
def main(argv: list[str] | None = None) -> int:
211+
parser = argparse.ArgumentParser(description=__doc__)
212+
parser.add_argument("--input", type=Path, required=True, help="immutable bundle JSON to validate locally")
213+
args = parser.parse_args(argv)
214+
try:
215+
bundle = parse_bundle_json(args.input.read_text(encoding="utf-8"))
216+
except (OSError, BundleValidationError) as exc:
217+
print(f"deployment bundle validation failed: {exc}", file=sys.stderr)
218+
return 1
219+
print(json.dumps({"bundle_sha256": bundle["bundle_sha256"], "schema": bundle["schema"]}, sort_keys=True))
220+
return 0
221+
222+
223+
if __name__ == "__main__":
224+
raise SystemExit(main())

0 commit comments

Comments
 (0)