@soleur/opencode 3.354.7 → 3.354.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soleur/opencode",
3
- "version": "3.354.7",
3
+ "version": "3.354.8",
4
4
  "description": "Soleur AI organization plugin for OpenCode",
5
5
  "type": "module",
6
6
  "bin": {
@@ -2,12 +2,17 @@
2
2
  """One proxy session for the suite (#7980). stdlib only.
3
3
 
4
4
  proxy-session-driver.py --out <dir> --proxy <proxy.py> --server <argv...>
5
- [--proxy-arg <arg>]... [--env K=V]... [--send <json-line>]... [--end eof|sigterm|sigkill|killchild|none] [--timeout S]
5
+ [--proxy-arg <arg>]... [--env K=V]... [--send <json-line>]... [--end eof|sigterm|sigkill|killchild|none] [--timeout S] [--expect-death]
6
6
 
7
7
  Launches `python3 <proxy.py> <proxy-arg...> -- <server argv...>` in a NEW SESSION (so the
8
8
  proxy's own pid is the group we assert on for a SIGKILLed proxy), writes each
9
9
  --send line, reads until every sent request id has a reply or the timeout
10
- lapses, then ends the session per --end and waits. Writes into <out>:
10
+ lapses, then ends the session per --end and waits. With --expect-death the proxy is
11
+ expected to die at startup: the reply-wait phase is skipped (a stalled startup must
12
+ not burn a deadline window waiting for a reply that never comes) and the post-`--end`
13
+ wait is bounded by EXPECT_DEATH_TIMEOUT_S, so a slow-but-correct death records its real
14
+ exit code instead of "TIMEOUT". Under --expect-death, --timeout still applies to the
15
+ group_after poll but is inert for the death wait. Writes into <out>:
11
16
  stdout.bin every byte the proxy wrote responses.json parsed objects
12
17
  stderr.txt proxy + server stderr rc proxy exit code
13
18
  child_pgid the pgid the proxy logged (or "")
@@ -28,8 +33,11 @@ import time
28
33
  from pathlib import Path
29
34
 
30
35
 
36
+ EXPECT_DEATH_TIMEOUT_S = 30
37
+
38
+
31
39
  def parse(argv):
32
- o = {"env": {}, "send": [], "end": "eof", "timeout": 8.0, "out": None, "proxy": None, "proxy_args": [], "server": []}
40
+ o = {"env": {}, "send": [], "end": "eof", "timeout": 8.0, "out": None, "proxy": None, "proxy_args": [], "server": [], "expect_death": False}
33
41
  i = 0
34
42
  while i < len(argv):
35
43
  a = argv[i]
@@ -40,6 +48,7 @@ def parse(argv):
40
48
  elif a == "--send": o["send"].append(argv[i + 1]); i += 2
41
49
  elif a == "--end": o["end"] = argv[i + 1]; i += 2
42
50
  elif a == "--timeout": o["timeout"] = float(argv[i + 1]); i += 2
51
+ elif a == "--expect-death": o["expect_death"] = True; i += 1
43
52
  elif a == "--server": o["server"] = argv[i + 1:]; break
44
53
  else: sys.stderr.write(__doc__); sys.exit(2)
45
54
  if not (o["out"] and o["proxy"] and o["server"]): sys.stderr.write(__doc__); sys.exit(2)
@@ -93,7 +102,7 @@ def main():
93
102
  objs.append({"__unparsable__": line.decode("utf-8", "replace")})
94
103
  return True
95
104
 
96
- while want - got and time.monotonic() < deadline and p.stdout in [k.fileobj for k in sel.get_map().values()]:
105
+ while not o["expect_death"] and want - got and time.monotonic() < deadline and p.stdout in [k.fileobj for k in sel.get_map().values()]:
97
106
  if pump(True) is None: break
98
107
  while sel.get_map() and pump(False): pass
99
108
  err.flush()
@@ -110,8 +119,10 @@ def main():
110
119
  os.killpg(int(child_pgid), signal.SIGTERM)
111
120
  elif o["end"] == "sigkill":
112
121
  os.kill(p.pid, signal.SIGKILL)
113
- # drain anything the proxy still says, then wait for it
114
- t_end = time.monotonic() + o["timeout"]
122
+ # drain anything the proxy still says, then wait for it. An expect-death
123
+ # session is bounded by the death budget, not --timeout: a slow-but-correct
124
+ # startup death must record its real rc, never the deadline's TIMEOUT.
125
+ t_end = time.monotonic() + (EXPECT_DEATH_TIMEOUT_S if o["expect_death"] else o["timeout"])
115
126
  while time.monotonic() < t_end:
116
127
  if p.poll() is not None: break
117
128
  with contextlib.suppress(Exception):
@@ -366,31 +366,31 @@ assert_true 'FR15: --user-data-dir=<XDG_CACHE_HOME>/<name> injected once, before
366
366
  session argv-udd-home "$PROXY" "${UDD[@]}" --env "XDG_CACHE_HOME=" --env "HOME=$WORK/home" --env FAKE_PW_ARGV_OUT="$WORK/argv-udd-home" --send "$INIT" --end eof >/dev/null
367
367
  assert_true 'FR15: XDG_CACHE_HOME empty → profile resolves under $HOME/.cache' bash -c 'test -f "$1" && grep -qxF -- "--user-data-dir=$2/.cache/soleur-test-profile" "$1"' _ "$WORK/argv-udd-home" "$WORK/home"
368
368
  assert_true 'FR15: flag absent → child argv carries no --user-data-dir (unchanged behaviour)' bash -c 'test -f "$1" && ! grep -q -- "--user-data-dir" "$1"' _ "$WORK/argv-out"
369
- r="$(session udd-dotdot "$PROXY" --proxy-arg --user-data-dir-name --proxy-arg '..' --send "$INIT" --end eof --timeout 3)"
369
+ r="$(session udd-dotdot "$PROXY" --proxy-arg --user-data-dir-name --proxy-arg '..' --send "$INIT" --end eof --expect-death)"
370
370
  assert_refused_start 'FR15: a .. basename refuses to start' "$r" '..'
371
- r="$(session udd-sep "$PROXY" --proxy-arg --user-data-dir-name --proxy-arg 'a/b' --send "$INIT" --end eof --timeout 3)"
371
+ r="$(session udd-sep "$PROXY" --proxy-arg --user-data-dir-name --proxy-arg 'a/b' --send "$INIT" --end eof --expect-death)"
372
372
  assert_refused_start 'FR15: a basename containing / refuses to start' "$r" 'separator'
373
- r="$(session udd-sep-bs "$PROXY" --proxy-arg --user-data-dir-name --proxy-arg 'a\b' --send "$INIT" --end eof --timeout 3)"
373
+ r="$(session udd-sep-bs "$PROXY" --proxy-arg --user-data-dir-name --proxy-arg 'a\b' --send "$INIT" --end eof --expect-death)"
374
374
  assert_refused_start 'FR15: a basename containing a backslash refuses to start' "$r" 'separator'
375
- r="$(session udd-conflict "$PROXY" "${UDD[@]}" --send "$INIT" --end eof --timeout 3 --server python3 "$STUB" --user-data-dir=/x)"
375
+ r="$(session udd-conflict "$PROXY" "${UDD[@]}" --send "$INIT" --end eof --expect-death --server python3 "$STUB" --user-data-dir=/x)"
376
376
  assert_refused_start 'FR15: the flag combined with an explicit --user-data-dir refuses to start' "$r" 'explicit'
377
- r="$(session udd-relxdg "$PROXY" "${UDD[@]}" --env XDG_CACHE_HOME=relative/cache --send "$INIT" --end eof --timeout 3)"
377
+ r="$(session udd-relxdg "$PROXY" "${UDD[@]}" --env XDG_CACHE_HOME=relative/cache --send "$INIT" --end eof --expect-death)"
378
378
  assert_refused_start 'FR15: a relative XDG_CACHE_HOME refuses to start' "$r" 'absolute'
379
- r="$(session udd-nohome "$PROXY" "${UDD[@]}" --env XDG_CACHE_HOME= --env HOME= --send "$INIT" --end eof --timeout 3)"
379
+ r="$(session udd-nohome "$PROXY" "${UDD[@]}" --env XDG_CACHE_HOME= --env HOME= --send "$INIT" --end eof --expect-death)"
380
380
  assert_refused_start 'FR15: XDG_CACHE_HOME and HOME both unset refuses to start' "$r" 'HOME is set'
381
- r="$(session udd-missing "$PROXY" --proxy-arg --user-data-dir-name --send "$INIT" --end eof --timeout 3)"
381
+ r="$(session udd-missing "$PROXY" --proxy-arg --user-data-dir-name --send "$INIT" --end eof --expect-death)"
382
382
  assert_refused_start 'FR15: --user-data-dir-name with no argument refuses to start' "$r" 'basename argument'
383
- r="$(session udd-unknown "$PROXY" --proxy-arg --bogus-flag --send "$INIT" --end eof --timeout 3)"
383
+ r="$(session udd-unknown "$PROXY" --proxy-arg --bogus-flag --send "$INIT" --end eof --expect-death)"
384
384
  assert_refused_start 'FR15: an unrecognised proxy flag refuses to start' "$r" 'unrecognised'
385
- r="$(session udd-dup "$PROXY" --proxy-arg --user-data-dir-name --proxy-arg a --proxy-arg --user-data-dir-name=b --send "$INIT" --end eof --timeout 3)"
385
+ r="$(session udd-dup "$PROXY" --proxy-arg --user-data-dir-name --proxy-arg a --proxy-arg --user-data-dir-name=b --send "$INIT" --end eof --expect-death)"
386
386
  assert_refused_start 'FR15: the flag given twice refuses to start' "$r" 'twice'
387
- r="$(session udd-dash "$PROXY" --proxy-arg --user-data-dir-name --proxy-arg '--bogus' --send "$INIT" --end eof --timeout 3)"
387
+ r="$(session udd-dash "$PROXY" --proxy-arg --user-data-dir-name --proxy-arg '--bogus' --send "$INIT" --end eof --expect-death)"
388
388
  assert_refused_start 'FR15: a basename beginning with - refuses to start' "$r" "leading '-'"
389
389
  # the = spelling, and a root that only resolves after realpath normalisation
390
390
  UDDEQ=(--proxy-arg --user-data-dir-name=eq-profile)
391
391
  session argv-udd-eq "$PROXY" "${UDDEQ[@]}" --env "XDG_CACHE_HOME=$WORK/xdg" --env FAKE_PW_ARGV_OUT="$WORK/argv-udd-eq" --send "$INIT" --end eof >/dev/null
392
392
  assert_true 'FR15: --user-data-dir-name=<name> injects the same profile dir' bash -c 'test -f "$1" && grep -qxF -- "--user-data-dir=$2/eq-profile" "$1"' _ "$WORK/argv-udd-eq" "$WORK/xdg"
393
- r="$(session udd-eqempty "$PROXY" --proxy-arg '--user-data-dir-name=' --send "$INIT" --end eof --timeout 3)"
393
+ r="$(session udd-eqempty "$PROXY" --proxy-arg '--user-data-dir-name=' --send "$INIT" --end eof --expect-death)"
394
394
  assert_refused_start 'FR15: --user-data-dir-name= (empty value) refuses to start' "$r" 'basename'
395
395
  mkdir -p "$WORK/sub" "$WORK/xdg-real"; ln -sfn "$WORK/xdg-real" "$WORK/xdg-link"
396
396
  session argv-udd-dotroot "$PROXY" "${UDD[@]}" --env "XDG_CACHE_HOME=$WORK/sub/.." --env FAKE_PW_ARGV_OUT="$WORK/argv-udd-dotroot" --send "$INIT" --end eof >/dev/null
@@ -508,18 +508,18 @@ for node in tree.body:
508
508
  found = ast.literal_eval(node.value)
509
509
  sys.exit(0 if found == {"linux": [sys.argv[2]], "darwin": [sys.argv[3]]} else 1)
510
510
  PY
511
- r="$(session fb-twice "$PROXY" --proxy-arg --chromium-fallback --proxy-arg --chromium-fallback --send "$INIT" --end eof --timeout 3)"
511
+ r="$(session fb-twice "$PROXY" --proxy-arg --chromium-fallback --proxy-arg --chromium-fallback --send "$INIT" --end eof --expect-death)"
512
512
  assert_refused_start 'FR16: --chromium-fallback given twice refuses to start' "$r" 'twice'
513
- r="$(session fb-eq "$PROXY" --proxy-arg --chromium-fallback=1 --send "$INIT" --end eof --timeout 3)"
513
+ r="$(session fb-eq "$PROXY" --proxy-arg --chromium-fallback=1 --send "$INIT" --end eof --expect-death)"
514
514
  assert_refused_start 'FR16: --chromium-fallback=<v> is not a recognised spelling (the flag is a boolean) and refuses to start' "$r" 'unrecognised'
515
- r="$(session fb-trailing "$PROXY" "${CFB[@]}" --env "$NOCHROME" --send "$INIT" --end eof --timeout 3 --server python3 "$STUB" --browser)"
515
+ r="$(session fb-trailing "$PROXY" "${CFB[@]}" --env "$NOCHROME" --send "$INIT" --end eof --expect-death --server python3 "$STUB" --browser)"
516
516
  assert_refused_start 'FR16: a trailing --browser would bind the appended flag as its value — refuses even with the fallback on' "$r" 'expects a value'
517
- r="$(session fb-name-swallow "$PROXY" --proxy-arg --user-data-dir-name --proxy-arg --chromium-fallback --send "$INIT" --end eof --timeout 3)"
517
+ r="$(session fb-name-swallow "$PROXY" --proxy-arg --user-data-dir-name --proxy-arg --chromium-fallback --send "$INIT" --end eof --expect-death)"
518
518
  assert_refused_start 'FR16: --user-data-dir-name never swallows the fallback flag as its basename (a leading - refuses)' "$r" "leading '-'"
519
519
  # The sandbox refusals hold with the fallback ON: the fallback adds --sandbox, it never opens a bypass for the forbidden spelling.
520
- r="$(session fb-nosandbox "$PROXY" "${CFB[@]}" --env "$NOCHROME" --send "$INIT" --end eof --timeout 3 --server python3 "$STUB" --no-sandbox)"
520
+ r="$(session fb-nosandbox "$PROXY" "${CFB[@]}" --env "$NOCHROME" --send "$INIT" --end eof --expect-death --server python3 "$STUB" --no-sandbox)"
521
521
  assert_refused_start 'FR16: --no-sandbox in the server argv still refuses to start with the fallback on' "$r" '--no-sandbox'
522
- r="$(session fb-nosandbox-env "$PROXY" "${CFB[@]}" --env "$NOCHROME" --env PLAYWRIGHT_MCP_SANDBOX=1 --send "$INIT" --end eof --timeout 3)"
522
+ r="$(session fb-nosandbox-env "$PROXY" "${CFB[@]}" --env "$NOCHROME" --env PLAYWRIGHT_MCP_SANDBOX=1 --send "$INIT" --end eof --expect-death)"
523
523
  assert_refused_start 'FR16: PLAYWRIGHT_MCP_SANDBOX in the env still refuses to start with the fallback on' "$r" 'PLAYWRIGHT_MCP_SANDBOX'
524
524
  session fb-udd "$PROXY" "${CFB[@]}" "${UDD[@]}" --env "$NOCHROME" --env "XDG_CACHE_HOME=$WORK/xdg" --env FAKE_PW_ARGV_OUT="$WORK/fb-udd" --send "$INIT" --end eof >/dev/null
525
525
  assert_true 'FR16: with a profile flag too, the appended flags arrive as --browser chromium --sandbox, --user-data-dir=<dir>, --snapshot-mode none, in that order' python3 "$FBCHK" "$WORK/fb-udd" udd-order "$WORK/xdg/soleur-test-profile"
@@ -532,50 +532,50 @@ assert_true 'FR17 control: the stub reports <unset> when the variable is absent
532
532
  # FR5 — refuse to start (no child spawned)
533
533
  # ---------------------------------------------------------------------------
534
534
  NOSIB="$WORK/nosib"; mkdir -p "$NOSIB"; cp "$PROXY" "$NOSIB/proxy.py"
535
- r="$(session nosib "$NOSIB/proxy.py" --send "$INIT" --end eof --timeout 3)"
535
+ r="$(session nosib "$NOSIB/proxy.py" --send "$INIT" --end eof --expect-death)"
536
536
  assert_refused_start 'FR5: proxy beside no redactor refuses to start' "$r" 'missing'
537
- r="$(session savesess "$PROXY" --send "$INIT" --end eof --timeout 3 --server python3 "$STUB" --save-session)"
537
+ r="$(session savesess "$PROXY" --send "$INIT" --end eof --expect-death --server python3 "$STUB" --save-session)"
538
538
  assert_refused_start 'FR5: --save-session in the wrapped argv refuses to start' "$r" 'save-session'
539
539
  printf '{"saveSession": true}\n' > "$WORK/cfg-save.json"
540
- r="$(session savecfg "$PROXY" --send "$INIT" --end eof --timeout 3 --server python3 "$STUB" --config "$WORK/cfg-save.json")"
540
+ r="$(session savecfg "$PROXY" --send "$INIT" --end eof --expect-death --server python3 "$STUB" --config "$WORK/cfg-save.json")"
541
541
  assert_refused_start 'FR5: config saveSession (via --config) refuses to start' "$r" 'saveSession'
542
- r="$(session savecfgenv "$PROXY" --env PLAYWRIGHT_MCP_CONFIG="$WORK/cfg-save.json" --send "$INIT" --end eof --timeout 3)"
542
+ r="$(session savecfgenv "$PROXY" --env PLAYWRIGHT_MCP_CONFIG="$WORK/cfg-save.json" --send "$INIT" --end eof --expect-death)"
543
543
  assert_refused_start 'FR5: config saveSession (via $PLAYWRIGHT_MCP_CONFIG) refuses to start' "$r" 'saveSession'
544
544
  printf 'saveSession = true\n' > "$WORK/cfg-save.ini"
545
- r="$(session saveini "$PROXY" --send "$INIT" --end eof --timeout 3 --server python3 "$STUB" --config="$WORK/cfg-save.ini")"
545
+ r="$(session saveini "$PROXY" --send "$INIT" --end eof --expect-death --server python3 "$STUB" --config="$WORK/cfg-save.ini")"
546
546
  assert_refused_start 'FR5: a config the proxy cannot parse as JSON (the server reads INI) refuses to start' "$r" 'not JSON'
547
- r="$(session cfgmissing "$PROXY" --send "$INIT" --end eof --timeout 3 --server python3 "$STUB" --config="$WORK/nope.json")"
547
+ r="$(session cfgmissing "$PROXY" --send "$INIT" --end eof --expect-death --server python3 "$STUB" --config="$WORK/nope.json")"
548
548
  assert_refused_start 'FR5: a --config that does not exist refuses to start with a named reason' "$r" 'does not exist'
549
549
  printf '{"saveVideo": {"width": 800, "height": 600}}\n' > "$WORK/cfg-video.json"
550
- r="$(session savevideo "$PROXY" --send "$INIT" --end eof --timeout 3 --server python3 "$STUB" --config="$WORK/cfg-video.json")"
550
+ r="$(session savevideo "$PROXY" --send "$INIT" --end eof --expect-death --server python3 "$STUB" --config="$WORK/cfg-video.json")"
551
551
  assert_refused_start 'FR5: config saveVideo refuses to start' "$r" 'saveVideo'
552
552
  printf '{"capabilities": ["devtools"]}\n' > "$WORK/cfg-caps.json"
553
- r="$(session cfgcaps "$PROXY" --send "$INIT" --end eof --timeout 3 --server python3 "$STUB" --config="$WORK/cfg-caps.json")"
553
+ r="$(session cfgcaps "$PROXY" --send "$INIT" --end eof --expect-death --server python3 "$STUB" --config="$WORK/cfg-caps.json")"
554
554
  assert_refused_start 'FR5: config capabilities other than vision refuses to start' "$r" 'capabilities'
555
555
  printf '{"server": {"port": 8931}}\n' > "$WORK/cfg-port.json"
556
- r="$(session cfgport "$PROXY" --send "$INIT" --end eof --timeout 3 --server python3 "$STUB" --config="$WORK/cfg-port.json")"
556
+ r="$(session cfgport "$PROXY" --send "$INIT" --end eof --expect-death --server python3 "$STUB" --config="$WORK/cfg-port.json")"
557
557
  assert_refused_start 'FR5: config server.port refuses to start' "$r" 'server.port'
558
- r="$(session port "$PROXY" --send "$INIT" --end eof --timeout 3 --server python3 "$STUB" --port 8931)"
558
+ r="$(session port "$PROXY" --send "$INIT" --end eof --expect-death --server python3 "$STUB" --port 8931)"
559
559
  assert_refused_start 'FR5: --port (HTTP transport around the relay) refuses to start' "$r" '--port'
560
- r="$(session portenv "$PROXY" --env PLAYWRIGHT_MCP_PORT=8931 --send "$INIT" --end eof --timeout 3)"
560
+ r="$(session portenv "$PROXY" --env PLAYWRIGHT_MCP_PORT=8931 --send "$INIT" --end eof --expect-death)"
561
561
  assert_refused_start 'FR5: PLAYWRIGHT_MCP_PORT refuses to start' "$r" '--port'
562
- r="$(session host "$PROXY" --send "$INIT" --end eof --timeout 3 --server python3 "$STUB" --host=0.0.0.0)"
562
+ r="$(session host "$PROXY" --send "$INIT" --end eof --expect-death --server python3 "$STUB" --host=0.0.0.0)"
563
563
  assert_refused_start 'FR5: --host refuses to start' "$r" '--host'
564
- r="$(session caps "$PROXY" --send "$INIT" --end eof --timeout 3 --server python3 "$STUB" --caps=vision,devtools)"
564
+ r="$(session caps "$PROXY" --send "$INIT" --end eof --expect-death --server python3 "$STUB" --caps=vision,devtools)"
565
565
  assert_refused_start 'FR5: --caps with devtools refuses to start' "$r" '--caps'
566
- r="$(session capsenv "$PROXY" --env PLAYWRIGHT_MCP_CAPS=storage --send "$INIT" --end eof --timeout 3)"
566
+ r="$(session capsenv "$PROXY" --env PLAYWRIGHT_MCP_CAPS=storage --send "$INIT" --end eof --expect-death)"
567
567
  assert_refused_start 'FR5: PLAYWRIGHT_MCP_CAPS=storage refuses to start' "$r" '--caps'
568
- r="$(session outmode "$PROXY" --send "$INIT" --end eof --timeout 3 --server python3 "$STUB" --output-mode file)"
568
+ r="$(session outmode "$PROXY" --send "$INIT" --end eof --expect-death --server python3 "$STUB" --output-mode file)"
569
569
  assert_refused_start 'FR5: --output-mode file refuses to start' "$r" 'output-mode'
570
570
  printf '{"snapshot": {"mode": "full"}, "capabilities": ["vision"]}\n' > "$WORK/cfg-ok.json"
571
571
  r="$(session cfgok "$PROXY" --send "$INIT" --send "$SNAP" --end eof --server python3 "$STUB" --config="$WORK/cfg-ok.json" --caps vision)"
572
572
  assert_redacted_result 'FR5 companion: a benign JSON --config=<file> and --caps vision start and serve' "$r" 3 1
573
573
  for dbg in 'pw:mcp:server:response' '*' 'pw:*' 'pw:api *' '*:response' 'express:router, *' 'p*'; do
574
- r="$(session "debug-$(printf '%s' "$dbg" | md5sum | cut -c1-8)" "$PROXY" --env "DEBUG=$dbg" --send "$INIT" --end eof --timeout 3)"
574
+ r="$(session "debug-$(printf '%s' "$dbg" | md5sum | cut -c1-8)" "$PROXY" --env "DEBUG=$dbg" --send "$INIT" --end eof --expect-death)"
575
575
  assert_refused_start "FR5: DEBUG='$dbg' (enables a pw:* logger) refuses to start" "$r" 'DEBUG'
576
576
  done
577
577
  assert_true 'FR5: the DEBUG reason never quotes the value' bash -c 'stderr_has "$1" "refusing to start" && ! grep -qF "p*" "$1/stderr.txt"' _ "$r"
578
- r="$(session debugfile "$PROXY" --env DEBUG_FILE="$WORK/dbg" --send "$INIT" --end eof --timeout 3)"
578
+ r="$(session debugfile "$PROXY" --env DEBUG_FILE="$WORK/dbg" --send "$INIT" --end eof --expect-death)"
579
579
  assert_refused_start 'FR5: DEBUG_FILE refuses to start' "$r" 'DEBUG_FILE'
580
580
  r="$(session debugother "$PROXY" --env 'DEBUG=express:router,-pw:*' --send "$INIT" --send "$SNAP" --end eof)"
581
581
  assert_redacted_result 'FR5 companion: an unrelated DEBUG namespace (plus a pw exclusion) starts and serves' "$r" 3 1
@@ -599,78 +599,78 @@ for pair in \
599
599
  '--no-sandbox:PLAYWRIGHT_MCP_SANDBOX' \
600
600
  '--daemon:PLAYWRIGHT_MCP_DAEMON'; do
601
601
  flag="${pair%%:*}"; envvar="${pair##*:}"
602
- r="$(session "sink-$flag" "$PROXY" --send "$INIT" --end eof --timeout 3 --server python3 "$STUB" "$flag=/tmp/x")"
602
+ r="$(session "sink-$flag" "$PROXY" --send "$INIT" --end eof --expect-death --server python3 "$STUB" "$flag=/tmp/x")"
603
603
  assert_refused_start "FR5: $flag=<v> in the wrapped argv refuses to start" "$r" "$flag"
604
- r="$(session "sinkenv-${envvar#PLAYWRIGHT_MCP_}" "$PROXY" --env "$envvar=/tmp/x" --send "$INIT" --end eof --timeout 3)"
604
+ r="$(session "sinkenv-${envvar#PLAYWRIGHT_MCP_}" "$PROXY" --env "$envvar=/tmp/x" --send "$INIT" --end eof --expect-death)"
605
605
  assert_refused_start "FR5: $envvar refuses to start" "$r" "$envvar"
606
606
  done
607
- r="$(session sink-aufa "$PROXY" --send "$INIT" --end eof --timeout 3 --server python3 "$STUB" --allow-unrestricted-file-access)"
607
+ r="$(session sink-aufa "$PROXY" --send "$INIT" --end eof --expect-death --server python3 "$STUB" --allow-unrestricted-file-access)"
608
608
  assert_refused_start 'FR5: bare --allow-unrestricted-file-access refuses to start' "$r" 'allow-unrestricted-file-access'
609
- r="$(session sinkenv-aufa "$PROXY" --env PLAYWRIGHT_MCP_ALLOW_UNRESTRICTED_FILE_ACCESS=1 --send "$INIT" --end eof --timeout 3)"
609
+ r="$(session sinkenv-aufa "$PROXY" --env PLAYWRIGHT_MCP_ALLOW_UNRESTRICTED_FILE_ACCESS=1 --send "$INIT" --end eof --expect-death)"
610
610
  assert_refused_start 'FR5: PLAYWRIGHT_MCP_ALLOW_UNRESTRICTED_FILE_ACCESS refuses to start' "$r" 'PLAYWRIGHT_MCP_ALLOW_UNRESTRICTED_FILE_ACCESS'
611
- r="$(session sink-savesess-eq "$PROXY" --send "$INIT" --end eof --timeout 3 --server python3 "$STUB" --save-session=/x)"
611
+ r="$(session sink-savesess-eq "$PROXY" --send "$INIT" --end eof --expect-death --server python3 "$STUB" --save-session=/x)"
612
612
  assert_refused_start 'FR5: --save-session=<v> (the = spelling) refuses to start' "$r" 'save-session'
613
- r="$(session sinkenv-savesess "$PROXY" --env PLAYWRIGHT_MCP_SAVE_SESSION=1 --send "$INIT" --end eof --timeout 3)"
613
+ r="$(session sinkenv-savesess "$PROXY" --env PLAYWRIGHT_MCP_SAVE_SESSION=1 --send "$INIT" --end eof --expect-death)"
614
614
  assert_refused_start 'FR5: PLAYWRIGHT_MCP_SAVE_SESSION refuses to start' "$r" 'PLAYWRIGHT_MCP_SAVE_SESSION'
615
- r="$(session sinkenv-udd "$PROXY" --env PLAYWRIGHT_MCP_USER_DATA_DIR=/tmp/p --send "$INIT" --end eof --timeout 3)"
615
+ r="$(session sinkenv-udd "$PROXY" --env PLAYWRIGHT_MCP_USER_DATA_DIR=/tmp/p --send "$INIT" --end eof --expect-death)"
616
616
  assert_refused_start 'FR5: PLAYWRIGHT_MCP_USER_DATA_DIR refuses to start (the env form of an audited argv setting)' "$r" 'PLAYWRIGHT_MCP_USER_DATA_DIR'
617
- r="$(session sinkenv-snap "$PROXY" --env PLAYWRIGHT_MCP_SNAPSHOT_MODE=full --send "$INIT" --end eof --timeout 3)"
617
+ r="$(session sinkenv-snap "$PROXY" --env PLAYWRIGHT_MCP_SNAPSHOT_MODE=full --send "$INIT" --end eof --expect-death)"
618
618
  assert_refused_start 'FR5: PLAYWRIGHT_MCP_SNAPSHOT_MODE refuses to start (the proxy owns the flag)' "$r" 'PLAYWRIGHT_MCP_SNAPSHOT_MODE'
619
619
  # a bare valued flag as the LAST server arg would bind an appended
620
620
  # `--user-data-dir=`/`--snapshot-mode` token as its value — refused, whatever
621
621
  # the flag
622
- r="$(session trailcfg "$PROXY" --send "$INIT" --end eof --timeout 3 --server python3 "$STUB" --config)"
622
+ r="$(session trailcfg "$PROXY" --send "$INIT" --end eof --expect-death --server python3 "$STUB" --config)"
623
623
  assert_refused_start 'FR5: a trailing --config would swallow an appended flag into its value — refuses' "$r" 'expects a value'
624
- r="$(session trailua "$PROXY" --send "$INIT" --end eof --timeout 3 --server python3 "$STUB" --user-agent)"
624
+ r="$(session trailua "$PROXY" --send "$INIT" --end eof --expect-death --server python3 "$STUB" --user-agent)"
625
625
  assert_refused_start 'FR5: a trailing --user-agent (valued, not a sink) refuses the same way' "$r" 'expects a value'
626
626
  r="$(session midua "$PROXY" --send "$INIT" --send "$SNAP" --end eof --server python3 "$STUB" --user-agent suite-ua)"
627
627
  assert_redacted_result 'FR5 companion: a valued flag with its value mid-argv starts and serves' "$r" 3 1
628
628
  # the config-file forms of the same sinks (audited argv stays allowed)
629
629
  printf '{"saveTrace": true}\n' > "$WORK/cfg-trace.json"
630
- r="$(session cfgtrace "$PROXY" --send "$INIT" --end eof --timeout 3 --server python3 "$STUB" --config="$WORK/cfg-trace.json")"
630
+ r="$(session cfgtrace "$PROXY" --send "$INIT" --end eof --expect-death --server python3 "$STUB" --config="$WORK/cfg-trace.json")"
631
631
  assert_refused_start 'FR5: config saveTrace refuses to start' "$r" 'saveTrace'
632
632
  printf '{"secrets": "/tmp/s.json"}\n' > "$WORK/cfg-secrets.json"
633
- r="$(session cfgsecrets "$PROXY" --send "$INIT" --end eof --timeout 3 --server python3 "$STUB" --config="$WORK/cfg-secrets.json")"
633
+ r="$(session cfgsecrets "$PROXY" --send "$INIT" --end eof --expect-death --server python3 "$STUB" --config="$WORK/cfg-secrets.json")"
634
634
  assert_refused_start 'FR5: config secrets refuses to start' "$r" 'secrets'
635
635
  printf '{"outputDir": "/tmp/o"}\n' > "$WORK/cfg-outdir.json"
636
- r="$(session cfgoutdir "$PROXY" --send "$INIT" --end eof --timeout 3 --server python3 "$STUB" --config="$WORK/cfg-outdir.json")"
636
+ r="$(session cfgoutdir "$PROXY" --send "$INIT" --end eof --expect-death --server python3 "$STUB" --config="$WORK/cfg-outdir.json")"
637
637
  assert_refused_start 'FR5: config outputDir refuses to start' "$r" 'outputDir'
638
638
  printf '{"allowUnrestrictedFileAccess": true}\n' > "$WORK/cfg-aufa.json"
639
- r="$(session cfgaufa "$PROXY" --send "$INIT" --end eof --timeout 3 --server python3 "$STUB" --config="$WORK/cfg-aufa.json")"
639
+ r="$(session cfgaufa "$PROXY" --send "$INIT" --end eof --expect-death --server python3 "$STUB" --config="$WORK/cfg-aufa.json")"
640
640
  assert_refused_start 'FR5: config allowUnrestrictedFileAccess refuses to start' "$r" 'allowUnrestrictedFileAccess'
641
641
  printf '{"extension": true}\n' > "$WORK/cfg-ext.json"
642
- r="$(session cfgext "$PROXY" --send "$INIT" --end eof --timeout 3 --server python3 "$STUB" --config="$WORK/cfg-ext.json")"
642
+ r="$(session cfgext "$PROXY" --send "$INIT" --end eof --expect-death --server python3 "$STUB" --config="$WORK/cfg-ext.json")"
643
643
  assert_refused_start 'FR5: config extension refuses to start' "$r" 'extension'
644
644
  printf '{"browser": {"cdpEndpoint": "ws://127.0.0.1:1"}}\n' > "$WORK/cfg-cdp.json"
645
- r="$(session cfgcdp "$PROXY" --send "$INIT" --end eof --timeout 3 --server python3 "$STUB" --config="$WORK/cfg-cdp.json")"
645
+ r="$(session cfgcdp "$PROXY" --send "$INIT" --end eof --expect-death --server python3 "$STUB" --config="$WORK/cfg-cdp.json")"
646
646
  assert_refused_start 'FR5: config browser.cdpEndpoint refuses to start' "$r" 'cdpEndpoint'
647
647
  printf '{"browser": {"initScript": "/tmp/x.js"}}\n' > "$WORK/cfg-initscript.json"
648
- r="$(session cfginitscript "$PROXY" --send "$INIT" --end eof --timeout 3 --server python3 "$STUB" --config="$WORK/cfg-initscript.json")"
648
+ r="$(session cfginitscript "$PROXY" --send "$INIT" --end eof --expect-death --server python3 "$STUB" --config="$WORK/cfg-initscript.json")"
649
649
  assert_refused_start 'FR5: config browser.initScript refuses to start' "$r" 'initScript'
650
650
  printf '{"browser": {"launchOptions": {"executablePath": "/tmp/x"}}}\n' > "$WORK/cfg-execpath.json"
651
- r="$(session cfgexecpath "$PROXY" --send "$INIT" --end eof --timeout 3 --server python3 "$STUB" --config="$WORK/cfg-execpath.json")"
651
+ r="$(session cfgexecpath "$PROXY" --send "$INIT" --end eof --expect-death --server python3 "$STUB" --config="$WORK/cfg-execpath.json")"
652
652
  assert_refused_start 'FR5: config browser.launchOptions.executablePath refuses to start' "$r" 'executablePath'
653
653
  printf '{"browser": {"launchOptions": {"args": ["--remote-debugging-port=9222"]}}}\n' > "$WORK/cfg-rdbg.json"
654
- r="$(session cfgrdbg "$PROXY" --send "$INIT" --end eof --timeout 3 --server python3 "$STUB" --config="$WORK/cfg-rdbg.json")"
654
+ r="$(session cfgrdbg "$PROXY" --send "$INIT" --end eof --expect-death --server python3 "$STUB" --config="$WORK/cfg-rdbg.json")"
655
655
  assert_refused_start 'FR5: config launchOptions.args with a remote debugging flag refuses to start' "$r" 'remote debugging'
656
656
  printf '{"browser": {"userDataDir": "/tmp/p"}}\n' > "$WORK/cfg-udd.json"
657
- r="$(session cfgudd "$PROXY" "${UDD[@]}" --env "XDG_CACHE_HOME=$WORK/xdg" --send "$INIT" --end eof --timeout 3 --server python3 "$STUB" --config="$WORK/cfg-udd.json")"
657
+ r="$(session cfgudd "$PROXY" "${UDD[@]}" --env "XDG_CACHE_HOME=$WORK/xdg" --send "$INIT" --end eof --expect-death --server python3 "$STUB" --config="$WORK/cfg-udd.json")"
658
658
  assert_refused_start 'FR5: config browser.userDataDir conflicts with --user-data-dir-name' "$r" 'userDataDir'
659
659
  r="$(session cfgudd-ok "$PROXY" --send "$INIT" --send "$SNAP" --end eof --server python3 "$STUB" --config="$WORK/cfg-udd.json")"
660
660
  assert_redacted_result 'FR5 companion: config browser.userDataDir WITHOUT the profile flag is the audited form — starts and serves' "$r" 3 1
661
661
  printf '{"browser": {"contextOptions": {"storageState": "/tmp/s.json"}}}\n' > "$WORK/cfg-ctxstorage.json"
662
- r="$(session cfgctxstorage "$PROXY" --send "$INIT" --end eof --timeout 3 --server python3 "$STUB" --config="$WORK/cfg-ctxstorage.json")"
662
+ r="$(session cfgctxstorage "$PROXY" --send "$INIT" --end eof --expect-death --server python3 "$STUB" --config="$WORK/cfg-ctxstorage.json")"
663
663
  assert_refused_start 'FR5: config browser.contextOptions.storageState refuses to start' "$r" 'storageState'
664
664
  printf '{"browser": {"contextOptions": {"permissions": ["clipboard-read"]}}}\n' > "$WORK/cfg-ctxperms.json"
665
- r="$(session cfgctxperms "$PROXY" --send "$INIT" --end eof --timeout 3 --server python3 "$STUB" --config="$WORK/cfg-ctxperms.json")"
665
+ r="$(session cfgctxperms "$PROXY" --send "$INIT" --end eof --expect-death --server python3 "$STUB" --config="$WORK/cfg-ctxperms.json")"
666
666
  assert_refused_start 'FR5: config browser.contextOptions.permissions refuses to start' "$r" 'permissions'
667
667
  printf '{"browser": {"contextOptions": {"ignoreHTTPSErrors": true}}}\n' > "$WORK/cfg-ctxhttps.json"
668
- r="$(session cfgctxhttps "$PROXY" --send "$INIT" --end eof --timeout 3 --server python3 "$STUB" --config="$WORK/cfg-ctxhttps.json")"
668
+ r="$(session cfgctxhttps "$PROXY" --send "$INIT" --end eof --expect-death --server python3 "$STUB" --config="$WORK/cfg-ctxhttps.json")"
669
669
  assert_refused_start 'FR5: config browser.contextOptions.ignoreHTTPSErrors refuses to start' "$r" 'ignoreHTTPSErrors'
670
670
  printf '{"browser": {"launchOptions": {"chromiumSandbox": false}}}\n' > "$WORK/cfg-sandbox.json"
671
- r="$(session cfgsandbox "$PROXY" --send "$INIT" --end eof --timeout 3 --server python3 "$STUB" --config="$WORK/cfg-sandbox.json")"
671
+ r="$(session cfgsandbox "$PROXY" --send "$INIT" --end eof --expect-death --server python3 "$STUB" --config="$WORK/cfg-sandbox.json")"
672
672
  assert_refused_start 'FR5: config browser.launchOptions.chromiumSandbox refuses to start' "$r" 'chromiumSandbox'
673
- r="$(session traildaemon "$PROXY" --send "$INIT" --end eof --timeout 3 --server python3 "$STUB" --daemon)"
673
+ r="$(session traildaemon "$PROXY" --send "$INIT" --end eof --expect-death --server python3 "$STUB" --daemon)"
674
674
  assert_refused_start 'FR5: a trailing --daemon (hidden valued option) would swallow an appended flag — refuses' "$r" 'expects a value'
675
675
  usage_rc=0; python3 "$PROXY" -- </dev/null >"$WORK/usage.out" 2>"$WORK/usage.err" || usage_rc=$?
676
676
  assert_true 'B1: empty server argv → exit 2, usage on stderr, nothing on stdout' bash -c '[[ "$1" == "2" ]] && grep -qF "refusing to start: usage:" "$2" && [[ ! -s "$3" ]]' _ "$usage_rc" "$WORK/usage.err" "$WORK/usage.out"
@@ -690,13 +690,13 @@ open(sys.argv[2], "w").write(src.replace(old, new))
690
690
  PY
691
691
  }
692
692
  mk_variant "$WORK/v-identity" "$PROXY" 'def redact_text(text: str) -> str:' 'def redact_text(text: str) -> str:\n return text\n\ndef _unused_redact_text(text: str) -> str:'
693
- r="$(session v-identity "$WORK/v-identity/proxy.py" --send "$INIT" --end eof --timeout 3)"
693
+ r="$(session v-identity "$WORK/v-identity/proxy.py" --send "$INIT" --end eof --expect-death)"
694
694
  assert_refused_start 'FR6: a redactor returning its input unchanged → refuse to start' "$r" 'self-test'
695
695
  mk_variant "$WORK/v-append" "$PROXY" 'def redact_text(text: str) -> str:' 'def redact_text(text: str) -> str:\n return text + " <redacted>"\n\ndef _unused_redact_text(text: str) -> str:'
696
- r="$(session v-append "$WORK/v-append/proxy.py" --send "$INIT" --end eof --timeout 3)"
696
+ r="$(session v-append "$WORK/v-append/proxy.py" --send "$INIT" --end eof --expect-death)"
697
697
  assert_refused_start 'FR6/row 9 input: a redactor appending <redacted> without removing the value → refuse' "$r" 'self-test'
698
698
  mk_variant "$WORK/v-false" "$PROXY" 'def looks_like_a11y_tree(value: str) -> bool:' 'def looks_like_a11y_tree(value: str) -> bool:\n return False\n\ndef _unused_looks(value: str) -> bool:'
699
- r="$(session v-false "$WORK/v-false/proxy.py" --send "$INIT" --end eof --timeout 3)"
699
+ r="$(session v-false "$WORK/v-false/proxy.py" --send "$INIT" --end eof --expect-death)"
700
700
  assert_refused_start 'FR6/row 29 input: constant-False looks_like_a11y_tree → refuse' "$r" 'looks_like_a11y_tree'
701
701
  # raises only for long text, so the startup self-test passes (row 34's input)
702
702
  mk_variant "$WORK/v-raise" "$PROXY" 'def redact_text(text: str) -> str:' 'def redact_text(text: str) -> str:\n if len(text) > 200:\n raise RuntimeError("boom")\n return _orig_redact_text(text)\n\ndef _orig_redact_text(text: str) -> str:'
@@ -707,33 +707,49 @@ assert_stderr_marker 'FR7b: one withheld line for the raise' "$r" 'withheld tool
707
707
  assert_rc 'FR7: the session ended cleanly after the withhold' "$r" 0
708
708
  # the four-name contract: a redactor missing one name → refuse, not a per-result withhold
709
709
  mk_variant "$WORK/v-rename" "$PROXY" 'def main() -> None:' 'del looks_like_a11y_tree\n\n\ndef main() -> None:'
710
- r="$(session v-rename "$WORK/v-rename/proxy.py" --send "$INIT" --end eof --timeout 3)"
710
+ r="$(session v-rename "$WORK/v-rename/proxy.py" --send "$INIT" --end eof --expect-death)"
711
711
  assert_refused_start 'B2: a redactor missing one of the four names → refuse (not a per-result withhold)' "$r" 'does not export looks_like_a11y_tree'
712
712
  # Startup refusal (slice 1 of #10331): the startup handlers catch (Exception, SystemExit, KeyboardInterrupt), not BaseException. Each excluded
713
713
  # class must still refuse with rc 2 and the unchanged reason line, at every member: the import, the redact_text call and the
714
714
  # looks_like_a11y_tree call in the self-test (a case for one member does not cover the other).
715
715
  for cls in SystemExit KeyboardInterrupt; do
716
716
  mk_variant "$WORK/v-load-$cls" "$PROXY" 'def main() -> None:' "raise $cls()\n\n\ndef main() -> None:"
717
- r="$(session "v-load-$cls" "$WORK/v-load-$cls/proxy.py" --send "$INIT" --end eof --timeout 3)"
717
+ r="$(session "v-load-$cls" "$WORK/v-load-$cls/proxy.py" --send "$INIT" --end eof --expect-death)"
718
718
  assert_refused_start "G1: a redactor raising $cls at import → refuse to start (rc 2), class named" "$r" "cannot load redact-a11y-snapshot.py: $cls"
719
719
  done
720
720
  mk_variant "$WORK/v-st-redact" "$PROXY" 'def redact_text(text: str) -> str:' 'def redact_text(text: str) -> str:\n raise KeyboardInterrupt()\n\ndef _unused_redact_text(text: str) -> str:'
721
- r="$(session v-st-redact "$WORK/v-st-redact/proxy.py" --send "$INIT" --end eof --timeout 3)"
721
+ r="$(session v-st-redact "$WORK/v-st-redact/proxy.py" --send "$INIT" --end eof --expect-death)"
722
722
  assert_refused_start 'G1: redact_text raising KeyboardInterrupt in the self-test → refuse (rc 2), class named' "$r" 'redactor self-test raised KeyboardInterrupt'
723
723
  mk_variant "$WORK/v-st-redact-exit" "$PROXY" 'def redact_text(text: str) -> str:' 'def redact_text(text: str) -> str:\n raise SystemExit(3)\n\ndef _unused_redact_text(text: str) -> str:'
724
- r="$(session v-st-redact-exit "$WORK/v-st-redact-exit/proxy.py" --send "$INIT" --end eof --timeout 3)"
724
+ r="$(session v-st-redact-exit "$WORK/v-st-redact-exit/proxy.py" --send "$INIT" --end eof --expect-death)"
725
725
  assert_refused_start 'G1: redact_text raising SystemExit in the self-test → refuse (rc 2), class named' "$r" 'redactor self-test raised SystemExit'
726
726
  mk_variant "$WORK/v-st-looks" "$PROXY" 'def looks_like_a11y_tree(value: str) -> bool:' 'def looks_like_a11y_tree(value: str) -> bool:\n raise KeyboardInterrupt()\n\ndef _unused_looks(value: str) -> bool:'
727
- r="$(session v-st-looks "$WORK/v-st-looks/proxy.py" --send "$INIT" --end eof --timeout 3)"
727
+ r="$(session v-st-looks "$WORK/v-st-looks/proxy.py" --send "$INIT" --end eof --expect-death)"
728
728
  assert_refused_start 'G1: looks_like_a11y_tree raising KeyboardInterrupt in the self-test → refuse (rc 2), class named' "$r" 'looks_like_a11y_tree raised KeyboardInterrupt'
729
729
  mk_variant "$WORK/v-st-looks-exit" "$PROXY" 'def looks_like_a11y_tree(value: str) -> bool:' 'def looks_like_a11y_tree(value: str) -> bool:\n raise SystemExit(3)\n\ndef _unused_looks(value: str) -> bool:'
730
- r="$(session v-st-looks-exit "$WORK/v-st-looks-exit/proxy.py" --send "$INIT" --end eof --timeout 3)"
730
+ r="$(session v-st-looks-exit "$WORK/v-st-looks-exit/proxy.py" --send "$INIT" --end eof --expect-death)"
731
731
  assert_refused_start 'G1: looks_like_a11y_tree raising SystemExit in the self-test → refuse (rc 2), class named' "$r" 'looks_like_a11y_tree raised SystemExit'
732
732
  # The narrowing is deliberate: a BaseException subclass outside (Exception, SystemExit, KeyboardInterrupt) is not caught, so the proxy
733
733
  # dies with rc 1 and a traceback. Still fail-closed (no stdout line, no child) -- pinned so widening back to BaseException (rc 2) is a decision.
734
734
  mk_variant "$WORK/v-load-outside" "$PROXY" 'def main() -> None:' "class _OutsideBase(BaseException):\n pass\n\n\nraise _OutsideBase()\n\n\ndef main() -> None:"
735
- r="$(session v-load-outside "$WORK/v-load-outside/proxy.py" --send "$INIT" --end eof --timeout 3)"
736
- assert_true 'G1: a BaseException subclass outside the narrowed tuple at import → rc 1, nothing relayed, no child spawned' bash -c '[[ "$(rcof "$1")" == "1" && "$(fact "$1" 0 lines)" == "0" && -z "$(cat "$1/child_pgid" 2>/dev/null)" ]] && stderr_has "$1" "_OutsideBase"' _ "$r"
735
+ r="$(session v-load-outside "$WORK/v-load-outside/proxy.py" --send "$INIT" --end eof --expect-death)"
736
+ # Split per-observable (#10919): a recurrence must name the failed conjunct, not
737
+ # just the label — the 4-in-1 assert_true is what made #10913 undiagnosable.
738
+ assert_rc 'G1: a BaseException subclass outside the narrowed tuple at import → proxy exit 1' "$r" 1
739
+ assert_true 'G1: … nothing relayed (zero stdout lines)' bash -c '[[ "$(fact "$1" 0 lines)" == "0" ]]' _ "$r"
740
+ assert_true 'G1: … no child spawned' bash -c '[[ -z "$(cat "$1/child_pgid" 2>/dev/null)" ]]' _ "$r"
741
+ assert_true 'G1: … _OutsideBase named on stderr' bash -c 'stderr_has "$1" "_OutsideBase"' _ "$r"
742
+ # Mechanism proof for the deadline→verdict decoupling (AC-2): a redactor whose
743
+ # module top sleeps well past BOTH 3s deadline windows (10s — the measured stall
744
+ # was 6.07s; margin covers driver descheduling between the phase-A deadline and
745
+ # computing t_end) then raises _OutsideBase must still record TIMEOUT under --timeout 3
746
+ # (negative control pinning the deadline arm) and the real rc 1 under
747
+ # --expect-death.
748
+ mk_variant "$WORK/v-load-outside-slow" "$PROXY" 'def main() -> None:' "import time\ntime.sleep(10)\n\n\nclass _OutsideBase(BaseException):\n pass\n\n\nraise _OutsideBase()\n\n\ndef main() -> None:"
749
+ r="$(session v-load-outside-slow-deadline "$WORK/v-load-outside-slow/proxy.py" --send "$INIT" --end eof --timeout 3)"
750
+ assert_rc 'G1b: negative control — a >6s-window slow death (10s for margin) still records TIMEOUT under --timeout 3' "$r" TIMEOUT
751
+ r="$(session v-load-outside-slow "$WORK/v-load-outside-slow/proxy.py" --send "$INIT" --end eof --expect-death)"
752
+ assert_rc 'G1b: the same slow death under --expect-death records the real exit 1' "$r" 1
737
753
  # FR13 behavioural: a SPY redactor tags every string it is handed. Every text block
738
754
  # the proxy delivers must carry the tag — a proxy-side predicate that skips some
739
755
  # text (a literal, a tool-name allowlist, any spelling of either) cannot hide.
@@ -1146,9 +1162,9 @@ if [[ -n "$MUTANT_PATH" ]]; then r="$(session m60 "$MUTANT_PATH" "${UDD[@]}" --e
1146
1162
  mutant 61-udd-conflict-ok refuse_argv_and_env ' if profile_dir is not None and (argv_values(server, "--user-data-dir") or flag_present(server, "--user-data-dir")):' ' if False:'
1147
1163
  if [[ -n "$MUTANT_PATH" ]]; then r="$(session m61 "$MUTANT_PATH" "${UDD[@]}" --send "$INIT" --end eof --timeout 3 --server python3 "$STUB" --user-data-dir=/x)"; red 'row 61: the conflict check removed → flag + explicit --user-data-dir starts the proxy' started "$r"; fi
1148
1164
  mutant 62-udd-nohome-ok profile_dir_from_args ' if not home:' ' if False:'
1149
- if [[ -n "$MUTANT_PATH" ]]; then r="$(session m62 "$MUTANT_PATH" "${UDD[@]}" --env XDG_CACHE_HOME= --env HOME= --send "$INIT" --end eof --timeout 3)"; red 'row 62: the HOME check removed → the refusal no longer names the setting' bash -c 'started "$1" || ! stderr_has "$1" "HOME is set"' _ "$r"; fi
1165
+ if [[ -n "$MUTANT_PATH" ]]; then r="$(session m62 "$MUTANT_PATH" "${UDD[@]}" --env XDG_CACHE_HOME= --env HOME= --send "$INIT" --end eof --expect-death)"; red 'row 62: the HOME check removed → the refusal no longer names the setting' bash -c 'started "$1" || { stderr_has "$1" "refusing to start" && ! stderr_has "$1" "HOME is set"; }' _ "$r"; fi
1150
1166
  mutant 63-udd-missing-ok profile_dir_from_args ' if i + 1 >= len(args):' ' if False:'
1151
- if [[ -n "$MUTANT_PATH" ]]; then r="$(session m63 "$MUTANT_PATH" --proxy-arg --user-data-dir-name --send "$INIT" --end eof --timeout 3)"; red 'row 63: the missing-argument check removed → a bare flag crashes instead of refusing cleanly' bash -c '[[ "$(rcof "$1")" != "2" ]] || ! stderr_has "$1" "refusing to start"' _ "$r"; fi
1167
+ if [[ -n "$MUTANT_PATH" ]]; then r="$(session m63 "$MUTANT_PATH" --proxy-arg --user-data-dir-name --send "$INIT" --end eof --expect-death)"; red 'row 63: the missing-argument check removed → a bare flag crashes instead of refusing cleanly' bash -c '[[ "$(rcof "$1")" != "2" ]] || ! stderr_has "$1" "refusing to start"' _ "$r"; fi
1152
1168
  mutant 64-udd-dup-ok profile_dir_from_args ' if name is not None:' ' if False:'
1153
1169
  if [[ -n "$MUTANT_PATH" ]]; then r="$(session m64 "$MUTANT_PATH" --proxy-arg --user-data-dir-name --proxy-arg a --proxy-arg --user-data-dir-name=b --send "$INIT" --end eof --timeout 3)"; red 'row 64: the duplicate-flag check removed → the second value silently wins' started "$r"; fi
1154
1170
  mutant 65-udd-dash-ok profile_dir_from_args ' if name.startswith("-"):' ' if False:'