Skip to content

Commit 4683907

Browse files
hyperpolymathclaude
andcommitted
feat(container): add k9-svc deployment spec (Phase E E1; standards#100)
The integration plan in boj-server prescribes container/gateway-deploy.k9.ncl by exact path (`docs/integration/ http-capability-gateway-plan.md` § Phase E, E1), and the rollout runbook gates Phase E §2 staging cut-over on its existence (`docs/integration/ hcg-tier2-rollout-runbook.md` § 1.5 gateway-side prerequisites — the checkbox for this file was the open item). Shape mirrors the boj-server reference (`boj-server:container/ deploy.k9.ncl`) so the canonical k9-validate-action regex finds the top-level `pedigree = { ... metadata = { name = ..., version = ... } ... }` without traversing `let` bindings. The five-level pedigree (Snout / Scent / Leash / Gut / Muscle) is filled in for the gateway: Hunt security level, Linux + Podman target, MPL-2.0 licensed. Per-environment config is sourced from the Phase A contract (`http-capability-gateway-boj-contract.md` § 1, Transport): - staging: BACKEND_URL=http://127.0.0.1:7700 (TCP loopback to BoJ). - production: BACKEND_URL=http://unix:/run/boj/gnosis.sock:/ (loopback Unix domain socket; back-side TCP port never opened). Trust source defaults to "header" in staging per plan § E2, flipped to "mtls" in production after the §2.4 staging cert-rotation rehearsal. Three TLS material env vars (MTLS_CA_CERT_PATH, GATEWAY_CERT_PATH, GATEWAY_KEY_PATH) are declared so that a missing owner-supplied value fails the deploy precheck rather than silently disabling mTLS. `max_unavailable = 0` preserves the gateway's atomic policy-swap guarantee across replica churn. Failure mode `fail-closed` matches the boj-server `Trustfile.a2ml [SEAMS]` declaration for the gateway↔BoJ seam (`failure_mode: "fail-closed (circuit breaker)"`). Scripts are thin orchestration over the existing `Justfile` recipes (`just container-build`, `just container-up`, `just container-down`); the substantive rollback procedure lives in the runbook § 5 and is not duplicated here. The `signature` and `validation.checksum` fields are PLACEHOLDER — they are populated at cerro-torre `.ctp` signing time per rollout runbook § 1.5. This commit lands the declarative artefact; signing, staging soak, percentage split, and the final Trustfile flip remain owner-driven follow-ups. Refs hyperpolymath/standards#91 Refs hyperpolymath/standards#100 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent cb8ad46 commit 4683907

1 file changed

Lines changed: 261 additions & 0 deletions

File tree

container/gateway-deploy.k9.ncl

Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
K9!
2+
# SPDX-License-Identifier: MPL-2.0
3+
# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
4+
#
5+
# container/gateway-deploy.k9.ncl
6+
# http-capability-gateway — k9-svc deployment component (Hunt level)
7+
#
8+
# Phase E E1 deliverable for the BoJ tier-2 wiring channel
9+
# (hyperpolymath/standards#91; Phase E = standards#100).
10+
#
11+
# The wider integration plan (boj-server `docs/integration/
12+
# http-capability-gateway-plan.md` § Phase E, E1) prescribes this exact
13+
# path. Until this file existed, the gateway-side §1.5 prereq in the
14+
# rollout runbook (boj-server `docs/integration/hcg-tier2-rollout-runbook.md`)
15+
# was an open checkbox; landing it flips that single prereq, and only
16+
# that prereq — staging soak (§2), production split (§3) and the
17+
# Trustfile flip (§6.4) remain owner-driven follow-ups gated on real
18+
# infrastructure.
19+
#
20+
# Security Level: 'Hunt (requires cryptographic handshake for execution).
21+
#
22+
# WARNING: This component can execute shell commands when invoked
23+
# through k9-svc. It declares intent only; the Hunt-handshake gate
24+
# enforces authorisation at deploy time.
25+
#
26+
# Usage:
27+
# nickel typecheck container/gateway-deploy.k9.ncl
28+
# k9-svc validate container/gateway-deploy.k9.ncl
29+
# k9-svc deploy container/gateway-deploy.k9.ncl --env staging
30+
31+
# ─────────────────────────────────────────────────────────────────────
32+
# Deployment configuration
33+
# ─────────────────────────────────────────────────────────────────────
34+
#
35+
# Backend URL forms are sourced from the contract document
36+
# (boj-server `docs/integration/http-capability-gateway-boj-contract.md`
37+
# § 1, Transport): staging uses TCP loopback to BoJ on :7700,
38+
# production uses a loopback Unix domain socket. The gateway proxy
39+
# module currently supports a single `backend_url`; both forms below
40+
# are within that single-backend capability.
41+
#
42+
# Trust source defaults to `"header"` per plan § E2 ("Trust level
43+
# source: \"header\" initially, switched to \"mtls\" after Phase B
44+
# cert rotation runbook is exercised"). The switch to `"mtls"` is a
45+
# config flip, not a code change; it happens at the §2.4 rehearsal.
46+
let deployment = {
47+
environments = {
48+
staging = {
49+
replicas = 1,
50+
memory = "512Mi",
51+
cpu = "250m",
52+
image_tag = "staging",
53+
# Phase A contract § 1: staging gateway → BoJ over TCP loopback.
54+
backend_url = "http://127.0.0.1:7700",
55+
# Plan § E2: external port is gateway-owned, TLS for direct, or
56+
# 8080 when fronted by a Cloudflare Tunnel.
57+
port = 8443,
58+
trust_level_source = "header",
59+
},
60+
production = {
61+
replicas = 1,
62+
memory = "1Gi",
63+
cpu = "500m",
64+
image_tag = "latest",
65+
# Phase A contract § 1: production gateway → BoJ over a loopback
66+
# Unix domain socket; the back-side TCP port is not opened.
67+
backend_url = "http://unix:/run/boj/gnosis.sock:/",
68+
port = 8443,
69+
# Production trust source: Phase B mTLS (http-capability-gateway#10).
70+
# Flipped from `"header"` after the §2.4 staging cert-rotation
71+
# rehearsal completes per the rollout runbook.
72+
trust_level_source = "mtls",
73+
},
74+
},
75+
76+
container = {
77+
# Image is built from the in-tree Containerfile (multi-stage
78+
# release build). Cerro-torre signing into a `.ctp` bundle is the
79+
# remaining § 1.5 prereq tracked on standards#100; the bundle name
80+
# is `http-capability-gateway` regardless of registry destination.
81+
image = "ghcr.io/hyperpolymath/http-capability-gateway",
82+
# Container listens on 4000 internally (Containerfile EXPOSE 4000);
83+
# the environment's `port` above is the externally bound port.
84+
internal_port = 4000,
85+
health_check = "/health",
86+
readiness_check = "/ready",
87+
metrics_path = "/metrics",
88+
},
89+
90+
# Environment variables consumed by `config/runtime.exs`. Paths
91+
# marked `!OWNER:` in the rollout runbook § 1.3 are supplied at
92+
# deploy time, not committed here.
93+
env = {
94+
POLICY_PATH = "/etc/http-capability-gateway/policy.yaml",
95+
BACKEND_URL = "<from environments[<env>].backend_url>",
96+
PORT = "<from environments[<env>].port>",
97+
TRUST_LEVEL_SOURCE = "<from environments[<env>].trust_level_source>",
98+
# mTLS material — owner-provided at deploy time per rollout
99+
# runbook § 1.3. The keys are declared here so a missing value
100+
# fails the deploy precheck rather than silently disabling mTLS.
101+
MTLS_CA_CERT_PATH = "<owner-provided>",
102+
GATEWAY_CERT_PATH = "<owner-provided>",
103+
GATEWAY_KEY_PATH = "<owner-provided>",
104+
},
105+
106+
# Mounts: the policy YAML is delivered into the container read-only
107+
# per the policy-authoring workflow (boj-server `docs/integration/
108+
# http-capability-gateway-policy-authoring.md` § 3).
109+
mounts = [
110+
{
111+
name = "gateway-policy",
112+
source = "boj-server:config/gateway-policy-boj.yaml",
113+
target = "/etc/http-capability-gateway/policy.yaml",
114+
read_only = true,
115+
},
116+
{
117+
name = "mtls-material",
118+
source = "<owner-provided secret>",
119+
target = "/etc/http-capability-gateway/tls",
120+
read_only = true,
121+
},
122+
],
123+
124+
# Rolling strategy with max_unavailable = 0 preserves the
125+
# gateway's atomic policy-swap guarantee across replica churn:
126+
# a new replica with a new policy comes up before an old replica
127+
# is drained.
128+
strategy = {
129+
type = "rolling",
130+
max_surge = 1,
131+
max_unavailable = 0,
132+
},
133+
134+
# Failure mode contract: the gateway↔BoJ seam declares
135+
# `failure_mode: "fail-closed (circuit breaker)"` in the BoJ
136+
# `Trustfile.a2ml [SEAMS]` entry. If the BoJ backend becomes
137+
# unreachable the gateway's circuit breaker trips and returns 503;
138+
# this deployment spec does not paper over that with a retry loop.
139+
failure_mode = "fail-closed",
140+
} in
141+
142+
# ─────────────────────────────────────────────────────────────────────
143+
# Deployment scripts (executed at Hunt level)
144+
# ─────────────────────────────────────────────────────────────────────
145+
#
146+
# Shell-level recipes. The substantive logic lives in `Justfile`
147+
# (`just container-build`, `just container-up`) so these scripts are
148+
# thin orchestration. The pre-deploy script enforces that the policy
149+
# file mounted into the container loads + validates cleanly before
150+
# any traffic shift — the same `PolicyLoader.load_policy/1` +
151+
# `PolicyValidator.validate/1` pair the gateway runs at startup, so a
152+
# malformed policy never reaches a running container.
153+
let scripts = {
154+
pre_deploy = m%"
155+
#!/bin/sh
156+
set -eu
157+
echo "K9: Pre-deployment validation for http-capability-gateway..."
158+
just container-verify
159+
just policy-validate "${POLICY_FILE:-config/policy.yaml}"
160+
echo "K9: Validation passed."
161+
"%,
162+
163+
deploy = m%"
164+
#!/bin/sh
165+
set -eu
166+
ENV="${1:-staging}"
167+
echo "K9: Deploying http-capability-gateway to $ENV environment..."
168+
just container-build
169+
just container-up
170+
echo "K9: Deployment to $ENV complete."
171+
"%,
172+
173+
rollback = m%"
174+
#!/bin/sh
175+
set -eu
176+
echo "K9: Rolling back http-capability-gateway deployment..."
177+
just container-down
178+
echo "K9: Rollback complete (immediate-bypass)."
179+
echo "K9: See boj-server docs/integration/hcg-tier2-rollout-runbook.md § 5"
180+
echo "K9: for the full rollback procedure (immediate vs permanent)."
181+
"%,
182+
} in
183+
184+
# ─────────────────────────────────────────────────────────────────────
185+
# Top-level k9-svc component
186+
# ─────────────────────────────────────────────────────────────────────
187+
#
188+
# `pedigree` is inlined at the top level so the canonical
189+
# hyperpolymath/k9-validate-action regex finds
190+
# pedigree = { … metadata = { name = …, version = … } … }
191+
# without traversing `let` bindings — same shape as the boj-server
192+
# reference (`container/deploy.k9.ncl`).
193+
{
194+
pedigree = {
195+
schema_version = "1.0.0",
196+
component_type = "deployment-component",
197+
# L1: Snout — identity
198+
metadata = {
199+
name = "http-capability-gateway-deploy",
200+
version = "0.1.0",
201+
breed = "application/vnd.k9+nickel",
202+
magic_number = "K9!",
203+
description = "http-capability-gateway tier-2 deployment component (Hunt level)",
204+
},
205+
# L2: Scent — target environment
206+
target = {
207+
os = 'Linux,
208+
is_edge = false,
209+
requires_podman = true,
210+
min_memory_mb = 512,
211+
},
212+
# L3: Leash — security
213+
security = {
214+
leash = 'Hunt,
215+
trust_level = 'Hunt,
216+
allow_network = true,
217+
allow_filesystem_write = true,
218+
allow_subprocess = true,
219+
# Real Ed25519 signature is added at bundle-signing time
220+
# (cerro-torre `.ctp` step, rollout runbook § 1.5).
221+
signature = "PLACEHOLDER-SIGNATURE-REQUIRED-FOR-HUNT",
222+
},
223+
# L4: Gut — self-validation
224+
validation = {
225+
checksum = "sha256:placeholder",
226+
pedigree_version = "1.0.0",
227+
hunt_authorized = false,
228+
},
229+
# L5: Muscle — deployment recipes
230+
recipes = {
231+
install = "just container-build",
232+
validate = "just container-verify",
233+
deploy = "just container-up",
234+
migrate = "just container-build && just container-up",
235+
},
236+
},
237+
238+
deployment = deployment,
239+
scripts = scripts,
240+
241+
required_level = 'Hunt,
242+
243+
warning = m%"
244+
WARNING: This is a Hunt-level component.
245+
246+
It can execute shell commands and modify your system.
247+
Before running, ensure you have:
248+
249+
1. Reviewed the deployment scripts above.
250+
2. Verified the signature (added at cerro-torre `.ctp` signing).
251+
3. Explicitly authorised Hunt-level execution.
252+
253+
Run with:
254+
k9-svc authorize container/gateway-deploy.k9.ncl &&
255+
k9-svc deploy container/gateway-deploy.k9.ncl
256+
257+
The rollback procedure is in boj-server docs/integration/
258+
hcg-tier2-rollout-runbook.md § 5; the script in this file only
259+
covers the immediate-bypass step.
260+
"%,
261+
}

0 commit comments

Comments
 (0)