Files
jakman85andClaude Opus 4.8 8c1368977e Phase 0 clarification follow-ups + reconcile CI integration seams
Adopt the spec's Phase-0 clarifications (spec now at pin a7fc19f) into the impl:
- schema v1.1: enforce exposure:public => auth.mode!=none (positive+negative tested).
- Kyverno: add longhorn=>size and longhorn=>NOT-reacquirable invariants (R-STATE-02/03).
- Ratify the homelab.keithjackson.dev/* label-key convention (now in spec docs/03).
- R-TEST-06 spec-immutability gate: ci/SPEC_PIN + ci/spec-immutability.sh + gate.sh
  stage + Makefile target + a Gitea job (checks spec repo clean & at pin).
- touched-requirements.sh: conformance tags are the authoritative source.

Reconcile 3 CI seams the CI worker flagged (gate was RED, now GREEN):
- create schemas/validate.sh (SCHEMA_VALIDATE_CMD entrypoint) — L0 schema gate runs.
- fix l1.sh/conformance.sh to run.sh's real interface (fixed _artifacts/ paths; no
  --junit/--coverage) and collect artifacts.
- exec bits on new scripts.

make -C ci gate => PASS (l0 skips locally w/o kyverno binary; green in CI).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-16 00:42:43 -05:00
..

ServiceDefinition L0 validation (R-GITOPS-02)

Validates the shape of each catalog/ ServiceDefinition against the canonical schema and invariants in the read-only spec docs/13-service-catalog.md (with auth from docs/15, egress from docs/03). Runs in CI without a cluster.

Why kyverno-json (not the resource CLI)

A ServiceDefinition is arbitrary YAML — its own kind: field means container | vm, not a Kubernetes Kind — so it cannot be validated by kyverno apply/kyverno test (which expect Kubernetes resources). Per ADR-0011 the Kyverno-ecosystem tool for arbitrary YAML/JSON is kyverno-json.

Invariants enforced (validate-servicedefinition.yaml)

  • name is a DNS-1123 label (→ <name>.home.keithjackson.dev).
  • kind ∈ {container, vm}; container ⇒ source.image, vm ⇒ source.vmTemplate.
  • exposure ∈ {internal, public}.
  • egress ∈ {internet, vpn}.
  • auth.mode ∈ {oidc, forward-auth, native, none}.
  • public exposure ⇒ auth.mode ≠ none (no unauthenticated public UI, docs/15).
  • each dependencies[] has to, port ∈ 1–65535, protocol ∈ {tcp, udp}.
  • each storage[].class ∈ {longhorn, bulk}.

Fixtures

payloads/valid-*.yaml must PASS; payloads/invalid-*.yaml must FAIL — one failure reason each (bad egress, public+none auth, missing image, bad dependency).

Run (CI entrypoint)

# Valid payloads: scan must exit 0
kyverno-json scan \
  --policy validate-servicedefinition.yaml \
  --payload payloads/valid-plex.yaml \
  --payload payloads/valid-sonarr.yaml

# Invalid payloads: each scan must exit NON-zero (loop asserts failure)
for f in payloads/invalid-*.yaml; do
  if kyverno-json scan --policy validate-servicedefinition.yaml --payload "$f"; then
    echo "FAIL: expected $f to be rejected"; exit 1
  fi
done

The scripts/run-l0.sh wrapper (see ../README.md) runs both halves and is what CI invokes.

Caveat (escalated in the T-0.5 report)

kyverno-json is pre-1.0; its assertion syntax evolves. The invariants above are the normative content. The CI agent must pin an exact kyverno-json version and confirm the check: expressions parse under it; adjust syntax (never the invariants) if the pinned release differs. The overlapping JSON Schema from WS-GITOPS (T-0.3) is the complementary structural contract.