NK-WP-0049-T42: fix launcher preflight entry point; test the launcher path

The MFA launcher called m.preflight() but the module only exposed pi.preflight,
so its catch-all reported a generic preflight failure before any key prompt.
Add preflight() and execute the launcher's own snippet in the tests.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 328925@bnt-lap001
Assistant-Session: 14b795fd-c632-4c01-8d28-e7e490c7b0ed
This commit is contained in:
tegwick 2026-10-24 18:17:08 +02:00
parent 8058b80213
commit 9a3a84c99c
3 changed files with 28 additions and 1 deletions

View file

@ -29,6 +29,22 @@ print(json.dumps(out))
'''
def test_launcher_preflight_entry_point_exists_and_launcher_calls_it():
# The launcher swallows exceptions into one message; a missing attribute once hid here.
assert callable(m.preflight)
launcher = (ROOT / 'tools/bootstrap-recovery/run-isolated-privacyidea-mfa.sh').read_text()
assert 'isolated-privacyidea-mfa.py' in launcher and 'm.preflight()' in launcher
def test_launcher_preflight_snippet_passes_against_the_pinned_target():
import re
import subprocess
launcher = (ROOT / 'tools/bootstrap-recovery/run-isolated-privacyidea-mfa.sh').read_text()
snippet = re.search(r"python3 - <<'PY'\n(.*?)\nPY\n", launcher, re.S).group(1)
done = subprocess.run(['python3', '-c', snippet], cwd=ROOT, capture_output=True, text=True, timeout=120)
assert done.returncode == 0 and done.stdout.startswith('OK: exact bnt-lap001'), done.stdout + done.stderr
def test_wrong_code_keeps_shape_but_changes_value():
assert m.different_code('123456') == '123450'
assert m.different_code('123450') == '123451'

View file

@ -94,6 +94,11 @@ print(json.dumps(result))
'''
def preflight():
"""The launcher calls this before any key prompt."""
return pi.preflight()
def worker_json(worker, script, values=None, timeout=300):
args = ['docker', 'exec'] + (['-i'] if values is not None else []) + [worker, 'python3', '-c', script]
raw = run(args, input=None if values is None else json.dumps(values).encode(), timeout=timeout)
@ -202,7 +207,7 @@ def main():
work = None
stage = 'target_preflight'
try:
result['target'] = pi.preflight()
result['target'] = preflight()
stage = 'transfer_validation'
transfer = json.loads(Path(args.transfer_receipt).read_text())
local.offline.validate(transfer)

View file

@ -1568,6 +1568,12 @@ validates by user and realm, so the restored LDAP resolver, its encrypted bind
password and the LLDAP user lookup need a chained run (T18 phase B). This prepares
T19; it is not T19 acceptance and touches no production service.
Correction 2026-10-24, found when the founder first ran the launcher: it called
`m.preflight()` on a module that only exposed `pi.preflight`, and its catch-all printed
the generic preflight message. The fixture tests had called `main()` directly and
never went through the launcher. Fixed; tests now execute the launcher's own
preflight snippet. No key was requested and nothing was consumed.
## Accept remaining recovery and caller evidence and close the G08 handoff
```task