Onboard tenant-engine to the staged-promotion contract
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 50s
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 50s
TEN-WP-0008. railiance/app.toml declares criticality=high, empty secrets, isolated canary, and the live PostgreSQL digest as previous_stable. Manifests render through kustomize (deploy/ and deploy/canary/). Stage 1 passed. Stage 2/3 Helm-only CLI gap requested as RAIL-BS-IN-0001 rather than a dummy chart. Assistant: grok Assistant-Session: 01a04cea-e5e8-7081-a0fc-808ebbc35fa9
This commit is contained in:
parent
f9f8e0c54f
commit
6644ad8402
19 changed files with 1053 additions and 18 deletions
70
railiance/README.md
Normal file
70
railiance/README.md
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
# Railiance staged promotion — tenant-engine
|
||||
|
||||
TEN-WP-0008 onboards this repo to `railiance.app.v1` without changing the
|
||||
contract. Production is described, not redeployed.
|
||||
|
||||
## T02 decision: kustomize, not Helm, not a dummy chart
|
||||
|
||||
The schema accepts any stage `commands` and check `type`s including
|
||||
`command` and `kubernetes`. This workload ships a multi-document
|
||||
manifest, now rendered by `deploy/kustomization.yaml` (`kubectl apply -k
|
||||
deploy/`). That is option 2 in TEN-WP-0008-T02: converting to kustomize is
|
||||
cheap and matches the small-headless-service precedent
|
||||
(`qonto-assistant`).
|
||||
|
||||
What the schema does **not** require, and what `bin/railiance
|
||||
deploy|promote --apply` still requires, is a Helm chart plus
|
||||
`values/stage3-production.yaml`. Plan mode of those commands fails closed
|
||||
on "no Stage 2 helm check declared" / missing chart. That is a CLI gap,
|
||||
not a missing field in `app.toml`. Per the workplan we do not paper over
|
||||
it with a chart we would never apply. Requested of `railiance-bootstrap`
|
||||
as `RAIL-BS-IN-0001`.
|
||||
|
||||
Stage commands in `app.toml` are therefore the kubectl invocations that
|
||||
actually work against this shape. Stage 1 (`bin/railiance run`) validates
|
||||
them by rendering both overlays locally.
|
||||
|
||||
## Canary
|
||||
|
||||
TEN-WP-0009 removed the ReadWriteOnce PVC and switched the Deployment to
|
||||
`RollingUpdate`, so a second pod is storage-feasible. `canary_mode =
|
||||
"isolated"` is a distinct Deployment/Service (`tenant-engine-canary`)
|
||||
whose selector does not match the production Service. It does not take
|
||||
`user-engine` traffic. Overlay: `deploy/canary/`.
|
||||
|
||||
## Recorded stable (T03)
|
||||
|
||||
`[stages.stage3].previous_stable` is the contract's copy of "what should
|
||||
be running":
|
||||
|
||||
```
|
||||
tenant-engine@sha256:a8e8086ffc5b772c1391b166f5e1884b90f7d327b152c205eceae129df555c24
|
||||
```
|
||||
|
||||
That is the TEN-WP-0009 PostgreSQL runtime pin, the same digest
|
||||
`deploy/base/tenant-engine.yaml` and `make verify-pin` use. Onboarding does
|
||||
not ship a newer image.
|
||||
|
||||
## Rollback ladder
|
||||
|
||||
| Digest | Role |
|
||||
| --- | --- |
|
||||
| `a8e8086ffc…` | current stable (PostgreSQL) |
|
||||
| `023ac49f5c…` | previous PostgreSQL image (leased-login ownership defect) |
|
||||
| `7dc9d7bf1f…` | last SQLite/PVC image — **not** a rollback target after any PostgreSQL write |
|
||||
|
||||
Older TEN-WP-0007 pins (`44ca65f3…` / `08be0b1d…` / `2249e8c6…`) remain
|
||||
the lifecycle/guardrail feature ladder: rolling past a pre-lifecycle
|
||||
digest removes routes `user-engine` depends on.
|
||||
|
||||
## Secrets
|
||||
|
||||
`secrets.references = []` is deliberate. Postgres URL and flex-auth
|
||||
caller token are projected by those platforms from OpenBao. This repo
|
||||
does not catalogue them.
|
||||
|
||||
## Criticality
|
||||
|
||||
`high`. Argument is in `[app].description`. Not `critical`: not
|
||||
internet-facing, holds no application secret, fail-closed on outage.
|
||||
Not `medium`: flex-auth and key-cape are live consumers.
|
||||
242
railiance/app.toml
Normal file
242
railiance/app.toml
Normal file
|
|
@ -0,0 +1,242 @@
|
|||
schema_version = "railiance.app.v1"
|
||||
|
||||
# TEN-WP-0008. Modelled on qonto-assistant (small headless service) for
|
||||
# stage commands, and on flex-auth for an empty secrets list and isolated
|
||||
# canary. Stage 2/3 *commands* are kubectl against this repo's kustomize
|
||||
# overlays. `bin/railiance deploy|promote --apply` currently requires a Helm
|
||||
# chart the schema does not; that gap is requested of railiance-bootstrap
|
||||
# rather than papered over with a dummy chart. See railiance/README.md.
|
||||
|
||||
[app]
|
||||
id = "tenant-engine"
|
||||
name = "tenant-engine"
|
||||
repo = "tenant-engine"
|
||||
owner = "infotech"
|
||||
criticality = "high"
|
||||
description = """
|
||||
Canonical PIP for tenant-as-an-entity facts. flex-auth consults it live on
|
||||
high-stakes decisions and key-cape consults it at token issuance. It holds
|
||||
no application secrets and is not internet-facing, but an outage makes the
|
||||
PDP fail closed fleet-wide. high, not critical: the PDP itself is high,
|
||||
this is a data source whose unavailability is deny rather than an
|
||||
unauthorized allow, and it does not hold a credential of its own.
|
||||
"""
|
||||
|
||||
[source]
|
||||
revision = "git:main"
|
||||
artifact = "image"
|
||||
digest_policy = "required"
|
||||
|
||||
[rollback]
|
||||
strategy = "image-digest"
|
||||
command = "kubectl -n tenant-engine rollout undo deploy/tenant-engine"
|
||||
verification = """
|
||||
make verify-pin agrees repo, spec, and running pod; GET /health returns
|
||||
200 with store_backend=postgresql; lifecycle routes still present.
|
||||
Do not roll back to sha256:7dc9d7bf1fcbb3f3ad7035366f4cd44038322995a126b4423142e446ca5c9cd5
|
||||
(SQLite/PVC) after any PostgreSQL write. The TEN-WP-0007 ladder still
|
||||
holds for older images: rolling past a pre-lifecycle digest removes
|
||||
GET/PATCH/retire/reactivate that user-engine depends on.
|
||||
"""
|
||||
|
||||
[[platform.dependencies]]
|
||||
name = "platform-pg"
|
||||
kind = "postgres"
|
||||
required = true
|
||||
stage = "stage2"
|
||||
evidence = "rapp-postgres consumer tenant-engine can reach platform-pg:5432; /health reports store_backend=postgresql."
|
||||
|
||||
[[platform.dependencies]]
|
||||
name = "flex-auth-tenant-engine"
|
||||
kind = "identity"
|
||||
required = true
|
||||
stage = "stage2"
|
||||
evidence = "http://flex-auth-tenant-engine.flex-auth.svc.cluster.local:8080/healthz returns 200."
|
||||
|
||||
[[platform.dependencies]]
|
||||
name = "forgejo-oci"
|
||||
kind = "other"
|
||||
required = true
|
||||
stage = "stage2"
|
||||
evidence = "Image forgejo.coulomb.social/coulomb/tenant-engine is pullable by digest."
|
||||
|
||||
[[platform.dependencies]]
|
||||
name = "railiance01-k3s"
|
||||
kind = "network"
|
||||
required = true
|
||||
stage = "stage2"
|
||||
evidence = "kubectl -n tenant-engine get ns reports Active on railiance01."
|
||||
|
||||
[secrets]
|
||||
# Runtime secret custody is OpenBao's. Postgres URL and flex-auth caller
|
||||
# token are projected files owned by those platforms, not entries this
|
||||
# repo catalogues. An empty list is the correct answer (TEN-WP-0008-T01).
|
||||
references = []
|
||||
|
||||
[[observability.health_endpoints]]
|
||||
name = "local-health"
|
||||
url = "http://127.0.0.1:8090/health"
|
||||
stage = "stage1"
|
||||
expected_status = 200
|
||||
|
||||
[[observability.health_endpoints]]
|
||||
name = "cluster-health"
|
||||
url = "http://tenant-engine.tenant-engine.svc.cluster.local:8090/health"
|
||||
stage = "stage2"
|
||||
expected_status = 200
|
||||
|
||||
[[observability.health_endpoints]]
|
||||
name = "canary-health"
|
||||
url = "http://tenant-engine-canary.tenant-engine.svc.cluster.local:8090/health"
|
||||
stage = "stage2"
|
||||
expected_status = 200
|
||||
|
||||
[stages.stage1]
|
||||
enabled = true
|
||||
namespace = "local"
|
||||
release = "tenant-engine-local"
|
||||
commands = [
|
||||
"make test",
|
||||
"make validate-app-toml",
|
||||
"kubectl kustomize deploy",
|
||||
"kubectl kustomize deploy/canary",
|
||||
]
|
||||
checks = ["unit-tests", "contract-schema", "kustomize-production", "kustomize-canary"]
|
||||
evidence = ["pytest output", "app.toml schema validation", "kustomize render of deploy/ and deploy/canary"]
|
||||
requires_approval = false
|
||||
|
||||
[stages.stage2]
|
||||
enabled = true
|
||||
namespace = "tenant-engine"
|
||||
release = "tenant-engine-canary"
|
||||
commands = [
|
||||
"kubectl apply -k deploy/canary --dry-run=server",
|
||||
"kubectl apply -k deploy/canary",
|
||||
]
|
||||
checks = ["server-dry-run", "canary-ready", "canary-health", "operator-approval"]
|
||||
evidence = ["server-side dry-run of isolated canary", "deploy/tenant-engine-canary Available", "canary /health 200", "State Hub approval id"]
|
||||
requires_approval = true
|
||||
canary_mode = "isolated"
|
||||
observation_minutes = 15
|
||||
|
||||
[stages.stage3]
|
||||
enabled = true
|
||||
namespace = "tenant-engine"
|
||||
release = "tenant-engine"
|
||||
commands = [
|
||||
"kubectl apply -k deploy --dry-run=server",
|
||||
"kubectl apply -k deploy",
|
||||
]
|
||||
checks = ["stage2-accepted", "rollback-target", "cluster-health", "operator-approval"]
|
||||
evidence = ["promotion commit", "new stable digest in previous_stable and deploy/base/tenant-engine.yaml", "post-promotion make verify-pin"]
|
||||
requires_approval = true
|
||||
promotion_mode = "release-replace"
|
||||
# TEN-WP-0008-T03: the contract's recorded stable, not only the manifest pin.
|
||||
# Current production (TEN-WP-0009 PostgreSQL runtime), CI-built from d2a7fe3.
|
||||
previous_stable = "tenant-engine@sha256:a8e8086ffc5b772c1391b166f5e1884b90f7d327b152c205eceae129df555c24"
|
||||
|
||||
[[checks]]
|
||||
id = "unit-tests"
|
||||
type = "command"
|
||||
stage = "stage1"
|
||||
description = "Run repository unit tests."
|
||||
required = true
|
||||
run = "make test"
|
||||
timeout_seconds = 600
|
||||
|
||||
[[checks]]
|
||||
id = "contract-schema"
|
||||
type = "command"
|
||||
stage = "stage1"
|
||||
description = "Validate railiance/app.toml against railiance.app.v1."
|
||||
required = true
|
||||
run = "make validate-app-toml"
|
||||
timeout_seconds = 60
|
||||
|
||||
[[checks]]
|
||||
id = "kustomize-production"
|
||||
type = "command"
|
||||
stage = "stage1"
|
||||
description = "Render the production kustomization locally, no cluster contact."
|
||||
required = true
|
||||
run = "kubectl kustomize deploy"
|
||||
|
||||
[[checks]]
|
||||
id = "kustomize-canary"
|
||||
type = "command"
|
||||
stage = "stage1"
|
||||
description = "Render the isolated canary kustomization locally, no cluster contact."
|
||||
required = true
|
||||
run = "kubectl kustomize deploy/canary"
|
||||
|
||||
[[checks]]
|
||||
id = "local-health"
|
||||
type = "http"
|
||||
stage = "stage1"
|
||||
description = "Confirm a locally served /health when one is running."
|
||||
required = false
|
||||
url = "http://127.0.0.1:8090/health"
|
||||
expected_status = 200
|
||||
timeout_seconds = 10
|
||||
|
||||
[[checks]]
|
||||
id = "server-dry-run"
|
||||
type = "command"
|
||||
stage = "stage2"
|
||||
description = "Server-side dry run of the isolated canary overlay before apply."
|
||||
required = true
|
||||
run = "kubectl apply -k deploy/canary --dry-run=server"
|
||||
|
||||
[[checks]]
|
||||
id = "canary-ready"
|
||||
type = "kubernetes"
|
||||
stage = "stage2"
|
||||
description = "Isolated canary Deployment reaches Available."
|
||||
required = true
|
||||
namespace = "tenant-engine"
|
||||
resource = "deploy/tenant-engine-canary"
|
||||
condition = "Available"
|
||||
|
||||
[[checks]]
|
||||
id = "canary-health"
|
||||
type = "http"
|
||||
stage = "stage2"
|
||||
description = "Isolated canary Service /health returns 200."
|
||||
required = true
|
||||
url = "http://tenant-engine-canary.tenant-engine.svc.cluster.local:8090/health"
|
||||
expected_status = 200
|
||||
timeout_seconds = 10
|
||||
|
||||
[[checks]]
|
||||
id = "cluster-health"
|
||||
type = "http"
|
||||
stage = "stage2"
|
||||
description = "Production Service /health returns 200."
|
||||
required = true
|
||||
url = "http://tenant-engine.tenant-engine.svc.cluster.local:8090/health"
|
||||
expected_status = 200
|
||||
timeout_seconds = 10
|
||||
|
||||
[[checks]]
|
||||
id = "operator-approval"
|
||||
type = "manual"
|
||||
stage = "stage2"
|
||||
description = "Human approval recorded before the isolated canary is applied and before the production pin moves."
|
||||
required = true
|
||||
evidence_required = "State Hub approval note id, candidate digest, rollback target (previous_stable)."
|
||||
|
||||
[[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 digest is recorded in previous_stable before promotion."
|
||||
required = true
|
||||
evidence_required = "tenant-engine image digest currently in previous_stable and deploy/base/tenant-engine.yaml."
|
||||
Loading…
Add table
Add a link
Reference in a new issue