Import core rail-kubernetes contract artifacts

This commit is contained in:
codex 2026-07-25 19:28:32 +02:00
parent 513407acaa
commit b48eaea942
9 changed files with 1375 additions and 5 deletions

View file

@ -42,4 +42,6 @@ The boundary and source material come from:
- `declarations/rail.yaml` — source-controlled rail contract
- `docs/` — wave-1 contract and import plan
- `schemas/` — machine-readable workload contract schema
- `examples/` — canonical example workload declarations for the rail
- `workplans/` — repo-local execution work

View file

@ -12,4 +12,4 @@
| task | RAIL-K8S-WP-0001-T01 | done | — | workplans/RAIL-K8S-WP-0001-bootstrap-and-wave1-import.md |
| task | RAIL-K8S-WP-0001-T02 | progress | — | workplans/RAIL-K8S-WP-0001-bootstrap-and-wave1-import.md |
| task | RAIL-K8S-WP-0001-T03 | wait | — | workplans/RAIL-K8S-WP-0001-bootstrap-and-wave1-import.md |
| task | RAIL-K8S-WP-0001-T04 | wait | — | workplans/RAIL-K8S-WP-0001-bootstrap-and-wave1-import.md |
| task | RAIL-K8S-WP-0001-T04 | progress | — | workplans/RAIL-K8S-WP-0001-bootstrap-and-wave1-import.md |

View file

@ -1,4 +1,6 @@
# rail-kubernetes Docs
- `app-toml-contract.md` — canonical workload declaration contract for the rail
- `deployment-lifecycle.md` — canonical stage 1 / 2 / 3 lifecycle contract
- `wave-1-contract.md` — current rail contract and boundary summary
- `source-import-plan.md` — source material and migration direction from existing repos

240
docs/app-toml-contract.md Normal file
View file

@ -0,0 +1,240 @@
# Railiance app.toml Contract
This document defines the repository-local `railiance/app.toml` contract used by
Railiance staged promotion tooling. The file tells Railiance how a workload
moves through Stage 1 local validation, Stage 2 production canary, and Stage 3
production promotion without relying on bespoke operator notes.
The contract is intentionally declarative. Commands, health checks, platform
dependencies, and secret references are described by stable names. Plaintext
secrets, bearer tokens, kubeconfigs, and private key material must never appear
in `railiance/app.toml`.
Ownership note: this contract now lives in `rail-kubernetes` as part of the
wave-1 split. `railiance-cluster` may keep compatibility copies during the
migration window, but long-term ownership belongs with the generic
workload-on-Kubernetes contract here rather than with S2 cluster substrate
ownership.
The machine-readable schema lives at `schemas/railiance-app.schema.json`. A
minimal example lives at `examples/railiance/app.toml`.
## File Location
Participating workload repositories declare the contract at:
```text
railiance/app.toml
```
Overlay repositories for third-party applications use the same path in the
overlay repo, not in the upstream source repository.
## Versioning
Every file must include:
```toml
schema_version = "railiance.app.v1"
```
Breaking contract changes require a new schema version. Tooling must fail closed
when it sees an unsupported `schema_version`.
## Top-Level Sections
### app
Identifies the workload and its ownership boundary.
Required fields:
- `id`: stable lowercase id using letters, numbers, and hyphens.
- `name`: human-readable workload name.
- `repo`: owning source or overlay repository slug.
- `owner`: owning team, domain, or operator group.
- `criticality`: one of `low`, `medium`, `high`, or `critical`.
- `description`: short purpose statement.
Production-critical workloads include source forge, identity, State Hub,
Inter-Hub, databases, object stores, backup systems, ingress, and cluster-wide
policy controllers. For those workloads, `criticality = "critical"` requires
explicit human approval before Stage 2 traffic exposure and Stage 3 promotion.
### source
Identifies the candidate under promotion.
Required fields:
- `revision`: commit id, tag, or immutable source revision expression.
- `artifact`: artifact kind, normally `image`, `helm-chart`, or `bundle`.
- `digest_policy`: one of `required`, `preferred`, or `not-applicable`.
If an image is promoted, Stage 2 and Stage 3 tooling should prefer immutable
image digests over mutable tags.
### platform.dependencies
Declares platform services required before canary or production promotion.
Each dependency has:
- `name`: stable service name.
- `kind`: dependency kind such as `postgres`, `redis`, `object-store`,
`identity`, `state-hub`, `inter-hub`, `network`, or `other`.
- `required`: boolean.
- `stage`: earliest stage that needs it, one of `stage1`, `stage2`, `stage3`.
- `evidence`: non-secret evidence expected before promotion, such as a health
endpoint result, Kubernetes Ready condition, or State Hub progress id.
### secrets.references
Declares required secret references without secret values.
Each reference has:
- `name`: workload-local secret name.
- `route`: approved credential route id, for example `openbao-api-key`,
`key-cape-oidc-login`, or `activity-core-issue-sink`.
- `target`: non-secret target reference such as a Kubernetes Secret name,
ExternalSecret name, OpenBao path, or environment variable name.
- `stage`: earliest stage that needs the secret.
- `required`: boolean.
Forbidden fields include plaintext values, tokens, passwords, kubeconfigs, or
private keys. Tooling must reject suspicious field names such as `value`,
`token`, `password`, `secret`, `private_key`, or `kubeconfig` inside secret
reference objects unless they are part of the approved non-secret `target` text.
### observability
Defines how promotion tooling proves the workload is alive and observable.
Required fields:
- `health_endpoints`: one or more HTTP health endpoint declarations.
- `metrics`: optional metrics endpoint or query references.
- `logs`: optional log selectors or query references.
Health endpoint declarations include `name`, `url`, `stage`, and expected
status code. URLs may be internal service URLs for Stage 2/3; they must not
embed credentials.
### rollback
Defines how the workload returns to a previous stable state.
Required fields:
- `strategy`: one of `helm-revision`, `image-digest`, `traffic-shift`,
`manual-runbook`, or `none`.
- `command`: command name or runbook path. This may be a placeholder before
T07 implements automation, but it must tell the operator where rollback lives.
- `verification`: non-secret check to confirm rollback succeeded.
`strategy = "none"` is allowed only for Stage 1-only workloads and must not be
used for production-critical workloads.
## Stage Sections
The contract has one table for each stage:
```toml
[stages.stage1]
[stages.stage2]
[stages.stage3]
```
Each stage includes:
- `enabled`: boolean.
- `namespace`: target Kubernetes namespace, or a local namespace for Stage 1.
- `release`: release identity.
- `commands`: ordered command aliases or shell commands that tooling may run.
- `checks`: ordered check ids to evaluate.
- `evidence`: expected non-secret evidence outputs.
- `requires_approval`: boolean.
Stage 2 additionally includes `canary_mode`, one of `weighted`, `header`,
`path`, `shadow`, or `isolated`, plus `observation_minutes` and optional
`traffic_percent` when weighted routing is used.
Stage 3 additionally includes `promotion_mode`, one of `traffic-shift`,
`release-replace`, `selector-switch`, or `workflow`, plus `previous_stable`.
## Check Definitions
Checks live under `[[checks]]` entries and are referenced by stage `checks`.
Required fields:
- `id`: stable check id.
- `type`: one of `command`, `http`, `kubernetes`, `helm`, `metric`, `log`, or
`manual`.
- `stage`: earliest stage that may run the check.
- `description`: human-readable purpose.
- `required`: boolean.
Type-specific fields:
- `command`: `run` command string and optional `timeout_seconds`.
- `http`: `url`, `expected_status`, and optional `timeout_seconds`.
- `kubernetes`: `namespace`, `resource`, and `condition`.
- `helm`: `chart`, `values`, and `mode` such as `template` or
`server-dry-run`.
- `metric`: `query`, `window_minutes`, and `threshold`.
- `log`: `selector`, `window_minutes`, and `forbidden_patterns`.
- `manual`: `evidence_required` text.
Checks must not print secrets. If a check needs secret-backed access, the result
records only the route, target object, and pass/fail state.
## Command Semantics
Commands in `app.toml` are declarations for Railiance tooling. Stage 1, Stage
2, and Stage 3 commands now have local CLI support; workload scripts may still
wrap them for service-specific checks.
Expected mapping:
- Stage 1 commands are consumed by `bin/railiance run <overlay-dir>`.
- Stage 2 commands are consumed by `bin/railiance deploy --stage 2 <overlay-dir>`
and `bin/railiance observe --stage 2 <overlay-dir>`.
- Stage 3 commands are consumed by `bin/railiance promote <overlay-dir>` and
`bin/railiance rollback <overlay-dir>`.
Tooling must emit machine-readable results with workload identity, candidate
revision, checks run, pass/fail status, non-secret evidence, rollback target,
and approval state.
## Minimal Example
See `examples/railiance/app.toml`. It declares a critical internal service with:
- immutable image digest requirement;
- Stage 1 local validation;
- Stage 2 isolated canary;
- Stage 3 release replacement;
- OpenBao-routed secret references without values;
- HTTP, Helm, Kubernetes, and manual approval checks.
## Adoption Rules
A workload can enter Stage 1 when `app.toml` passes schema validation and all
Stage 1 required checks are declared.
A workload can enter Stage 2 only when:
- Stage 1 passed for the same candidate artifact;
- Stage 2 namespace, release, canary mode, health checks, dependencies, and
rollback target are declared;
- secret references use approved routes and contain no values;
- production-critical workloads have explicit approval.
A workload can enter Stage 3 only when:
- Stage 2 acceptance gates passed for the same candidate artifact;
- `previous_stable` and rollback verification are recorded;
- backup/restore posture is current for stateful workloads;
- production-critical workloads have explicit human approval.

View file

@ -0,0 +1,340 @@
# Railiance Deployment Lifecycle
This document defines the Railiance three-stage promotion lifecycle for
workloads that run on the Railiance Kubernetes substrate.
The lifecycle exists so production workloads move through repeatable gates
instead of one-off operator memory. It is intentionally conservative: every
stage must leave evidence, every promotion must have a rollback path, and
critical workloads require explicit human approval before production traffic is
changed.
Ownership note: this specification now lives in `rail-kubernetes` as the
wave-1 home of the default Kubernetes execution rail. `railiance-cluster` may
retain compatibility shims during migration, but long-term ownership of the
generic lifecycle contract belongs here rather than in the S2 substrate repo.
## Scope
This specification now lives beside the Kubernetes rail contract rather than
beside the cluster substrate. The compatibility tooling may still live
elsewhere during migration, but long-term ownership is not S2.
Repo boundaries:
- `rail-kubernetes` owns lifecycle semantics, generic promotion gates,
canary validation, production routing expectations, rollback expectations,
and the generic workload contract.
- `railiance-cluster` owns only the substrate prerequisites published to that
rail: cluster connectivity, namespace behavior, ingress availability, and
operator/runtime expectations.
- `railiance-apps` owns workload-specific Helm values, application release
definitions, and production workload configuration.
- `railiance-platform` owns shared platform services such as databases,
caches, object storage, and backup targets.
- `railiance-enablement` owns developer-facing templates, CI workflows, and
local ergonomics.
- `railiance-infra` owns host provisioning, OS hardening, SSH, firewall, and
node bootstrap below Kubernetes.
## Lifecycle Overview
Railiance promotes a workload through three stages:
1. Stage 1: local validation.
2. Stage 2: production canary.
3. Stage 3: production promotion.
The stages are sequential. A workload may return to an earlier stage at any
time, but it must not skip a stage when moving toward production unless an
operator records an emergency exception in State Hub.
Each stage emits a machine-readable result with:
- workload identity;
- source revision or image digest;
- target environment;
- checks run;
- pass/fail status;
- non-secret evidence references;
- rollback target, when applicable;
- approving human or explicit "not required" decision.
## Workload Declaration
Each participating workload should declare its promotion contract in a
repository-local `railiance/app.toml`. The contract is defined in
`docs/app-toml-contract.md`, with a machine-readable schema at
`schemas/railiance-app.schema.json`. This lifecycle expects every workload
declaration to provide at least:
- stable workload name and owning repo;
- source revision, image tag, or image digest policy;
- stage-specific namespaces or release names;
- health checks and observability endpoints;
- ingress or routing targets;
- platform dependencies;
- rollback command or previous-stable reference;
- secret references by name or path, never plaintext secret values.
If a workload cannot provide a machine-readable declaration yet, it may still
use this lifecycle through a written operator runbook, but that is a temporary
compatibility path. The runbook must identify the missing declaration fields.
## Stage 1: Local Validation
Stage 1 proves that the workload can be built, configured, and checked outside
production traffic.
Typical Stage 1 targets:
- local container runtime;
- local Kubernetes such as k3d, kind, or a disposable namespace;
- dry-run Helm rendering;
- unit, integration, migration, and smoke checks that do not require production
credentials.
Required Stage 1 checks:
- source revision is cleanly identified;
- build or artifact selection is deterministic;
- Helm templates or manifests render without invalid Kubernetes objects;
- local health checks pass;
- required secrets are referenced by name only and are not printed;
- database migrations, if any, are classified as reversible, forward-only, or
requiring human approval;
- a Stage 2 candidate artifact is named by immutable digest or equivalent
immutable revision.
Stage 1 fails closed when:
- local checks are skipped without an approved reason;
- generated manifests contain plaintext secrets;
- the artifact cannot be traced to source;
- the workload cannot state how it will be observed in Stage 2.
Stage 1 completion does not authorize production traffic. It only makes a
workload eligible for Stage 2 review.
## Stage 2: Production Canary
Stage 2 deploys the candidate to production infrastructure with limited or
isolated exposure. The goal is to observe the candidate against real platform
dependencies while keeping blast radius small.
Acceptable canary forms:
- weighted ingress split between stable and canary;
- header-based or path-based routing for operator traffic only;
- shadow deployment receiving replicated non-mutating traffic;
- isolated production namespace with manually triggered probes.
The selected canary form must be declared before deployment. If weighted
routing is unavailable, the fallback must preserve the same safety property:
the candidate can be observed without silently replacing stable production.
Required Stage 2 prechecks:
- Stage 1 result passed for the same candidate artifact;
- cluster connectivity and namespace readiness are verified;
- target image digest or immutable tag exists in the registry;
- Helm server-side dry-run succeeds;
- ingress, certificate, and DNS prerequisites are present where applicable;
- platform dependencies are healthy or explicitly degraded with operator
approval;
- rollback target is known before the canary is applied;
- monitoring and log queries are available for the canary release.
Required Stage 2 evidence:
- rendered release identity;
- applied namespace and release name;
- pod readiness and restart status;
- ingress or routing state;
- key health endpoint result;
- relevant metrics window or explicit "metrics unavailable" note;
- State Hub progress note with non-secret evidence;
- operator approval when the workload is production-critical.
Canary acceptance gates:
- canary pods remain ready for the configured observation window;
- no crash loops, repeated restarts, or pending pods remain unexplained;
- health checks pass from inside and outside the cluster when both are
applicable;
- error rate, latency, and saturation do not regress beyond the workload's
declared threshold;
- no unexpected schema, storage, or queue side effects are observed;
- logs show no secret leakage and no repeated authorization failures;
- rollback has been tested previously or is a single documented command with a
known previous-stable target.
Default observation windows:
- non-critical internal service: 15 minutes;
- user-facing or shared platform service: 30 minutes;
- production-critical infrastructure such as Forgejo, identity, registry, or
State Hub: operator-defined window, minimum 60 minutes unless explicitly
waived.
Stage 2 fails closed when:
- the canary cannot be distinguished from stable production;
- production routing changes more traffic than intended;
- any required evidence is missing and no operator waiver is recorded;
- rollback target is unknown;
- the candidate needs a secret, credential, or platform dependency that was not
declared before the canary.
## Stage 3: Production Promotion
Stage 3 promotes the accepted candidate to the stable production path.
Promotion may mean:
- shifting weighted traffic to the canary release;
- replacing the stable Helm release with the accepted candidate;
- changing an ingress selector or service target;
- activating an operator-approved rollout workflow.
Required Stage 3 prechecks:
- Stage 2 acceptance gates passed for the same candidate artifact;
- the previous stable version is recorded;
- backup and restore posture is current for stateful workloads;
- migrations are approved and sequenced;
- production-critical workloads have explicit human approval;
- a rollback command and rollback verification check are available.
Required Stage 3 evidence:
- promotion command or workflow id;
- previous stable version;
- new stable version;
- production routing state after promotion;
- smoke result after promotion;
- rollback target retained;
- State Hub progress note with non-secret evidence.
Stage 3 is complete only after the post-promotion smoke passes and the
workload's stable routing points at the promoted candidate.
## Rollback Expectations
Rollback is part of every promotion, not an afterthought.
Every Stage 2 and Stage 3 action must identify one of:
- previous stable Helm release revision;
- previous image digest and values file;
- previous ingress/routing configuration;
- documented manual recovery path when automation is not yet safe.
Rollback must be immediate when:
- production availability is degraded;
- canary traffic escapes the declared blast radius;
- the workload emits repeated authorization or secret-handling errors;
- data integrity is at risk;
- an operator revokes approval during the observation window.
Rollback may be deferred only when the rollback itself is more dangerous than
the incident state. That decision requires a State Hub note and human approval.
After rollback, record:
- triggering symptom;
- rollback action;
- final stable version;
- remaining cleanup;
- whether the failed candidate is blocked, abandoned, or returned to Stage 1.
## Human Approval Gates
Human approval is required before production traffic changes for
production-critical workloads.
Production-critical workloads include:
- source forge and package registry workloads such as Forgejo or Gitea;
- identity, MFA, SSO, or authorization systems;
- State Hub, Inter-Hub, and operator coordination services;
- databases, object stores, and backup systems;
- ingress, certificate, or cluster-wide policy controllers;
- any workload whose failure blocks multiple repos or domains.
Approval must be recorded as a non-secret State Hub note or task comment. The
approval record should name:
- approving operator;
- candidate artifact;
- stage being approved;
- observation window;
- rollback target;
- any waived gates and why.
Emergency approval can be retrospective only when delaying the action would
increase production risk. Retrospective approval must be recorded immediately
after stabilization.
## Evidence And Secret Handling
Lifecycle evidence must be useful without being sensitive.
Allowed evidence:
- commit ids;
- image tags and digests;
- workflow ids;
- Kubernetes object names;
- pod status summaries;
- HTTP status codes;
- timestamps;
- State Hub progress ids;
- pass/fail summaries.
Forbidden evidence:
- plaintext secrets;
- bearer tokens;
- static API keys;
- kubeconfigs;
- private key material;
- full environment dumps;
- logs that contain credentials or user private data.
When a check needs secret-backed access, record only the access path and result,
for example: "OpenBao path configured, token exchange returned 200".
## Forgejo Readiness Interpretation
This lifecycle is clear enough for Forgejo when a future Forgejo workplan can
answer these questions before production cutover:
- What source revision and image digest are being promoted?
- What local checks prove the candidate is viable?
- How is the production canary isolated or traffic-limited?
- Which health, registry, SSH, web, Actions, and email recovery checks define
acceptance?
- Who approves the Stage 3 traffic switch?
- What is the previous stable target?
- How is repository data protected before and after promotion?
- How will rollback be verified without losing package or repository state?
If any answer is missing, Forgejo remains in Stage 1 or Stage 2 preparation and
must not cut over to Stage 3.
## Minimum Command Contract
The Railiance CLI makes these lifecycle operations repeatable:
```text
bin/railiance run <overlay-dir> # Stage 1 local validation
bin/railiance deploy --stage 2 <overlay-dir> --plan # Stage 2 canary plan
bin/railiance observe --stage 2 <overlay-dir> --plan # Stage 2 evidence targets
bin/railiance promote <overlay-dir> --plan # Stage 3 production promotion
bin/railiance rollback <overlay-dir> --plan # rollback to previous stable
```
The exact command names may change as implementation lands, but the behavior
must preserve the stage gates and evidence requirements in this document.

View file

@ -4,12 +4,22 @@
Record which reviewed source materials are expected to seed this repo.
## First-Wave Imports
## Imported In The Current Wave
Expected source documents from `railiance-cluster`:
Imported source documents from `railiance-cluster`:
- `docs/deployment-lifecycle.md`
- `docs/app-toml-contract.md`
Imported source assets from `railiance-cluster`:
- `schemas/railiance-app.schema.json`
- `examples/railiance/app.toml`
## Pending First-Wave Imports
Expected source documents from `railiance-cluster`:
- `docs/overlay-repo-pattern.md`
- `docs/canary-helm-template.md`
- `docs/stage2-deploy-observe.md`
@ -18,8 +28,6 @@ Expected source documents from `railiance-cluster`:
Expected source assets from `railiance-cluster`:
- `schemas/railiance-app.schema.json`
- `examples/railiance/app.toml`
- `tools/create_railiance_overlay_repo.sh`
- `tools/cmd/railiance-run`
- `tools/cmd/railiance-stage2`

176
examples/railiance/app.toml Normal file
View file

@ -0,0 +1,176 @@
schema_version = "railiance.app.v1"
[app]
id = "example-service"
name = "Example Service"
repo = "railiance-apps/example-service"
owner = "platform"
criticality = "critical"
description = "Reference declaration for the Railiance staged promotion lifecycle."
[source]
revision = "git:main"
artifact = "image"
digest_policy = "required"
[rollback]
strategy = "helm-revision"
command = "bin/railiance rollback example-service"
verification = "GET /health returns 200 on the restored stable release."
[[platform.dependencies]]
name = "state-hub"
kind = "state-hub"
required = true
stage = "stage2"
evidence = "State Hub /healthz returns ok from the cluster path."
[[platform.dependencies]]
name = "postgres"
kind = "postgres"
required = true
stage = "stage2"
evidence = "Target database reports Ready and backup posture is current."
[[secrets.references]]
name = "runtime-api-key"
route = "openbao-api-key"
target = "ExternalSecret/example-service-runtime"
stage = "stage2"
required = true
[[observability.health_endpoints]]
name = "local-health"
url = "http://127.0.0.1:8080/health"
stage = "stage1"
expected_status = 200
[[observability.health_endpoints]]
name = "cluster-health"
url = "http://example-service.example-service.svc.cluster.local:8080/health"
stage = "stage2"
expected_status = 200
[[observability.metrics]]
name = "request-errors"
reference = 'promql:rate(http_requests_total{status=~"5.."}[5m])'
stage = "stage2"
[[observability.logs]]
name = "secret-leak-scan"
reference = "kubectl logs -n example-service deploy/example-service-canary"
stage = "stage2"
[stages.stage1]
enabled = true
namespace = "local"
release = "example-service-local"
commands = ["make test", "helm template charts/example-service"]
checks = ["unit-tests", "helm-template", "local-health"]
evidence = ["pytest output", "helm template success", "local health 200"]
requires_approval = false
[stages.stage2]
enabled = true
namespace = "example-service"
release = "example-service-canary"
commands = ["bin/railiance deploy --stage 2 example-service", "bin/railiance observe example-service"]
checks = ["server-dry-run", "canary-ready", "cluster-health", "operator-approval"]
evidence = ["release name", "pod readiness", "health 200", "State Hub progress id"]
requires_approval = true
canary_mode = "isolated"
observation_minutes = 60
[stages.stage3]
enabled = true
namespace = "example-service"
release = "example-service"
commands = ["bin/railiance promote example-service", "bin/railiance observe example-service"]
checks = ["stage2-accepted", "rollback-target", "cluster-health", "operator-approval"]
evidence = ["promotion command id", "new stable digest", "post-promotion smoke"]
requires_approval = true
promotion_mode = "release-replace"
previous_stable = "helm:example-service:previous"
[[checks]]
id = "unit-tests"
type = "command"
stage = "stage1"
description = "Run repository unit tests."
required = true
run = "make test"
timeout_seconds = 600
[[checks]]
id = "helm-template"
type = "helm"
stage = "stage1"
description = "Render Helm templates locally."
required = true
chart = "charts/example-service"
values = "values/local.yaml"
mode = "template"
[[checks]]
id = "local-health"
type = "http"
stage = "stage1"
description = "Confirm local service health."
required = true
url = "http://127.0.0.1:8080/health"
expected_status = 200
timeout_seconds = 10
[[checks]]
id = "server-dry-run"
type = "helm"
stage = "stage2"
description = "Render and submit a server-side dry run before canary."
required = true
chart = "charts/example-service"
values = "values/canary.yaml"
mode = "server-dry-run"
[[checks]]
id = "canary-ready"
type = "kubernetes"
stage = "stage2"
description = "Canary deployment reaches Available."
required = true
namespace = "example-service"
resource = "deploy/example-service-canary"
condition = "Available"
[[checks]]
id = "cluster-health"
type = "http"
stage = "stage2"
description = "Cluster health endpoint returns 200."
required = true
url = "http://example-service.example-service.svc.cluster.local:8080/health"
expected_status = 200
timeout_seconds = 10
[[checks]]
id = "operator-approval"
type = "manual"
stage = "stage2"
description = "Human approval is recorded before production-critical traffic changes."
required = true
evidence_required = "State Hub approval note id, candidate digest, rollback target."
[[checks]]
id = "stage2-accepted"
type = "manual"
stage = "stage3"
description = "Stage 2 gates passed for the same candidate artifact."
required = true
evidence_required = "State Hub Stage 2 acceptance progress id."
[[checks]]
id = "rollback-target"
type = "manual"
stage = "stage3"
description = "Previous stable release is recorded before promotion."
required = true
evidence_required = "Previous Helm revision or image digest."

View file

@ -0,0 +1,596 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://railiance.local/schemas/railiance-app.schema.json",
"title": "Railiance app.toml contract",
"type": "object",
"additionalProperties": false,
"required": [
"schema_version",
"app",
"source",
"platform",
"secrets",
"observability",
"rollback",
"stages",
"checks"
],
"properties": {
"schema_version": {
"const": "railiance.app.v1"
},
"app": {
"type": "object",
"additionalProperties": false,
"required": [
"id",
"name",
"repo",
"owner",
"criticality",
"description"
],
"properties": {
"id": {
"type": "string",
"pattern": "^[a-z0-9][a-z0-9-]*$"
},
"name": {
"type": "string",
"minLength": 1
},
"repo": {
"type": "string",
"minLength": 1
},
"owner": {
"type": "string",
"minLength": 1
},
"criticality": {
"enum": [
"low",
"medium",
"high",
"critical"
]
},
"description": {
"type": "string",
"minLength": 1
}
}
},
"source": {
"type": "object",
"additionalProperties": false,
"required": [
"revision",
"artifact",
"digest_policy"
],
"properties": {
"revision": {
"type": "string",
"minLength": 1
},
"artifact": {
"enum": [
"image",
"helm-chart",
"bundle",
"manifest",
"other"
]
},
"digest_policy": {
"enum": [
"required",
"preferred",
"not-applicable"
]
}
}
},
"platform": {
"type": "object",
"additionalProperties": false,
"required": [
"dependencies"
],
"properties": {
"dependencies": {
"type": "array",
"items": {
"$ref": "#/$defs/dependency"
}
}
}
},
"secrets": {
"type": "object",
"additionalProperties": false,
"required": [
"references"
],
"properties": {
"references": {
"type": "array",
"items": {
"$ref": "#/$defs/secretReference"
}
}
}
},
"observability": {
"type": "object",
"additionalProperties": false,
"required": [
"health_endpoints"
],
"properties": {
"health_endpoints": {
"type": "array",
"minItems": 1,
"items": {
"$ref": "#/$defs/healthEndpoint"
}
},
"metrics": {
"type": "array",
"default": [],
"items": {
"$ref": "#/$defs/observationReference"
}
},
"logs": {
"type": "array",
"default": [],
"items": {
"$ref": "#/$defs/observationReference"
}
}
}
},
"rollback": {
"type": "object",
"additionalProperties": false,
"required": [
"strategy",
"command",
"verification"
],
"properties": {
"strategy": {
"enum": [
"helm-revision",
"image-digest",
"traffic-shift",
"manual-runbook",
"none"
]
},
"command": {
"type": "string",
"minLength": 1
},
"verification": {
"type": "string",
"minLength": 1
}
}
},
"stages": {
"type": "object",
"additionalProperties": false,
"required": [
"stage1",
"stage2",
"stage3"
],
"properties": {
"stage1": {
"$ref": "#/$defs/stage1"
},
"stage2": {
"$ref": "#/$defs/stage2"
},
"stage3": {
"$ref": "#/$defs/stage3"
}
}
},
"checks": {
"type": "array",
"minItems": 1,
"items": {
"$ref": "#/$defs/check"
}
}
},
"$defs": {
"stageName": {
"enum": [
"stage1",
"stage2",
"stage3"
]
},
"dependency": {
"type": "object",
"additionalProperties": false,
"required": [
"name",
"kind",
"required",
"stage",
"evidence"
],
"properties": {
"name": {
"type": "string",
"minLength": 1
},
"kind": {
"enum": [
"postgres",
"redis",
"object-store",
"identity",
"state-hub",
"inter-hub",
"network",
"other"
]
},
"required": {
"type": "boolean"
},
"stage": {
"$ref": "#/$defs/stageName"
},
"evidence": {
"type": "string",
"minLength": 1
}
}
},
"secretReference": {
"type": "object",
"additionalProperties": false,
"required": [
"name",
"route",
"target",
"stage",
"required"
],
"properties": {
"name": {
"type": "string",
"minLength": 1
},
"route": {
"type": "string",
"minLength": 1
},
"target": {
"type": "string",
"minLength": 1
},
"stage": {
"$ref": "#/$defs/stageName"
},
"required": {
"type": "boolean"
}
},
"not": {
"anyOf": [
{
"required": [
"value"
]
},
{
"required": [
"token"
]
},
{
"required": [
"password"
]
},
{
"required": [
"secret"
]
},
{
"required": [
"private_key"
]
},
{
"required": [
"kubeconfig"
]
}
]
}
},
"healthEndpoint": {
"type": "object",
"additionalProperties": false,
"required": [
"name",
"url",
"stage",
"expected_status"
],
"properties": {
"name": {
"type": "string",
"minLength": 1
},
"url": {
"type": "string",
"minLength": 1
},
"stage": {
"$ref": "#/$defs/stageName"
},
"expected_status": {
"type": "integer",
"minimum": 100,
"maximum": 599
}
}
},
"observationReference": {
"type": "object",
"additionalProperties": false,
"required": [
"name",
"reference",
"stage"
],
"properties": {
"name": {
"type": "string",
"minLength": 1
},
"reference": {
"type": "string",
"minLength": 1
},
"stage": {
"$ref": "#/$defs/stageName"
}
}
},
"check": {
"type": "object",
"additionalProperties": true,
"required": [
"id",
"type",
"stage",
"description",
"required"
],
"properties": {
"id": {
"type": "string",
"minLength": 1
},
"type": {
"enum": [
"command",
"http",
"kubernetes",
"helm",
"metric",
"log",
"manual"
]
},
"stage": {
"$ref": "#/$defs/stageName"
},
"description": {
"type": "string",
"minLength": 1
},
"required": {
"type": "boolean"
}
}
},
"stage1": {
"type": "object",
"additionalProperties": false,
"required": [
"enabled",
"namespace",
"release",
"commands",
"checks",
"evidence",
"requires_approval"
],
"properties": {
"enabled": {
"type": "boolean"
},
"namespace": {
"type": "string",
"minLength": 1
},
"release": {
"type": "string",
"minLength": 1
},
"commands": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"checks": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"evidence": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"requires_approval": {
"type": "boolean"
}
}
},
"stage2": {
"type": "object",
"additionalProperties": false,
"required": [
"enabled",
"namespace",
"release",
"commands",
"checks",
"evidence",
"requires_approval",
"canary_mode",
"observation_minutes"
],
"properties": {
"enabled": {
"type": "boolean"
},
"namespace": {
"type": "string",
"minLength": 1
},
"release": {
"type": "string",
"minLength": 1
},
"commands": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"checks": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"evidence": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"requires_approval": {
"type": "boolean"
},
"canary_mode": {
"enum": [
"weighted",
"header",
"path",
"shadow",
"isolated"
]
},
"observation_minutes": {
"type": "integer",
"minimum": 1
},
"traffic_percent": {
"type": "integer",
"minimum": 0,
"maximum": 100
}
}
},
"stage3": {
"type": "object",
"additionalProperties": false,
"required": [
"enabled",
"namespace",
"release",
"commands",
"checks",
"evidence",
"requires_approval",
"promotion_mode",
"previous_stable"
],
"properties": {
"enabled": {
"type": "boolean"
},
"namespace": {
"type": "string",
"minLength": 1
},
"release": {
"type": "string",
"minLength": 1
},
"commands": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"checks": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"evidence": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
},
"requires_approval": {
"type": "boolean"
},
"promotion_mode": {
"enum": [
"traffic-shift",
"release-replace",
"selector-switch",
"workflow"
]
},
"previous_stable": {
"type": "string",
"minLength": 1
}
}
}
}
}

View file

@ -73,6 +73,12 @@ Acceptance:
import plan. Full import of docs, schema, examples, and helper surfaces is
still pending.
2026-07-25: Imported the first core generic contract artifacts from
`railiance-cluster`: `docs/deployment-lifecycle.md`,
`docs/app-toml-contract.md`, `schemas/railiance-app.schema.json`, and
`examples/railiance/app.toml`. Helper command surfaces and the remaining
supporting docs are still pending.
## T03 - Prepare the compatibility handoff from `railiance-cluster`
```task