-
Notifications
You must be signed in to change notification settings - Fork 697
Expand file tree
/
Copy pathconftest.py
More file actions
4520 lines (3894 loc) · 233 KB
/
Copy pathconftest.py
File metadata and controls
4520 lines (3894 loc) · 233 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
"""Repo-root pytest configuration: the host-mutation floor.
``test/conftest.py`` holds the bulk of the suite's isolation, but it only applies
to ``test/``. ``[tool:pytest] testpaths`` also collects ``transfer`` and
``src/kiro_crew/apps/builtins`` (~108 test modules that ship inside the package,
next to the code they cover), and those get no ``test/conftest.py`` fixtures at
all -- only this file, plus that app's own ``tests/conftest.py`` where one exists.
Anything that must hold for EVERY test therefore has to live here, at the
rootdir, which is the one conftest pytest applies to every testpath.
Only the HOST-MUTATION FLOOR belongs in this file: the guards that must hold for a
test collected from any testpath, because what they protect is the
developer's machine rather than the correctness of one suite. Everything that is
merely suite-specific isolation stays in ``test/conftest.py``.
The floor has eight parts, and each one exists because the "remember to isolate
this" contract failed at least once:
* **Services.** ``$XDG_CONFIG_HOME`` is redirected and the stdlib spawn funnels
refuse a ``systemctl``/``launchctl`` invocation carrying a mutating verb, so no
test can reconfigure or restart the operator's real gateway (issue #1722).
* **The data home.** ``KIROCREW_HOME`` is pinned per test, and the ``~/.kiro``
paths that production binds at IMPORT time (which the env var cannot reach) are
pinned with it. Without this, the ~108 test modules that ship inside the package
under ``src/kiro_crew/apps/builtins/*/tests/`` -- which see this conftest and no
other -- write the operator's live ``~/.kiro/crew`` the moment they touch
``config_dir()``. ``KIROCREW_WORKSPACE`` is pinned alongside it for the same
reason: ``workspace_root()`` is a SEPARATE default from ``config_dir()`` (it
falls back to ``~/workplace/kirocrew-workspace``, not under the data home at
all), so pinning only ``KIROCREW_HOME`` leaves it resolving to the operator's
real ``~/workplace`` the moment a test reaches an agent working directory.
* **Credential environment.** Recognised fixed credentials and validated
``JIRA_TOKEN_<HEX>`` keys are restored after every test, so a fabricated
``.env`` cannot silently override the next test's credentials in the same
worker.
* **The inherited shell environment.** The entries ``name_grant`` refuses as
inherited preloads are removed for each test's duration and restored
afterwards: the ``_ENV_PRELOAD_VARS`` names (``BASH_ENV``, ``ENV``, ...) and
exported bash functions (``BASH_FUNC_*`` keys, or the legacy bare-name
spelling whose value starts with ``() {``). RHEL-family hosts export ``which``
as a function from ``/etc/profile.d/which2.sh``, and that refusal is checked
before every narrower code, so 79 of 163 name-grant tests observed the wrong
refusal on those hosts while Ubuntu CI stayed green (issue #8395).
* **The agent-spec home.** ``kiro_agents_dir()`` is a LAZY resolver, so neither of
the two above reaches it, and a test that reaches the spec write path rewrites
the machine-wide ``<kiro home>/agents/kirocrew.json`` -- the file that decides
which MCP servers the operator's real agent has (issue #4912). The per-module
override seams are pinned instead of ``KIRO_HOME``, which cannot be pinned
without overriding ~35 tests' own ``Path.home()`` isolation.
* **The system temp directory.** ``tempfile``'s base is redirected to a per-run
directory for the whole process, so a bare ``mkdtemp()`` whose cleanup is missing
or skipped leaves its directory somewhere this run owns and removes, instead of
accumulating in the shared temp root forever. What was left behind is REPORTED
first, so the leak is a red rather than silent inode consumption. On macOS the
base is additionally moved to ``/tmp`` -- the prefix Linux and CI already use --
because the launchd per-user temp dir is long enough to break an AF_UNIX bind and
random enough to read as a credential. See :data:`_SHORT_TMP_BASE`.
* **The sandbox mount-source sweep.** The periodic janitor's candidate roots
(the operator's real ``/run/user/$UID`` and ``/dev/shm``) and its /proc pin
scan are both pinned to inert stand-ins -- the roots to an empty directory,
the scan to fail-closed -- so no test deletes real orphaned bind-mount
sources from the developer's machine or depends on host process state.
* **The repository checkout.** The run fails when it ends with residue anywhere in
the checkout, which is how a subprocess spawned without ``cwd=`` announces
itself.
One consequence of living at the rootdir: the module name ``conftest`` is now
resolvable from the repository root as well as from ``test/``, and 11 test modules
import helpers by bare name (``from conftest import requires_git``). Under pytest's
default ``prepend`` import mode each test module's own directory goes on
``sys.path`` first, so those imports still bind to ``test/conftest.py`` -- but the
name IS shadowed, so switching to ``--import-mode=importlib`` would need those
imports made explicit first. The visible effect today is that isort now classifies
``conftest`` as first-party (a root-level module is inside its default
``src_paths``), which is why this change also reorders that import in the modules
that use it.
Why this floor exists (issue #1722): a test asserting that a staged cutover can
be *cancelled* rewrote the operator's real ``kirocrew-gateway.service`` drop-in
to point at its own pytest temp dir. pytest deleted the temp dir at the end of
the run; the drop-in survived, so systemd looped on ``203/EXEC`` — 548 failed
starts over 25 minutes. The test never intended to touch the host: it called the
real ``_make_live()`` because that function was the subject under test, and two
of that function's seams (the drop-in path and the subprocess layer) were left
for each test to remember to stub.
The fixtures below remove that "remember to" from the contract. None of them
changes the behaviour of a test that already isolates itself correctly: every one
sets a value a test can still override, and a test that sets its own
``KIROCREW_HOME`` or its own temp dir keeps winning.
Imports at MODULE level are stdlib + pytest only, on purpose: a rootdir conftest is
imported before every collection, so pulling ``kiro_crew`` in here would make the
whole suite depend on import-time side effects of the package under test. The
fixtures that do need ``kiro_crew`` import it in their own body, which runs at test
setup -- by which point the test module has already imported the package anyway --
and tolerate an ImportError so a partial checkout cannot break collection.
"""
from __future__ import annotations
import asyncio
import asyncio.base_events
import atexit
import contextlib
import functools
import gc
import getpass
import importlib
import linecache
import logging
import os
import pathlib
import shutil
import subprocess
import sys
import tempfile
import threading
import time
import warnings
import _pytest.outcomes
import _pytest.runner
import pytest
# ── ACP frame recorder switch (rootdir floor) ───────────────────────────────
# ``kiro_crew.acp._frame_record`` starts its writer thread at IMPORT time when
# ``KIROCREW_ACP_RECORD_FRAMES`` is set, and ``acp.client`` (hence the dashboard
# server, the session handle, the Slack handler, ...) imports it transitively. An
# operator running the suite with a live recording configured would otherwise
# have every collected test module's fake frames appended to their real corpus
# file -- from ANY testpath, including the ~108 modules under
# ``src/kiro_crew/apps/builtins/*/tests/`` that never see ``test/conftest.py``.
# Cleared here, at rootdir-conftest import, which precedes every test module's
# first ``kiro_crew`` import; tests that need the switch set it themselves
# through monkeypatch.
os.environ.pop("KIROCREW_ACP_RECORD_FRAMES", None)
# ── how this run names the directories it leaves in the platform temp root ────
#
# Defined up here, far from :func:`_create_tmp_root` and the rest of the temp-base
# section, because the import-time data-home floor below is created BEFORE any of
# that runs and has to be named by the same stem: a stray directory is attributable
# to the run that made it or it is attributable to nobody.
#: Prefix for the run's own temp base, a sibling of the platform temp root.
#:
#: The name is ``kc-pytest-<user>-<pid>``. The pid is what lets a later run tell an
#: ABANDONED root (its process is gone) from one a concurrent run is still using. The
#: user segment is not decoration: on POSIX the platform temp root is SHARED between
#: accounts, so a bare pid collides across users -- two accounts can hold the same pid
#: at the same time, and the second would try to reuse a directory it cannot write.
#: Windows gives each account its own temp root, so there the segment is redundant and
#: harmless.
_TMP_ROOT_PREFIX = "kc-pytest-"
def _tmp_root_prefix_for_run() -> str:
"""``kc-pytest-<user>-<pid>-`` -- the stem this run's temp root is created under.
The user segment is not decoration: on POSIX the platform temp root is SHARED between
accounts, so a bare pid collides across users -- two accounts can hold the same pid at
the same time. The pid is what lets a later run tell an ABANDONED root from one a
concurrent run is still using. The trailing hyphen is where ``mkdtemp`` appends its
random component; see :func:`_create_tmp_root` for why that randomness is required and
not cosmetic.
"""
try:
raw = getpass.getuser()
except Exception: # noqa: BLE001 - no passwd entry and no env fallback
raw = "u"
user = "".join(ch if ch.isalnum() else "_" for ch in raw)[:24] or "u"
return f"{_TMP_ROOT_PREFIX}{user}-{os.getpid()}-"
# ── The data-home floor, installed at IMPORT time ────────────────────────────
#
# Every per-test redirect of ``KIROCREW_HOME`` is a value laid over whatever the
# process started with, so the danger is never a wrong value -- it is the ABSENCE
# of one. ``config.paths._valid_override_home`` reads the variable and, finding it
# unset, falls through to ``_default_home()``: the operator's real ``~/.kiro/crew``.
# Anything that can return the variable to "unset" mid-session therefore aims the
# whole suite at live data, and a single ``monkeypatch.undo()`` on the shared
# function-scoped instance does exactly that -- it reverts every patch on that
# stack, including a redirect some fixture installed. One such undo truncated an
# operator's real 36 MB ``memory.db`` to 29 bytes, with no backup.
#
# Setting the variable HERE, before pytest imports a fixture or a test module,
# removes "unset" from the set of reachable states: an undo restores the value the
# process started with, which is now this scratch directory rather than nothing.
# That closes the entire class at the process level, so it holds no matter which
# fixture owns a redirect, whether that fixture used a private ``MonkeyPatch``, or
# what a future test does to the shared one.
#
# An operator's own ``KIROCREW_HOME`` is honoured untouched: someone running the
# suite against a deliberately chosen home keeps it, and only the unset case --
# the one that resolves to live data -- is given a floor.
# ``mkdtemp``, never ``mkdir(exist_ok=True)`` on a pid-derived name -- the same rule
# :func:`_create_tmp_root` states and for the same reason: a pid-derived name is
# PREDICTABLE, so another local account can pre-create it as a SYMLINK to a directory
# it controls, and ``exist_ok=True`` succeeds against a symlink-to-directory. This
# directory becomes the data home the whole session falls back to, so every secret,
# token and ``memory.db`` the suite fabricates would land where that account chose.
# ``mkdtemp`` creates with O_EXCL at mode 0700 and fails rather than adopting.
# These literals are also the session guard's source of truth below. They must
# exist before choosing the import-time floor so even an inherited temp root
# inside live memory cannot influence that choice.
_REAL_DATA_HOMES = (
pathlib.Path.home() / ".kiro" / "crew",
pathlib.Path.home() / ".kirocrew",
)
def _create_host_home_floor() -> pathlib.Path:
"""Create the import-time floor without consulting temp environment vars.
Named with :func:`_tmp_root_prefix_for_run` plus ``home-``, so the directory reads
``kc-pytest-<user>-<pid>-home-<random>`` and sorts beside the temp root the same run
creates later. That naming is the whole point: this floor is removed only by the
``atexit`` handler below, which a SIGKILL (a CI timeout, a watchdog, an agent-driven
run's kill) does not run, and ``_tmp_residue`` scans only under the run's OWN temp
base -- so a surviving floor is invisible to the residue report and the account plus
pid in its name is the only signal a human has for attributing it. The earlier
``kirocrew-test-floor-`` name identified neither the account nor the run, so a stray
one could not be told from another user's or from a run still in progress.
"""
if os.name == "nt":
candidates = (
pathlib.Path.home() / "AppData" / "Local" / "Temp",
pathlib.Path(os.environ.get("SystemRoot", r"C:\Windows")) / "Temp",
pathlib.Path(os.environ.get("ProgramData", r"C:\ProgramData")),
)
else:
candidates = (pathlib.Path("/tmp"), pathlib.Path("/var/tmp"), pathlib.Path("/usr/tmp"))
for candidate in candidates:
try:
base = candidate.resolve(strict=True)
except OSError:
continue
unsafe = False
for real in _REAL_DATA_HOMES:
try:
live = real.resolve()
except OSError:
live = real.absolute()
if base == live or live in base.parents or base in live.parents:
unsafe = True
break
if unsafe or not base.is_dir():
continue
try:
prefix = f"{_tmp_root_prefix_for_run()}home-"
return pathlib.Path(tempfile.mkdtemp(dir=base, prefix=prefix))
except OSError:
continue
raise RuntimeError("no writable system temp directory exists outside live Kiro Crew homes")
_HOST_HOME_FLOOR: "pathlib.Path | None" = None
if not os.environ.get("KIROCREW_HOME"):
_HOST_HOME_FLOOR = _create_host_home_floor()
os.environ["KIROCREW_HOME"] = str(_HOST_HOME_FLOOR)
@atexit.register
def _remove_host_home_floor() -> None:
"""Remove the floor, so it does not accumulate in the platform temp root.
This is the ONLY removal, and it is deliberately the only one. ``atexit`` is
skipped by the SIGKILL that produces the residue actually MEASURED on developer
hosts (one stray floor per aborted run), but no in-process hook does better: a
session-fixture finalizer or ``pytest_unconfigure`` is skipped by the same signal,
and under ``-n auto`` the process that OWNS the floor is the CONTROLLER -- it sets
``KIROCREW_HOME`` at conftest import, every execnet worker inherits it and so takes
the branch above as false, and the controller runs no session fixtures at all.
Sweeping OTHER runs' floors is ruled out by the same unsoundness argument
:func:`_isolate_tempfile_base` spells out for the temp root: the name is
pre-createable by another local account and a pid means nothing across PID
namespaces. So a killed run leaves TWO directories for the platform to reclaim --
the run's temp root and this floor -- and the floor leaks MORE often, because it is
created at conftest import and therefore survives even a run aborted during
collection, before any session fixture existed to make a temp root.
Nothing reports it either: ``_tmp_residue`` scans only under the run's own temp
base, and this floor is a SIBLING of that base in the platform temp root, so
neither the residue warning nor ``KIROCREW_TMP_RESIDUE_STRICT`` can see it or
anything a test wrote into it. That invisibility is why the directory carries
``_tmp_root_prefix_for_run()`` in its name.
"""
if _HOST_HOME_FLOOR is not None:
shutil.rmtree(_HOST_HOME_FLOOR, ignore_errors=True)
def _refuse_a_real_data_home() -> None:
"""Refuse to run at all if the session would resolve a real data home.
A second, independent barrier to the floor above, and the one that fails LOUD.
The floor prevents the accident; this catches the case where someone exports
``KIROCREW_HOME`` to their real home by hand -- copying a command out of a
runbook, or reusing a shell that was pointed at live data -- which no amount of
per-test isolation can distinguish from a deliberate choice.
Called from ``pytest_configure`` rather than being a second hook of that name:
a module defines one, and a duplicate silently replaces the earlier definition
instead of running alongside it.
"""
resolved = pathlib.Path(os.environ["KIROCREW_HOME"]).expanduser()
for real in _REAL_DATA_HOMES:
try:
here, live = resolved.resolve(), real.resolve()
except OSError: # an unreadable component cannot be the live home
continue
# CONTAINMENT in both directions, not equality. A PARENT is the dangerous
# miss: ``~/.kiro`` is kiro-cli's own home and resolves the whole live tree
# underneath it, and ``~`` resolves everything. A CHILD is refused too, since
# the suite deletes directories it believes it created.
if here == live or live in here.parents or here in live.parents:
raise pytest.UsageError(
f"KIROCREW_HOME points at the live data home {real}. The suite writes, "
f"truncates and deletes under it, so running here destroys real memory, "
f"sessions and config. Unset KIROCREW_HOME or point it at a scratch dir."
)
# ── Hypothesis example database (rootdir floor) ─────────────────────────────
# ``test/conftest.py`` registers the "default"/"thorough" profiles but never sets
# ``database=``, so hypothesis falls back to its own default: ``.hypothesis/examples``
# resolved against the CURRENT WORKING DIRECTORY, i.e. the repo root, for every test
# collected from ANY testpath -- including the ~108 modules under
# ``src/kiro_crew/apps/builtins/*/tests/`` that never import ``test/conftest.py`` at all.
# That writes an untracked directory into the checkout on every run (git status shows it
# under "Ignored files" since ``.gitignore`` covers it, but it still needs a place to live
# that is not the source tree).
#
# Profiles are process-global, so whichever profile is active when a ``@given`` test runs
# applies regardless of which testpath collected it. Registering a "default" profile here
# at module level would not stick: ``test/conftest.py`` re-registers "default" by name
# right after this module loads (pytest imports the rootdir conftest first, then
# ``test/conftest.py`` -- see ``pytest_load_initial_conftests``), and ``register_profile``
# without ``parent=`` builds a fresh settings object from scratch, silently resetting
# ``database`` back to the on-disk default. Doing the redirect from ``pytest_configure``
# instead runs after both conftests' module-level code, so it lands last: ``parent=`` keeps
# whatever ``max_examples``/``deadline``/health-check suppression ``test/conftest.py`` set,
# and only ``database`` is overridden.
_HYPOTHESIS_DB_DIR: str | None = None
def _redirect_hypothesis_database() -> None:
"""Point the active hypothesis profile's example database off the checkout.
Uses a stable per-user directory under the platform cache root
(``$XDG_CACHE_HOME`` or ``~/.cache``, ``kirocrew/hypothesis``) rather than
``database=None`` or a per-run temp dir: the shrunk counterexample hypothesis
saves is what makes a property-test failure replay on the NEXT run instead of
costing a full re-search, so the database has to outlive the process. What
moves is only WHERE it lives -- out of the checkout and into the same cache
tree the xdist slot files already use -- not whether it persists. Tolerates
hypothesis being absent (a partial checkout, or an environment where it was
never installed), an unwritable cache root (falls back to ``database=None``
rather than to the checkout), and no profile named "default" existing yet,
since this floor must not be the reason collection fails for a suite that
does not use hypothesis at all.
"""
global _HYPOTHESIS_DB_DIR
try:
from hypothesis import settings as _hyp_settings
from hypothesis.database import DirectoryBasedExampleDatabase
except ImportError:
return
try:
_current = _hyp_settings.get_profile(_hyp_settings._current_profile)
except Exception: # noqa: BLE001 - no active profile to inherit from
return
if _HYPOTHESIS_DB_DIR is None:
cache_root = os.environ.get("XDG_CACHE_HOME") or os.path.join(
os.path.expanduser("~"), ".cache"
)
candidate = os.path.join(cache_root, "kirocrew", "hypothesis")
try:
os.makedirs(candidate, exist_ok=True)
_HYPOTHESIS_DB_DIR = candidate
except OSError:
_HYPOTHESIS_DB_DIR = ""
# The example database is only one tenant of ``.hypothesis/``: the
# unicode-data cache (``storage_directory("unicode_data", ...)``) is written
# through hypothesis's HOME directory, which defaults to ``.hypothesis``
# under the CWD, i.e. the checkout. Move the home too, so the whole tree
# lands in the cache dir; the env var is what a spawned child reads.
if _HYPOTHESIS_DB_DIR:
try:
from hypothesis.configuration import set_hypothesis_home_dir
set_hypothesis_home_dir(_HYPOTHESIS_DB_DIR)
os.environ.setdefault("HYPOTHESIS_STORAGE_DIRECTORY", _HYPOTHESIS_DB_DIR)
except Exception: # noqa: BLE001 - an older hypothesis without the hook
pass
database = DirectoryBasedExampleDatabase(_HYPOTHESIS_DB_DIR) if _HYPOTHESIS_DB_DIR else None
_hyp_settings.register_profile(
_hyp_settings._current_profile,
parent=_current,
database=database,
)
_hyp_settings.load_profile(_hyp_settings._current_profile)
def _redirect_bytecode_cache() -> None:
"""Write every ``.pyc`` this run compiles under the per-user cache, not the checkout.
Importing a module writes its bytecode beside the source, and the suite imports
a lot that is not a package: ``scripts/*.py``, ``packaging/signing/*.py``, every
skill's ``scripts/`` helper, ``.github/scripts``, all loaded by path through
``spec_from_file_location`` + ``exec_module``. Each such import left a
``__pycache__/`` in the tree (fifteen of them per full run at the last count),
after two audits had closed individual sites with a scoped
``sys.dont_write_bytecode`` (``test/skill_script_helpers.py`` is the helper for
that). A per-site opt-out is the "remember to" contract this file exists to
delete, so the class is closed here instead: ``sys.pycache_prefix`` sends the
bytecode of EVERY import to a mirror tree under the cache root, and the env var
does the same for every interpreter a test spawns. The cache still persists
across runs (it is keyed on the absolute source path), so warm imports stay
warm; only WHERE the files land changes. Same cache root, same fallback posture
and same tolerance of an unwritable root as the hypothesis redirect above.
Runs in ``pytest_configure``, before any test module or ``test/conftest.py`` is
imported, so the test tree's own bytecode moves too.
"""
if os.environ.get("PYTHONDONTWRITEBYTECODE"):
return # nothing is written anywhere; nothing to redirect
cache_root = os.environ.get("XDG_CACHE_HOME") or os.path.join(os.path.expanduser("~"), ".cache")
candidate = os.path.join(cache_root, "kirocrew", "pycache")
try:
os.makedirs(candidate, exist_ok=True)
except OSError:
return
sys.pycache_prefix = candidate
os.environ["PYTHONPYCACHEPREFIX"] = candidate
class _AsyncFixtureScanGate:
"""Run pytest-asyncio's fixture scan only when a fixture was registered since.
pytest-asyncio 0.20.3 hooks ``pytest_pycollect_makeitem`` and, for EVERY test
function name it sees, walks EVERY fixture definition the session has registered
so far to wrap the async ones (``_preprocess_async_fixtures``). Fixtures already
wrapped are skipped by a set lookup, but the ~1,400 synchronous ones are
re-inspected with ``asyncio.iscoroutinefunction`` on each call. That is
O(tests x fixtures): cProfile of a ``--collect-only`` over this suite (112,246
tests) counted 87,244 scans x ~1,420 fixtures = 123.8 million coroutine checks,
1,098 of the 1,285 profiled seconds -- 85% of collection. Every xdist worker pays
it in full, and under coverage instrumentation each check costs ~2.3x more, which
is what made the CI shards' ~33-minute "collection" phase.
The scan's result only changes when a fixture is ADDED, and pytest funnels every
registration -- conftest, module, class, unittest, plugin -- through
``FixtureManager._register_fixture``. So this wraps that one method to raise a
dirty flag, and lets the scan through only while the flag is up. A scan on a clean
flag would iterate the same definitions and find nothing new: the async marker
(``_force_asyncio_fixture``) is set by the decorator at definition time and
``asyncio_mode`` is fixed for the run, so the skip is behaviour-preserving.
Pinned to the plugin version it patches: upstream's own fix for this (v0.25.1,
then v1.0.0) sits behind the v0.23 event-loop-scope rework this suite has not
migrated to. When pytest-asyncio moves, delete this class and the install below.
"""
def __init__(self, scan) -> None:
self._scan = scan
self.dirty = True
self.scans = 0
def mark_dirty(self) -> None:
self.dirty = True
def __call__(self, config, processed_fixturedefs) -> None:
if not self.dirty:
return
self._scan(config, processed_fixturedefs)
self.scans += 1
self.dirty = False
def _gate_pytest_asyncio_fixture_scan() -> None:
"""Install :class:`_AsyncFixtureScanGate` once per process (each xdist worker)."""
try:
import pytest_asyncio.plugin as pa
except ImportError: # pragma: no cover - plugin absent; nothing to gate
return
from _pytest.fixtures import FixtureManager
scan = getattr(pa, "_preprocess_async_fixtures", None)
register = getattr(FixtureManager, "_register_fixture", None)
if isinstance(scan, _AsyncFixtureScanGate):
return # already installed (pytest_configure re-entered in-process)
if scan is None or register is None:
# Both seams are private to their packages. A version that renamed either
# must not turn into a crash before collection; it turns into the slow
# collection this gate exists to remove, said out loud so the pin is revisited.
warnings.warn(
"pytest-asyncio fixture-scan gate not installed: a private seam moved "
"(pytest_asyncio.plugin._preprocess_async_fixtures / "
"_pytest.fixtures.FixtureManager._register_fixture); collection will be slow",
RuntimeWarning,
stacklevel=2,
)
return
gate = _AsyncFixtureScanGate(scan)
@functools.wraps(register)
def _register_fixture(self, *args, **kwargs):
gate.mark_dirty()
return register(self, *args, **kwargs)
FixtureManager._register_fixture = _register_fixture
pa._preprocess_async_fixtures = gate
def _root_can_create_real_symlink() -> bool:
"""Probe real-link capability for tests collected outside ``test/`` too.
Ordinary Windows shells commonly lack ``SeCreateSymbolicLinkPrivilege``.
This is a capability probe, not an OS guess: Developer Mode/elevated Windows
runners retain the security coverage, while only the exact tests inventoried
in ``test/requires-real-symlinks.txt`` skip on an incapable host.
"""
with tempfile.TemporaryDirectory() as tmp:
target = os.path.join(tmp, "target")
os.mkdir(target)
try:
os.symlink(
target,
os.path.join(tmp, "link"),
target_is_directory=True,
)
except (OSError, NotImplementedError, AttributeError):
return False
return True
_ROOT_HAS_REAL_SYMLINKS = _root_can_create_real_symlink()
#: Service managers whose *mutating* subcommands reconfigure, start, or stop a
#: real service. Matched on BASENAME against every token of the argv, not just
#: ``argv[0]``, because in this codebase the interesting name is usually not
#: first: ``dev_fleet._run_cmd`` rewrites ``argv[0]`` to a trusted absolute path
#: and then routes the spawn through ``sandboxed_spawn_argv``, and
#: ``SystemdBackend.restart_detached`` invokes through ``systemd-run``, so the
#: real program ends up in the middle of the final argv behind a wrapper.
#:
#: Deliberately NOT here:
#:
#: * ``systemd-run`` — in this codebase it is not a service-control tool at all.
#: ``sandbox`` wraps essentially EVERY subprocess in
#: ``systemd-run --user --scope --slice=kirocrew-agents.slice -p MemoryMax=…``
#: to apply cgroup resource limits, so denying it would refuse an ordinary
#: ``git config`` spawn. Its one service-control use (``restart_detached``)
#: passes ``systemctl restart`` as the wrapped command, which this guard
#: catches on the inner token instead.
#: * ``sudo`` — a privilege prefix, not an action. Whether the spawn mutates
#: anything is decided by the command it wraps, and ``sudo systemctl restart``
#: is already caught on ``systemctl``.
_SERVICE_MANAGERS = frozenset({"systemctl", "launchctl", "schtasks", "schtasks.exe"})
#: Subcommands of the managers above that CHANGE host service state.
#:
#: The verb matters as much as the binary. ``systemctl show``, ``systemctl cat``
#: and ``systemctl is-active`` are read-only queries, and tests legitimately run
#: them through the sandbox to inspect the environment they are running in — a
#: guard keyed on the binary alone would fail those for no safety gain. Only the
#: verbs below actually write.
_MUTATING_VERBS = frozenset(
{
# systemctl
"start",
"stop",
"restart",
"try-restart",
"reload",
"reload-or-restart",
"try-reload-or-restart",
"daemon-reload",
"daemon-reexec",
"enable",
"disable",
"reenable",
"mask",
"unmask",
"preset",
"revert",
"set-property",
"set-environment",
"unset-environment",
"import-environment",
"edit",
"link",
"isolate",
"kill",
# launchctl
"load",
"unload",
"bootstrap",
"bootout",
"kickstart",
"remove",
"submit",
"setenv",
"unsetenv",
"attach",
# schtasks (Windows Task Scheduler). Verbs are `/Create`-style switches
# and case-insensitive on the command line, so they are matched lowercased.
"/create",
"/delete",
"/run",
"/end",
"/change",
}
)
#: Programs that have no read-only mode worth allowing in a test: any invocation
#: rewrites host policy.
_ALWAYS_REFUSED = frozenset({"apparmor_parser"})
#: Test modules permitted to really mutate host service state.
#:
#: EMPTY, and it should stay that way. Every suite that exercises these paths
#: today already stubs them — ``test_service.py`` patches
#: ``service.linux.subprocess.run`` / ``service.macos.subprocess.run``,
#: ``test_pod.py`` and ``test_pod_launchd.py`` stub at the module boundary, and
#: the ``dev_fleet`` make-live tests stub ``_run_cmd``. So this guard breaks no
#: existing test, and an addition here means a test is about to restart a real
#: service on whoever runs the suite. Same shape as ``_ALLOWED`` in
#: ``test/test_spawn_preexec_guard.py``: an entry needs a comment saying why the
#: host mutation is acceptable.
#:
#: The two entries below are the real-service end-to-end suites, and they are the
#: reason the guard also recognises the pod CLI (see ``_POD_MUTATING_VERBS``): a
#: test that drives ``kirocrew pod up`` through a CHILD interpreter reaches
#: ``systemctl start`` / ``launchctl bootstrap`` / ``schtasks /Create`` one
#: process removed, where the in-process manager check cannot see it. Both
#: suites self-skip unless an operator sets their own opt-in variable
#: (``KIROCREW_E2E_SCENARIOS`` / ``KIROCREW_E2E_POD_WINDOWS``), run against a
#: hermetic plane (their own root, env dir, port base and unit prefix), and tear
#: down every pod, the plane root and the plane's template unit in ``finally``.
_HOST_SERVICE_EXEC_ALLOWED_MODULES: frozenset[str] = frozenset(
{
# The pod scenario suite (nightly ``pod-scenarios``): boots one real pod
# per session on the ``kirocrew-e2e-pod`` plane and reclaims it.
"e2e.scenarios.test_cron_fire",
"e2e.scenarios.test_service_install_dry_run",
"e2e.scenarios.test_settings_save",
"e2e.scenarios.test_subagent_spawn",
"e2e.scenarios.test_wheel_install",
# The Windows pod boot canary: one real Task Scheduler task on a
# per-run plane, removed by ``pod down`` before the test returns.
"test_pod_windows_boot",
}
)
#: ``kirocrew pod`` verbs that create, start, stop or delete a host service
#: definition (a systemd unit, a launchd agent, a Task Scheduler task). The pod
#: CLI is how a test reaches a service manager without naming one, so a spawn of
#: ``kirocrew pod <verb>`` or ``python -m kiro_crew pod <verb>`` is refused for
#: every module not listed above. Read-only verbs (``ls``, ``status``, ``api``,
#: ``logs``) stay allowed.
_POD_MUTATING_VERBS = frozenset({"up", "down", "install", "prune", "restart"})
def _tokens(argv: object, *, shell: bool = False) -> list[str]:
"""Normalise every spawn-API argv shape into a list of string tokens.
Accepts a string (shell form, or a lone program), a ``PathLike``, or a
sequence of either. Anything uninterpretable yields no tokens: this guard
refuses on a POSITIVE match only, so an exotic argv shape can never turn into
a spurious failure in an unrelated suite.
"""
if isinstance(argv, (str, bytes, os.PathLike)):
raw = os.fsdecode(argv)
return raw.split() if shell else [raw]
if isinstance(argv, (list, tuple)):
out = []
for item in argv:
if isinstance(item, (str, bytes, os.PathLike)):
out.append(os.fsdecode(item))
return out
return []
def _basename(token: str) -> str:
# PurePath handles both separators, so a Windows C:\...\sc.exe form and a
# POSIX /usr/bin/systemctl normalise the same way.
return pathlib.PurePath(token.replace("\\", "/")).name
def _refusal_reason(argv: object, *, shell: bool = False) -> str | None:
"""Describe why *argv* mutates host service state, or ``None`` if it does not.
A service manager alone is not enough — the argv must also carry a mutating
verb AFTER the manager token. Scanning only the tail keeps a unit named after
a verb, or a wrapper flag, from being read as the action.
"""
tokens = _tokens(argv, shell=shell)
for index, token in enumerate(tokens):
name = _basename(token)
if name in _ALWAYS_REFUSED:
return f"{name!r} rewrites host security policy"
if name not in _SERVICE_MANAGERS:
continue
for candidate in tokens[index + 1 :]:
# `schtasks` verbs are `/Create`-style switches: `_basename` would
# strip the slash, so the raw token is compared lowercased as well.
if _basename(candidate) in _MUTATING_VERBS or candidate.lower() in _MUTATING_VERBS:
return f"{name} {candidate!r} changes host service state"
pod_reason = _pod_cli_refusal(tokens)
if pod_reason:
return pod_reason
return None
def _pod_cli_refusal(tokens: list[str]) -> str | None:
"""Name the ``kirocrew pod`` mutation in *tokens*, or ``None``.
Matches the console script (``kirocrew``, ``kirocrew.exe``) and the module
form (``python -m kiro_crew``), then requires the literal ``pod`` subcommand
followed by a verb from :data:`_POD_MUTATING_VERBS`. Anything looser would
refuse a test that merely passes ``"pod"`` as an argument to something else.
"""
for index, token in enumerate(tokens):
name = _basename(token)
if name in {"kirocrew", "kirocrew.exe"}:
rest = tokens[index + 1 :]
elif token == "-m" and index + 1 < len(tokens) and tokens[index + 1] == "kiro_crew":
rest = tokens[index + 2 :]
else:
continue
if len(rest) >= 2 and rest[0] == "pod" and rest[1] in _POD_MUTATING_VERBS:
return f"kirocrew pod {rest[1]!r} changes host service state"
return None
return None
def _refuse(reason: str, argv: object) -> None:
"""Fail the test with the stub it is missing, not just 'permission denied'."""
raise AssertionError(
f"Test tried to run a command that mutates host service state: {reason} "
f"(see issue #1722).\n"
f" argv: {argv!r}\n"
f"This spawn must be stubbed. Depending on the code under test:\n"
f" - dev_fleet make-live: stub BOTH `_run_cmd` and `_dropin_path`\n"
f" - kiro_crew.service.*: patch `service.<platform>.subprocess.run`\n"
f" - pod runtime: stub the runtime's `systemctl` / `launchctl` helper\n"
f"Read-only queries (`systemctl show`, `cat`, `is-active`) are allowed and "
f"need no stub.\n"
f"If this test genuinely must drive a real service, add its module to "
f"_HOST_SERVICE_EXEC_ALLOWED_MODULES in the root conftest.py with a "
f"comment explaining why."
)
@pytest.fixture(scope="session")
def _xdg_config_root(tmp_path_factory):
"""One tmp dir per session (per xdist worker) to stand in for ``~/.config``.
Session-scoped so the redirect below costs one ``mkdir`` for the whole run
rather than one per test: nothing here is written by a passing test — the
point of the guard is that these writes should not happen at all — so a
per-test directory would isolate nothing and add a syscall to every test.
"""
return tmp_path_factory.mktemp("xdg")
@pytest.fixture
def _floor_monkeypatch():
"""The isolation floor's OWN ``MonkeyPatch`` -- never the shared ``monkeypatch``.
Every floor fixture below patches through this instance instead of the
``monkeypatch`` fixture a test also receives. The two are undone
independently, and that is the point: ``monkeypatch.undo()`` reverts EVERY
record on the instance it is called on, so a test that called it mid-way to
drop one of its own patches -- ~90 sites do, to restore a function before a
final assertion -- also dropped the floor's ``KIROCREW_HOME`` pin, and
whatever ran after that resolved the OPERATOR's real data home. Five full
runs on a developer machine left ``~/.kiro/crew/trash/session-storage.lock``
(``test_session_storage`` ran ``empty_trash()`` against the real trash) and
``~/.kiro/crew/inbound-spool/refused.jsonl.lock`` behind this way.
A test that pins something itself still wins: its ``monkeypatch`` is set up
after the floor and torn down before it, so its value overrides during the
test and the floor's value is what gets put back first, then the original.
"""
mp = pytest.MonkeyPatch()
# In-process CLI calls clear the sandbox markers and publish the UTF-8
# process contract. Preserve the worker's exact outer environment for the
# next test, including absent and explicitly empty values.
cli_process_environment = {
name: os.environ.get(name)
for name in (
"KIROCREW_SANDBOX_ACTIVE",
"KIROCREW_SANDBOX_LEVEL",
"PYTHONUTF8",
"PYTHONIOENCODING",
)
}
try:
yield mp
finally:
mp.undo()
for name, value in cli_process_environment.items():
if value is None:
os.environ.pop(name, None)
else:
os.environ[name] = value
@pytest.fixture
def monkeypatch(_floor_monkeypatch):
"""pytest's own ``monkeypatch``, re-declared to order it AFTER the floor.
Body-identical to ``_pytest.monkeypatch.monkeypatch``; the only change is the
dependency on ``_floor_monkeypatch``, which is what GUARANTEES the order the
docstring above relies on: the floor is set up before the test's ``monkeypatch``
and torn down after it, whatever autouse fixture in whatever conftest happens to
request ``monkeypatch`` first. A test's ``setenv("KIROCREW_HOME", ...)`` therefore
records the pinned value, restores it on the test's undo, and the floor's undo then
restores the operator's. A test cannot tell the difference otherwise: same type,
same scope, same undo semantics for everything IT patched.
``test_host_isolation_floor.py::TestTheDataHomeIsPinnedForEveryTestpath`` pins it.
"""
mp = pytest.MonkeyPatch()
yield mp
mp.undo()
@pytest.fixture(autouse=True)
def _isolate_xdg_config_home(_xdg_config_root, _floor_monkeypatch):
"""Point ``$XDG_CONFIG_HOME`` at a tmp dir so no test writes a real unit file.
``dev_fleet._dropin_path()`` resolves the make-live systemd drop-in as
``$XDG_CONFIG_HOME/systemd/user/kirocrew-gateway.service.d/make-live.conf``,
falling back to ``~/.config`` when the variable is unset — which is the
default on most developer machines and in CI. So a test that reaches the
cutover path without stubbing ``_dropin_path`` writes the operator's real
drop-in. That is what took the gateway down in #1722.
Redirecting the variable fixes the whole class rather than one call site,
because the production code already honours it (its docstring notes a literal
``~/.config`` would be the wrong directory on a host that sets XDG). It is
also not dev-fleet-specific: ``pptx_maker/backend/paths.py`` resolves against
the same variable, so its tests stop touching the real config dir too.
A test that wants its own value still wins — it sets XDG later, and reverts
independently.
"""
monkeypatch = _floor_monkeypatch
monkeypatch.setenv("XDG_CONFIG_HOME", str(_xdg_config_root))
@pytest.fixture(autouse=True)
def _isolate_launchd_paths(_xdg_config_root, _floor_monkeypatch):
"""Pin the launchd install paths, which ``$XDG_CONFIG_HOME`` cannot reach.
The macOS half of Make Live does not resolve through XDG at all. Its paths are
module globals bound at IMPORT time from ``Path.home()``::
PLIST_DIR = ~/Library/LaunchAgents
PLIST_PATH = PLIST_DIR / "dev.kirocrew.gateway.plist"
LOG_DIR = ~/Library/Logs/... (+ STDOUT_LOG, STDERR_LOG)
LIVE_PROGRAM = launchd_live_program() (under ~/Library/Application Support)
That is the same import-time-binding class the suite already documents (#874):
an env var read *after* the module captured the path changes nothing, so the
redirect above leaves the launchd side wide open.
It is reachable today, not hypothetically. ``macos.install()`` calls
``write_live_program(render_live_program(kirocrew_bin()))`` with no path
argument, so the launcher lands on the real ``LIVE_PROGRAM`` even in a test that
carefully pinned every ``PLIST_*`` constant — which
``test_install_writes_plist_and_loads`` does. Raised in review of #1722.
Every binding is patched rather than just the canonical one, because both
consumers import by value: ``dev_fleet.gateway_service`` holds its own
``PLIST_PATH`` and its own ``launchd_live_program`` reference, and
``LaunchdBackend.live_program()`` calls that function fresh on each use instead
of reading the constant.
``gateway_service`` is patched only when it is already imported. It is a heavy
module and forcing it in for all ~31k tests would cost more than it protects; a
test that imports it later binds from the already-patched ``service.macos``, so
it inherits the tmp paths anyway.
Tolerant by design: an unimportable module is skipped rather than failing
collection, and every attribute uses ``raising=False`` so a renamed constant
does not become a suite-wide error.
"""
monkeypatch = _floor_monkeypatch
root = pathlib.Path(_xdg_config_root) / "launchd"
launcher = root / "live-gateway"
plist_dir = root / "LaunchAgents"
plist_path = plist_dir / "dev.kirocrew.gateway.plist"
log_dir = root / "Logs"
eager: dict[str, dict[str, object]] = {
"kiro_crew.service.macos": {
"PLIST_DIR": plist_dir,
"PLIST_PATH": plist_path,
"LOG_DIR": log_dir,
"STDOUT_LOG": log_dir / "gateway.log",
"STDERR_LOG": log_dir / "gateway.err",
"LIVE_PROGRAM": launcher,
},
"kiro_crew.service.common": {"launchd_live_program": lambda: launcher},
}
lazy: dict[str, dict[str, object]] = {
"kiro_crew.apps.builtins.dev_fleet.gateway_service": {
"PLIST_PATH": plist_path,
"launchd_live_program": lambda: launcher,
},
}
for name, attrs in eager.items():
try:
imported = importlib.import_module(name)
except Exception: # pragma: no cover - a partial checkout must not break collection
continue
for attr, value in attrs.items():
monkeypatch.setattr(imported, attr, value, raising=False)
for name, attrs in lazy.items():
already = sys.modules.get(name)
if already is None:
continue
for attr, value in attrs.items():
monkeypatch.setattr(already, attr, value, raising=False)
#: Originals of the sandbox-sweep functions the host-isolation floor patches,
#: stashed here (conftest-owned) rather than as attributes on the production
#: module. Tests reach them via the ``sandbox_sweep_original`` fixture — a
#: fixture rather than an importable name, because ``import conftest`` from a
#: test resolves to ``test/conftest.py``, not this rootdir file.
_SANDBOX_SWEEP_ORIGINALS: dict = {}
@pytest.fixture()
def sandbox_sweep_original():
"""Accessor for the pre-patch sandbox-sweep functions stashed by the floor."""
return _SANDBOX_SWEEP_ORIGINALS.__getitem__
@pytest.fixture(scope="session")
def _sandbox_mount_source_root(tmp_path_factory):
"""An empty stand-in tmpfs root for the sandbox mount-source sweep.
Session-scoped and owned by no individual test, because nothing ever writes
into it -- it exists only so the sweep has a harmless directory to scan.
"""
return tmp_path_factory.mktemp("sb-mount-src")
@pytest.fixture(autouse=True)
def _isolate_sandbox_mount_source_roots(_sandbox_mount_source_root, _floor_monkeypatch):
"""Point the sandbox mount-source sweep away from the host's real tmpfs.
``sandbox._cleanup_stale_sandbox_mount_sources()`` -- reached from every
``cleanup_stale_sandbox_profiles()`` call, including the periodic sweep in
``session.py`` -- resolves its candidate roots from the REAL host:
``/run/user/$UID``, ``/dev/shm``, and the system tempdir. The tempdir is
already redirected by the floor above, but the first two are the operator's
live runtime tmpfs, so an unpinned test would (a) delete real orphaned
bind-mount sources from the developer's machine -- a host mutation, however
garbage-shaped -- and (b) have its removal COUNT inflated by whatever real
orphans the host happens to carry, turning exact-count assertions into
host-state-dependent flakes.
A test that wants the sweep's real behaviour passes ``roots=`` explicitly or
monkeypatches ``_mount_source_candidate_roots`` itself (a later patch wins
and reverts independently); the real function stays reachable through this
conftest's ``sandbox_sweep_original`` accessor so its resolution logic
remains testable without mutating the production module.
Eagerly imported -- ``sandbox`` is a low-level dependency most of the suite
already pulls in, so this costs nothing and a lazy patch would leave the
first importer unprotected -- but tolerant of a partial checkout: an
unimportable module is skipped rather than failing collection. The setattr
itself is STRICT: a silent miss on a renamed attribute would revert the
whole suite to sweeping the operator's real tmpfs, which is exactly what
this fixture exists to prevent.
"""
monkeypatch = _floor_monkeypatch
try:
sandbox_mod = importlib.import_module("kiro_crew.sandbox")
except Exception: # pragma: no cover - a partial checkout must not break collection
return
_SANDBOX_SWEEP_ORIGINALS.setdefault(
"_mount_source_candidate_roots", sandbox_mod._mount_source_candidate_roots
)
monkeypatch.setattr(
sandbox_mod,
"_mount_source_candidate_roots",
lambda: [str(_sandbox_mount_source_root)],
)
# Second half of the same floor: the pin scan reads the operator's real