From a4b6a8f753ea9124e1edebc8a5b9d5cfbbbb48d3 Mon Sep 17 00:00:00 2001 From: tegwick Date: Fri, 30 Oct 2026 21:54:47 +0100 Subject: [PATCH] NK-WP-0056-T04: reconcile tool names the failing phase (custody, cluster, live-pair) Co-Authored-By: Claude Opus 5.5 Assistant: claude-code Assistant-Model: opus Assistant-Process: 673304@bnt-lap001 Assistant-Session: 959b12c7-cf2e-440b-a60d-91229cc9e96e --- sso-mfa/bootstrap/reconcile-keycape-client-secret.py | 5 ++++- tests/test_reconcile_keycape_client_secret.py | 10 +++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/sso-mfa/bootstrap/reconcile-keycape-client-secret.py b/sso-mfa/bootstrap/reconcile-keycape-client-secret.py index ee29612..209dac1 100755 --- a/sso-mfa/bootstrap/reconcile-keycape-client-secret.py +++ b/sso-mfa/bootstrap/reconcile-keycape-client-secret.py @@ -52,11 +52,14 @@ def main(): print(__doc__.strip().splitlines()[-2], file=sys.stderr) return 2 root = roots[0] - phase = 'read' + phase = 'custody' try: + custody_value(root) # fail first, and distinctly, if the decrypted copy is absent + phase = 'cluster' hashed = secret_data('authelia-secrets', 'keycape_client_secret_hash') config = yaml.safe_load(secret_data('keycape-config', 'config.yaml')) live = str(config['authelia']['clientSecret']).encode() + phase = 'live-pair' if not bcrypt.checkpw(live, hashed): raise Refused() # live pair must agree first before = bcrypt.checkpw(custody_value(root), hashed) report = {'live_keycape_matches_authelia_hash': True, 'custody_matched_before': before, diff --git a/tests/test_reconcile_keycape_client_secret.py b/tests/test_reconcile_keycape_client_secret.py index 7dea1e4..e701d56 100644 --- a/tests/test_reconcile_keycape_client_secret.py +++ b/tests/test_reconcile_keycape_client_secret.py @@ -83,5 +83,13 @@ def test_refuses_when_live_pair_disagrees(case): before = env.read_bytes() result = run(case, '--apply') assert result.returncode == 1 and result.stdout == '' - assert json.loads(result.stderr) == {'result': 'unverified', 'phase': 'read', 'partial_changes_possible': False} + assert json.loads(result.stderr) == {'result': 'unverified', 'phase': 'live-pair', 'partial_changes_possible': False} assert env.read_bytes() == before + + +def test_missing_custody_copy_is_reported_as_custody(case): + private, *_ = case + import shutil; shutil.rmtree(private) + result = run(case) + assert result.returncode == 1 + assert json.loads(result.stderr)['phase'] == 'custody'