Allow stopped single-writer outbox maintenance through Helm
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-6-astra
Assistant-Session: 01a140c4-354a-7823-8f0d-8f717397f786
This commit is contained in:
tegwick 2026-10-16 20:25:05 +02:00
parent 84c78e072c
commit dc042022f7
4 changed files with 96 additions and 4 deletions

View file

@ -29,8 +29,10 @@ metadata:
{{- include "access-engine.labels" . | nindent 4 }}
spec:
{{- if .Values.outbox.enabled }}
{{- if ne (int .Values.replicaCount) 1 }}
{{- fail "outbox requires replicaCount=1: one writer per source and PVC" }}
{{- $replicas := .Values.replicaCount }}
{{- $numericKind := has (kindOf $replicas) (list "int" "int64" "int32" "uint" "uint64" "float64" "float32") }}
{{- if or (not $numericKind) (not (or (eq (toString $replicas) "0") (eq (toString $replicas) "1"))) }}
{{- fail "outbox requires numeric integer replicaCount 0 (stopped) or 1 (single writer)" }}
{{- end }}
strategy:
type: Recreate

View file

@ -28,8 +28,10 @@ metadata:
{{- include "flex-auth.labels" . | nindent 4 }}
spec:
{{- if .Values.outbox.enabled }}
{{- if ne (int .Values.replicaCount) 1 }}
{{- fail "outbox requires replicaCount=1: one writer per source and PVC" }}
{{- $replicas := .Values.replicaCount }}
{{- $numericKind := has (kindOf $replicas) (list "int" "int64" "int32" "uint" "uint64" "float64" "float32") }}
{{- if or (not $numericKind) (not (or (eq (toString $replicas) "0") (eq (toString $replicas) "1"))) }}
{{- fail "outbox requires numeric integer replicaCount 0 (stopped) or 1 (single writer)" }}
{{- end }}
strategy:
type: Recreate

View file

@ -0,0 +1,65 @@
#!/usr/bin/env python3
"""Actual Helm stop/start renders and unsafe replica value rejection; offline only."""
import copy
import json
from pathlib import Path
import subprocess
import tempfile
import yaml
ROOT = Path(__file__).resolve().parents[1]
def render(chart, value, encoding="yaml", success=True):
with tempfile.TemporaryDirectory() as directory:
command = ["helm", "template", "flex-auth-secrets-engine", str(ROOT / "charts" / chart),
"--namespace", "flex-auth", "-f", str(ROOT / "values/secrets-engine.yaml"),
"--set", "outbox.enabled=true", "--set", "outbox.source=flex-auth.secrets-engine",
"--set", "envelopeSigning.enabled=true", "--set", "envelopeSigning.kid=test-kid",
"--set", "envelopeSigning.secretName=proposed-seed", "--set", "envelopeSigning.secretKey=seed"]
if encoding == "yaml":
overlay = Path(directory) / "values.yaml"
overlay.write_text(yaml.safe_dump({"replicaCount": value}))
command += ["-f", str(overlay)]
elif encoding == "json":
command += ["--set-json", "replicaCount=" + json.dumps(value)]
elif encoding == "string":
command += ["--set-string", "replicaCount=" + str(value)]
else:
command += ["--set", "replicaCount=" + str(value)]
result = subprocess.run(command, capture_output=True, text=True)
if not success:
assert result.returncode != 0, (chart, encoding, value)
assert "replicaCount" in result.stderr, result.stderr
return
assert result.returncode == 0, result.stderr
documents = [d for d in yaml.safe_load_all(result.stdout) if d]
deployment = next(d for d in documents if d["kind"] == "Deployment")
assert deployment["spec"]["strategy"] == {"type": "Recreate"}
assert type(deployment["spec"]["replicas"]) is int
assert deployment["spec"]["replicas"] == value
return documents
count = 0
for chart in ("flex-auth", "access-engine"):
for encoding in ("yaml", "json", "cli"):
stopped = render(chart, 0, encoding)
ready = render(chart, 1, encoding)
normalized = copy.deepcopy(ready)
next(d for d in normalized if d["kind"] == "Deployment")["spec"]["replicas"] = 0
assert stopped == normalized, "stop must change replicas only, including identical pod projection/PVC"
count += 2
for encoding in ("yaml", "json"):
for value in (-1, 2, 1.5, 0.5, True, False, None, "0", "1", [], {}):
render(chart, value, encoding, success=False)
count += 1
# Helm loses lexical 0 vs0.0 and1 vs1.0 before template evaluation.
for value in (0.0, 1.0):
render(chart, value, encoding)
count += 1
for value in ("-1", "2", "0.5", "1.5", "true", "false", "null", "0.0", "1.0"):
render(chart, value, "cli", success=False)
count += 1
for value in ("0", "1"):
render(chart, value, "string", success=False)
count += 1
print(f"stopped/single-writer chart checks passed: {count} actual Helm renders")

View file

@ -302,3 +302,26 @@ unchanged. Exact local source resolution is no central/private read-placement,
reef binding, custody, native rollout or consumer trust admission. Future signer
image/config adoption remains separate. T06 remains `progress`; no second work
item duplicates this bounded dependency.
2026-10-16 bounded stopped-chart source preparation (Custodian
`access-native-admission/stopped-chart-r2`): both outbox charts admit numeric
replicaCount exactly 0 (maintenance stop) or 1 (single writer), with Recreate,
process flock and bounded persistence unchanged. Fractions, strings, booleans,
negative values and 2+ refuse; Helm decodes YAML/JSON integral 0.0/1.0 as the same
numeric values as 0/1, so lexical float spelling is not distinguished. Actual
Helm stopped/ready renders differ only in replicas and retain the same Platform
source-rendered deployment projection. Owner preparation and independent
coordinator checks approve this bounded source change only; 86 Helm renders and
the existing outbox/delivery/receiver-port suite pass. No native stop, bootstrap,
rollout, custody, public trust, resource or protected-effect approval follows.
The proposed immutable image remains source revision
`84c78e072c8f3daf2bc3b90f8efc8fbeb16c3cf0`, ordinary CI402 verified digest
`sha256:09a83b273740133518cc7e7915fb211381e06545d347d2dd1974042525ac981f`.
This chart-only successor requires its own exact source/chart pin and reviewed
runtime scope; it can explicitly select the unchanged verified 84 image without
rebuilding it or claiming that its revision label names the new chart commit.
These chart/test/workplan-only paths do not trigger the existing image workflow.
Frozen rollout R1, Platform P2 and the package extraction2b/current05a remain
unchanged. T06 stays `progress`; actual stopped-fence verification and all native
admission gates remain open under the same existing task.