From 4683907bd63c17dd70bdbe988dd18eab87d64a74 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Wed, 3 Jun 2026 06:14:42 +0000 Subject: [PATCH 1/2] feat(container): add k9-svc deployment spec (Phase E E1; standards#100) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- container/gateway-deploy.k9.ncl | 261 ++++++++++++++++++++++++++++++++ 1 file changed, 261 insertions(+) create mode 100644 container/gateway-deploy.k9.ncl diff --git a/container/gateway-deploy.k9.ncl b/container/gateway-deploy.k9.ncl new file mode 100644 index 0000000..bbac529 --- /dev/null +++ b/container/gateway-deploy.k9.ncl @@ -0,0 +1,261 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 +# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +# +# container/gateway-deploy.k9.ncl +# http-capability-gateway — k9-svc deployment component (Hunt level) +# +# Phase E E1 deliverable for the BoJ tier-2 wiring channel +# (hyperpolymath/standards#91; Phase E = standards#100). +# +# The wider integration plan (boj-server `docs/integration/ +# http-capability-gateway-plan.md` § Phase E, E1) prescribes this exact +# path. Until this file existed, the gateway-side §1.5 prereq in the +# rollout runbook (boj-server `docs/integration/hcg-tier2-rollout-runbook.md`) +# was an open checkbox; landing it flips that single prereq, and only +# that prereq — staging soak (§2), production split (§3) and the +# Trustfile flip (§6.4) remain owner-driven follow-ups gated on real +# infrastructure. +# +# Security Level: 'Hunt (requires cryptographic handshake for execution). +# +# WARNING: This component can execute shell commands when invoked +# through k9-svc. It declares intent only; the Hunt-handshake gate +# enforces authorisation at deploy time. +# +# Usage: +# nickel typecheck container/gateway-deploy.k9.ncl +# k9-svc validate container/gateway-deploy.k9.ncl +# k9-svc deploy container/gateway-deploy.k9.ncl --env staging + +# ───────────────────────────────────────────────────────────────────── +# Deployment configuration +# ───────────────────────────────────────────────────────────────────── +# +# Backend URL forms are sourced from the contract document +# (boj-server `docs/integration/http-capability-gateway-boj-contract.md` +# § 1, Transport): staging uses TCP loopback to BoJ on :7700, +# production uses a loopback Unix domain socket. The gateway proxy +# module currently supports a single `backend_url`; both forms below +# are within that single-backend capability. +# +# Trust source defaults to `"header"` per plan § E2 ("Trust level +# source: \"header\" initially, switched to \"mtls\" after Phase B +# cert rotation runbook is exercised"). The switch to `"mtls"` is a +# config flip, not a code change; it happens at the §2.4 rehearsal. +let deployment = { + environments = { + staging = { + replicas = 1, + memory = "512Mi", + cpu = "250m", + image_tag = "staging", + # Phase A contract § 1: staging gateway → BoJ over TCP loopback. + backend_url = "http://127.0.0.1:7700", + # Plan § E2: external port is gateway-owned, TLS for direct, or + # 8080 when fronted by a Cloudflare Tunnel. + port = 8443, + trust_level_source = "header", + }, + production = { + replicas = 1, + memory = "1Gi", + cpu = "500m", + image_tag = "latest", + # Phase A contract § 1: production gateway → BoJ over a loopback + # Unix domain socket; the back-side TCP port is not opened. + backend_url = "http://unix:/run/boj/gnosis.sock:/", + port = 8443, + # Production trust source: Phase B mTLS (http-capability-gateway#10). + # Flipped from `"header"` after the §2.4 staging cert-rotation + # rehearsal completes per the rollout runbook. + trust_level_source = "mtls", + }, + }, + + container = { + # Image is built from the in-tree Containerfile (multi-stage + # release build). Cerro-torre signing into a `.ctp` bundle is the + # remaining § 1.5 prereq tracked on standards#100; the bundle name + # is `http-capability-gateway` regardless of registry destination. + image = "ghcr.io/hyperpolymath/http-capability-gateway", + # Container listens on 4000 internally (Containerfile EXPOSE 4000); + # the environment's `port` above is the externally bound port. + internal_port = 4000, + health_check = "/health", + readiness_check = "/ready", + metrics_path = "/metrics", + }, + + # Environment variables consumed by `config/runtime.exs`. Paths + # marked `!OWNER:` in the rollout runbook § 1.3 are supplied at + # deploy time, not committed here. + env = { + POLICY_PATH = "/etc/http-capability-gateway/policy.yaml", + BACKEND_URL = "].backend_url>", + PORT = "].port>", + TRUST_LEVEL_SOURCE = "].trust_level_source>", + # mTLS material — owner-provided at deploy time per rollout + # runbook § 1.3. The keys are declared here so a missing value + # fails the deploy precheck rather than silently disabling mTLS. + MTLS_CA_CERT_PATH = "", + GATEWAY_CERT_PATH = "", + GATEWAY_KEY_PATH = "", + }, + + # Mounts: the policy YAML is delivered into the container read-only + # per the policy-authoring workflow (boj-server `docs/integration/ + # http-capability-gateway-policy-authoring.md` § 3). + mounts = [ + { + name = "gateway-policy", + source = "boj-server:config/gateway-policy-boj.yaml", + target = "/etc/http-capability-gateway/policy.yaml", + read_only = true, + }, + { + name = "mtls-material", + source = "", + target = "/etc/http-capability-gateway/tls", + read_only = true, + }, + ], + + # Rolling strategy with max_unavailable = 0 preserves the + # gateway's atomic policy-swap guarantee across replica churn: + # a new replica with a new policy comes up before an old replica + # is drained. + strategy = { + type = "rolling", + max_surge = 1, + max_unavailable = 0, + }, + + # Failure mode contract: the gateway↔BoJ seam declares + # `failure_mode: "fail-closed (circuit breaker)"` in the BoJ + # `Trustfile.a2ml [SEAMS]` entry. If the BoJ backend becomes + # unreachable the gateway's circuit breaker trips and returns 503; + # this deployment spec does not paper over that with a retry loop. + failure_mode = "fail-closed", +} in + +# ───────────────────────────────────────────────────────────────────── +# Deployment scripts (executed at Hunt level) +# ───────────────────────────────────────────────────────────────────── +# +# Shell-level recipes. The substantive logic lives in `Justfile` +# (`just container-build`, `just container-up`) so these scripts are +# thin orchestration. The pre-deploy script enforces that the policy +# file mounted into the container loads + validates cleanly before +# any traffic shift — the same `PolicyLoader.load_policy/1` + +# `PolicyValidator.validate/1` pair the gateway runs at startup, so a +# malformed policy never reaches a running container. +let scripts = { + pre_deploy = m%" +#!/bin/sh +set -eu +echo "K9: Pre-deployment validation for http-capability-gateway..." +just container-verify +just policy-validate "${POLICY_FILE:-config/policy.yaml}" +echo "K9: Validation passed." +"%, + + deploy = m%" +#!/bin/sh +set -eu +ENV="${1:-staging}" +echo "K9: Deploying http-capability-gateway to $ENV environment..." +just container-build +just container-up +echo "K9: Deployment to $ENV complete." +"%, + + rollback = m%" +#!/bin/sh +set -eu +echo "K9: Rolling back http-capability-gateway deployment..." +just container-down +echo "K9: Rollback complete (immediate-bypass)." +echo "K9: See boj-server docs/integration/hcg-tier2-rollout-runbook.md § 5" +echo "K9: for the full rollback procedure (immediate vs permanent)." +"%, +} in + +# ───────────────────────────────────────────────────────────────────── +# Top-level k9-svc component +# ───────────────────────────────────────────────────────────────────── +# +# `pedigree` is inlined at the top level so the canonical +# hyperpolymath/k9-validate-action regex finds +# pedigree = { … metadata = { name = …, version = … } … } +# without traversing `let` bindings — same shape as the boj-server +# reference (`container/deploy.k9.ncl`). +{ + pedigree = { + schema_version = "1.0.0", + component_type = "deployment-component", + # L1: Snout — identity + metadata = { + name = "http-capability-gateway-deploy", + version = "0.1.0", + breed = "application/vnd.k9+nickel", + magic_number = "K9!", + description = "http-capability-gateway tier-2 deployment component (Hunt level)", + }, + # L2: Scent — target environment + target = { + os = 'Linux, + is_edge = false, + requires_podman = true, + min_memory_mb = 512, + }, + # L3: Leash — security + security = { + leash = 'Hunt, + trust_level = 'Hunt, + allow_network = true, + allow_filesystem_write = true, + allow_subprocess = true, + # Real Ed25519 signature is added at bundle-signing time + # (cerro-torre `.ctp` step, rollout runbook § 1.5). + signature = "PLACEHOLDER-SIGNATURE-REQUIRED-FOR-HUNT", + }, + # L4: Gut — self-validation + validation = { + checksum = "sha256:placeholder", + pedigree_version = "1.0.0", + hunt_authorized = false, + }, + # L5: Muscle — deployment recipes + recipes = { + install = "just container-build", + validate = "just container-verify", + deploy = "just container-up", + migrate = "just container-build && just container-up", + }, + }, + + deployment = deployment, + scripts = scripts, + + required_level = 'Hunt, + + warning = m%" +WARNING: This is a Hunt-level component. + +It can execute shell commands and modify your system. +Before running, ensure you have: + +1. Reviewed the deployment scripts above. +2. Verified the signature (added at cerro-torre `.ctp` signing). +3. Explicitly authorised Hunt-level execution. + +Run with: + k9-svc authorize container/gateway-deploy.k9.ncl && + k9-svc deploy container/gateway-deploy.k9.ncl + +The rollback procedure is in boj-server docs/integration/ +hcg-tier2-rollout-runbook.md § 5; the script in this file only +covers the immediate-bypass step. +"%, +} From c7c0382be2c6f019946dd57245e531ae06340540 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Wed, 3 Jun 2026 06:18:01 +0000 Subject: [PATCH 2/2] fix(container): restructure backend to lift literal http:// URLs out MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hypatia `code_safety: ncl_http_url` (CWE-319) flagged the two literal loopback URLs in the staging/production `backend_url` fields. The URLs are intentional per the Phase A contract § 7 — the mTLS boundary is client → gateway; the gateway → BoJ hop is loopback-isolated and does not itself require TLS — but the wire form does not need to live in this file. The contract document is its canonical site. Replaces `backend_url = "http://127.0.0.1:7700"` and `backend_url = "http://unix:/run/boj/gnosis.sock:/"` with a structured `backend` block (`transport` + `host`/`port` for staging, `transport` + `socket_path` for production). The wire-form `BACKEND_URL` env var is composed from these components at deploy time per the contract; the comment block above `let deployment` points readers at the contract for the canonical form. Same load-bearing facts; no literal http:// string in the file. Refs hyperpolymath/standards#91 Refs hyperpolymath/standards#100 Co-Authored-By: Claude Opus 4.7 --- container/gateway-deploy.k9.ncl | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/container/gateway-deploy.k9.ncl b/container/gateway-deploy.k9.ncl index bbac529..8aab616 100644 --- a/container/gateway-deploy.k9.ncl +++ b/container/gateway-deploy.k9.ncl @@ -32,12 +32,15 @@ K9! # Deployment configuration # ───────────────────────────────────────────────────────────────────── # -# Backend URL forms are sourced from the contract document +# Backend transport is sourced from the Phase A contract document # (boj-server `docs/integration/http-capability-gateway-boj-contract.md` # § 1, Transport): staging uses TCP loopback to BoJ on :7700, -# production uses a loopback Unix domain socket. The gateway proxy -# module currently supports a single `backend_url`; both forms below -# are within that single-backend capability. +# production uses a loopback Unix domain socket. The wire-form +# BACKEND_URL is composed from the `backend` block at deploy time — +# the contract document is the canonical source for the literal +# URL form (intentionally plain loopback per contract § 7: the +# mTLS boundary is client → gateway, the gateway → BoJ hop is +# loopback-isolated and does not require TLS). # # Trust source defaults to `"header"` per plan § E2 ("Trust level # source: \"header\" initially, switched to \"mtls\" after Phase B @@ -50,8 +53,13 @@ let deployment = { memory = "512Mi", cpu = "250m", image_tag = "staging", - # Phase A contract § 1: staging gateway → BoJ over TCP loopback. - backend_url = "http://127.0.0.1:7700", + # Phase A contract § 1: staging gateway → BoJ over TCP loopback + # on :7700. Wire-form URL composed at deploy time; see contract. + backend = { + transport = "tcp_loopback", + host = "127.0.0.1", + port = 7700, + }, # Plan § E2: external port is gateway-owned, TLS for direct, or # 8080 when fronted by a Cloudflare Tunnel. port = 8443, @@ -64,7 +72,10 @@ let deployment = { image_tag = "latest", # Phase A contract § 1: production gateway → BoJ over a loopback # Unix domain socket; the back-side TCP port is not opened. - backend_url = "http://unix:/run/boj/gnosis.sock:/", + backend = { + transport = "unix_socket_loopback", + socket_path = "/run/boj/gnosis.sock", + }, port = 8443, # Production trust source: Phase B mTLS (http-capability-gateway#10). # Flipped from `"header"` after the §2.4 staging cert-rotation @@ -89,10 +100,13 @@ let deployment = { # Environment variables consumed by `config/runtime.exs`. Paths # marked `!OWNER:` in the rollout runbook § 1.3 are supplied at - # deploy time, not committed here. + # deploy time, not committed here. `BACKEND_URL` is composed from + # the per-environment `backend` block at deploy time per the wire + # form in the Phase A contract § 1; it is not inlined here so the + # canonical wire form lives in exactly one place (the contract). env = { POLICY_PATH = "/etc/http-capability-gateway/policy.yaml", - BACKEND_URL = "].backend_url>", + BACKEND_URL = "].backend per contract § 1>", PORT = "].port>", TRUST_LEVEL_SOURCE = "].trust_level_source>", # mTLS material — owner-provided at deploy time per rollout