Compare commits

...

7 commits

Author SHA1 Message Date
codex
f1c717b791 Merge remote bootstrap history 2026-07-25 22:19:56 +02:00
codex
433949ab57 Import supporting rail-kubernetes docs 2026-07-25 19:39:40 +02:00
codex
b48eaea942 Import core rail-kubernetes contract artifacts 2026-07-25 19:28:32 +02:00
codex
513407acaa Bootstrap the first rail-kubernetes repo 2026-07-25 18:57:55 +02:00
custodian-sync
e6867e5ca9 chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-07-25:
  - update .custodian-brief.md for rail-kubernetes
2026-07-25 18:56:52 +02:00
custodian-sync
90c89a4d7d chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-07-25:
  - RAIL-K8S-WP-0001-T04: wait → progress
2026-07-25 18:56:51 +02:00
custodian-sync
0e320c5632 chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-07-25:
  - RAIL-K8S-WP-0001-T04: progress → wait
2026-07-25 18:56:09 +02:00
24 changed files with 2356 additions and 1 deletions

24
.custodian-brief.md Normal file
View file

@ -0,0 +1,24 @@
<!-- custodian-brief: generated by fix-consistency — do not edit manually -->
# Custodian Brief — rail-kubernetes
**Domain:** financials
**Last synced:** 2026-07-25 16:56 UTC
**State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)*
## Active Workstreams
### Bootstrap rail-kubernetes and import the wave-1 contract
Progress: 1/4 done | workplan_id: `1f2254f5-a873-46fc-a77c-bc725eeaaeb8`
**Open tasks:**
- ! T03 - Prepare the compatibility handoff from `railiance-cluster` `18140d90`
- ► T02 - Import the reviewed generic contract material `148276a2`
- ► T04 - Register `rail-kubernetes` in State Hub and Fabric `d78b5712`
---
## MCP Orientation (when available)
If the state-hub MCP server is reachable, call:
`get_domain_summary("financials")`
This provides richer cross-domain context.
If the MCP call fails, use this file as your orientation source.

7
.gitignore vendored Normal file
View file

@ -0,0 +1,7 @@
.DS_Store
Thumbs.db
__pycache__/
*.pyc
.venv/
venv/
env/

18
.repo-classification.yaml Normal file
View file

@ -0,0 +1,18 @@
repo_classification:
standard: Repo Classification Standard
version: "1.0"
classified_at: "2026-07-25"
classified_by: agent
category: project
domain: financials
secondary_domains:
- infotech
capability_tags:
- platform
- operations
business_stake:
- technology
- operations
business_mechanics:
- coordination
- operation

41
AGENTS.md Normal file
View file

@ -0,0 +1,41 @@
# rail-kubernetes — Agent Instructions
## Repo Identity
**Purpose:** Default Railiance execution rail for generic
workload-on-Kubernetes semantics.
**Domain:** financials
**Repo slug:** rail-kubernetes
**Topic slug:** railiance
**Workplan prefix:** `RAIL-K8S-WP-`
---
## Session Protocol
**Start:**
1. Read `README.md`, `INTENT.md`, and `SCOPE.md`
2. Review active work in `workplans/`
3. Re-read the current rail declaration in `declarations/rail.yaml`
**During work:**
- Keep the boundary with `railiance-cluster` explicit
- Keep workload packaging concerns out of this repo
- Prefer source-controlled declarations over implicit conventions
**Close:**
1. Update workplan files to reflect progress
2. Register or refresh the repo from `.repo-classification.yaml` when needed
3. Run `statehub fix-consistency`
---
## Boundary Reminders
- Do not move substrate bootstrap into this repo
- Do not normalize workload-specific helper commands here
- Do not let `rapp-*` packaging or `reef-*` substrate inventory drift into this repo
The canonical framework boundary lives in
`railiance-master/docs/rail-kubernetes-boundary.md`.

4
CLAUDE.md Normal file
View file

@ -0,0 +1,4 @@
# rail-kubernetes — Claude Code Instructions
@SCOPE.md
@AGENTS.md

55
INTENT.md Normal file
View file

@ -0,0 +1,55 @@
# INTENT
## Why This Repo Exists
`rail-kubernetes` exists so Railiance has a first-class home for the default
execution rail used by platform services and managed workloads.
Before this repo, generic workload lifecycle semantics, workload declarations,
and Kubernetes rollout helpers were mixed into `railiance-cluster`. That was
pragmatic while Railiance had only one runtime path, but it is the wrong steady
state once Railiance starts introducing multiple rails.
This repo establishes the durable answer to:
- how a Railiance-managed workload targets Kubernetes as a rail
- which workload lifecycle semantics are generic to Kubernetes-managed
workloads
- which substrate guarantees the rail expects from S2
- where future packages such as `rapp-openbao` attach when they run on the
default rail
## What This Repo Must Protect
This repo must keep the wave-1 Kubernetes rail narrow and reusable.
That means:
- protect the distinction between rail semantics and cluster ownership
- protect the distinction between rail semantics and workload packaging
- keep the compatibility path from `railiance-cluster` explicit while the
extraction is incomplete
- avoid encoding assumptions that only work for one workload or one substrate
## What This Repo Is Not
This repo is not:
- the ownership home for the Kubernetes substrate
- the ownership home for OpenBao, Forgejo, or any other single workload
- the ownership home for one specific server or cluster substrate
- the place to normalize workload-specific operator commands
## Initial Operating Context
Wave 1 is intentionally centered on Kubernetes.
Railiance should treat this repo as the default rail path until a later
workload presents a strong enough reason to justify a different rail such as
`rail-knative`.
The first version of this repo therefore prioritizes:
- a clean boundary with `railiance-cluster`
- a stable attachment point for future `rapp-*` repos
- compatibility with the currently published S2 substrate profile

View file

@ -1,3 +1,47 @@
# rail-kubernetes
Railiance support for kubernetes workloads
Wave-1 execution rail for Railiance-managed workloads on Kubernetes.
## Purpose
`rail-kubernetes` is the default base rail for the current Railiance
ecosystem. It owns the generic workload-on-Kubernetes execution contract that
should no longer stay mixed into `railiance-cluster`.
This repo exists to hold:
- the Kubernetes rail declaration
- the generic workload lifecycle contract
- the migration path from the current `railiance-cluster` compatibility surface
- future rail-local tooling and docs for Railiance-managed workloads on Kubernetes
## Boundary
`rail-kubernetes` does not own:
- k3s bootstrap
- cluster-level operator ownership
- cluster backup/restore posture
- workload-specific wrappers that belong in `rapp-*` repos
- substrate inventories that belong in `reef-*` repos
Those concerns stay with `railiance-cluster`, the relevant `rapp-*` repo, or
the relevant `reef-*` repo.
## Current Status
This repo is in first-wave bootstrap.
The boundary and source material come from:
- `railiance-master/docs/rail-kubernetes-boundary.md`
- `railiance-cluster/docs/rail-kubernetes-extraction-map.md`
- `railiance-cluster/docs/rail-kubernetes-substrate-profile.md`
## Initial Layout
- `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

118
SCOPE.md Normal file
View file

@ -0,0 +1,118 @@
# SCOPE
> This file helps you quickly understand what this repository is about,
> when it is relevant, and when it is not.
> It is intentionally lightweight and may be incomplete.
---
## One-liner
Default Railiance execution rail for generic workload-on-Kubernetes semantics.
---
## Core Idea
`rail-kubernetes` is the first concrete `rail-*` repo in Railiance.
It exists to hold the generic execution contract for Railiance-managed
workloads that run on Kubernetes, without keeping those semantics mixed into
the S2 ownership repo.
---
## In Scope
- Kubernetes rail declaration and compatibility notes
- Generic workload lifecycle semantics for Kubernetes-managed workloads
- Generic workload contract surface such as `railiance/app.toml`
- Generic rollout, observe, promote, and rollback semantics for the rail
- Migration compatibility guidance while the extraction from `railiance-cluster`
is still in progress
---
## Out of Scope
- k3s bootstrap and cluster installation
- cluster-level ingress, operator, and certificate ownership
- cluster backup, restore, and preflight safety rails
- workload-specific wrappers and app packaging
- substrate inventories and machine grouping
---
## Relevant When
- Defining how a workload targets the default Kubernetes rail
- Moving generic workload lifecycle material out of `railiance-cluster`
- Attaching a `rapp-*` package to its Kubernetes execution contract
- Clarifying which S2 substrate facts the rail may depend on
---
## Not Relevant When
- The work is purely cluster substrate ownership
- The work is workload-specific packaging
- The work is substrate inventory or server grouping
---
## Current State
- Status: active bootstrap
- Implementation: baseline repo, declaration, and first repo-local workplan are present
- Stability: evolving
- Usage: wave-1 home for the default Kubernetes rail
---
## How It Fits
- Upstream dependencies: `railiance-cluster`, `railiance-master`
- Downstream consumers: future `rapp-*` repos and reefs that host the Kubernetes rail
- Often used with: `railiance-fabric`, `railiance-platform`, `railiance-infra`
---
## Terminology
- Preferred terms: rail, execution contract, workload lifecycle, substrate profile
- Also known as: default base rail
- Potentially confusing terms: this repo defines rail semantics; it does not own the Kubernetes substrate itself
---
## Related / Overlapping Repositories
- `railiance-cluster` — owns the Kubernetes substrate; `rail-kubernetes` owns the generic workload-on-Kubernetes contract
- `railiance-master` — defines the framework architecture and boundary
- future `rapp-*` repos — own workload packaging and scaffolding on top of this rail
- future `reef-*` repos — own the substrate boundaries that host this rail
---
## Getting Oriented
- Start with: `README.md`, `INTENT.md`
- Key files / directories: `declarations/`, `docs/`, `workplans/`
- Entry points: `declarations/rail.yaml`, `docs/wave-1-contract.md`
---
## Provided Capabilities
```capability
type: governance
title: Kubernetes execution rail contract
description: Defines the generic execution contract for Railiance-managed workloads that run on Kubernetes.
keywords: [railiance, rail, kubernetes, workload, execution]
```
---
## Notes
Keep this repo generic. If a behavior is specific to one workload or one
substrate, it belongs somewhere else.

15
WORK-RECORDS.md Normal file
View file

@ -0,0 +1,15 @@
# Work Records — rail-kubernetes
> Generated by `statehub fix-consistency` (CUST-WP-0061-T04, work-record
> stage 3). Do not edit by hand — edit the source file/block listed for
> each record and re-run fix-consistency to refresh this index. Archived
> workplans are omitted; closed decisions/intakes/engagements stay listed
> so recently-resolved work is still visible. [auto]
| Kind | ID | Status | Lane | Source |
| --- | --- | --- | --- | --- |
| workplan | RAIL-K8S-WP-0001 | active | — | 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-T03 | 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 |

26
declarations/rail.yaml Normal file
View file

@ -0,0 +1,26 @@
kind: execution-rail
rail_id: rail-kubernetes
repo: rail-kubernetes
ownership_repo: railiance-cluster
execution_architecture: kubernetes
status: wave-1-bootstrap
supported_rollout_modes:
- stage1-run
- stage2-isolated-canary
- stage3-promote-rollback
substrate_prerequisites:
- kubernetes-api
- helm-apply-path
- operator-driven-kubeconfig-access
- namespace-creation-supported
compatibility_notes:
- Weighted routing is optional and must not be assumed in wave 1.
- Metrics availability is opportunistic and not a rollout success criterion.
- Storage guarantees remain explicit-review territory until S2 publishes them.
source_documents:
- repo: railiance-master
path: docs/rail-kubernetes-boundary.md
- repo: railiance-cluster
path: docs/rail-kubernetes-extraction-map.md
- repo: railiance-cluster
path: docs/rail-kubernetes-substrate-profile.md

11
docs/README.md Normal file
View file

@ -0,0 +1,11 @@
# rail-kubernetes Docs
- `app-toml-contract.md` — canonical workload declaration contract for the rail
- `overlay-repo-pattern.md` — compatibility-era overlay pattern on the path to `rapp-*`
- `canary-helm-template.md` — stage-aware canary/stable chart pattern
- `deployment-lifecycle.md` — canonical stage 1 / 2 / 3 lifecycle contract
- `stage2-deploy-observe.md` — stage 2 command behavior contract
- `promote-rollback-onboarding.md` — representative lifecycle command path
- `railiance-run-command.md` — stage 1 command behavior 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,55 @@
# Canary Helm Template
Generated Railiance overlays include a stage-aware Helm chart for Stage 2
canaries and Stage 3 stable promotion.
The chart keeps stable and canary release identities explicit:
- `railiance.stableRelease` names the current stable release;
- `railiance.canaryRelease` names the Stage 2 candidate release;
- `railiance.stage` selects the rendered identity, labels, and selectors;
- `railiance.previousStable` records rollback context before promotion.
## Traffic Shape
The default Stage 2 values use an isolated canary ingress:
```yaml
railiance:
stage: canary
traffic:
mode: isolated
ingress:
enabled: true
```
This creates canary Deployment, Service, and Ingress resources without changing
the stable release. For environments that use Traefik weighted routing, set:
```yaml
railiance:
traffic:
mode: weighted
provider: traefik
stableWeight: 95
canaryWeight: 5
```
The chart then renders a `TraefikService` and `IngressRoute` that split traffic
between the stable and canary services. Other ingress controllers can use the
same stable/canary values layout with controller-specific annotations or a later
provider template.
## Observability And Safety
Generated workloads include:
- Prometheus-compatible scrape annotations on pods and services;
- readiness and liveness HTTP probes;
- conservative resource requests/limits for single-node clusters;
- separate `values/stage2-canary.yaml` and `values/stage3-production.yaml` so
canary exposure and stable promotion can be reviewed independently.
Run `tests/stage2-template.sh` in the overlay repo before any Stage 2 attempt.
It verifies the scaffold and runs `helm template` when Helm is available.

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

@ -0,0 +1,179 @@
# Railiance Overlay Repo Pattern
A Railiance overlay repo wraps a third-party upstream application without
forking Railiance deployment logic into the upstream source repository.
The overlay repo owns the Railiance deployment contract, promotion evidence,
Helm/Kubernetes overlay, values, probes, and runbooks. The upstream repository
remains the source of application code and release artifacts.
This pattern is a compatibility-era scaffolding model inside the
`rail-kubernetes` migration. Long-term first-class package ownership should
move toward `rapp-*` repos. Until the `rapp-*` path is fully materialized, the
overlay structure remains a useful generic pattern for Kubernetes-managed
workloads.
## Goals
- Keep upstream code and Railiance deployment mechanics separate.
- Make Stage 1, Stage 2, Stage 3, and rollback behavior reproducible from Git.
- Declare all platform dependencies, health checks, and secret references in
`railiance/app.toml` without plaintext secret values.
- Allow third-party applications to adopt the staged promotion lifecycle without
requiring changes to their upstream repositories.
## Repo Layout
A generated compatibility overlay repo should look like this:
```text
<app>-railiance-overlay/
README.md
railiance/
app.toml
upstream.toml
charts/
<app>/
Chart.yaml
values.yaml
templates/
deployment.yaml
service.yaml
values/
stage1.yaml
stage2-canary.yaml
stage3-production.yaml
patches/
upstream/.gitkeep
tests/
stage1.sh
runbooks/
rollback.md
docs/
promotion.md
```
When a workload graduates into a durable package repo, the preferred target is
to keep the same contract surfaces while moving the repo identity toward
`rapp-<workload>`.
## Ownership Boundary
The overlay repo owns:
- `railiance/app.toml` staged promotion declaration;
- Railiance-specific Helm chart, values, probes, and runbooks;
- canary and promotion evidence expectations;
- secret references by approved route and target object name;
- compatibility notes for a specific upstream revision or release line.
The upstream repo owns:
- application source code;
- upstream build and release artifacts;
- upstream tests and release notes;
- upstream vulnerability and license notices.
The overlay must not vendor upstream source by default. If a patch is required,
store the patch under `patches/upstream/` and record why the patch exists, when
it can be retired, and which upstream issue or release should replace it.
## Required Files
### `railiance/app.toml`
This file follows `docs/app-toml-contract.md` and
`schemas/railiance-app.schema.json` from `rail-kubernetes`. It is the primary
machine-readable contract for promotion tooling.
### `railiance/upstream.toml`
This file records non-secret upstream identity:
```toml
[upstream]
url = "https://example.com/vendor/app.git"
revision = "v1.2.3"
tracking = "tag"
license = "see-upstream"
notes = "Railiance overlay only; upstream code is not vendored here."
```
`revision` should be immutable where possible: tag, commit SHA, release id, or
image digest. Mutable branches are acceptable only before a workload becomes a
Stage 2 candidate.
### `charts/<app>/`
The chart is the Railiance deployment wrapper. It may start as a thin Helm
chart around an upstream image and grow only as required by the promotion gates.
Generated charts include stable/canary release identities, Prometheus-compatible
annotations, HTTP probes, resource limits, isolated canary ingress, and optional
Traefik weighted routing. Production-specific choices stay in `values/` files.
### `values/`
Stage values separate local validation, canary, and production settings:
- `stage1.yaml`: local or dry-run defaults;
- `stage2-canary.yaml`: limited exposure canary defaults;
- `stage3-production.yaml`: stable production defaults.
Secret values do not belong in these files. Use Kubernetes Secret,
ExternalSecret, OpenBao, KeyCape, or another approved route and record only the
reference name.
### `tests/stage1.sh` And `tests/stage2-template.sh`
Stage 1 should be runnable without production credentials. The generated script
performs syntax and Helm rendering checks when the relevant tools are available.
Stage 2 template validation verifies the canary scaffold, stable/canary values,
Prometheus annotations, rollback labels, and Helm rendering when Helm is
available. Workload-specific tests can extend either script.
### `runbooks/rollback.md`
Rollback instructions must exist before Stage 2. Early overlays may include a
placeholder, but it must name the intended rollback target and verification
check.
## Creation Tool
During the migration window, the compatibility scaffold still comes from:
```bash
railiance-cluster/tools/create_railiance_overlay_repo.sh \
--app-id forgejo \
--name "Forgejo" \
--owner railiance-forge \
--criticality critical \
--upstream-url https://codeberg.org/forgejo/forgejo.git \
--upstream-revision v12.0.0 \
--out-dir /tmp/forgejo-railiance-overlay
```
The tool writes only local files. It does not call Gitea, clone upstream code,
fetch secrets, or push Git remotes.
Long-term, the generic scaffolding behavior should move under the rail and/or
become `rapp-*`-aware rather than preserving the overlay name forever.
## Promotion Use
1. Generate or update the overlay repo.
2. Fill in accurate image, namespace, health, dependency, and rollback fields.
3. Validate `railiance/app.toml` against the schema.
4. Run `tests/stage1.sh` and `tests/stage2-template.sh`.
5. Use later Stage 2 and Stage 3 commands to deploy, observe, promote, and
rollback.
## Safety Rules
- No plaintext secrets in `railiance/app.toml`, values files, tests, runbooks,
or generated evidence.
- Do not hide deployment logic in upstream source patches.
- Do not promote mutable upstream branches to Stage 2/3 without an explicit
operator exception.
- Production-critical overlays require human approval before canary exposure and
production promotion.

View file

@ -0,0 +1,71 @@
# Promote, Rollback, And Onboarding
This guide shows the representative Railiance lifecycle for an overlay repo.
Commands default to plan mode so the path is repeatable before cluster access or
operator approval exists.
## Stage 1
```bash
bin/railiance run /path/to/overlay --pretty
```
Stage 1 validates `railiance/app.toml`, local commands, and local checks. Save
the JSON result as non-secret evidence before Stage 2.
## Stage 2
```bash
bin/railiance deploy --stage 2 /path/to/overlay --plan --pretty
bin/railiance observe --stage 2 /path/to/overlay --plan --pretty
```
When Helm, kubectl, cluster access, and approval evidence are ready:
```bash
bin/railiance deploy --stage 2 /path/to/overlay --apply --approval-id <state-hub-id>
bin/railiance observe --stage 2 /path/to/overlay --live --pretty
```
For critical workloads, Stage 2 apply must not run until the operator has
approved canary exposure and rollback context is known.
## Stage 3
```bash
bin/railiance promote /path/to/overlay --plan --pretty
bin/railiance rollback /path/to/overlay --plan --pretty
```
Promotion plan mode emits a `railiance.stage3-promote-result.v1` JSON result
with stable release identity, chart and values paths, previous-stable target,
expected evidence, and approval requirements.
Rollback plan mode emits a `railiance.stage3-rollback-result.v1` JSON result
with rollback strategy, release identity, verification text, and apply-time
requirements.
When approval evidence and Helm access are ready:
```bash
bin/railiance promote /path/to/overlay --apply --approval-id <state-hub-id>
bin/railiance rollback /path/to/overlay --apply --approval-id <state-hub-id> --revision <helm-revision>
```
Stage 3 apply fails closed if the chart or values are missing, previous stable
is not recorded, Helm is unavailable, or approval evidence is missing. Rollback
apply fails closed if the rollback strategy is missing, Helm is unavailable,
approval evidence is missing, or a Helm revision is required but absent.
## Human Approval Points
Critical infrastructure workloads require explicit operator approval before:
- Stage 2 canary exposure;
- Stage 3 stable promotion;
- rollback apply, unless an incident runbook defines a narrower break-glass
process and records the evidence id.
Progress notes should include only non-secret result summaries: schema version,
status, release, namespace, approval id, check counts, and command byte counts.
Do not paste command logs, kubeconfigs, tokens, or private service output.

View file

@ -0,0 +1,52 @@
# Railiance Run Command
`bin/railiance run` executes Stage 1 local validation for a repository that
contains `railiance/app.toml`.
The command is intentionally local and conservative:
- reads `railiance/app.toml` using the `railiance.app.v1` contract;
- runs `[stages.stage1].commands` from the app directory;
- evaluates Stage 1 check ids listed in `[stages.stage1].checks` when they can
be checked locally;
- emits a machine-readable `railiance.run-result.v1` JSON result;
- records command references, exit codes, durations, and output byte counts,
but not shell text or command stdout/stderr content;
- strips credentials, query strings, and fragments from URLs before reporting HTTP
check results.
## Usage
```bash
bin/railiance run /path/to/app-or-overlay --pretty
bin/railiance run . --json-out .railiance/stage1-result.json
```
The process exits `0` only when all Stage 1 commands and required checks pass.
Optional checks may be skipped without failing the run. For example, an optional
local health endpoint can be declared before a local server command exists.
## Supported Local Checks
- `command`: runs the check `run` command in the app directory.
- `http`: calls the declared URL and compares the HTTP status.
- `helm`: runs `helm template` when Helm is installed. Required Helm checks fail
if Helm is unavailable; optional Helm checks are skipped.
Other check types are reported as skipped or failed depending on whether the
check is required. Stage 2 and Stage 3 checks are never executed by
`railiance run`.
## Result Shape
The JSON result includes:
- app identity and source revision;
- contract path and app directory;
- command/check status summaries using contract references instead of raw shell
commands;
- expected evidence labels from Stage 1;
- timing and exit status metadata.
The result is suitable for later promotion gates and State Hub progress notes,
without embedding secrets or verbose logs.

View file

@ -0,0 +1,45 @@
# Source Import Plan
## Purpose
Record which reviewed source materials are expected to seed this repo.
## Imported In The Current Wave
Imported source documents from `railiance-cluster`:
- `docs/deployment-lifecycle.md`
- `docs/app-toml-contract.md`
- `docs/overlay-repo-pattern.md`
- `docs/canary-helm-template.md`
- `docs/stage2-deploy-observe.md`
- `docs/promote-rollback-onboarding.md`
- `docs/railiance-run-command.md`
Imported source assets from `railiance-cluster`:
- `schemas/railiance-app.schema.json`
- `examples/railiance/app.toml`
## Pending First-Wave Imports
Expected source assets from `railiance-cluster`:
- `tools/create_railiance_overlay_repo.sh`
- `tools/cmd/railiance-run`
- `tools/cmd/railiance-stage2`
- `tools/cmd/railiance-stage3`
## Deferred Migration Debt
Do not import these into `rail-kubernetes` as part of wave 1:
- workload-specific activity-core helper commands
- workload-specific smoke or reconciliation helpers
- cluster substrate operations
- shared bootstrap helpers that still need a separate placement review
## Rule
Imports into this repo should preserve the boundary already reviewed in
`railiance-master` and `railiance-cluster`.

View file

@ -0,0 +1,49 @@
# Stage 2 Deploy And Observe
`bin/railiance deploy --stage 2` and `bin/railiance observe --stage 2` provide
the repeatable command path for production canaries declared in
`railiance/app.toml`.
Both commands default to non-mutating plan mode.
## Deploy
```bash
bin/railiance deploy --stage 2 /path/to/overlay --pretty
bin/railiance deploy --stage 2 /path/to/overlay --server-dry-run --pretty
bin/railiance deploy --stage 2 /path/to/overlay --apply --approval-id <state-hub-id>
```
Plan mode validates the local Stage 2 chart and values paths and emits a
`railiance.stage2-deploy-result.v1` JSON plan. It does not contact the cluster.
`--server-dry-run` runs `helm upgrade --install --dry-run=server` when Helm and
cluster access are available. `--apply` runs the Helm canary apply path with
`--atomic --wait`. If Stage 2 declares `requires_approval = true`, apply mode
fails closed unless `--approval-id` is provided.
The result records release identity, namespace, chart path, values path,
expected checks/evidence, precheck status, and command byte counts. It does not
embed Helm or kubectl logs.
## Observe
```bash
bin/railiance observe --stage 2 /path/to/overlay --pretty
bin/railiance observe --stage 2 /path/to/overlay --live --pretty
```
Plan mode emits the rollout, pod selector, ingress selector, health URL, and
metrics targets that live observation will query.
Live mode uses `kubectl` to check rollout status, deployment JSON, canary pods,
ingress/routing resources, and pod metrics when metrics-server is available.
Metrics unavailability is reported separately so a canary can fail for rollout
or readiness problems without hiding missing observability.
## Safety
Stage 2 remains blocked when required local paths are missing, Helm is missing
for dry-run/apply, `kubectl` is missing for live observe, or approval evidence
is missing for an apply that requires approval. Use the emitted JSON as
non-secret evidence in State Hub progress notes.

59
docs/wave-1-contract.md Normal file
View file

@ -0,0 +1,59 @@
# Wave-1 Kubernetes Rail Contract
## Purpose
Summarize the initial contract `rail-kubernetes` owns in wave 1.
This repo starts from already-reviewed source documents rather than inventing a
new runtime model:
- `railiance-master/docs/rail-kubernetes-boundary.md`
- `railiance-cluster/docs/rail-kubernetes-extraction-map.md`
- `railiance-cluster/docs/rail-kubernetes-substrate-profile.md`
## What `rail-kubernetes` Owns
Wave 1 assigns these concerns to the rail:
- the generic workload-on-Kubernetes execution contract
- the generic `railiance/app.toml` workload declaration surface
- generic stage 1 / stage 2 / stage 3 lifecycle semantics
- generic canary, observe, promote, and rollback behavior
- generic wrapper and overlay guidance for workloads that still use the current
overlay pattern during migration
## What Stays In `railiance-cluster`
Wave 1 keeps these concerns in the S2 ownership repo:
- k3s bootstrap and host-to-cluster installation
- cluster backup, restore, smoke, and preflight operations
- operator ownership for ingress, TLS, and other cluster-level components
- cluster access delivery and kubeconfig handling
- substrate-level safety rails
## Published Substrate Assumptions
Wave 1 `rail-kubernetes` may assume:
- a Kubernetes API backed by k3s
- Helm-based apply paths
- operator-driven kubeconfig access
- isolated canary exposure as the safe default
- namespace creation during workload rollout
Wave 1 `rail-kubernetes` must not assume:
- guaranteed weighted routing
- a generic workload RBAC model
- a generic persistent-storage contract
- a secret-delivery contract at the S2 layer
## Migration Constraint
Until the extraction is complete, `railiance-cluster` remains the compatibility
surface for current operators.
`rail-kubernetes` should become the durable home of the contract first. Only
then should commands, docs, schemas, and helpers move here in a migration-safe
sequence.

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

@ -0,0 +1,129 @@
---
id: RAIL-K8S-WP-0001
type: workplan
title: "Bootstrap rail-kubernetes and import the wave-1 contract"
domain: financials
repo: rail-kubernetes
status: active
owner: codex
topic_slug: railiance
created: "2026-07-25"
updated: "2026-07-25"
state_hub_workstream_id: "1f2254f5-a873-46fc-a77c-bc725eeaaeb8"
---
# RAIL-K8S-WP-0001 - Bootstrap rail-kubernetes and import the wave-1 contract
## Goal
Turn `rail-kubernetes` into the concrete home of the default Railiance
execution rail.
## Boundaries
This workplan may bootstrap the repo, publish the initial declaration, and
import the generic wave-1 contract.
It must not:
- absorb S2 substrate ownership from `railiance-cluster`
- normalize workload-specific helper commands into the rail
- pretend the migration is complete before compatibility shims exist
## T01 - Establish the repo baseline and rail declaration
```task
id: RAIL-K8S-WP-0001-T01
status: done
priority: high
state_hub_task_id: "9bab36db-e094-4e41-af76-b6bf5e73be78"
```
Create the baseline repo files, the initial rail declaration, and the first
wave-1 contract summary.
Acceptance:
- baseline repo files exist
- `declarations/rail.yaml` exists
- the repo states its boundary with `railiance-cluster`
2026-07-25: Created the initial repo baseline, declaration, and wave-1
contract summary.
## T02 - Import the reviewed generic contract material
```task
id: RAIL-K8S-WP-0001-T02
status: progress
priority: high
state_hub_task_id: "148276a2-2e40-4d56-89dd-2930c5e4309c"
```
Bring the reviewed generic lifecycle docs, schema, examples, and helper
surfaces into this repo in a migration-safe sequence.
Acceptance:
- the import sequence is anchored in the reviewed extraction map
- imported material stays generic to the Kubernetes rail
- the repo becomes a real home for the default rail rather than only a shell
2026-07-25: Established the repo-local contract summary and explicit source
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.
2026-07-25: Imported the remaining generic supporting docs from
`railiance-cluster`: `docs/overlay-repo-pattern.md`,
`docs/canary-helm-template.md`, `docs/stage2-deploy-observe.md`,
`docs/promote-rollback-onboarding.md`, and
`docs/railiance-run-command.md`. Helper scripts and command implementations
remain pending.
## T03 - Prepare the compatibility handoff from `railiance-cluster`
```task
id: RAIL-K8S-WP-0001-T03
status: wait
priority: medium
state_hub_task_id: "18140d90-d10e-47fe-b7da-d454550466b1"
```
Define the thin compatibility path that keeps current operators functional
while the extraction proceeds.
Acceptance:
- the compatibility path is explicit
- current operators do not need a flag day
- the retained S2 surface stays narrow and temporary
## T04 - Register `rail-kubernetes` in State Hub and Fabric
```task
id: RAIL-K8S-WP-0001-T04
status: progress
priority: medium
state_hub_task_id: "d78b5712-f305-4967-9034-9b9471633755"
```
Register the repo from classification and add it to the Fabric repository
registry with rail-family metadata.
Acceptance:
- State Hub recognizes the repo and its workplan
- Fabric recognizes the repo as `repo_family: rail`
- ownership and primary contract metadata are visible
2026-07-25: Registered `rail-kubernetes` in State Hub and generated
`WORK-RECORDS.md` plus `.custodian-brief.md`. Added `rail-kubernetes` to
`railiance-fabric/registry/railiance-repos.yaml` as `repo_family: rail` with
`ownership_repo: railiance-cluster`. Live Fabric registry sync and Forgejo
remote provisioning remain pending.

View file

@ -0,0 +1 @@