Import core rail-kubernetes contract artifacts
This commit is contained in:
parent
513407acaa
commit
b48eaea942
9 changed files with 1375 additions and 5 deletions
|
|
@ -42,4 +42,6 @@ The boundary and source material come from:
|
||||||
|
|
||||||
- `declarations/rail.yaml` — source-controlled rail contract
|
- `declarations/rail.yaml` — source-controlled rail contract
|
||||||
- `docs/` — wave-1 contract and import plan
|
- `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
|
- `workplans/` — repo-local execution work
|
||||||
|
|
|
||||||
|
|
@ -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-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-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-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 |
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
# rail-kubernetes Docs
|
# 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
|
- `wave-1-contract.md` — current rail contract and boundary summary
|
||||||
- `source-import-plan.md` — source material and migration direction from existing repos
|
- `source-import-plan.md` — source material and migration direction from existing repos
|
||||||
|
|
|
||||||
240
docs/app-toml-contract.md
Normal file
240
docs/app-toml-contract.md
Normal 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.
|
||||||
340
docs/deployment-lifecycle.md
Normal file
340
docs/deployment-lifecycle.md
Normal 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.
|
||||||
|
|
@ -4,12 +4,22 @@
|
||||||
|
|
||||||
Record which reviewed source materials are expected to seed this repo.
|
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/deployment-lifecycle.md`
|
||||||
- `docs/app-toml-contract.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/overlay-repo-pattern.md`
|
||||||
- `docs/canary-helm-template.md`
|
- `docs/canary-helm-template.md`
|
||||||
- `docs/stage2-deploy-observe.md`
|
- `docs/stage2-deploy-observe.md`
|
||||||
|
|
@ -18,8 +28,6 @@ Expected source documents from `railiance-cluster`:
|
||||||
|
|
||||||
Expected source assets 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/create_railiance_overlay_repo.sh`
|
||||||
- `tools/cmd/railiance-run`
|
- `tools/cmd/railiance-run`
|
||||||
- `tools/cmd/railiance-stage2`
|
- `tools/cmd/railiance-stage2`
|
||||||
|
|
|
||||||
176
examples/railiance/app.toml
Normal file
176
examples/railiance/app.toml
Normal 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."
|
||||||
596
schemas/railiance-app.schema.json
Normal file
596
schemas/railiance-app.schema.json
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -73,6 +73,12 @@ Acceptance:
|
||||||
import plan. Full import of docs, schema, examples, and helper surfaces is
|
import plan. Full import of docs, schema, examples, and helper surfaces is
|
||||||
still pending.
|
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`
|
## T03 - Prepare the compatibility handoff from `railiance-cluster`
|
||||||
|
|
||||||
```task
|
```task
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue