9740bc64c9
Continuous Deployment / Deploy to Production (push) Blocked by required conditions
Continuous Deployment / Rollback Deployment (push) Blocked by required conditions
Continuous Deployment / Post-deployment Monitoring (push) Blocked by required conditions
Continuous Deployment / Notify Deployment Status (push) Blocked by required conditions
Firmware QEMU Tests (ADR-061) / QEMU Test (edge-tier1) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / QEMU Test (full-adr060) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / QEMU Test (tdm-3node) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / Swarm Test (ADR-062) (push) Has been skipped
npm packages / tools/ruview-mcp (node 22) (push) Failing after 1s
nvsim-server → ghcr.io / build-and-publish (push) Failing after 1s
ruview-swarm CI guard / tests (full+train) (push) Failing after 2s
Bench Regression Guard / bench compile-verify (--no-run) (push) Failing after 0s
Bench Regression Guard / bench fast-run (informational, non-gating) (push) Has been skipped
Firmware CI / Verify version.txt matches release tag (push) Has been skipped
Dashboard a11y + cross-browser / a11y (push) Failing after 0s
nvsim Dashboard → GitHub Pages / build-and-deploy (push) Failing after 2s
Firmware CI / Build firmware (esp32s3 / 4mb) (push) Failing after 15s
Firmware QEMU Tests (ADR-061) / Build Espressif QEMU (push) Failing after 1s
Firmware QEMU Tests (ADR-061) / Fuzz Testing (ADR-061 Layer 6) (push) Failing after 1s
Continuous Deployment / Pre-deployment Checks (push) Has been skipped
Continuous Deployment / Deploy to Staging (push) Waiting to run
Firmware CI / Build firmware (esp32c6 / c6-4mb) (push) Failing after 15s
Firmware CI / Build firmware (esp32s3 / 8mb) (push) Failing after 15s
Firmware QEMU Tests (ADR-061) / QEMU Test (boundary-max) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / QEMU Test (boundary-min) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / QEMU Test (default) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / QEMU Test (edge-tier0) (push) Has been skipped
Firmware QEMU Tests (ADR-061) / NVS Matrix Generation (push) Failing after 1s
Security Scanning / Security Policy Compliance (push) Failing after 0s
Security Scanning / Security Report (push) Waiting to run
Security Scanning / Dependency Vulnerability Scan (push) Failing after 0s
Security Scanning / Static Application Security Testing (push) Failing after 1s
Security Scanning / Infrastructure Security Scan (push) Failing after 1s
Security Scanning / Secret Scanning (push) Failing after 1s
npm packages / harness/ruview (node 22) (push) Failing after 17s
Security Scanning / License Compliance Scan (push) Failing after 1s
Security Scanning / Container Security Scan (push) Failing after 4s
three.js demos → GitHub Pages / build-and-deploy (push) Failing after 1s
Verify Pipeline Determinism / Verify Pipeline Determinism (3.11) (push) Failing after 1s
Fix-Marker Regression Guard / Verify fix markers (push) Failing after 1s
ADR-115 MQTT integration tests / mqtt-integration (push) Failing after 1s
npm packages / harness/ruview (node 20) (push) Failing after 1s
npm packages / tools/ruview-mcp (node 20) (push) Failing after 1s
npm packages / tools/ruview-cli (node 20) (push) Failing after 1s
npm packages / tools/ruview-cli (node 22) (push) Failing after 1s
BFLD MQTT Integration / cargo test --features mqtt (live mosquitto) (push) Failing after 29s
ruview-swarm CI guard / build train_marl bin (push) Failing after 2s
ruview-swarm CI guard / clippy (-D warnings, --no-deps) (push) Failing after 3s
ruview-swarm CI guard / tests (ruflo) (push) Failing after 1s
ruview-swarm CI guard / tests (train) (push) Failing after 2s
ruview-swarm CI guard / tests (default) (push) Failing after 2s
Point Cloud Viewer → GitHub Pages / build-and-deploy (push) Failing after 8s
ruview-swarm CI guard / ITAR / publish guard (push) Failing after 0s
wifi-densepose sensing-server → Docker Hub + ghcr.io / build · push · smoke-test (push) Failing after 1s
115 lines
3.2 KiB
Python
115 lines
3.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Validate every YAML file under examples/ha-blueprints/.
|
|
|
|
HA Blueprints use the `!input` YAML tag, which stock PyYAML doesn't
|
|
know how to construct. We register a no-op constructor for it so we
|
|
can still safe_load the files and assert on their structure.
|
|
|
|
Exits 0 if all blueprints are well-formed, non-zero otherwise. Intended
|
|
to run in CI on every PR that touches examples/ha-blueprints/.
|
|
|
|
Usage:
|
|
python scripts/validate-ha-blueprints.py
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import glob
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import yaml
|
|
|
|
|
|
class InputTag(str):
|
|
"""No-op holder for HA `!input` markers — we don't expand them, just
|
|
verify the file parses."""
|
|
|
|
|
|
def _input_constructor(loader, node):
|
|
return InputTag(loader.construct_scalar(node))
|
|
|
|
|
|
def _secret_constructor(loader, node):
|
|
return f"<!secret {loader.construct_scalar(node)}>"
|
|
|
|
|
|
yaml.SafeLoader.add_constructor("!input", _input_constructor)
|
|
yaml.SafeLoader.add_constructor("!secret", _secret_constructor)
|
|
|
|
|
|
REQUIRED_BLUEPRINT_KEYS = {"name", "description", "domain"}
|
|
ALLOWED_DOMAINS = {"automation", "script"}
|
|
|
|
|
|
def validate(path: Path) -> list[str]:
|
|
"""Return a list of issues; empty list means the blueprint is valid."""
|
|
issues: list[str] = []
|
|
try:
|
|
with path.open(encoding="utf-8") as fh:
|
|
doc = yaml.safe_load(fh)
|
|
except yaml.YAMLError as e:
|
|
return [f"YAML parse error: {e}"]
|
|
except OSError as e:
|
|
return [f"could not open: {e}"]
|
|
|
|
if not isinstance(doc, dict):
|
|
return ["top-level must be a mapping"]
|
|
|
|
bp = doc.get("blueprint")
|
|
if not isinstance(bp, dict):
|
|
issues.append("missing `blueprint` mapping at top level")
|
|
return issues
|
|
|
|
missing = REQUIRED_BLUEPRINT_KEYS - bp.keys()
|
|
if missing:
|
|
issues.append(f"missing blueprint keys: {', '.join(sorted(missing))}")
|
|
|
|
domain = bp.get("domain")
|
|
if domain not in ALLOWED_DOMAINS:
|
|
issues.append(
|
|
f"unsupported blueprint.domain={domain!r}; allowed: {ALLOWED_DOMAINS}"
|
|
)
|
|
|
|
if not isinstance(bp.get("input"), dict) or not bp["input"]:
|
|
issues.append("blueprint.input must declare at least one input")
|
|
|
|
# The automation body must contain at least one of: trigger,
|
|
# action, sequence (script body).
|
|
if "trigger" not in doc and "action" not in doc and "sequence" not in doc:
|
|
issues.append(
|
|
"no `trigger`/`action`/`sequence` block — blueprint can't fire"
|
|
)
|
|
|
|
return issues
|
|
|
|
|
|
def main() -> int:
|
|
root = Path(__file__).resolve().parent.parent
|
|
files = sorted(glob.glob(str(root / "examples" / "ha-blueprints" / "*.yaml")))
|
|
if not files:
|
|
print("ERROR: no blueprint YAML files found", file=sys.stderr)
|
|
return 2
|
|
|
|
fails = 0
|
|
for f in files:
|
|
issues = validate(Path(f))
|
|
rel = Path(f).relative_to(root)
|
|
if issues:
|
|
fails += 1
|
|
print(f"FAIL {rel}")
|
|
for i in issues:
|
|
print(f" {i}")
|
|
else:
|
|
print(f"ok {rel}")
|
|
|
|
if fails:
|
|
print(f"\n{fails} blueprint(s) failed validation", file=sys.stderr)
|
|
return 1
|
|
print(f"\nAll {len(files)} HA Blueprints validate OK")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|