Import core rail-kubernetes contract artifacts
This commit is contained in:
parent
513407acaa
commit
b48eaea942
9 changed files with 1375 additions and 5 deletions
|
|
@ -42,4 +42,6 @@ The boundary and source material come from:
|
|||
|
||||
- `declarations/rail.yaml` — source-controlled rail contract
|
||||
- `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
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
# rail-kubernetes Docs
|
||||
|
||||
- `app-toml-contract.md` — canonical workload declaration contract for the rail
|
||||
- `deployment-lifecycle.md` — canonical stage 1 / 2 / 3 lifecycle contract
|
||||
- `wave-1-contract.md` — current rail contract and boundary summary
|
||||
- `source-import-plan.md` — source material and migration direction from existing repos
|
||||
|
|
|
|||
240
docs/app-toml-contract.md
Normal file
240
docs/app-toml-contract.md
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
# Railiance app.toml Contract
|
||||
|
||||
This document defines the repository-local `railiance/app.toml` contract used by
|
||||
Railiance staged promotion tooling. The file tells Railiance how a workload
|
||||
moves through Stage 1 local validation, Stage 2 production canary, and Stage 3
|
||||
production promotion without relying on bespoke operator notes.
|
||||
|
||||
The contract is intentionally declarative. Commands, health checks, platform
|
||||
dependencies, and secret references are described by stable names. Plaintext
|
||||
secrets, bearer tokens, kubeconfigs, and private key material must never appear
|
||||
in `railiance/app.toml`.
|
||||
|
||||
Ownership note: this contract now lives in `rail-kubernetes` as part of the
|
||||
wave-1 split. `railiance-cluster` may keep compatibility copies during the
|
||||
migration window, but long-term ownership belongs with the generic
|
||||
workload-on-Kubernetes contract here rather than with S2 cluster substrate
|
||||
ownership.
|
||||
|
||||
The machine-readable schema lives at `schemas/railiance-app.schema.json`. A
|
||||
minimal example lives at `examples/railiance/app.toml`.
|
||||
|
||||
## File Location
|
||||
|
||||
Participating workload repositories declare the contract at:
|
||||
|
||||
```text
|
||||
railiance/app.toml
|
||||
```
|
||||
|
||||
Overlay repositories for third-party applications use the same path in the
|
||||
overlay repo, not in the upstream source repository.
|
||||
|
||||
## Versioning
|
||||
|
||||
Every file must include:
|
||||
|
||||
```toml
|
||||
schema_version = "railiance.app.v1"
|
||||
```
|
||||
|
||||
Breaking contract changes require a new schema version. Tooling must fail closed
|
||||
when it sees an unsupported `schema_version`.
|
||||
|
||||
## Top-Level Sections
|
||||
|
||||
### app
|
||||
|
||||
Identifies the workload and its ownership boundary.
|
||||
|
||||
Required fields:
|
||||
|
||||
- `id`: stable lowercase id using letters, numbers, and hyphens.
|
||||
- `name`: human-readable workload name.
|
||||
- `repo`: owning source or overlay repository slug.
|
||||
- `owner`: owning team, domain, or operator group.
|
||||
- `criticality`: one of `low`, `medium`, `high`, or `critical`.
|
||||
- `description`: short purpose statement.
|
||||
|
||||
Production-critical workloads include source forge, identity, State Hub,
|
||||
Inter-Hub, databases, object stores, backup systems, ingress, and cluster-wide
|
||||
policy controllers. For those workloads, `criticality = "critical"` requires
|
||||
explicit human approval before Stage 2 traffic exposure and Stage 3 promotion.
|
||||
|
||||
### source
|
||||
|
||||
Identifies the candidate under promotion.
|
||||
|
||||
Required fields:
|
||||
|
||||
- `revision`: commit id, tag, or immutable source revision expression.
|
||||
- `artifact`: artifact kind, normally `image`, `helm-chart`, or `bundle`.
|
||||
- `digest_policy`: one of `required`, `preferred`, or `not-applicable`.
|
||||
|
||||
If an image is promoted, Stage 2 and Stage 3 tooling should prefer immutable
|
||||
image digests over mutable tags.
|
||||
|
||||
### platform.dependencies
|
||||
|
||||
Declares platform services required before canary or production promotion.
|
||||
|
||||
Each dependency has:
|
||||
|
||||
- `name`: stable service name.
|
||||
- `kind`: dependency kind such as `postgres`, `redis`, `object-store`,
|
||||
`identity`, `state-hub`, `inter-hub`, `network`, or `other`.
|
||||
- `required`: boolean.
|
||||
- `stage`: earliest stage that needs it, one of `stage1`, `stage2`, `stage3`.
|
||||
- `evidence`: non-secret evidence expected before promotion, such as a health
|
||||
endpoint result, Kubernetes Ready condition, or State Hub progress id.
|
||||
|
||||
### secrets.references
|
||||
|
||||
Declares required secret references without secret values.
|
||||
|
||||
Each reference has:
|
||||
|
||||
- `name`: workload-local secret name.
|
||||
- `route`: approved credential route id, for example `openbao-api-key`,
|
||||
`key-cape-oidc-login`, or `activity-core-issue-sink`.
|
||||
- `target`: non-secret target reference such as a Kubernetes Secret name,
|
||||
ExternalSecret name, OpenBao path, or environment variable name.
|
||||
- `stage`: earliest stage that needs the secret.
|
||||
- `required`: boolean.
|
||||
|
||||
Forbidden fields include plaintext values, tokens, passwords, kubeconfigs, or
|
||||
private keys. Tooling must reject suspicious field names such as `value`,
|
||||
`token`, `password`, `secret`, `private_key`, or `kubeconfig` inside secret
|
||||
reference objects unless they are part of the approved non-secret `target` text.
|
||||
|
||||
### observability
|
||||
|
||||
Defines how promotion tooling proves the workload is alive and observable.
|
||||
|
||||
Required fields:
|
||||
|
||||
- `health_endpoints`: one or more HTTP health endpoint declarations.
|
||||
- `metrics`: optional metrics endpoint or query references.
|
||||
- `logs`: optional log selectors or query references.
|
||||
|
||||
Health endpoint declarations include `name`, `url`, `stage`, and expected
|
||||
status code. URLs may be internal service URLs for Stage 2/3; they must not
|
||||
embed credentials.
|
||||
|
||||
### rollback
|
||||
|
||||
Defines how the workload returns to a previous stable state.
|
||||
|
||||
Required fields:
|
||||
|
||||
- `strategy`: one of `helm-revision`, `image-digest`, `traffic-shift`,
|
||||
`manual-runbook`, or `none`.
|
||||
- `command`: command name or runbook path. This may be a placeholder before
|
||||
T07 implements automation, but it must tell the operator where rollback lives.
|
||||
- `verification`: non-secret check to confirm rollback succeeded.
|
||||
|
||||
`strategy = "none"` is allowed only for Stage 1-only workloads and must not be
|
||||
used for production-critical workloads.
|
||||
|
||||
## Stage Sections
|
||||
|
||||
The contract has one table for each stage:
|
||||
|
||||
```toml
|
||||
[stages.stage1]
|
||||
[stages.stage2]
|
||||
[stages.stage3]
|
||||
```
|
||||
|
||||
Each stage includes:
|
||||
|
||||
- `enabled`: boolean.
|
||||
- `namespace`: target Kubernetes namespace, or a local namespace for Stage 1.
|
||||
- `release`: release identity.
|
||||
- `commands`: ordered command aliases or shell commands that tooling may run.
|
||||
- `checks`: ordered check ids to evaluate.
|
||||
- `evidence`: expected non-secret evidence outputs.
|
||||
- `requires_approval`: boolean.
|
||||
|
||||
Stage 2 additionally includes `canary_mode`, one of `weighted`, `header`,
|
||||
`path`, `shadow`, or `isolated`, plus `observation_minutes` and optional
|
||||
`traffic_percent` when weighted routing is used.
|
||||
|
||||
Stage 3 additionally includes `promotion_mode`, one of `traffic-shift`,
|
||||
`release-replace`, `selector-switch`, or `workflow`, plus `previous_stable`.
|
||||
|
||||
## Check Definitions
|
||||
|
||||
Checks live under `[[checks]]` entries and are referenced by stage `checks`.
|
||||
|
||||
Required fields:
|
||||
|
||||
- `id`: stable check id.
|
||||
- `type`: one of `command`, `http`, `kubernetes`, `helm`, `metric`, `log`, or
|
||||
`manual`.
|
||||
- `stage`: earliest stage that may run the check.
|
||||
- `description`: human-readable purpose.
|
||||
- `required`: boolean.
|
||||
|
||||
Type-specific fields:
|
||||
|
||||
- `command`: `run` command string and optional `timeout_seconds`.
|
||||
- `http`: `url`, `expected_status`, and optional `timeout_seconds`.
|
||||
- `kubernetes`: `namespace`, `resource`, and `condition`.
|
||||
- `helm`: `chart`, `values`, and `mode` such as `template` or
|
||||
`server-dry-run`.
|
||||
- `metric`: `query`, `window_minutes`, and `threshold`.
|
||||
- `log`: `selector`, `window_minutes`, and `forbidden_patterns`.
|
||||
- `manual`: `evidence_required` text.
|
||||
|
||||
Checks must not print secrets. If a check needs secret-backed access, the result
|
||||
records only the route, target object, and pass/fail state.
|
||||
|
||||
## Command Semantics
|
||||
|
||||
Commands in `app.toml` are declarations for Railiance tooling. Stage 1, Stage
|
||||
2, and Stage 3 commands now have local CLI support; workload scripts may still
|
||||
wrap them for service-specific checks.
|
||||
|
||||
Expected mapping:
|
||||
|
||||
- Stage 1 commands are consumed by `bin/railiance run <overlay-dir>`.
|
||||
- Stage 2 commands are consumed by `bin/railiance deploy --stage 2 <overlay-dir>`
|
||||
and `bin/railiance observe --stage 2 <overlay-dir>`.
|
||||
- Stage 3 commands are consumed by `bin/railiance promote <overlay-dir>` and
|
||||
`bin/railiance rollback <overlay-dir>`.
|
||||
|
||||
Tooling must emit machine-readable results with workload identity, candidate
|
||||
revision, checks run, pass/fail status, non-secret evidence, rollback target,
|
||||
and approval state.
|
||||
|
||||
## Minimal Example
|
||||
|
||||
See `examples/railiance/app.toml`. It declares a critical internal service with:
|
||||
|
||||
- immutable image digest requirement;
|
||||
- Stage 1 local validation;
|
||||
- Stage 2 isolated canary;
|
||||
- Stage 3 release replacement;
|
||||
- OpenBao-routed secret references without values;
|
||||
- HTTP, Helm, Kubernetes, and manual approval checks.
|
||||
|
||||
## Adoption Rules
|
||||
|
||||
A workload can enter Stage 1 when `app.toml` passes schema validation and all
|
||||
Stage 1 required checks are declared.
|
||||
|
||||
A workload can enter Stage 2 only when:
|
||||
|
||||
- Stage 1 passed for the same candidate artifact;
|
||||
- Stage 2 namespace, release, canary mode, health checks, dependencies, and
|
||||
rollback target are declared;
|
||||
- secret references use approved routes and contain no values;
|
||||
- production-critical workloads have explicit approval.
|
||||
|
||||
A workload can enter Stage 3 only when:
|
||||
|
||||
- Stage 2 acceptance gates passed for the same candidate artifact;
|
||||
- `previous_stable` and rollback verification are recorded;
|
||||
- backup/restore posture is current for stateful workloads;
|
||||
- production-critical workloads have explicit human approval.
|
||||
340
docs/deployment-lifecycle.md
Normal file
340
docs/deployment-lifecycle.md
Normal file
|
|
@ -0,0 +1,340 @@
|
|||
# Railiance Deployment Lifecycle
|
||||
|
||||
This document defines the Railiance three-stage promotion lifecycle for
|
||||
workloads that run on the Railiance Kubernetes substrate.
|
||||
|
||||
The lifecycle exists so production workloads move through repeatable gates
|
||||
instead of one-off operator memory. It is intentionally conservative: every
|
||||
stage must leave evidence, every promotion must have a rollback path, and
|
||||
critical workloads require explicit human approval before production traffic is
|
||||
changed.
|
||||
|
||||
Ownership note: this specification now lives in `rail-kubernetes` as the
|
||||
wave-1 home of the default Kubernetes execution rail. `railiance-cluster` may
|
||||
retain compatibility shims during migration, but long-term ownership of the
|
||||
generic lifecycle contract belongs here rather than in the S2 substrate repo.
|
||||
|
||||
## Scope
|
||||
|
||||
This specification now lives beside the Kubernetes rail contract rather than
|
||||
beside the cluster substrate. The compatibility tooling may still live
|
||||
elsewhere during migration, but long-term ownership is not S2.
|
||||
|
||||
Repo boundaries:
|
||||
|
||||
- `rail-kubernetes` owns lifecycle semantics, generic promotion gates,
|
||||
canary validation, production routing expectations, rollback expectations,
|
||||
and the generic workload contract.
|
||||
- `railiance-cluster` owns only the substrate prerequisites published to that
|
||||
rail: cluster connectivity, namespace behavior, ingress availability, and
|
||||
operator/runtime expectations.
|
||||
- `railiance-apps` owns workload-specific Helm values, application release
|
||||
definitions, and production workload configuration.
|
||||
- `railiance-platform` owns shared platform services such as databases,
|
||||
caches, object storage, and backup targets.
|
||||
- `railiance-enablement` owns developer-facing templates, CI workflows, and
|
||||
local ergonomics.
|
||||
- `railiance-infra` owns host provisioning, OS hardening, SSH, firewall, and
|
||||
node bootstrap below Kubernetes.
|
||||
|
||||
## Lifecycle Overview
|
||||
|
||||
Railiance promotes a workload through three stages:
|
||||
|
||||
1. Stage 1: local validation.
|
||||
2. Stage 2: production canary.
|
||||
3. Stage 3: production promotion.
|
||||
|
||||
The stages are sequential. A workload may return to an earlier stage at any
|
||||
time, but it must not skip a stage when moving toward production unless an
|
||||
operator records an emergency exception in State Hub.
|
||||
|
||||
Each stage emits a machine-readable result with:
|
||||
|
||||
- workload identity;
|
||||
- source revision or image digest;
|
||||
- target environment;
|
||||
- checks run;
|
||||
- pass/fail status;
|
||||
- non-secret evidence references;
|
||||
- rollback target, when applicable;
|
||||
- approving human or explicit "not required" decision.
|
||||
|
||||
## Workload Declaration
|
||||
|
||||
Each participating workload should declare its promotion contract in a
|
||||
repository-local `railiance/app.toml`. The contract is defined in
|
||||
`docs/app-toml-contract.md`, with a machine-readable schema at
|
||||
`schemas/railiance-app.schema.json`. This lifecycle expects every workload
|
||||
declaration to provide at least:
|
||||
|
||||
- stable workload name and owning repo;
|
||||
- source revision, image tag, or image digest policy;
|
||||
- stage-specific namespaces or release names;
|
||||
- health checks and observability endpoints;
|
||||
- ingress or routing targets;
|
||||
- platform dependencies;
|
||||
- rollback command or previous-stable reference;
|
||||
- secret references by name or path, never plaintext secret values.
|
||||
|
||||
If a workload cannot provide a machine-readable declaration yet, it may still
|
||||
use this lifecycle through a written operator runbook, but that is a temporary
|
||||
compatibility path. The runbook must identify the missing declaration fields.
|
||||
|
||||
## Stage 1: Local Validation
|
||||
|
||||
Stage 1 proves that the workload can be built, configured, and checked outside
|
||||
production traffic.
|
||||
|
||||
Typical Stage 1 targets:
|
||||
|
||||
- local container runtime;
|
||||
- local Kubernetes such as k3d, kind, or a disposable namespace;
|
||||
- dry-run Helm rendering;
|
||||
- unit, integration, migration, and smoke checks that do not require production
|
||||
credentials.
|
||||
|
||||
Required Stage 1 checks:
|
||||
|
||||
- source revision is cleanly identified;
|
||||
- build or artifact selection is deterministic;
|
||||
- Helm templates or manifests render without invalid Kubernetes objects;
|
||||
- local health checks pass;
|
||||
- required secrets are referenced by name only and are not printed;
|
||||
- database migrations, if any, are classified as reversible, forward-only, or
|
||||
requiring human approval;
|
||||
- a Stage 2 candidate artifact is named by immutable digest or equivalent
|
||||
immutable revision.
|
||||
|
||||
Stage 1 fails closed when:
|
||||
|
||||
- local checks are skipped without an approved reason;
|
||||
- generated manifests contain plaintext secrets;
|
||||
- the artifact cannot be traced to source;
|
||||
- the workload cannot state how it will be observed in Stage 2.
|
||||
|
||||
Stage 1 completion does not authorize production traffic. It only makes a
|
||||
workload eligible for Stage 2 review.
|
||||
|
||||
## Stage 2: Production Canary
|
||||
|
||||
Stage 2 deploys the candidate to production infrastructure with limited or
|
||||
isolated exposure. The goal is to observe the candidate against real platform
|
||||
dependencies while keeping blast radius small.
|
||||
|
||||
Acceptable canary forms:
|
||||
|
||||
- weighted ingress split between stable and canary;
|
||||
- header-based or path-based routing for operator traffic only;
|
||||
- shadow deployment receiving replicated non-mutating traffic;
|
||||
- isolated production namespace with manually triggered probes.
|
||||
|
||||
The selected canary form must be declared before deployment. If weighted
|
||||
routing is unavailable, the fallback must preserve the same safety property:
|
||||
the candidate can be observed without silently replacing stable production.
|
||||
|
||||
Required Stage 2 prechecks:
|
||||
|
||||
- Stage 1 result passed for the same candidate artifact;
|
||||
- cluster connectivity and namespace readiness are verified;
|
||||
- target image digest or immutable tag exists in the registry;
|
||||
- Helm server-side dry-run succeeds;
|
||||
- ingress, certificate, and DNS prerequisites are present where applicable;
|
||||
- platform dependencies are healthy or explicitly degraded with operator
|
||||
approval;
|
||||
- rollback target is known before the canary is applied;
|
||||
- monitoring and log queries are available for the canary release.
|
||||
|
||||
Required Stage 2 evidence:
|
||||
|
||||
- rendered release identity;
|
||||
- applied namespace and release name;
|
||||
- pod readiness and restart status;
|
||||
- ingress or routing state;
|
||||
- key health endpoint result;
|
||||
- relevant metrics window or explicit "metrics unavailable" note;
|
||||
- State Hub progress note with non-secret evidence;
|
||||
- operator approval when the workload is production-critical.
|
||||
|
||||
Canary acceptance gates:
|
||||
|
||||
- canary pods remain ready for the configured observation window;
|
||||
- no crash loops, repeated restarts, or pending pods remain unexplained;
|
||||
- health checks pass from inside and outside the cluster when both are
|
||||
applicable;
|
||||
- error rate, latency, and saturation do not regress beyond the workload's
|
||||
declared threshold;
|
||||
- no unexpected schema, storage, or queue side effects are observed;
|
||||
- logs show no secret leakage and no repeated authorization failures;
|
||||
- rollback has been tested previously or is a single documented command with a
|
||||
known previous-stable target.
|
||||
|
||||
Default observation windows:
|
||||
|
||||
- non-critical internal service: 15 minutes;
|
||||
- user-facing or shared platform service: 30 minutes;
|
||||
- production-critical infrastructure such as Forgejo, identity, registry, or
|
||||
State Hub: operator-defined window, minimum 60 minutes unless explicitly
|
||||
waived.
|
||||
|
||||
Stage 2 fails closed when:
|
||||
|
||||
- the canary cannot be distinguished from stable production;
|
||||
- production routing changes more traffic than intended;
|
||||
- any required evidence is missing and no operator waiver is recorded;
|
||||
- rollback target is unknown;
|
||||
- the candidate needs a secret, credential, or platform dependency that was not
|
||||
declared before the canary.
|
||||
|
||||
## Stage 3: Production Promotion
|
||||
|
||||
Stage 3 promotes the accepted candidate to the stable production path.
|
||||
|
||||
Promotion may mean:
|
||||
|
||||
- shifting weighted traffic to the canary release;
|
||||
- replacing the stable Helm release with the accepted candidate;
|
||||
- changing an ingress selector or service target;
|
||||
- activating an operator-approved rollout workflow.
|
||||
|
||||
Required Stage 3 prechecks:
|
||||
|
||||
- Stage 2 acceptance gates passed for the same candidate artifact;
|
||||
- the previous stable version is recorded;
|
||||
- backup and restore posture is current for stateful workloads;
|
||||
- migrations are approved and sequenced;
|
||||
- production-critical workloads have explicit human approval;
|
||||
- a rollback command and rollback verification check are available.
|
||||
|
||||
Required Stage 3 evidence:
|
||||
|
||||
- promotion command or workflow id;
|
||||
- previous stable version;
|
||||
- new stable version;
|
||||
- production routing state after promotion;
|
||||
- smoke result after promotion;
|
||||
- rollback target retained;
|
||||
- State Hub progress note with non-secret evidence.
|
||||
|
||||
Stage 3 is complete only after the post-promotion smoke passes and the
|
||||
workload's stable routing points at the promoted candidate.
|
||||
|
||||
## Rollback Expectations
|
||||
|
||||
Rollback is part of every promotion, not an afterthought.
|
||||
|
||||
Every Stage 2 and Stage 3 action must identify one of:
|
||||
|
||||
- previous stable Helm release revision;
|
||||
- previous image digest and values file;
|
||||
- previous ingress/routing configuration;
|
||||
- documented manual recovery path when automation is not yet safe.
|
||||
|
||||
Rollback must be immediate when:
|
||||
|
||||
- production availability is degraded;
|
||||
- canary traffic escapes the declared blast radius;
|
||||
- the workload emits repeated authorization or secret-handling errors;
|
||||
- data integrity is at risk;
|
||||
- an operator revokes approval during the observation window.
|
||||
|
||||
Rollback may be deferred only when the rollback itself is more dangerous than
|
||||
the incident state. That decision requires a State Hub note and human approval.
|
||||
|
||||
After rollback, record:
|
||||
|
||||
- triggering symptom;
|
||||
- rollback action;
|
||||
- final stable version;
|
||||
- remaining cleanup;
|
||||
- whether the failed candidate is blocked, abandoned, or returned to Stage 1.
|
||||
|
||||
## Human Approval Gates
|
||||
|
||||
Human approval is required before production traffic changes for
|
||||
production-critical workloads.
|
||||
|
||||
Production-critical workloads include:
|
||||
|
||||
- source forge and package registry workloads such as Forgejo or Gitea;
|
||||
- identity, MFA, SSO, or authorization systems;
|
||||
- State Hub, Inter-Hub, and operator coordination services;
|
||||
- databases, object stores, and backup systems;
|
||||
- ingress, certificate, or cluster-wide policy controllers;
|
||||
- any workload whose failure blocks multiple repos or domains.
|
||||
|
||||
Approval must be recorded as a non-secret State Hub note or task comment. The
|
||||
approval record should name:
|
||||
|
||||
- approving operator;
|
||||
- candidate artifact;
|
||||
- stage being approved;
|
||||
- observation window;
|
||||
- rollback target;
|
||||
- any waived gates and why.
|
||||
|
||||
Emergency approval can be retrospective only when delaying the action would
|
||||
increase production risk. Retrospective approval must be recorded immediately
|
||||
after stabilization.
|
||||
|
||||
## Evidence And Secret Handling
|
||||
|
||||
Lifecycle evidence must be useful without being sensitive.
|
||||
|
||||
Allowed evidence:
|
||||
|
||||
- commit ids;
|
||||
- image tags and digests;
|
||||
- workflow ids;
|
||||
- Kubernetes object names;
|
||||
- pod status summaries;
|
||||
- HTTP status codes;
|
||||
- timestamps;
|
||||
- State Hub progress ids;
|
||||
- pass/fail summaries.
|
||||
|
||||
Forbidden evidence:
|
||||
|
||||
- plaintext secrets;
|
||||
- bearer tokens;
|
||||
- static API keys;
|
||||
- kubeconfigs;
|
||||
- private key material;
|
||||
- full environment dumps;
|
||||
- logs that contain credentials or user private data.
|
||||
|
||||
When a check needs secret-backed access, record only the access path and result,
|
||||
for example: "OpenBao path configured, token exchange returned 200".
|
||||
|
||||
## Forgejo Readiness Interpretation
|
||||
|
||||
This lifecycle is clear enough for Forgejo when a future Forgejo workplan can
|
||||
answer these questions before production cutover:
|
||||
|
||||
- What source revision and image digest are being promoted?
|
||||
- What local checks prove the candidate is viable?
|
||||
- How is the production canary isolated or traffic-limited?
|
||||
- Which health, registry, SSH, web, Actions, and email recovery checks define
|
||||
acceptance?
|
||||
- Who approves the Stage 3 traffic switch?
|
||||
- What is the previous stable target?
|
||||
- How is repository data protected before and after promotion?
|
||||
- How will rollback be verified without losing package or repository state?
|
||||
|
||||
If any answer is missing, Forgejo remains in Stage 1 or Stage 2 preparation and
|
||||
must not cut over to Stage 3.
|
||||
|
||||
## Minimum Command Contract
|
||||
|
||||
The Railiance CLI makes these lifecycle operations repeatable:
|
||||
|
||||
```text
|
||||
bin/railiance run <overlay-dir> # Stage 1 local validation
|
||||
bin/railiance deploy --stage 2 <overlay-dir> --plan # Stage 2 canary plan
|
||||
bin/railiance observe --stage 2 <overlay-dir> --plan # Stage 2 evidence targets
|
||||
bin/railiance promote <overlay-dir> --plan # Stage 3 production promotion
|
||||
bin/railiance rollback <overlay-dir> --plan # rollback to previous stable
|
||||
```
|
||||
|
||||
The exact command names may change as implementation lands, but the behavior
|
||||
must preserve the stage gates and evidence requirements in this document.
|
||||
|
|
@ -4,12 +4,22 @@
|
|||
|
||||
Record which reviewed source materials are expected to seed this repo.
|
||||
|
||||
## First-Wave Imports
|
||||
## Imported In The Current Wave
|
||||
|
||||
Expected source documents from `railiance-cluster`:
|
||||
Imported source documents from `railiance-cluster`:
|
||||
|
||||
- `docs/deployment-lifecycle.md`
|
||||
- `docs/app-toml-contract.md`
|
||||
|
||||
Imported source assets from `railiance-cluster`:
|
||||
|
||||
- `schemas/railiance-app.schema.json`
|
||||
- `examples/railiance/app.toml`
|
||||
|
||||
## Pending First-Wave Imports
|
||||
|
||||
Expected source documents from `railiance-cluster`:
|
||||
|
||||
- `docs/overlay-repo-pattern.md`
|
||||
- `docs/canary-helm-template.md`
|
||||
- `docs/stage2-deploy-observe.md`
|
||||
|
|
@ -18,8 +28,6 @@ Expected source documents from `railiance-cluster`:
|
|||
|
||||
Expected source assets from `railiance-cluster`:
|
||||
|
||||
- `schemas/railiance-app.schema.json`
|
||||
- `examples/railiance/app.toml`
|
||||
- `tools/create_railiance_overlay_repo.sh`
|
||||
- `tools/cmd/railiance-run`
|
||||
- `tools/cmd/railiance-stage2`
|
||||
|
|
|
|||
176
examples/railiance/app.toml
Normal file
176
examples/railiance/app.toml
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
schema_version = "railiance.app.v1"
|
||||
|
||||
[app]
|
||||
id = "example-service"
|
||||
name = "Example Service"
|
||||
repo = "railiance-apps/example-service"
|
||||
owner = "platform"
|
||||
criticality = "critical"
|
||||
description = "Reference declaration for the Railiance staged promotion lifecycle."
|
||||
|
||||
[source]
|
||||
revision = "git:main"
|
||||
artifact = "image"
|
||||
digest_policy = "required"
|
||||
|
||||
[rollback]
|
||||
strategy = "helm-revision"
|
||||
command = "bin/railiance rollback example-service"
|
||||
verification = "GET /health returns 200 on the restored stable release."
|
||||
|
||||
[[platform.dependencies]]
|
||||
name = "state-hub"
|
||||
kind = "state-hub"
|
||||
required = true
|
||||
stage = "stage2"
|
||||
evidence = "State Hub /healthz returns ok from the cluster path."
|
||||
|
||||
[[platform.dependencies]]
|
||||
name = "postgres"
|
||||
kind = "postgres"
|
||||
required = true
|
||||
stage = "stage2"
|
||||
evidence = "Target database reports Ready and backup posture is current."
|
||||
|
||||
[[secrets.references]]
|
||||
name = "runtime-api-key"
|
||||
route = "openbao-api-key"
|
||||
target = "ExternalSecret/example-service-runtime"
|
||||
stage = "stage2"
|
||||
required = true
|
||||
|
||||
[[observability.health_endpoints]]
|
||||
name = "local-health"
|
||||
url = "http://127.0.0.1:8080/health"
|
||||
stage = "stage1"
|
||||
expected_status = 200
|
||||
|
||||
[[observability.health_endpoints]]
|
||||
name = "cluster-health"
|
||||
url = "http://example-service.example-service.svc.cluster.local:8080/health"
|
||||
stage = "stage2"
|
||||
expected_status = 200
|
||||
|
||||
[[observability.metrics]]
|
||||
name = "request-errors"
|
||||
reference = 'promql:rate(http_requests_total{status=~"5.."}[5m])'
|
||||
stage = "stage2"
|
||||
|
||||
[[observability.logs]]
|
||||
name = "secret-leak-scan"
|
||||
reference = "kubectl logs -n example-service deploy/example-service-canary"
|
||||
stage = "stage2"
|
||||
|
||||
[stages.stage1]
|
||||
enabled = true
|
||||
namespace = "local"
|
||||
release = "example-service-local"
|
||||
commands = ["make test", "helm template charts/example-service"]
|
||||
checks = ["unit-tests", "helm-template", "local-health"]
|
||||
evidence = ["pytest output", "helm template success", "local health 200"]
|
||||
requires_approval = false
|
||||
|
||||
[stages.stage2]
|
||||
enabled = true
|
||||
namespace = "example-service"
|
||||
release = "example-service-canary"
|
||||
commands = ["bin/railiance deploy --stage 2 example-service", "bin/railiance observe example-service"]
|
||||
checks = ["server-dry-run", "canary-ready", "cluster-health", "operator-approval"]
|
||||
evidence = ["release name", "pod readiness", "health 200", "State Hub progress id"]
|
||||
requires_approval = true
|
||||
canary_mode = "isolated"
|
||||
observation_minutes = 60
|
||||
|
||||
[stages.stage3]
|
||||
enabled = true
|
||||
namespace = "example-service"
|
||||
release = "example-service"
|
||||
commands = ["bin/railiance promote example-service", "bin/railiance observe example-service"]
|
||||
checks = ["stage2-accepted", "rollback-target", "cluster-health", "operator-approval"]
|
||||
evidence = ["promotion command id", "new stable digest", "post-promotion smoke"]
|
||||
requires_approval = true
|
||||
promotion_mode = "release-replace"
|
||||
previous_stable = "helm:example-service:previous"
|
||||
|
||||
[[checks]]
|
||||
id = "unit-tests"
|
||||
type = "command"
|
||||
stage = "stage1"
|
||||
description = "Run repository unit tests."
|
||||
required = true
|
||||
run = "make test"
|
||||
timeout_seconds = 600
|
||||
|
||||
[[checks]]
|
||||
id = "helm-template"
|
||||
type = "helm"
|
||||
stage = "stage1"
|
||||
description = "Render Helm templates locally."
|
||||
required = true
|
||||
chart = "charts/example-service"
|
||||
values = "values/local.yaml"
|
||||
mode = "template"
|
||||
|
||||
[[checks]]
|
||||
id = "local-health"
|
||||
type = "http"
|
||||
stage = "stage1"
|
||||
description = "Confirm local service health."
|
||||
required = true
|
||||
url = "http://127.0.0.1:8080/health"
|
||||
expected_status = 200
|
||||
timeout_seconds = 10
|
||||
|
||||
[[checks]]
|
||||
id = "server-dry-run"
|
||||
type = "helm"
|
||||
stage = "stage2"
|
||||
description = "Render and submit a server-side dry run before canary."
|
||||
required = true
|
||||
chart = "charts/example-service"
|
||||
values = "values/canary.yaml"
|
||||
mode = "server-dry-run"
|
||||
|
||||
[[checks]]
|
||||
id = "canary-ready"
|
||||
type = "kubernetes"
|
||||
stage = "stage2"
|
||||
description = "Canary deployment reaches Available."
|
||||
required = true
|
||||
namespace = "example-service"
|
||||
resource = "deploy/example-service-canary"
|
||||
condition = "Available"
|
||||
|
||||
[[checks]]
|
||||
id = "cluster-health"
|
||||
type = "http"
|
||||
stage = "stage2"
|
||||
description = "Cluster health endpoint returns 200."
|
||||
required = true
|
||||
url = "http://example-service.example-service.svc.cluster.local:8080/health"
|
||||
expected_status = 200
|
||||
timeout_seconds = 10
|
||||
|
||||
[[checks]]
|
||||
id = "operator-approval"
|
||||
type = "manual"
|
||||
stage = "stage2"
|
||||
description = "Human approval is recorded before production-critical traffic changes."
|
||||
required = true
|
||||
evidence_required = "State Hub approval note id, candidate digest, rollback target."
|
||||
|
||||
[[checks]]
|
||||
id = "stage2-accepted"
|
||||
type = "manual"
|
||||
stage = "stage3"
|
||||
description = "Stage 2 gates passed for the same candidate artifact."
|
||||
required = true
|
||||
evidence_required = "State Hub Stage 2 acceptance progress id."
|
||||
|
||||
[[checks]]
|
||||
id = "rollback-target"
|
||||
type = "manual"
|
||||
stage = "stage3"
|
||||
description = "Previous stable release is recorded before promotion."
|
||||
required = true
|
||||
evidence_required = "Previous Helm revision or image digest."
|
||||
596
schemas/railiance-app.schema.json
Normal file
596
schemas/railiance-app.schema.json
Normal file
|
|
@ -0,0 +1,596 @@
|
|||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://railiance.local/schemas/railiance-app.schema.json",
|
||||
"title": "Railiance app.toml contract",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"schema_version",
|
||||
"app",
|
||||
"source",
|
||||
"platform",
|
||||
"secrets",
|
||||
"observability",
|
||||
"rollback",
|
||||
"stages",
|
||||
"checks"
|
||||
],
|
||||
"properties": {
|
||||
"schema_version": {
|
||||
"const": "railiance.app.v1"
|
||||
},
|
||||
"app": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"id",
|
||||
"name",
|
||||
"repo",
|
||||
"owner",
|
||||
"criticality",
|
||||
"description"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-z0-9][a-z0-9-]*$"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"repo": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"owner": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"criticality": {
|
||||
"enum": [
|
||||
"low",
|
||||
"medium",
|
||||
"high",
|
||||
"critical"
|
||||
]
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"source": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"revision",
|
||||
"artifact",
|
||||
"digest_policy"
|
||||
],
|
||||
"properties": {
|
||||
"revision": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"artifact": {
|
||||
"enum": [
|
||||
"image",
|
||||
"helm-chart",
|
||||
"bundle",
|
||||
"manifest",
|
||||
"other"
|
||||
]
|
||||
},
|
||||
"digest_policy": {
|
||||
"enum": [
|
||||
"required",
|
||||
"preferred",
|
||||
"not-applicable"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"platform": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"dependencies"
|
||||
],
|
||||
"properties": {
|
||||
"dependencies": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/dependency"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"secrets": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"references"
|
||||
],
|
||||
"properties": {
|
||||
"references": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/secretReference"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"observability": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"health_endpoints"
|
||||
],
|
||||
"properties": {
|
||||
"health_endpoints": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/$defs/healthEndpoint"
|
||||
}
|
||||
},
|
||||
"metrics": {
|
||||
"type": "array",
|
||||
"default": [],
|
||||
"items": {
|
||||
"$ref": "#/$defs/observationReference"
|
||||
}
|
||||
},
|
||||
"logs": {
|
||||
"type": "array",
|
||||
"default": [],
|
||||
"items": {
|
||||
"$ref": "#/$defs/observationReference"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"rollback": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"strategy",
|
||||
"command",
|
||||
"verification"
|
||||
],
|
||||
"properties": {
|
||||
"strategy": {
|
||||
"enum": [
|
||||
"helm-revision",
|
||||
"image-digest",
|
||||
"traffic-shift",
|
||||
"manual-runbook",
|
||||
"none"
|
||||
]
|
||||
},
|
||||
"command": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"verification": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"stages": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"stage1",
|
||||
"stage2",
|
||||
"stage3"
|
||||
],
|
||||
"properties": {
|
||||
"stage1": {
|
||||
"$ref": "#/$defs/stage1"
|
||||
},
|
||||
"stage2": {
|
||||
"$ref": "#/$defs/stage2"
|
||||
},
|
||||
"stage3": {
|
||||
"$ref": "#/$defs/stage3"
|
||||
}
|
||||
}
|
||||
},
|
||||
"checks": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/$defs/check"
|
||||
}
|
||||
}
|
||||
},
|
||||
"$defs": {
|
||||
"stageName": {
|
||||
"enum": [
|
||||
"stage1",
|
||||
"stage2",
|
||||
"stage3"
|
||||
]
|
||||
},
|
||||
"dependency": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"name",
|
||||
"kind",
|
||||
"required",
|
||||
"stage",
|
||||
"evidence"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"kind": {
|
||||
"enum": [
|
||||
"postgres",
|
||||
"redis",
|
||||
"object-store",
|
||||
"identity",
|
||||
"state-hub",
|
||||
"inter-hub",
|
||||
"network",
|
||||
"other"
|
||||
]
|
||||
},
|
||||
"required": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"stage": {
|
||||
"$ref": "#/$defs/stageName"
|
||||
},
|
||||
"evidence": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"secretReference": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"name",
|
||||
"route",
|
||||
"target",
|
||||
"stage",
|
||||
"required"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"route": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"target": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"stage": {
|
||||
"$ref": "#/$defs/stageName"
|
||||
},
|
||||
"required": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"not": {
|
||||
"anyOf": [
|
||||
{
|
||||
"required": [
|
||||
"value"
|
||||
]
|
||||
},
|
||||
{
|
||||
"required": [
|
||||
"token"
|
||||
]
|
||||
},
|
||||
{
|
||||
"required": [
|
||||
"password"
|
||||
]
|
||||
},
|
||||
{
|
||||
"required": [
|
||||
"secret"
|
||||
]
|
||||
},
|
||||
{
|
||||
"required": [
|
||||
"private_key"
|
||||
]
|
||||
},
|
||||
{
|
||||
"required": [
|
||||
"kubeconfig"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"healthEndpoint": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"name",
|
||||
"url",
|
||||
"stage",
|
||||
"expected_status"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"url": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"stage": {
|
||||
"$ref": "#/$defs/stageName"
|
||||
},
|
||||
"expected_status": {
|
||||
"type": "integer",
|
||||
"minimum": 100,
|
||||
"maximum": 599
|
||||
}
|
||||
}
|
||||
},
|
||||
"observationReference": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"name",
|
||||
"reference",
|
||||
"stage"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"reference": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"stage": {
|
||||
"$ref": "#/$defs/stageName"
|
||||
}
|
||||
}
|
||||
},
|
||||
"check": {
|
||||
"type": "object",
|
||||
"additionalProperties": true,
|
||||
"required": [
|
||||
"id",
|
||||
"type",
|
||||
"stage",
|
||||
"description",
|
||||
"required"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"command",
|
||||
"http",
|
||||
"kubernetes",
|
||||
"helm",
|
||||
"metric",
|
||||
"log",
|
||||
"manual"
|
||||
]
|
||||
},
|
||||
"stage": {
|
||||
"$ref": "#/$defs/stageName"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"required": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"stage1": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"enabled",
|
||||
"namespace",
|
||||
"release",
|
||||
"commands",
|
||||
"checks",
|
||||
"evidence",
|
||||
"requires_approval"
|
||||
],
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"namespace": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"release": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"commands": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"checks": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"evidence": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"requires_approval": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"stage2": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"enabled",
|
||||
"namespace",
|
||||
"release",
|
||||
"commands",
|
||||
"checks",
|
||||
"evidence",
|
||||
"requires_approval",
|
||||
"canary_mode",
|
||||
"observation_minutes"
|
||||
],
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"namespace": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"release": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"commands": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"checks": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"evidence": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"requires_approval": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"canary_mode": {
|
||||
"enum": [
|
||||
"weighted",
|
||||
"header",
|
||||
"path",
|
||||
"shadow",
|
||||
"isolated"
|
||||
]
|
||||
},
|
||||
"observation_minutes": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"traffic_percent": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 100
|
||||
}
|
||||
}
|
||||
},
|
||||
"stage3": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"enabled",
|
||||
"namespace",
|
||||
"release",
|
||||
"commands",
|
||||
"checks",
|
||||
"evidence",
|
||||
"requires_approval",
|
||||
"promotion_mode",
|
||||
"previous_stable"
|
||||
],
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"namespace": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"release": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"commands": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"checks": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"evidence": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"requires_approval": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"promotion_mode": {
|
||||
"enum": [
|
||||
"traffic-shift",
|
||||
"release-replace",
|
||||
"selector-switch",
|
||||
"workflow"
|
||||
]
|
||||
},
|
||||
"previous_stable": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -73,6 +73,12 @@ Acceptance:
|
|||
import plan. Full import of docs, schema, examples, and helper surfaces is
|
||||
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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue