Commit a3ae35f
fix(k8s): Service for BoJ to ClusterIP (HCG tier-2 E1 prereq) (#131)
## Summary
Per ADR-0004 §1 (http-capability-gateway tier-2 placement) and the Phase
E rollout-runbook §1.4 prereq #8, BoJ **MUST NOT be externally
addressable** when fronted by HCG. Previously `k8s/service.yaml`
declared `type: LoadBalancer`, exposing all four BoJ ports (REST 7700,
gRPC 7701, GraphQL 7702, SSE 7703) externally — defeating the runbook
§3.4 decommission invariant before Phase E even starts.
This is **action item #8** from the [Phase E consumer-side
audit](hyperpolymath/standards#100 (comment))
— companion to action #6 (`boj-server#130` Cowboy bind tightening). The
two layers together give defence in depth: BoJ binds loopback (#130)
**AND** the k8s Service does not expose it externally (this PR).
`Refs hyperpolymath/standards#100` (NOT Closes — joint-close is
owner-only).
`Refs hyperpolymath/standards#91`.
## What's in
| File | Change |
|---|---|
| `k8s/service.yaml` | `type: LoadBalancer` → `type: ClusterIP`. Header
comment explaining the Phase E posture. New annotations:
`hyperpolymath.dev/exposure: "internal-only"`,
`hyperpolymath.dev/external-via: "http-capability-gateway (tier-2)"`.
Ports 7700–7703 retained forward-compatibly. |
| `CHANGELOG.md` | New `### Changed` entry under `[Unreleased]`. |
Estate cross-check: `hypatia/*`, `rsr-certifier`, and `opsm-service` all
use `ClusterIP` for backend Services. The estate's
gateway-fronted-backend pattern is `Service: ClusterIP`. The only
`LoadBalancer` in the estate's k8s manifests today (besides the BoJ
Service this PR fixes) is `svalinn-gateway` — i.e. the *gateway* tier,
not a backend. This PR brings BoJ's Service in line with the estate
pattern.
## Why DRAFT
**Breaking for anyone running this manifest as-is with a LoadBalancer in
production.** I don't know whether a live LoadBalancer-fronted BoJ
deployment exists. Marking draft so the owner gates merge on confirming
no such deployment will break. If one exists, the migration path is
HCG-in-front (per the rollout-runbook §2).
For ad-hoc / dev / non-HCG-fronted deployments that need BoJ exposed
externally, the header comment in `service.yaml` shows the
kustomize/helm overlay recipe rather than editing the canonical
manifest:
```yaml
- op: replace
path: /spec/type
value: LoadBalancer # only valid for non-HCG-fronted deployments
```
## Test plan
- [x] YAML lints clean (manual review — single-document Service spec,
valid v1 API).
- [ ] CI green — governance / a2ml / k9 / hypatia all pass.
- [ ] Owner: confirm no live LoadBalancer-fronted BoJ deployment will
break, then flip from DRAFT to ready.
- [ ] Post-merge sanity: `kubectl apply -f k8s/service.yaml
--dry-run=client` accepts the spec; `kubectl describe svc/boj-server`
shows the new annotations.
## Risk
**Medium for the manifest, low for the codebase.** The codebase is
untouched — only k8s manifest. Breakage scope is limited to k8s
operators relying on the LoadBalancer external IP for BoJ; they get a
fixable Service-type override. No mix.exs / Elixir / Zig / Idris2 /
cartridge changes; CI should not show any logic regressions.
## Follow-ups not in this PR (audit residue)
- Action #7 — `stapeln.toml` `APP_HOST = "[::]"` → loopback (separate
concern: packager). Can pick up next.
- Optional: add a `NetworkPolicy` restricting BoJ pod ingress to only
the HCG pod. Defence-in-depth beyond ClusterIP. Not strictly required
for Phase E acceptance — Service-type is the controlling lever — but
worth filing as a follow-up issue.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1 parent 08703bd commit a3ae35f
2 files changed
Lines changed: 51 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
27 | 27 | | |
28 | 28 | | |
29 | 29 | | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
30 | 47 | | |
31 | 48 | | |
32 | 49 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
1 | 31 | | |
2 | 32 | | |
3 | 33 | | |
4 | 34 | | |
5 | 35 | | |
6 | 36 | | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
7 | 40 | | |
8 | 41 | | |
9 | 42 | | |
| |||
20 | 53 | | |
21 | 54 | | |
22 | 55 | | |
23 | | - | |
| 56 | + | |
0 commit comments