fix(config): bind the instance-identity settings to the env vars the chart sets
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Build and Publish Multi-Context Image / build-and-push (push) Successful in 24s

pydantic-settings derives the env var from the field name, so `instance_role`
bound INSTANCE_ROLE and silently ignored the chart's STATE_HUB_INSTANCE_ROLE.
The value reached the pod and was discarded: central reported "unknown" while
its ConfigMap said "primary".

That is the same failure this workplan closes — configuration declared but
never reaching what it configures — reintroduced while building the guard
against it. Rendering the key in `helm template` was mistaken for evidence
that it bound.

Renames to state_hub_instance_role / state_hub_instance_label, matching the
existing state_hub_report_dir precedent, so the env var the chart already sets
is the one that binds.

tests/test_instance_identity.py asserts the env var *name* binds, which is the
check that would have caught this before deploy, plus the unknown default and
rejection of invalid roles.

Refs CUST-WP-0067-T03

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 2583210@bnt-lap001
Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
This commit is contained in:
tegwick 2026-08-25 12:55:11 +02:00
parent 56287708ca
commit 5c73cd32b3
3 changed files with 45 additions and 4 deletions

View file

@ -17,9 +17,12 @@ class Settings(BaseSettings):
# that need the authoritative hub must be able to tell the difference.
# Answering on a port is not evidence of authority — that assumption cost
# seven weeks of onboarding (CUST-WP-0067-T03, ADR-010).
instance_role: Literal["primary", "cache", "unknown"] = "unknown"
state_hub_instance_role: Literal["primary", "cache", "unknown"] = "unknown"
# Env vars are STATE_HUB_INSTANCE_ROLE / STATE_HUB_INSTANCE_LABEL — the
# field name *is* the binding, so renaming either breaks the deployment
# silently. Covered by tests/test_instance_identity.py.
# Free-form label to name *which* instance answered, e.g. "railiance01".
instance_label: str | None = None
state_hub_instance_label: str | None = None
debug: bool = False
state_hub_report_dir: str = "reports/recently-on-scope"
state_hub_markitect_cli_path: str | None = None

View file

@ -1113,8 +1113,8 @@ async def health_check(session: AsyncSession = Depends(get_session)) -> dict:
"status": "ok",
"db": "connected",
# Identity, so a caller can verify it reached the hub it meant to.
"instance_role": settings.instance_role,
"instance_label": settings.instance_label,
"instance_role": settings.state_hub_instance_role,
"instance_label": settings.state_hub_instance_label,
}
except Exception as exc:
return JSONResponse(

View file

@ -0,0 +1,38 @@
"""The instance-identity env vars must actually bind (CUST-WP-0067-T03).
The chart sets STATE_HUB_INSTANCE_ROLE. pydantic-settings derives the env var
from the *field name*, so a field called `instance_role` silently ignores it and
the hub reports "unknown" while the ConfigMap says "primary" config that is
declared but never reaches what it configures, which is the entire failure this
workplan exists to close.
"""
from __future__ import annotations
import pytest
from api.config import Settings
@pytest.mark.parametrize(
"env_name,attr,value",
[
("STATE_HUB_INSTANCE_ROLE", "state_hub_instance_role", "primary"),
("STATE_HUB_INSTANCE_LABEL", "state_hub_instance_label", "railiance01"),
],
)
def test_env_var_binds_to_setting(monkeypatch, env_name, attr, value):
monkeypatch.setenv(env_name, value)
assert getattr(Settings(), attr) == value
def test_role_defaults_to_unknown(monkeypatch):
"""An instance that has not declared itself is not the primary."""
monkeypatch.delenv("STATE_HUB_INSTANCE_ROLE", raising=False)
assert Settings().state_hub_instance_role == "unknown"
def test_role_rejects_unknown_values(monkeypatch):
monkeypatch.setenv("STATE_HUB_INSTANCE_ROLE", "authoritative")
with pytest.raises(Exception):
Settings()