Enforce private-by-default enablement templates
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a02669-87ee-7a31-b111-edc95a16e0fa
This commit is contained in:
tegwick 2026-08-22 12:34:25 +02:00
parent abe1865877
commit 27ac54b32d
9 changed files with 138 additions and 28 deletions

View file

@ -108,26 +108,25 @@ This layer is:
* a **promotion handoff** surface between source repos and S5 deployments * a **promotion handoff** surface between source repos and S5 deployments
* a future **developer portal** and self-service entry point * a future **developer portal** and self-service entry point
* a place to codify delivery knowledge into **repeatable automation** * a place to codify delivery knowledge into **repeatable automation**
* as of 2026-08-11, the owner of the **forge layer responsibility**: runner * the owner of the **consumer-facing forge contract**: which runner labels,
placement and labels, registry retention, artifact lifecycle, and package registry interfaces, credentials, and artifact evidence paved paths require;
credentials `railiance-forge` remains the operational provider of those capabilities
--- ---
## Inherited: the forge layer responsibility ## Forge accountability and operating boundary
Decision `d151d817` (2026-08-11) placed `railiance-forge`. The Forgejo Decision `d151d817` (2026-08-11) placed the consumer-facing forge
**workload** becomes `rapp-forgejo`; the **layer responsibility** folds into S4 responsibility in S4. That means this repo owns the reusable workflow contract
— here — because this layer already declared a handoff contract with forge for for runner labels, package credentials, registry endpoints, and artifact
runner labels, package credentials, registry endpoints, and artifact evidence. evidence. It does not make this repo the operator of those systems:
Owning the contract's other side is the natural resolution. `railiance-forge` continues to own the live forge, registries, runners,
credentials, retention, and operating evidence until a separately approved
migration changes that boundary. The Forgejo workload itself belongs in
`rapp-forgejo`.
This is S4's first concrete owned responsibility. That is worth stating plainly: The split is deliberate: S4 makes the correct delivery path reusable;
until now this layer has been almost entirely aspiration — a rich Direction of `railiance-forge` makes the underlying capability dependable.
Evolution with 25 commits and no workplans behind it. The forge responsibility
gives it something real to be accountable for, and the gap between what this
INTENT promises and what the repo contains should be read as an open debt, not
as a description of the present.
--- ---

View file

@ -3,3 +3,11 @@ SHELL := /usr/bin/env bash
help: ## Show this help help: ## Show this help
@grep -E '^[a-zA-Z0-9_-]+:.*?## ' $(MAKEFILE_LIST) | sort | sed 's/:.*##/: /' @grep -E '^[a-zA-Z0-9_-]+:.*?## ' $(MAKEFILE_LIST) | sort | sed 's/:.*##/: /'
check: ## Verify workflow templates remain private and deployment-free
python3 tools/check_private_defaults.py
test: ## Run enablement regression tests
python3 -m unittest discover -s tests -v
.PHONY: help check test

View file

@ -74,13 +74,11 @@ services. S5 applications consume S4 templates and conventions, while
## Current State ## Current State
- Status: emerging — **now owns the forge layer responsibility** (decision - Status: emerging, with reusable Forgejo workflow templates and an active
`d151d817`, 2026-08-11), which is S4's first concrete owned accountability; private-by-default template workplan.
still no S4-owned workplans - S4 owns the consumer-facing contract for runner labels, registries,
- Inherited from `railiance-forge`: runner placement and labels, registry credentials, and artifact evidence. `railiance-forge` continues to operate
retention, artifact lifecycle, package credentials. The Forgejo **workload** those capabilities; they are not duplicated here.
becomes `rapp-forgejo`; this layer takes the **layer** responsibility, because
it already declared the handoff contract for exactly those concerns
- Implementation: ArgoCD is deployed in the `argocd` namespace on CoulombCore as - Implementation: ArgoCD is deployed in the `argocd` namespace on CoulombCore as
a cluster addon managed from S2 — verified still true 2026-08-12 a cluster addon managed from S2 — verified still true 2026-08-12
- **But GitOps does not reach the live cluster.** ArgoCD is not deployed on - **But GitOps does not reach the live cluster.** ArgoCD is not deployed on
@ -151,5 +149,5 @@ keywords: [template, sdk, helm, deployment, developer, buildpack]
## Getting Oriented ## Getting Oriented
- Start with: `CLAUDE.md` (session protocol, OAS boundary rules) - Start with: `CLAUDE.md` (session protocol, OAS boundary rules)
- Key files / directories: `workplans/` (empty), `Makefile` - Key files / directories: `workflows/`, `docs/`, `workplans/`, `Makefile`
- Pre-conditions: railiance-platform (S3) must be operational - Pre-conditions: railiance-platform (S3) must be operational

View file

@ -8,5 +8,5 @@
| Kind | ID | Status | Lane | Source | | Kind | ID | Status | Lane | Source |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| workplan | RAIL-EN-WP-0001 | ready | — | workplans/RAIL-EN-WP-0001-private-by-default-templates.md | | workplan | RAIL-EN-WP-0001 | finished | — | workplans/RAIL-EN-WP-0001-private-by-default-templates.md |
| task | RAIL-EN-WP-0001-T01 | todo | — | workplans/RAIL-EN-WP-0001-private-by-default-templates.md | | task | RAIL-EN-WP-0001-T01 | done | — | workplans/RAIL-EN-WP-0001-private-by-default-templates.md |

View file

@ -0,0 +1,16 @@
# Private-by-default template contract
Railiance enablement templates produce build and promotion evidence; they do
not create a public listener. ADR-0008 is enforced by the execution rail:
- Kubernetes workload scaffolding is owned by `rail-kubernetes`.
- Its default Service is `ClusterIP`, its generated ingress policy is
default-deny, and its Stage 2 values do not enable Ingress.
- A public Ingress requires matching rapp and reef declarations at the rail's
deploy gate. An Ingress object or successful deployment is not a grant.
- Operator access uses the named tunnel documented by the owning rail or rapp.
`make check` rejects enablement workflow templates that embed Ingress,
LoadBalancer, NodePort, or direct Kubernetes/Helm deployment commands. This
keeps reusable build workflows from becoming an accidental application or
cluster deployment owner.

View file

@ -0,0 +1,35 @@
from __future__ import annotations
import sys
import tempfile
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "tools"))
from check_private_defaults import violations # noqa: E402
class PrivateDefaultsTests(unittest.TestCase):
def check(self, text: str) -> list[str]:
with tempfile.TemporaryDirectory() as temp:
path = Path(temp) / "template.yaml"
path.write_text(text, encoding="utf-8")
return violations([path])
def test_build_workflow_is_allowed(self) -> None:
self.assertEqual([], self.check("jobs:\n build:\n runs-on: container-build\n"))
def test_ingress_is_rejected(self) -> None:
self.assertTrue(self.check("apiVersion: networking.k8s.io/v1\nkind: Ingress\n"))
def test_public_service_is_rejected(self) -> None:
self.assertTrue(self.check("kind: Service\nspec:\n type: LoadBalancer\n"))
def test_direct_apply_is_rejected(self) -> None:
self.assertTrue(self.check("run: kubectl apply -f deployment.yaml\n"))
if __name__ == "__main__":
unittest.main()

44
tools/check_private_defaults.py Executable file
View file

@ -0,0 +1,44 @@
#!/usr/bin/env python3
"""Fail when an enablement template creates a public Kubernetes path."""
from __future__ import annotations
import argparse
import re
import sys
from pathlib import Path
FORBIDDEN = {
"Ingress resource": re.compile(r"(?m)^\s*kind:\s*(Ingress|IngressRoute)\s*$"),
"public Service type": re.compile(r"(?m)^\s*type:\s*(LoadBalancer|NodePort)\s*$"),
"direct kubectl apply": re.compile(r"\bkubectl\s+(?:[^\n]*\s)?apply\b"),
"direct helm deployment": re.compile(r"\bhelm\s+(upgrade|install)\b"),
}
def violations(paths: list[Path]) -> list[str]:
found: list[str] = []
for path in paths:
text = path.read_text(encoding="utf-8")
for label, pattern in FORBIDDEN.items():
if pattern.search(text):
found.append(f"{path}: {label}")
return found
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("paths", nargs="*", type=Path)
args = parser.parse_args()
paths = args.paths or sorted(Path("workflows").glob("*.yaml"))
problems = violations(paths)
if problems:
print("\n".join(problems), file=sys.stderr)
return 1
print(f"private-default template check passed: {len(paths)} file(s)")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -4,11 +4,11 @@ type: workplan
title: "Templates emit private Services, never a public Ingress by default" title: "Templates emit private Services, never a public Ingress by default"
domain: financials domain: financials
repo: railiance-enablement repo: railiance-enablement
status: ready status: finished
owner: codex owner: codex
topic_slug: railiance topic_slug: railiance
created: "2026-08-15" created: "2026-08-15"
updated: "2026-08-15" updated: "2026-08-22"
related: related:
- RMASTER-WP-0023 - RMASTER-WP-0023
- ADR-0008 - ADR-0008
@ -29,7 +29,7 @@ They never emit a public Ingress by default. If a template mentions
```task ```task
id: RAIL-EN-WP-0001-T01 id: RAIL-EN-WP-0001-T01
status: todo status: done
priority: high priority: high
state_hub_task_id: "ceea9e5a-385e-4f13-b8ea-0b1e72cddaee" state_hub_task_id: "ceea9e5a-385e-4f13-b8ea-0b1e72cddaee"
``` ```
@ -40,3 +40,13 @@ that cites ADR-0008.
**Done when:** a new package from the paved path is private unless the **Done when:** a new package from the paved path is private unless the
author adds an explicit grant. author adds an explicit grant.
**Outcome (2026-08-22):** all reusable workflow templates are checked by
`tools/check_private_defaults.py`. The check rejects Ingress/IngressRoute,
LoadBalancer/NodePort Services, and direct deployment commands. The documented
template contract points public exposure through the rail-owned ADR-0008 gate.
## Completion evidence
- `make check`: 4 workflow templates passed.
- `make test`: 4 regression tests passed.