From 0e320c5632875df8a59337d9cc5893767f3e873b Mon Sep 17 00:00:00 2001 From: custodian-sync Date: Sat, 25 Jul 2026 18:56:09 +0200 Subject: [PATCH 1/6] chore(consistency): sync task status from DB [auto] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updated by fix-consistency on 2026-07-25: - RAIL-K8S-WP-0001-T04: progress → wait --- ...-K8S-WP-0001-bootstrap-and-wave1-import.md | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 workplans/RAIL-K8S-WP-0001-bootstrap-and-wave1-import.md diff --git a/workplans/RAIL-K8S-WP-0001-bootstrap-and-wave1-import.md b/workplans/RAIL-K8S-WP-0001-bootstrap-and-wave1-import.md new file mode 100644 index 0000000..ab352e1 --- /dev/null +++ b/workplans/RAIL-K8S-WP-0001-bootstrap-and-wave1-import.md @@ -0,0 +1,116 @@ +--- +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. + +## 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: wait +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. From 90c89a4d7dc426ac1af0a880d2fa291fb30522f7 Mon Sep 17 00:00:00 2001 From: custodian-sync Date: Sat, 25 Jul 2026 18:56:51 +0200 Subject: [PATCH 2/6] chore(consistency): sync task status from DB [auto] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updated by fix-consistency on 2026-07-25: - RAIL-K8S-WP-0001-T04: wait → progress --- workplans/RAIL-K8S-WP-0001-bootstrap-and-wave1-import.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workplans/RAIL-K8S-WP-0001-bootstrap-and-wave1-import.md b/workplans/RAIL-K8S-WP-0001-bootstrap-and-wave1-import.md index ab352e1..c401528 100644 --- a/workplans/RAIL-K8S-WP-0001-bootstrap-and-wave1-import.md +++ b/workplans/RAIL-K8S-WP-0001-bootstrap-and-wave1-import.md @@ -95,7 +95,7 @@ Acceptance: ```task id: RAIL-K8S-WP-0001-T04 -status: wait +status: progress priority: medium state_hub_task_id: "d78b5712-f305-4967-9034-9b9471633755" ``` From e6867e5ca9160243999d60e584835a969d14aca8 Mon Sep 17 00:00:00 2001 From: custodian-sync Date: Sat, 25 Jul 2026 18:56:52 +0200 Subject: [PATCH 3/6] chore(consistency): sync task status from DB [auto] Updated by fix-consistency on 2026-07-25: - update .custodian-brief.md for rail-kubernetes --- .custodian-brief.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .custodian-brief.md diff --git a/.custodian-brief.md b/.custodian-brief.md new file mode 100644 index 0000000..319d8f6 --- /dev/null +++ b/.custodian-brief.md @@ -0,0 +1,24 @@ + +# 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. From 513407acaa7404a7c10a79013dea3761d693bcb3 Mon Sep 17 00:00:00 2001 From: codex Date: Sat, 25 Jul 2026 18:57:55 +0200 Subject: [PATCH 4/6] Bootstrap the first rail-kubernetes repo --- .gitignore | 7 +++ .repo-classification.yaml | 18 ++++++ AGENTS.md | 41 +++++++++++++ CLAUDE.md | 4 ++ INTENT.md | 55 +++++++++++++++++ README.md | 45 ++++++++++++++ SCOPE.md | 118 ++++++++++++++++++++++++++++++++++++ WORK-RECORDS.md | 15 +++++ declarations/rail.yaml | 26 ++++++++ docs/README.md | 4 ++ docs/source-import-plan.md | 40 ++++++++++++ docs/wave-1-contract.md | 59 ++++++++++++++++++ workplans/archived/.gitkeep | 1 + 13 files changed, 433 insertions(+) create mode 100644 .gitignore create mode 100644 .repo-classification.yaml create mode 100644 AGENTS.md create mode 100644 CLAUDE.md create mode 100644 INTENT.md create mode 100644 README.md create mode 100644 SCOPE.md create mode 100644 WORK-RECORDS.md create mode 100644 declarations/rail.yaml create mode 100644 docs/README.md create mode 100644 docs/source-import-plan.md create mode 100644 docs/wave-1-contract.md create mode 100644 workplans/archived/.gitkeep diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..52b0764 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +Thumbs.db +__pycache__/ +*.pyc +.venv/ +venv/ +env/ diff --git a/.repo-classification.yaml b/.repo-classification.yaml new file mode 100644 index 0000000..0fb3609 --- /dev/null +++ b/.repo-classification.yaml @@ -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 diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..b1d50d5 --- /dev/null +++ b/AGENTS.md @@ -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`. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..48a886b --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,4 @@ +# rail-kubernetes — Claude Code Instructions + +@SCOPE.md +@AGENTS.md diff --git a/INTENT.md b/INTENT.md new file mode 100644 index 0000000..d8a36d7 --- /dev/null +++ b/INTENT.md @@ -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 diff --git a/README.md b/README.md new file mode 100644 index 0000000..296cda8 --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ +# rail-kubernetes + +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 +- `workplans/` — repo-local execution work diff --git a/SCOPE.md b/SCOPE.md new file mode 100644 index 0000000..89f87ec --- /dev/null +++ b/SCOPE.md @@ -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. diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md new file mode 100644 index 0000000..a266b74 --- /dev/null +++ b/WORK-RECORDS.md @@ -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 | wait | — | workplans/RAIL-K8S-WP-0001-bootstrap-and-wave1-import.md | diff --git a/declarations/rail.yaml b/declarations/rail.yaml new file mode 100644 index 0000000..7af8ac2 --- /dev/null +++ b/declarations/rail.yaml @@ -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 diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..6534413 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,4 @@ +# rail-kubernetes Docs + +- `wave-1-contract.md` — current rail contract and boundary summary +- `source-import-plan.md` — source material and migration direction from existing repos diff --git a/docs/source-import-plan.md b/docs/source-import-plan.md new file mode 100644 index 0000000..8a6cf7b --- /dev/null +++ b/docs/source-import-plan.md @@ -0,0 +1,40 @@ +# Source Import Plan + +## Purpose + +Record which reviewed source materials are expected to seed this repo. + +## First-Wave Imports + +Expected 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` + +Expected source assets from `railiance-cluster`: + +- `schemas/railiance-app.schema.json` +- `examples/railiance/app.toml` +- `tools/create_railiance_overlay_repo.sh` +- `tools/cmd/railiance-run` +- `tools/cmd/railiance-stage2` +- `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`. diff --git a/docs/wave-1-contract.md b/docs/wave-1-contract.md new file mode 100644 index 0000000..f7fd29e --- /dev/null +++ b/docs/wave-1-contract.md @@ -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. diff --git a/workplans/archived/.gitkeep b/workplans/archived/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/workplans/archived/.gitkeep @@ -0,0 +1 @@ + From b48eaea942a363c1b2c99df1ad804bc924e24569 Mon Sep 17 00:00:00 2001 From: codex Date: Sat, 25 Jul 2026 19:28:32 +0200 Subject: [PATCH 5/6] Import core rail-kubernetes contract artifacts --- README.md | 2 + WORK-RECORDS.md | 2 +- docs/README.md | 2 + docs/app-toml-contract.md | 240 +++++++ docs/deployment-lifecycle.md | 340 ++++++++++ docs/source-import-plan.md | 16 +- examples/railiance/app.toml | 176 ++++++ schemas/railiance-app.schema.json | 596 ++++++++++++++++++ ...-K8S-WP-0001-bootstrap-and-wave1-import.md | 6 + 9 files changed, 1375 insertions(+), 5 deletions(-) create mode 100644 docs/app-toml-contract.md create mode 100644 docs/deployment-lifecycle.md create mode 100644 examples/railiance/app.toml create mode 100644 schemas/railiance-app.schema.json diff --git a/README.md b/README.md index 296cda8..0a331f1 100644 --- a/README.md +++ b/README.md @@ -42,4 +42,6 @@ The boundary and source material come from: - `declarations/rail.yaml` — source-controlled rail contract - `docs/` — wave-1 contract and import plan +- `schemas/` — machine-readable workload contract schema +- `examples/` — canonical example workload declarations for the rail - `workplans/` — repo-local execution work diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index a266b74..98e3aed 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -12,4 +12,4 @@ | task | RAIL-K8S-WP-0001-T01 | done | — | workplans/RAIL-K8S-WP-0001-bootstrap-and-wave1-import.md | | task | RAIL-K8S-WP-0001-T02 | progress | — | workplans/RAIL-K8S-WP-0001-bootstrap-and-wave1-import.md | | task | RAIL-K8S-WP-0001-T03 | wait | — | workplans/RAIL-K8S-WP-0001-bootstrap-and-wave1-import.md | -| task | RAIL-K8S-WP-0001-T04 | wait | — | workplans/RAIL-K8S-WP-0001-bootstrap-and-wave1-import.md | +| task | RAIL-K8S-WP-0001-T04 | progress | — | workplans/RAIL-K8S-WP-0001-bootstrap-and-wave1-import.md | diff --git a/docs/README.md b/docs/README.md index 6534413..1c50899 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,4 +1,6 @@ # rail-kubernetes Docs +- `app-toml-contract.md` — canonical workload declaration contract for the rail +- `deployment-lifecycle.md` — canonical stage 1 / 2 / 3 lifecycle contract - `wave-1-contract.md` — current rail contract and boundary summary - `source-import-plan.md` — source material and migration direction from existing repos diff --git a/docs/app-toml-contract.md b/docs/app-toml-contract.md new file mode 100644 index 0000000..3088047 --- /dev/null +++ b/docs/app-toml-contract.md @@ -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 `. +- Stage 2 commands are consumed by `bin/railiance deploy --stage 2 ` + and `bin/railiance observe --stage 2 `. +- Stage 3 commands are consumed by `bin/railiance promote ` and + `bin/railiance rollback `. + +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. diff --git a/docs/deployment-lifecycle.md b/docs/deployment-lifecycle.md new file mode 100644 index 0000000..58f2939 --- /dev/null +++ b/docs/deployment-lifecycle.md @@ -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 # Stage 1 local validation +bin/railiance deploy --stage 2 --plan # Stage 2 canary plan +bin/railiance observe --stage 2 --plan # Stage 2 evidence targets +bin/railiance promote --plan # Stage 3 production promotion +bin/railiance rollback --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. diff --git a/docs/source-import-plan.md b/docs/source-import-plan.md index 8a6cf7b..591047f 100644 --- a/docs/source-import-plan.md +++ b/docs/source-import-plan.md @@ -4,12 +4,22 @@ Record which reviewed source materials are expected to seed this repo. -## First-Wave Imports +## Imported In The Current Wave -Expected source documents from `railiance-cluster`: +Imported source documents from `railiance-cluster`: - `docs/deployment-lifecycle.md` - `docs/app-toml-contract.md` + +Imported source assets from `railiance-cluster`: + +- `schemas/railiance-app.schema.json` +- `examples/railiance/app.toml` + +## Pending First-Wave Imports + +Expected source documents from `railiance-cluster`: + - `docs/overlay-repo-pattern.md` - `docs/canary-helm-template.md` - `docs/stage2-deploy-observe.md` @@ -18,8 +28,6 @@ Expected source documents from `railiance-cluster`: Expected source assets from `railiance-cluster`: -- `schemas/railiance-app.schema.json` -- `examples/railiance/app.toml` - `tools/create_railiance_overlay_repo.sh` - `tools/cmd/railiance-run` - `tools/cmd/railiance-stage2` diff --git a/examples/railiance/app.toml b/examples/railiance/app.toml new file mode 100644 index 0000000..d97f37b --- /dev/null +++ b/examples/railiance/app.toml @@ -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." diff --git a/schemas/railiance-app.schema.json b/schemas/railiance-app.schema.json new file mode 100644 index 0000000..2ff92a5 --- /dev/null +++ b/schemas/railiance-app.schema.json @@ -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 + } + } + } + } +} diff --git a/workplans/RAIL-K8S-WP-0001-bootstrap-and-wave1-import.md b/workplans/RAIL-K8S-WP-0001-bootstrap-and-wave1-import.md index c401528..2c4237a 100644 --- a/workplans/RAIL-K8S-WP-0001-bootstrap-and-wave1-import.md +++ b/workplans/RAIL-K8S-WP-0001-bootstrap-and-wave1-import.md @@ -73,6 +73,12 @@ Acceptance: import plan. Full import of docs, schema, examples, and helper surfaces is still pending. +2026-07-25: Imported the first core generic contract artifacts from +`railiance-cluster`: `docs/deployment-lifecycle.md`, +`docs/app-toml-contract.md`, `schemas/railiance-app.schema.json`, and +`examples/railiance/app.toml`. Helper command surfaces and the remaining +supporting docs are still pending. + ## T03 - Prepare the compatibility handoff from `railiance-cluster` ```task From 433949ab5714843ef210cfae02b707585c5b86e5 Mon Sep 17 00:00:00 2001 From: codex Date: Sat, 25 Jul 2026 19:39:40 +0200 Subject: [PATCH 6/6] Import supporting rail-kubernetes docs --- docs/README.md | 5 + docs/canary-helm-template.md | 55 ++++++ docs/overlay-repo-pattern.md | 179 ++++++++++++++++++ docs/promote-rollback-onboarding.md | 71 +++++++ docs/railiance-run-command.md | 52 +++++ docs/source-import-plan.md | 13 +- docs/stage2-deploy-observe.md | 49 +++++ ...-K8S-WP-0001-bootstrap-and-wave1-import.md | 7 + 8 files changed, 423 insertions(+), 8 deletions(-) create mode 100644 docs/canary-helm-template.md create mode 100644 docs/overlay-repo-pattern.md create mode 100644 docs/promote-rollback-onboarding.md create mode 100644 docs/railiance-run-command.md create mode 100644 docs/stage2-deploy-observe.md diff --git a/docs/README.md b/docs/README.md index 1c50899..eaff010 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +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 diff --git a/docs/canary-helm-template.md b/docs/canary-helm-template.md new file mode 100644 index 0000000..54d4ce1 --- /dev/null +++ b/docs/canary-helm-template.md @@ -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. diff --git a/docs/overlay-repo-pattern.md b/docs/overlay-repo-pattern.md new file mode 100644 index 0000000..f3cc109 --- /dev/null +++ b/docs/overlay-repo-pattern.md @@ -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 +-railiance-overlay/ + README.md + railiance/ + app.toml + upstream.toml + charts/ + / + 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-`. + +## 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//` + +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. diff --git a/docs/promote-rollback-onboarding.md b/docs/promote-rollback-onboarding.md new file mode 100644 index 0000000..99ced46 --- /dev/null +++ b/docs/promote-rollback-onboarding.md @@ -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 +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 +bin/railiance rollback /path/to/overlay --apply --approval-id --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. diff --git a/docs/railiance-run-command.md b/docs/railiance-run-command.md new file mode 100644 index 0000000..7390e2a --- /dev/null +++ b/docs/railiance-run-command.md @@ -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. diff --git a/docs/source-import-plan.md b/docs/source-import-plan.md index 591047f..892c0bd 100644 --- a/docs/source-import-plan.md +++ b/docs/source-import-plan.md @@ -10,6 +10,11 @@ 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`: @@ -18,14 +23,6 @@ Imported source assets from `railiance-cluster`: ## Pending First-Wave Imports -Expected source documents from `railiance-cluster`: - -- `docs/overlay-repo-pattern.md` -- `docs/canary-helm-template.md` -- `docs/stage2-deploy-observe.md` -- `docs/promote-rollback-onboarding.md` -- `docs/railiance-run-command.md` - Expected source assets from `railiance-cluster`: - `tools/create_railiance_overlay_repo.sh` diff --git a/docs/stage2-deploy-observe.md b/docs/stage2-deploy-observe.md new file mode 100644 index 0000000..b6e315b --- /dev/null +++ b/docs/stage2-deploy-observe.md @@ -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 +``` + +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. diff --git a/workplans/RAIL-K8S-WP-0001-bootstrap-and-wave1-import.md b/workplans/RAIL-K8S-WP-0001-bootstrap-and-wave1-import.md index 2c4237a..a1712a1 100644 --- a/workplans/RAIL-K8S-WP-0001-bootstrap-and-wave1-import.md +++ b/workplans/RAIL-K8S-WP-0001-bootstrap-and-wave1-import.md @@ -79,6 +79,13 @@ still pending. `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