Record native Windows browser workaround for pilot recovery login
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 3s

Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a1abf1-7e95-7141-87c9-ee78332bf790
This commit is contained in:
tegwick 2026-11-06 19:57:59 +01:00
parent 00739c22a6
commit f6eec13f7c
7 changed files with 566 additions and 10 deletions

View file

@ -0,0 +1,105 @@
{
"schema": "railiance.vergabe-pilot-browser-window-repair.v1",
"recorded_at": "2026-11-06T18:55:09.652746+00:00",
"workplan": "RAPPS-WP-0014",
"task": "RAPPS-WP-0014-T03",
"run_id": "20261106T100825Z-acc2a468a535",
"latest_failed_attempt": {
"status": "owner_login_or_child_not_completed",
"provider_outcome": "unconfirmed",
"warden_exit_code": 10,
"login_category": "callback_timeout",
"before_otp": true,
"observed_hostname": "auth.coulomb.social",
"receipt_file": "/home/worsch/.local/state/vergabe-pilot-recovery/20261106T100825Z-acc2a468a535/provider-receipt-d6a550f9d01d.json",
"session_cleanup_confirmed": null,
"session_revocation_confirmed": null,
"session_storage_cleanup_confirmed": null
},
"user_observations": [
"Sign in registers but OTP page never appears before CLI timeout",
"Titlebar X unresponsive",
"Second tab appears, tab switching unresponsive",
"Real-launcher X11/software local probe: random control activation, double click can select another label, unable to open another tab"
],
"read_only_checks": {
"issuer_readiness": {
"status": "ready",
"authelia": "ok",
"factor_reader": "ok",
"lldap": "ok",
"privacyidea": "ok",
"elapsed_seconds": 0.183
},
"authelia_health": {
"http_status": 200,
"elapsed_seconds": 0.047
},
"authelia_keycape_lldap_deployments": "Ready, observed generation current",
"app_node_metrics_grant": "not present; no wider access requested"
},
"stuck_browser_metadata": {
"profile_lock_metadata_used_for_pid_guard": true,
"pid": 1388237,
"main_cpu_percent_over_two_seconds": 0.5,
"memory_available_mib_approx": 5249,
"uninterruptible_wait_observed": false,
"graphics_markers": [
"Vulkan",
"llvmpipe threads"
],
"real_page_url_arguments_environment_or_profile_contents_read": false,
"terminated_only_identified_operator_process_after_user_tried_close": true
},
"comparison": {
"linux_x11_software": "still unreliable; candidate not adopted",
"native_windows_edge": "Everything works normally",
"native_windows_verdict_source": "founder response to mouse/keyboard/new-tab/titlebar-X question",
"causal_limit": "Different platform/browser combination; exact underlying WSLg/Chrome defect not established"
},
"windows_profile_setup": {
"initial_set_acl_failure": "Windows domain-account translation failed; no domain/account setting changed",
"working_method": "numeric current-user SID icacls grant, inheritance removed, protected owner-only ACL verified"
},
"platform_fix": {
"commit": "8519459e6fedd1bfb25dc2cdee6372d7252f5d0a",
"launcher": "railiance-platform/scripts/operator-browser/xdg-open",
"worker": "railiance-platform/scripts/operator-browser/windows_browser.py",
"wsl_default": "native Windows Edge, new private disposable profile, no fallback to shared/default browser",
"non_wsl": "pinned Linux Chromium retained",
"issuer_and_callback_allowlist": "unchanged; worker rechecks and refuses credential-bearing query keys",
"url_transport": "URL passed to PowerShell worker on stdin; browser itself receives initial authorization URL as startup argument, as before",
"password_otp_tokens_or_keys_transferred_to_worker": false,
"token_aliases_cleared_in_windows_child_process": true,
"owned_process_lifetime_seconds": 600,
"scope": "Browser opener only; no API role, grant, factor, registration, callback, output allowlist or token-helper change",
"projection": {
"status": "applied",
"instance_role": "primary",
"instance_label": "railiance01",
"expected_commit": "8519459e6fedd1bfb25dc2cdee6372d7252f5d0a",
"derived_commit": "8519459e6fedd1bfb25dc2cdee6372d7252f5d0a",
"refused": 0,
"retired": 0,
"identifier_assignments": []
}
},
"validation": {
"operator_tests": 32,
"targeted_opener_cases": 15,
"apps_probe_scope_tests": 3,
"lint": "passed",
"native_lifecycle_fixture": {
"production_guard_rejects_local_fixture_url": true,
"windows_ipv4_to_localhost_redirect": true,
"private_acl_verified_before_launch": true,
"owned_process_closed": true,
"private_profile_removed": true,
"native_identity_request": false
},
"final_credential_query_guard": "passed in Windows without browser creation or network request"
},
"next": "Pull railiance-platform, then retry --publish 20261106T100825Z-acc2a468a535 --verify-restore in founder terminal; no recapture/pause",
"native_owner_login_with_new_opener_verified": false,
"fresh_point_provider_or_restore_verified": false
}

View file

@ -30,8 +30,8 @@ as historical evidence. No allowlist, token helper or login control is bypassed.
Subsequent same-point retries also failed before child handoff: first callback
timeout, then authentication_error (both Warden 10). The founder reports slow
browser interaction and an unresponsive feedback/confirmation page. **Pause
further native retries pending diagnosis.** The [investigation receipt](../../evidence/2026-11-06-vergabe-owner-login-investigation.json)
browser interaction and an unresponsive feedback/confirmation page. Further
native retries were paused pending diagnosis. The [investigation receipt](../../evidence/2026-11-06-vergabe-owner-login-investigation.json)
records responsive public/tunnel checks and synthetic local callbacks through
both hostname and IPv4, in headless and headed fresh browser contexts. Those
checks do not reproduce the actual identity flow or human input latency and do
@ -43,6 +43,50 @@ No setting, callback registration, output allowlist or authentication requiremen
was changed. The completed capture remains available; native retry/restore proof
and possible session revocation remain unconfirmed.
## Current retry after the browser workaround
The founder subsequently observed a pre-OTP stall on Authelia, unresponsive
titlebar/tab controls, and random control activation in a credential-free Linux
X11/software-rendering test. Native Windows Edge in a new private profile worked
normally for mouse, keyboard, tabs and window close. The
[window-repair receipt](../../evidence/2026-11-06-vergabe-browser-window-repair.json)
records the comparison; it does not establish the precise underlying WSLg/Chrome
defect. The unsuccessful X11/software launcher candidate was not adopted.
Platform commit `8519459e6fedd1bfb25dc2cdee6372d7252f5d0a` now selects native
Windows Edge for this WSL workstation, with a fresh owner-only disposable
profile rather than the usual browser session. It uses the same issuer/callback
allowlist, rejects credential-bearing URL query keys, hands the URL to its
PowerShell worker through stdin and clears token aliases in that process.
Edge receives the initial authorization URL as a browser startup argument, as
Linux Chromium did. No password, OTP, authorization code or token is passed that
way. The worker closes only its owned PID/start-time/executable and attempts
profile removal after close or its 10-minute limit. Non-WSL Linux retains the
pinned Chromium path. Native launch failure refuses instead of opening a shared
or default browser profile.
Thirty-two operator tests pass, including 15 opener cases. A local-only native
lifecycle fixture verifies protected profile creation, startup, redirect from
127.0.0.1 to localhost, owned-process close and private-profile removal. The real
issuer guard refuses the synthetic URL; the separate fixture copy admits only
its exact local listener. A final native guard check refuses synthetic credential
query data before browser creation. These checks perform no identity request.
The actual owner login and this fresh point's provider/restore proof remain
pending. No issuer/role, factor, callback-registration, output-allowlist or token-
helper control changed.
From the Apps checkout, update the owner helper and retry the captured point:
```bash
git -C ../railiance-platform pull --ff-only
python3 tools/vergabe_pilot_backup.py --publish 20261106T100825Z-acc2a468a535 --verify-restore
```
Use the founder identity in the new Windows window, complete OTP and the
diagnosis page's Continue while the CLI is waiting, and close that dedicated
window afterward. Return the sanitized receipts. No recapture or app pause is
needed for this retry.
## First operating run — founder terminal
```bash

View file

@ -53,12 +53,16 @@ timeout before provider command handoff. Retry `--publish` for that same run wit
`--verify-restore` and return the sanitized backup/worker/transport receipts.
The [return](evidence/2026-11-06-vergabe-attended-callback-timeout-return.json)
does not establish fresh provider publication, restore or session revocation.
Two further retries report callback_timeout and authentication_error before
provider handoff. Further native retries await the founder's stalled-page
hostname/heading details; see the [login investigation](evidence/2026-11-06-vergabe-owner-login-investigation.json).
Public/tunnel and fresh synthetic headed/headless callback checks pass, but do
not reproduce the real identity flow or establish a root cause. The completed
point remains available and no authentication control was changed.
Further retries report callback_timeout/authentication_error before provider
handoff. The founder identifies a pre-OTP Authelia stall and unresponsive browser
tabs/titlebar; the real-launcher X11/software test remains unreliable. The native
Windows Edge private-profile test works normally. The
[window-repair receipt](evidence/2026-11-06-vergabe-browser-window-repair.json)
and [updated operating packet](proposals/2026-11-06-vergabe-attended-backups/README.md#current-retry-after-the-browser-workaround)
record platform commit `8519459` selecting the isolated native Windows opener on
WSL, its 32 passing operator tests and credential-free lifecycle/cleanup proof.
Actual owner login and publication/restore of the completed point remain pending;
no issuer, factor, callback-registration or token-helper control changed.
Review provider retention previews
and execute only when eligible excess points exist; no artificial deletion is
needed to accept an empty retention plan.

View file

@ -0,0 +1,49 @@
"""Scope checks for the credential-free browser UI probe; no GUI launch."""
import base64
import sys
import unittest
from pathlib import Path
from types import SimpleNamespace
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "tools"))
import vergabe_operator_browser_probe as probe
class ProbeBoundaryTests(unittest.TestCase):
def test_windows_probe_accepts_only_fixed_loopback_test_page(self):
for url in ("https://auth.coulomb.social/", "http://127.0.0.1:1234/?code=synthetic",
"http://127.0.0.1:1234/oidc/callback", "http://user:password@127.0.0.1:1234/",
"http://127.0.0.1:1234/#fragment", "http://127.0.0.1:0/"):
with self.subTest(url=url), self.assertRaises(ValueError):
probe.windows_argv(url, 120)
def test_native_command_uses_private_disposable_profile_and_owned_process_cleanup(self):
argv = probe.windows_argv("http://127.0.0.1:1234/", 120)
self.assertEqual(argv[:4], [probe.POWERSHELL, "-NoProfile", "-NonInteractive", "-EncodedCommand"])
script = base64.b64decode(argv[4]).decode("utf-16le")
self.assertIn("'/inheritance:r'", script)
self.assertIn("$grant='*'+$sid.Value+':(OI)(CI)F'", script)
self.assertIn("WindowsIdentity]::GetCurrent().User", script)
self.assertIn("--user-data-dir=", script)
self.assertIn("$current.StartTime.ToUniversalTime().Ticks -eq $startTicks", script)
self.assertIn("$current.Path -eq $browser", script)
self.assertNotIn("Stop-Process -Name", script)
for flag in ("--no-sandbox", "--disable-web-security", "--ignore-certificate-errors", "--remote-debugging"):
self.assertNotIn(flag, script)
self.assertNotIn("auth.coulomb.social", script)
self.assertNotIn("kc.coulomb.social", script)
def test_software_comparison_keeps_real_launcher_profile_and_url(self):
module = SimpleNamespace(launch_argv=lambda browser, profile, url: [browser, "--user-data-dir=" + profile,
"--no-first-run", url])
argv = probe.probe_argv(module, "/cache/chrome", Path("/tmp/empty-probe"), "http://127.0.0.1:1234/", True)
self.assertIn("--user-data-dir=/tmp/empty-probe", argv)
self.assertEqual(argv[-1], "http://127.0.0.1:1234/")
self.assertIn("--ozone-platform=x11", argv)
self.assertIn("--disable-gpu", argv)
self.assertNotIn("--no-sandbox", argv)
if __name__ == "__main__":
unittest.main()

View file

@ -0,0 +1,83 @@
"""Manual native lifecycle fixture: disposable local page, no identity request.
The production worker's issuer/callback guard is first checked to reject the
local URL. A separate in-memory fixture copy admits only the exact disposable
loopback listener. Production source is never modified. No login, real profile
or token helper is used. Run only with explicit GUI approval.
"""
import argparse
import base64
import importlib.util
import json
import subprocess
import threading
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--module", type=Path, default=Path(__file__).resolve().parents[2]
/ "railiance-platform/scripts/operator-browser/windows_browser.py")
args = parser.parse_args()
spec = importlib.util.spec_from_file_location("native_lifecycle_fixture", args.module)
native = importlib.util.module_from_spec(spec)
spec.loader.exec_module(native)
visits = []
class Handler(BaseHTTPRequestHandler):
def log_message(self, *args):
pass
def do_GET(self):
visits.append(self.path)
if self.path == "/":
self.send_response(302)
self.send_header("Location", "http://localhost:" + str(self.server.server_port) + "/ready")
self.end_headers()
return
self.send_response(200)
self.send_header("Content-Type", "text/html")
self.end_headers()
self.wfile.write(b"<h1>Native browser lifecycle check</h1><p>No login or credentials. This window closes automatically.</p>")
server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
worker = threading.Thread(target=server.serve_forever, daemon=True)
worker.start()
url = "http://127.0.0.1:" + str(server.server_port) + "/"
payload = json.dumps({"url": url}).encode()
try:
refused = subprocess.run(native.command(10), input=payload, capture_output=True, timeout=20, check=False)
assert refused.stdout.strip() == b"failed"
source = native.SCRIPT.replace("__MAXIMUM__", "10")
original = "if($uri.Scheme -ne 'https' -or $uri.Host -ne 'kc.coulomb.social' -or $uri.Port -ne 443 -or $uri.UserInfo -or $uri.Fragment){throw 'invalid origin'}"
assert source.count(original) == 1
source = source.replace(original, "if($url -ne '" + url + "'){throw 'fixture origin mismatch'}")
original = "if($callbacks.Count -ne 1 -or $callbacks[0] -notin @('http://localhost:8250/oidc/callback','http://127.0.0.1:8250/oidc/callback')){throw 'invalid callback'}"
assert source.count(original) == 1
source = source.replace(original, "if($uri.Query){throw 'fixture query refused'}")
source = source.rstrip()
assert source.endswith("}")
source = source[:-1] + "\n[Console]::Out.WriteLine((@{fixture_complete=$true;opened=$opened;private_profile_removed=$removed}|ConvertTo-Json -Compress))\n}"
command = [native.POWERSHELL, "-NoProfile", "-NonInteractive", "-EncodedCommand",
base64.b64encode(source.encode("utf-16le")).decode()]
result = subprocess.run(command, input=payload, capture_output=True, timeout=45, check=False)
lines = result.stdout.decode("utf-8", errors="replace").splitlines()
assert lines[0] == "opened"
metadata = json.loads(lines[-1])
assert metadata == {"fixture_complete": True, "opened": True, "private_profile_removed": True}
assert "/" in visits
assert "/ready" in visits
print(json.dumps({"status": "passed", "native_identity_request": False,
"production_guard_refused_synthetic_url": True, "worker_url_received_on_stdin": True,
"windows_loopback_reached": True, "private_acl_verified_before_launch": True,
"windows_localhost_redirect_reached": True,
"owned_process_closed": True, "private_profile_removed": True}))
finally:
server.shutdown()
server.server_close()
worker.join(timeout=2)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,236 @@
#!/usr/bin/env python3
"""Credential-free manual UI probe using the real operator Chromium launcher.
Default reviews only. --run opens a fresh window and private profile against a
disposable loopback page. --software-x11 selects X11/software rendering for
comparison. No issuer URL, account, credentials, callbacks or real profile.
"""
import argparse
import base64
import importlib.machinery
import importlib.util
import json
import os
import subprocess
import tempfile
import threading
import time
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from urllib.parse import urlsplit
LAUNCHER = Path(__file__).resolve().parents[2] / "railiance-platform/scripts/operator-browser/xdg-open"
POWERSHELL = "/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe"
WINDOWS_PROBE = r'''
$ErrorActionPreference='Stop'
$ProgressPreference='SilentlyContinue'
$profile=$null;$process=$null;$startTicks=$null;$closed=$false;$removed=$false;$userClosed=$false;$ok=$false
$stage='browser_detection';$failureStage=$null;$errorKind=$null
try {
$browser=Join-Path ${env:ProgramFiles(x86)} 'Microsoft\Edge\Application\msedge.exe'
if(-not (Test-Path -LiteralPath $browser)){throw 'browser unavailable'}
$stage='profile_creation'
$profile=Join-Path ([IO.Path]::GetTempPath()) ('railiance-browser-probe-'+[guid]::NewGuid().ToString('N'))
New-Item -ItemType Directory -Path $profile | Out-Null
$stage='profile_acl'
$stage='profile_acl_identity'
$sid=[Security.Principal.WindowsIdentity]::GetCurrent().User
$stage='profile_acl_apply'
$grant='*'+$sid.Value+':(OI)(CI)F'
& "$env:SystemRoot\System32\icacls.exe" $profile '/grant:r' $grant '/inheritance:r' | Out-Null
if($LASTEXITCODE -ne 0){throw 'private profile ACL command failed'}
$stage='profile_acl_verify'
$verified=Get-Acl -LiteralPath $profile
$rules=$verified.GetAccessRules($true,$true,[Security.Principal.SecurityIdentifier])
if(-not $verified.AreAccessRulesProtected -or $rules.Count -ne 1 -or $rules[0].IdentityReference.Value -ne $sid.Value){throw 'private profile ACL verification failed'}
$arguments=@(('--user-data-dir="'+$profile+'"'),'--no-first-run','--no-default-browser-check','--disable-sync','--new-window','__URL__')
$stage='process_start'
$process=Start-Process -FilePath $browser -ArgumentList $arguments -PassThru
$startTicks=$process.StartTime.ToUniversalTime().Ticks
$stage='process_wait'
$deadline=[datetime]::UtcNow.AddSeconds(__MAXIMUM__)
while(-not $process.HasExited -and [datetime]::UtcNow -lt $deadline){Start-Sleep -Milliseconds 250;$process.Refresh()}
$userClosed=$process.HasExited;$ok=$true
} catch {
$ok=$false;$failureStage=$stage;$errorKind=$_.Exception.GetType().Name
} finally {
if($process -and $startTicks){
$current=Get-Process -Id $process.Id -ErrorAction SilentlyContinue
if($current -and $current.StartTime.ToUniversalTime().Ticks -eq $startTicks -and $current.Path -eq $browser){
$null=$current.CloseMainWindow()
if(-not $current.WaitForExit(5000)){Stop-Process -Id $current.Id -Force -ErrorAction SilentlyContinue;$null=$current.WaitForExit(5000)}
}
$process.Refresh();$closed=$process.HasExited
}
if($profile -and ($closed -or -not $process)){
try {Remove-Item -LiteralPath $profile -Recurse -Force -ErrorAction Stop;$removed=$true} catch {$removed=$false}
}
@{status=$(if($ok){'native_probe_finished'}else{'native_probe_failed'});failure_stage=$failureStage;error_kind=$errorKind;browser='Windows Edge';user_closed=$userClosed;owned_process_closed=$closed;private_profile_removed=$removed;native_login=$false} | ConvertTo-Json -Compress
}
'''
HTML = b'''<!doctype html><meta charset="utf-8"><title>Browser response check - no login</title>
<style>body{font:20px system-ui;max-width:40rem;margin:2rem auto}button{display:block;margin:1rem;padding:1rem;background:#def;color:#123;width:32rem;text-align:left}output{display:block;padding:1rem;background:#eee}</style>
<h1>Browser response check</h1><p>This is a local test. No sign-in or credentials.</p>
<button id="one">1. First test button</button>
<button id="two">2. Second test button</button>
<button id="three">3. Third test button</button>
<output id="result">No test button activated yet.</output>
<p>Click each numbered button and check that the reported number matches.
Then press Tab to focus a button and Enter to activate it. Close this window with its titlebar X when done.</p>
<script>
for(const button of document.querySelectorAll('button'))button.addEventListener('click',event=>{
document.getElementById('result').textContent='Activated: '+button.textContent;
const r=button.getBoundingClientRect();
const observation={target:button.id,keyboard:event.detail===0,client_x:event.clientX,client_y:event.clientY,
device_scale:window.devicePixelRatio,button_top:r.top,button_bottom:r.bottom};
fetch('/event',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(observation)});
});
</script>
'''
def launcher_module():
loader = importlib.machinery.SourceFileLoader("operator_browser_probe_launcher", str(LAUNCHER))
spec = importlib.util.spec_from_loader(loader.name, loader)
module = importlib.util.module_from_spec(spec)
loader.exec_module(module)
return module
def probe_argv(module, browser, profile, url, software_x11):
command = module.launch_argv(browser, str(profile), url)
if software_x11:
command[-1:-1] = ["--ozone-platform=x11", "--disable-gpu"]
return command
def windows_argv(url, maximum):
parsed = urlsplit(url)
if not (parsed.scheme == "http" and parsed.hostname == "127.0.0.1" and parsed.port
and parsed.path == "/" and not parsed.query and not parsed.fragment
and not parsed.username and not parsed.password and 10 <= maximum <= 180):
raise ValueError("synthetic_loopback_only")
script = WINDOWS_PROBE.replace("__URL__", url).replace("__MAXIMUM__", str(maximum - 10))
encoded = base64.b64encode(script.encode("utf-16le")).decode()
return [POWERSHELL, "-NoProfile", "-NonInteractive", "-EncodedCommand", encoded]
def run(software_x11, maximum, windows=False):
if not windows:
module = launcher_module()
browser = module.browser_path()
if not browser:
raise RuntimeError("pinned_browser_unavailable")
events = []
started = time.monotonic()
class Handler(BaseHTTPRequestHandler):
def log_message(self, *args):
pass
def do_GET(self):
if self.path not in ("/", "/favicon.ico"):
self.send_error(404)
return
if self.path != "/favicon.ico":
event = {"event": self.path, "elapsed_seconds": round(time.monotonic() - started, 2)}
events.append(event)
print(json.dumps(event), flush=True)
self.send_response(200)
self.send_header("Content-Type", "text/html; charset=utf-8")
self.send_header("Cache-Control", "no-store")
self.send_header("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'; script-src 'unsafe-inline'; connect-src 'self'; frame-ancestors 'none'")
self.end_headers()
self.wfile.write(HTML)
def do_POST(self):
if self.path != "/event" or not 0 < int(self.headers.get("Content-Length", 0)) <= 1024:
self.send_error(400)
return
try:
body = json.loads(self.rfile.read(int(self.headers["Content-Length"])))
if set(body) != {"target", "keyboard", "client_x", "client_y", "device_scale", "button_top", "button_bottom"}:
raise ValueError
if body["target"] not in ("one", "two", "three") or type(body["keyboard"]) is not bool:
raise ValueError
if any(type(body[k]) not in (int, float) for k in body if k not in ("target", "keyboard")):
raise ValueError
except (ValueError, KeyError, TypeError):
self.send_error(400)
return
event = {"event": "button_activated", "elapsed_seconds": round(time.monotonic() - started, 2), **body}
events.append(event)
print(json.dumps(event), flush=True)
self.send_response(204)
self.end_headers()
with tempfile.TemporaryDirectory(prefix="vergabe-browser-probe-") as temporary:
root = Path(temporary)
home, profile = root / "empty-home", root / "empty-profile"
home.mkdir(mode=0o700)
profile.mkdir(mode=0o700)
server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
worker = threading.Thread(target=server.serve_forever, daemon=True)
worker.start()
url = "http://127.0.0.1:" + str(server.server_port) + "/"
command = windows_argv(url, maximum) if windows else probe_argv(module, browser, profile, url, software_x11)
env = {k: v for k, v in os.environ.items() if k not in ("BAO_TOKEN", "VAULT_TOKEN", "OPENBAO_TOKEN")}
env["HOME"] = str(home)
process = subprocess.Popen(command, env=env, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE if windows else subprocess.DEVNULL,
stderr=subprocess.DEVNULL, start_new_session=True)
print(json.dumps({"status": "ui_probe_started", "software_x11": software_x11,
"native_windows": windows, "native_login": False, "maximum_seconds": maximum}), flush=True)
try:
while process.poll() is None and time.monotonic() - started < maximum:
time.sleep(0.25)
finally:
user_closed = process.poll() is not None
if process.poll() is None:
process.terminate()
try:
process.wait(timeout=8)
except subprocess.TimeoutExpired:
process.kill()
process.wait(timeout=5)
server.shutdown()
server.server_close()
worker.join(timeout=2)
if windows:
raw = process.stdout.read(16385)
process.stdout.close()
try:
metadata = json.loads(raw) if len(raw) <= 16384 else {}
print(json.dumps({k: metadata.get(k) for k in ("status", "failure_stage", "error_kind", "browser", "user_closed", "owned_process_closed", "private_profile_removed", "native_login")}), flush=True)
except (ValueError, UnicodeError):
print(json.dumps({"status": "native_probe_metadata_unavailable", "details_suppressed": True}), flush=True)
print(json.dumps({"status": "ui_probe_complete", "software_x11": software_x11,
"window_process_exited_before_deadline": user_closed, "events": events,
"human_wait_included_in_times": True, "native_login": False}), flush=True)
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--run", action="store_true")
parser.add_argument("--software-x11", action="store_true")
parser.add_argument("--windows", action="store_true", help="use native Windows Edge with its own disposable profile")
parser.add_argument("--maximum-seconds", type=int, default=120)
args = parser.parse_args()
if not 10 <= args.maximum_seconds <= 180:
parser.error("maximum must be between 10 and 180 seconds")
if args.windows and args.software_x11:
parser.error("Windows and Linux rendering modes are mutually exclusive")
if not args.run:
print(json.dumps({"status": "prepared", "native_login": False, "fresh_profile": True,
"software_x11": args.software_x11, "native_windows": args.windows, "next": "--run"}))
return 0
run(args.software_x11, args.maximum_seconds, args.windows)
return 0
if __name__ == "__main__":
os.umask(0o077)
try:
raise SystemExit(main())
except Exception: # noqa: BLE001 — do not return browser or environment diagnostics
print(json.dumps({"status": "ui_probe_failed", "details_suppressed": True}))
raise SystemExit(1)

View file

@ -196,10 +196,10 @@ id: RAPPS-WP-0014-T03
status: wait
needs_human: true
decision_id: d06307e5-2737-461a-a2f4-a12c3b492a9a
intervention_note: "Baseline recovery and fresh attended capture/transfer pass. Same-point login retries fail before provider child: callback_timeout, then authentication_error; possible revocation unconfirmed. Founder reports slow/unresponsive confirmation page. Pause retries. Public discovery/tunnel and synthetic headed/headless Continue-to-loopback probes pass; actual stalled page hostname/heading requested without URLs/credentials. Preserve run 20261106T100825Z-acc2a468a535; publication/restore still pending."
intervention_note: "Baseline recovery and fresh capture/transfer pass. WSL Chrome titlebar/tabs/control input unreliable even in X11/software probe; native Windows Edge private-profile probe is founder-confirmed responsive. Platform fix 8519459 selects isolated native Windows opener on WSL; 32 operator tests plus lifecycle/localhost/cleanup checks pass. Pull railiance-platform then retry tools/vergabe_pilot_backup.py --publish 20261106T100825Z-acc2a468a535 --verify-restore. Actual owner login and this point's provider/restore receipts remain pending; no recapture/pause needed."
priority: high
assignee: the-custodian
blocking_reason: "Baseline recovery and accepted attended model pass; repeatable capture/transfer now pass. Contained owner login fails before provider handoff with callback_timeout/authentication_error; slow confirmation-page cause awaits identification. Same fresh point still needs provider/readback/decryption and restore. Restored native SSO/ingress and broader version rollback remain explicit limits."
blocking_reason: "Baseline recovery and accepted attended model pass; repeatable capture/transfer now pass. Native Windows browser workaround validated and published; await actual contained owner login, provider/readback/decryption and restore for the same fresh point. Restored native SSO/ingress and broader version rollback remain explicit limits."
state_hub_task_id: "dd069c6d-fcc1-5bac-b233-976f2f0d5cd1"
```
@ -594,6 +594,41 @@ issuer, callback, output allowlist or token-helper control was changed. Preserve
the completed run; publication/restore and possible session revocation remain
unconfirmed. T03 retains the existing wait.
2026-11-06 live browser diagnosis: the founder locates the delay at Authelia
before OTP, then reports titlebar X and tab switching unresponsive. The next
attempt exits callback_timeout before any OTP, receipt `provider-receipt-d6a550f9d01d.json`.
Issuer dependency readiness, Authelia health and read-only deployment status
are healthy; these do not exercise the password request. The identified
operator process is idle/low CPU with ample available memory; only lock/process/
graphics-library metadata is read, no URL, page, environment or profile content.
The founder-requested frozen window is closed by its guarded PID. A real-launcher
X11/software local probe still has random control activation and unusable tab
controls, so that candidate is not adopted. Native Windows Edge in a fresh
owner-only profile works normally for mouse/keyboard/tabs/titlebar X, confirmed
by the founder. The exact underlying WSLg/Chrome defect is not established.
Applied and published the owner fix in railiance-platform commit
`8519459e6fedd1bfb25dc2cdee6372d7252f5d0a`: on WSL, open the approved issuer in
native Edge with a new private disposable profile; retain pinned Chromium on
non-WSL Linux. Numeric-SID ACLs avoid the Windows domain-name translation error
encountered while preparing the test; no domain/account setting is changed.
Keep exact issuer/callback checks, reject credential-bearing query keys, pass
the authorization URL to the worker over stdin, clear Windows token aliases,
and bound cleanup to its owned PID/start/executable/profile. The browser still
receives the initial authorization URL as startup argument, as before; no
password/OTP/code/token is passed that way. Thirty-two operator tests pass,
including 15 opener cases, plus a credential-free Windows lifecycle fixture
with IPv4-to-localhost navigation and verified process/profile cleanup. The
production guard refuses the fixture URL, and a separate native negative check
refuses credential query data without opening a browser. No issuer/role/factor,
callback registration, output allowlist or token-helper control is changed.
Repo Manager confirms exact commit applied on primary/railiance01, zero refused
or retired records and no identifier assignments. Receipt:
`docs/evidence/2026-11-06-vergabe-browser-window-repair.json`.
After pulling railiance-platform, retry the same `--publish ... --verify-restore`
command in the founder terminal. Actual owner login, publication and restore for
this fresh point remain pending; T03 stays wait. No recapture/pause is needed.
## Validate synthetic sizing against the exact release and shared contention