Version the common and derived rail contract

This commit is contained in:
codex 2026-07-26 13:34:56 +02:00
parent fee395f0c4
commit 3ece1b55fb
8 changed files with 407 additions and 2 deletions

View file

@ -9,7 +9,12 @@
| Kind | ID | Status | Lane | Source |
| --- | --- | --- | --- | --- |
| workplan | RAIL-K8S-WP-0001 | finished | — | workplans/RAIL-K8S-WP-0001-bootstrap-and-wave1-import.md |
| workplan | RAIL-K8S-WP-0002 | finished | — | workplans/RAIL-K8S-WP-0002-versioned-common-and-derived-rail-contract.md |
| task | RAIL-K8S-WP-0001-T01 | done | — | workplans/RAIL-K8S-WP-0001-bootstrap-and-wave1-import.md |
| task | RAIL-K8S-WP-0001-T02 | done | — | workplans/RAIL-K8S-WP-0001-bootstrap-and-wave1-import.md |
| task | RAIL-K8S-WP-0001-T03 | done | — | workplans/RAIL-K8S-WP-0001-bootstrap-and-wave1-import.md |
| task | RAIL-K8S-WP-0001-T04 | done | — | workplans/RAIL-K8S-WP-0001-bootstrap-and-wave1-import.md |
| task | RAIL-K8S-WP-0002-T01 | done | — | workplans/RAIL-K8S-WP-0002-versioned-common-and-derived-rail-contract.md |
| task | RAIL-K8S-WP-0002-T02 | done | — | workplans/RAIL-K8S-WP-0002-versioned-common-and-derived-rail-contract.md |
| task | RAIL-K8S-WP-0002-T03 | done | — | workplans/RAIL-K8S-WP-0002-versioned-common-and-derived-rail-contract.md |
| task | RAIL-K8S-WP-0002-T04 | done | — | workplans/RAIL-K8S-WP-0002-versioned-common-and-derived-rail-contract.md |

View file

@ -2,17 +2,22 @@ kind: execution-rail
rail_id: rail-kubernetes
repo: rail-kubernetes
ownership_repo: railiance-cluster
contract_version: 1.0.0
composition_kind: base
execution_architecture: kubernetes
status: wave-1-bootstrap
readiness_state: verified
supported_rollout_modes:
- stage1-run
- stage2-isolated-canary
- stage3-promote-rollback
substrate_prerequisites:
required_substrate_capabilities:
- kubernetes-api
- helm-apply-path
- operator-driven-kubeconfig-access
- namespace-creation-supported
default_for:
- platform-workloads
- managed-applications
compatibility_notes:
- Weighted routing is optional and must not be assumed in wave 1.
- Metrics availability is opportunistic and not a rollout success criterion.

View file

@ -0,0 +1,30 @@
{
"contract_version": "1.0.0",
"workload_id": "example-platform-service",
"ownership_repo": "railiance-platform",
"criticality": "medium",
"data_classification": "internal",
"dependencies": ["kubernetes-api"],
"identity": {
"principal": "example-platform-service",
"secret_references": []
},
"health": {
"readiness": "HTTP readiness probe succeeds",
"smoke": "Authenticated package smoke succeeds"
},
"recovery": {
"rollback": "Restore the previous verified Kubernetes release"
},
"rail_bindings": [
{
"rail_id": "rail-kubernetes",
"rail_contract": "1.0",
"readiness_state": "declared",
"required_substrate_capabilities": ["kubernetes-api"],
"deployment_artifacts": ["railiance/app.toml"],
"rollout": {"mode": "stage2-isolated-canary"},
"rollback": {"mode": "previous-release"}
}
]
}

View file

@ -0,0 +1,73 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://railiance.dev/schemas/common-workload-v1.schema.json",
"title": "Railiance common managed workload contract",
"type": "object",
"additionalProperties": false,
"required": [
"contract_version",
"workload_id",
"ownership_repo",
"criticality",
"data_classification",
"dependencies",
"identity",
"health",
"recovery",
"rail_bindings"
],
"properties": {
"contract_version": {"const": "1.0.0"},
"workload_id": {"type": "string", "pattern": "^[a-z][a-z0-9-]+$"},
"ownership_repo": {"type": "string", "pattern": "^[a-z][a-z0-9-]+$"},
"criticality": {
"enum": ["low", "medium", "high", "critical"]
},
"data_classification": {
"enum": ["public", "internal", "confidential", "restricted"]
},
"dependencies": {
"type": "array",
"items": {"type": "string"},
"uniqueItems": true
},
"identity": {
"type": "object",
"additionalProperties": false,
"required": ["principal", "secret_references"],
"properties": {
"principal": {"type": "string", "minLength": 1},
"tenant": {"type": "string", "minLength": 1},
"secret_references": {
"type": "array",
"items": {"type": "string"},
"uniqueItems": true
}
}
},
"health": {
"type": "object",
"required": ["readiness", "smoke"],
"properties": {
"readiness": {"type": "string", "minLength": 1},
"smoke": {"type": "string", "minLength": 1},
"audit": {"type": "string"}
}
},
"recovery": {
"type": "object",
"required": ["rollback"],
"properties": {
"rollback": {"type": "string", "minLength": 1},
"rto": {"type": "string"},
"rpo": {"type": "string"}
}
},
"rail_bindings": {
"type": "array",
"minItems": 1,
"items": {"$ref": "rail-binding-v1.schema.json"},
"uniqueItems": true
}
}
}

View file

@ -0,0 +1,54 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://railiance.dev/schemas/derived-rail-v1.schema.json",
"title": "Railiance derived rail declaration",
"type": "object",
"additionalProperties": true,
"required": [
"kind",
"rail_id",
"ownership_repo",
"contract_version",
"composition_kind",
"base_rail",
"base_rail_contract",
"execution_architecture",
"required_substrate_capabilities",
"inherited_semantics",
"overridden_semantics",
"compatibility_constraints",
"readiness_state"
],
"properties": {
"kind": {"const": "execution-rail"},
"rail_id": {"type": "string", "pattern": "^rail-[a-z0-9-]+$"},
"ownership_repo": {"type": "string"},
"contract_version": {"type": "string"},
"composition_kind": {"const": "derived"},
"base_rail": {"const": "rail-kubernetes"},
"base_rail_contract": {"const": "1.0.0"},
"execution_architecture": {"type": "string"},
"required_substrate_capabilities": {
"type": "array",
"minItems": 1,
"items": {"type": "string"},
"uniqueItems": true
},
"inherited_semantics": {
"type": "array",
"minItems": 1,
"items": {"type": "string"},
"uniqueItems": true
},
"overridden_semantics": {
"type": "array",
"minItems": 1,
"items": {"type": "string"},
"uniqueItems": true
},
"compatibility_constraints": {"type": "array", "items": {"type": "string"}},
"readiness_state": {
"enum": ["declared", "installed", "verified", "production-approved", "deprecated"]
}
}
}

View file

@ -0,0 +1,37 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://railiance.dev/schemas/rail-binding-v1.schema.json",
"title": "Railiance workload rail binding",
"type": "object",
"additionalProperties": false,
"required": [
"rail_id",
"rail_contract",
"readiness_state",
"required_substrate_capabilities",
"deployment_artifacts",
"rollout",
"rollback"
],
"properties": {
"rail_id": {"type": "string", "pattern": "^rail-[a-z0-9-]+$"},
"rail_contract": {"type": "string", "pattern": "^[0-9]+\\.[0-9]+(\\.[0-9]+)?$"},
"readiness_state": {
"enum": ["declared", "installed", "verified", "production-approved", "deprecated"]
},
"required_substrate_capabilities": {
"type": "array",
"items": {"type": "string"},
"uniqueItems": true
},
"deployment_artifacts": {
"type": "array",
"minItems": 1,
"items": {"type": "string"}
},
"scaling": {"type": "object"},
"routing": {"type": "object"},
"rollout": {"type": "object"},
"rollback": {"type": "object"}
}
}

118
tools/validate_contracts.py Normal file
View file

@ -0,0 +1,118 @@
#!/usr/bin/env python3
"""Validate Railiance rail and workload contracts without reading credentials."""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
from typing import Any
import yaml
from jsonschema import Draft202012Validator, RefResolver
READINESS_STATES = {
"declared",
"installed",
"verified",
"production-approved",
"deprecated",
}
def load(path: Path) -> Any:
with path.open(encoding="utf-8") as handle:
if path.suffix in {".yaml", ".yml"}:
return yaml.safe_load(handle)
return json.load(handle)
def validate_schema(instance_path: Path, schema_path: Path) -> list[str]:
instance = load(instance_path)
schema = load(schema_path)
store: dict[str, Any] = {}
for candidate in schema_path.parent.glob("*.json"):
candidate_schema = load(candidate)
store[candidate.resolve().as_uri()] = candidate_schema
if schema_id := candidate_schema.get("$id"):
store[schema_id] = candidate_schema
resolver = RefResolver(
base_uri=schema_path.parent.resolve().as_uri() + "/",
referrer=schema,
store=store,
)
validator = Draft202012Validator(schema, resolver=resolver)
return [
f"{instance_path}: {'/'.join(str(part) for part in error.path) or '<root>'}: {error.message}"
for error in sorted(validator.iter_errors(instance), key=lambda item: list(item.path))
]
def validate_rail(path: Path) -> list[str]:
data = load(path)
errors: list[str] = []
required = {
"rail_id",
"ownership_repo",
"contract_version",
"composition_kind",
"execution_architecture",
"required_substrate_capabilities",
"readiness_state",
}
for field in sorted(required - set(data or {})):
errors.append(f"{path}: missing required field {field}")
if not isinstance(data, dict):
return [f"{path}: declaration must be an object"]
if data.get("readiness_state") not in READINESS_STATES:
errors.append(f"{path}: invalid readiness_state {data.get('readiness_state')!r}")
kind = data.get("composition_kind")
if kind not in {"base", "derived"}:
errors.append(f"{path}: composition_kind must be base or derived")
if kind == "derived":
for field in (
"base_rail",
"base_rail_contract",
"inherited_semantics",
"overridden_semantics",
"compatibility_constraints",
):
if field not in data:
errors.append(f"{path}: derived rail missing {field}")
if data.get("base_rail") == data.get("rail_id"):
errors.append(f"{path}: derived rail cannot inherit from itself")
return errors
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--rail", action="append", default=[], type=Path)
parser.add_argument("--workload", action="append", default=[], type=Path)
parser.add_argument(
"--schema-dir",
type=Path,
default=Path(__file__).resolve().parents[1] / "schemas",
)
args = parser.parse_args()
errors: list[str] = []
for path in args.rail:
errors.extend(validate_rail(path))
for path in args.workload:
errors.extend(
validate_schema(path, args.schema_dir / "common-workload.schema.json")
)
if errors:
print("\n".join(errors), file=sys.stderr)
return 1
print(
f"validated {len(args.rail)} rail declaration(s) and "
f"{len(args.workload)} workload declaration(s)"
)
return 0
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -0,0 +1,83 @@
---
id: RAIL-K8S-WP-0002
type: workplan
title: "Version the common workload and derived-rail contract"
domain: financials
repo: rail-kubernetes
status: finished
owner: codex
topic_slug: railiance
created: "2026-07-26"
updated: "2026-07-26"
state_hub_workstream_id: "028b0456-3e54-4539-b563-0eec012c3ecb"
---
# RAIL-K8S-WP-0002 - Version the common workload and derived-rail contract
## Goal
Make `rail-kubernetes` the explicit versioned base contract used by default for
platform workloads and inherited by justified specializations such as
`rail-knative`.
## T01 - Version the base rail declaration
```task
id: RAIL-K8S-WP-0002-T01
status: done
priority: high
state_hub_task_id: "3a2c7e33-f1d4-42dd-b45a-08a5835a22cf"
```
Add composition kind, contract version, required capabilities, and readiness
state without changing current lifecycle behavior.
2026-07-26: `declarations/rail.yaml` now declares base composition, contract
`1.0.0`, required substrate capabilities, verified readiness, and the default
platform/managed-application scope.
## T02 - Add the rail-neutral workload and binding schemas
```task
id: RAIL-K8S-WP-0002-T02
status: done
priority: high
state_hub_task_id: "61e92217-b7d9-4a53-ae28-1c60eb6a201e"
```
Separate common workload identity, criticality, dependencies, security,
health, evidence, and recovery requirements from rail-specific bindings.
2026-07-26: Added the common workload, rail-binding, and derived-rail JSON
schemas plus a valid Kubernetes-default example.
## T03 - Add compatibility conformance
```task
id: RAIL-K8S-WP-0002-T03
status: done
priority: high
state_hub_task_id: "06e1aa1f-2111-4be5-9b4b-28fd440eb7c3"
```
Provide an idempotent validator suitable for local, agent, CI, and scheduled
execution. Reject incompatible base contracts, missing ownership, and
unsupported substrate capabilities.
2026-07-26: Added `tools/validate_contracts.py`. It validates base/derived rail
shape and common workload JSON Schema without accessing runtime credentials.
## T04 - Preserve the Kubernetes default
```task
id: RAIL-K8S-WP-0002-T04
status: done
priority: medium
state_hub_task_id: "54793f28-a417-43c4-9a02-aa55199fca9f"
```
Document and test that workloads without a justified specialization continue
to bind to `rail-kubernetes`.
2026-07-26: The base declaration identifies Kubernetes as the default for
platform workloads and managed applications; the example uses that binding.