From ccb5972b42116b6c0bc45c81a6b5079df74b6385 Mon Sep 17 00:00:00 2001 From: codex Date: Fri, 14 Aug 2026 00:39:06 +0200 Subject: [PATCH] Close RAILIANCE-WP-0015-T02 platform rapp schema convergence Converge the S3 platform-service pattern onto ADR-0007, emit the reef-railiance live deployable inventory for the family coverage check, and mark T02 done. Declaration edits land in rapp-openbao and rapp-postgres. --- Makefile | 7 +- docs/evidence/reef-railiance-deployables.json | 222 ++++++++++++++++++ docs/rapp-platform-service-pattern.md | 56 ++++- scripts/capture-reef-deployables.py | 143 +++++++++++ ...IANCE-WP-0015-platform-rapp-consistency.md | 22 +- 5 files changed, 430 insertions(+), 20 deletions(-) create mode 100644 docs/evidence/reef-railiance-deployables.json create mode 100755 scripts/capture-reef-deployables.py diff --git a/Makefile b/Makefile index 238e6d4..1527bc7 100644 --- a/Makefile +++ b/Makefile @@ -381,6 +381,11 @@ forgejo-package-prune-dry-run: ## List Forgejo package versions beyond retention forgejo-package-prune: ## Prune Forgejo packages — keep newest 3 versions per package tools/cmd/forgejo-package-prune --apply +##@ Family declarations + +reef-deployables: ## Capture live reef-railiance deployables for the family validator + python3 scripts/capture-reef-deployables.py -o docs/evidence/reef-railiance-deployables.json + ##@ Help help: ## Show this help @@ -388,4 +393,4 @@ help: ## Show this help /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-22s\033[0m %s\n", $$1, $$2 } \ /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) }' $(MAKEFILE_LIST) -.PHONY: db-deploy db-status db-shell db-logs forgejo-db-deploy forgejo-db-status forgejo-db-shell apps-pg-deploy apps-pg-status apps-pg-shell apps-pg-logs net-kingdom-pg-inter-hub-networkpolicy-deploy pg-deploy pg-status pg-pgpool-check valkey-deploy valkey-status openbao-repo openbao-dry-run openbao-overlay-apply openbao-verify-login-overlay openbao-deploy openbao-status openbao-verify openbao-verify-post-unseal openbao-configure-initial openbao-configure-ssh openbao-verify-ssh openbao-verify-authenticated openbao-configure-external-secrets-issue-core openbao-configure-external-secrets-activity-core openbao-configure-external-secrets-forgejo openbao-validate-restore-evidence openbao-validate-emergency-evidence credential-grants-validate credential-change-applier-dry-run credential-change-applier-apply-plan credential-change-applier-apply credential-change-runbook credential-change-record-evidence credential-change-lifecycle-plan credential-change-lifecycle-event credential-change-import-inventory openbao-credential-change-appliers-dry-run openbao-configure-credential-change-appliers openbao-token-grants-dry-run openbao-configure-token-grants openbao-verify-token-grants-dry-run openbao-verify-token-grants openbao-verify-token-grants-smoke credential-helper-dry-run credential-tests credential-exec-ops-warden-smoke argocd-bootstrap-dry-run argocd-bootstrap-deploy argocd-repo-apply argocd-status backup forgejo-backup forgejo-backup-dry-run forgejo-backup-status forgejo-package-prune forgejo-package-prune-dry-run help +.PHONY: db-deploy db-status db-shell db-logs forgejo-db-deploy forgejo-db-status forgejo-db-shell apps-pg-deploy apps-pg-status apps-pg-shell apps-pg-logs net-kingdom-pg-inter-hub-networkpolicy-deploy pg-deploy pg-status pg-pgpool-check valkey-deploy valkey-status openbao-repo openbao-dry-run openbao-overlay-apply openbao-verify-login-overlay openbao-deploy openbao-status openbao-verify openbao-verify-post-unseal openbao-configure-initial openbao-configure-ssh openbao-verify-ssh openbao-verify-authenticated openbao-configure-external-secrets-issue-core openbao-configure-external-secrets-activity-core openbao-configure-external-secrets-forgejo openbao-validate-restore-evidence openbao-validate-emergency-evidence credential-grants-validate credential-change-applier-dry-run credential-change-applier-apply-plan credential-change-applier-apply credential-change-runbook credential-change-record-evidence credential-change-lifecycle-plan credential-change-lifecycle-event credential-change-import-inventory openbao-credential-change-appliers-dry-run openbao-configure-credential-change-appliers openbao-token-grants-dry-run openbao-configure-token-grants openbao-verify-token-grants-dry-run openbao-verify-token-grants openbao-verify-token-grants-smoke credential-helper-dry-run credential-tests credential-exec-ops-warden-smoke argocd-bootstrap-dry-run argocd-bootstrap-deploy argocd-repo-apply argocd-status backup forgejo-backup forgejo-backup-dry-run forgejo-backup-status forgejo-package-prune forgejo-package-prune-dry-run reef-deployables help diff --git a/docs/evidence/reef-railiance-deployables.json b/docs/evidence/reef-railiance-deployables.json new file mode 100644 index 0000000..84eeded --- /dev/null +++ b/docs/evidence/reef-railiance-deployables.json @@ -0,0 +1,222 @@ +{ + "source": "railiance-platform", + "captured_at": "2026-08-13T22:37:46Z", + "reef": "reef-railiance", + "deployables": [ + { + "name": "actcore-api", + "namespace": "activity-core", + "kind": "Deployment" + }, + { + "name": "actcore-app-db", + "namespace": "activity-core", + "kind": "StatefulSet" + }, + { + "name": "actcore-event-router", + "namespace": "activity-core", + "kind": "Deployment" + }, + { + "name": "actcore-issue-core-bridge", + "namespace": "activity-core", + "kind": "Deployment" + }, + { + "name": "actcore-nats", + "namespace": "activity-core", + "kind": "StatefulSet" + }, + { + "name": "actcore-statehub-edge-relay", + "namespace": "activity-core", + "kind": "Deployment" + }, + { + "name": "actcore-temporal", + "namespace": "activity-core", + "kind": "Deployment" + }, + { + "name": "actcore-temporal-db", + "namespace": "activity-core", + "kind": "StatefulSet" + }, + { + "name": "actcore-temporal-ui", + "namespace": "activity-core", + "kind": "Deployment" + }, + { + "name": "actcore-worker", + "namespace": "activity-core", + "kind": "Deployment" + }, + { + "name": "llm-connect", + "namespace": "activity-core", + "kind": "Deployment" + }, + { + "name": "audit-core", + "namespace": "audit-core", + "kind": "Deployment" + }, + { + "name": "ihp-railiance-probe", + "namespace": "coulomb", + "kind": "Deployment" + }, + { + "name": "coulomb-social", + "namespace": "coulomb-social", + "kind": "Deployment" + }, + { + "name": "apps-pg", + "namespace": "databases", + "kind": "Cluster" + }, + { + "name": "forgejo-db", + "namespace": "databases", + "kind": "Cluster" + }, + { + "name": "net-kingdom-pg", + "namespace": "databases", + "kind": "Cluster" + }, + { + "name": "platform-pg", + "namespace": "databases", + "kind": "Cluster" + }, + { + "name": "state-hub-db", + "namespace": "databases", + "kind": "Cluster" + }, + { + "name": "email-connect", + "namespace": "email-connect", + "kind": "Deployment" + }, + { + "name": "flex-auth-tenant-engine", + "namespace": "flex-auth", + "kind": "Deployment" + }, + { + "name": "flex-auth-user-engine", + "namespace": "flex-auth", + "kind": "Deployment" + }, + { + "name": "forgejo-gitea", + "namespace": "forgejo", + "kind": "Deployment" + }, + { + "name": "forgejo-runner", + "namespace": "forgejo", + "kind": "Deployment" + }, + { + "name": "inter-hub", + "namespace": "inter-hub", + "kind": "Deployment" + }, + { + "name": "privacyidea", + "namespace": "mfa", + "kind": "Deployment" + }, + { + "name": "openbao", + "namespace": "openbao", + "kind": "StatefulSet" + }, + { + "name": "openbao-ui-gateway", + "namespace": "openbao", + "kind": "Deployment" + }, + { + "name": "rapp-qonto", + "namespace": "rapp-qonto", + "kind": "KnativeService" + }, + { + "name": "qonto-egress-proxy", + "namespace": "rapp-qonto-egress", + "kind": "Deployment" + }, + { + "name": "rein-aharness", + "namespace": "rein-aharness", + "kind": "Deployment" + }, + { + "name": "reuse-surface", + "namespace": "reuse", + "kind": "Deployment" + }, + { + "name": "reuse-surface-landing", + "namespace": "reuse", + "kind": "Deployment" + }, + { + "name": "authelia", + "namespace": "sso", + "kind": "Deployment" + }, + { + "name": "identity-provisioner", + "namespace": "sso", + "kind": "Deployment" + }, + { + "name": "keycape", + "namespace": "sso", + "kind": "Deployment" + }, + { + "name": "lldap", + "namespace": "sso", + "kind": "Deployment" + }, + { + "name": "state-hub", + "namespace": "state-hub", + "kind": "Deployment" + }, + { + "name": "target-revenue", + "namespace": "target-revenue", + "kind": "Deployment" + }, + { + "name": "target-revenue-pg", + "namespace": "target-revenue", + "kind": "Cluster" + }, + { + "name": "tenant-engine", + "namespace": "tenant-engine", + "kind": "Deployment" + }, + { + "name": "user-engine", + "namespace": "user-engine", + "kind": "Deployment" + }, + { + "name": "user-engine-pg", + "namespace": "user-engine", + "kind": "Cluster" + } + ] +} diff --git a/docs/rapp-platform-service-pattern.md b/docs/rapp-platform-service-pattern.md index b104e56..a919a6d 100644 --- a/docs/rapp-platform-service-pattern.md +++ b/docs/rapp-platform-service-pattern.md @@ -70,10 +70,9 @@ applies decides it. ## Reference `rapp.yaml` for a platform service -Pending the normative schema from `railiance-master`, a platform-service rapp -should carry at least the following. Fields marked † are the consistency fields -currently present in `rail.yaml` and `rapp-qonto` but missing from both platform -rapps; `RAILIANCE-WP-0015-T02` adds them once the schema settles. +The normative shape is `railiance-master/schemas/rapp.schema.json` (ADR-0007). +A platform-service rapp fills that schema as follows. `RAILIANCE-WP-0015-T02` +converged `rapp-openbao` and `rapp-postgres` onto this shape. ```yaml kind: managed-workload-package @@ -81,10 +80,10 @@ repo_family: rapp rapp_id: rapp- repo: rapp- ownership_repo: railiance-platform # the S3 home retaining governance -contract_version: 1.0.0 # † -readiness_state: verified # † -data_classification: # † -criticality: # † +contract_version: 1.0.0 +readiness_state: verified +data_classification: +criticality: workload_identity: name: # the workload, never the repo name package_type: helm-managed-platform-service @@ -93,10 +92,27 @@ workload_identity: app_version: primary_rail: rail-kubernetes supported_rails: [rail-kubernetes] -runtime_dependencies: [...] # what must exist for this to run -rollout_contract: {...} -smoke_contract: {...} -rollback_contract: {...} +bound_reefs: [reef-railiance] # empty only if the package is not placed +runtime_dependencies: [...] # capabilities, not the resource that satisfies them +composition: + purpose: + member_repos: + - repo: rapp- # single-repo rapp: one member, itself + role: + deployables: [] + upstream_components: + - name: + source: + version: +rollout_contract: + default_mode: + commands: [...] # renamed from supported_commands +smoke_contract: + required: [...] # outcomes the commands establish + commands: [...] +rollback_contract: + order: [...] # most-preferred strategy first + commands: [...] source_documents: [...] ``` @@ -147,7 +163,7 @@ its rollout. That distinction is what makes the coverage check well-defined: A rapp declares what it combines: first-party repos and pinned third-party components, plus the purpose the combination serves. The normative shape is -`railiance-master`'s to settle; the proposed form is: +`railiance-master/schemas/rapp.schema.json`; the form is: ```yaml composition: @@ -187,6 +203,20 @@ The existing rule holds without exception: the package never commits credentials, and a workload receives a short-lived lease through the platform broker rather than a package-managed secret. +## Live deployable inventory + +`railiance-master` coverage check (`tools/validate-family-declarations.py +--inventory`) must not query the cluster. S3 emits the interchange file: + +```text +docs/evidence/reef-railiance-deployables.json +``` + +Refresh with `scripts/capture-reef-deployables.py -o docs/evidence/reef-railiance-deployables.json`. +The file lists non-substrate Deployments, StatefulSets, CNPG Clusters, and +Knative Services on `reef-railiance`. Declared `composition.member_repos.deployables` +names must match these live names. + ## When a platform service earns a rapp Not every S3 service needs one. A platform service is ready for extraction when diff --git a/scripts/capture-reef-deployables.py b/scripts/capture-reef-deployables.py new file mode 100755 index 0000000..268c94a --- /dev/null +++ b/scripts/capture-reef-deployables.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python3 +"""Capture live reef-railiance deployables for the family-declaration validator. + +The validator in railiance-master consumes this file via +`--inventory` and must not grow a cluster dependency of its own. +Substrate namespaces and Knative revision Deployments are omitted so +the coverage check sees workload units, not rail machinery. +""" +from __future__ import annotations + +import argparse +import json +import re +import subprocess +import sys +from datetime import datetime, timezone +from typing import Any + +DEFAULT_REMOTE = "railiance01" +DEFAULT_REEF = "reef-railiance" +SOURCE = "railiance-platform" + +SUBSTRATE_NAMESPACES = { + "cert-manager", + "cert-manager-test", + "cnpg-system", + "default", + "external-secrets", + "knative-serving", + "kube-node-lease", + "kube-public", + "kube-system", + "kourier-system", + "local-path-storage", +} + +EPHEMERAL_NAMESPACE_SUFFIXES = ("-drill", "-test") +KNATIVE_REVISION_DEPLOY = re.compile(r".*-\d{5}-deployment$") + +RESOURCE_SPECS = ( + ("Deployment", "deploy"), + ("StatefulSet", "sts"), + ("Cluster", "cluster.postgresql.cnpg.io"), + ("KnativeService", "service.serving.knative.dev"), +) + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description=__doc__.split("\n\n", 1)[0]) + parser.add_argument( + "--remote", + default=DEFAULT_REMOTE, + help=f"SSH host that can reach the reef API (default: {DEFAULT_REMOTE})", + ) + parser.add_argument( + "--reef", + default=DEFAULT_REEF, + help=f"Reef identifier recorded in the inventory (default: {DEFAULT_REEF})", + ) + parser.add_argument( + "-o", + "--output", + help="Write JSON to this path instead of stdout", + ) + return parser.parse_args() + + +def remote_get(remote: str, resource: str) -> dict[str, Any]: + cmd = [ + "ssh", + "-o", + "BatchMode=yes", + remote, + "kubectl", + "get", + resource, + "-A", + "-o", + "json", + ] + result = subprocess.run(cmd, check=False, capture_output=True, text=True) + if result.returncode != 0: + stderr = (result.stderr or "").strip() + if "the server doesn't have a resource type" in stderr: + return {"items": []} + raise RuntimeError(f"{' '.join(cmd)} failed ({result.returncode}): {stderr}") + return json.loads(result.stdout) + + +def skip_namespace(name: str) -> bool: + if name in SUBSTRATE_NAMESPACES: + return True + return any(name.endswith(suffix) for suffix in EPHEMERAL_NAMESPACE_SUFFIXES) + + +def collect(remote: str) -> list[dict[str, str]]: + seen: set[tuple[str, str, str]] = set() + deployables: list[dict[str, str]] = [] + for kind, resource in RESOURCE_SPECS: + payload = remote_get(remote, resource) + for item in payload.get("items") or []: + meta = item.get("metadata") or {} + name = meta.get("name") + namespace = meta.get("namespace") or "" + if not isinstance(name, str) or not name: + continue + if skip_namespace(namespace): + continue + if kind == "Deployment" and KNATIVE_REVISION_DEPLOY.fullmatch(name): + continue + key = (name, namespace, kind) + if key in seen: + continue + seen.add(key) + deployables.append({"name": name, "namespace": namespace, "kind": kind}) + deployables.sort(key=lambda row: (row["namespace"], row["name"], row["kind"])) + return deployables + + +def main() -> int: + args = parse_args() + try: + deployables = collect(args.remote) + except (RuntimeError, json.JSONDecodeError) as exc: + print(f"capture failed: {exc}", file=sys.stderr) + return 1 + inventory = { + "source": SOURCE, + "captured_at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), + "reef": args.reef, + "deployables": deployables, + } + text = json.dumps(inventory, indent=2) + "\n" + if args.output: + with open(args.output, "w", encoding="utf-8") as fh: + fh.write(text) + else: + sys.stdout.write(text) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/workplans/RAILIANCE-WP-0015-platform-rapp-consistency.md b/workplans/RAILIANCE-WP-0015-platform-rapp-consistency.md index afa3d13..700c953 100644 --- a/workplans/RAILIANCE-WP-0015-platform-rapp-consistency.md +++ b/workplans/RAILIANCE-WP-0015-platform-rapp-consistency.md @@ -8,7 +8,7 @@ status: active owner: codex topic_slug: railiance created: "2026-08-11" -updated: "2026-08-11" +updated: "2026-08-13" related_repos: - railiance-master - the-custodian @@ -119,7 +119,7 @@ inventing a third boundary vocabulary. ```task id: RAILIANCE-WP-0015-T02 -status: todo +status: done priority: high state_hub_task_id: "eada3a6f-9f4e-47bc-bfe5-2ec585f5d312" ``` @@ -189,7 +189,17 @@ the existing lane, it does not build a parallel one. ## Outcome Session of 2026-08-11: T01 published the S3 platform-service pattern; T03, T04 -and T05 routed their proposals to the owning repos (reef-railiance -f0f44ce0, railiance-master 04c776c4, the-custodian cc64764a). T02 is -deliberately held until railiance-master settles the schema, so the platform -rapps and the schema do not converge on different answers. T06 is open. +and T05 routed their proposals to the owning repos (reef-railiance +f0f44ce0, railiance-master 04c776c4, the-custodian cc64764a). T02 was +held until railiance-master settled the schema. + +Session of 2026-08-13: T02 done. `rapp-openbao` and `rapp-postgres` +`declarations/rapp.yaml` now pass `schemas/rapp.schema.json` (contract +metadata, `composition`, `bound_reefs: [reef-railiance]`, `commands`, +`smoke_contract.required`, `rollback_contract.order`). Live inventory for +the T06 coverage check is +`docs/evidence/reef-railiance-deployables.json`, refreshable with +`scripts/capture-reef-deployables.py`. Family-validator remainder is +outside this task: `rapp-qonto` still on the pre-schema shape, three +undeclared `rapp-*` engine repos, and `reef-railiance` `bound_rapps` +still hand-listed. T06 is open.